Browse Source

Merge branch 'master' into update-missing-translation-warning

pull/14257/head
Motov Yurii 9 months ago
committed by GitHub
parent
commit
2da187ae04
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      README.md
  2. 3
      docs/en/data/sponsors.yml
  3. BIN
      docs/en/docs/img/sponsors/serpapi-banner.png
  4. BIN
      docs/en/docs/img/sponsors/serpapi.png
  5. 12
      docs/en/docs/release-notes.md
  6. 6
      docs/en/overrides/main.html
  7. 2
      fastapi/__init__.py
  8. 28
      fastapi/_compat/v2.py
  9. 33
      fastapi/dependencies/utils.py
  10. 25
      fastapi/params.py
  11. 203
      tests/test_no_schema_split.py
  12. 11
      tests/test_params_repr.py
  13. 15
      tests/test_pydantic_v1_v2_multifile/test_multifile.py

1
README.md

@ -55,6 +55,7 @@ The key features are:
<a href="https://www.coderabbit.ai/?utm_source=fastapi&utm_medium=badge&utm_campaign=fastapi" target="_blank" title="Cut Code Review Time & Bugs in Half with CodeRabbit"><img src="https://fastapi.tiangolo.com/img/sponsors/coderabbit.png"></a> <a href="https://www.coderabbit.ai/?utm_source=fastapi&utm_medium=badge&utm_campaign=fastapi" target="_blank" title="Cut Code Review Time & Bugs in Half with CodeRabbit"><img src="https://fastapi.tiangolo.com/img/sponsors/coderabbit.png"></a>
<a href="https://subtotal.com/?utm_source=fastapi&utm_medium=sponsorship&utm_campaign=open-source" target="_blank" title="The Gold Standard in Retail Account Linking"><img src="https://fastapi.tiangolo.com/img/sponsors/subtotal.svg"></a> <a href="https://subtotal.com/?utm_source=fastapi&utm_medium=sponsorship&utm_campaign=open-source" target="_blank" title="The Gold Standard in Retail Account Linking"><img src="https://fastapi.tiangolo.com/img/sponsors/subtotal.svg"></a>
<a href="https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi" target="_blank" title="Deploy enterprise applications at startup speed"><img src="https://fastapi.tiangolo.com/img/sponsors/railway.png"></a> <a href="https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi" target="_blank" title="Deploy enterprise applications at startup speed"><img src="https://fastapi.tiangolo.com/img/sponsors/railway.png"></a>
<a href="https://serpapi.com/?utm_source=fastapi_website" target="_blank" title="SerpApi: Web Search API"><img src="https://fastapi.tiangolo.com/img/sponsors/serpapi.png"></a>
<a href="https://databento.com/?utm_source=fastapi&utm_medium=sponsor&utm_content=display" target="_blank" title="Pay as you go for market data"><img src="https://fastapi.tiangolo.com/img/sponsors/databento.svg"></a> <a href="https://databento.com/?utm_source=fastapi&utm_medium=sponsor&utm_content=display" target="_blank" title="Pay as you go for market data"><img src="https://fastapi.tiangolo.com/img/sponsors/databento.svg"></a>
<a href="https://speakeasy.com/editor?utm_source=fastapi+repo&utm_medium=github+sponsorship" target="_blank" title="SDKs for your API | Speakeasy"><img src="https://fastapi.tiangolo.com/img/sponsors/speakeasy.png"></a> <a href="https://speakeasy.com/editor?utm_source=fastapi+repo&utm_medium=github+sponsorship" target="_blank" title="SDKs for your API | Speakeasy"><img src="https://fastapi.tiangolo.com/img/sponsors/speakeasy.png"></a>
<a href="https://www.svix.com/" target="_blank" title="Svix - Webhooks as a service"><img src="https://fastapi.tiangolo.com/img/sponsors/svix.svg"></a> <a href="https://www.svix.com/" target="_blank" title="Svix - Webhooks as a service"><img src="https://fastapi.tiangolo.com/img/sponsors/svix.svg"></a>

3
docs/en/data/sponsors.yml

@ -26,6 +26,9 @@ gold:
- url: https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi - url: https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi
title: Deploy enterprise applications at startup speed title: Deploy enterprise applications at startup speed
img: https://fastapi.tiangolo.com/img/sponsors/railway.png img: https://fastapi.tiangolo.com/img/sponsors/railway.png
- url: https://serpapi.com/?utm_source=fastapi_website
title: "SerpApi: Web Search API"
img: https://fastapi.tiangolo.com/img/sponsors/serpapi.png
silver: silver:
- url: https://databento.com/?utm_source=fastapi&utm_medium=sponsor&utm_content=display - url: https://databento.com/?utm_source=fastapi&utm_medium=sponsor&utm_content=display
title: Pay as you go for market data title: Pay as you go for market data

BIN
docs/en/docs/img/sponsors/serpapi-banner.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
docs/en/docs/img/sponsors/serpapi.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

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

@ -7,8 +7,20 @@ hide:
## Latest Changes ## Latest Changes
### Refactors
* ♻️ Refactor internals of dependencies, simplify code and remove `get_param_sub_dependant`. PR [#14255](https://github.com/fastapi/fastapi/pull/14255) by [@tiangolo](https://github.com/tiangolo).
* ♻️ Refactor internals of dependencies, simplify using dataclasses. PR [#14254](https://github.com/fastapi/fastapi/pull/14254) by [@tiangolo](https://github.com/tiangolo).
## 0.120.2
### Fixes
* 🐛 Fix separation of schemas with nested models introduced in 0.119.0. PR [#14246](https://github.com/fastapi/fastapi/pull/14246) by [@tiangolo](https://github.com/tiangolo).
### Internal ### Internal
* 🔧 Add sponsor: SerpApi. PR [#14248](https://github.com/fastapi/fastapi/pull/14248) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Bump actions/download-artifact from 5 to 6. PR [#14236](https://github.com/fastapi/fastapi/pull/14236) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump actions/download-artifact from 5 to 6. PR [#14236](https://github.com/fastapi/fastapi/pull/14236) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#14237](https://github.com/fastapi/fastapi/pull/14237) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#14237](https://github.com/fastapi/fastapi/pull/14237) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
* ⬆ Bump actions/upload-artifact from 4 to 5. PR [#14235](https://github.com/fastapi/fastapi/pull/14235) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump actions/upload-artifact from 4 to 5. PR [#14235](https://github.com/fastapi/fastapi/pull/14235) by [@dependabot[bot]](https://github.com/apps/dependabot).

6
docs/en/overrides/main.html

@ -80,6 +80,12 @@
<img class="sponsor-image" src="/img/sponsors/railway-banner.png" /> <img class="sponsor-image" src="/img/sponsors/railway-banner.png" />
</a> </a>
</div> </div>
<div class="item">
<a title="SerpApi: Web Search API" style="display: block; position: relative;" href="https://serpapi.com/?utm_source=fastapi_website" target="_blank">
<span class="sponsor-badge">sponsor</span>
<img class="sponsor-image" src="/img/sponsors/serpapi-banner.png" />
</a>
</div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

2
fastapi/__init__.py

@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production""" """FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.120.1" __version__ = "0.120.2"
from starlette import status as status from starlette import status as status

28
fastapi/_compat/v2.py

@ -207,11 +207,31 @@ def get_definitions(
override_mode: Union[Literal["validation"], None] = ( override_mode: Union[Literal["validation"], None] = (
None if separate_input_output_schemas else "validation" None if separate_input_output_schemas else "validation"
) )
flat_models = get_flat_models_from_fields(fields, known_models=set()) validation_fields = [field for field in fields if field.mode == "validation"]
flat_model_fields = [ serialization_fields = [field for field in fields if field.mode == "serialization"]
ModelField(field_info=FieldInfo(annotation=model), name=model.__name__) flat_validation_models = get_flat_models_from_fields(
for model in flat_models validation_fields, known_models=set()
)
flat_serialization_models = get_flat_models_from_fields(
serialization_fields, known_models=set()
)
flat_validation_model_fields = [
ModelField(
field_info=FieldInfo(annotation=model),
name=model.__name__,
mode="validation",
)
for model in flat_validation_models
]
flat_serialization_model_fields = [
ModelField(
field_info=FieldInfo(annotation=model),
name=model.__name__,
mode="serialization",
)
for model in flat_serialization_models
] ]
flat_model_fields = flat_validation_model_fields + flat_serialization_model_fields
input_types = {f.type_ for f in fields} input_types = {f.type_ for f in fields}
unique_flat_model_fields = { unique_flat_model_fields = {
f for f in flat_model_fields if f.type_ not in input_types f for f in flat_model_fields if f.type_ not in input_types

33
fastapi/dependencies/utils.py

@ -125,23 +125,6 @@ def ensure_multipart_is_installed() -> None:
raise RuntimeError(multipart_not_installed_error) from None raise RuntimeError(multipart_not_installed_error) from None
def get_param_sub_dependant(
*,
param_name: str,
depends: params.Depends,
path: str,
security_scopes: Optional[List[str]] = None,
) -> Dependant:
assert depends.dependency
return get_sub_dependant(
depends=depends,
dependency=depends.dependency,
path=path,
name=param_name,
security_scopes=security_scopes,
)
def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant: def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:
assert callable(depends.dependency), ( assert callable(depends.dependency), (
"A parameter-less dependency must have a callable dependency" "A parameter-less dependency must have a callable dependency"
@ -160,8 +143,8 @@ def get_sub_dependant(
security_requirement = None security_requirement = None
security_scopes = security_scopes or [] security_scopes = security_scopes or []
if isinstance(depends, params.Security): if isinstance(depends, params.Security):
dependency_scopes = depends.scopes if depends.scopes:
security_scopes.extend(dependency_scopes) security_scopes.extend(depends.scopes)
if isinstance(dependency, SecurityBase): if isinstance(dependency, SecurityBase):
use_scopes: List[str] = [] use_scopes: List[str] = []
if isinstance(dependency, (OAuth2, OpenIdConnect)): if isinstance(dependency, (OAuth2, OpenIdConnect)):
@ -282,9 +265,6 @@ def get_dependant(
security_scopes: Optional[List[str]] = None, security_scopes: Optional[List[str]] = None,
use_cache: bool = True, use_cache: bool = True,
) -> Dependant: ) -> Dependant:
path_param_names = get_path_param_names(path)
endpoint_signature = get_typed_signature(call)
signature_params = endpoint_signature.parameters
dependant = Dependant( dependant = Dependant(
call=call, call=call,
name=name, name=name,
@ -292,6 +272,9 @@ def get_dependant(
security_scopes=security_scopes, security_scopes=security_scopes,
use_cache=use_cache, use_cache=use_cache,
) )
path_param_names = get_path_param_names(path)
endpoint_signature = get_typed_signature(call)
signature_params = endpoint_signature.parameters
for param_name, param in signature_params.items(): for param_name, param in signature_params.items():
is_path_param = param_name in path_param_names is_path_param = param_name in path_param_names
param_details = analyze_param( param_details = analyze_param(
@ -301,10 +284,12 @@ def get_dependant(
is_path_param=is_path_param, is_path_param=is_path_param,
) )
if param_details.depends is not None: if param_details.depends is not None:
sub_dependant = get_param_sub_dependant( assert param_details.depends.dependency
param_name=param_name, sub_dependant = get_sub_dependant(
depends=param_details.depends, depends=param_details.depends,
dependency=param_details.depends.dependency,
path=path, path=path,
name=param_name,
security_scopes=security_scopes, security_scopes=security_scopes,
) )
dependant.dependencies.append(sub_dependant) dependant.dependencies.append(sub_dependant)

25
fastapi/params.py

@ -1,4 +1,5 @@
import warnings import warnings
from dataclasses import dataclass
from enum import Enum from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Sequence, Union from typing import Any, Callable, Dict, List, Optional, Sequence, Union
@ -761,26 +762,12 @@ class File(Form): # type: ignore[misc]
) )
@dataclass
class Depends: class Depends:
def __init__( dependency: Optional[Callable[..., Any]] = None
self, dependency: Optional[Callable[..., Any]] = None, *, use_cache: bool = True use_cache: bool = True
):
self.dependency = dependency
self.use_cache = use_cache
def __repr__(self) -> str:
attr = getattr(self.dependency, "__name__", type(self.dependency).__name__)
cache = "" if self.use_cache else ", use_cache=False"
return f"{self.__class__.__name__}({attr}{cache})"
@dataclass
class Security(Depends): class Security(Depends):
def __init__( scopes: Optional[Sequence[str]] = None
self,
dependency: Optional[Callable[..., Any]] = None,
*,
scopes: Optional[Sequence[str]] = None,
use_cache: bool = True,
):
super().__init__(dependency=dependency, use_cache=use_cache)
self.scopes = scopes or []

203
tests/test_no_schema_split.py

@ -0,0 +1,203 @@
# Test with parts from, and to verify the report in:
# https://github.com/fastapi/fastapi/discussions/14177
# Made an issue in:
# https://github.com/fastapi/fastapi/issues/14247
from enum import Enum
from typing import List
from fastapi import FastAPI
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
from pydantic import BaseModel, Field
from tests.utils import pydantic_snapshot
class MessageEventType(str, Enum):
alpha = "alpha"
beta = "beta"
class MessageEvent(BaseModel):
event_type: MessageEventType = Field(default=MessageEventType.alpha)
output: str
class MessageOutput(BaseModel):
body: str = ""
events: List[MessageEvent] = []
class Message(BaseModel):
input: str
output: MessageOutput
app = FastAPI(title="Minimal FastAPI App", version="1.0.0")
@app.post("/messages", response_model=Message)
async def create_message(input_message: str) -> Message:
return Message(
input=input_message,
output=MessageOutput(body=f"Processed: {input_message}"),
)
client = TestClient(app)
def test_create_message():
response = client.post("/messages", params={"input_message": "Hello"})
assert response.status_code == 200, response.text
assert response.json() == {
"input": "Hello",
"output": {"body": "Processed: Hello", "events": []},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == snapshot(
{
"openapi": "3.1.0",
"info": {"title": "Minimal FastAPI App", "version": "1.0.0"},
"paths": {
"/messages": {
"post": {
"summary": "Create Message",
"operationId": "create_message_messages_post",
"parameters": [
{
"name": "input_message",
"in": "query",
"required": True,
"schema": {"type": "string", "title": "Input Message"},
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Message"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
}
},
"components": {
"schemas": {
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"type": "array",
"title": "Detail",
}
},
"type": "object",
"title": "HTTPValidationError",
},
"Message": {
"properties": {
"input": {"type": "string", "title": "Input"},
"output": {"$ref": "#/components/schemas/MessageOutput"},
},
"type": "object",
"required": ["input", "output"],
"title": "Message",
},
"MessageEvent": {
"properties": {
"event_type": pydantic_snapshot(
v2=snapshot(
{
"$ref": "#/components/schemas/MessageEventType",
"default": "alpha",
}
),
v1=snapshot(
{
"allOf": [
{
"$ref": "#/components/schemas/MessageEventType"
}
],
"default": "alpha",
}
),
),
"output": {"type": "string", "title": "Output"},
},
"type": "object",
"required": ["output"],
"title": "MessageEvent",
},
"MessageEventType": pydantic_snapshot(
v2=snapshot(
{
"type": "string",
"enum": ["alpha", "beta"],
"title": "MessageEventType",
}
),
v1=snapshot(
{
"type": "string",
"enum": ["alpha", "beta"],
"title": "MessageEventType",
"description": "An enumeration.",
}
),
),
"MessageOutput": {
"properties": {
"body": {"type": "string", "title": "Body", "default": ""},
"events": {
"items": {"$ref": "#/components/schemas/MessageEvent"},
"type": "array",
"title": "Events",
"default": [],
},
},
"type": "object",
"title": "MessageOutput",
},
"ValidationError": {
"properties": {
"loc": {
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
"type": "array",
"title": "Location",
},
"msg": {"type": "string", "title": "Message"},
"type": {"type": "string", "title": "Error Type"},
},
"type": "object",
"required": ["loc", "msg", "type"],
"title": "ValidationError",
},
}
},
}
)

11
tests/test_params_repr.py

@ -1,7 +1,7 @@
from typing import Any, List from typing import Any, List
from dirty_equals import IsOneOf from dirty_equals import IsOneOf
from fastapi.params import Body, Cookie, Depends, Header, Param, Path, Query from fastapi.params import Body, Cookie, Header, Param, Path, Query
test_data: List[Any] = ["teststr", None, ..., 1, []] test_data: List[Any] = ["teststr", None, ..., 1, []]
@ -141,12 +141,3 @@ def test_body_repr_number():
def test_body_repr_list(): def test_body_repr_list():
assert repr(Body([])) == "Body([])" assert repr(Body([])) == "Body([])"
def test_depends_repr():
assert repr(Depends()) == "Depends(NoneType)"
assert repr(Depends(get_user)) == "Depends(get_user)"
assert repr(Depends(use_cache=False)) == "Depends(NoneType, use_cache=False)"
assert (
repr(Depends(get_user, use_cache=False)) == "Depends(get_user, use_cache=False)"
)

15
tests/test_pydantic_v1_v2_multifile/test_multifile.py

@ -1028,17 +1028,6 @@ def test_openapi_schema():
"type": "object", "type": "object",
"title": "HTTPValidationError", "title": "HTTPValidationError",
}, },
"SubItem-Output": {
"properties": {
"new_sub_name": {
"type": "string",
"title": "New Sub Name",
}
},
"type": "object",
"required": ["new_sub_name"],
"title": "SubItem",
},
"ValidationError": { "ValidationError": {
"properties": { "properties": {
"loc": { "loc": {
@ -1113,11 +1102,11 @@ def test_openapi_schema():
"title": "New Description", "title": "New Description",
}, },
"new_sub": { "new_sub": {
"$ref": "#/components/schemas/SubItem-Output" "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
}, },
"new_multi": { "new_multi": {
"items": { "items": {
"$ref": "#/components/schemas/SubItem-Output" "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
}, },
"type": "array", "type": "array",
"title": "New Multi", "title": "New Multi",

Loading…
Cancel
Save