Skip to content

OpenAPI Integration API

This module handles the modification of the OpenAPI (Swagger) schema to reflect the deprecation status of your endpoints.

Dynamic Schema Updates

FastAPI does not automatically set deprecated: true in OpenAPI based on custom decorators or deeply nested dependencies. This module solves that elegantly.

Call auto_deprecate_openapi(app) after you have defined all your routes. This method will seamlessly override FastAPI's internal .openapi() generator. Because it evaluates deprecation states dynamically on every single request to /docs or /openapi.json, your long-running applications (like those in Kubernetes or VMs) will automatically update the Swagger UI when a deprecation date occurs, without requiring a server restart.

from fastapi import FastAPI
from fastapi_deprecation import deprecated, auto_deprecate_openapi

app = FastAPI()

@app.get("/old")
@deprecated(deprecation_date="2020-01-01")
def old(): ...

# Must call this after routes are defined!
auto_deprecate_openapi(app)

Recursive Support

The function automatically detects mounted sub-applications (using app.mount()) and updates their OpenAPI definitions recursively.

Upcoming Deprecations

If the endpoint has a deprecation_date that is in the future, the library will not set deprecated: true yet (as the endpoint is still fully active). However, it will append an **UPCOMING DEPRECATION** warning to the endpoint's description so clients can prepare. Once the date passes, deprecated: true is automatically applied.

Reference

fastapi_deprecation.openapi

auto_deprecate_openapi(app, always_rebuild=True)

Register the global exception handler for custom sunset responses. Overrides FastAPI's openapi() method to dynamically inject deprecation information on every request, ensuring that long-running apps correctly reflect state changes (warning -> sunset) over time.

Source code in src/fastapi_deprecation/openapi.py
def auto_deprecate_openapi(app: FastAPI, always_rebuild: bool = True):
    """
    Register the global exception handler for custom sunset responses.
    Overrides FastAPI's openapi() method to dynamically inject deprecation
    information on every request, ensuring that long-running apps
    correctly reflect state changes (warning -> sunset) over time.
    """
    app.add_exception_handler(DeprecationSunset, sunset_exception_handler)

    def extract_deps(target_app: FastAPI):
        deps = {}
        if hasattr(target_app, "user_middleware"):
            for mw in target_app.user_middleware:
                if mw.cls == DeprecationMiddleware:
                    mw_configs = mw.kwargs.get("deprecations", {})
                    for p, d in mw_configs.items():
                        deps[p] = d.config if hasattr(d, "config") else d
        return deps

    def wrap_app(
        target_app: FastAPI, prefix_for_globals: str, parent_global_deps: dict
    ):
        local_deps = extract_deps(target_app)
        combined_deps = {**parent_global_deps, **local_deps}

        original_openapi = target_app.openapi

        def custom_openapi():
            if not always_rebuild and getattr(
                target_app, "_custom_openapi_schema", None
            ):
                return target_app._custom_openapi_schema

            # Force FastAPI engine to build a fresh schema from the pristine routes
            target_app.openapi_schema = None
            openapi_schema = original_openapi()

            # Inject RFC compliance tags
            openapi_schema.setdefault("info", {})["x-rfc-compliance"] = [
                "RFC9745",
                "RFC8594",
            ]

            # Dynamically inject deprecations into the fresh schema
            _apply_dynamic_deprecations(
                target_app.routes,
                openapi_schema,
                prefix_for_globals=prefix_for_globals,
                global_deps=combined_deps,
            )

            if not always_rebuild:
                target_app._custom_openapi_schema = openapi_schema

            return openapi_schema

        target_app.openapi = custom_openapi

        # Find mounted sub-apps
        for route in target_app.routes:
            if isinstance(route, Mount) and isinstance(route.app, FastAPI):
                # Prepend the mount's path to the prefix for globals
                # so that router paths resolve correctly against middleware configurations
                new_prefix = prefix_for_globals + getattr(route, "path", "")
                wrap_app(route.app, new_prefix, combined_deps)

    wrap_app(app, "", extract_deps(app))