From 8ae4603d680f86ae1e5f96e58546dee4f543171f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pacheco?= Date: Sat, 12 Oct 2024 05:36:32 -0400 Subject: [PATCH 1/8] =?UTF-8?q?=F0=9F=90=9B=20Remove=20`Required`=20shadow?= =?UTF-8?q?ing=20from=20fastapi=20using=20Pydantic=20v2=20(#12197)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Sofie Van Landeghem --- .../query_params_str_validations/tutorial006d.py | 3 +-- .../tutorial006d_an.py | 3 +-- .../tutorial006d_an_py39.py | 3 +-- fastapi/_compat.py | 9 +++++---- fastapi/dependencies/utils.py | 14 ++++++++------ 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs_src/query_params_str_validations/tutorial006d.py b/docs_src/query_params_str_validations/tutorial006d.py index 42c5bf4eb..a8d69c889 100644 --- a/docs_src/query_params_str_validations/tutorial006d.py +++ b/docs_src/query_params_str_validations/tutorial006d.py @@ -1,11 +1,10 @@ from fastapi import FastAPI, Query -from pydantic import Required app = FastAPI() @app.get("/items/") -async def read_items(q: str = Query(default=Required, min_length=3)): +async def read_items(q: str = Query(default=..., min_length=3)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006d_an.py b/docs_src/query_params_str_validations/tutorial006d_an.py index bc8283e15..ea3b02583 100644 --- a/docs_src/query_params_str_validations/tutorial006d_an.py +++ b/docs_src/query_params_str_validations/tutorial006d_an.py @@ -1,12 +1,11 @@ from fastapi import FastAPI, Query -from pydantic import Required from typing_extensions import Annotated app = FastAPI() @app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)] = Required): +async def read_items(q: Annotated[str, Query(min_length=3)] = ...): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006d_an_py39.py b/docs_src/query_params_str_validations/tutorial006d_an_py39.py index 035d9e3bd..687a9f544 100644 --- a/docs_src/query_params_str_validations/tutorial006d_an_py39.py +++ b/docs_src/query_params_str_validations/tutorial006d_an_py39.py @@ -1,13 +1,12 @@ from typing import Annotated from fastapi import FastAPI, Query -from pydantic import Required app = FastAPI() @app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)] = Required): +async def read_items(q: Annotated[str, Query(min_length=3)] = ...): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/fastapi/_compat.py b/fastapi/_compat.py index 4b07b44fa..56c5d744e 100644 --- a/fastapi/_compat.py +++ b/fastapi/_compat.py @@ -71,7 +71,7 @@ if PYDANTIC_V2: general_plain_validator_function as with_info_plain_validator_function, # noqa: F401 ) - Required = PydanticUndefined + RequiredParam = PydanticUndefined Undefined = PydanticUndefined UndefinedType = PydanticUndefinedType evaluate_forwardref = eval_type_lenient @@ -313,9 +313,10 @@ else: from pydantic.fields import ( # type: ignore[no-redef,attr-defined] ModelField as ModelField, # noqa: F401 ) - from pydantic.fields import ( # type: ignore[no-redef,attr-defined] - Required as Required, # noqa: F401 - ) + + # Keeping old "Required" functionality from Pydantic V1, without + # shadowing typing.Required. + RequiredParam: Any = Ellipsis # type: ignore[no-redef] from pydantic.fields import ( # type: ignore[no-redef,attr-defined] Undefined as Undefined, ) diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 813c74620..87653c80d 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -24,7 +24,7 @@ from fastapi._compat import ( PYDANTIC_V2, ErrorWrapper, ModelField, - Required, + RequiredParam, Undefined, _regenerate_error_with_loc, copy_field_info, @@ -377,7 +377,9 @@ def analyze_param( field_info = copy_field_info( field_info=fastapi_annotation, annotation=use_annotation ) - assert field_info.default is Undefined or field_info.default is Required, ( + assert ( + field_info.default is Undefined or field_info.default is RequiredParam + ), ( f"`{field_info.__class__.__name__}` default value cannot be set in" f" `Annotated` for {param_name!r}. Set the default value with `=` instead." ) @@ -385,7 +387,7 @@ def analyze_param( assert not is_path_param, "Path parameters cannot have default values" field_info.default = value else: - field_info.default = Required + field_info.default = RequiredParam # Get Annotated Depends elif isinstance(fastapi_annotation, params.Depends): depends = fastapi_annotation @@ -434,9 +436,9 @@ def analyze_param( ), 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 Required + default_value = value if value is not inspect.Signature.empty else RequiredParam if is_path_param: - # We might check here that `default_value is Required`, but the fact is that the same + # We might check here that `default_value is RequiredParam`, but the fact is that the same # parameter might sometimes be a path parameter and sometimes not. See # `tests/test_infer_param_optionality.py` for an example. field_info = params.Path(annotation=use_annotation) @@ -480,7 +482,7 @@ def analyze_param( type_=use_annotation_from_field_info, default=field_info.default, alias=alias, - required=field_info.default in (Required, Undefined), + required=field_info.default in (RequiredParam, Undefined), field_info=field_info, ) if is_path_param: From b29cf1621a9b9046ff68cf21c67f96953bc8feeb Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 12 Oct 2024 09:36:55 +0000 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 4fe600ed4..d070a84a3 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Fixes + +* 🐛 Remove `Required` shadowing from fastapi using Pydantic v2. PR [#12197](https://github.com/fastapi/fastapi/pull/12197) by [@pachewise](https://github.com/pachewise). + ### Refactors * ♻️ Update type annotations for improved `python-multipart`. PR [#12407](https://github.com/fastapi/fastapi/pull/12407) by [@tiangolo](https://github.com/tiangolo). From e049fc4ea15a7b844326efa545a7e370713f6612 Mon Sep 17 00:00:00 2001 From: Felix Fanghaenel <35657654+flxdot@users.noreply.github.com> Date: Sat, 12 Oct 2024 11:44:57 +0200 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=90=9B=20Fix=20openapi=20generation?= =?UTF-8?q?=20with=20responses=20kwarg=20(#10895)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: flxdot Co-authored-by: Sofie Van Landeghem Co-authored-by: Sławek Ehlert --- fastapi/routing.py | 4 ++- tests/test_computed_fields.py | 27 ++++++++++++-- ...t_openapi_separate_input_output_schemas.py | 36 ++++++++++++++++--- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/fastapi/routing.py b/fastapi/routing.py index 86e303602..8ea4bb219 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -541,7 +541,9 @@ class APIRoute(routing.Route): 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) + response_field = create_model_field( + name=response_name, type_=model, mode="serialization" + ) response_fields[additional_status_code] = response_field if response_fields: self.response_fields: Dict[Union[int, str], ModelField] = response_fields diff --git a/tests/test_computed_fields.py b/tests/test_computed_fields.py index 5286507b2..a1b412168 100644 --- a/tests/test_computed_fields.py +++ b/tests/test_computed_fields.py @@ -24,13 +24,18 @@ def get_client(): def read_root() -> Rectangle: return Rectangle(width=3, length=4) + @app.get("/responses", responses={200: {"model": Rectangle}}) + def read_responses() -> Rectangle: + return Rectangle(width=3, length=4) + client = TestClient(app) return client +@pytest.mark.parametrize("path", ["/", "/responses"]) @needs_pydanticv2 -def test_get(client: TestClient): - response = client.get("/") +def test_get(client: TestClient, path: str): + response = client.get(path) assert response.status_code == 200, response.text assert response.json() == {"width": 3, "length": 4, "area": 12} @@ -58,7 +63,23 @@ def test_openapi_schema(client: TestClient): } }, } - } + }, + "/responses": { + "get": { + "summary": "Read Responses", + "operationId": "read_responses_responses_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Rectangle"} + } + }, + } + }, + } + }, }, "components": { "schemas": { diff --git a/tests/test_openapi_separate_input_output_schemas.py b/tests/test_openapi_separate_input_output_schemas.py index aeb85f735..f7e045259 100644 --- a/tests/test_openapi_separate_input_output_schemas.py +++ b/tests/test_openapi_separate_input_output_schemas.py @@ -26,8 +26,8 @@ class Item(BaseModel): def get_app_client(separate_input_output_schemas: bool = True) -> TestClient: app = FastAPI(separate_input_output_schemas=separate_input_output_schemas) - @app.post("/items/") - def create_item(item: Item): + @app.post("/items/", responses={402: {"model": Item}}) + def create_item(item: Item) -> Item: return item @app.post("/items-list/") @@ -174,7 +174,23 @@ def test_openapi_schema(): "responses": { "200": { "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item-Output" + } + } + }, + }, + "402": { + "description": "Payment Required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item-Output" + } + } + }, }, "422": { "description": "Validation Error", @@ -374,7 +390,19 @@ def test_openapi_schema_no_separate(): "responses": { "200": { "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "402": { + "description": "Payment Required", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, }, "422": { "description": "Validation Error", From f0be7686468229a7c9aa49c0c2cfff754c81dbdc Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 12 Oct 2024 09:45:17 +0000 Subject: [PATCH 4/8] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index d070a84a3..07035bf94 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Fixes +* 🐛 Fix openapi generation with responses kwarg. PR [#10895](https://github.com/fastapi/fastapi/pull/10895) by [@flxdot](https://github.com/flxdot). * 🐛 Remove `Required` shadowing from fastapi using Pydantic v2. PR [#12197](https://github.com/fastapi/fastapi/pull/12197) by [@pachewise](https://github.com/pachewise). ### Refactors From 113da5b0a7f33be300b7fea8dddbe51ee7b96157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 12 Oct 2024 11:51:09 +0200 Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.115.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 07035bf94..4843370e7 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.115.1 + ### Fixes * 🐛 Fix openapi generation with responses kwarg. PR [#10895](https://github.com/fastapi/fastapi/pull/10895) by [@flxdot](https://github.com/flxdot). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 7dd74c28f..09c8074ed 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.115.0" +__version__ = "0.115.1" from starlette import status as status From b77f2351d1561c0e9599205c3faaaed7b52c24fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 12 Oct 2024 11:59:01 +0200 Subject: [PATCH 6/8] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20Starlette=20?= =?UTF-8?q?to=20`>=3D0.37.2,<0.41.0`=20(#12431)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1be2817a1..c934356d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ classifiers = [ "Topic :: Internet :: WWW/HTTP", ] dependencies = [ - "starlette>=0.37.2,<0.39.0", + "starlette>=0.37.2,<0.41.0", "pydantic>=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0", "typing-extensions>=4.8.0", ] From 63c428fbf9363aaad0e967d30f8b199f5dc23e39 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 12 Oct 2024 09:59:23 +0000 Subject: [PATCH 7/8] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 4843370e7..780f41ed3 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Upgrades + +* ⬆️ Upgrade Starlette to `>=0.37.2,<0.41.0`. PR [#12431](https://github.com/fastapi/fastapi/pull/12431) by [@tiangolo](https://github.com/tiangolo). + ## 0.115.1 ### Fixes From 07684aea793d042fb5a12dde46dfe6d1e2196725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 12 Oct 2024 12:00:47 +0200 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.115.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 780f41ed3..3ba765b08 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.115.2 + ### Upgrades * ⬆️ Upgrade Starlette to `>=0.37.2,<0.41.0`. PR [#12431](https://github.com/fastapi/fastapi/pull/12431) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 09c8074ed..77b52f35b 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.115.1" +__version__ = "0.115.2" from starlette import status as status