diff --git a/docs/quickstart/index.md b/docs/quickstart/index.md index 8287179fd..dc30191fe 100644 --- a/docs/quickstart/index.md +++ b/docs/quickstart/index.md @@ -1,6 +1,6 @@ # Quick Start -Here are some common use cases to help you get started quickly. For more detailed usage, please refer to the [Usage](../usage/authentication.md) section. +Here are some common use cases to help you get started quickly. For more detailed usage, please refer to the [Usage](../usage/getting-started/authentication.md) section. !!! note diff --git a/docs/usage/authentication.md b/docs/usage/authentication.md deleted file mode 100644 index 8d282a5f0..000000000 --- a/docs/usage/authentication.md +++ /dev/null @@ -1,339 +0,0 @@ -# Authentication - -githubkit supports multiple authentication strategies to access the GitHub API. You can also switch between them easily when doing auth flow. - -## Without Authentication - -If you want to access GitHub API without authentication or make a temporary test, you can use `UnauthAuthStrategy`: - -```python -from githubkit import GitHub, UnauthAuthStrategy - -# simply omit the auth parameter -github = GitHub() -# or, use UnauthAuthStrategy -github = GitHub(UnauthAuthStrategy()) -``` - -## Token Authentication - -You can authenticate to GitHub using a [Personal Access Token (PAT)](https://github.com/settings/personal-access-tokens/new). Replace `` with your token: - -```python -from githubkit import GitHub, TokenAuthStrategy - -# simply pass the token as a string -github = GitHub("") -# or, use TokenAuthStrategy -github = GitHub(TokenAuthStrategy("")) -``` - -## GitHub APP Authentication - -If you are developing a GitHub App, you can authenticate using the GitHub App's private key and app ID. See [GitHub Docs - Authenticating as a GitHub App](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app) for more information. - -If you do not want to use OAuth features, you can omit the client ID and client secret. Replace ``, ``, ``, and `` with your app's credentials: - -```python -from githubkit import GitHub, AppAuthStrategy - -github = GitHub( - AppAuthStrategy( - "", "", "", "" - ) -) -``` - -GitHub currently supports authentication using a GitHub App's private key and client ID. So, you could provide either the app ID or the client ID. - -```python -from githubkit import GitHub, AppAuthStrategy - -github = GitHub( - AppAuthStrategy( - None, "", "", "" - ) -) -``` - -## GitHub APP Installation Authentication - -GitHub Ap installation allows the app to access resources owned by that installation, as long as the app was granted the necessary repository access and permissions. API requests made by an app installation are attributed to the app. For more information, see [GitHub Docs - Authenticating as an installation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation). - -```python -from githubkit import GitHub, AppInstallationAuthStrategy - -github = GitHub( - AppInstallationAuthStrategy( - "", "", installation_id, "", "", - ) -) -``` - -App installation authentication also supports using the client ID instead of the app ID: - -```python -from githubkit import GitHub, AppInstallationAuthStrategy - -github = GitHub( - AppInstallationAuthStrategy( - None, "", installation_id, "", "", - ) -) -``` - -## OAuth APP Authentication - -If you are developing an OAuth App, you can authenticate using the client ID and client secret. See [GitHub Docs - Authenticating with an OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authenticating-to-the-rest-api-with-an-oauth-app) for more information. - -```python -from githubkit import GitHub, OAuthAppAuthStrategy - -github = GitHub(OAuthAppAuthStrategy("", "")) -``` - -## OAuth User Token Authentication - -OAuth App (GitHub App) allows you to act on behalf of a user after the user authorized your app. You can authenticate using the user token. This auth strategy is usefull when you stored the user token in a database. - -If you are developing an OAuth App or a GitHub App without user-to-server token expiration, you can authenticate using the user token directly: - -```python -from githubkit import GitHub, OAuthTokenAuthStrategy - -github = GitHub( - OAuthTokenAuthStrategy( - "", "", token="" - ) -) -``` - -If you are developing a GitHub App with user-to-server token expiration, you need to provide the refresh token to keep the token up-to-date. In this case, you can omit the access token, but if you provide the access token, the access token expiration time is required: - -```python -from datetime import datetime - -from githubkit import GitHub, OAuthTokenAuthStrategy - -github = GitHub( - OAuthTokenAuthStrategy( - "", - "", - token="", # optional - expire_time=datetime(), # optional - refresh_token="", - refresh_token_expire_time=datetime(), # optional - ) -) -``` - -If you want to refresh the access token immediately, you can normally call the `refresh` method: - -```python -# sync -auth = github.auth.refresh(github) -# async -auth = await github.auth.async_refresh(github) -``` - -This will update the access token and refresh token inplace. You can now store the new refresh token in `github.auth` to your database. - -## OAuth Web Flow Authentication - -GitHub OAuth web flow allows you to exchange the user access token with the web flow code. githubkit has built-in support for OAuth web flow token exchanging. See [GitHub Docs - Using the web application flow](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#using-the-web-application-flow-to-generate-a-user-access-token) for more information. - -```python -from githubkit import GitHub, OAuthWebAuthStrategy - -github = GitHub( - OAuthWebAuthStrategy( - "", "", "" - ) -) -``` - -Note that this auth strategy is only for one-time use. You need to store the user access token and refresh token in your database for future use: - -```python -from githubkit import GitHub, OAuthWebAuthStrategy, OAuthTokenAuthStrategy - -github = GitHub( - OAuthWebAuthStrategy( - "", "", "" - ) -) - -# sync -auth: OAuthTokenAuthStrategy = github.auth.exchange_token(github) -# async -auth: OAuthTokenAuthStrategy = await github.auth.async_exchange_token(github) - -access_token = auth.token -refresh_token = auth.refresh_token -``` - -See [Switch between AuthStrategy](#switch-between-authstrategy) for more detail about oauth flow. - -## OAuth Device Flow Authentication - -OAuth device flow allows you to authenticate as a user without a web browser (e.g., cli tools, desktop apps). githubkit has built-in support for OAuth device flow. See [GitHub Docs - Using the device flow](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#using-the-device-flow-to-generate-a-user-access-token) for more information. - -Before you start the device flow, you need to create a callback function to display the user code to the user. The callback function will be called when the user code is generated, and githubkit will poll the server until the user successfully authenticated or code expired. - -```python -from githubkit import GitHub, OAuthDeviceAuthStrategy - -# sync/async func for displaying user code to user -# the data dict is the generation response from the github server. -# see the link above for more fields in the response. -def callback(data: dict): - print(data["user_code"]) - -github = GitHub( - OAuthDeviceAuthStrategy( - "", callback - ) -) -``` - -Note that this auth strategy is only for one-time use too. You need to store the user access token and refresh token in your database for future use: - -```python -from githubkit import GitHub, OAuthDeviceAuthStrategy, OAuthTokenAuthStrategy - -github = GitHub( - OAuthDeviceAuthStrategy( - "", callback - ) -) - -# sync -auth: OAuthTokenAuthStrategy = github.auth.exchange_token(github) -# async -auth: OAuthTokenAuthStrategy = await github.auth.async_exchange_token(github) - -access_token = auth.token -refresh_token = auth.refresh_token -``` - -## GitHub Action Authentication - -githubkit provides a built-in auth strategy for GitHub Actions. You can use the `ActionAuthStrategy` to automatically authenticate to the GitHub API. - -```python -from githubkit import GitHub, ActionAuthStrategy - -github = GitHub(ActionAuthStrategy()) -``` - -and add input or env to the action step: - -```yaml -- name: Some step use githubkit - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - -- name: Some step use githubkit - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -``` - -## Switch between AuthStrategy - -You can change the auth strategy and get a new client simplely using `with_auth`. - -Switch from GitHub App to a specific installation: - -```python -from githubkit import GitHub, AppAuthStrategy - -github = GitHub(AppAuthStrategy("", "")) -installation_github = github.with_auth( - github.auth.as_installation(installation_id) -) -``` - -Switch from GitHub App to an OAuth App: - -```python -from githubkit import GitHub, AppAuthStrategy, OAuthAppAuthStrategy - -github = GitHub( - AppAuthStrategy( - "", "", "", "" - ) -) - -oauth_github = github.with_auth(github.auth.as_oauth_app()) -``` - -Switch from OAuth App to a web user authorization (OAuth Web Flow): - -```python -from githubkit import GitHub, OAuthAppAuthStrategy - -github = GitHub(OAuthAppAuthStrategy("", "")) -user_github = github.with_auth(github.auth.as_web_user("")) - -# 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 (Switch from `OAuthWebAuthStrategy` to `OAuthTokenAuthStrategy`): - -```python -from githubkit import GitHub, OAuthAppAuthStrategy, OAuthTokenAuthStrategy - -github = GitHub(OAuthAppAuthStrategy("", "")) - -auth: OAuthTokenAuthStrategy = github.auth.as_web_user("").exchange_token(github) -# or asynchronously -auth: OAuthTokenAuthStrategy = await github.auth.as_web_user("").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) -``` - -Switch from `OAuthDeviceAuthStrategy` to `OAuthTokenAuthStrategy`: - -```python -from githubkit import GitHub, OAuthDeviceAuthStrategy - -def callback(data: dict): - print(data["user_code"]) - -user_github = GitHub(OAuthDeviceAuthStrategy("", 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) -``` diff --git a/docs/usage/configuration.md b/docs/usage/configuration.md deleted file mode 100644 index 3630c4647..000000000 --- a/docs/usage/configuration.md +++ /dev/null @@ -1,184 +0,0 @@ -# Configuration - -githubkit is highly configurable, you can change the default config by passing config options to `GitHub`: - -```python -from githubkit import GitHub - -github = GitHub( - base_url="https://api.github.com/", - accept_format=None, - previews=None, - user_agent="GitHubKit/Python", - follow_redirects=True, - timeout=None, - ssl_verify=True, - cache_strategy=None, - http_cache=True, - throttler=None, - auto_retry=True, - rest_api_validate_body=True, -) -``` - -Or, you can pass the config object directly (not recommended): - -```python -import httpx -from githubkit import GitHub, Config -from githubkit.retry import RETRY_DEFAULT -from githubkit.cache import DEFAULT_CACHE_STRATEGY - -config = Config( - base_url="https://api.github.com/", - accept="application/vnd.github+json", - user_agent="GitHubKit/Python", - follow_redirects=True, - timeout=httpx.Timeout(None), - ssl_verify=True, - cache_strategy=DEFAULT_CACHE_STRATEGY, - http_cache=True, - throttler=None, - auto_retry=RETRY_DEFAULT, - rest_api_validate_body=True, -) - -github = GitHub(config=config) -``` - -## Options - -### `base_url` - -The `base_url` option is used to set the base URL of the GitHub API. If you are using GitHub Enterprise Server, you need to include the `/api/v3/` path in the base URL. - -### `accept_format`, `previews` - -The `accept_format` and `previews` are used to set the default `Accept` header. By default, githubkit uses `application/vnd.github+json`. You can find more details in [GitHub API docs](https://docs.github.com/en/rest/overview/media-types). - -The `accept_format` option could be set to `PARAM+json`, such as `raw+json`. - -The `previews` option could be set to a list of preview features, such as `["starfox"]`. You can find the preview feature names in the GitHub API docs. Note that you do not need to include the `-preview` suffix in the preview feature name. - -### `user_agent` - -The `user_agent` option is used to set the `User-Agent` header. By default, githubkit uses `GitHubKit/Python`. - -### `follow_redirects` - -The `follow_redirects` option is used to enable or disable the HTTP redirect following feature. By default, githubkit follows the redirects. - -### `timeout` - -The `timeout` option is used to set the request timeout. You can pass a float, `None` or `httpx.Timeout` to this field. By default, the requests will never timeout. See [Timeout](https://www.python-httpx.org/advanced/timeouts/) for more information. - -### `ssl_verify` - -The `ssl_verify` option is used to customize the SSL certificate verification. By default, githubkit enables the SSL certificate verification. If you want to disable the SSL certificate verification, you can set this option to `False`. Or you can provide a custom ssl context to this option. See [SSL](https://www.python-httpx.org/advanced/ssl/) for more information. - -### `trust_env` - -If `trust_env` is set to `True`, githubkit (httpx) will look for the environment variables to configure the proxy and SSL certificate verification. By default, this option is set to `True`. If you want to disable this feature, you can set this option to `False`. - -### `proxy` - -If you want to set a proxy for client programmatically, you can pass a proxy URL to the `proxy` option. See [httpx's proxies documentation](https://www.python-httpx.org/advanced/proxies/) for more information. - -### `transport`, `async_transport` - -These two options let you provide a custom [HTTPX transport](https://www.python-httpx.org/advanced/transports/) for the underlying HTTP client. - -They accept instances of the following types: - -- `httpx.BaseTransport` (sync transport) — pass via the `transport` option. -- `httpx.AsyncBaseTransport` (async transport) — pass via the `async_transport` option. - -When provided, githubkit will forward the transport to create the client. This is useful for: - -- providing a custom network implementation; -- injecting test-only transports (for example `httpx.MockTransport`) to stub responses in unit tests; -- using alternative transports provided by HTTPX or third parties. - -Note that if you provide a custom transport, proxy environment variables will have no effect. If you set the option to `None`, HTTPX will create the default transport. - -### `cache_strategy` - -The `cache_strategy` option defines how to cache the tokens or http responses. You can provide a githubkit built-in cache strategy or a custom one that implements the `BaseCacheStrategy` interface. By default, githubkit uses the `MemCacheStrategy` to cache the data in memory. - -Available built-in cache strategies: - -- `MemCacheStrategy`: Cache the data in memory. - - Normally, you do not need to specifically use this cache strategy. It is used by default. - - ```python - from githubkit.cache import DEFAULT_CACHE_STRATEGY, MemCacheStrategy - - # Use the default cache strategy - github = GitHub(cache_strategy=DEFAULT_CACHE_STRATEGY) - # Or you can initialize another MemCacheStrategy instance - # this will create a new cache instance and not share the cache with the global one - github = GitHub(cache_strategy=MemCacheStrategy()) - ``` - -- `RedisCacheStrategy`: Cache the data in Redis (Sync only). - - To cache the data in Redis (Sync only), you need to provide a redis client to the `RedisCacheStrategy`. For example: - - ```python - from redis import Redis - - github = GitHub( - cache_strategy=RedisCacheStrategy( - client=Redis(host="localhost", port=6379), prefix="githubkit:" - ) - ) - ``` - - The `prefix` option is used to set the key prefix in Redis. You should add a `:` at the end of the prefix if you want to use namespace like key format. Both `githubkit` and `hishel` will use the prefix to store the cache data. - - Note that using this sync only cache strategy will cause the `GitHub` instance to be sync only. - -- `AsyncRedisCacheStrategy`: Cache the data in Redis (Async only). - - To cache the data in Redis (Async only), you need to provide an async redis client to the `AsyncRedisCacheStrategy`. For example: - - ```python - from redis.asyncio import Redis - - github = GitHub( - cache_strategy=AsyncRedisCacheStrategy( - client=Redis(host="localhost", port=6379), prefix="githubkit:" - ) - ) - ``` - - The `prefix` option is used to set the key prefix in Redis. You should add a `:` at the end of the prefix if you want to use namespace like key format. Both `githubkit` and `hishel` will use the prefix to store the cache data. - - Note that using this async only cache strategy will cause the `GitHub` instance to be async only. - -### `http_cache` - -The `http_cache` option enables the http caching feature powered by [Hishel](https://hishel.com/) 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. - -### `throttler` - -The `throttler` option is used to control the request concurrency to avoid hitting the rate limit. You can provide a githubkit built-in throttler or a custom one that implements the `BaseThrottler` interface. By default, githubkit uses the `LocalThrottler` to control the request concurrency. - -Available built-in throttlers: - -- `LocalThrottler`: Control the request concurrency in the local process / event loop. - - ```python - from githubkit.throttling import LocalThrottler - - github = GitHub(throttler=LocalThrottler(100)) - ``` - -### `auto_retry` - -The `auto_retry` option enables request retrying when rate limit exceeded and server error encountered. See [Auto Retry](./auto-retry.md) for more infomation. - -### `rest_api_validate_body` - -The `rest_api_validate_body` option is used to enable or disable the rest API request body validation. By default, githubkit validates the input data against the GitHub API schema. If you do not want to validate the input data, you can set this option to `False`. diff --git a/docs/usage/getting-started/authentication.md b/docs/usage/getting-started/authentication.md new file mode 100644 index 000000000..d7498fda1 --- /dev/null +++ b/docs/usage/getting-started/authentication.md @@ -0,0 +1,374 @@ +# Authentication + +githubkit supports a variety of authentication strategies for the GitHub API. The following table summarizes all available strategies: + +| Strategy | Use Case | +| ------------------------------------------------------------------------ | ------------------------------ | +| [`UnauthAuthStrategy`](#without-authentication) | Public endpoints, quick tests | +| [`TokenAuthStrategy`](#token-authentication) | Personal Access Tokens (PAT) | +| [`AppAuthStrategy`](#github-app-authentication) | GitHub App (JWT) | +| [`AppInstallationAuthStrategy`](#github-app-installation-authentication) | GitHub App installation | +| [`OAuthAppAuthStrategy`](#oauth-app-authentication) | OAuth App (client credentials) | +| [`OAuthTokenAuthStrategy`](#oauth-user-token-authentication) | Stored user access tokens | +| [`OAuthWebAuthStrategy`](#oauth-web-flow-authentication) | OAuth web flow code exchange | +| [`OAuthDeviceAuthStrategy`](#oauth-device-flow-authentication) | OAuth device flow (CLI, IoT) | +| [`ActionAuthStrategy`](#github-action-authentication) | GitHub Actions workflows | + +All strategies are importable from the top-level `githubkit` package. + +## Without Authentication + +Access public GitHub API endpoints without credentials. Useful for quick tests, but subject to much lower [rate limits](https://docs.github.com/en/rest/rate-limit). + +```python +from githubkit import GitHub, UnauthAuthStrategy + +# Simply omit the auth parameter +github = GitHub() + +# Or explicitly use UnauthAuthStrategy +github = GitHub(UnauthAuthStrategy()) +``` + +## Token Authentication + +The simplest way to authenticate — pass a [Personal Access Token (PAT)](https://github.com/settings/personal-access-tokens/new) as a string. This works for both classic tokens and fine-grained PATs. + +```python +from githubkit import GitHub, TokenAuthStrategy + +# Shorthand: pass a token string directly +github = GitHub("") + +# Explicit: use TokenAuthStrategy +github = GitHub(TokenAuthStrategy("")) +``` + +!!! tip + + Store tokens in environment variables or a secrets manager — never hard-code them in source files. + +## GitHub App Authentication + +Authenticate **as a GitHub App** using a JSON Web Token (JWT) signed with the app's private key. This gives you access to app-level API endpoints (e.g., listing installations). See [GitHub Docs — Authenticating as a GitHub App](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app) for background. + +You can identify the app by **app ID** or **client ID** (GitHub supports both). The client ID and client secret are only required if you plan to use OAuth features (e.g., exchanging user tokens). + +```python +from githubkit import GitHub, AppAuthStrategy + +# Using app ID (client ID and secret are optional) +github = GitHub( + AppAuthStrategy( + "", "", "", "" + ) +) + +# Using client ID instead of app ID +github = GitHub( + AppAuthStrategy( + None, "", "", "" + ) +) +``` + +## GitHub App Installation Authentication + +Authenticate **as a specific installation** of your GitHub App. This grants access to the repositories and permissions that the organization or user has approved for that installation. API requests are attributed to the app. + +See [GitHub Docs — Authenticating as an installation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation) for details. + +```python +from githubkit import GitHub, AppInstallationAuthStrategy + +# Using app ID +github = GitHub( + AppInstallationAuthStrategy( + "", "", installation_id, "", "", + ) +) + +# Using client ID instead of app ID +github = GitHub( + AppInstallationAuthStrategy( + None, "", installation_id, "", "", + ) +) +``` + +!!! tip + + You can also derive an installation client from a `GitHub` App instance using `with_auth` — see [Switching Auth Strategies](#switching-auth-strategies) below. + +## OAuth App Authentication + +Authenticate as an **OAuth App** using client credentials. This strategy uses [Basic authentication](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authenticating-to-the-rest-api-with-an-oauth-app) with the client ID and secret, which is required for certain endpoints (e.g., managing OAuth tokens). + +```python +from githubkit import GitHub, OAuthAppAuthStrategy + +github = GitHub(OAuthAppAuthStrategy("", "")) +``` + +## OAuth User Token Authentication + +Act **on behalf of a user** using a previously obtained OAuth access token. This is the strategy to use when you have stored a user's token in your database and want to make API calls as that user. + +### Without token expiration + +For OAuth Apps or GitHub Apps **without** user-to-server token expiration enabled: + +```python +from githubkit import GitHub, OAuthTokenAuthStrategy + +github = GitHub( + OAuthTokenAuthStrategy( + "", "", token="" + ) +) +``` + +### With token expiration (refresh tokens) + +For GitHub Apps **with** user-to-server token expiration enabled, provide the refresh token so githubkit can automatically renew expired access tokens. If you also supply the access token, you **must** include its expiration time: + +```python +from datetime import datetime + +from githubkit import GitHub, OAuthTokenAuthStrategy + +github = GitHub( + OAuthTokenAuthStrategy( + "", + "", + token="", # optional if refresh_token is set + expire_time=datetime(), # required when token is provided + refresh_token="", # optional if token is set + refresh_token_expire_time=datetime(), # optional + ) +) +``` + +### Manually refreshing the token + +You can force a token refresh at any time: + +```python +# sync +github.auth.refresh(github) +# async +await github.auth.async_refresh(github) +``` + +This updates the `token` and `refresh_token` attributes **in place** on the auth strategy object. After refreshing, persist the new values from `github.auth` to your database. + +## OAuth Web Flow Authentication + +Exchange an OAuth **authorization code** (from the web application flow) for a user access token. See [GitHub Docs — Using the web application flow](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#using-the-web-application-flow-to-generate-a-user-access-token). + +```python +from githubkit import GitHub, OAuthWebAuthStrategy + +github = GitHub( + OAuthWebAuthStrategy("", "", "") +) +``` + +!!! warning + + This strategy is **one-time use** — the authorization code can only be exchanged once. After obtaining the token, persist it and switch to `OAuthTokenAuthStrategy` for subsequent requests. + +To exchange the code and retrieve the resulting token: + +```python +from githubkit import GitHub, OAuthWebAuthStrategy, OAuthTokenAuthStrategy + +github = GitHub( + OAuthWebAuthStrategy("", "", "") +) + +# sync +auth: OAuthTokenAuthStrategy = github.auth.exchange_token(github) +# async +auth: OAuthTokenAuthStrategy = await github.auth.async_exchange_token(github) + +# Persist these values to your database +access_token = auth.token +refresh_token = auth.refresh_token +``` + +See [Switching Auth Strategies](#switching-auth-strategies) for a complete example of web flow integration with `OAuthAppAuthStrategy`. + +## OAuth Device Flow Authentication + +Authenticate a user on **input-constrained devices** (CLI tools, IoT, smart TVs) without a web browser. githubkit handles the polling loop automatically. See [GitHub Docs — Using the device flow](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#using-the-device-flow-to-generate-a-user-access-token). + +You must provide a **callback function** that displays the user code. githubkit calls this function when a code is generated, then polls the server until the user completes authentication or the code expires. + +```python +from githubkit import GitHub, OAuthDeviceAuthStrategy + +def callback(data: dict): + """Display the user code and verification URL to the user.""" + print(f"Open {data['verification_uri']} and enter code: {data['user_code']}") + +github = GitHub( + OAuthDeviceAuthStrategy("", callback) +) +``` + +The callback receives the full device authorization response as a dict. Key fields can be found in the [GitHub Docs — Using the device flow](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#using-the-device-flow-to-generate-a-user-access-token). + +!!! warning + + This strategy is also **one-time use**. Exchange and persist the token for future requests: + +```python +from githubkit import GitHub, OAuthDeviceAuthStrategy, OAuthTokenAuthStrategy + +github = GitHub(OAuthDeviceAuthStrategy("", callback)) + +# sync +auth: OAuthTokenAuthStrategy = github.auth.exchange_token(github) +# async +auth: OAuthTokenAuthStrategy = await github.auth.async_exchange_token(github) + +# Persist these values +access_token = auth.token +refresh_token = auth.refresh_token +``` + +## GitHub Action Authentication + +Authenticate automatically inside **GitHub Actions** workflows using the `GITHUB_TOKEN` provided by the runner. + +```python +from githubkit import GitHub, ActionAuthStrategy + +github = GitHub(ActionAuthStrategy()) +``` + +Make sure the token is available to your step — pass it as an **input** or **environment variable**: + +```yaml +- name: Run my script + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +# Or as an environment variable +- name: Run my script + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` + +## Switching Auth Strategies + +The `with_auth` method creates a **new `GitHub` instance** with a different auth strategy while **sharing the same configuration** (base URL, timeout, cache, etc.). This is essential for OAuth flows where you start as an app and then act as a user. + +### GitHub App → Installation + +```python +from githubkit import GitHub, AppAuthStrategy + +github = GitHub(AppAuthStrategy("", "")) + +# Create a new client authenticated as a specific installation +installation_github = github.with_auth( + github.auth.as_installation(installation_id) +) +``` + +### GitHub App → OAuth App + +Requires `client_id` and `client_secret` to be set on the `AppAuthStrategy`: + +```python +from githubkit import GitHub, AppAuthStrategy + +github = GitHub( + AppAuthStrategy( + "", "", "", "" + ) +) + +oauth_github = github.with_auth(github.auth.as_oauth_app()) +``` + +### OAuth App → User (Web Flow) + +Exchange a web flow code for a user token and make requests as the user: + +```python +from githubkit import GitHub, OAuthAppAuthStrategy + +github = GitHub(OAuthAppAuthStrategy("", "")) + +# Create a client that acts as the user +user_github = github.with_auth(github.auth.as_web_user("")) + +# Make a request as the user +resp = user_github.rest.users.get_authenticated() +user = resp.parsed_data + +# Retrieve the tokens for persistence +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 code for a token **without making a request first**: + +```python +from githubkit import GitHub, OAuthAppAuthStrategy, OAuthTokenAuthStrategy + +github = GitHub(OAuthAppAuthStrategy("", "")) + +# sync +auth: OAuthTokenAuthStrategy = github.auth.as_web_user("").exchange_token(github) +# async +auth: OAuthTokenAuthStrategy = await github.auth.as_web_user("").async_exchange_token(github) + +# Persist the tokens +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 + +# Create a new client with the exchanged token +user_github = github.with_auth(auth) +``` + +### Device Flow → Token + +```python +from githubkit import GitHub, OAuthDeviceAuthStrategy, OAuthTokenAuthStrategy + +def callback(data: dict): + print(data["user_code"]) + +user_github = GitHub(OAuthDeviceAuthStrategy("", callback)) + +# Option 1: Make a request first (triggers the device flow automatically) +resp = user_github.rest.users.get_authenticated() +user = resp.parsed_data + +# Retrieve the tokens for persistence +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 + +# Create a reusable client with the token +token_github = user_github.with_auth(auth) +``` diff --git a/docs/usage/getting-started/configuration.md b/docs/usage/getting-started/configuration.md new file mode 100644 index 000000000..9d0adc980 --- /dev/null +++ b/docs/usage/getting-started/configuration.md @@ -0,0 +1,270 @@ +# Configuration + +githubkit is highly configurable. You can customize its behavior by passing keyword arguments directly to the `GitHub` constructor: + +```python +from githubkit import GitHub + +github = GitHub( + base_url="https://api.github.com/", + accept_format=None, + previews=None, + user_agent="GitHubKit/Python", + follow_redirects=True, + timeout=None, + ssl_verify=True, + trust_env=True, + proxy=None, + transport=None, + async_transport=None, + cache_strategy=None, + http_cache=True, + throttler=None, + auto_retry=True, + rest_api_validate_body=True, +) +``` + +Alternatively, you can build a `Config` object and pass it via the `config` parameter. This is useful when you want to share the same configuration across multiple `GitHub` instances: + +```python +import httpx +from githubkit import GitHub, Config +from githubkit.retry import RETRY_DEFAULT +from githubkit.cache import DEFAULT_CACHE_STRATEGY + +config = Config( + base_url="https://api.github.com/", + accept="application/vnd.github+json", + user_agent="GitHubKit/Python", + follow_redirects=True, + timeout=httpx.Timeout(None), + ssl_verify=True, + trust_env=True, + proxy=None, + transport=None, + async_transport=None, + cache_strategy=DEFAULT_CACHE_STRATEGY, + http_cache=True, + throttler=None, + auto_retry=RETRY_DEFAULT, + rest_api_validate_body=True, +) + +github = GitHub(config=config) +``` + +!!! note + + When using the `config` parameter, you **cannot** pass individual keyword arguments at the same time — they are mutually exclusive. + +## Options + +### `base_url` + +The base URL for all API requests. Defaults to `https://api.github.com/`. + +If you are using **GitHub Enterprise Server**, you must include the `/api/v3/` path suffix: + +```python +github = GitHub(base_url="https://github.example.com/api/v3/") +``` + +!!! note + + githubkit automatically appends a trailing slash (`/`) if one is missing. + +### `accept_format`, `previews` + +These options control the `Accept` header sent with every request. By default, githubkit uses `application/vnd.github+json`. + +- **`accept_format`** — a media type suffix such as `"raw+json"` or `"html+json"`. The leading dot is optional; githubkit adds it automatically. See [GitHub API Media Types](https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api#media-types) for available formats. +- **`previews`** — a list of API preview feature names, e.g., `["starfox"]`. Do **not** include the `-preview` suffix — githubkit adds it for you. + +```python +# Request raw markdown content as JSON +github = GitHub(accept_format="raw+json") + +# Enable an API preview feature +github = GitHub(previews=["starfox"]) +``` + +### `user_agent` + +Sets the `User-Agent` header. Defaults to `"GitHubKit/Python"`. + +### `follow_redirects` + +Whether to automatically follow HTTP redirects (3xx responses). Enabled by default. + +### `timeout` + +The request timeout. Accepts a `float` (seconds), an `httpx.Timeout` object for fine-grained control, or `None` for no timeout (default). See [HTTPX Timeouts](https://www.python-httpx.org/advanced/timeouts/) for details. + +```python +import httpx + +# Simple: 10 second timeout for all operations +github = GitHub(timeout=10.0) + +# Fine-grained: different timeouts for connect vs. read +github = GitHub(timeout=httpx.Timeout(5.0, read=30.0)) +``` + +### `ssl_verify` + +Controls SSL certificate verification. Defaults to `True`. + +- `True` — verify SSL certificates using the default CA bundle. +- `False` — **disable** SSL verification (not recommended for production). +- `ssl.SSLContext` — provide a custom SSL context for advanced use cases. + +See [HTTPX SSL](https://www.python-httpx.org/advanced/ssl/) for details. + +### `trust_env` + +When `True` (default), githubkit (via HTTPX) reads environment variables such as `HTTP_PROXY`, `HTTPS_PROXY`, and `SSL_CERT_FILE` to configure proxies and SSL. Set to `False` to ignore these variables. + +### `proxy` + +Sets a proxy URL for all requests. Accepts a string, `httpx.URL`, or `httpx.Proxy` object. + +```python +github = GitHub(proxy="http://proxy.example.com:8080") +``` + +See [HTTPX Proxies](https://www.python-httpx.org/advanced/proxies/) for more details. + +!!! note + + If `trust_env` is `True` and no `proxy` is set, githubkit respects the `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY` environment variables. + +### `transport`, `async_transport` + +Provide custom [HTTPX transports](https://www.python-httpx.org/advanced/transports/) to replace the default networking layer. This is useful for: + +- **Unit testing** — inject `httpx.MockTransport` to stub API responses without making real HTTP calls. +- **Custom networking** — use alternative transport implementations (e.g., HTTP/3, Unix sockets). + +| Option | Type | Used for | +| ----------------- | -------------------------- | -------------- | +| `transport` | `httpx.BaseTransport` | Sync requests | +| `async_transport` | `httpx.AsyncBaseTransport` | Async requests | + +```python +import httpx + +def mock_handler(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, json={"login": "octocat"}) + +github = GitHub(transport=httpx.MockTransport(mock_handler)) +``` + +!!! warning + + When a custom transport is provided, proxy-related environment variables (`HTTP_PROXY`, etc.) have no effect. Set `transport` / `async_transport` to `None` (default) to use HTTPX's built-in transport. + +### `cache_strategy` + +Controls how githubkit caches **tokens** (e.g., GitHub App installation tokens) and **HTTP responses**. The default is `MemCacheStrategy`, which stores data in process memory. + +You can provide any built-in strategy or implement a custom one by subclassing `BaseCacheStrategy`. + +#### Built-in Cache Strategies + +**`MemCacheStrategy`** — In-memory cache (default) + +No extra setup is needed. Each `MemCacheStrategy` instance maintains its own cache; the default instance is shared globally. + +```python +from githubkit import GitHub +from githubkit.cache import DEFAULT_CACHE_STRATEGY, MemCacheStrategy + +# Use the global default (shared across all GitHub instances) +github = GitHub(cache_strategy=DEFAULT_CACHE_STRATEGY) + +# Or create an isolated cache instance +github = GitHub(cache_strategy=MemCacheStrategy()) +``` + +**`RedisCacheStrategy`** — Redis cache (sync only) + +Stores tokens and HTTP responses in Redis. Requires the `redis` package. + +```python +from redis import Redis +from githubkit import GitHub +from githubkit.cache import RedisCacheStrategy + +github = GitHub( + cache_strategy=RedisCacheStrategy( + client=Redis(host="localhost", port=6379), + prefix="githubkit:", + ) +) +``` + +!!! warning + + Using `RedisCacheStrategy` restricts the `GitHub` instance to **sync-only** operations. + +**`AsyncRedisCacheStrategy`** — Redis cache (async only) + +The async counterpart of `RedisCacheStrategy`. Requires the `redis` package with async support (`redis.asyncio`). + +```python +from redis.asyncio import Redis +from githubkit import GitHub +from githubkit.cache import AsyncRedisCacheStrategy + +github = GitHub( + cache_strategy=AsyncRedisCacheStrategy( + client=Redis(host="localhost", port=6379), + prefix="githubkit:", + ) +) +``` + +!!! warning + + Using `AsyncRedisCacheStrategy` restricts the `GitHub` instance to **async-only** operations. + +!!! tip + + The `prefix` parameter adds a key namespace in Redis. Include a trailing colon (`:`) for readable key names (e.g., `"githubkit:"`). Both githubkit and [Hishel](https://hishel.com/) use this prefix. + +### `http_cache` + +Enables HTTP response caching powered by [Hishel](https://hishel.com/). When enabled, githubkit respects standard HTTP caching headers (`ETag`, `Last-Modified`, `Cache-Control`) returned by the GitHub API. This reduces redundant API calls and helps you stay within [rate limits](https://docs.github.com/en/rest/rate-limit). + +Enabled by default (`True`). Set to `False` to disable. + +### `throttler` + +Controls request concurrency to prevent hitting GitHub's rate limits. By default, githubkit uses `LocalThrottler`, which limits concurrent requests within the current process or event loop. + +```python +from githubkit import GitHub +from githubkit.throttling import LocalThrottler + +# Allow at most 100 concurrent requests +github = GitHub(throttler=LocalThrottler(100)) +``` + +You can implement a custom throttler by subclassing `BaseThrottler`. + +### `auto_retry` + +Enables automatic retrying of requests when a **rate limit is exceeded** (HTTP 403/429) or a **server error** (HTTP 5xx) is encountered. Enabled by default (`True`). + +- `True` — use the built-in retry strategy. +- `False` — disable auto-retry. +- `RetryDecisionFunc` — provide a custom callable for fine-grained control. + +See [Auto Retry](../auto-retry.md) for more information. + +### `rest_api_validate_body` + +When `True` (default), githubkit validates REST API request bodies against the GitHub API schema **before** sending the request. This catches invalid payloads early with clear error messages. + +Set to `False` to skip validation, which can be useful if you need to send payloads that don't strictly match the schema (e.g., undocumented fields). diff --git a/docs/usage/getting-started/reusing-client.md b/docs/usage/getting-started/reusing-client.md new file mode 100644 index 000000000..c67425c20 --- /dev/null +++ b/docs/usage/getting-started/reusing-client.md @@ -0,0 +1,50 @@ +# Reusing Client + +githubkit manages an underlying [HTTPX](https://www.python-httpx.org/) client for making HTTP requests. You can use a **context manager** to ensure the HTTP client is properly created, reused, and closed when you're done. + +## Using the Context Manager + +Wrapping your `GitHub` instance in a `with` (sync) or `async with` (async) block creates a single HTTP client that is shared across all requests made inside that block, and automatically closed on exit: + +=== "Sync" + + ```python hl_lines="4" + from githubkit import GitHub, Response + from githubkit.versions.latest.models import FullRepository + + with GitHub("") as github: + resp: Response[FullRepository] = github.rest.repos.get(owner="owner", repo="repo") + repo: FullRepository = resp.parsed_data + ``` + +=== "Async" + + ```python hl_lines="4" + from githubkit import GitHub, Response + from githubkit.versions.latest.models import FullRepository + + async with GitHub("") as github: + resp: Response[FullRepository] = await github.rest.repos.async_get(owner="owner", repo="repo") + repo: FullRepository = resp.parsed_data + ``` + +!!! note + + A context manager cannot be entered twice. Attempting to nest `with github:` inside another `with github:` on the same instance will raise a `RuntimeError`. + +## Without the Context Manager + +You can also use `GitHub` without a context manager. In this case, githubkit creates a new HTTP client per request, which is convenient for one-off calls but less efficient for multiple requests: + +```python +from githubkit import GitHub + +github = GitHub("") +resp = github.rest.repos.get(owner="owner", repo="repo") +``` + + + +!!! warning + + Repeatedly creating new HTTP clients may lead to **memory leaks**. We strongly recommend **using the context manager** for any workload that makes multiple API calls. diff --git a/docs/usage/rest-api.md b/docs/usage/rest-api.md index ef1ce3e62..f893d05f6 100644 --- a/docs/usage/rest-api.md +++ b/docs/usage/rest-api.md @@ -66,7 +66,7 @@ If you are calling an API that requires request body parameters, you can pass th !!! tip - By default, githubkit will validate the request body against the API schema. If you want to skip the validation, you can set the client config `rest_api_validate_body` to `False`. See [Configuration](./configuration.md#rest_api_validate_body) for more information. + By default, githubkit will validate the request body against the API schema. If you want to skip the validation, you can set the client config `rest_api_validate_body` to `False`. See [Configuration](./getting-started/configuration.md#rest_api_validate_body) for more information. Or you can pass the json request body as a dictionary: @@ -179,36 +179,6 @@ In some cases, you may need to pass additional headers to the API request. You c content = resp.text ``` -## Reusing Client - -You can make multiple requests with the same client instance in one context: - -=== "Sync" - - ```python hl_lines="4" - from githubkit import GitHub, Response - from githubkit.versions.latest.models import FullRepository - - with GitHub("") as github: - resp: Response[FullRepository] = github.rest.repos.get(owner="owner", repo="repo") - repo: FullRepository = resp.parsed_data - ``` - -=== "Async" - - ```python hl_lines="4" - from githubkit import GitHub, Response - from githubkit.versions.latest.models import FullRepository - - async with GitHub("") as github: - resp: Response[FullRepository] = await github.rest.repos.async_get(owner="owner", repo="repo") - repo: FullRepository = resp.parsed_data - ``` - -!!! warning - - Repeatedly creating new client instances may lead to memory leaks. We recommend reusing the same client instance within a single context to avoid this issue. - ## Data Validation 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. The loaded json data is also typed but not validated. For example: diff --git a/mkdocs.yml b/mkdocs.yml index 831577cae..f9c0e748f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -131,8 +131,10 @@ nav: - OAuth APP Device Flow: quickstart/oauth-device-flow.md - GitHub APP: quickstart/github-app.md - Usage: - - Authentication: usage/authentication.md - - Configuration: usage/configuration.md + - Getting Started: + - Authentication: usage/getting-started/authentication.md + - Configuration: usage/getting-started/configuration.md + - Reusing Client: usage/getting-started/reusing-client.md - REST API: usage/rest-api.md - GraphQL: usage/graphql.md - Auto Retry: usage/auto-retry.md