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.

starlette.routing.NoMatchFound when including Starlette router into FastAPI app

See original GitHub issue

Describe the bug

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.

To Reproduce

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

Expected behavior

Web router should be working on http://127.0.0.1:8000/vlantoggler/

Environment

  • OS: Ubuntu 18.04.03
  • FastAPI Version 0.52.0, get it with: pip
  • Starlette Version 0.13.2, get it with pip
python3 -c "import fastapi; print(fastapi.__version__)"
0.52.0
  • Python version 3.6.7, get it with System
python3 --version                                      
Python 3.6.7

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

4reactions
zinglaxcommented, Feb 4, 2021

@horseinthesky & @tiangolo Thanks for getting to the bottom of this one. The switch from APIRouter to a mounted sub-application (class FastAPI) with mounted static directory worked, it allowed me to directly access the file via the expected URL.

I just wanted to mention that my url_for() calls in my template needed to have their name parameter prefixed with the sub-application name and a colon. This is because I had set the name field when I mounted my sub-application.

For Example: Using your code from above, if you had mounted your sub-application with a name parameter like the following

app.mount('/vlantoggler', vlantoggler_web_app, name="vlantoggler_web_app")

You would have also needed to change your url_for calls in templates from

<link href="{{ url_for('static', path='/css/bootstrap.min.css') }}" rel="stylesheet">

to

<link href="{{ url_for('vlantoggler_web_app:static', path='/css/bootstrap.min.css') }}" rel="stylesheet">

This tripped me up for awhile but just wanted to make a note of it for anyone else having this issue. Had to hunt through the code to find what I was doing wrong. Only found a small mention of it after the fact in the Starlette 0.7.1 release notes, might be worth noting in the Sub-Application or Templates & Static-Files docs.

Anyways thanks for all the hard work, You guys rock! 🎉🎉🎉

4reactions
horseintheskycommented, Apr 15, 2020

@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?

Read more comments on GitHub >

github_iconTop Results From Across the Web

starlette.routing.NoMatchFound: No route exists for name ...
As you know this error is output by class Router in routing.py I am developing a service with FastAPI as just REST API...
Read more >
Routing - Starlette
Starlette has a simple but capable request routing system. A routing table is defined as a list of routes, and passed when instantiating...
Read more >
tiangolo/fastapi - Gitter
and in my tests url = app.url_path_for("users_byemail") fails. with starlette.routing.NoMatchFound. I can see users_byemail in [r.name for r in app.routes].
Read more >
Static Files - FastAPI
FastAPI provides the same starlette.staticfiles as fastapi.staticfiles just ... The OpenAPI and docs from your main application won't include anything from ...
Read more >
starlette.routing.Router Example - Program Talk
Router. Learn how to use python api starlette.routing.Router. ... on this Operation" # error triggered with lifespan with TestClient(app) as client: with ......
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