FastAPI: Retrieve URL from view name ( route name )
Solution 1
We have got Router.url_path_for(...)
method which is located inside the starlette package
Method-1: Using FastAPI
instance
This method is useful when you are able to access the FastAPI
instance in your current context. (Thanks to @Yagizcan Degirmenci)
from fastapi import FastAPI
app = FastAPI()
@app.get('/hello/')
def hello_world():
return {"msg": "Hello World"}
@app.get('/hello/{number}/')
def hello_world_number(number: int):
return {"msg": "Hello World Number", "number": number}
print(app.url_path_for('hello_world'))
print(app.url_path_for('hello_world_number', number=1}))
print(app.url_path_for('hello_world_number', number=2}))
# Results
/hello/
/hello/1/
/hello/2/
Drawback
- If we are using
APIRouter
,router.url_path_for('hello_world')
may not work sincerouter
isn't an instance ofFastAPI
class. That is, we must have theFastAPI
instance to resolve the URL
Method-2: Request
instance
This method is useful when you are able to access the Request
instance (the incoming request), usually, within a view.
from fastapi import FastAPI, Request
app = FastAPI()
@app.get('/hello/')
def hello_world():
return {"msg": "Hello World"}
@app.get('/hello/{number}/')
def hello_world_number(number: int):
return {"msg": "Hello World Number", "number": number}
@app.get('/')
def named_url_reveres(request: Request):
return {
"URL for 'hello_world'": request.url_for("hello_world"),
"URL for 'hello_world_number' with number '1'": request.url_for("hello_world_number", number=1),
"URL for 'hello_world_number' with number '2''": request.url_for("hello_world_number", number=2})
}
# Result Response
{
"URL for 'hello_world'": "http://0.0.0.0:6022/hello/",
"URL for 'hello_world_number' with number '1'": "http://0.0.0.0:6022/hello/1/",
"URL for 'hello_world_number' with number '2''": "http://0.0.0.0:6022/hello/2/"
}
Drawback
- We must include the
request
parameter in every (or required) view to resolve the URL, which might raise an ugly feel to developers.
Solution 2
Actually you don't need to reinvent the wheel. FastAPI supports this out-of-box (Actually Starlette), and it works pretty well.
app = FastAPI()
@app.get("/hello/{number}/")
def hello_world_number(number: int):
return {"msg": "Hello World Number", "number": number}
If you have an endpoint like this you can simply use
In: app.url_path_for("hello_world_number", number=3)
In: app.url_path_for("hello_world_number", number=50)
Out: /hello/3/
Out: /hello/50/
In FastAPI, APIRouter and FastAPI(APIRoute) inherits from Router(Starlette's) so, if you have an APIRouter like this, you can keep using this feature
router = APIRouter()
@router.get("/hello")
def hello_world():
return {"msg": "Hello World"}
In: router.url_path_for("hello_world")
Out: /hello
Solution 3
url_for exists, but is provided by starlette, the server underpinning FastApi: https://www.starlette.io/routing/#reverse-url-lookups
Solution 4
If the same function name is defined under multiple APIRouters, request.url_for
and router.url_path_for
would return the first matching function name (in the order of include_router).
Here is a way to get the correct url with the tag of APIRouter when there is a function name conflict, if someone needs it:
Step 1: put this in your __init__.py
:
def url_of(request: Request, name: str, **path_params: dict):
from fastapi.routing import APIRoute
from starlette.routing import NoMatchFound
tag, tid, fname = None, name.find('.'), name
if tid > 0:
tag = name[:tid]
fname = name[tid + 1:]
url_no_tag = None
for route in request.app.router.routes:
if not isinstance(route, APIRoute):
continue
if fname == route.name and (not tag or tag in route.tags):
try:
url_path = route.url_path_for(fname, **path_params)
url_no_tag = url_path.make_absolute_url(base_url=request.base_url)
if tag:
return url_no_tag
except NoMatchFound:
pass
if url_no_tag:
return url_no_tag
return request.url_for(name, **path_params)
Step 2: add a tag for APIRouters:
router = APIRouter(prefix='/user', tags=['user'])
@router.get('/')
def login():
return 'login page'
Step 3: retrieve the url in any where:
@router2.get('/test')
def test(request: Request):
return RedirectResponse(url_of(request, 'user.login') + '?a=1')
2021/07/10 rename url_as
to url_of
Related videos on Youtube
JPG
Copied, but a fact! An answer a day, keeps dementia away.
Updated on September 28, 2022Comments
-
JPG over 1 year
Suppose I have following views,
from fastapi import FastAPI app = FastAPI() @app.get('/hello/') def hello_world(): return {"msg": "Hello World"} @app.get('/hello/{number}/') def hello_world_number(number: int): return {"msg": "Hello World Number", "number": number}
I have been using these functions in Flask and Django
So, how can I obtain/build the URLs of
hello_world
andhello_world_number
in a similar way? -
Shawn about 3 yearsWhat would you suggest as an approach when you have multiple router files, and want to get the
url_path_for
a route in a different file? Mymain.py
does a bunch ofapp.include_router
to get all the routes.Thanks! -
NAGA RAJ S almost 3 yearsit's working fine.but you can only redirectResponse when parent and target method have same route .Eg if test is post method means you can only call a post method instead you can't call the get method using the post method request object.
-
liber almost 3 yearsThis answer aims to solve the problem of building URL from function name proposed by 'jpg'. RedirectResponse is an example of how to use the built URL. RedirectResponse is returned with 307 as the default status code (a new request is initiated in the same way during redirection). If the 'test' needs to be POST and 'login' is GET, we can set the status_code parameter as 302:
RedirectResponse(url_as(request, 'user.login') + '?a=1', status_code=302)
. Theurl_as
can also be used in other ways. In fact, I register theurl_as
as a global template method in jinja2 @NAGARAJS -
JPG almost 3 yearsIf the
request
Inrequest.url_for
is an incoming request instance, you don't need to implement the functionurl_of(...)
, because, therequest
object has all the route informations. -
liber almost 3 yearsI didn't test the
request.url_for
adequately,url_for
can indeed get all the urls of the app by the function name. But if the same function name is defined under multiple APIRouters,url_for
would return the first matching function name (in the order of include_router).url_of
provides a way to get the correct url with the tag of APIRouter when there is a function name conflict. The answer has been updated. Thanks @JPG -
ruslaniv over 2 years@Shawn I used
return fastapi.responses.RedirectResponse(url=request.url_for(name='account'), status_code=status.HTTP_302_FOUND)
in my view function based view -
stephane over 2 yearsIt was not mentioned that
url_for
is exported in the template context. -
Alex-Bogdanov over 2 yearsyour approach returns path, not URL.
flask.url_for()
returns absolute URL value -
smartexpert about 2 yearsGreat answer! I'm using Method 2 with ViewModel. I've defined the base ViewModel class to pass the
request.url_for
as an argument calledurl_for
(flask nostalgia) and thereby transparently have access to it in my jinja templates (as long as we pass request, which I was already doing)