Browse Source

Merge branch 'master' into master

pull/13326/head
Manish 4 weeks ago
committed by GitHub
parent
commit
fe664c6992
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      .pre-commit-config.yaml
  2. 7
      docs/en/docs/release-notes.md
  3. 17
      docs/vi/docs/deployment/cloud.md
  4. 42
      fastapi/dependencies/utils.py
  5. 12
      fastapi/openapi/utils.py
  6. 24
      fastapi/routing.py
  7. 2
      requirements-docs-tests.txt
  8. 2
      requirements-docs.txt
  9. 2
      requirements-tests.txt
  10. 12
      scripts/translate.py
  11. 6
      tests/test_enforce_once_required_parameter.py
  12. 4
      tests/test_generic_parameterless_depends.py
  13. 6
      tests/test_repeated_dependency_schema.py
  14. 48
      tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py
  15. 54
      tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py
  16. 54
      tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py
  17. 6
      tests/test_tutorial/test_sql_databases/test_tutorial002.py

2
.pre-commit-config.yaml

@ -14,7 +14,7 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.7.4
rev: v0.9.4
hooks:
- id: ruff
args:

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

@ -7,8 +7,15 @@ hide:
## Latest Changes
### Translations
* 🌐 Add Vietnamese translation for `docs/vi/docs/deployment/cloud.md`. PR [#13407](https://github.com/fastapi/fastapi/pull/13407) by [@ptt3199](https://github.com/ptt3199).
### Internal
* ⬆ Bump sqlmodel from 0.0.22 to 0.0.23. PR [#13437](https://github.com/fastapi/fastapi/pull/13437) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump black from 24.10.0 to 25.1.0. PR [#13436](https://github.com/fastapi/fastapi/pull/13436) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump ruff to 0.9.4. PR [#13299](https://github.com/fastapi/fastapi/pull/13299) by [@dependabot[bot]](https://github.com/apps/dependabot).
* 🔧 Update sponsors: pause TestDriven. PR [#13446](https://github.com/fastapi/fastapi/pull/13446) by [@tiangolo](https://github.com/tiangolo).
## 0.115.11

17
docs/vi/docs/deployment/cloud.md

@ -0,0 +1,17 @@
# Triển khai FastAPI trên các Dịch vụ Cloud
Bạn có thể sử dụng **bất kỳ nhà cung cấp dịch vụ cloud** nào để triển khai ứng dụng FastAPI của mình.
Trong hầu hết các trường hợp, các nhà cung cấp dịch vụ cloud lớn đều có hướng dẫn triển khai FastAPI với họ.
## Nhà cung cấp dịch vụ Cloud - Nhà tài trợ
Một vài nhà cung cấp dịch vụ cloud ✨ [**tài trợ cho FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, điều này giúp đảm bảo sự phát triển liên tục và khỏe mạnh của FastAPI và hệ sinh thái của nó.
Thêm nữa, điều này cũng thể hiện cam kết thực sự của họ đối với FastAPI và **cộng đồng người dùng** (bạn), vì họ không chỉ muốn cung cấp cho bạn một **dịch vụ tốt** mà còn muốn đảm bảo rằng bạn có một **framework tốt và bền vững**, đó chính là FastAPI. 🙇
Bạn có thể thử các dịch vụ của họ và làm theo hướng dẫn của họ:
* <a href="https://docs.platform.sh/languages/python.html?utm_source=fastapi-signup&utm_medium=banner&utm_campaign=FastAPI-signup-June-2023" class="external-link" target="_blank">Platform.sh</a>
* <a href="https://docs.porter.run/language-specific-guides/fastapi" class="external-link" target="_blank">Porter</a>
* <a href="https://www.withcoherence.com/?utm_medium=advertising&utm_source=fastapi&utm_campaign=website" class="external-link" target="_blank">Coherence</a>
* <a href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" class="external-link" target="_blank">Render</a>

42
fastapi/dependencies/utils.py

@ -133,9 +133,9 @@ def get_param_sub_dependant(
def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:
assert callable(
depends.dependency
), "A parameter-less dependency must have a callable dependency"
assert callable(depends.dependency), (
"A parameter-less dependency must have a callable dependency"
)
return get_sub_dependant(depends=depends, dependency=depends.dependency, path=path)
@ -302,9 +302,9 @@ def get_dependant(
type_annotation=param_details.type_annotation,
dependant=dependant,
):
assert (
param_details.field is None
), f"Cannot specify multiple FastAPI annotations for {param_name!r}"
assert param_details.field is None, (
f"Cannot specify multiple FastAPI annotations for {param_name!r}"
)
continue
assert param_details.field is not None
if isinstance(param_details.field.field_info, params.Body):
@ -439,9 +439,9 @@ def analyze_param(
),
):
assert depends is None, f"Cannot specify `Depends` for type {type_annotation!r}"
assert (
field_info is None
), f"Cannot specify FastAPI annotation for type {type_annotation!r}"
assert field_info is None, (
f"Cannot specify FastAPI annotation for type {type_annotation!r}"
)
# Handle default assignations, neither field_info nor depends was not found in Annotated nor default value
elif field_info is None and depends is None:
default_value = value if value is not inspect.Signature.empty else RequiredParam
@ -494,9 +494,9 @@ def analyze_param(
field_info=field_info,
)
if is_path_param:
assert is_scalar_field(
field=field
), "Path params must be of one of the supported types"
assert is_scalar_field(field=field), (
"Path params must be of one of the supported types"
)
elif isinstance(field_info, params.Query):
assert (
is_scalar_field(field)
@ -521,9 +521,9 @@ def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None:
elif field_info_in == params.ParamTypes.header:
dependant.header_params.append(field)
else:
assert (
field_info_in == params.ParamTypes.cookie
), f"non-body parameters must be in path, query, header or cookie: {field.name}"
assert field_info_in == params.ParamTypes.cookie, (
f"non-body parameters must be in path, query, header or cookie: {field.name}"
)
dependant.cookie_params.append(field)
@ -782,9 +782,9 @@ def request_params_to_args(
if single_not_embedded_field:
field_info = first_field.field_info
assert isinstance(
field_info, params.Param
), "Params must be subclasses of Param"
assert isinstance(field_info, params.Param), (
"Params must be subclasses of Param"
)
loc: Tuple[str, ...] = (field_info.in_.value,)
v_, errors_ = _validate_value_with_model_field(
field=first_field, value=params_to_process, values=values, loc=loc
@ -794,9 +794,9 @@ def request_params_to_args(
for field in fields:
value = _get_multidict_value(field, received_params)
field_info = field.field_info
assert isinstance(
field_info, params.Param
), "Params must be subclasses of Param"
assert isinstance(field_info, params.Param), (
"Params must be subclasses of Param"
)
loc = (field_info.in_.value, field.alias)
v_, errors_ = _validate_value_with_model_field(
field=field, value=value, values=values, loc=loc

12
fastapi/openapi/utils.py

@ -364,9 +364,9 @@ def get_openapi_path(
openapi_response = operation_responses.setdefault(
status_code_key, {}
)
assert isinstance(
process_response, dict
), "An additional response must be a dict"
assert isinstance(process_response, dict), (
"An additional response must be a dict"
)
field = route.response_fields.get(additional_status_code)
additional_field_schema: Optional[Dict[str, Any]] = None
if field:
@ -434,9 +434,9 @@ def get_fields_from_routes(
route, routing.APIRoute
):
if route.body_field:
assert isinstance(
route.body_field, ModelField
), "A request body must be a Pydantic Field"
assert isinstance(route.body_field, ModelField), (
"A request body must be a Pydantic Field"
)
body_fields_from_routes.append(route.body_field)
if route.response_field:
responses_from_routes.append(route.response_field)

24
fastapi/routing.py

@ -504,9 +504,9 @@ class APIRoute(routing.Route):
status_code = int(status_code)
self.status_code = status_code
if self.response_model:
assert is_body_allowed_for_status_code(
status_code
), f"Status code {status_code} must not have a response body"
assert is_body_allowed_for_status_code(status_code), (
f"Status code {status_code} must not have a response body"
)
response_name = "Response_" + self.unique_id
self.response_field = create_model_field(
name=response_name,
@ -537,9 +537,9 @@ class APIRoute(routing.Route):
assert isinstance(response, dict), "An additional response must be a dict"
model = response.get("model")
if model:
assert is_body_allowed_for_status_code(
additional_status_code
), f"Status code {additional_status_code} must not have a response body"
assert is_body_allowed_for_status_code(additional_status_code), (
f"Status code {additional_status_code} must not have a response body"
)
response_name = f"Response_{additional_status_code}_{self.unique_id}"
response_field = create_model_field(
name=response_name, type_=model, mode="serialization"
@ -844,9 +844,9 @@ class APIRouter(routing.Router):
)
if prefix:
assert prefix.startswith("/"), "A path prefix must start with '/'"
assert not prefix.endswith(
"/"
), "A path prefix must not end with '/', as the routes will start with '/'"
assert not prefix.endswith("/"), (
"A path prefix must not end with '/', as the routes will start with '/'"
)
self.prefix = prefix
self.tags: List[Union[str, Enum]] = tags or []
self.dependencies = list(dependencies or [])
@ -1256,9 +1256,9 @@ class APIRouter(routing.Router):
"""
if prefix:
assert prefix.startswith("/"), "A path prefix must start with '/'"
assert not prefix.endswith(
"/"
), "A path prefix must not end with '/', as the routes will start with '/'"
assert not prefix.endswith("/"), (
"A path prefix must not end with '/', as the routes will start with '/'"
)
else:
for r in router.routes:
path = getattr(r, "path") # noqa: B009

2
requirements-docs-tests.txt

@ -1,4 +1,4 @@
# For mkdocstrings and tests
httpx >=0.23.0,<0.28.0
# For linting and generating docs versions
ruff ==0.6.4
ruff ==0.9.4

2
requirements-docs.txt

@ -14,6 +14,6 @@ cairosvg==2.7.1
mkdocstrings[python]==0.26.1
griffe-typingdoc==0.2.7
# For griffe, it formats with black
black==24.10.0
black==25.1.0
mkdocs-macros-plugin==1.3.7
markdown-include-variants==0.0.4

2
requirements-tests.txt

@ -4,7 +4,7 @@ pytest >=7.1.3,<9.0.0
coverage[toml] >= 6.5.0,< 8.0
mypy ==1.8.0
dirty-equals ==0.8.0
sqlmodel==0.0.22
sqlmodel==0.0.23
flask >=1.1.2,<4.0.0
anyio[trio] >=3.2.1,<5.0.0
PyJWT==2.8.0

12
scripts/translate.py

@ -38,9 +38,9 @@ def get_langs() -> dict[str, str]:
def generate_lang_path(*, lang: str, path: Path) -> Path:
en_docs_path = Path("docs/en/docs")
assert str(path).startswith(
str(en_docs_path)
), f"Path must be inside {en_docs_path}"
assert str(path).startswith(str(en_docs_path)), (
f"Path must be inside {en_docs_path}"
)
lang_docs_path = Path(f"docs/{lang}/docs")
out_path = Path(str(path).replace(str(en_docs_path), str(lang_docs_path)))
return out_path
@ -56,9 +56,9 @@ def translate_page(*, lang: str, path: Path) -> None:
lang_prompt_content = lang_prompt_path.read_text()
en_docs_path = Path("docs/en/docs")
assert str(path).startswith(
str(en_docs_path)
), f"Path must be inside {en_docs_path}"
assert str(path).startswith(str(en_docs_path)), (
f"Path must be inside {en_docs_path}"
)
out_path = generate_lang_path(lang=lang, path=path)
out_path.parent.mkdir(parents=True, exist_ok=True)
original_content = path.read_text()

6
tests/test_enforce_once_required_parameter.py

@ -48,7 +48,7 @@ expected_schema = {
"type": "array",
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error " "Type", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
"required": ["loc", "msg", "type"],
"title": "ValidationError",
@ -73,7 +73,7 @@ expected_schema = {
"responses": {
"200": {
"content": {"application/json": {"schema": {}}},
"description": "Successful " "Response",
"description": "Successful Response",
},
"422": {
"content": {
@ -83,7 +83,7 @@ expected_schema = {
}
}
},
"description": "Validation " "Error",
"description": "Validation Error",
},
},
"summary": "Foo Handler",

4
tests/test_generic_parameterless_depends.py

@ -55,7 +55,7 @@ def test_openapi_schema():
"responses": {
"200": {
"content": {"application/json": {"schema": {}}},
"description": "Successful " "Response",
"description": "Successful Response",
}
},
"summary": "A",
@ -67,7 +67,7 @@ def test_openapi_schema():
"responses": {
"200": {
"content": {"application/json": {"schema": {}}},
"description": "Successful " "Response",
"description": "Successful Response",
}
},
"summary": "B",

6
tests/test_repeated_dependency_schema.py

@ -41,7 +41,7 @@ schema = {
"type": "array",
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error " "Type", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
"required": ["loc", "msg", "type"],
"title": "ValidationError",
@ -66,7 +66,7 @@ schema = {
"responses": {
"200": {
"content": {"application/json": {"schema": {}}},
"description": "Successful " "Response",
"description": "Successful Response",
},
"422": {
"content": {
@ -76,7 +76,7 @@ schema = {
}
}
},
"description": "Validation " "Error",
"description": "Validation Error",
},
},
"summary": "Get Deps",

48
tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py

@ -8,31 +8,31 @@ client = TestClient(app)
def test_swagger_ui():
response = client.get("/docs")
assert response.status_code == 200, response.text
assert (
'"syntaxHighlight": false' in response.text
), "syntaxHighlight should be included and converted to JSON"
assert (
'"dom_id": "#swagger-ui"' in response.text
), "default configs should be preserved"
assert '"syntaxHighlight": false' in response.text, (
"syntaxHighlight should be included and converted to JSON"
)
assert '"dom_id": "#swagger-ui"' in response.text, (
"default configs should be preserved"
)
assert "presets: [" in response.text, "default configs should be preserved"
assert (
"SwaggerUIBundle.presets.apis," in response.text
), "default configs should be preserved"
assert (
"SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text
), "default configs should be preserved"
assert (
'"layout": "BaseLayout",' in response.text
), "default configs should be preserved"
assert (
'"deepLinking": true,' in response.text
), "default configs should be preserved"
assert (
'"showExtensions": true,' in response.text
), "default configs should be preserved"
assert (
'"showCommonExtensions": true,' in response.text
), "default configs should be preserved"
assert "SwaggerUIBundle.presets.apis," in response.text, (
"default configs should be preserved"
)
assert "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text, (
"default configs should be preserved"
)
assert '"layout": "BaseLayout",' in response.text, (
"default configs should be preserved"
)
assert '"deepLinking": true,' in response.text, (
"default configs should be preserved"
)
assert '"showExtensions": true,' in response.text, (
"default configs should be preserved"
)
assert '"showCommonExtensions": true,' in response.text, (
"default configs should be preserved"
)
def test_get_users():

54
tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py

@ -8,34 +8,34 @@ client = TestClient(app)
def test_swagger_ui():
response = client.get("/docs")
assert response.status_code == 200, response.text
assert (
'"syntaxHighlight": false' not in response.text
), "not used parameters should not be included"
assert (
'"syntaxHighlight": {"theme": "obsidian"}' in response.text
), "parameters with middle dots should be included in a JSON compatible way"
assert (
'"dom_id": "#swagger-ui"' in response.text
), "default configs should be preserved"
assert '"syntaxHighlight": false' not in response.text, (
"not used parameters should not be included"
)
assert '"syntaxHighlight": {"theme": "obsidian"}' in response.text, (
"parameters with middle dots should be included in a JSON compatible way"
)
assert '"dom_id": "#swagger-ui"' in response.text, (
"default configs should be preserved"
)
assert "presets: [" in response.text, "default configs should be preserved"
assert (
"SwaggerUIBundle.presets.apis," in response.text
), "default configs should be preserved"
assert (
"SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text
), "default configs should be preserved"
assert (
'"layout": "BaseLayout",' in response.text
), "default configs should be preserved"
assert (
'"deepLinking": true,' in response.text
), "default configs should be preserved"
assert (
'"showExtensions": true,' in response.text
), "default configs should be preserved"
assert (
'"showCommonExtensions": true,' in response.text
), "default configs should be preserved"
assert "SwaggerUIBundle.presets.apis," in response.text, (
"default configs should be preserved"
)
assert "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text, (
"default configs should be preserved"
)
assert '"layout": "BaseLayout",' in response.text, (
"default configs should be preserved"
)
assert '"deepLinking": true,' in response.text, (
"default configs should be preserved"
)
assert '"showExtensions": true,' in response.text, (
"default configs should be preserved"
)
assert '"showCommonExtensions": true,' in response.text, (
"default configs should be preserved"
)
def test_get_users():

54
tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py

@ -8,34 +8,34 @@ client = TestClient(app)
def test_swagger_ui():
response = client.get("/docs")
assert response.status_code == 200, response.text
assert (
'"deepLinking": false,' in response.text
), "overridden configs should be preserved"
assert (
'"deepLinking": true' not in response.text
), "overridden configs should not include the old value"
assert (
'"syntaxHighlight": false' not in response.text
), "not used parameters should not be included"
assert (
'"dom_id": "#swagger-ui"' in response.text
), "default configs should be preserved"
assert '"deepLinking": false,' in response.text, (
"overridden configs should be preserved"
)
assert '"deepLinking": true' not in response.text, (
"overridden configs should not include the old value"
)
assert '"syntaxHighlight": false' not in response.text, (
"not used parameters should not be included"
)
assert '"dom_id": "#swagger-ui"' in response.text, (
"default configs should be preserved"
)
assert "presets: [" in response.text, "default configs should be preserved"
assert (
"SwaggerUIBundle.presets.apis," in response.text
), "default configs should be preserved"
assert (
"SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text
), "default configs should be preserved"
assert (
'"layout": "BaseLayout",' in response.text
), "default configs should be preserved"
assert (
'"showExtensions": true,' in response.text
), "default configs should be preserved"
assert (
'"showCommonExtensions": true,' in response.text
), "default configs should be preserved"
assert "SwaggerUIBundle.presets.apis," in response.text, (
"default configs should be preserved"
)
assert "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text, (
"default configs should be preserved"
)
assert '"layout": "BaseLayout",' in response.text, (
"default configs should be preserved"
)
assert '"showExtensions": true,' in response.text, (
"default configs should be preserved"
)
assert '"showCommonExtensions": true,' in response.text, (
"default configs should be preserved"
)
def test_get_users():

6
tests/test_tutorial/test_sql_databases/test_tutorial002.py

@ -71,9 +71,9 @@ def test_crud_app(client: TestClient):
assert response.json() == snapshot(
{"age": 30, "id": IsInt(), "name": "Dead Pond"}
)
assert (
response.json()["id"] != 9000
), "The ID should be generated by the database"
assert response.json()["id"] != 9000, (
"The ID should be generated by the database"
)
# Read a hero
hero_id = response.json()["id"]

Loading…
Cancel
Save