-
-
Notifications
You must be signed in to change notification settings - Fork 6.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OpenAPI routes do not acknowledge root_path #829
Comments
Faced it when I was using uvicorn-gunicorn-fastapi-docker which was configured as nginx sub app Example config:
|
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. -- 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 |
Running into the same issue as @iwpnd - I am unable to host OpenAPI docs via Lambda because the |
you can use it like that app = FastAPI(
title=PROJECT_NAME,
# if not custom domain
openapi_prefix="/your_stage"
) but yeah, it's not ideal. |
@iwpnd Hah nice response time.. I just dove through the source and started seeing that but haven't tested it yet. Thanks for verifying that |
Ah, so I think my issue is that I'm combining FastAPI with Mangum, with the latter's As far as my app is concerned, there is no "v2" prefix (because it's a sub-app and I felt that it shouldn't have knowledge of where it lives) but this breaks the "openapi.json" URL link in the HTML docs pages because it doesn't know to append "v2/" to the URL. Neither the |
same here with AWS Lambda and API Gateway, my standard base path is api.mycompany.com and I have /my_stage (staging / production) how should we do ? |
As raised in #1294, another possibility could be to have the
Therefore, using the example app from the initial post: from fastapi import FastAPI, Request
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) We would get:
{
"openapi": "3.0.2",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"servers": [
{
"url": "/foo/"
}
],
"paths": {
"/app": {
"get": {
"summary": "Read Main",
"operationId": "read_main_app_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
}
}
}
{
"openapi": "3.0.2",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/sub": {
"get": {
"summary": "Read Sub",
"operationId": "read_sub_sub_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
}
},
"servers": [
{
"url": "/foo/subapi"
}
]
} Which does exactly what we need in terms of ensuring the doc acknowledges the reverse proxy, and everything else just falls into place. |
Hey all, thanks for the discussion here! 🍰 This was fixed in #1199 implementing support for Available in FastAPI You can read the new docs at Sub Applications - Mounts, which now ✨ just works ✨ without any extra parameters. And the new extensive docs at Behind a Proxy that explain it all in detail, including how to test it all using a local Traefik proxy on top of Uvicorn, to test all the This took me a bit of time to handle as I wanted to first re-read the WSGI and ASGI specs to make sure I wasn't missing anything. And then to test it all with an actual reverse proxy with path prefix stripping (what the |
@tiangolo: What's your opinion about prepending the root_path/mount path to every route in the openapi doc (what we're doing now) compared to declaring the root as a |
Closing this, now that #1596 has been merged. I much prefer this new approach compared to the other one, since it seems more in line with how the OpenAPI specs intends its definitons to look (the routes known to Starlette actually match what's in openapi.yml). |
I'm a little confused. if i'm not behind any proxy, i just want all routes to be prefixed with /api/v1 (e.g. /api/v1/user), root_path option doesn't work, i'm getting the "Not Found /api/v1/openapi.json" error when i go to /docs. So do I need to manually add "/api/v1" to every @app.get entry? |
@andreixk: No, If you just want a route mounted at |
@andreixk when you mount an application it basically looks into the [
{'path': '/openapi.json'},
{'path': '/docs'},
{'path': '/docs/oauth2-redirect'},
{'path': '/redoc'},
] When you mount another application you are just adding that route to the Now it will look like this [
{'path': '/openapi.json'},
{'path': '/docs'},
{'path': '/docs/oauth2-redirect'},
{'path': '/redoc'},
{'path': '/subapi'},
] You are just making When you go to So if you are not behind the proxy you can use prefix which simply does the same thing underneath for example, when you are including a router app.include_router(api, prefix="/api") It just adds a prefix when adding the paths that declared inside from fastapi import APIRouter
api = APIRouter()
@app.get("/dummy")
...
@app.post("/dummier")
...
@app.delete("/dummiest")
... From the main application when you are including it from fastapi import FastAPI
from somewhere import api
app = FastAPI()
app.include_router(api, prefix="/api") This only adds a prefix when adding paths to the So in your case adding a prefix should be enough when including your router. If you are still getting |
@sm-Fifteen @ycd Thanks guys! |
Recently ran into this issue and the best solution I could think of was monkey patching 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 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:
Edit 2: the above can be achieved in a simpler way with a downside (or upside) of the "servers" drop-down appearing and the @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 |
Adding some commentary here for any future travelers. app = FastAPI(
openapi_url="/api",
root_path=settings.ROOT_PATH,
servers=[{"url": settings.ROOT_PATH}],
) That's it. No need to create your own |
Isn't |
Also raised on #26 (comment). 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:
uvicorn --root-path="bar" test_app:app
http://127.0.0.1:8000/docs
.GET /app
route./app
and succeeds.Expected behavior
The above test should fail after having called
/bar/app
, sinceroot_path
is supposed to prefix all generated URLs in case the application is served behind a reverse-proxy, among ther things. FastAPI only acknowledgesopenapi_prefix
for the API doc.Environment
Additional context
A similar issue applies to sub-applications:
(
url_for
not being prefixed withroot_path
is fixed upstream by encode/starlette#699)Unless
openapi_prefix="/subapi"
is passed when creating the subapplication, bothhttp://127.0.0.1:8000/docs
andhttp://127.0.0.1:8000/subapi/docs
will point towardshttp://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 matchroot_path
if absent.The text was updated successfully, but these errors were encountered: