FastAPI老王整理(不想更新了,不想玩这个了...)

1,971 阅读8分钟

Python类型提示

指定参数类型和返回类型

>>> def demo1(first_name: str, last_name: str) -> str:
	return first_name.title() + " " + last_name.title()

>>> demo1("wang", "haha")
'Wang Haha'
>>> 

带有子类型的类型

>>> from typing import List, Tuple, Dict
>>> def demo2(items: List[str]):
    for item in items:
        print(item)

>>> demo2(["ryan", "king", 24])
ryan
king
24
>>> def demo3(items: Tuple[int, str, int]):
    for item in items:
        print(item, type(item))

        
>>> demo3((3, "hello", 89))
3 <class 'int'>
hello <class 'str'>
89 <class 'int'>
>>> def demo4(info: Dict[str, str]):
    for k, v in enumerate(info):
        print(k, v)

        
>>> demo4({"name": "ryan", "age": 24})
0 name
1 age

类作为参数类型

>>> class Student:
    def __init__(self, name):
        self.name = name

    def getName(self):
        return self.name

>>> def printName(stu: Student):
    print(stu.getName())

    
>>> stu = Student("ryan")
>>> printName(stu)
ryan

Pydantic 模型

>>> from pydantic import BaseModel
>>> from typing import List
>>> from pydantic.error_wrappers import ValidationError
>>> class User(BaseModel):
    id: int
    name = "ryan"
    hobby: List[str]

    
>>> data = {
    "id": [123],
    "name": "ryan",
    "hobby": ["篮球", "皮球", "奶球"]
}
>>> try:
    user = User(**data)
    print(user)
    print(user.id, user.name, user.hobby)
except ValidationError as e:
    print(e)

    
1 validation error for User
id
  value is not a valid integer (type=type_error.integer)

安装

pip install fastapi

ASGI服务器

pip install uvicorn

HelloWorld

from fastapi import FastAPI  

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"} 
  
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}

启动

uvicorn main:app --reload

接收PUT请求数据

from fastapi import FastAPI
from pydantic import BaseModel


app = FastAPI()


class Item(BaseModel):
    name: str
    price: float
    is_offer: bool = None


@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
    return {"item_name": item.name, "item_id": item_id}

交互式API文档

http://127.0.0.1:8000/docs

http://127.0.0.1:8000/redoc

请求方法

@app.post()
@app.put()
@app.delete()
@app.options()
@app.head()
@app.patch()
@app.trace()

通常

POST: 创建数据
GET: 读取数据
PUT: 更新数据
DELETE: 删除数据

路径参数

from fastapi import FastAPI

app = FastAPI()


@app.get("/index/{id}")
async def index(id):
    return {"id": id}

http://127.0.0.1:8000/index/20

带有类型的路径参数

from fastapi import FastAPI

app = FastAPI()


@app.get("/index/{id}")
async def index(id: int):
    return {"id": id}

http://127.0.0.1:8000/index/25

执行顺序

因为路径操作是按顺序计算的,所以您需要确保/users/me的路径是在/users/{user_id}之前声明的, 不然路由永远匹配到/users/{user_id}

from fastapi import FastAPI

app = FastAPI()


@app.get('/users/me')
async def read_user_me():
    return {'user_id': '10010'}


@app.get('/users/{user_id}')
async def read_user(user_id: str):
    return {'user_id': user_id}

枚举参数

导入Enum并创建一个继承自strEnum的子类。

from fastapi import FastAPI
from enum import Enum

app = FastAPI()


# 通过从str中继承,API文档将能够知道值的类型必须是string,并且能够正确呈现。
class ModelName(str, Enum):
    a = 'ryan'
    b = 'king'
    c = 'ryan'


@app.get('/model/{model_name}')
async def get_model(model_name: ModelName):
    print('model_name', model_name)
    print('key=', model_name.name)
    print('val=', model_name.value)
    return {model_name.name: model_name.value}

路径参数包含路径

from fastapi import FastAPI

app = FastAPI()


@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
    return {"file_path": file_path}

http://127.0.0.1:8000/files/a/b/c/d/e/f/g

查询参数

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 10):
    print(skip)
    print(limit)

http://127.0.0.1:8000/items/?skip=1&limit=3

可选参数

你可以声明可选的查询参数,通过设置它们的默认值为None

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(item_id: str, q: str = None):
    if q:
        return {"item_id": item_id, "q": q}
    return {"item_id": item_id}

http://127.0.0.1:8000/items/5

多个路径和查询参数

from fastapi import FastAPI

app = FastAPI()


@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(
    user_id: int, item_id: str, q: str = None, short: bool = False
):
    item = {"item_id": item_id, "owner_id": user_id}
    if q:
        item.update({"q": q})
    if not short:
        item.update(
            {"description": "This is an amazing item that has a long description"}
        )
    return item

Optional

from typing import Optional

limit: Optional[int] = None
from typing import Optional
from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_user_item(item_id: str, limit: Optional[int] = None):
    item = {"item_id": item_id, "limit": limit}
    return item

请求体

当你需要从一个客户端(简单来说是浏览器)发送数据给你的API,你发送的内容叫做请求体

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item

使用模型

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    print(item)
    print("json->", item.json())
    print("dict->", item.dict())
    print("Config->", item.Config)
    print(item.dict().get("name"))
    return item

请求体+路径参数

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None


app = FastAPI()


@app.put("/items/{item_id}")
async def create_item(item_id: int, item: Item):
    return {"item_id": item_id, **item.dict()}

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

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None
    
    
app = FastAPI()


@app.put("/items/{item_id}")
async def create_item(item_id: int, item: Item, q: str = None):
    result = {"item_id": item_id, **item.dict()}
    if q:
        result.update({"q": q})
    return result

功能参数将被识别如下:

  • 如果在路径中也声明了该参数,它将用作路径参数。
  • 如果参数是单数类型(例如intfloatstrbool等),它将被解释为查询参数。
  • 如果参数被声明为是一个Pydantic 模型,它将被解释为请求体。

查询参数和字符串的验证

额外的验证

限制查询参数长度

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/test")
async def test(item: str = Query(None, max_length=20, min_length=5)):
    return item

当我们使用默认值None替代Query(None)时,Query的第一个参数具有定义默认值的相同目的。

q: str = Query(None)

等同于

q: str = None

匹配正则表达式参数

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/test")
async def test(item: str = Query(None, max_length=20, min_length=5, regex='^w.*a$')):
    return item

http://127.0.0.1:8000/test?item=wanghaha

默认值

具有默认值也会使该参数成为可选参数。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/test")
async def test(item: str = Query('wanghaha', max_length=20, min_length=5, regex='^w.*a$')):
    return item

http://127.0.0.1:8000/test

当我们使用Query声明一个值时,你可以使用...作为第一个参数, 它会让FastAPI知道这个参数是被需要的。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str = Query(..., min_length=3)):
    print(q)
    return q

查询参数列表/多个值

from fastapi import FastAPI, Query
from typing import List

app = FastAPI()


@app.get("/items/")
async def read_items(q: List[str] = Query(None)):
    query_items = {"q": q}
    return query_items

http://127.0.0.1:8000/items/?q=abc&q=defaazv&q=23

查询参数列表/多个值 默认值

from fastapi import FastAPI, Query
from typing import List

app = FastAPI()


@app.get("/items/")
async def read_items(q: List[str] = Query(["foo", "bar"])):
    query_items = {"q": q}
    return query_items

http://127.0.0.1:8000/items/

不指定list子元素类型

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: list = Query(None)):
    query_items = {"q": q}
    return query_items

声明更多的原信息

添加title, description

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items1/")
async def read_items(q: str = Query(None, title="Query string", min_length=3)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results


@app.get("/items2/")
async def read_items(
    q: str = Query(
        None,
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        min_length=3,
    )
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

参数别名

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str = Query(None, alias="item-query")):
    print(q)
    return q

http://127.0.0.1:8000/items/?item-query=hello

弃用参数

现在你再也不想看到这个参数。

您必须将其保留一段时间,因为有许多客户在使用它,但是您希望文档将其清楚地显示为已弃用。

然后将参数deprecated = True传递给Query

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: str = Query(
        None,
        deprecated=True,
    )
):
    print(q)
    return q

http://127.0.0.1:8000/docs#/default/read_items_items__get

路径参数和数值验证

参数升序

Python不会对*做任何操作,但是它将知道以下所有参数都应称为关键字参数(键/值对),也称为kwargs。即使它们没有默认值。

from fastapi import FastAPI, Query, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    *, item_id: int = Path(..., title="The ID of the item to get"), q: str
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

数值验证: 大于等于

from fastapi import FastAPI, Query, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    *, item_id: int = Path(..., title="The ID of the item to get", ge=1), q: str
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

http://127.0.0.1:8000/items/3?q=hello

  • gt: 大于
  • le: 小于等于
  • lt: 小于

Body多参数

from fastapi import FastAPI, Path
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None


@app.put("/items/{item_id}")
async def update_item(
    *,
    item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000),
    q: str = None,
    item: Item = None,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    if item:
        results.update({"item": item})
    return results

多个请求参数

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None
    
    
class User(BaseModel):
    username: str
    full_name: str = None
    
    
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):
    results = {"item_id": item_id, "item": item, "user": user}
    return results

请求体

{
    "item": {
        "name": "Foo",
        "description": "The pretender",
        "price": 42.0,
        "tax": 3.2
    },
    "user": {
        "username": "dave",
        "full_name": "Dave Grohl"
    }
}

Body指示FastAPI将其视为另一个主体密钥

from fastapi import Body, FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None


class User(BaseModel):
    username: str
    full_name: str = None


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

期望请求体

{
    "item": {
        "name": "Foo",
        "description": "The pretender",
        "price": 42.0,
        "tax": 3.2
    },
    "user": {
        "username": "dave",
        "full_name": "Dave Grohl"
    },
    "importance": 5
}

Body还具有与QueryPath和其他稍后将看到的所有相同的额外验证和元数据参数。

嵌入单个主体参数

假设您只有Pydantic模型Item中的单个body参数。

默认情况下,FastAPI将直接期望其主体。

但是,如果您希望它期望一个带有关键item的JSON,并且在其中包含模型内容,就像声明额外的主体参数时那样,则可以使用特殊的Body参数embed

item: Item = Body(..., embed=True)
from fastapi import Body, FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(..., embed=True)):
    results = {"item_id": item_id, "item": item}
    return results

期望请求主体

{
    "item": {
        "name": "Foo",
        "description": "The pretender",
        "price": 42.0,
        "tax": 3.2
    }
}

而不是

{
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2
}

Body字段

声名模型属性

from fastapi import Body, FastAPI
from pydantic import BaseModel, Field

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str = Field(None, title="The description of the item", max_length=300)
    price: float = Field(..., gt=0, description="The price must be greater than zero")
    tax: float = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(..., embed=True)):
    results = {"item_id": item_id, "item": item}
    return results

Field的工作方式与QueryPathBody相同,它具有所有相同的参数,等等。

详细教程 实际上,接下来将看到QueryPath和其他对象,它们创建了常见Param类的子类的对象,而Param类本身是Pydantic的FieldInfo类的子类。 并且Pydantic的Field也返回FieldInfo的实例。 Body还直接返回FieldInfo子类的对象。还有其他一些您稍后会看到的是Body类的子类。 请记住,当您从fastapi导入Query,Path等时,这些实际上是返回特殊类的函数。

提示 请注意,具有类型,默认值和Field的每个模型的属性如何与路径操作函数的参数具有相同的结构,用字段而不是PathQueryBody

您可以使用Pydantic的Field声明模型属性的额外验证和元数据。

您还可以使用extra关键字参数来传递其他JSON Schema元数据。