diff --git a/.github/workflows/pyright.yml b/.github/workflows/pyright.yml index 29f6a654a..20e8b5909 100644 --- a/.github/workflows/pyright.yml +++ b/.github/workflows/pyright.yml @@ -15,6 +15,7 @@ on: - "uv.lock" - "packages/**/*.py" - "packages/**/pyproject.toml" + - "packages/pyrightconfig.json" jobs: lint-githubkit: @@ -51,6 +52,14 @@ jobs: 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 + lint-codegen: name: Codegen Lint runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 1d7034333..c5bb88ab9 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/codegen/parser/utils.py b/codegen/parser/utils.py index 30d0e8e2c..39c0009fc 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 ddb675c96..b5d327c70 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, cast +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,23 +84,11 @@ 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: - 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_python(type_: TypeForm[T], data: Any) -> T: + return parse_obj_as(cast(type[T], type_), data) - def type_validate_json(type_: type[T], data: Any) -> T: - return parse_raw_as(type_, data) + def type_validate_json(type_: TypeForm[T], data: Any) -> T: + 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 8c0f0fdad..047b75a5a 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 85ac617f2..0f35c4046 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 diff --git a/githubkit/utils.py b/githubkit/utils.py index 864dce6c5..af840f1d2 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/packages/pyrightconfig.json b/packages/pyrightconfig.json new file mode 100644 index 000000000..cc67d5d17 --- /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 38aa0abcd..a6afce602 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,15 +163,15 @@ keep-runtime-typing = true [tool.pyright] typeCheckingMode = "standard" reportPrivateImportUsage = false -reportShadowedImports = 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 }, + # 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" }, diff --git a/uv.lock b/uv.lock index 6a05649a9..ae950c56e 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"]