FastAPI如何无需循环从HTML表单获取Map列表?
问题
我想在FastAPI中从HTML表单直接获取以下格式的数据:
[EmploymentHistory(work_description='3wert', location='ADSF'), EmploymentHistory(work_description='3wert', location='cghvjbn,m')]
或其字典列表形式:
[{'work_description': '3wert', 'location': '2q3wer'}, {'work_description': '3wert', 'location': 'ADSF'}]
当前已有的代码如下:
FastAPI端点代码
@app.post('/formz', response_class=HTMLResponse) async def post_formz(request: Request,data: EmploymentHistory = Depends(EmploymentHistory.as_form)): print(data) return templates.TemplateResponse("resume/create.html",{"request": request})
Pydantic模型代码
class EmploymentHistory(BaseModel): work_description: str location: str @classmethod def as_form( cls, work_description: str = Form(...), location: str = Form(...) ): return cls( work_description=work_description, location= location, )
HTML表单代码
<form method = 'POST' enctype="multipart/form-data"> <div> <p>Employment History</p> </div> <div> <ol id="employment_history-form"> <li> <label for= 'work_description'>Work discription</label> <input type='work_description' required="required" name='work_description' placeholder='jone briggs' /> <label for= 'location'>Location</label> <input type='location' required="required" name='location' placeholder='jone briggs' /> </li> <li> <label for= 'work_description'>Work discription</label> <input type='work_description' required="required" name='work_description' placeholder='jone briggs' /> <label for= 'location'>Location</label> <input type='location' required="required" name='location' placeholder='jone briggs' /> </li> </ol> </div> <button type="submit" >create history</button> </form>
尝试过以下方案但均报错:
- 直接用
List包装模型:
@app.post('/formz', response_class=HTMLResponse) async def post_formz(request: Request,data: List[EmploymentHistory] = Depends(List[EmploymentHistory.as_form])):
错误信息:
{"detail":[{"loc":["query","args"],"msg":"field required","type":"value_error.missing"},{"loc":["query","kwargs"],"msg":"field required","type":"value_error.missing"}]}
- 嵌套模型类:
class User(BaseModel): employments_history: List[EmploymentHistory] @classmethod def as_form( cls, employments_history: List[EmploymentHistory] = List[EmploymentHistory.as_form], ): return cls( employments_history=employments_history, )
错误信息:
{"detail":[{"loc":["body"],"msg":"value is not a valid list","type":"type_error.list"}]}
- 移除嵌套类的列表:
class User(BaseModel): employments_history: EmploymentHistory @classmethod def as_form( cls, employments_history: EmploymentHistory = EmploymentHistory.as_form, ): return cls( employments_history=employments_history, )
错误信息:
{"detail":[{"loc":["body"],"msg":"value is not a valid dict","type":"type_error.dict"}]}
目前通过zip循环实现了需求,但希望能避免在端点中写循环,寻求更简洁的方案。
可行方案
方案1:自定义依赖直接返回目标列表
先修正HTML表单的两个问题:
- 输入框的
type属性错误,应改为text(浏览器会默认解析为text,但显式声明更规范) - 表单字段名改为
work_description[]和location[],让FastAPI自动解析为列表
修改后的HTML表单关键部分:
<li> <label for='work_description_0'>Work discription</label> <input type='text' required="required" name='work_description[]' placeholder='jone briggs' /> <label for='location_0'>Location</label> <input type='text' required="required" name='location[]' placeholder='jone briggs' /> </li> <li> <label for='work_description_1'>Work discription</label> <input type='text' required="required" name='work_description[]' placeholder='jone briggs' /> <label for='location_1'>Location</label> <input type='text' required="required" name='location[]' placeholder='jone briggs' /> </li>
然后创建自定义依赖函数,接收列表并直接转换为List[EmploymentHistory]:
from typing import List from fastapi import Form, Depends async def get_employment_history_list( work_description: List[str] = Form(...), location: List[str] = Form(...) ) -> List[EmploymentHistory]: # 用列表推导式替代显式循环,代码更简洁 return [EmploymentHistory(work_description=w, location=l) for w, l in zip(work_description, location)]
最后修改端点代码,直接使用该依赖:
@app.post('/formz', response_class=HTMLResponse) async def post_formz(request: Request, data: List[EmploymentHistory] = Depends(get_employment_history_list)): print(data) # 直接得到目标格式的列表 return templates.TemplateResponse("resume/create.html", {"request": request})
方案2:封装模型转换方法
如果希望保留EmploymentList模型,可以在模型内部封装转换逻辑,避免端点代码中出现循环:
修改EmploymentList模型:
class EmploymentList(BaseModel): work_description: List[str] location: List[str] @classmethod def as_form( cls, work_description: List[str] = Form(...), location: List[str] = Form(...) ): return cls( work_description=work_description, location= location ) # 封装转换逻辑 def to_employment_history(self) -> List[EmploymentHistory]: return [EmploymentHistory(work_description=w, location=l) for w, l in zip(self.work_description, self.location)]
端点代码简化为:
@app.post('/formz', response_class=HTMLResponse) async def post_formz(request: Request, data: EmploymentList = Depends(EmploymentList.as_form)): employment_list = data.to_employment_history() print(employment_list) return templates.TemplateResponse("resume/create.html", {"request": request})
说明
两种方案本质都是通过映射列表元素生成目标模型,但将循环逻辑(或列表推导式)封装到了依赖函数或模型方法中,端点代码保持简洁,符合需求。
内容的提问来源于stack exchange,提问作者olu
相关产品推荐
相关产品推荐

