FastAPI:从视图名称(路由名称)中检索 URL

FastAPI: Retrieve URL from view name ( route name )

假设我有以下观点,

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}

我一直在 Flask 和 Django 中使用这些函数

那么,我如何才能以类似的方式 obtain/build hello_worldhello_world_number 的 URL?

我们有Router.url_path_for(...) method which is located inside the starlette

方法一:使用FastAPI实例

当您能够在当前上下文中访问 FastAPI 实例时,此方法很有用。 (感谢 @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/"

缺点

  • 如果我们使用 APIRouterrouter.url_path_for('hello_world') 可能无法工作,因为 router 不是不是 FastAPI class 的实例。也就是说,我们必须有 FastAPI 实例来解析 URL

方法二:Request实例

当您能够访问 Request 实例(传入请求)时,此方法很有用,通常是在视图中。

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 参数来解决 URL,这可能会给开发人员带来 丑陋 的感觉。

url_for 存在,但由支持 FastApi 的服务器 starlette 提供: https://www.starlette.io/routing/#reverse-url-lookups

其实你不需要重新发明轮子。 FastAPI 支持此 out-of-box (实际上是 Starlette)并且效果很好。

app = FastAPI()

@app.get("/hello/{number}/")
def hello_world_number(number: int):
    return {"msg": "Hello World Number", "number": number}

如果你有这样的端点,你可以简单地使用

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/

FastAPI中,APIRouterFastAPI(APIRoute)继承自Router(Starlette's) 所以,如果你有这样的 APIRouter,你可以继续使用这个功能

router = APIRouter()

@router.get("/hello")
def hello_world():
    return {"msg": "Hello World"}

In:  router.url_path_for("hello_world")
Out: /hello

如果在多个APIRouter下定义了相同的函数名,request.url_forrouter.url_path_for将return第一个匹配的函数名(按照include_router的顺序)。
这里有一个在函数名冲突时用APIRouter的tag获取正确url的方法,如果有人需要的话:
第 1 步: 将其放入您的 __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)

步骤 2: 为 APIRouters 添加标签:

router = APIRouter(prefix='/user', tags=['user'])
@router.get('/')
def login():
    return 'login page'

第 3 步: 在任意位置检索 url:

@router2.get('/test')
def test(request: Request):
    return RedirectResponse(url_of(request, 'user.login') + '?a=1')

2021/07/10 将 url_as 重命名为 url_of

如果您需要解析模板中的 URL,Starlette(所以 FastAPI)定义了 url_for() 在默认 Jinja 环境中使用 request 函数 上下文实例和 url_for 方法:

https://github.com/encode/starlette/blob/master/starlette/templating.py#L63

要在模板中使用它:{{ url_for('hello_world_number', number=42) }}