是否有任何方法在FastAPI查询参数中具有自定义验证逻辑?
我有一个FastAPI
应用程序与一堆请求处理程序采取Path
组件作为查询参数。例如:
def _raise_if_non_relative_path(path: Path):
if path.is_absolute():
raise HTTPException(
status_code=409,
detail=f"Absolute paths are not allowed, {path} is absolute."
)
@app.get("/new",)
def new_file(where: Path):
_raise_if_non_relative_path(where)
# do save a file
return Response(status_code=requests.codes.ok)
@app.get("/new",)
def delete_file(where: Path):
_raise_if_non_relative_path(where)
# do save a file
return Response(status_code=requests.codes.ok)
我想知道是否有办法确保处理程序甚至不调用当给定的文件路径是绝对的。现在我不得不到处用_raise_if_non_relative_path
重复我自己。
what I tried
fastapi.Query
:
这只允许非常基本的验证(字符串长度和正则表达式)。在这个例子中,我可以定义一个绝对路径regex。但是一个正则表达式解决方案真的不是通用的,我想用一个自定义函数来验证。- 子类
pathlib.Path
的验证逻辑在__init__
:
这不起作用,类型签名中给出的类型被忽略,我的处理程序中的对象是一个常规的pathlib.PosixPath
。 - 使用
@app.middleware
:
这可以工作,但似乎过度因为不是所有的请求处理程序都处理Path
对象。 class RelativePath(pydantic.Basemodel)
:
即定义一个具有单个path
字段的类,我可以根据需要对其进行验证。不幸的是,这不适用于查询参数。如果我这样做,请求处理程序坚持使用json内容体。或者至少这是swagger文档说的。
这是Depends
依赖管理功能非常适合的验证类型。它允许您为给定的视图函数定义依赖关系,并添加逻辑来验证(和惰性创建)这些依赖关系。这提供了一组可组合的依赖项,这些依赖项可以在需要的视图中重用。
您可以创建一个relative_where_query
依赖项,然后依赖它来执行任何所需的验证:
from fastapi import Depends, FastAPI, Response, Query
from fastapi.exceptions import HTTPException
from pathlib import Path
import requests
app = FastAPI()
def relative_where_query(where: Path = Query(...)):
if where.is_absolute():
raise HTTPException(
status_code=409,
detail=f"Absolute paths are not allowed, {where} is absolute."
)
return where
@app.get("/new")
def new_file(where: Path = Depends(relative_where_query)):
return Response(status_code=requests.codes.ok)
这提供了小的、易读的(和可理解的)视图函数,而依赖项("我需要来自where
查询参数的相对路径")已经被移动到它自己的定义中。
你可以在每个需要where
查询参数的相对路径的视图函数中重用这个依赖项(如果需要的话,你可以进一步分解和重组这些依赖项)。
更新2023-05-01如果你想推广这个处理,将字段名与验证函数解耦,你可以通过生成一个动态依赖来实现。这个例子用Pydantic创建了一个动态的基本模型来完成它——但是肯定有其他的方法。
由于我们现在在Pydantic验证器中引发错误,因此我们添加了一个异常处理程序,以便在路径无效时给出适当的错误响应。
魔法本身发生在path_validator
内部——这个函数接受给定的名称,在create_model
调用中使用它作为字段名——从它的参数动态创建Pydantic模型。然后,该模型被用作控制器中的依赖项。
作为函数参数的名称将是实际的参数名称-无论是在调用时(即在本例中的URL中)还是在显示文档时。控制器函数本身的path
名称仅在函数内部相关,不用于文档或作为查询参数(模型中的字段名称用于此)。
from fastapi import Depends, FastAPI, Response, Query, Request
from fastapi.exceptions import HTTPException
from fastapi.responses import JSONResponse
from pathlib import Path
from pydantic import create_model, validator
import requests
app = FastAPI()
class PathNotAbsoluteError(Exception):
pass
@app.exception_handler(PathNotAbsoluteError)
async def value_error_exception_handler(request: Request, exc: PathNotAbsoluteError):
return JSONResponse(
status_code=400,
content={"message": str(exc)},
)
def path_is_absolute(cls, value):
if not value.is_absolute():
raise PathNotAbsoluteError("Given path is not absolute")
return value
def path_validator(param_name):
validators = {
'pathname_validator': validator(param_name)(path_is_absolute)
}
return create_model(
"AbsolutePathQuery",
**{param_name: (Path, Query(...))},
__validators__=validators,
)
@app.get("/path")
def new_file(path: Path = Depends(path_validator("where"))):
return Response(status_code=requests.codes.ok)
你现在可以在任何你想要的位置通过添加一个依赖于一个给定的参数名来重用它:
def new_file(path: Path = Depends(path_validator("my_path"))):
...
def old_file(path: Path = Depends(path_validator("where"))):
...