diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 8172b9f655..20b36f9cc4 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +The deprecated `**extra` keyword arguments have been removed from `Path`, `Query`, `Header`, `Cookie`, `Body`, `Form`, and `File`. Unknown keyword arguments now raise `TypeError`. For additional JSON Schema properties, replace, for example, `Query(custom="value")` with `Query(json_schema_extra={"custom": "value"})`. + ## 0.154.0 (2026-10-20) ### Features diff --git a/docs/en/docs/tutorial/body-fields.md b/docs/en/docs/tutorial/body-fields.md index 11d4068480..f27085b85a 100644 --- a/docs/en/docs/tutorial/body-fields.md +++ b/docs/en/docs/tutorial/body-fields.md @@ -43,13 +43,13 @@ Notice how each model's attribute with a type, default value and `Field` has the ## Add extra information { #add-extra-information } -You can declare extra information in `Field`, `Query`, `Body`, etc. And it will be included in the generated JSON Schema. +You can declare extra information with `json_schema_extra` in `Field`, `Query`, `Body`, etc. And it will be included in the generated JSON Schema. You will learn more about adding extra information later in the docs, when learning to declare examples. /// warning -Extra keys passed to `Field` will also be present in the resulting OpenAPI schema for your application. +Extra keys passed in `json_schema_extra` will also be present in the resulting OpenAPI schema for your application. As these keys may not necessarily be part of the OpenAPI specification, some OpenAPI tools, for example [the OpenAPI validator](https://validator.swagger.io/), may not work with your generated schema. /// @@ -58,4 +58,4 @@ As these keys may not necessarily be part of the OpenAPI specification, some Ope You can use Pydantic's `Field` to declare extra validations and metadata for model attributes. -You can also use the extra keyword arguments to pass additional JSON Schema metadata. +You can also use `json_schema_extra` to pass additional JSON Schema metadata. diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py index 39ec493f96..68478aedc1 100644 --- a/fastapi/param_functions.py +++ b/fastapi/param_functions.py @@ -315,19 +315,6 @@ def Path( # noqa: N802 """ ), ] = None, - **extra: Annotated[ - Any, - Doc( - """ - Include extra fields used by the JSON Schema. - """ - ), - deprecated( - """ - The `extra` kwargs is deprecated. Use `json_schema_extra` instead. - """ - ), - ], ) -> Any: """ Declare a path parameter for a *path operation*. @@ -382,7 +369,6 @@ def Path( # noqa: N802 deprecated=deprecated, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, - **extra, ) @@ -709,19 +695,6 @@ def Query( # noqa: N802 """ ), ] = None, - **extra: Annotated[ - Any, - Doc( - """ - Include extra fields used by the JSON Schema. - """ - ), - deprecated( - """ - The `extra` kwargs is deprecated. Use `json_schema_extra` instead. - """ - ), - ], ) -> Any: return params.Query( default=default, @@ -755,7 +728,6 @@ def Query( # noqa: N802 deprecated=deprecated, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, - **extra, ) @@ -1054,19 +1026,6 @@ def Header( # noqa: N802 """ ), ] = None, - **extra: Annotated[ - Any, - Doc( - """ - Include extra fields used by the JSON Schema. - """ - ), - deprecated( - """ - The `extra` kwargs is deprecated. Use `json_schema_extra` instead. - """ - ), - ], ) -> Any: return params.Header( default=default, @@ -1101,7 +1060,6 @@ def Header( # noqa: N802 deprecated=deprecated, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, - **extra, ) @@ -1389,19 +1347,6 @@ def Cookie( # noqa: N802 """ ), ] = None, - **extra: Annotated[ - Any, - Doc( - """ - Include extra fields used by the JSON Schema. - """ - ), - deprecated( - """ - The `extra` kwargs is deprecated. Use `json_schema_extra` instead. - """ - ), - ], ) -> Any: return params.Cookie( default=default, @@ -1435,7 +1380,6 @@ def Cookie( # noqa: N802 deprecated=deprecated, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, - **extra, ) @@ -1746,19 +1690,6 @@ def Body( # noqa: N802 """ ), ] = None, - **extra: Annotated[ - Any, - Doc( - """ - Include extra fields used by the JSON Schema. - """ - ), - deprecated( - """ - The `extra` kwargs is deprecated. Use `json_schema_extra` instead. - """ - ), - ], ) -> Any: return params.Body( default=default, @@ -1794,7 +1725,6 @@ def Body( # noqa: N802 deprecated=deprecated, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, - **extra, ) @@ -2091,19 +2021,6 @@ def Form( # noqa: N802 """ ), ] = None, - **extra: Annotated[ - Any, - Doc( - """ - Include extra fields used by the JSON Schema. - """ - ), - deprecated( - """ - The `extra` kwargs is deprecated. Use `json_schema_extra` instead. - """ - ), - ], ) -> Any: return params.Form( default=default, @@ -2138,7 +2055,6 @@ def Form( # noqa: N802 deprecated=deprecated, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, - **extra, ) @@ -2435,19 +2351,6 @@ def File( # noqa: N802 """ ), ] = None, - **extra: Annotated[ - Any, - Doc( - """ - Include extra fields used by the JSON Schema. - """ - ), - deprecated( - """ - The `extra` kwargs is deprecated. Use `json_schema_extra` instead. - """ - ), - ], ) -> Any: return params.File( default=default, @@ -2482,7 +2385,6 @@ def File( # noqa: N802 deprecated=deprecated, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, - **extra, ) diff --git a/fastapi/params.py b/fastapi/params.py index b3344c6b4a..f560e9ab36 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -75,7 +75,6 @@ class Param(FieldInfo): # type: ignore[misc] # ty: ignore[subclass-of-final-cl json_schema_extra: dict[str, Any] | Callable[[dict[str, Any]], None] | None = None, - **extra: Any, ): if example is not _Unset: warnings.warn( @@ -86,28 +85,27 @@ class Param(FieldInfo): # type: ignore[misc] # ty: ignore[subclass-of-final-cl self.example = example self.include_in_schema = include_in_schema self.openapi_examples = openapi_examples - kwargs = dict( - default=default, - default_factory=default_factory, - alias=alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - discriminator=discriminator, - validate_default=validate_default, - union_mode=union_mode, - fail_fast=fail_fast, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - **extra, - ) + kwargs = { + "default": default, + "default_factory": default_factory, + "alias": alias, + "title": title, + "description": description, + "gt": gt, + "ge": ge, + "lt": lt, + "le": le, + "min_length": min_length, + "max_length": max_length, + "discriminator": discriminator, + "validate_default": validate_default, + "union_mode": union_mode, + "fail_fast": fail_fast, + "multiple_of": multiple_of, + "allow_inf_nan": allow_inf_nan, + "max_digits": max_digits, + "decimal_places": decimal_places, + } if examples is not None: kwargs["examples"] = examples if regex is not None: @@ -116,7 +114,6 @@ class Param(FieldInfo): # type: ignore[misc] # ty: ignore[subclass-of-final-cl category=FastAPIDeprecationWarning, stacklevel=4, ) - current_json_schema_extra = json_schema_extra or extra kwargs["deprecated"] = deprecated if serialization_alias in (_Unset, None) and isinstance(alias, str): @@ -130,7 +127,7 @@ class Param(FieldInfo): # type: ignore[misc] # ty: ignore[subclass-of-final-cl "validation_alias": validation_alias, "serialization_alias": serialization_alias, "strict": strict, - "json_schema_extra": current_json_schema_extra, + "json_schema_extra": json_schema_extra, } ) kwargs["pattern"] = pattern or regex @@ -194,7 +191,6 @@ class Path(Param): # type: ignore[misc] json_schema_extra: dict[str, Any] | Callable[[dict[str, Any]], None] | None = None, - **extra: Any, ): assert default is ..., "Path parameters cannot have a default value" self.in_ = self.in_ @@ -231,7 +227,6 @@ class Path(Param): # type: ignore[misc] openapi_examples=openapi_examples, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, - **extra, ) @@ -286,7 +281,6 @@ class Query(Param): # type: ignore[misc] json_schema_extra: dict[str, Any] | Callable[[dict[str, Any]], None] | None = None, - **extra: Any, ): super().__init__( default=default, @@ -321,7 +315,6 @@ class Query(Param): # type: ignore[misc] openapi_examples=openapi_examples, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, - **extra, ) @@ -377,7 +370,6 @@ class Header(Param): # type: ignore[misc] json_schema_extra: dict[str, Any] | Callable[[dict[str, Any]], None] | None = None, - **extra: Any, ): self.convert_underscores = convert_underscores super().__init__( @@ -413,7 +405,6 @@ class Header(Param): # type: ignore[misc] openapi_examples=openapi_examples, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, - **extra, ) @@ -468,7 +459,6 @@ class Cookie(Param): # type: ignore[misc] json_schema_extra: dict[str, Any] | Callable[[dict[str, Any]], None] | None = None, - **extra: Any, ): super().__init__( default=default, @@ -503,7 +493,6 @@ class Cookie(Param): # type: ignore[misc] openapi_examples=openapi_examples, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, - **extra, ) @@ -558,7 +547,6 @@ class Body(FieldInfo): # type: ignore[misc] # ty: ignore[subclass-of-final-cla json_schema_extra: dict[str, Any] | Callable[[dict[str, Any]], None] | None = None, - **extra: Any, ): self.embed = embed self.media_type = media_type @@ -571,28 +559,27 @@ class Body(FieldInfo): # type: ignore[misc] # ty: ignore[subclass-of-final-cla self.example = example self.include_in_schema = include_in_schema self.openapi_examples = openapi_examples - kwargs = dict( - default=default, - default_factory=default_factory, - alias=alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - discriminator=discriminator, - validate_default=validate_default, - union_mode=union_mode, - fail_fast=fail_fast, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - **extra, - ) + kwargs = { + "default": default, + "default_factory": default_factory, + "alias": alias, + "title": title, + "description": description, + "gt": gt, + "ge": ge, + "lt": lt, + "le": le, + "min_length": min_length, + "max_length": max_length, + "discriminator": discriminator, + "validate_default": validate_default, + "union_mode": union_mode, + "fail_fast": fail_fast, + "multiple_of": multiple_of, + "allow_inf_nan": allow_inf_nan, + "max_digits": max_digits, + "decimal_places": decimal_places, + } if examples is not None: kwargs["examples"] = examples if regex is not None: @@ -601,7 +588,6 @@ class Body(FieldInfo): # type: ignore[misc] # ty: ignore[subclass-of-final-cla category=FastAPIDeprecationWarning, stacklevel=4, ) - current_json_schema_extra = json_schema_extra or extra kwargs["deprecated"] = deprecated if serialization_alias in (_Unset, None) and isinstance(alias, str): serialization_alias = alias @@ -614,7 +600,7 @@ class Body(FieldInfo): # type: ignore[misc] # ty: ignore[subclass-of-final-cla "validation_alias": validation_alias, "serialization_alias": serialization_alias, "strict": strict, - "json_schema_extra": current_json_schema_extra, + "json_schema_extra": json_schema_extra, } ) kwargs["pattern"] = pattern or regex @@ -677,7 +663,6 @@ class Form(Body): # type: ignore[misc] json_schema_extra: dict[str, Any] | Callable[[dict[str, Any]], None] | None = None, - **extra: Any, ): super().__init__( default=default, @@ -713,7 +698,6 @@ class Form(Body): # type: ignore[misc] openapi_examples=openapi_examples, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, - **extra, ) @@ -767,7 +751,6 @@ class File(Form): # type: ignore[misc] json_schema_extra: dict[str, Any] | Callable[[dict[str, Any]], None] | None = None, - **extra: Any, ): super().__init__( default=default, @@ -803,7 +786,6 @@ class File(Form): # type: ignore[misc] openapi_examples=openapi_examples, include_in_schema=include_in_schema, json_schema_extra=json_schema_extra, - **extra, ) diff --git a/tests/test_union_form_normalization.py b/tests/test_union_form_normalization.py index 4c4ee0934b..22d470f962 100644 --- a/tests/test_union_form_normalization.py +++ b/tests/test_union_form_normalization.py @@ -370,15 +370,18 @@ def test_model_validator_does_not_mutate_other_branches(): assert response.json() == {"value": ["", "hello"]} -def test_embedded_union_still_rejects_a_string(): +def test_union_with_another_form_parameter_rejects_a_string(): app = FastAPI() @app.post("/") - def read_form(data: Annotated[TextForm | ListForm, Form(embed=True)]): + def read_form( + data: Annotated[TextForm | ListForm, Form()], + token: Annotated[str, Form()], + ): pytest.fail("Invalid form data reached the endpoint") # pragma: no cover client = TestClient(app) - response = client.post("/", data={"data": "hello"}) + response = client.post("/", data={"data": "hello", "token": "example"}) assert response.status_code == 422 assert [error["loc"] for error in response.json()["detail"]] == [ ["body", "data", "TextForm"],