question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[BUG] OpenAPI routes do not acknowledge root_path

See original GitHub issue

Also raised on https://github.com/tiangolo/fastapi/pull/26#issuecomment-562755647. See also #544.

Describe the bug

Write here a clear and concise description of what the bug is.

To Reproduce

Replace each part with your own scenario:

  1. Create a file with:
from fastapi import FastAPI

app = FastAPI()

@app.get("/app")
def read_root():
    return {"Hello": "World"}
  1. Launch it using uvicorn --root-path="bar" test_app:app
  2. Open the browser and go to http://127.0.0.1:8000/docs.
  3. From the documentation, call the GET /app route.
  4. The doc page calls /app and succeeds.

Expected behavior

The above test should fail after having called /bar/app, since root_path is supposed to prefix all generated URLs in case the application is served behind a reverse-proxy, among ther things. FastAPI only acknowledges openapi_prefix for the API doc.

Environment

  • OS: Windows
  • FastAPI Version: 0.45.0
  • Python version: 3.8.0

Additional context

A similar issue applies to sub-applications:

from fastapi import FastAPI

app = FastAPI()


@app.get("/app")
def read_main():
    return {"message": "Hello World from main app"}


#subapi = FastAPI(openapi_prefix="/subapi")
subapi = FastAPI()

@subapi.get("/sub")
def read_sub(request: Request):
    return {
        "root_path": request.scope['root_path'],
        "raw_path": request.scope['raw_path'],
        "path": request.scope['path'],
        "app_url_for": app.url_path_for("read_sub"),
        "subapp_url_for": subapi.url_path_for("read_sub"),
    }

app.mount("/subapi", subapi)
{
  "root_path":"bar/subapi",
  "raw_path":"/subapi/sub",
  "path":"/sub",
  "app_url_for":"/subapi/sub",
  "subapp_url_for":"/sub"
}

(url_for not being prefixed with root_path is fixed upstream by encode/starlette#699)

Unless openapi_prefix="/subapi" is passed when creating the subapplication, both http://127.0.0.1:8000/docs and http://127.0.0.1:8000/subapi/docs will point towards http://127.0.0.1:8000/openapi.json, which goes against the point of having isolated subapplications.

openapi_prefix should probably just be deprecated and assumed to match root_path if absent.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:14
  • Comments:17 (8 by maintainers)

github_iconTop GitHub Comments

7reactions
iwpndcommented, Jan 21, 2020

I feel that this is closely related to a question I was about to write, so I will post it here instead to avoid duplication.

– Hi, I’m deploying a minimal application written with FastAPI and Mangum as an adapter using AWS SAM. It works like a charm, and I’m in the process off writing an article about it to append in the fastapi documentation. Yet one thing that boggles my mind is the path of the /openapi.json and I can’t wrap my head around how to work it in different situations.

So let’s assume I have a little application like this:

from fastapi import FastAPI
from example_app.api.api_v1.api import router as api_router
from example_app.core.config import API_V1_STR, PROJECT_NAME
from mangum import Mangum

app = FastAPI(
    title=PROJECT_NAME,
    # if not custom domain
    # openapi_prefix="/prod"
)
app.include_router(api_router, prefix=API_V1_STR)

@app.get("/ping")
def pong():
    return {"ping": "pong!"}

handler = Mangum(app, enable_lifespan=False, )

I deploy it to API Gateway/Lambda to a stage called prod so the resulting url is https://xxxxxxxxxx.execute-api.eu-west-1.amazonaws.com/prod. The API Gateway is set up with {proxy+} integration. Now how is it, that the /ping endpoint “knows” that the base-url is […].amazonaws.com/prod, yet openapi.json assumes to be at […].amazonaws.com/openapi.json?
I know I can change the prefix with openapi_prefix="/prod" but that makes it inconvenient if I wanted to use another stage than prod. After all I don’t have to do it for my other endpoints either. So is there a reason it doesn’t work the same way as with my other endpoints? Is it a bug, or am I just missing something very obvious?

5reactions
georgebvcommented, Apr 2, 2021

Recently ran into this issue and the best solution I could think of was monkey patching openapi.json:

app = FastAPI(
    title=settings.PROJECT_NAME,
    description=settings.PROJECT_DESCRIPTION,
    version=settings.PROJECT_VERSION,
    openapi_url=None,
    openapi_tags=openapi_tags,
    docs_url=None,
    redoc_url=None,
    root_path=settings.ROOT_PATH,
)

@app.get("/", include_in_schema=False)
async def access_documentation():
    openapi_url = app.root_path + "/openapi.json"
    return get_swagger_ui_html(openapi_url=openapi_url, title="docs")

@app.get("/openapi.json", include_in_schema=False)
async def access_openapi():
    openapi = get_openapi(
        title=app.title,
        version=app.version,
        description=app.description,
        routes=app.routes,
        tags=app.openapi_tags,
    )

    monkey_patched_openapi = {
        key: value for key, value in openapi.items() if key != "paths"
    }
    monkey_patched_openapi["paths"] = {}
    for key, value in openapi["paths"].items():
        monkey_patched_openapi["paths"][app.root_path + key] = value

    return monkey_patched_openapi

Not elegant, but this did exactly what I was looking for: serving my API at /api/v1/ with swaggerui working as expected.

Edit: I had to declare separate openapi functions because I wanted to protected them using basic auth, which is not used for anything else in the app (dependencies not shown above for clarity).

FYI, I’m running behind NGINX in docker-compose which looks like this:

events {}

http {
    upstream backend_server {
        server api:80;
    }

    server {
        listen 80;

        location /api/v1/ {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://backend_server/;
        }
    }
}

Edit 2: the above can be achieved in a simpler way with a downside (or upside) of the “servers” drop-down appearing and the /api/v1 prefix disappearing:

@app.get("/openapi.json", include_in_schema=False)
async def access_openapi():
    openapi = get_openapi(
        title=app.title,
        version=app.version,
        description=app.description,
        routes=app.routes,
        tags=app.openapi_tags,
    )
    openapi["servers"] = [{"url": app.root_path}]
    return openapi
Read more comments on GitHub >

github_iconTop Results From Across the Web

Fastapi root_path ignored while using api versioning
Is there any way to make fast api routes acknowledge root_path while using api versioning Sample Code to reproduce:
Read more >
Dropwizard Core
If RiakClientManager#start() throws an exception–e.g., an error connecting to the server–your application will not start and a full exception will be logged. If ......
Read more >
There's No Reason to Write OpenAPI By Hand
Many of these API descriptions (OpenAPI, JSON Schema, ... One downside to annotations is that they don't confirm the code is doing what...
Read more >
Apache Camel Development Guide Red Hat Fuse 7.10
So the In message at the start of a route is typically not the same as the In ... is true then OpenAPI...
Read more >
F.A.Q - Springdoc-openapi
You can set list of paths to include using the following property: ... If you don't want to serve the UI from your...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found