Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@ jobs:
name: Test Coverage
runs-on: ${{ matrix.os }}
concurrency:
group: test-coverage-${{ github.ref }}-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.env }}
group: test-coverage-${{ github.ref }}-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.env }}-${{ matrix.http-client }}
cancel-in-progress: true
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
os: [ubuntu-latest, windows-latest, macos-latest]
env: [pydantic-v1, pydantic-v2]
http-client: [httpx2]
include:
- python-version: "3.14"
os: ubuntu-latest
env: pydantic-v2
http-client: httpx
fail-fast: false
env:
OS: ${{ matrix.os }}
Expand All @@ -42,6 +48,10 @@ jobs:
python-version: ${{ matrix.python-version }}
env-group: ${{ matrix.env }}

- name: Use httpx without httpx2
if: matrix.http-client == 'httpx'
run: uv pip uninstall httpx2

Comment on lines +51 to +54

@yanyongyu yanyongyu Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be achieved by adding an environment variable within githubkit to force the use of httpx. FYI, like this.

- name: Run Pytest
run: |
uv run --no-sync bash ./scripts/run-tests.sh
Expand Down
2 changes: 1 addition & 1 deletion codegen/parser/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from contextvars import ContextVar
from typing import TYPE_CHECKING, Optional

import httpx
import httpx2 as httpx
from openapi_pydantic import OpenAPI

from ..log import logger
Expand Down
2 changes: 1 addition & 1 deletion codegen/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os
from typing import Any

import httpx
import httpx2 as httpx
from jsonpointer import JsonPointer

GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ githubkit provides several features including:
- :material-tag-multiple: REST API versioning, including GHEC
- :material-page-next: Built-in pagination support
- :material-database-check: Optional data validation with [Pydantic](https://docs.pydantic.dev/latest/), for both webhook events and REST API responses
- :material-cached: Built-in http cache (powered by [Hishel](https://hishel.com/) for HTTPX) and auto retry
- :material-cached: Built-in http cache (powered by [Hishel](https://hishel.com/) for HTTPX and HTTPX2) and auto retry
- :material-lightning-bolt: Lazy loading of APIs and models
- :material-check-circle: Fully typed APIs

Expand Down
60 changes: 33 additions & 27 deletions docs/usage/getting-started/configuration.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Configuration

githubkit is highly configurable. You can customize its behavior by passing keyword arguments directly to the `GitHub` constructor:
githubkit is highly configurable.

GitHubKit installs HTTPX by default to preserve compatibility with existing transports. It prefers [HTTPX2](https://pydantic.dev/docs/httpx2/) when you install it separately. The default HTTPX path remains supported, but emits a `DeprecationWarning`. To opt in, run `pip install httpx2`, then use `import httpx2` and `httpx2.*` as in the examples below. If you remain on HTTPX, use `import httpx` and `httpx.*` instead.

Custom timeouts, URLs, proxies, transports, and responses must come from the selected package: HTTPX2 when installed, or HTTPX on the deprecated fallback path. Objects from the two packages are not interchangeable. HTTPX2 uses the operating system's trust store by default instead of HTTPX's bundled CA certificates; its logger names are `httpx2` and `httpcore2.*`.
Comment on lines +5 to +7

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section can be explained to the user by adding admonition block below the original paragraph.


You can customize GitHubKit's behavior by passing keyword arguments directly to the `GitHub` constructor:

```python
from githubkit import GitHub
Expand Down Expand Up @@ -30,7 +36,7 @@ github = GitHub(
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
import httpx2
from githubkit import GitHub, Config
from githubkit.retry import RETRY_DEFAULT
from githubkit.cache import DEFAULT_CACHE_STRATEGY
Expand All @@ -40,7 +46,7 @@ config = Config(
accept="application/vnd.github+json",
user_agent="GitHubKit/Python",
follow_redirects=True,
timeout=httpx.Timeout(None),
timeout=httpx2.Timeout(None),
ssl_verify=True,
trust_env=True,
proxy=None,
Expand Down Expand Up @@ -103,16 +109,16 @@ Whether to automatically follow HTTP redirects (3xx responses). Enabled by defau

### `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.
The request timeout. Accepts a `float` (seconds), an `httpx2.Timeout` object for fine-grained control, or `None` for no timeout (default). See [HTTPX2 Timeouts](https://pydantic.dev/docs/httpx2/advanced/timeouts/) for details.

```python
import httpx
import httpx2

# 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))
github = GitHub(timeout=httpx2.Timeout(5.0, read=30.0))
```

### `ssl_verify`
Expand All @@ -123,76 +129,76 @@ Controls SSL certificate verification. Defaults to `True`.
- `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.
See [HTTPX2 SSL](https://pydantic.dev/docs/httpx2/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.
When `True` (default), githubkit (via HTTPX2) 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.
Sets a proxy URL for all requests. Accepts a string, `httpx2.URL`, or `httpx2.Proxy` object.

```python
github = GitHub(proxy="http://proxy.example.com:8080")
```

See [HTTPX Proxies](https://www.python-httpx.org/advanced/proxies/) for more details.
See [HTTPX2 Proxies](https://pydantic.dev/docs/httpx2/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:
Provide custom [HTTPX2 transports](https://pydantic.dev/docs/httpx2/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.
- **Unit testing** — inject `httpx2.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 |
| `transport` | `httpx2.BaseTransport` | Sync requests |
| `async_transport` | `httpx2.AsyncBaseTransport` | Async requests |

```python
import httpx
import httpx2


def mock_handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(200, json={"login": "octocat"})
def mock_handler(request: httpx2.Request) -> httpx2.Response:
return httpx2.Response(200, json={"login": "octocat"})


github = GitHub(transport=httpx.MockTransport(mock_handler))
github = GitHub(transport=httpx2.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.
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 HTTPX2's built-in transport.

### `event_hooks`, `async_event_hooks`

Register [HTTPX event hooks](https://www.python-httpx.org/advanced/event-hooks/) that run on every request and/or response. This is useful for logging, injecting headers, collecting metrics, or raising on error status codes — without modifying your business logic.
Register [HTTPX2 event hooks](https://pydantic.dev/docs/httpx2/advanced/event-hooks/) that run on every request and/or response. This is useful for logging, injecting headers, collecting metrics, or raising on error status codes — without modifying your business logic.

| Option | Hook signatures | Used for |
| ------------------- | ------------------------------------------------------ | -------------- |
| `event_hooks` | `def hook(request)` / `def hook(response)` | Sync requests |
| `async_event_hooks` | `async def hook(request)` / `async def hook(response)` | Async requests |

Both options accept a dictionary mapping event names (`"request"`, `"response"`) to a list of callables. Each callable receives an `httpx.Request` or `httpx.Response` object respectively.
Both options accept a dictionary mapping event names (`"request"`, `"response"`) to a list of callables. Each callable receives an `httpx2.Request` or `httpx2.Response` object respectively.

=== "Sync"

```python
import httpx
import httpx2
from githubkit import GitHub


def log_request(request: httpx.Request) -> None:
def log_request(request: httpx2.Request) -> None:
print(f"-> {request.method} {request.url}")


def log_response(response: httpx.Response) -> None:
def log_response(response: httpx2.Response) -> None:
print(f"<- {response.status_code}")


Expand All @@ -207,15 +213,15 @@ Both options accept a dictionary mapping event names (`"request"`, `"response"`)
=== "Async"

```python
import httpx
import httpx2
from githubkit import GitHub


async def log_request(request: httpx.Request) -> None:
async def log_request(request: httpx2.Request) -> None:
print(f"-> {request.method} {request.url}")


async def log_response(response: httpx.Response) -> None:
async def log_response(response: httpx2.Response) -> None:
print(f"<- {response.status_code}")


Expand Down
2 changes: 1 addition & 1 deletion docs/usage/getting-started/reusing-client.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 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.
githubkit manages an underlying HTTPX client by default, preferring [HTTPX2](https://pydantic.dev/docs/httpx2/) when you install it separately. You can use a **context manager** to ensure the HTTP client is properly created, reused, and closed when you're done.

<!-- https://github.com/yanyongyu/githubkit/issues/285 -->

Expand Down
26 changes: 14 additions & 12 deletions docs/usage/unit-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

If you are using githubkit in your business logic, you may want to mock the github API in your unit tests. There are two ways to reach this.

These examples use HTTPX2, the preferred opt-in backend. Run `pip install httpx2` to use it. GitHubKit installs HTTPX by default; if you use that deprecated fallback, use `import httpx` and replace `httpx2.*` with `httpx.*` instead. Mock requests, responses, and transports must use the same package as GitHubKit; HTTPX and HTTPX2 objects are not interchangeable.

## Mocking the API Calls

If you can't provide a githubkit test client to your business logic, you can mock the `request`/`arequest` method of the `GitHub` class to custom the response. Here is an example of how to mock githubkit's API calls:
Expand All @@ -13,7 +15,7 @@ If you can't provide a githubkit test client to your business logic, you can moc
from pathlib import Path
from typing import Any, Type

import httpx
import httpx2
import pytest

from githubkit import GitHub
Expand Down Expand Up @@ -41,7 +43,7 @@ If you can't provide a githubkit test client to your business logic, you can moc
) -> Response[Any]:
if method == "GET" and url == "/repos/owner/repo": # (3)!
return Response(
httpx.Response(status_code=200, json=FAKE_RESPONSE),
httpx2.Response(status_code=200, json=FAKE_RESPONSE),
Any if response_model is UNSET else response_model,
)
raise RuntimeError(f"Unexpected request: {method} {url}")
Expand All @@ -68,7 +70,7 @@ If you can't provide a githubkit test client to your business logic, you can moc
from pathlib import Path
from typing import Any, Type

import httpx
import httpx2
import pytest

from githubkit import GitHub
Expand Down Expand Up @@ -96,7 +98,7 @@ If you can't provide a githubkit test client to your business logic, you can moc
) -> Response[Any]:
if method == "GET" and url == "/repos/owner/repo": # (3)!
return Response(
httpx.Response(status_code=200, json=FAKE_RESPONSE),
httpx2.Response(status_code=200, json=FAKE_RESPONSE),
Any if response_model is UNSET else response_model,
)
raise RuntimeError(f"Unexpected request: {method} {url}")
Expand Down Expand Up @@ -127,7 +129,7 @@ You can also create a test client with mock transport and provide it to your bus
import json
from pathlib import Path

import httpx
import httpx2
import pytest

from githubkit import GitHub
Expand All @@ -141,14 +143,14 @@ You can also create a test client with mock transport and provide it to your bus
return resp.parsed_data


def mock_transport_handler(request: httpx.Request) -> httpx.Response:
def mock_transport_handler(request: httpx2.Request) -> httpx2.Response:
if request.method == "GET" and request.url.path == "/repos/owner/repo":
return httpx.Response(status_code=200, json=FAKE_RESPONSE)
return httpx2.Response(status_code=200, json=FAKE_RESPONSE)
raise RuntimeError(f"Unexpected request: {request.method} {request.url.path}")


def test_sync_mock():
g = GitHub("xxxxx", transport=httpx.MockTransport(mock_transport_handler))
g = GitHub("xxxxx", transport=httpx2.MockTransport(mock_transport_handler))
repo = target_sync_func(g)
assert isinstance(repo, FullRepository)
```
Expand All @@ -159,7 +161,7 @@ You can also create a test client with mock transport and provide it to your bus
import json
from pathlib import Path

import httpx
import httpx2
import pytest

from githubkit import GitHub
Expand All @@ -173,15 +175,15 @@ You can also create a test client with mock transport and provide it to your bus
return resp.parsed_data


def mock_transport_handler(request: httpx.Request) -> httpx.Response:
def mock_transport_handler(request: httpx2.Request) -> httpx2.Response:
if request.method == "GET" and request.url.path == "/repos/owner/repo":
return httpx.Response(status_code=200, json=FAKE_RESPONSE)
return httpx2.Response(status_code=200, json=FAKE_RESPONSE)
raise RuntimeError(f"Unexpected request: {request.method} {request.url.path}")


@pytest.mark.anyio
async def test_async_mock():
g = GitHub("xxxxx", async_transport=httpx.MockTransport(mock_transport_handler))
g = GitHub("xxxxx", async_transport=httpx2.MockTransport(mock_transport_handler))
repo = await target_async_func(g)
assert isinstance(repo, FullRepository)
```
31 changes: 31 additions & 0 deletions githubkit/_httpx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import TYPE_CHECKING
import warnings

if TYPE_CHECKING:
from hishel.httpx2 import AsyncCacheTransport as AsyncCacheTransport
from hishel.httpx2 import SyncCacheTransport as SyncCacheTransport
import httpx2 as httpx
else:
try:
from hishel.httpx2 import AsyncCacheTransport, SyncCacheTransport
import httpx2 as httpx
except ImportError:
# Hishel wraps a missing HTTP client in ImportError.
try:
from hishel.httpx import AsyncCacheTransport as AsyncCacheTransport
from hishel.httpx import SyncCacheTransport as SyncCacheTransport
import httpx as httpx
except ImportError:
raise RuntimeError(
"GitHubKit requires httpx2 and its Hishel integration "
"to be installed.\n"
"You can install them with:\n"
' $ pip install httpx2 "hishel[async,httpx2]>=1.4.0"\n'
) from None
else:
warnings.warn(
"Using `httpx` with GitHubKit is deprecated; install `httpx2` "
"and `hishel[async,httpx2]>=1.4.0` instead.",
DeprecationWarning,
stacklevel=2,
)
Comment thread
CoderJoshDK marked this conversation as resolved.
2 changes: 1 addition & 1 deletion githubkit/auth/_url.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import re

import httpx
from githubkit._httpx import httpx

APP_ROUTES = {
r"/app",
Expand Down
3 changes: 1 addition & 2 deletions githubkit/auth/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
import os
from typing import TYPE_CHECKING

import httpx

from githubkit._httpx import httpx
from githubkit.exception import AuthCredentialError

from .base import BaseAuthStrategy
Expand Down
3 changes: 1 addition & 2 deletions githubkit/auth/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
from typing import TYPE_CHECKING, ClassVar
from typing_extensions import LiteralString

import httpx

from githubkit._httpx import httpx
from githubkit.compat import model_dump, type_validate_python
from githubkit.exception import AuthCredentialError
from githubkit.utils import UNSET, Unset, exclude_unset
Expand Down
Loading
Loading