Browse Source

Merge remote-tracking branch 'upstream/master' into deprecate-scopes-parameter

pull/14407/head
Yurii Motov 5 months ago
parent
commit
fb4f9f3382
  1. 3
      docs/en/docs/release-notes.md
  2. 7
      fastapi/dependencies/utils.py
  3. 4
      fastapi/encoders.py
  4. 2
      fastapi/security/utils.py
  5. 6
      tests/test_security_http_base.py
  6. 6
      tests/test_security_oauth2_authorization_code_bearer.py
  7. 30
      tests/test_stringified_annotation_dependency_py314.py
  8. 10
      tests/test_tutorial/test_dependencies/test_tutorial008.py
  9. 4
      tests/utils.py

3
docs/en/docs/release-notes.md

@ -14,10 +14,13 @@ hide:
### Fixes ### Fixes
* 🐛 Fix TYPE_CHECKING annotations for Python 3.14 (PEP 649). PR [#14789](https://github.com/fastapi/fastapi/pull/14789) by [@mgu](https://github.com/mgu).
* 🐛 Strip whitespaces from `Authorization` header credentials. PR [#14786](https://github.com/fastapi/fastapi/pull/14786) by [@WaveTheory1](https://github.com/WaveTheory1).
* 🐛 Fix OpenAPI duplication of `anyOf` refs for app-level responses with specified `content` and `model` as `Union`. PR [#14463](https://github.com/fastapi/fastapi/pull/14463) by [@DJMcoder](https://github.com/DJMcoder). * 🐛 Fix OpenAPI duplication of `anyOf` refs for app-level responses with specified `content` and `model` as `Union`. PR [#14463](https://github.com/fastapi/fastapi/pull/14463) by [@DJMcoder](https://github.com/DJMcoder).
### Refactors ### Refactors
* 🎨 Tweak types for mypy. PR [#14816](https://github.com/fastapi/fastapi/pull/14816) by [@tiangolo](https://github.com/tiangolo).
* 🏷️ Re-export `IncEx` type from Pydantic instead of duplicating it. PR [#14641](https://github.com/fastapi/fastapi/pull/14641) by [@mvanderlee](https://github.com/mvanderlee). * 🏷️ Re-export `IncEx` type from Pydantic instead of duplicating it. PR [#14641](https://github.com/fastapi/fastapi/pull/14641) by [@mvanderlee](https://github.com/mvanderlee).
* 💡 Update comment for Pydantic internals. PR [#14814](https://github.com/fastapi/fastapi/pull/14814) by [@tiangolo](https://github.com/tiangolo). * 💡 Update comment for Pydantic internals. PR [#14814](https://github.com/fastapi/fastapi/pull/14814) by [@tiangolo](https://github.com/tiangolo).

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

4
fastapi/encoders.py

@ -219,9 +219,9 @@ def jsonable_encoder(
if isinstance(obj, encoder_type): if isinstance(obj, encoder_type):
return encoder_instance(obj) return encoder_instance(obj)
if include is not None and not isinstance(include, (set, dict)): if include is not None and not isinstance(include, (set, dict)):
include = set(include) include = set(include) # type: ignore[assignment]
if exclude is not None and not isinstance(exclude, (set, dict)): if exclude is not None and not isinstance(exclude, (set, dict)):
exclude = set(exclude) exclude = set(exclude) # type: ignore[assignment]
if isinstance(obj, BaseModel): if isinstance(obj, BaseModel):
obj_dict = obj.model_dump( obj_dict = obj.model_dump(
mode="json", mode="json",

2
fastapi/security/utils.py

@ -7,4 +7,4 @@ def get_authorization_scheme_param(
if not authorization_header_value: if not authorization_header_value:
return "", "" return "", ""
scheme, _, param = authorization_header_value.partition(" ") scheme, _, param = authorization_header_value.partition(" ")
return scheme, param return scheme, param.strip()

6
tests/test_security_http_base.py

@ -21,6 +21,12 @@ def test_security_http_base():
assert response.json() == {"scheme": "Other", "credentials": "foobar"} assert response.json() == {"scheme": "Other", "credentials": "foobar"}
def test_security_http_base_with_whitespaces():
response = client.get("/users/me", headers={"Authorization": "Other foobar "})
assert response.status_code == 200, response.text
assert response.json() == {"scheme": "Other", "credentials": "foobar"}
def test_security_http_base_no_credentials(): def test_security_http_base_no_credentials():
response = client.get("/users/me") response = client.get("/users/me")
assert response.status_code == 401, response.text assert response.status_code == 401, response.text

6
tests/test_security_oauth2_authorization_code_bearer.py

@ -37,6 +37,12 @@ def test_token():
assert response.json() == {"token": "testtoken"} assert response.json() == {"token": "testtoken"}
def test_token_with_whitespaces():
response = client.get("/items", headers={"Authorization": "Bearer testtoken "})
assert response.status_code == 200, response.text
assert response.json() == {"token": "testtoken"}
def test_openapi_schema(): def test_openapi_schema():
response = client.get("/openapi.json") response = client.get("/openapi.json")
assert response.status_code == 200, response.text assert response.status_code == 200, response.text

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