当前位置:   article > 正文

FastAPI官方教程太棒了(上)

fastapi教程

Python第三流行的Web框架

在2020年的Python开发者调查结果中,有这样一段话:“FastAPI在此次调查迭代中首次被引为选项,表现为Python第三流行的Web框架。”

FastAPI创立于2018年12月,不到2年就成为仅次于Flask和Django的第三流行的Web框架。而又经过了一年发展来到2022年,虽然2021年Python开发者调查结果还没有出来,但是从GitHub的star来看,Flask 58.7k,Django 63.6k,FastAPI 44.2k,这个差距缩得越来越小。

FastAPI特性

这里就不做机器翻译了,大家看下原文:

我说下我选择FastAPI的理由:首先就是HttpRunner集成了FastAPI,有大佬背书,相信这个框架足以优秀。其次是注解,用多了SpringBoot以后,越来越喜欢注解,层次清晰。对于前后端分离项目来说,Flask虽然非常精简却又自带了Jinja模板引擎,Django虽然是百宝箱却又显得太重,而FastAPI介于两者之间,就是一个纯粹的后端应用。并且FastAPI是基于Starlette框架的,集成了实用功能比如类型检查、OpenAPI(Swagger)等等,这跟我基于pytest框架做tep测试工具的理念很相似。

安装

对Python版本要求是3.6+。

先安装FastAPI:

pip install fastapi

再安装ASGI服务器,比如Uvicorn:

pip install "uvicorn[standard]"

也可以同时安装fastapi和uvicorn:

pip install "fastapi[all]"

运行

写个main.py文件:

  1. from typing import Optional
  2. from fastapi import FastAPI
  3. app = FastAPI()
  4. @app.get("/")
  5. def read_root():
  6. return {"Hello": "World"}
  7. @app.get("/items/{item_id}")
  8. def read_item(item_id: int, q: Optional[str] = None):
  9. return {"item_id": item_id, "q": q}

在命令行输入启动应用:

uvicorn main:app --reload

main是Python模块名。

appapp = FastAPI()

--reload在代码变化时自动重启服务器。

打开浏览器访问:

http://127.0.0.1:8000/items/5?q=somequery

就能看到JSON响应:

{"item_id": 5, "q": "somequery"}

访问:

http://127.0.0.1:8000/docs

就能看到Swagger接口文档:

pydantic

pydantic是一个数据验证的库,FastAPI使用它来做模型校验。比如:

  1. from typing import Optional
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. price: float
  8. is_offer: Optional[bool] = None
  9. @app.get("/")
  10. def read_root():
  11. return {"Hello": "World"}
  12. @app.get("/items/{item_id}")
  13. def read_item(item_id: int, q: Optional[str] = None):
  14. return {"item_id": item_id, "q": q}
  15. @app.put("/items/{item_id}")
  16. def update_item(item_id: int, item: Item):
  17. return {"item_name": item.name, "item_id": item_id}

Item是个入参模型,它的name必须str类型,price必须float类型,is_offer是可选的,可以为bool类型或不传。

  1. PUT http://127.0.0.1:8000/items/6
  2. {
  3. "name": "dongfanger",
  4. "price": 2.3,
  5. "is_offer": true
  6. }
  7. {
  8. "item_name": "dongfanger",
  9. "item_id": 6
  10. }

路径参数

把路径参数传递给函数:

  1. from fastapi import FastAPI
  2. app = FastAPI()
  3. @app.get("/items/{item_id}")
  4. async def read_item(item_id):
  5. return {"item_id": item_id}

也可以指定Python类型:

  1. from fastapi import FastAPI
  2. app = FastAPI()
  3. @app.get("/items/{item_id}")
  4. async def read_item(item_id: int):
  5. return {"item_id": item_id}

效果是访问 http://127.0.0.1:8000/items/foo 会返回{"item_id":"foo"}

指定了Python类型后,FastAPI会强制检查,比如传str会报错:

http://127.0.0.1:8000/items/foo

  1. {
  2. "detail": [
  3. {
  4. "loc": [
  5. "path",
  6. "item_id"
  7. ],
  8. "msg": "value is not a valid integer",
  9. "type": "type_error.integer"
  10. }
  11. ]
  12. }

传float也会报错:

http://127.0.0.1:8000/items/4.2

匹配先后顺序

代码定义的先后顺序会决定匹配结果,比如正常来说,下面的/users/me会返回{"user_id": "the current user"}

  1. from fastapi import FastAPI
  2. app = FastAPI()
  3. @app.get("/users/me")
  4. async def read_user_me():
  5. return {"user_id": "the current user"}
  6. @app.get("/users/{user_id}")
  7. async def read_user(user_id: str):
  8. return {"user_id": user_id}

假如这2个path定义顺序反过来,那么/users/me就会匹配到/users/{user_id}而返回{"user_id": me}了。

枚举路径

借助于Enun类,可以实现枚举路径:

  1. from enum import Enum
  2. from fastapi import FastAPI
  3. class ModelName(str, Enum):
  4. alexnet = "alexnet"
  5. resnet = "resnet"
  6. lenet = "lenet"
  7. app = FastAPI()
  8. @app.get("/models/{model_name}")
  9. async def get_model(model_name: ModelName):
  10. if model_name == ModelName.alexnet:
  11. return {"model_name": model_name, "message": "Deep Learning FTW!"}
  12. if model_name.value == "lenet":
  13. return {"model_name": model_name, "message": "LeCNN all the images"}
  14. return {"model_name": model_name, "message": "Have some residuals"}

效果:

path匹配

FastAPI提供了一个path类型,可以用来对文件路径进行格式匹配:

  1. from fastapi import FastAPI
  2. app = FastAPI()
  3. @app.get("/files/{file_path:path}")
  4. async def read_file(file_path: str):
  5. return {"file_path": file_path}

查询参数

查询参数是跟在路径参数后面,用?分隔用&连接的参数,比如http://127.0.0.1:8000/items/?skip=0&limit=10

实现:

  1. from fastapi import FastAPI
  2. app = FastAPI()
  3. fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
  4. @app.get("/items/")
  5. async def read_item(skip: int = 0, limit: int = 10):
  6. return fake_items_db[skip : skip + limit]

参数是可选的并且设置了默认值:limit: int = 10

参数是可选的,无默认值:limit: Optional[int] = None

注意:是否可选是由None来决定的,而Optional只是为编译器提供支持,跟FastAPI没有关系。

参数是必填的:limit: int

请求体

FastAPI的请求体借助于pydantic来实现:

  1. from typing import Optional
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel
  4. class Item(BaseModel):
  5. name: str
  6. description: Optional[str] = None
  7. price: float
  8. tax: Optional[float] = None
  9. app = FastAPI()
  10. @app.post("/items/")
  11. async def create_item(item: Item):
  12. return item

继承于BaseModel来自定义Model,FastAPI会自动转换为JSON。

Pydantic PyCharm Plugin插件提高编码体验:

  • auto-completion
  • type checks
  • refactoring
  • searching
  • inspections

路径参数+查询参数+请求体

总结一下,在函数参数中,url path中定义的叫做路径参数,没有定义的叫做查询参数,类型是pydantic model的叫做请求体,FastAPI会根据这套规则来自动识别:

  1. from typing import Optional
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel
  4. class Item(BaseModel):
  5. name: str
  6. description: Optional[str] = None
  7. price: float
  8. tax: Optional[float] = None
  9. app = FastAPI()
  10. @app.put("/items/{item_id}")
  11. async def create_item(item_id: int, item: Item, q: Optional[str] = None):
  12. result = {"item_id": item_id, **item.dict()}
  13. if q:
  14. result.update({"q": q})
  15. return result

查询参数字符串校验

FastAPI提供了Query来支持对入参的字符串校验,比如最小长度和最大长度:

  1. from typing import Optional
  2. from fastapi import FastAPI, Query
  3. app = FastAPI()
  4. @app.get("/items/")
  5. async def read_items(
  6. q: Optional[str] = Query(None, min_length=3, max_length=50, regex="^fixedquery$")
  7. ):
  8. results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
  9. if q:
  10. results.update({"q": q})
  11. return results

甚至其中也能包含正则表达式:regex="^fixedquery$"

用Query时指定默认值:

  1. from fastapi import FastAPI, Query
  2. app = FastAPI()
  3. @app.get("/items/")
  4. async def read_items(q: str = Query("fixedquery", min_length=3)):
  5. results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
  6. if q:
  7. results.update({"q": q})
  8. return results

用Query时必填:

  1. from fastapi import FastAPI, Query
  2. app = FastAPI()
  3. @app.get("/items/")
  4. async def read_items(q: str = Query(..., min_length=3)):
  5. results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
  6. if q:
  7. results.update({"q": q})
  8. return results

查询参数传list

  1. from typing import List, Optional
  2. from fastapi import FastAPI, Query
  3. app = FastAPI()
  4. @app.get("/items/")
  5. async def read_items(q: Optional[List[str]] = Query(None)):
  6. query_items = {"q": q}
  7. return query_items

指定默认值:

  1. from typing import List
  2. from fastapi import FastAPI, Query
  3. app = FastAPI()
  4. @app.get("/items/")
  5. async def read_items(q: List[str] = Query(["foo", "bar"])):
  6. query_items = {"q": q}
  7. return query_items

url就像这样:http://localhost:8000/items/?q=foo&q=bar

指定别名,比如http://127.0.0.1:8000/items/?item-query=foobaritems中的item-query不是Python变量命名,那么可以设置别名:

  1. from typing import Optional
  2. from fastapi import FastAPI, Query
  3. app = FastAPI()
  4. @app.get("/items/")
  5. async def read_items(q: Optional[str] = Query(None, alias="item-query")):
  6. results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
  7. if q:
  8. results.update({"q": q})
  9. return results

路径参数数字校验

查询参数用Query做字符串(String)校验,路径参数用Path做数字(Numeric)校验:

  1. from fastapi import FastAPI, Path
  2. app = FastAPI()
  3. @app.get("/items/{item_id}")
  4. async def read_items(
  5. *,
  6. item_id: int = Path(..., title="The ID of the item to get", gt=0, le=1000),
  7. q: str,
  8. ):
  9. results = {"item_id": item_id}
  10. if q:
  11. results.update({"q": q})
  12. return results

路径参数永远都是必填的,因为它是url一部分。...表示必填,就算设置为None也没有用,仍然是必填。

ge表示大于等于,greater equal。

le表示小于等于,less equal。

gt表示大于,greater than。

lt表示小于,less than。

请求体-多参数

一、如果请求体嵌套了多个JSON:

  1. {
  2. "item": {
  3. "name": "Foo",
  4. "description": "The pretender",
  5. "price": 42.0,
  6. "tax": 3.2
  7. },
  8. "user": {
  9. "username": "dave",
  10. "full_name": "Dave Grohl"
  11. }
  12. }

那么就需要在FastAPI中定义多参数:

  1. from typing import Optional
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Optional[str] = None
  8. price: float
  9. tax: Optional[float] = None
  10. class User(BaseModel):
  11. username: str
  12. full_name: Optional[str] = None
  13. @app.put("/items/{item_id}")
  14. async def update_item(item_id: int, item: Item, user: User):
  15. results = {"item_id": item_id, "item": item, "user": user}
  16. return results

这里定义了2个Model:Item和User。

二、而如果多个参数中有个参数只是单个值,比如这里的importance

  1. {
  2. "item": {
  3. "name": "Foo",
  4. "description": "The pretender",
  5. "price": 42.0,
  6. "tax": 3.2
  7. },
  8. "user": {
  9. "username": "dave",
  10. "full_name": "Dave Grohl"
  11. },
  12. "importance": 5
  13. }

那么定义成变量并赋值= Body()即可:

  1. @app.put("/items/{item_id}")
  2. async def update_item(item_id: int, item: Item, user: User, importance: int = Body()):
  3. results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
  4. return results

三、在只有一个Item的时候,FastAPI默认会接收这样的body:

  1. {
  2. "name": "Foo",
  3. "description": "The pretender",
  4. "price": 42.0,
  5. "tax": 3.2
  6. }

假如想把item也放到JSON中:

  1. {
  2. "item": {
  3. "name": "Foo",
  4. "description": "The pretender",
  5. "price": 42.0,
  6. "tax": 3.2
  7. }
  8. }

那么可以使用Body(embed=True))

  1. from typing import Union
  2. from fastapi import Body, FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. @app.put("/items/{item_id}")
  11. async def update_item(item_id: int, item: Item = Body(embed=True)):
  12. results = {"item_id": item_id, "item": item}
  13. return results

请求体-字段

Pydantic提供了Field来给body中的字段添加额外校验:

  1. from typing import Union
  2. from fastapi import Body, FastAPI
  3. from pydantic import BaseModel, Field
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = Field(
  8. default=None, title="The description of the item", max_length=300
  9. )
  10. price: float = Field(gt=0, description="The price must be greater than zero")
  11. tax: Union[float, None] = None
  12. @app.put("/items/{item_id}")
  13. async def update_item(item_id: int, item: Item = Body(embed=True)):
  14. results = {"item_id": item_id, "item": item}
  15. return results

跟FastAPI提供的QueryPathBody作用类似。

请求体-嵌套模型

传List:

  1. from typing import List, Union
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. tags: List[str] = []
  11. @app.put("/items/{item_id}")
  12. async def update_item(item_id: int, item: Item):
  13. results = {"item_id": item_id, "item": item}
  14. return results

传Set,自动去重:

  1. from typing import Set, Union
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. tags: Set[str] = set()
  11. @app.put("/items/{item_id}")
  12. async def update_item(item_id: int, item: Item):
  13. results = {"item_id": item_id, "item": item}
  14. return results

传Model:

  1. from typing import Set, Union
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Image(BaseModel):
  6. url: str
  7. name: str
  8. class Item(BaseModel):
  9. name: str
  10. description: Union[str, None] = None
  11. price: float
  12. tax: Union[float, None] = None
  13. tags: Set[str] = set()
  14. image: Union[Image, None] = None
  15. @app.put("/items/{item_id}")
  16. async def update_item(item_id: int, item: Item):
  17. results = {"item_id": item_id, "item": item}
  18. return results

入参会像这样:

  1. {
  2. "name": "Foo",
  3. "description": "The pretender",
  4. "price": 42.0,
  5. "tax": 3.2,
  6. "tags": ["rock", "metal", "bar"],
  7. "image": {
  8. "url": "http://example.com/baz.jpg",
  9. "name": "The Foo live"
  10. }
  11. }

对于url,pydantic提供了HttpUrl来做校验:

  1. class Image(BaseModel):
  2. url: HttpUrl
  3. name: str

传Model的List:

  1. from typing import List, Set, Union
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel, HttpUrl
  4. app = FastAPI()
  5. class Image(BaseModel):
  6. url: HttpUrl
  7. name: str
  8. class Item(BaseModel):
  9. name: str
  10. description: Union[str, None] = None
  11. price: float
  12. tax: Union[float, None] = None
  13. tags: Set[str] = set()
  14. images: Union[List[Image], None] = None
  15. @app.put("/items/{item_id}")
  16. async def update_item(item_id: int, item: Item):
  17. results = {"item_id": item_id, "item": item}
  18. return results

入参像这样:

  1. {
  2. "name": "Foo",
  3. "description": "The pretender",
  4. "price": 42.0,
  5. "tax": 3.2,
  6. "tags": [
  7. "rock",
  8. "metal",
  9. "bar"
  10. ],
  11. "images": [
  12. {
  13. "url": "http://example.com/baz.jpg",
  14. "name": "The Foo live"
  15. },
  16. {
  17. "url": "http://example.com/dave.jpg",
  18. "name": "The Baz"
  19. }
  20. ]
  21. }

添加示例请求

通过Configschema_extra添加示例请求:

  1. from typing import Union
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. class Config:
  11. schema_extra = {
  12. "example": {
  13. "name": "Foo",
  14. "description": "A very nice Item",
  15. "price": 35.4,
  16. "tax": 3.2,
  17. }
  18. }
  19. @app.put("/items/{item_id}")
  20. async def update_item(item_id: int, item: Item):
  21. results = {"item_id": item_id, "item": item}
  22. return results

在使用以下任一时,都可以添加example:

  • Path()
  • Query()
  • Header()
  • Cookie()
  • Body()
  • Form()
  • File()

比如:

  1. from typing import Union
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel, Field
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str = Field(example="Foo")
  7. description: Union[str, None] = Field(default=None, example="A very nice Item")
  8. price: float = Field(example=35.4)
  9. tax: Union[float, None] = Field(default=None, example=3.2)
  10. @app.put("/items/{item_id}")
  11. async def update_item(item_id: int, item: Item):
  12. results = {"item_id": item_id, "item": item}
  13. return results
  1. from typing import Union
  2. from fastapi import Body, FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. @app.put("/items/{item_id}")
  11. async def update_item(
  12. item_id: int,
  13. item: Item = Body(
  14. example={
  15. "name": "Foo",
  16. "description": "A very nice Item",
  17. "price": 35.4,
  18. "tax": 3.2,
  19. },
  20. ),
  21. ):
  22. results = {"item_id": item_id, "item": item}
  23. return results
  1. from typing import Union
  2. from fastapi import Body, FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. @app.put("/items/{item_id}")
  11. async def update_item(
  12. *,
  13. item_id: int,
  14. item: Item = Body(
  15. examples={
  16. "normal": {
  17. "summary": "A normal example",
  18. "description": "A **normal** item works correctly.",
  19. "value": {
  20. "name": "Foo",
  21. "description": "A very nice Item",
  22. "price": 35.4,
  23. "tax": 3.2,
  24. },
  25. },
  26. "converted": {
  27. "summary": "An example with converted data",
  28. "description": "FastAPI can convert price `strings` to actual `numbers` automatically",
  29. "value": {
  30. "name": "Bar",
  31. "price": "35.4",
  32. },
  33. },
  34. "invalid": {
  35. "summary": "Invalid data is rejected with an error",
  36. "value": {
  37. "name": "Baz",
  38. "price": "thirty five point four",
  39. },
  40. },
  41. },
  42. ),
  43. ):
  44. results = {"item_id": item_id, "item": item}
  45. return results

额外数据类型

FastAPI除了支持常见的数据类型:

  • int
  • float
  • str
  • bool

还支持额外的数据类型:

  • UUID
  • datetime.datetime
  • datetime.date
  • datetime.time
  • datetime.timedelta
  • frozenset
  • bytes
  • Decimal

示例:

  1. from datetime import datetime, time, timedelta
  2. from typing import Union
  3. from uuid import UUID
  4. from fastapi import Body, FastAPI
  5. app = FastAPI()
  6. @app.put("/items/{item_id}")
  7. async def read_items(
  8. item_id: UUID,
  9. start_datetime: Union[datetime, None] = Body(default=None),
  10. end_datetime: Union[datetime, None] = Body(default=None),
  11. repeat_at: Union[time, None] = Body(default=None),
  12. process_after: Union[timedelta, None] = Body(default=None),
  13. ):
  14. start_process = start_datetime + process_after
  15. duration = end_datetime - start_process
  16. return {
  17. "item_id": item_id,
  18. "start_datetime": start_datetime,
  19. "end_datetime": end_datetime,
  20. "repeat_at": repeat_at,
  21. "process_after": process_after,
  22. "start_process": start_process,
  23. "duration": duration,
  24. }
  1. from typing import Union
  2. from fastapi import Cookie, FastAPI
  3. app = FastAPI()
  4. @app.get("/items/")
  5. async def read_items(ads_id: Union[str, None] = Cookie(default=None)):
  6. return {"ads_id": ads_id}

QueryPath用法类似。

  1. from typing import Union
  2. from fastapi import FastAPI, Header
  3. app = FastAPI()
  4. @app.get("/items/")
  5. async def read_items(user_agent: Union[str, None] = Header(default=None)):
  6. return {"User-Agent": user_agent}

多重header用List,比如:

  1. from typing import Union
  2. from fastapi import FastAPI, Header
  3. app = FastAPI()
  4. @app.get("/items/")
  5. async def read_items(user_agent: Union[str, None] = Header(default=None)):
  6. return {"User-Agent": user_agent}
  1. X-Token: foo
  2. X-Token: bar
  1. {
  2. "X-Token values": [
  3. "bar",
  4. "foo"
  5. ]
  6. }

响应模型

通过response_model定义返回模型:

  1. from typing import List, Union
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: Union[float, None] = None
  10. tags: List[str] = []
  11. @app.post("/items/", response_model=Item)
  12. async def create_item(item: Item):
  13. return item

response_model的作用是对函数返回值进行过滤,比如:

  1. from typing import Union
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel, EmailStr
  4. app = FastAPI()
  5. class UserIn(BaseModel):
  6. username: str
  7. password: str
  8. email: EmailStr
  9. full_name: Union[str, None] = None
  10. class UserOut(BaseModel):
  11. username: str
  12. email: EmailStr
  13. full_name: Union[str, None] = None
  14. @app.post("/user/", response_model=UserOut)
  15. async def create_user(user: UserIn):
  16. return user

函数返回值是UserIn模型的对象user,而response_model的值为UserOut(UserOut相比于UserIn来说,没有password),那么FastAPI的响应,就是用UserOut对UserIn进行了过滤,返回的是没有password的UserOut。

响应模型可以返回默认值:

  1. from typing import List, Union
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: Union[str, None] = None
  8. price: float
  9. tax: float = 10.5
  10. tags: List[str] = []
  11. items = {
  12. "foo": {"name": "Foo", "price": 50.2},
  13. "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
  14. "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
  15. }
  16. @app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
  17. async def read_item(item_id: str):
  18. return items[item_id]

response_model_exclude_unset=True不返回未显式设置的字段,response_model_exclude_defaults不返回带默认值的字段,response_model_exclude_none不返回None的字段。

附加模型

在上面的示例中,UserIn是入参,UserOut是出参,不包含password,但是在实际情况中,还需要第三个模型UserInDB,在存入数据库时,把password进行加密。

代码实现如下:

  1. from typing import Union
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel, EmailStr
  4. app = FastAPI()
  5. class UserIn(BaseModel):
  6. username: str
  7. password: str
  8. email: EmailStr
  9. full_name: Union[str, None] = None
  10. class UserOut(BaseModel):
  11. username: str
  12. email: EmailStr
  13. full_name: Union[str, None] = None
  14. class UserInDB(BaseModel):
  15. username: str
  16. hashed_password: str
  17. email: EmailStr
  18. full_name: Union[str, None] = None
  19. def fake_password_hasher(raw_password: str):
  20. return "supersecret" + raw_password
  21. def fake_save_user(user_in: UserIn):
  22. hashed_password = fake_password_hasher(user_in.password)
  23. user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
  24. print("User saved! ..not really")
  25. return user_in_db
  26. @app.post("/user/", response_model=UserOut)
  27. async def create_user(user_in: UserIn):
  28. user_saved = fake_save_user(user_in)
  29. return user_saved

重点是user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)里面的**user_in.dict()

user_in是UserIn类的Pydantic模型,它有个dict()方法能返回字典。**是拆包,把字典拆成key value的形式,上面这行代码等价于:

  1. UserInDB(
  2. username="john",
  3. password="secret",
  4. email="john.doe@example.com",
  5. full_name=None,
  6. hashed_password=hashed_password
  7. )

也相当于:

  1. UserInDB(
  2. username = user_dict["username"],
  3. password = user_dict["password"],
  4. email = user_dict["email"],
  5. full_name = user_dict["full_name"],
  6. hashed_password = hashed_password,
  7. )

FastAPI的一大设计原则是尽量减少重复代码,所以对于UserIn、UserOut、UserInDB可以把里面的相同字段抽象为一个UserBase,其他Model继承即可:

  1. from typing import Union
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel, EmailStr
  4. app = FastAPI()
  5. class UserBase(BaseModel):
  6. username: str
  7. email: EmailStr
  8. full_name: Union[str, None] = None
  9. class UserIn(UserBase):
  10. password: str
  11. class UserOut(UserBase):
  12. pass
  13. class UserInDB(UserBase):
  14. hashed_password: str
  15. def fake_password_hasher(raw_password: str):
  16. return "supersecret" + raw_password
  17. def fake_save_user(user_in: UserIn):
  18. hashed_password = fake_password_hasher(user_in.password)
  19. user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
  20. print("User saved! ..not really")
  21. return user_in_db
  22. @app.post("/user/", response_model=UserOut)
  23. async def create_user(user_in: UserIn):
  24. user_saved = fake_save_user(user_in)
  25. return user_saved

response_model除了定义一个Model以外,也能定义多个附加模型。

比如Union:

  1. from typing import Union
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class BaseItem(BaseModel):
  6. description: str
  7. type: str
  8. class CarItem(BaseItem):
  9. type = "car"
  10. class PlaneItem(BaseItem):
  11. type = "plane"
  12. size: int
  13. items = {
  14. "item1": {"description": "All my friends drive a low rider", "type": "car"},
  15. "item2": {
  16. "description": "Music is my aeroplane, it's my aeroplane",
  17. "type": "plane",
  18. "size": 5,
  19. },
  20. }
  21. @app.get("/items/{item_id}", response_model=Union[PlaneItem, CarItem])
  22. async def read_item(item_id: str):
  23. return items[item_id]

比如List:

  1. from typing import List
  2. from fastapi import FastAPI
  3. from pydantic import BaseModel
  4. app = FastAPI()
  5. class Item(BaseModel):
  6. name: str
  7. description: str
  8. items = [
  9. {"name": "Foo", "description": "There comes my hero"},
  10. {"name": "Red", "description": "It's my aeroplane"},
  11. ]
  12. @app.get("/items/", response_model=List[Item])
  13. async def read_items():
  14. return items

比如Dict:

  1. from typing import Dict
  2. from fastapi import FastAPI
  3. app = FastAPI()
  4. @app.get("/keyword-weights/", response_model=Dict[str, float])
  5. async def read_keyword_weights():
  6. return {"foo": 2.3, "bar": 3.4}

参考资料:

https://fastapi.tiangolo.com/

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/530211
推荐阅读
相关标签
  

闽ICP备14008679号