From 535b5daa317a9d1d1f9a1058e57650a7beefa861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 26 Dec 2025 04:45:20 -0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=8A=20Add=20a=20custom=20`FastAPIDepre?= =?UTF-8?q?cationWarning`=20(#14605)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/dependencies/utils.py | 4 ++-- fastapi/exceptions.py | 7 +++++++ fastapi/openapi/utils.py | 5 +++-- fastapi/params.py | 9 ++++---- fastapi/routing.py | 5 +++-- fastapi/temp_pydantic_v1_params.py | 9 ++++---- fastapi/utils.py | 5 +++-- tests/benchmarks/test_general_performance.py | 3 ++- tests/test_compat_params_v1.py | 9 ++++---- .../test_pydantic_v1_deprecation_warnings.py | 9 ++++---- tests/test_regex_deprecated_body.py | 3 ++- tests/test_regex_deprecated_params.py | 3 ++- tests/test_schema_extra_examples.py | 21 ++++++++++--------- .../test_tutorial002.py | 3 ++- .../test_tutorial003.py | 3 ++- .../test_tutorial004.py | 3 ++- .../test_tutorial004.py | 2 +- .../test_tutorial002_pv1.py | 3 ++- .../test_tutorial001_pv1.py | 3 ++- 19 files changed, 66 insertions(+), 43 deletions(-) diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 39d0bd89c..af2bed9ad 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -51,7 +51,7 @@ from fastapi.concurrency import ( contextmanager_in_threadpool, ) from fastapi.dependencies.models import Dependant -from fastapi.exceptions import DependencyScopeError +from fastapi.exceptions import DependencyScopeError, FastAPIDeprecationWarning from fastapi.logger import logger from fastapi.security.oauth2 import SecurityScopes from fastapi.types import DependencyCacheKey @@ -327,7 +327,7 @@ def get_dependant( warnings.warn( "pydantic.v1 is deprecated and will soon stop being supported by FastAPI." f" Please update the param {param_name}: {param_details.type_annotation!r}.", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=5, ) if isinstance( diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py index 8e0c55902..53e505281 100644 --- a/fastapi/exceptions.py +++ b/fastapi/exceptions.py @@ -231,3 +231,10 @@ class ResponseValidationError(ValidationException): ) -> None: super().__init__(errors, endpoint_ctx=endpoint_ctx) self.body = body + + +class FastAPIDeprecationWarning(UserWarning): + """ + A custom deprecation warning as DeprecationWarning is ignored + Ref: https://sethmlarson.dev/deprecations-via-warnings-dont-work-for-python-libraries + """ diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index a99d4188e..6180fcde6 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -23,6 +23,7 @@ from fastapi.dependencies.utils import ( get_validation_alias, ) from fastapi.encoders import jsonable_encoder +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX from fastapi.openapi.models import OpenAPI from fastapi.params import Body, ParamTypes @@ -215,9 +216,9 @@ def generate_operation_id( *, route: routing.APIRoute, method: str ) -> str: # pragma: nocover warnings.warn( - "fastapi.openapi.utils.generate_operation_id() was deprecated, " + message="fastapi.openapi.utils.generate_operation_id() was deprecated, " "it is not used internally, and will be removed soon", - DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=2, ) if route.operation_id: diff --git a/fastapi/params.py b/fastapi/params.py index c776c4a59..cc2934f44 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from enum import Enum from typing import Annotated, Any, Callable, Optional, Union +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.openapi.models import Example from pydantic.fields import FieldInfo from typing_extensions import Literal, deprecated @@ -75,7 +76,7 @@ class Param(FieldInfo): # type: ignore[misc] if example is not _Unset: warnings.warn( "`example` has been deprecated, please use `examples` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) self.example = example @@ -105,7 +106,7 @@ class Param(FieldInfo): # type: ignore[misc] if regex is not None: warnings.warn( "`regex` has been deprecated, please use `pattern` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) current_json_schema_extra = json_schema_extra or extra @@ -530,7 +531,7 @@ class Body(FieldInfo): # type: ignore[misc] if example is not _Unset: warnings.warn( "`example` has been deprecated, please use `examples` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) self.example = example @@ -560,7 +561,7 @@ class Body(FieldInfo): # type: ignore[misc] if regex is not None: warnings.warn( "`regex` has been deprecated, please use `pattern` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) current_json_schema_extra = json_schema_extra or extra diff --git a/fastapi/routing.py b/fastapi/routing.py index 2770e3253..3f78e93e8 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -47,6 +47,7 @@ from fastapi.dependencies.utils import ( from fastapi.encoders import jsonable_encoder from fastapi.exceptions import ( EndpointContext, + FastAPIDeprecationWarning, FastAPIError, RequestValidationError, ResponseValidationError, @@ -640,7 +641,7 @@ class APIRoute(routing.Route): warnings.warn( "pydantic.v1 is deprecated and will soon stop being supported by FastAPI." f" Please update the response model {self.response_model!r}.", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) self.response_field = create_model_field( @@ -680,7 +681,7 @@ class APIRoute(routing.Route): warnings.warn( "pydantic.v1 is deprecated and will soon stop being supported by FastAPI." f" In responses={{}}, please update {model}.", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) response_field = create_model_field( diff --git a/fastapi/temp_pydantic_v1_params.py b/fastapi/temp_pydantic_v1_params.py index 1bda0ea9b..62230e42c 100644 --- a/fastapi/temp_pydantic_v1_params.py +++ b/fastapi/temp_pydantic_v1_params.py @@ -1,6 +1,7 @@ import warnings from typing import Annotated, Any, Callable, Optional, Union +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.openapi.models import Example from fastapi.params import ParamTypes from typing_extensions import deprecated @@ -63,7 +64,7 @@ class Param(FieldInfo): if example is not _Unset: warnings.warn( "`example` has been deprecated, please use `examples` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) self.example = example @@ -93,7 +94,7 @@ class Param(FieldInfo): if regex is not None: warnings.warn( "`regex` has been deprecated, please use `pattern` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) current_json_schema_extra = json_schema_extra or extra @@ -503,7 +504,7 @@ class Body(FieldInfo): if example is not _Unset: warnings.warn( "`example` has been deprecated, please use `examples` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) self.example = example @@ -533,7 +534,7 @@ class Body(FieldInfo): if regex is not None: warnings.warn( "`regex` has been deprecated, please use `pattern` instead", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=4, ) current_json_schema_extra = json_schema_extra or extra diff --git a/fastapi/utils.py b/fastapi/utils.py index c4631d7ed..8ae50aa14 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -23,6 +23,7 @@ from fastapi._compat import ( may_v1, ) from fastapi.datastructures import DefaultPlaceholder, DefaultType +from fastapi.exceptions import FastAPIDeprecationWarning from pydantic import BaseModel from pydantic.fields import FieldInfo from typing_extensions import Literal @@ -195,9 +196,9 @@ def generate_operation_id_for_path( *, name: str, path: str, method: str ) -> str: # pragma: nocover warnings.warn( - "fastapi.utils.generate_operation_id_for_path() was deprecated, " + message="fastapi.utils.generate_operation_id_for_path() was deprecated, " "it is not used internally, and will be removed soon", - DeprecationWarning, + category=FastAPIDeprecationWarning, stacklevel=2, ) operation_id = f"{name}{path}" diff --git a/tests/benchmarks/test_general_performance.py b/tests/benchmarks/test_general_performance.py index 2da74b95c..dac297e4e 100644 --- a/tests/benchmarks/test_general_performance.py +++ b/tests/benchmarks/test_general_performance.py @@ -6,6 +6,7 @@ from typing import Annotated, Any import pytest from fastapi import Depends, FastAPI +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient if "--codspeed" not in sys.argv: @@ -89,7 +90,7 @@ def app(basemodel_class: type[Any]) -> FastAPI: warnings.filterwarnings( "ignore", message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, ) @app.post("/sync/validated", response_model=ItemOut) diff --git a/tests/test_compat_params_v1.py b/tests/test_compat_params_v1.py index 2ac96993a..704b3f77a 100644 --- a/tests/test_compat_params_v1.py +++ b/tests/test_compat_params_v1.py @@ -3,6 +3,7 @@ import warnings from typing import Optional import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from tests.utils import skip_module_if_py_gte_314 @@ -504,23 +505,23 @@ def test_body_repr(): # Deprecation warning tests for regex parameter def test_query_regex_deprecation_warning(): - with pytest.warns(DeprecationWarning, match="`regex` has been deprecated"): + with pytest.warns(FastAPIDeprecationWarning, match="`regex` has been deprecated"): Query(regex="^test$") def test_body_regex_deprecation_warning(): - with pytest.warns(DeprecationWarning, match="`regex` has been deprecated"): + with pytest.warns(FastAPIDeprecationWarning, match="`regex` has been deprecated"): Body(regex="^test$") # Deprecation warning tests for example parameter def test_query_example_deprecation_warning(): - with pytest.warns(DeprecationWarning, match="`example` has been deprecated"): + with pytest.warns(FastAPIDeprecationWarning, match="`example` has been deprecated"): Query(example="test example") def test_body_example_deprecation_warning(): - with pytest.warns(DeprecationWarning, match="`example` has been deprecated"): + with pytest.warns(FastAPIDeprecationWarning, match="`example` has been deprecated"): Body(example={"test": "example"}) diff --git a/tests/test_pydantic_v1_deprecation_warnings.py b/tests/test_pydantic_v1_deprecation_warnings.py index e0008e218..89ca6a865 100644 --- a/tests/test_pydantic_v1_deprecation_warnings.py +++ b/tests/test_pydantic_v1_deprecation_warnings.py @@ -1,6 +1,7 @@ import sys import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from tests.utils import skip_module_if_py_gte_314 @@ -19,7 +20,7 @@ def test_warns_pydantic_v1_model_in_endpoint_param() -> None: app = FastAPI() with pytest.warns( - DeprecationWarning, + FastAPIDeprecationWarning, match=r"pydantic\.v1 is deprecated.*Please update the param data:", ): @@ -40,7 +41,7 @@ def test_warns_pydantic_v1_model_in_return_type() -> None: app = FastAPI() with pytest.warns( - DeprecationWarning, + FastAPIDeprecationWarning, match=r"pydantic\.v1 is deprecated.*Please update the response model", ): @@ -61,7 +62,7 @@ def test_warns_pydantic_v1_model_in_response_model() -> None: app = FastAPI() with pytest.warns( - DeprecationWarning, + FastAPIDeprecationWarning, match=r"pydantic\.v1 is deprecated.*Please update the response model", ): @@ -82,7 +83,7 @@ def test_warns_pydantic_v1_model_in_additional_responses_model() -> None: app = FastAPI() with pytest.warns( - DeprecationWarning, + FastAPIDeprecationWarning, match=r"pydantic\.v1 is deprecated.*In responses=\{\}, please update", ): diff --git a/tests/test_regex_deprecated_body.py b/tests/test_regex_deprecated_body.py index cfbff19c8..9d58c5dae 100644 --- a/tests/test_regex_deprecated_body.py +++ b/tests/test_regex_deprecated_body.py @@ -3,6 +3,7 @@ from typing import Annotated import pytest from dirty_equals import IsDict from fastapi import FastAPI, Form +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient from .utils import needs_py310 @@ -10,7 +11,7 @@ from .utils import needs_py310 def get_client(): app = FastAPI() - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.post("/items/") async def read_items( diff --git a/tests/test_regex_deprecated_params.py b/tests/test_regex_deprecated_params.py index 7d9988f9f..8973657a9 100644 --- a/tests/test_regex_deprecated_params.py +++ b/tests/test_regex_deprecated_params.py @@ -3,6 +3,7 @@ from typing import Annotated import pytest from dirty_equals import IsDict from fastapi import FastAPI, Query +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient from .utils import needs_py310 @@ -10,7 +11,7 @@ from .utils import needs_py310 def get_client(): app = FastAPI() - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/items/") async def read_items( diff --git a/tests/test_schema_extra_examples.py b/tests/test_schema_extra_examples.py index 176b5588d..8f5195ba1 100644 --- a/tests/test_schema_extra_examples.py +++ b/tests/test_schema_extra_examples.py @@ -3,6 +3,7 @@ from typing import Union import pytest from dirty_equals import IsDict from fastapi import Body, Cookie, FastAPI, Header, Path, Query +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient from pydantic import BaseModel, ConfigDict @@ -21,7 +22,7 @@ def create_app(): def schema_extra(item: Item): return item - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.post("/example/") def example(item: Item = Body(example={"data": "Data in Body example"})): @@ -38,7 +39,7 @@ def create_app(): ): return item - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.post("/example_examples/") def example_examples( @@ -83,7 +84,7 @@ def create_app(): # ): # return lastname - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/path_example/{item_id}") def path_example( @@ -101,7 +102,7 @@ def create_app(): ): return item_id - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/path_example_examples/{item_id}") def path_example_examples( @@ -112,7 +113,7 @@ def create_app(): ): return item_id - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/query_example/") def query_example( @@ -132,7 +133,7 @@ def create_app(): ): return data - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/query_example_examples/") def query_example_examples( @@ -144,7 +145,7 @@ def create_app(): ): return data - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/header_example/") def header_example( @@ -167,7 +168,7 @@ def create_app(): ): return data - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/header_example_examples/") def header_example_examples( @@ -179,7 +180,7 @@ def create_app(): ): return data - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/cookie_example/") def cookie_example( @@ -199,7 +200,7 @@ def create_app(): ): return data - with pytest.warns(DeprecationWarning): + with pytest.warns(FastAPIDeprecationWarning): @app.get("/cookie_example_examples/") def cookie_example_examples( diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py index ab7e1d8a7..9d1baf853 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py @@ -2,6 +2,7 @@ import sys import warnings import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from inline_snapshot import snapshot from tests.utils import skip_module_if_py_gte_314 @@ -29,7 +30,7 @@ def get_client(request: pytest.FixtureRequest): warnings.filterwarnings( "ignore", message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, ) mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py index c45e04248..23b236888 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py @@ -2,6 +2,7 @@ import sys import warnings import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from inline_snapshot import snapshot from tests.utils import skip_module_if_py_gte_314 @@ -29,7 +30,7 @@ def get_client(request: pytest.FixtureRequest): warnings.filterwarnings( "ignore", message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, ) mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py index f3da849e0..61c0f6357 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py @@ -2,6 +2,7 @@ import sys import warnings import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from inline_snapshot import snapshot from tests.utils import skip_module_if_py_gte_314 @@ -29,7 +30,7 @@ def get_client(request: pytest.FixtureRequest): warnings.filterwarnings( "ignore", message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, ) mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py index 95efab2dc..585989a82 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py @@ -18,7 +18,7 @@ from ...utils import needs_py310 marks=( needs_py310, pytest.mark.filterwarnings( - "ignore:`regex` has been deprecated, please use `pattern` instead:DeprecationWarning" + "ignore:`regex` has been deprecated, please use `pattern` instead:fastapi.exceptions.FastAPIDeprecationWarning" ), ), ), diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py index 515a5a8d7..50be45896 100644 --- a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py +++ b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py @@ -2,6 +2,7 @@ import importlib import warnings import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient from ...utils import needs_pydanticv1 @@ -19,7 +20,7 @@ def get_client(request: pytest.FixtureRequest): warnings.filterwarnings( "ignore", message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, ) mod = importlib.import_module(f"docs_src.request_form_models.{request.param}") diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py index c5526b19c..83c717656 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py @@ -2,6 +2,7 @@ import importlib import warnings import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -20,7 +21,7 @@ def get_client(request: pytest.FixtureRequest): warnings.filterwarnings( "ignore", message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", - category=DeprecationWarning, + category=FastAPIDeprecationWarning, ) mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}")