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

Posted

技术标签:

【中文标题】FastAPI:从视图名称(路由名称)中检索 URL【英文标题】:FastAPI: Retrieve URL from view name ( route name ) 【发布时间】:2020-12-20 06:58:03 【问题描述】:

假设我有以下观点,

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 中使用这些函数

Flask: url_for(...) Django: reverse(...)

那么,如何以类似的方式获取/构建hello_worldhello_world_number 的URL?

【问题讨论】:

【参考方案1】:

我们有位于starlette 包内的Router.url_path_for(...) 方法

方法一:使用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 类的实例。也就是说,我们必须有 FastAPI 实例来解析 URL

方法 2: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,这可能会给开发人员带来的感觉。

【讨论】:

【参考方案2】:

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

【讨论】:

【参考方案3】:

其实你不需要重新发明***。 FastAPI 支持这种开箱即用的 (实际上是 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

【讨论】:

当您有多个路由器文件并希望在不同的文件中获得url_path_for 路由时,您会建议哪种方法?我的main.py 做了一堆app.include_router 来获取所有路线。谢谢! @Shawn 我在基于视图函数的视图中使用了return fastapi.responses.RedirectResponse(url=request.url_for(name='account'), status_code=status.HTTP_302_FOUND) 您的方法返回路径,而不是 URL。 flask.url_for() 返回绝对 URL 值【参考方案4】:

如果在多个 APIRouter 下定义了相同的函数名,request.url_forrouter.url_path_for 将返回第一个匹配的函数名(按 include_router 的顺序)。 如果有人需要,这是一种在函数名称冲突时使用 APIRouter 标签获取正确 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)

第二步:为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

【讨论】:

它工作正常。但是当父方法和目标方法具有相同的路由时,您只能重定向响应。例如,如果测试是 post 方法,则意味着您只能调用 post 方法,而不能使用 get 方法调用post 方法请求对象。 这个答案旨在解决'jpg'提出的函数名构建URL的问题。 RedirectResponse 是如何使用构建的 URL 的示例。 RedirectResponse 以 307 作为默认状态码返回(在重定向期间以相同的方式发起新请求)。如果 'test' 需要 POST 并且 'login' 是 GET,我们可以将 status_code 参数设置为 302:RedirectResponse(url_as(request, 'user.login') + '?a=1', status_code=302)url_as 也可以以其他方式使用。其实我把url_as注册为jinja2中的全局模板方法@NAGARAJS 如果request.url_for中的request是传入请求实例,则不需要实现url_of(...)函数,因为request对象拥有所有路由信息。 request.url_for没有充分测试,url_for确实可以通过函数名获取应用的所有url。但是如果在多个 APIRouter 下定义了相同的函数名,url_for 会返回第一个匹配的函数名(按照 include_router 的顺序)。 url_of 提供了一种在函数名冲突时获取带有 APIRouter 标签的正确 url 的方法。答案已更新。谢谢@JPG【参考方案5】:

如果您需要在模板中解析 URL,Starlette (so 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)

【讨论】:

没有提到url_for是在模板上下文中导出的。

以上是关于FastAPI:从视图名称(路由名称)中检索 URL的主要内容,如果未能解决你的问题,请参考以下文章

如何检索名称 | Listview中多个选中项目的值对?

如何在 Laravel 5 中检索视图名称?

在视图 2 (Drupal 6) 中检索被覆盖的主题钩子的名称

如何在Laravel 5中检索视图名称?

检索从中消费消息的 RabbitMQ 队列的名称

如何在 FastAPI 中保存 UploadFile