alexschimpf / fastapi-versionizer

FastAPI Versionizer
MIT License
79 stars 13 forks source link

Global docs for all versions #30

Closed ollz272 closed 11 months ago

ollz272 commented 11 months ago

Subject of the issue

We'd like to be able to have some docs that include all version routes in. This is proving quite difficult to generate. We were hoping you'd either have some idea on how to achieve this, or be able to implement something within the package that would generate global docs for all api versions.

What we've tried is something like:

@docs_router.get("/openapi.json", include_in_schema=False)
def openapi(self):
    """Return openapi spec json."""
    # Start by adding top level routes to the docs
    all_routes = []

    def add_routes(api_routes: list[BaseRoute], routes: list[APIRoute]):
        """Function to add routes to a list of routes.

        :param api_routes: A list of api routes, or a mounted application.
        :param routes: A list of routes which we add to.
        """
        for route in api_routes:
            match route:
                # If its mounted application, we recusively call add_routes with the
                # mounted applications routes, and the routes to be appened.
                case Mount():
                    add_routes(route.app.routes, routes)
                # Otherwise, we just add the route to the routes
                case _:
                    routes.append(route)

    add_routes(app.routes, all_routes)

    return get_openapi(title="GE Timescale API", version=app.version, routes=all_routes)

This captures all the endpoints, but doesn't include any prefixes on the routes, which defeats the purpose a bit.

alexschimpf commented 11 months ago

Ok, so I just tried whipping something together, but it's pretty hacky. Also, I haven't tested this super thoroughly. But the swagger docs do look correct from what I can tell. Lemme know if this works at all.

Not sure if there is a simpler way to clone the route.

@app.get("/test/openapi.json", include_in_schema=False)
def test_openapi():
    all_routes = []

    def add_routes(api_routes, routes, parent_path=''):
        for route in api_routes:
            if isinstance(route, Mount):
                add_routes(route.app.routes, routes, route.path)
            else:
                route_dict = route.__dict__
                for field in (
                    'app', 'path_format', 'param_convertors', 'path_regex', 'unique_id', 'response_field',
                    'secure_cloned_response_field', 'response_fields', 'dependant', 'body_field'
                ):
                    route_dict.pop(field, None)
                route_clone = APIRoute(**route_dict)
                route_clone.path_format = route_clone.path = f'{parent_path}{route_clone.path_format}'
                routes.append(route_clone)

    add_routes(app.routes, all_routes)

    return fastapi.openapi.utils.get_openapi(title="API", version=app.version, routes=all_routes)

Basic idea:

It seems like FastAPI's openapi utils module is pretty limited, so I'm not really seeing an easier way besides just building your own HTML page without Swagger.

alexschimpf commented 11 months ago

Another option is to make your own version of FastAPI's get_openapi function. It's not a crazy amount of code. Perhaps we could add some re-implementation of that in this project.

alexschimpf commented 11 months ago

@ollz272 Ok, I started thinking about things more and decided to just re-implement this whole library. It was getting a bit messy and overly complicated. During this process, I added automatic generation of a global/main docs page with all versioned endpoints. You can see my PR here: https://github.com/alexschimpf/fastapi-versionizer/pull/32

This is now available in version 2.0.0.

alexschimpf commented 11 months ago

Going to close this issue. Please re-open if you feel there is anything left here.