diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index e67836a032..d3727d51e2 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -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 }} @@ -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 + - name: Run Pytest run: | uv run --no-sync bash ./scripts/run-tests.sh diff --git a/codegen/parser/__init__.py b/codegen/parser/__init__.py index 1b528e3223..677ec80f23 100644 --- a/codegen/parser/__init__.py +++ b/codegen/parser/__init__.py @@ -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 diff --git a/codegen/source.py b/codegen/source.py index b400e03885..34003ab2bb 100644 --- a/codegen/source.py +++ b/codegen/source.py @@ -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") diff --git a/docs/index.md b/docs/index.md index 1a0adaa8b7..eb8461c1a1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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 diff --git a/docs/usage/getting-started/configuration.md b/docs/usage/getting-started/configuration.md index 206d355ce7..3921bf5dee 100644 --- a/docs/usage/getting-started/configuration.md +++ b/docs/usage/getting-started/configuration.md @@ -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.*`. + +You can customize GitHubKit's behavior by passing keyword arguments directly to the `GitHub` constructor: ```python from githubkit import GitHub @@ -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 @@ -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, @@ -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` @@ -123,21 +129,21 @@ 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 @@ -145,54 +151,54 @@ See [HTTPX Proxies](https://www.python-httpx.org/advanced/proxies/) for more det ### `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}") @@ -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}") diff --git a/docs/usage/getting-started/reusing-client.md b/docs/usage/getting-started/reusing-client.md index 89aca99aeb..63c422d6b8 100644 --- a/docs/usage/getting-started/reusing-client.md +++ b/docs/usage/getting-started/reusing-client.md @@ -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. diff --git a/docs/usage/unit-test.md b/docs/usage/unit-test.md index e046112935..034c957425 100644 --- a/docs/usage/unit-test.md +++ b/docs/usage/unit-test.md @@ -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: @@ -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 @@ -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}") @@ -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 @@ -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}") @@ -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 @@ -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) ``` @@ -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 @@ -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) ``` diff --git a/githubkit/_httpx.py b/githubkit/_httpx.py new file mode 100644 index 0000000000..f74ee26c57 --- /dev/null +++ b/githubkit/_httpx.py @@ -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, + ) diff --git a/githubkit/auth/_url.py b/githubkit/auth/_url.py index 7f2f4c5e16..85850bece1 100644 --- a/githubkit/auth/_url.py +++ b/githubkit/auth/_url.py @@ -1,6 +1,6 @@ import re -import httpx +from githubkit._httpx import httpx APP_ROUTES = { r"/app", diff --git a/githubkit/auth/action.py b/githubkit/auth/action.py index ee7af40bb5..753908a92e 100644 --- a/githubkit/auth/action.py +++ b/githubkit/auth/action.py @@ -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 diff --git a/githubkit/auth/app.py b/githubkit/auth/app.py index acd70e7719..7c17afe1ee 100644 --- a/githubkit/auth/app.py +++ b/githubkit/auth/app.py @@ -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 diff --git a/githubkit/auth/base.py b/githubkit/auth/base.py index 953fd6bd5d..7e20e11d6f 100644 --- a/githubkit/auth/base.py +++ b/githubkit/auth/base.py @@ -1,7 +1,7 @@ import abc from typing import TYPE_CHECKING -import httpx +from githubkit._httpx import httpx if TYPE_CHECKING: from githubkit import GitHubCore diff --git a/githubkit/auth/oauth.py b/githubkit/auth/oauth.py index 3c662eacd1..a1913b7fb2 100644 --- a/githubkit/auth/oauth.py +++ b/githubkit/auth/oauth.py @@ -5,8 +5,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, TypedDict, cast from typing_extensions import Self -import httpx - +from githubkit._httpx import httpx from githubkit.exception import AuthCredentialError, AuthExpiredError from githubkit.utils import is_async diff --git a/githubkit/auth/token.py b/githubkit/auth/token.py index 42294b7772..88461482d7 100644 --- a/githubkit/auth/token.py +++ b/githubkit/auth/token.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from typing import TYPE_CHECKING -import httpx +from githubkit._httpx import httpx from .base import BaseAuthStrategy diff --git a/githubkit/auth/unauth.py b/githubkit/auth/unauth.py index 5c378eab2d..c95ffb5f75 100644 --- a/githubkit/auth/unauth.py +++ b/githubkit/auth/unauth.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING -import httpx +from githubkit._httpx import httpx from .base import BaseAuthStrategy diff --git a/githubkit/config.py b/githubkit/config.py index 1abff8da2a..d00b2568eb 100644 --- a/githubkit/config.py +++ b/githubkit/config.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any from typing_extensions import Self -import httpx +from githubkit._httpx import httpx from .cache import DEFAULT_CACHE_STRATEGY, BaseCacheStrategy from .retry import RETRY_DEFAULT diff --git a/githubkit/core.py b/githubkit/core.py index 82fbf997f1..8c0f0fdad7 100644 --- a/githubkit/core.py +++ b/githubkit/core.py @@ -7,7 +7,8 @@ from typing import TYPE_CHECKING, Any, Generic, TypeVar, overload import anyio -import httpx + +from githubkit._httpx import httpx from .auth import BaseAuthStrategy, TokenAuthStrategy, UnauthAuthStrategy from .cache import BaseCacheStrategy diff --git a/githubkit/exception.py b/githubkit/exception.py index 33dfa039ce..90e19d37ae 100644 --- a/githubkit/exception.py +++ b/githubkit/exception.py @@ -1,7 +1,7 @@ from datetime import timedelta from typing import TYPE_CHECKING, Generic, TypeVar -import httpx +from ._httpx import httpx if TYPE_CHECKING: from .graphql import GraphQLResponse diff --git a/githubkit/github.py b/githubkit/github.py index c8b3208604..e0712661ba 100644 --- a/githubkit/github.py +++ b/githubkit/github.py @@ -14,8 +14,7 @@ if TYPE_CHECKING: import ssl - import httpx - + from ._httpx import httpx from .auth import TokenAuthStrategy, UnauthAuthStrategy from .cache import BaseCacheStrategy from .config import Config diff --git a/githubkit/hishel.py b/githubkit/hishel.py index 7d7be68054..5f506fa1d8 100644 --- a/githubkit/hishel.py +++ b/githubkit/hishel.py @@ -10,8 +10,8 @@ SyncBaseStorage, SyncSqliteStorage, ) -from hishel.httpx import AsyncCacheTransport, SyncCacheTransport -import httpx + +from ._httpx import AsyncCacheTransport, SyncCacheTransport, httpx class SyncCacheClient(httpx.Client): diff --git a/githubkit/response.py b/githubkit/response.py index 5deb315e98..85ac617f27 100644 --- a/githubkit/response.py +++ b/githubkit/response.py @@ -3,7 +3,7 @@ from typing import Any, Generic from typing_extensions import TypeVar -import httpx +from githubkit._httpx import httpx from .compat import type_validate_json from .exception import RequestError, RequestTimeout diff --git a/githubkit/rest/paginator.py b/githubkit/rest/paginator.py index 9f46ab2794..9fb6062954 100644 --- a/githubkit/rest/paginator.py +++ b/githubkit/rest/paginator.py @@ -11,8 +11,7 @@ ) from typing_extensions import ParamSpec, Self -import httpx - +from githubkit._httpx import httpx from githubkit.response import Response from githubkit.typing import HeaderTypes from githubkit.utils import is_async diff --git a/githubkit/throttling.py b/githubkit/throttling.py index 15bfd5e626..c73fb6ca5b 100644 --- a/githubkit/throttling.py +++ b/githubkit/throttling.py @@ -6,7 +6,8 @@ from typing_extensions import override import anyio -import httpx + +from githubkit._httpx import httpx class BaseThrottler(abc.ABC): diff --git a/githubkit/typing.py b/githubkit/typing.py index d1ea8e0943..19715ea318 100644 --- a/githubkit/typing.py +++ b/githubkit/typing.py @@ -11,9 +11,10 @@ TypeVar, ) -import httpx from pydantic import Field +from githubkit._httpx import httpx + from .compat import PYDANTIC_V2 from .exception import GitHubException from .utils import Unset diff --git a/pyproject.toml b/pyproject.toml index 84a49034af..2d5857dd94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ requires-python = ">=3.10, <4.0" dependencies = [ "anyio >=3.6.1, <5.0.0", "httpx >=0.23.0, <1.0.0", - "hishel[async,httpx] >=1.3.0, <=2.0.0", + "hishel[async,httpx] >=1.4.0, <=2.0.0", "typing-extensions >=4.11.0, <5.0.0", "pydantic >=1.9.1, <3.0.0, !=2.5.0, !=2.5.1", "githubkit-schemas >=26.5.7", @@ -46,9 +46,11 @@ codegen = [ "tomlkit >=0.13.3, <0.16.0", "jsonpointer >=3.0.0, <4.0.0", "openapi-pydantic >=0.5.0, <0.6.0", + "httpx2 >=2.13.1, <3.0.0", ] test = [ { include-group = "thirdparty" }, + "httpx2 >=2.13.1, <3.0.0", # exercise the opt-in backend alongside httpx "pytest >=8.1.0, <10.0.0", "pytest-cov >=7.0.0, <8.0.0", "pytest-xdist >=3.5.0, <4.0.0", diff --git a/tests/test_rest/test_call.py b/tests/test_rest/test_call.py index f859370c83..7ac89b1376 100644 --- a/tests/test_rest/test_call.py +++ b/tests/test_rest/test_call.py @@ -2,10 +2,10 @@ from githubkit_schemas.core import LATEST_VERSION from githubkit_schemas.latest.models import FullRepository, Issue -import httpx import pytest from githubkit import GitHub +from githubkit._httpx import httpx from tests.utils import get_mock_github OWNER = "yanyongyu" diff --git a/tests/test_unit_test/test_event_hooks.py b/tests/test_unit_test/test_event_hooks.py index 46191edf85..de62b93fd5 100644 --- a/tests/test_unit_test/test_event_hooks.py +++ b/tests/test_unit_test/test_event_hooks.py @@ -2,10 +2,10 @@ from pathlib import Path from githubkit_schemas.latest.models import FullRepository -import httpx import pytest from githubkit import GitHub +from githubkit._httpx import httpx FAKE_RESPONSE = json.loads((Path(__file__).parent / "fake_response.json").read_text()) diff --git a/tests/test_unit_test/test_mock_transport.py b/tests/test_unit_test/test_mock_transport.py index 2b802a7903..d0087044ce 100644 --- a/tests/test_unit_test/test_mock_transport.py +++ b/tests/test_unit_test/test_mock_transport.py @@ -2,10 +2,10 @@ from pathlib import Path from githubkit_schemas.latest.models import FullRepository -import httpx import pytest from githubkit import GitHub +from githubkit._httpx import httpx FAKE_RESPONSE = json.loads((Path(__file__).parent / "fake_response.json").read_text()) diff --git a/tests/test_unit_test/test_unit_test.py b/tests/test_unit_test/test_unit_test.py index e989f3808a..81854fc904 100644 --- a/tests/test_unit_test/test_unit_test.py +++ b/tests/test_unit_test/test_unit_test.py @@ -3,10 +3,10 @@ from typing import Any, TypeVar from githubkit_schemas.latest.models import FullRepository -import httpx import pytest from githubkit import GitHub +from githubkit._httpx import httpx from githubkit.response import Response from githubkit.typing import UnsetType, URLTypes from githubkit.utils import UNSET diff --git a/tests/utils.py b/tests/utils.py index 35672d1414..1f6a807efb 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,8 +1,7 @@ from collections.abc import Callable -import httpx - from githubkit import GitHub, UnauthAuthStrategy +from githubkit._httpx import httpx def get_mock_github( diff --git a/uv.lock b/uv.lock index 8ae3724a3d..8f56ee524e 100644 --- a/uv.lock +++ b/uv.lock @@ -1,8 +1,9 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.10, <4.0" resolution-markers = [ - "python_full_version >= '3.11'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.11' and sys_platform != 'emscripten') or (python_full_version == '3.11.*' and sys_platform == 'emscripten')", "python_full_version < '3.11'", ] conflicts = [[ @@ -695,6 +696,7 @@ jwt = [ [package.dev-dependencies] codegen = [ + { name = "httpx2" }, { name = "jinja2" }, { name = "jsonpointer" }, { name = "openapi-pydantic" }, @@ -703,6 +705,7 @@ codegen = [ dev = [ { name = "coverage-conditional-plugin" }, { name = "hishel", extra = ["redis"] }, + { name = "httpx2" }, { name = "jinja2" }, { name = "jsonpointer" }, { name = "mkdocs-git-committers-plugin-2" }, @@ -735,6 +738,7 @@ pydantic-v2 = [ test = [ { name = "coverage-conditional-plugin" }, { name = "hishel", extra = ["redis"] }, + { name = "httpx2" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-xdist" }, @@ -749,7 +753,7 @@ requires-dist = [ { name = "githubkit-schemas", editable = "packages/githubkit-schemas" }, { name = "githubkit-schemas", extras = ["all"], marker = "extra == 'all'", editable = "packages/githubkit-schemas" }, { name = "githubkit-schemas", extras = ["all"], marker = "extra == 'all-schemas'", editable = "packages/githubkit-schemas" }, - { name = "hishel", extras = ["async", "httpx"], specifier = ">=1.3.0,<=2.0.0" }, + { name = "hishel", extras = ["async", "httpx"], specifier = ">=1.4.0,<=2.0.0" }, { name = "httpx", specifier = ">=0.23.0,<1.0.0" }, { name = "pydantic", specifier = ">=1.9.1,!=2.5.0,!=2.5.1,<3.0.0" }, { name = "pyjwt", extras = ["crypto"], marker = "extra == 'all'", specifier = ">=2.4.0,<3.0.0" }, @@ -762,6 +766,7 @@ provides-extras = ["jwt", "auth-app", "auth-oauth-device", "auth", "all-schemas" [package.metadata.requires-dev] codegen = [ + { name = "httpx2", specifier = ">=2.13.1,<3.0.0" }, { name = "jinja2", specifier = ">=3.1.2,<4.0.0" }, { name = "jsonpointer", specifier = ">=3.0.0,<4.0.0" }, { name = "openapi-pydantic", specifier = ">=0.5.0,<0.6.0" }, @@ -770,6 +775,7 @@ codegen = [ dev = [ { name = "coverage-conditional-plugin", specifier = ">=0.9.0,<0.10.0" }, { name = "hishel", extras = ["redis"], specifier = ">=1.2.0,<=2.0.0" }, + { name = "httpx2", specifier = ">=2.13.1,<3.0.0" }, { name = "jinja2", specifier = ">=3.1.2,<4.0.0" }, { name = "jsonpointer", specifier = ">=3.0.0,<4.0.0" }, { name = "mkdocs-git-committers-plugin-2", specifier = ">=2.4.1,<3" }, @@ -798,6 +804,7 @@ pydantic-v2 = [{ name = "pydantic", specifier = ">=2.0.0,<3.0.0" }] test = [ { name = "coverage-conditional-plugin", specifier = ">=0.9.0,<0.10.0" }, { name = "hishel", extras = ["redis"], specifier = ">=1.2.0,<=2.0.0" }, + { name = "httpx2", specifier = ">=2.13.1,<3.0.0" }, { name = "pytest", specifier = ">=8.1.0,<10.0.0" }, { name = "pytest-cov", specifier = ">=7.0.0,<8.0.0" }, { name = "pytest-xdist", specifier = ">=3.5.0,<4.0.0" }, @@ -969,6 +976,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] +[[package]] +name = "httpcore2" +version = "2.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11", marker = "python_full_version < '3.12' or sys_platform != 'emscripten' or (extra == 'group-9-githubkit-pydantic-v1' and extra == 'group-9-githubkit-pydantic-v2')" }, + { name = "truststore", marker = "python_full_version < '3.12' or sys_platform != 'emscripten' or (extra == 'group-9-githubkit-pydantic-v1' and extra == 'group-9-githubkit-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/f3/1db7aa2bc2524062192bb0e0323969492d1883152a232fe36eea65f4e35c/httpcore2-2.13.1.tar.gz", hash = "sha256:e0aa977abe17e69a3b820a24542a6fa88702676d83880b8d194dcd18408e5103", size = 68071, upload-time = "2026-09-23T07:47:22.372Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/ba/a4568248771ce81957bfb7cc600264a40fbcda092391ee1c415c50be4bea/httpcore2-2.13.1-py3-none-any.whl", hash = "sha256:e1e05d4f25f7d7d496bfb96748f6f4b67657b03da069b3a68c36069f3db73d0a", size = 83423, upload-time = "2026-09-23T07:47:19.365Z" }, +] + [[package]] name = "httpx" version = "0.28.1" @@ -984,6 +1004,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] +[[package]] +name = "httpx2" +version = "2.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform != 'emscripten' or (extra == 'group-9-githubkit-pydantic-v1' and extra == 'group-9-githubkit-pydantic-v2')" }, + { name = "httpcore2", marker = "sys_platform != 'emscripten' or (extra == 'group-9-githubkit-pydantic-v1' and extra == 'group-9-githubkit-pydantic-v2')" }, + { name = "httpx2-jsfetch", marker = "(python_full_version >= '3.12' and sys_platform == 'emscripten') or (python_full_version < '3.12' and extra == 'group-9-githubkit-pydantic-v1' and extra == 'group-9-githubkit-pydantic-v2') or (sys_platform != 'emscripten' and extra == 'group-9-githubkit-pydantic-v1' and extra == 'group-9-githubkit-pydantic-v2')" }, + { name = "idna" }, + { name = "truststore", marker = "sys_platform != 'emscripten' or (extra == 'group-9-githubkit-pydantic-v1' and extra == 'group-9-githubkit-pydantic-v2')" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-9-githubkit-pydantic-v1' and extra == 'group-9-githubkit-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/44/474bef2a0e9d90f1715d32cb98b0738695ca17ba324095fb2497ed7fbd59/httpx2-2.13.1.tar.gz", hash = "sha256:e48744a19e3af5ee48313d0ce5fe941d5422fae5705ea922a4aabf94d7800dfa", size = 100405, upload-time = "2026-09-23T07:47:23.052Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/9c/6fe8931fd9f381042a9e4c7d5a7b4cbf7016b252bec0c99a49fce42c3326/httpx2-2.13.1-py3-none-any.whl", hash = "sha256:6dff50fabc270ee5fd25d845d0b078ed20564579744d6d962850975996d2f9a4", size = 95597, upload-time = "2026-09-23T07:47:20.995Z" }, +] + +[[package]] +name = "httpx2-jsfetch" +version = "1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" }, +] + [[package]] name = "identify" version = "2.6.20" @@ -1049,7 +1095,8 @@ name = "markdown" version = "3.11" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.11'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.11' and sys_platform != 'emscripten') or (python_full_version == '3.11.*' and sys_platform == 'emscripten')", ] sdist = { url = "https://files.pythonhosted.org/packages/f8/4f/700155c8c20d9e655dd0732b5fc3c7614f291b9148da271d7388e50bf774/markdown-3.11.tar.gz", hash = "sha256:180224db6aed87ba9ce1f2781ebcd5826253de8ff637112090e24b84502bbf9f", size = 485623, upload-time = "2026-09-25T13:46:23.473Z" } wheels = [ @@ -1482,7 +1529,8 @@ name = "pydantic" version = "1.10.26" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.11'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.11' and sys_platform != 'emscripten') or (python_full_version == '3.11.*' and sys_platform == 'emscripten')", "python_full_version < '3.11'", ] dependencies = [ @@ -1523,7 +1571,8 @@ name = "pydantic" version = "2.13.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.11'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.11' and sys_platform != 'emscripten') or (python_full_version == '3.11.*' and sys_platform == 'emscripten')", "python_full_version < '3.11'", ] dependencies = [ @@ -1971,6 +2020,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/bc/8c13eb66537dce1d2bd3a57132902f38d0e7f5bb46fa9f4daed9fe9d76ee/tomlkit-0.15.1-py3-none-any.whl", hash = "sha256:177a05aece5a8ca5266fd3c448abb47b8d352f09d477d3ca8332db4d89b24304", size = 49449, upload-time = "2026-07-17T01:48:05.728Z" }, ] +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, +] + [[package]] name = "typing-extensions" version = "4.16.0"