I have an FastAPI app object which I want to use to several applications via including them as a routers:
import uvicorn
from fastapi import FastAPI
from api import vlantoggler_api_router
from views import vlantoggler_web_router
app = FastAPI()
app.include_router(vlantoggler_api_router,
prefix='/vlantoggler/api')
app.include_router(vlantoggler_web_router,
prefix='/vlantoggler')
if __name__ == "__main__":
uvicorn.run(app, loop='uvloop', log_level='debug')
Then here is my api_router:
vlantoggler_api_router = APIRouter(
# title='Yandex.Cloud Netinfra API',
# description='A bundled API for Yandex.Cloud Netinfra team tools',
routes=[
APIRoute('/', check, methods=['GET'], tags=['VlanToggler'],
name='Get current interface state',
# summary='String replaces function name AND name on Swagger API page',
description='ToR and Interface names are validated and current interface state is returned',
response_description='Successfully got interface state',
response_class=JSONResponse,
response_model=Success,
responses={**get_responses}
),
APIRoute('/', toggle, methods=['POST'], tags=['VlanToggler'],
name='Switch interface state',
description='ToR and Interface names are validated and ToR interface is toggled to desired state',
response_description='Interface successfully switched',
response_class=JSONResponse,
response_model=Success,
responses={**post_responses},
),
],
)
and web form router
vlantoggler_web_router = Router(
[
Route('/', home, methods=['GET', 'POST']),
Mount('/statics', statics, name='static'),
],
)
As you can see I just import them from another module to make things nice and clean.
When I run the app:
python3 ft.py
INFO: Started server process [14057]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
My api works perfectly fine and accessable via http://127.0.0.1:8000/vlantoggler/api/.
There is also nothing wrong with the /docs, they are at http://127.0.0.1:8000/docs as I want it.
BUT web form doesn't work (should be accessable via http://127.0.0.1:8000/vlantoggler/). I have an Internal Server Error instead:
Traceback (most recent call last):
File "/usr/local/lib/python3.6/dist-packages/uvicorn/protocols/http/httptools_impl.py", line 385, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/usr/local/lib/python3.6/dist-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "/usr/local/lib/python3.6/dist-packages/fastapi/applications.py", line 151, in __call__
await super().__call__(scope, receive, send) # pragma: no cover
File "/usr/local/lib/python3.6/dist-packages/starlette/applications.py", line 102, in __call__
await self.middleware_stack(scope, receive, send)
File "/usr/local/lib/python3.6/dist-packages/starlette/middleware/errors.py", line 181, in __call__
raise exc from None
File "/usr/local/lib/python3.6/dist-packages/starlette/middleware/errors.py", line 159, in __call__
await self.app(scope, receive, _send)
File "/usr/local/lib/python3.6/dist-packages/starlette/exceptions.py", line 82, in __call__
raise exc from None
File "/usr/local/lib/python3.6/dist-packages/starlette/exceptions.py", line 71, in __call__
await self.app(scope, receive, sender)
File "/usr/local/lib/python3.6/dist-packages/starlette/routing.py", line 550, in __call__
await route.handle(scope, receive, send)
File "/usr/local/lib/python3.6/dist-packages/starlette/routing.py", line 227, in handle
await self.app(scope, receive, send)
File "/usr/local/lib/python3.6/dist-packages/starlette/routing.py", line 41, in app
response = await func(request)
File "/home/horseinthesky/vl_proto/views.py", line 83, in home
return render(request)
File "/home/horseinthesky/vl_proto/views.py", line 40, in render
return templates.TemplateResponse('index.html', context)
File "/usr/local/lib/python3.6/dist-packages/starlette/templating.py", line 87, in TemplateResponse
background=background,
File "/usr/local/lib/python3.6/dist-packages/starlette/templating.py", line 27, in __init__
content = template.render(context)
File "/usr/local/lib/python3.6/dist-packages/jinja2/asyncsupport.py", line 76, in render
return original_render(self, *args, **kwargs)
File "/usr/local/lib/python3.6/dist-packages/jinja2/environment.py", line 1008, in render
return self.environment.handle_exception(exc_info, True)
File "/usr/local/lib/python3.6/dist-packages/jinja2/environment.py", line 780, in handle_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python3.6/dist-packages/jinja2/_compat.py", line 37, in reraise
raise value.with_traceback(tb)
File "templates/index.html", line 10, in top-level template code
<link href="{{ url_for('static', path='/css/bootstrap.min.css') }}" rel="stylesheet">
File "/usr/local/lib/python3.6/dist-packages/starlette/templating.py", line 59, in url_for
return request.url_for(name, **path_params)
File "/usr/local/lib/python3.6/dist-packages/starlette/requests.py", line 137, in url_for
url_path = router.url_path_for(name, **path_params)
File "/usr/local/lib/python3.6/dist-packages/starlette/routing.py", line 486, in url_path_for
raise NoMatchFound()
starlette.routing.NoMatchFound
Web router should be working on http://127.0.0.1:8000/vlantoggler/
0.52.0, get it with: pippippython3 -c "import fastapi; print(fastapi.__version__)"
0.52.0
python3 --version
Python 3.6.7
Check the note with the "Very Technical Details" in this section: https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-prefix-tags-responses-and-dependencies
You can use include_router for FastAPI APIRouters, but not to mount a sub-application. If you need to do that, use a mount.
@tiangolo Thank you very much.
I changed
from starlette.routing import Router
vlantoggler_web_router = Router(
[
Route('/', home, methods=['GET', 'POST']),
Mount('/statics', statics, name='static'),
],
)
to
from starlette.applications import Starlette
vlantoggler_web_app = Starlette(
routes=[
Route('/', home, methods=['GET', 'POST']),
Mount('/statics', statics, name='static'),
],
)
And then included APIRouters with include_router and
from fastapi import FastAPI
from api import vlantoggler_api_router
from views import vlantoggler_web_app
app = FastAPI(
title='Yandex.Cloud Netinfra API',
description='A bundled API for Yandex.Cloud Netinfra team tools',
)
app.include_router(vlantoggler_api_router,
prefix='/vlantoggler/api')
app.mount('/vlantoggler', vlantoggler_web_app)
And this worked perfectly fine.
Just want to clarify: Router from starlette.routing is not similar object to APIRouter from fastapi.routing? And I cannot include_router a Router object to FASTApi app object?!
So Router is more like another FASTApi object and I can only mount one to another?
Yes, the APIRouter class inherits from Router, and APIRouter is what adds all the FastAPI stuff.
include_router doesn't "mount" an app, it creates a _path operation_ for each _path operation_ in the "included" router. That's what allows it to be in the same OpenAPI, the same app, the same docs, etc.
Check the details in the technical notes here: https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter
Ok. Thank you for your help.