Browse Source

🐛 Fix TYPE_CHECKING annotations for Python 3.14 (PEP 649) (#14789)

pull/14816/head
Mickaël Guérin 4 months ago
committed by GitHub
parent
commit
09f5941f0e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      fastapi/dependencies/utils.py
  2. 30
      tests/test_stringified_annotation_dependency_py314.py
  3. 10
      tests/test_tutorial/test_dependencies/test_tutorial008.py
  4. 4
      tests/utils.py

7
fastapi/dependencies/utils.py

@ -204,7 +204,12 @@ def _get_signature(call: Callable[..., Any]) -> inspect.Signature:
except NameError: except NameError:
# Handle type annotations with if TYPE_CHECKING, not used by FastAPI # Handle type annotations with if TYPE_CHECKING, not used by FastAPI
# e.g. dependency return types # e.g. dependency return types
signature = inspect.signature(call) if sys.version_info >= (3, 14):
from annotationlib import Format
signature = inspect.signature(call, annotation_format=Format.FORWARDREF)
else:
signature = inspect.signature(call)
else: else:
signature = inspect.signature(call) signature = inspect.signature(call)
return signature return signature

30
tests/test_stringified_annotation_dependency_py314.py

@ -0,0 +1,30 @@
from typing import TYPE_CHECKING, Annotated
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient
from .utils import needs_py314
if TYPE_CHECKING: # pragma: no cover
class DummyUser: ...
@needs_py314
def test_stringified_annotation():
# python3.14: Use forward reference without "from __future__ import annotations"
async def get_current_user() -> DummyUser | None:
return None
app = FastAPI()
client = TestClient(app)
@app.get("/")
async def get(
current_user: Annotated[DummyUser | None, Depends(get_current_user)],
) -> str:
return "hello world"
response = client.get("/")
assert response.status_code == 200

10
tests/test_tutorial/test_dependencies/test_tutorial008.py

@ -1,4 +1,5 @@
import importlib import importlib
import sys
from types import ModuleType from types import ModuleType
from typing import Annotated, Any from typing import Annotated, Any
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
@ -12,8 +13,13 @@ from fastapi.testclient import TestClient
name="module", name="module",
params=[ params=[
"tutorial008_py39", "tutorial008_py39",
# Fails with `NameError: name 'DepA' is not defined` pytest.param(
pytest.param("tutorial008_an_py39", marks=pytest.mark.xfail), "tutorial008_an_py39",
marks=pytest.mark.xfail(
sys.version_info < (3, 14),
reason="Fails with `NameError: name 'DepA' is not defined`",
),
),
], ],
) )
def get_module(request: pytest.FixtureRequest): def get_module(request: pytest.FixtureRequest):

4
tests/utils.py

@ -6,8 +6,8 @@ needs_py39 = pytest.mark.skipif(sys.version_info < (3, 9), reason="requires pyth
needs_py310 = pytest.mark.skipif( needs_py310 = pytest.mark.skipif(
sys.version_info < (3, 10), reason="requires python3.10+" sys.version_info < (3, 10), reason="requires python3.10+"
) )
needs_py_lt_314 = pytest.mark.skipif( needs_py314 = pytest.mark.skipif(
sys.version_info >= (3, 14), reason="requires python3.13-" sys.version_info < (3, 14), reason="requires python3.14+"
) )

Loading…
Cancel
Save