diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 3b978297c..83147f15f 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -1,4 +1,6 @@ import inspect +import sys +import typing from contextlib import AsyncExitStack, contextmanager from copy import copy, deepcopy from dataclasses import dataclass @@ -19,6 +21,7 @@ from typing import ( ) import anyio +import typing_extensions from fastapi import params from fastapi._compat import ( PYDANTIC_V2, @@ -71,13 +74,12 @@ from starlette.datastructures import ( from starlette.requests import HTTPConnection, Request from starlette.responses import Response from starlette.websockets import WebSocket -from typing_extensions import Annotated, get_args, get_origin +from typing_extensions import Annotated, TypeAliasType, TypeGuard, get_args, get_origin try: - from typing_extensions import TypeAliasType + from types import GenericAlias except ImportError: # pragma: no cover - TypeAliasType = None # type: ignore[misc,assignment] - + GenericAlias = None # type: ignore[misc,assignment] multipart_not_installed_error = ( 'Form data requires "python-multipart" to be installed. \n' @@ -362,7 +364,7 @@ def analyze_param( depends = None type_annotation: Any = Any use_annotation: Any = Any - if TypeAliasType is not None and isinstance(annotation, TypeAliasType): + if _is_typealiastype(annotation): # unpack in case py3.12 type syntax is used annotation = annotation.__value__ if annotation is not inspect.Signature.empty: @@ -1008,3 +1010,26 @@ def get_body_field( field_info=BodyFieldInfo(**BodyFieldInfo_kwargs), ) return final_field + + +def _is_typealiastype(tp: Any, /) -> TypeGuard[TypeAliasType]: + in_typing = hasattr(typing, "TypeAliasType") + in_typing_extensions = hasattr(typing_extensions, "TypeAliasType") + is_typealiastype = False + if in_typing and in_typing_extensions: + if getattr(typing, "TypeAliasType", None) is getattr( + typing_extensions, "TypeAliasType", None + ): # pragma: no cover + is_typealiastype = isinstance(tp, typing.TypeAliasType) # type: ignore [attr-defined] + else: + is_typealiastype = isinstance( + tp, + (typing.TypeAliasType, typing_extensions.TypeAliasType), # type: ignore [attr-defined] + ) + elif in_typing and not in_typing_extensions: # pragma: no cover + is_typealiastype = isinstance(tp, typing.TypeAliasType) # type: ignore [attr-defined] + elif not in_typing and in_typing_extensions: + is_typealiastype = isinstance(tp, typing_extensions.TypeAliasType) + if sys.version_info[:2] == (3, 10): + return type(tp) is not GenericAlias and is_typealiastype + return is_typealiastype diff --git a/pyproject.toml b/pyproject.toml index 28e7f6805..7709451ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -199,7 +199,6 @@ dynamic_context = "test_function" omit = [ "docs_src/response_model/tutorial003_04.py", "docs_src/response_model/tutorial003_04_py310.py", - "tests/test_dependency_pep695_py312.py" # syntax error for version < py312 ] [tool.coverage.report] diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 521e9f0f7..000000000 --- a/tests/conftest.py +++ /dev/null @@ -1,4 +0,0 @@ -import sys - -if sys.version_info < (3, 12): - collect_ignore_glob = ["*_py312.py"] diff --git a/tests/test_dependency_pep695_py312.py b/tests/test_dependency_pep695.py similarity index 75% rename from tests/test_dependency_pep695_py312.py rename to tests/test_dependency_pep695.py index 378c9d3ee..a60c1e5e6 100644 --- a/tests/test_dependency_pep695_py312.py +++ b/tests/test_dependency_pep695.py @@ -1,21 +1,19 @@ from __future__ import annotations -from typing import Annotated - from fastapi import Depends, FastAPI from fastapi.testclient import TestClient - -from .utils import needs_py312 +from typing_extensions import Annotated, TypeAliasType async def some_value() -> int: return 123 -type DependedValue = Annotated[int, Depends(some_value)] +DependedValue = TypeAliasType( + "DependedValue", Annotated[int, Depends(some_value)], type_params=() +) -@needs_py312 def test_pep695_type_dependencies(): app = FastAPI() diff --git a/tests/utils.py b/tests/utils.py index 51d8fabfd..ae9543e3b 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -8,9 +8,6 @@ needs_py39 = pytest.mark.skipif(sys.version_info < (3, 9), reason="requires pyth needs_py310 = pytest.mark.skipif( sys.version_info < (3, 10), reason="requires python3.10+" ) -needs_py312 = pytest.mark.skipif( - sys.version_info < (3, 12), reason="requires python3.12+" -) needs_pydanticv2 = pytest.mark.skipif(not PYDANTIC_V2, reason="requires Pydantic v2") needs_pydanticv1 = pytest.mark.skipif(PYDANTIC_V2, reason="requires Pydantic v1")