diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 767ef8d9e..05c33a608 100644
--- a/.pre-commit-config.yaml
+++ b/.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:
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index 72c47d4fb..ee987b544 100644
--- a/docs/en/docs/release-notes.md
+++ b/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
diff --git a/docs/vi/docs/deployment/cloud.md b/docs/vi/docs/deployment/cloud.md
new file mode 100644
index 000000000..9ab72769d
--- /dev/null
+++ b/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ọ:
+
+* Platform.sh
+* Porter
+* Coherence
+* Render
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index e2866b488..d205d17fa 100644
--- a/fastapi/dependencies/utils.py
+++ b/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
diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py
index 947eca948..bd8f3c106 100644
--- a/fastapi/openapi/utils.py
+++ b/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)
diff --git a/fastapi/routing.py b/fastapi/routing.py
index 8ea4bb219..457481e32 100644
--- a/fastapi/routing.py
+++ b/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
diff --git a/requirements-docs-tests.txt b/requirements-docs-tests.txt
index 331d2a5b3..e7718e61d 100644
--- a/requirements-docs-tests.txt
+++ b/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
diff --git a/requirements-docs.txt b/requirements-docs.txt
index cd2e4e58e..8812e27f9 100644
--- a/requirements-docs.txt
+++ b/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
diff --git a/requirements-tests.txt b/requirements-tests.txt
index 4a15844e4..6a870add6 100644
--- a/requirements-tests.txt
+++ b/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
diff --git a/scripts/translate.py b/scripts/translate.py
index ce11b3877..9a2136d1b 100644
--- a/scripts/translate.py
+++ b/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()
diff --git a/tests/test_enforce_once_required_parameter.py b/tests/test_enforce_once_required_parameter.py
index b64f8341b..30329282f 100644
--- a/tests/test_enforce_once_required_parameter.py
+++ b/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",
diff --git a/tests/test_generic_parameterless_depends.py b/tests/test_generic_parameterless_depends.py
index fe13ff89b..5aa35320c 100644
--- a/tests/test_generic_parameterless_depends.py
+++ b/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",
diff --git a/tests/test_repeated_dependency_schema.py b/tests/test_repeated_dependency_schema.py
index d7d0dfa05..c21829bd9 100644
--- a/tests/test_repeated_dependency_schema.py
+++ b/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",
diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py
index 72db54bd2..a04dba219 100644
--- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py
+++ b/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():
diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py
index d06a385b5..ea56b6f21 100644
--- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py
+++ b/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():
diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py
index 187e89ace..926bbb14f 100644
--- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py
+++ b/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():
diff --git a/tests/test_tutorial/test_sql_databases/test_tutorial002.py b/tests/test_tutorial/test_sql_databases/test_tutorial002.py
index 68c1966f5..79e48c1c3 100644
--- a/tests/test_tutorial/test_sql_databases/test_tutorial002.py
+++ b/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"]