✨ The modern, all-batteries-included GitHub SDK for Python ✨
✨ Support both sync and async calls, fully typed ✨
✨ Always up to date, like octokit ✨
pip install githubkit
# or, use poetry
poetry add githubkit
# or, use pdm
pdm add githubkit
if you want to auth as github app, extra dependencies are required:
pip install githubkit[auth-app]
# or, use poetry
poetry add githubkit[auth-app]
# or, use pdm
pdm add githubkit[auth-app]
if you want to mix sync and async calls in oauth device callback, extra dependencies are required:
pip install githubkit[auth-oauth-device]
# or, use poetry
poetry add githubkit[auth-oauth-device]
# or, use pdm
pdm add githubkit[auth-oauth-device]
githubkit supports both pydantic v1 and v2, but pydantic v2 is recommended. If you have encountered any problems with pydantic v1/v2, please file an issue.
Warning
githubkit uses GitHub's official openapi schema to generate apis and models. You may occasionally encounter breaking changes like model names or model field types changing when upgrading githubkit. This is due to upstream schema changes and githubkit can not control this.
Here is some common use cases to help you get started quickly. For more detailed usage, please refer to the Usage section.
from githubkit import GitHub
from githubkit.versions.latest.models import User
github = GitHub("<your_token_here>")
# call GitHub rest api
resp = github.rest.users.get_authenticated()
user: User = resp.parsed_data
# call GitHub graphql api
data: dict = github.graphql("{ viewer { login } }")
from githubkit.versions.latest.models import User
from githubkit import GitHub, OAuthAppAuthStrategy, OAuthTokenAuthStrategy
github = GitHub(OAuthAppAuthStrategy("<client_id>", "<client_secret>"))
# redirect user to github oauth page and get the code from callback
# one time usage
user_github = github.with_auth(github.auth.as_web_user("<code>"))
# or, store the user token in a database
auth: OAuthTokenAuthStrategy = github.auth.as_web_user("<code>").exchange_token(github)
access_token = auth.token
refresh_token = auth.refresh_token
# restore the user token from database
user_github = github.with_auth(
OAuthTokenAuthStrategy(
"<client_id>", "<client_secret>", refresh_token=refresh_token
)
)
# now you can act as the user
resp = user_github.rest.users.get_authenticated()
user: User = resp.parsed_data
from githubkit.versions.latest.models import User
from githubkit import GitHub, OAuthDeviceAuthStrategy, OAuthTokenAuthStrategy
# sync/async func for displaying user code to user
def callback(data: dict):
print(data["user_code"])
user_github = GitHub(OAuthDeviceAuthStrategy("<client_id>", callback))
# if you want to store the user token in a database
auth: OAuthTokenAuthStrategy = user_github.auth.exchange_token(user_github)
access_token = auth.token
refresh_token = auth.refresh_token
# restore the user token from database
user_github = user_github.with_auth(
OAuthTokenAuthStrategy(
"<client_id>", None, refresh_token=refresh_token
)
)
Authenticating as a repository installation to do something with the repository:
from githubkit import GitHub, AppAuthStrategy
from githubkit.versions.latest.models import Issue, Installation
github = GitHub(
AppAuthStrategy("your_app_id", "your_private_key", "client_id", "client_secret")
)
resp = github.rest.apps.get_repo_installation("owner", "repo")
repo_installation: Installation = resp.parsed_data
installation_github = github.with_auth(
github.auth.as_installation(repo_installation.id)
)
resp = installation_github.rest.issues.get("owner", "repo", 1)
issue: Issue = resp.parsed_data
Authenticating as a user installation to do something on behalf of the user:
from githubkit import GitHub, AppAuthStrategy
from githubkit.versions.latest.models import Installation, IssueComment
github = GitHub(
AppAuthStrategy("your_app_id", "your_private_key", "client_id", "client_secret")
)
resp = github.rest.apps.get_user_installation("username")
user_installation: Installation = resp.parsed_data
installation_github = github.with_auth(
github.auth.as_installation(user_installation.id)
)
resp = installation_github.rest.issues.create_comment("owner", "repo", 1, body="Hello")
issue: IssueComment = resp.parsed_data
Initialize a github client with no authentication:
from githubkit import GitHub, UnauthAuthStrategy
github = GitHub()
# or, use UnauthAuthStrategy
github = GitHub(UnauthAuthStrategy())
or using PAT (Token):
from githubkit import GitHub, TokenAuthStrategy
github = GitHub("<your_token_here>")
# or, use TokenAuthStrategy
github = GitHub(TokenAuthStrategy("<your_token_here>"))
or using GitHub APP authentication:
from githubkit import GitHub, AppAuthStrategy
github = GitHub(
AppAuthStrategy(
"<app_id>", "<private_key>", "<optional_client_id>", "<optional_client_secret>"
)
)
or using GitHub APP Installation authentication:
from githubkit import GitHub, AppInstallationAuthStrategy
github = GitHub(
AppInstallationAuthStrategy(
"<app_id>", "<private_key>", installation_id, "<optional_client_id>", "<optional_client_secret>",
)
)
or using OAuth APP authentication:
from githubkit import GitHub, OAuthAppAuthStrategy
github = GitHub(OAuthAppAuthStrategy("<client_id_here>", "<client_secret_here>"))
or using GitHub APP / OAuth APP token authentication (This is usefull when you stored the user token in a database):
from githubkit import GitHub, OAuthTokenAuthStrategy
github = GitHub(
OAuthTokenAuthStrategy(
"<client_id_here>",
"<client_secret_here>",
"<access_token_here>",
"<access_token_expire_time_here>",
"<refresh_token_here>",
"<refresh_token_expire_time_here>",
)
)
or using GitHub APP / OAuth APP web flow authentication:
from githubkit import GitHub, OAuthWebAuthStrategy
github = GitHub(
OAuthWebAuthStrategy(
"<client_id_here>", "<client_secret_here>", "<web_flow_exchange_code_here>"
)
)
or using GitHub APP / OAuth APP device flow authentication:
from githubkit import GitHub, OAuthDeviceAuthStrategy
# sync/async func for displaying user code to user
def callback(data: dict):
print(data["user_code"])
github = GitHub(
OAuthDeviceAuthStrategy(
"<client_id_here>", callback
)
)
See Switch between AuthStrategy for more detail about oauth flow.
or using GitHub Action authentication:
from githubkit import GitHub, ActionAuthStrategy
github = GitHub(ActionAuthStrategy())
and add env or input to the step:
- name: Some step use githubkit
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Some step use githubkit
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
githubkit is highly configurable, you can change the default config by passing config options to GitHub
:
from githubkit import GitHub
github = GitHub(
base_url="https://api.github.com/",
accept_format="full+json",
previews=["starfox"],
user_agent="GitHubKit/Python",
follow_redirects=True,
timeout=None,
http_cache=True,
auto_retry=True,
)
The accept_format
and previews
are used to set the default Accept
header, you can find more details in GitHub API docs.
The http_cache
option enables the http caching feature powered by Hishel for HTTPX. GitHub API limits the number of requests that you can make within a specific amount of time. This feature is useful to reduce the number of requests to GitHub API and avoid hitting the rate limit.
The auto_retry
option enables request retrying when rate limit exceeded and server error encountered. See Auto Retry for more infomation.
APIs are fully typed. Typing in the following examples is just for reference only.
Simple sync call:
from githubkit import Response
from githubkit.versions.latest.models import FullRepository
resp: Response[FullRepository] = github.rest.repos.get(owner="owner", repo="repo")
repo: FullRepository = resp.parsed_data
Simple async call:
from githubkit import Response
from githubkit.versions.latest.models import FullRepository
resp: Response[FullRepository] = await github.rest.repos.async_get(owner="owner", repo="repo")
repo: FullRepository = resp.parsed_data
Call API with context (reusing client):
from githubkit import Response
from githubkit.versions.latest.models import FullRepository
with GitHub("<your_token_here>") as github:
resp: Response[FullRepository] = github.rest.repos.get(owner="owner", repo="repo")
repo: FullRepository = resp.parsed_data
from githubkit import Response
from githubkit.versions.latest.models import FullRepository
async with GitHub("<your_token_here>") as github:
resp: Response[FullRepository] = await github.rest.repos.async_get(owner="owner", repo="repo")
repo: FullRepository = resp.parsed_data
Warning
Note that you should hold a strong reference to the githubkit client instance. Otherwise, githubkit client will fail to call the request. For example, you should not do this:
from githubkit import GitHub
def get_client() -> GitHub:
return GitHub()
# This will cause error
get_client().rest.repos.get("owner", "repo")
# This is ok
client = get_client()
client.rest.repos.get("owner", "repo")
As shown above, the response data is parsed and validated by accessing the response.parsed_data
property. This ensures that the data type returned by the API is as expected and your code is safe to use it (with static type checking). But sometimes you may want to get the raw data returned by the API, such as when the schema is not correct. You can use the response.text
property or response.json()
method to get the raw data:
from typing import Any, Dict
from githubkit import Response
resp: Response[FullRepository] = github.rest.repos.get(owner="owner", repo="repo")
repo: Dict[str, Any] = resp.json()
APIs are fully typed. Different versions of APIs are typed separately.
githubkit supports all versions of GitHub API, you can switch between versions as follows:
github.rest("2022-11-28").repos.get(owner="owner", repo="repo")
The models of versions can be imported from githubkit.versions.<version>.models
, for example:
from githubkit.versions.v2022_11_28.models import FullRepository
Specially, the latest
version is always linked to the latest version of GitHub API:
from githubkit.versions.latest.models import FullRepository
Note
For backward compatibility, the githubkit.rest
module is linked to the models of latest
version by default.
from githubkit.rest import FullRepository
You can also get the latest version name of GitHub API and all versions mapping of GitHub API:
from githubkit.versions import LATEST_VERSION, VERSIONS
Current supported versions are: (you can find it in the section [[tool.codegen.descriptions]]
of the pyproject.toml
file)
- 2022-11-28 (latest)
- ghec-2022-11-28
Pagination type checking is also supported:
Typing is tested with Pylance (Pyright).
from githubkit.versions.latest.models import Issue
for issue in github.paginate(
github.rest.issues.list_for_repo, owner="owner", repo="repo", state="open"
):
issue: Issue
print(issue.number)
from githubkit.versions.latest.models import Issue
async for issue in github.paginate(
github.rest.issues.async_list_for_repo, owner="owner", repo="repo", state="open"
):
issue: Issue
print(issue.number)
complex pagination with custom map function (some api returns data in a nested field):
async for accessible_repo in github.paginate(
github.rest.apps.async_list_installation_repos_for_authenticated_user,
map_func=lambda r: r.parsed_data.repositories,
installation_id=1,
):
accessible_repo: Repository
print(accessible_repo.full_name)
Simple sync call:
data: Dict[str, Any] = github.graphql(query, variables={"foo": "bar"})
Simple async call:
data: Dict[str, Any] = await github.async_graphql(query, variables={"foo": "bar"})
By default, githubkit will retry the request when specific exception encountered. When rate limit exceeded, githubkit will retry once after GitHub suggested waiting time. When server error encountered (http status >= 500), githubkit will retry max three times.
You can disable this feature by set the auto_retry
config to False
:
github = GitHub(
...
auto_retry=False
)
You can also customize the retry decision function by passing a callable:
from datetime import timedelta
from githubkit.retry import RetryOption
from githubkit.exception import GitHubException
def retry_decision_func(exc: GitHubException, retry_count: int) -> RetryOption:
if retry_count < 1:
return RetryOption(True, timedelta(seconds=60))
return RetryOption(False)
github = GitHub(
...
auto_retry=retry_decision_func
)
githubkit also provides some builtin retry decision function:
-
Retry when rate limit exceeded:
from githubkit.retry import RETRY_RATE_LIMIT, RetryRateLimit # default github = GitHub( ... auto_retry=RETRY_RATE_LIMIT ) # or, custom max retry count github = GitHub( ... auto_retry=RetryRateLimit(max_retry=1) )
-
Retry when server error encountered:
from githubkit.retry import RETRY_SERVER_ERROR, RetryServerError # default github = GitHub( ... auto_retry=RETRY_SERVER_ERROR ) # or, custom max retry count github = GitHub( ... auto_retry=RetryServerError(max_retry=1) )
-
Chain retry decision functions:
from githubkit.retry import RETRY_RATE_LIMIT, RETRY_SERVER_ERROR, RetryChainDecision github = GitHub( ... auto_retry=RetryChainDecision(RETRY_RATE_LIMIT, RETRY_SERVER_ERROR) )
githubkit.webhooks
module contains some shortcut functions to help you verify and parse webhook payload.
Simple webhook payload verification:
from githubkit.webhooks import verify
valid: bool = verify(secret, request.body, request.headers["X-Hub-Signature-256"])
Sign the webhook payload manually:
from githubkit.webhooks import sign
signature: str = sign(secret, payload, method="sha256")
githubkit.webhooks
module contains some shortcut functions to help you verify and parse webhook payload.
Parse the payload with event name:
from githubkit.webhooks import parse
event = parse(request.headers["X-GitHub-Event"], request.body)
(NOT RECOMMENDED) Parse the payload without event name (may cost longer time and more memory):
from githubkit.webhooks import parse_without_name
event = parse_without_name(request.body)
Warning
The parse_without_name
function will try to parse the payload with all supported event names.
The behavior of this function is not the same between pydantic v1 and v2.
When using pydantic v1, the function will return the first valid event model (known as left-to-right
mode).
When using pydantic v2, the function will return the highest scored valid event model (known as smart
mode).
See: Union Modes.
Parse dict like payload:
from githubkit.webhooks import parse_obj, parse_obj_without_name
event = parse_obj(request.headers["X-GitHub-Event"], request.json())
event = parse_obj_without_name(request.json()) # NOT RECOMMENDED
The parse
and parse_obj
function supports type overload, if you provide static value for the event_name
parameter, the return type will be inferred automatically.
Webhook also supports versioning, you can switch between versions as follows:
from githubkit import GitHub
event = GitHub.webhooks("2022-11-28").parse(request.headers["X-GitHub-Event"], request.body)
You can change the auth strategy and get a new client simplely using with_auth
.
Change from AppAuthStrategy
to AppInstallationAuthStrategy
:
from githubkit import GitHub, AppAuthStrategy
github = GitHub(AppAuthStrategy("<app_id>", "<private_key>"))
installation_github = github.with_auth(
github.auth.as_installation(installation_id)
)
Change from OAuthAppAuthStrategy
to OAuthWebAuthStrategy
(OAuth Web Flow):
from githubkit import GitHub, OAuthAppAuthStrategy
github = GitHub(OAuthAppAuthStrategy("<client_id>", "<client_secret>"))
user_github = github.with_auth(github.auth.as_web_user("<code>"))
# now you can act as the user
resp = user_github.rest.users.get_authenticated()
user = resp.parsed_data
# you can get the user token after you maked a request as user
user_token = user_github.auth.token
user_token_expire_time = user_github.auth.expire_time
refresh_token = user_github.auth.refresh_token
refresh_token_expire_time = user_github.auth.refresh_token_expire_time
you can also get the user token directly without making a request (Change from OAuthWebAuthStrategy
to OAuthTokenAuthStrategy
):
auth: OAuthTokenAuthStrategy = github.auth.as_web_user("<code>").exchange_token(github)
# or asynchronously
auth: OAuthTokenAuthStrategy = await github.auth.as_web_user("<code>").async_exchange_token(github)
user_token = auth.token
user_token_expire_time = auth.expire_time
refresh_token = auth.refresh_token
refresh_token_expire_time = auth.refresh_token_expire_time
user_github = github.with_auth(auth)
Change from OAuthDeviceAuthStrategy
to OAuthTokenAuthStrategy
:
from githubkit import GitHub, OAuthDeviceAuthStrategy
def callback(data: dict):
print(data["user_code"])
user_github = GitHub(OAuthDeviceAuthStrategy("<client_id>", callback))
# now you can act as the user
resp = user_github.rest.users.get_authenticated()
user = resp.parsed_data
# you can get the user token after you maked a request as user
user_token = user_github.auth.token
user_token_expire_time = user_github.auth.expire_time
refresh_token = user_github.auth.refresh_token
refresh_token_expire_time = user_github.auth.refresh_token_expire_time
# you can also exchange the token directly without making a request
auth: OAuthTokenAuthStrategy = github.auth.exchange_token(github)
# or asynchronously
auth: OAuthTokenAuthStrategy = await github.auth.async_exchange_token(github)
user_token = auth.token
user_token_expire_time = auth.expire_time
refresh_token = auth.refresh_token
refresh_token_expire_time = auth.refresh_token_expire_time
user_github = github.with_auth(auth)
Open in Codespaces (Dev Container):
Generate latest models and apis:
Warning
This may use about 400M memory and take a long time.
./scripts/run-codegen.sh
Run tests in dev env:
./scripts/run-tests.sh
Run tests in test envs, for example:
cd ./envs/pydantic-v2/
poetry run bash ../../scripts/run-tests.sh
Thanks to the following people who have contributed to this project: