Reputation: 88619
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
and hello_world_number
in a similar way?
Upvotes: 39
Views: 52272
Reputation: 10909
url_for
exists, but is provided by starlette, the server underpinning FastAPI:
https://www.starlette.io/routing/#reverse-url-lookups
Upvotes: 3
Reputation: 88619
We have got Router.url_path_for(...)
method which is located inside the starlette package
FastAPI
instanceThis 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/"
APIRouter
, router.url_path_for('hello_world')
may not work since router
isn't an instance of FastAPI
class. That is, we must have the FastAPI
instance to resolve the URLRequest
instanceThis 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/"
}
request
parameter in every (or required) view to resolve the URL, which might raise an ugly feel to developers.Upvotes: 63
Reputation: 31
If you need to resolve URLs in template, Starlette (so FastAPI) defines the
url_for()
function in the default Jinja environment by using the request
instance of the context and url_for
method:
https://github.com/encode/starlette/blob/master/starlette/templating.py#L63
To use it in template: {{ url_for('hello_world_number', number=42) }}
.
Upvotes: 0
Reputation: 196
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
Upvotes: 1
Reputation: 20756
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
Upvotes: 13