alexschimpf / fastapi-versionizer

FastAPI Versionizer
MIT License
79 stars 13 forks source link

[Question] Different swagger and API paths #31

Closed OlegZv closed 11 months ago

OlegZv commented 11 months ago

Subject of the issue

First of all, thank you for the great package! Just starting to experiment with it and have a question. How would I go about having separate /swagger and actual api routes? I think the example would be easiest to describe what I'm trying to achieve

Your environment

Steps to reproduce

from typing import Any, Dict, Tuple
from fastapi import APIRouter, FastAPI, openapi
from fastapi.responses import HTMLResponse
from fastapi_versionizer import versionize

# there are a bunch of routers with different prefixes
router = APIRouter(prefix="/version")
@router.get("")
async def get_version() -> str:
    return "latest_version"

def get_openapi(app_: FastAPI, version: Tuple[int, int]) -> Dict[str, Any]:
    openapi_schema = openapi.utils.get_openapi(
        title=app_.title,
        version=app_.version,
        routes=app_.routes,
        servers=[{"url": f"/api/v{version[0]}.{version[1]}"}],
    )

    # Remove 422 response from schema
    for schema_path in openapi_schema["paths"]:
        for method in openapi_schema["paths"][schema_path]:
            openapi_schema["paths"][schema_path][method]["responses"].pop("422", None)

    return openapi_schema

def get_docs(version: Tuple[int, int]) -> HTMLResponse:
    version_prefix = f"/v{version[0]}.{version[1]}"
    return openapi.docs.get_swagger_ui_html(
        openapi_url=f"{version_prefix}/openapi.json",
        title=f"Swagger - v{version[0]}.{version[1]}",
    )

app = FastAPI(
    title="My APIs",
    docs_url=None,
    redoc_url=None,
)
app.include_router(router)

versionize(
    app=app,
    docs_url="/swagger",
    prefix_format="/v{major}.{minor}",
    enable_latest=True,
    version_format="{major}.{minor}",
    get_openapi=get_openapi,
    get_docs=get_docs,
)

Desired behaviour

/{version}/swagger to open the documentation based on the /{version}/openapi.json. For example: /latest/swagger (with /latest/openapi.json) And API call to be prefixed with /api/{version}. From the example above, I'd like to have /api/v1.0/version return "latest_version"

Actual behaviour

Doesn't look like there's a way to provide a separate swagger and api paths. If I change the prefix_format to /api/v{major}.{minor} then API paths are as expected, but /swagger path is /api/{version}/swagger.

P.S. After writing this out, I'm thinking to myself "what is wrong with `/api/{version}/swagger?". Still curious if there is a way to do something like described. Thank you!

alexschimpf commented 11 months ago

The versionize function returns the list of all versions. So you could probably just loop over those and, for each, add a /v{version}/swagger route to the root FastAPI app manually. The swagger route would basically just call your get_docs function like how we do it here

alexschimpf commented 11 months ago

There is also a callback param you could utilize, but you'd add the route to your root FastAPI app (not the versioned app passed to the callback function).

OlegZv commented 11 months ago

The versionize function returns the list of all versions. So you could probably just loop over those and, for each, add a /v{version}/swagger route to the root FastAPI app manually. The swagger route would basically just call your get_docs function like how we do it here

This worked perfectly, thank you!