From d1e4925bb6357da02cfe6a0ca9878f0438b7fc31 Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Wed, 28 Oct 2026 12:51:25 +0000 Subject: [PATCH 1/4] :sparkles: use PEP 747 TypeForm --- .github/workflows/pyright.yml | 2 +- codegen/parser/utils.py | 7 +++++-- githubkit/compat.py | 37 ++++++----------------------------- githubkit/utils.py | 11 +++-------- pyproject.toml | 3 +-- uv.lock | 2 +- 6 files changed, 17 insertions(+), 45 deletions(-) diff --git a/.github/workflows/pyright.yml b/.github/workflows/pyright.yml index 29f6a654ad..7925432f77 100644 --- a/.github/workflows/pyright.yml +++ b/.github/workflows/pyright.yml @@ -47,7 +47,7 @@ jobs: uses: jakebailey/pyright-action@v3 with: pylance-version: latest-release - extra-args: githubkit + extra-args: githubkit packages env: NODE_OPTIONS: --max-old-space-size=12288 diff --git a/codegen/parser/utils.py b/codegen/parser/utils.py index 30d0e8e2c9..39c0009fc2 100644 --- a/codegen/parser/utils.py +++ b/codegen/parser/utils.py @@ -1,7 +1,8 @@ import builtins from keyword import iskeyword import re -from typing import TYPE_CHECKING, Any, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar, cast +from typing_extensions import TypeForm import openapi_pydantic as oas from pydantic import TypeAdapter @@ -173,6 +174,8 @@ def type_ref_from_source(source: "Source", type_: type[T]) -> oas.Reference | T: data = source.data try: - return TypeAdapter(oas.Reference | type_).validate_python(data) + return TypeAdapter( + cast(TypeForm[oas.Reference | T], oas.Reference | type_) + ).validate_python(data) except Exception as e: raise TypeError(f"Invalid {type_} from {source.uri}") from e diff --git a/githubkit/compat.py b/githubkit/compat.py index ddb675c961..21926181e3 100644 --- a/githubkit/compat.py +++ b/githubkit/compat.py @@ -1,5 +1,6 @@ from collections.abc import Callable, Generator -from typing import TYPE_CHECKING, Any, Protocol, TypeVar, overload +from typing import TYPE_CHECKING, Any, Protocol, TypeVar +from typing_extensions import TypeForm from pydantic import VERSION @@ -36,24 +37,10 @@ class GitHubModel(BaseModel): class ExtraGitHubModel(GitHubModel): model_config = ConfigDict(extra="allow") - # Remove the overload once [PEP747](https://peps.python.org/pep-0747/) is accepted - # We should use TypeForm here - @overload - def type_validate_python(type_: type[T], data: Any) -> T: ... - - @overload - def type_validate_python(type_: Any, data: Any) -> Any: ... - - def type_validate_python(type_: type[T], data: Any) -> T: + def type_validate_python(type_: TypeForm[T], data: Any) -> T: return TypeAdapter(type_).validate_python(data) - @overload - def type_validate_json(type_: type[T], data: Any) -> T: ... - - @overload - def type_validate_json(type_: Any, data: Any) -> Any: ... - - def type_validate_json(type_: type[T], data: Any) -> T: + def type_validate_json(type_: TypeForm[T], data: Any) -> T: return TypeAdapter(type_).validate_json(data) def model_dump(model: BaseModel, by_alias: bool = True) -> dict[str, Any]: @@ -97,22 +84,10 @@ class ExtraGitHubModel(BaseModel): class Config: extra = Extra.allow - @overload - def type_validate_python(type_: type[T], data: Any) -> T: ... - - @overload - def type_validate_python(type_: Any, data: Any) -> Any: ... - - def type_validate_python(type_: type[T], data: Any) -> T: + def type_validate_python(type_: TypeForm[T], data: Any) -> T: return parse_obj_as(type_, data) - @overload - def type_validate_json(type_: type[T], data: Any) -> T: ... - - @overload - def type_validate_json(type_: Any, data: Any) -> Any: ... - - def type_validate_json(type_: type[T], data: Any) -> T: + def type_validate_json(type_: TypeForm[T], data: Any) -> T: return parse_raw_as(type_, data) def to_jsonable_python(obj: Any) -> Any: diff --git a/githubkit/utils.py b/githubkit/utils.py index 864dce6c50..af840f1d24 100644 --- a/githubkit/utils.py +++ b/githubkit/utils.py @@ -1,7 +1,8 @@ from enum import Enum from functools import partial import inspect -from typing import Any, Generic, Literal, TypeVar, final, overload +from typing import Any, Generic, Literal, TypeVar, final +from typing_extensions import TypeForm from pydantic import BaseModel @@ -96,13 +97,7 @@ def parse_query_params(params: dict[str, Any]) -> dict[str, Any]: class TaggedUnion(Generic[T]): __slots__ = ("discriminator", "tag", "type_") - @overload - def __init__(self, type_: type[T], discriminator: str, tag: str) -> None: ... - - @overload - def __init__(self, type_: Any, discriminator: str, tag: str) -> None: ... - - def __init__(self, type_: type[T], discriminator: str, tag: str) -> None: + def __init__(self, type_: TypeForm[T], discriminator: str, tag: str) -> None: self.type_ = type_ self.discriminator = discriminator self.tag = tag diff --git a/pyproject.toml b/pyproject.toml index 38aa0abcda..618a37150e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ dependencies = [ "anyio >=3.6.1, <5.0.0", "httpx >=0.23.0, <1.0.0", "hishel[async,httpx] >=1.4.0, <=2.0.0", - "typing-extensions >=4.11.0, <5.0.0", + "typing-extensions >=4.13.0, <5.0.0", "pydantic >=1.9.1, <3.0.0, !=2.5.0, !=2.5.1", "githubkit-schemas >=26.5.7", ] @@ -163,7 +163,6 @@ keep-runtime-typing = true [tool.pyright] typeCheckingMode = "standard" reportPrivateImportUsage = false -reportShadowedImports = false disableBytesTypePromotions = true pythonPlatform = "All" diff --git a/uv.lock b/uv.lock index 6a05649a9d..ae950c56ea 100644 --- a/uv.lock +++ b/uv.lock @@ -929,7 +929,7 @@ requires-dist = [ { name = "pyjwt", extras = ["crypto"], marker = "extra == 'auth'", specifier = ">=2.4.0,<3.0.0" }, { name = "pyjwt", extras = ["crypto"], marker = "extra == 'auth-app'", specifier = ">=2.4.0,<3.0.0" }, { name = "pyjwt", extras = ["crypto"], marker = "extra == 'jwt'", specifier = ">=2.4.0,<3.0.0" }, - { name = "typing-extensions", specifier = ">=4.11.0,<5.0.0" }, + { name = "typing-extensions", specifier = ">=4.13.0,<5.0.0" }, ] provides-extras = ["jwt", "auth-app", "auth-oauth-device", "auth", "all-schemas", "all"] From 558caeb8f647fe228f5c40556c5979c0de967962 Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:14:10 +0000 Subject: [PATCH 2/4] :rotating_light: make pyright happy --- githubkit/compat.py | 6 +++--- githubkit/core.py | 25 +++++++++++++------------ githubkit/response.py | 4 ++-- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/githubkit/compat.py b/githubkit/compat.py index 21926181e3..b5d327c700 100644 --- a/githubkit/compat.py +++ b/githubkit/compat.py @@ -1,5 +1,5 @@ from collections.abc import Callable, Generator -from typing import TYPE_CHECKING, Any, Protocol, TypeVar +from typing import TYPE_CHECKING, Any, Protocol, TypeVar, cast from typing_extensions import TypeForm from pydantic import VERSION @@ -85,10 +85,10 @@ class Config: extra = Extra.allow def type_validate_python(type_: TypeForm[T], data: Any) -> T: - return parse_obj_as(type_, data) + return parse_obj_as(cast(type[T], type_), data) def type_validate_json(type_: TypeForm[T], data: Any) -> T: - return parse_raw_as(type_, data) + return parse_raw_as(cast(type[T], type_), data) def to_jsonable_python(obj: Any) -> Any: if isinstance(obj, dict): diff --git a/githubkit/core.py b/githubkit/core.py index 8c0f0fdad7..047b75a5a5 100644 --- a/githubkit/core.py +++ b/githubkit/core.py @@ -5,6 +5,7 @@ import time from types import TracebackType from typing import TYPE_CHECKING, Any, Generic, TypeVar, overload +from typing_extensions import TypeForm import anyio @@ -396,12 +397,16 @@ async def _arequest( raise RequestError(e) from e # check and parse response + def _check_is_error(self, response: httpx.Response) -> bool: + """Check if the response is an error.""" + return response.is_error + @overload def _check( self, response: httpx.Response, - response_model: type[T], - error_models: Mapping[str, type] | None = None, + response_model: TypeForm[T], + error_models: Mapping[str, Any] | None = None, ) -> Response[T]: ... @overload @@ -409,17 +414,13 @@ def _check( self, response: httpx.Response, response_model: UnsetType = UNSET, - error_models: Mapping[str, type] | None = None, + error_models: Mapping[str, Any] | None = None, ) -> Response[Any]: ... - def _check_is_error(self, response: httpx.Response) -> bool: - """Check if the response is an error.""" - return response.is_error - def _check( self, response: httpx.Response, - response_model: type[T] | UnsetType = UNSET, + response_model: TypeForm[T] | UnsetType = UNSET, error_models: Mapping[str, Any] | None = None, ) -> Response[T] | Response[Any]: if self._check_is_error(response): @@ -510,7 +511,7 @@ def request( headers: HeaderTypes | None = None, cookies: CookieTypes | None = None, stream: bool = False, - response_model: type[T], + response_model: TypeForm[T], error_models: Mapping[str, Any] | None = None, ) -> Response[T]: ... @@ -545,7 +546,7 @@ def request( headers: HeaderTypes | None = None, cookies: CookieTypes | None = None, stream: bool = False, - response_model: type[T] | UnsetType = UNSET, + response_model: TypeForm[T] | UnsetType = UNSET, error_models: Mapping[str, Any] | None = None, ) -> Response[T] | Response[Any]: """Send a request. @@ -600,7 +601,7 @@ async def arequest( headers: HeaderTypes | None = None, cookies: CookieTypes | None = None, stream: bool = False, - response_model: type[T], + response_model: TypeForm[T], error_models: Mapping[str, Any] | None = None, ) -> Response[T]: ... @@ -635,7 +636,7 @@ async def arequest( headers: HeaderTypes | None = None, cookies: CookieTypes | None = None, stream: bool = False, - response_model: type[T] | UnsetType = UNSET, + response_model: TypeForm[T] | UnsetType = UNSET, error_models: Mapping[str, Any] | None = None, ) -> Response[T] | Response[Any]: """Asynchronously send a request. diff --git a/githubkit/response.py b/githubkit/response.py index 85ac617f27..0f35c40461 100644 --- a/githubkit/response.py +++ b/githubkit/response.py @@ -1,7 +1,7 @@ from collections.abc import AsyncIterator, Iterator from contextlib import asynccontextmanager, contextmanager from typing import Any, Generic -from typing_extensions import TypeVar +from typing_extensions import TypeForm, TypeVar from githubkit._httpx import httpx @@ -15,7 +15,7 @@ class Response(Generic[MT, JT]): """A wrapper around httpx.Response that provides data model validation.""" - def __init__(self, response: httpx.Response, data_model: type[MT]): + def __init__(self, response: httpx.Response, data_model: TypeForm[MT]): self._response = response self._data_model = data_model self._cached_parsed_data: MT | None = None From a8c2382548ecb8295181ae37c9aa8f2aeb622e99 Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:53:35 +0000 Subject: [PATCH 3/4] :rotating_light: make pyright happy --- .github/workflows/pyright.yml | 11 ++++++++++- packages/pyproject.toml | 10 ++++++++++ pyproject.toml | 9 +-------- 3 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 packages/pyproject.toml diff --git a/.github/workflows/pyright.yml b/.github/workflows/pyright.yml index 7925432f77..229794b749 100644 --- a/.github/workflows/pyright.yml +++ b/.github/workflows/pyright.yml @@ -15,6 +15,7 @@ on: - "uv.lock" - "packages/**/*.py" - "packages/**/pyproject.toml" + - "packages/pyproject.toml" jobs: lint-githubkit: @@ -47,7 +48,15 @@ jobs: uses: jakebailey/pyright-action@v3 with: pylance-version: latest-release - extra-args: githubkit packages + extra-args: githubkit + env: + NODE_OPTIONS: --max-old-space-size=12288 + + - name: Run Pyright on generated schemas + uses: jakebailey/pyright-action@v3 + with: + working-directory: packages + pylance-version: latest-release env: NODE_OPTIONS: --max-old-space-size=12288 diff --git a/packages/pyproject.toml b/packages/pyproject.toml new file mode 100644 index 0000000000..6e1841d13a --- /dev/null +++ b/packages/pyproject.toml @@ -0,0 +1,10 @@ +[tool.pyright] +extends = "../pyproject.toml" + +# disable overload check for generated version codes +reportOverlappingOverload = false +reportIncompatibleMethodOverride = false + +executionEnvironments = [ + { root = ".", pythonVersion = "3.10" }, +] diff --git a/pyproject.toml b/pyproject.toml index 618a37150e..f6fafd5069 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -166,16 +166,9 @@ reportPrivateImportUsage = false disableBytesTypePromotions = true pythonPlatform = "All" +pythonVersion = "3.10" defineConstant = { PYDANTIC_V2 = true } -executionEnvironments = [ - # disable overload check for generated version codes - { root = "packages/**/githubkit_schemas/", pythonVersion = "3.10", reportOverlappingOverload = false }, - # codegen env config for dev - { root = "codegen", pythonVersion = "3.10" }, - { root = ".", pythonVersion = "3.10" }, -] - [tool.codegen] schemas_output_dir = "packages/githubkit-schemas/githubkit_schemas/" From 7d5dc531e5e092768abdfbcb642d5ac32636f15a Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:51:23 +0000 Subject: [PATCH 4/4] :construction_worker: fix ci --- .github/workflows/pyright.yml | 2 +- .gitignore | 2 +- packages/pyproject.toml | 10 ---------- packages/pyrightconfig.json | 5 +++++ pyproject.toml | 8 ++++++++ 5 files changed, 15 insertions(+), 12 deletions(-) delete mode 100644 packages/pyproject.toml create mode 100644 packages/pyrightconfig.json diff --git a/.github/workflows/pyright.yml b/.github/workflows/pyright.yml index 229794b749..20e8b59099 100644 --- a/.github/workflows/pyright.yml +++ b/.github/workflows/pyright.yml @@ -15,7 +15,7 @@ on: - "uv.lock" - "packages/**/*.py" - "packages/**/pyproject.toml" - - "packages/pyproject.toml" + - "packages/pyrightconfig.json" jobs: lint-githubkit: diff --git a/.gitignore b/.gitignore index 1d70343334..c5bb88ab9a 100644 --- a/.gitignore +++ b/.gitignore @@ -473,7 +473,7 @@ poetry.toml .ruff_cache/ # LSP config files -pyrightconfig.json +# pyrightconfig.json ### VisualStudioCode ### .vscode/* diff --git a/packages/pyproject.toml b/packages/pyproject.toml deleted file mode 100644 index 6e1841d13a..0000000000 --- a/packages/pyproject.toml +++ /dev/null @@ -1,10 +0,0 @@ -[tool.pyright] -extends = "../pyproject.toml" - -# disable overload check for generated version codes -reportOverlappingOverload = false -reportIncompatibleMethodOverride = false - -executionEnvironments = [ - { root = ".", pythonVersion = "3.10" }, -] diff --git a/packages/pyrightconfig.json b/packages/pyrightconfig.json new file mode 100644 index 0000000000..cc67d5d17d --- /dev/null +++ b/packages/pyrightconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../pyproject.toml", + "reportOverlappingOverload": false, + "reportIncompatibleMethodOverride": false +} diff --git a/pyproject.toml b/pyproject.toml index f6fafd5069..a6afce6027 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -169,6 +169,14 @@ pythonPlatform = "All" pythonVersion = "3.10" defineConstant = { PYDANTIC_V2 = true } +executionEnvironments = [ + # disable overload and override checks for generated version codes + { root = "packages/**/githubkit_schemas/", pythonVersion = "3.10", reportOverlappingOverload = false, reportIncompatibleMethodOverride = false }, + # codegen env config for dev + { root = "codegen", pythonVersion = "3.10" }, + { root = ".", pythonVersion = "3.10" }, +] + [tool.codegen] schemas_output_dir = "packages/githubkit-schemas/githubkit_schemas/"