Bootstrap

FastAPI入门

FastAPI 框架,高性能,易于学习,高效编码,生产可用
官方文档: https://fastapi.tiangolo.com
FastAPI 是一个用于构建 API 的现convert_underscores代、快速(高性能)的 web 框架,使用 Python 3.6+ 并基于标准的 Python 类型提示。
关键特性:

  • 快速:可与 NodeJS 和 Go 比肩的极高性能(归功于 Starlette 和 Pydantic)。最快的 Python web 框架之一
  • 高效编码:提高功能开发速度约 200% 至 300%。
  • 更少 bug:减少约 40% 的人为(开发者)导致错误。
  • 智能:极佳的编辑器支持。处处皆可自动补全,减少调试时间。
  • 简单:设计的易于使用和学习,阅读文档的时间更短。
  • 简短:使代码重复最小化。通过不同的参数声明实现丰富功能。bug 更少。
  • 健壮:生产可用级别的代码。还有自动生成的交互式文档。
  • 标准化:基于(并完全兼容)API 的相关开放标准。

安装FastApi:

pip3 install fastapi
pip3 install unicorn

开始第一个Demo

# 创建一个main.py文件

from typing import Optional

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello FastApi"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
    return {"item_id": item_id, "q": q}

运行服务器:

一、命令运行服务器:

uvicorn main:app --reload --port 8888 # 更改端口号

二、代码运行服务器调试:

if __name__ == '__main__':
    import uvicorn

    uvicorn.run("main:app", reload=True, port=5555)

FastApi提供交互式Api文档一:这很方便我们管理自己的接口

现在访问 http://localhost:8000/docs 就会生成一个Swagger文档

FastApi提供交互式Api文档二:这很方便我们管理自己的接口

现在访问 http://127.0.0.1:8000/redoc 就会生成一个redoc文档
 

FastApi中何时用Path、Query、Header、Body

Query:查询参数用
Path:路径参数
Body:需要在请求数据传入countent_type为json的
Form:请求数据为表单的

如果你想即「get」又「post」同时请求,你可以这么做:

@app.api_route("/login", methods=("GET", "POST", "PUT"))
def login():
    """这是一个登陆接口"""
    return {"msg": "login success", "code": 200}

何时用「Form」,何时用「Body」,何时用「Header」呢

如果你你以表单的形式传递数据,那你就应该用「Form」,看一下代码

@app.post("/login1")
def login_form(username=Form(None), password=Form(None)):
    return {"username": username, "password": password}


如果你以JSON的形式传递数据,那你就应该用「Body」,看一下代码

@app.post("/login")
def login(data=Body(None)):
    return {"data": data}


如果你你想传递「Header」数据,那你就应该用「Header」,看一下代码

@app.get("/user")
def user(id, num=Header(None)):
    return {"id": id, "num": num}

如何定制一个返回信息,看代码

作用:就是将自己定义好的响应结果返回回来

from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()


@app.get("/user")
def user():
    return JSONResponse(content={"msg": "get user"},   # 内容
                        status_code=202,  # 状态码,默认为200
                        headers={"a": "b"})


if __name__ == '__main__':
    import uvicorn

    uvicorn.run("04_response:app", reload=True)

如何将自己写的html网站动态加载到fastapi(jinja2模板返回Html页面)

我们需要安装一些依赖库

1、jinja2
pip install jinjia2
2、aiofiles
pip install aiofiles

废话不多说,看代码

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates   # 需要进入的库
from starlette.staticfiles import StaticFiles  # 引入静态文件库


app = FastAPI()
# 指定静态文件存放路径
app.mount("/page", StaticFiles(directory="page"), name="page")
# 指定html 存放目录
template = Jinja2Templates("page")


@app.get("/home")
def home (req: Request):
    return template.TemplateResponse("index.html", context={"request": req})


if __name__ == '__main__':
    import uvicorn

    uvicorn.run("d05_templates:app", reload=True)


如果你的代码中有引入到css样式,你就可以向我这样,你会发现样式就被引入进来了

如果你想自定义传参进来,你可以试试这样:

如果你想实现这样的例子

# main.py

from fastapi import FastAPI, Request, Form
from fastapi.responses import RedirectResponse
from fastapi.templating import Jinja2Templates

app = FastAPI()
template = Jinja2Templates("pages")
todos = ["写博客", "看电影", "玩游戏"]


@app.get("/")
async def index(req: Request):
    return template.TemplateResponse("index.html", context={"request": req, "todos": todos})


@app.post("/todo")
async def todo(todo=Form(None)):
    """处理用户发送过来的 todolist 数据"""
    todos.insert(0, todo)
    return RedirectResponse('/', status_code=302)


if __name__ == '__main__':
    import uvicorn

    uvicorn.run("main:app", reload=True)


在这里我说一下为什么要将状态码设置为302,如果你不设置这个status_code,浏览器发送给后端的请求状态码为307,因为307的状态码是不能从post请求跳转到get请求,原因是post请求如果要跳转到get请求不通用,如果想进行跳转,需要将307更改为302。

# index.html    <meta charset="UTF-8">    <title>Title</title><h1>待办事项</h1><div>    <form action="/todo" method="post">        <input name="todo" type="text" placeholder="请添加待办事件...">        <input type="submit" value="添加">    </form></div>    {% for todo in todos %}    <p>{{ todo }}</p>    {% endfor %}

关联数据库,将数据存储化

第一步:我们需要安装依赖库

pip install tortoise-orm
pip install aiomysq

第二步:电脑需要安装mysql,安装调试过程不在赘述

以我为例:先创建一个db为fastapi的库

create database fastapi;

第三步:配置数据库

from tortoise.contrib.fastapi import register_tortoise

# 数据库绑定
register_tortoise(app,
                  db_url="mysql://root:Ranyong_520@localhost:3306/fastapi",
                  modules={"models": []},
                  add_exception_handlers=True,
                  generate_schemas=True)

实例一:将数据存储到数据库并返回给前端

# d06_templates.py
from fastapi import FastAPI, Request, Form
from fastapi.responses import RedirectResponse
from fastapi.templating import Jinja2Templates
from tortoise.contrib.fastapi import register_tortoise

from dao.models import Todo

app = FastAPI()
template = Jinja2Templates("pages")

# 数据库绑定
register_tortoise(app,
                  db_url="mysql://root:Ranyong_520@localhost:3306/fastapi",
                  modules={"models": ['dao.models']},  # 设置模型类
                  add_exception_handlers=True,
                  generate_schemas=True)


@app.get("/")
async def index(req: Request):
    # 从数据库获取 todos 的代码
    # ORM,获取所有的 todos
    todos = await Todo.all()  # 获取所有的todos
    print(todos)
    return template.TemplateResponse("index.html", context={"request": req, "todos": todos})


@app.post("/todo")
async def todo(content=Form(None)):
    """处理用户发送过来的 todolist 数据"""
    await Todo(content=content).save()
    return RedirectResponse('/', status_code=302)


if __name__ == '__main__':
    import uvicorn

    uvicorn.run("d06_templates:app", reload=True)


然后创建一个dao的文件夹里面创建一个models的py文件

from tortoise import Model, fieldsclass Todo(Model):    """数据库当中的表 todo"""    id = fields.IntField(pk=True)  # id为int类型的 pk:是将id作为主键    content = fields.CharField(max_length=500)  # todo项里面的内容    例如:todos = ["写博客", "看电影", "玩游戏"]    created_at = fields.DatetimeField(auto_now_add=True)  # auto_now_add 当每次插入新数据的时候会引用本地时间    updated_at = fields.DatetimeField(auto_now=True)  # auto_now 当每次修改数据后会更新本地时间


这时候我们来运行下代码:
 


可以发现返回了,并没有返回添加的数据,那我们再去数据库看,点击数据库更新按钮后,可以发现我们的数据已经存储到了表中表。
 


 


可以看到数据库已经存了我们提交的数据,现在我们只需要改一下index.html文件一个地方就可以解决
 


最后效果

枚举(Enum)

在python3.4以后才可使用

第一步:需要倒入Enum库

from enum import Enum

第二步:创建一个Enum的子类

class ModelName(str, Enum):
    A = 'a'
    B = 'b'
    C = 'c'
# 预设值.py
from fastapi import FastAPI                                          
                                                                     
from enum import Enum                                                
                                                                     
                                                                     
# 预设值                                                                
# 如果你有一个接收路径参数的路径操作,但是你希望预先设定可能的有效参数值                                
                                                                     
# 创建一个继承str和Enum的子类                                                  
class ModelName(str, Enum):                                          
    A = 'a'                                                          
    B = 'b'                                                          
    C = 'c'                                                          
                                                                     
                                                                     
app = FastAPI()                                                      
                                                                     
                                                                     
@app.get("/models/{model_name}")                                     
async def get_model(model_name: ModelName):                          
    if model_name == ModelName.A:                                    
        """你可以用类里面ModelName.key来进行比较"""                              
        return {"model_name": model_name, "message": "You chose A!"} 
                                                                     
    if model_name.value == 'b':                                      
        """你也可以用model_name.value来进行比较"""                             
        return {"model_name": model_name, "message": "You chose B"}  
                                                                     
    return {"model_name": model_name, "message": "You chose C"}      
                                                                     
                                                                     
if __name__ == '__main__':                                           
    import uvicorn                                                   
                                                                     
    uvicorn.run("预设值:app", reload=True)                              

查询参数

默认值:

顾名思义,默认值就是已经写好了,你不需要在写任何参数,不过你写也不妨碍

from fastapi import FastAPIapp = FastAPI()fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]@app.get("/items/")async def read_item(skip: int = 0, limit: int = 10):    """    skip默认值为0    limit默认值为10    """    return fake_items_db[skip:skip + limit]# 传入 http://127.0.0.1:8000/items/ === http://127.0.0.1:8000/items/?skip=0&limit=10# 返回值:"""[{"item_name": "Foo"},{"item_name": "Bar"    },{"item_name": "Baz"}]"""if __name__ == '__main__':    import uvicorn    uvicorn.run("查询参数:app", reload=True)

可选参数

第一步:导入依赖库
from typing import Optional
第二步:写方法引用
from typing import Optional

from fastapi import FastAPI

app = FastAPI()

# 选传参数
@app.get("/items/{item_id}")
async def read_item(item_id: str, q: Optional[str] = None, short: bool = False):
    """
    :param item_id:
    :param q: 函数q是可选的,默认值为None
    :param short:
    :return:
    """
    item = {"item_id": item_id}
    if q:
        return {"item_id": item_id, "q": q}
    if not short:
        item.update(
            {"description": "这是一个令人惊异的项目,有一个很长的描述"}
        )
    return item

# 传入http://127.0.0.1:8000/items/1?short=false
# 返回值:
"""
{
 "item_id": "1",
  "description": "这是一个令人惊异的项目,有一个很长的描述"
}
"""
# 传入 http://127.0.0.1:8000/items/1?short=true
# 返回值:
"""
{
  "item_id": "1"
}
"""

if __name__ == '__main__':
    import uvicorn

    uvicorn.run("查询参数:app", reload=True)

必传参数

from fastapi import FastAPIapp = FastAPI()# 必传参数@app.get("/items1/{item_id}")async def read_user_item(item_id: str, needy: str):    """    :param item_id: 必传    :param needy: 必传    :return:    """    item = {"item_id": item_id, "needy": needy}    return item# 传入 http://127.0.0.1:8000/items1/1?needy=1# 返回值:"""{  "item_id": "1",  "needy": "1"}"""if __name__ == '__main__':    import uvicorn    uvicorn.run("查询参数:app", reload=True)
例子:
from fastapi import FastAPIapp = FastAPI()@app.get("/item2/{item_id}")async def read_user_item(        item_id: str,     	needy: str,     	skip: int = 0,     	limit: Optional[int] = None):    item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit}    return item# 传入:http://127.0.0.1:8000/item2/1?needy=2&skip=0&limit=1# 返回值:"""{  "item_id": "1",  "needy": "2",  "skip": 0,  "limit": 1}"""if __name__ == '__main__':    import uvicorn    uvicorn.run("查询参数:app", reload=True)

请求体

什么是请求体?就是将数据从客户端发送api数据,这部分就叫请求体
请求体是客户端发送api的数据,响应是api发送给客户端的数据
api几乎总是要发送响应体,但是客户端并不总是需要发送请求体
⚠️:发送请求体不能用GET,发送数据我们通常使用POSTPUTDELETEPATH

第一步:导入Pydantic中的BaseModel
from pydantic import BaseModel
第二步:创建数据模型,将你的数据模型声明为继承自BaseModel的类
class Itme(BaseModel):    name: str    description: Optional[str] = None   # Optional 参数为可选的    price: float    tax: Optional[float] = None
完整代码
from typing import Optional   
from fastapi import FastAPI
from pydantic import BaseModel   # 第一步导包


# 创建数据模型
class Itme(BaseModel):     # 第二步创建数据模型
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None


app = FastAPI()


# 使用模型
# 在函数内部,可以直接访问模型对象的所有属性
@app.post("/items/")
async def create_item(item: Itme):
    item_dict = item.dict()
    if item.tax:
        price_with_tax = item.proice + item.tax
        item_dict.updata({"Proice_with_tax": price_with_tax})
    return item_dict



# 请求体+路径参数
@app.put("/items/{item_id}")
async def create_item(item_id: int, item: Itme):
    return {"item_id": item_id, **item.dict()}


# 请求体+路径参数+查询参数
@app.put("/items1/{item_id}")
async def create_item(item_id: int, item: Itme, q: Optional[str] = None):
    result = {"item_id": item_id, **item.dict()}
    if q:
        result.update({"q": q})
    return result


if __name__ == '__main__':
    import uvicorn

    uvicorn.run("请求体:app", reload=True)
  • 如果在路径中也声明了该参数,它将被作用路径参数
  • 如果参数属于单一参数 类似(int、float、str、bool等) 它将被解释为查询参数
  • 如果参数的类型被声明为一个Pydantic 模型,它将被解释为请求体

查询参数和字符串校验

"""
在FastApi中提供了数据校验的功能
"""

举个例子:
from typing import Optionalfrom fastapi import FastAPIapp = FastAPI()@app.get("/items/")async def read_items(q: Optional[str] = None):    """    查询参数q的类型为str,可以看到默认值为None,因为它是可选的(Optional)    但是我们需要添加约束条件,即使q是可选的,我还想限制它不能超过50个字符串,下面可以看《第一步》    """    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}    if q:        results.update({"q": q})    return resultsif __name__ == '__main__':    uvicorn.run("查询参数和字符串校验:app", reload=True)
第一步:

导入Query

from fastapi import Query
第二步:

将Query作为查询参数的默认值,并进行限制

import uvicornfrom typing import Optionalfrom fastapi import FastAPI, Queryapp = FastAPI()# 默认值@app.get("/items/")async def read_items(q: Optional[str] = Query(None, min_length=3, max_length=50, regex="^love$")):    """    :param q: 可选参数    :None = 可填参数为空  如果将None 更换成一个字符串 比如 "test" 该q的值就会成默认值"test"    :min_length = 最小字符串数量    :max_length = 最大字符串数量    :regex = 正则匹配 ^ 以该符号之后的字符开头; $ 到此结束    :return:    """    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}    if q:        results.update({"q": q})    return resultsif __name__ == '__main__':    uvicorn.run("查询参数和字符串校验:app", reload=True)
默认值

"""
向Query 的第一个参数传入None的作用是默认值,同样你也可以传其他的默认值
假设你想要声明查询参数q,限制它的最短字符串为3,并默认值为 demo,你可以这么做
"""

import uvicornfrom typing import Optionalfrom fastapi import FastAPI, Queryapp = FastAPI()@app.get("/items/")async def read_items(q: str = Query("demo", min_length=3)):    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}    if q:        results.update({"q": q})    return resultsif __name__ == '__main__':    uvicorn.run("查询参数和字符串校验:app", reload=True)
必填值

"""
当我们要声明一个参数为必填值时,只需要将Query里面的「None」改成「...」就好了,不用管它是什么含义,你就简单的理解「...」是必传的就对了
"""

import uvicornfrom typing import Optionalfrom fastapi import FastAPI, Queryapp = FastAPI()@app.get("/items/")async def read_items(q: str = Query(..., min_length=3)):    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}    if q:        results.update({"q": q})    return resultsif __name__ == '__main__':    uvicorn.run("查询参数和字符串校验:app", reload=True)
参数传入多个值

"""
当你使用Query定义查询参数时,你还可以声明它去接收一组值,简单来说 接收多个值
"""

import uvicornfrom typing import Optionalfrom fastapi import FastAPI, Queryapp = FastAPI()@app.get("/items/")async def read_items(q: Optional[List[str]] = Query(None)):    query_items = {"q": q}    return query_items# 传入:http://localhost:8000/items/?q=foo&q=bar# 返回:"""{  "q": [    "foo",    "bar"  ]}"""if __name__ == '__main__':    uvicorn.run("查询参数和字符串校验:app", reload=True)
使用List

"""
你可以直接使用List代替List[str]
两者区别:
List[int] :将检查列表的内容必须为整数
List:将不会做任何检查
"""

import uvicornfrom typing import Optionalfrom fastapi import FastAPI, Queryapp = FastAPI()@app.get("/items/")async def read_items(q: Optional[list] = Query(None)):    query_items = {"q": q}    return query_items# 传入:http://localhost:8000/items/?q=foo&q=bar# 返回:"""{  "q": [    "foo",    "bar"  ]}"""if __name__ == '__main__':    uvicorn.run("查询参数和字符串校验:app", reload=True)
别名参数

"""
如果你想查询参数为 text
传入:http://localhost:8000/items/?text=demo
但是text 不是一个有效的Python变量名称
这时你可以用 alias 参数声明一个别名,该别名在URL中查到查询参数值
"""

import uvicorn
from typing import Optional

from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/items/")
async def read_items(q: Optional[str] = Query(None, alias="text")):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

# 传入:http://localhost:8000/items/?text=demo
# 返回:
"""
{
  "items": [
    {
      "item_id": "Foo"
    },
    {
      "item_id": "Bar"
    }
  ],
  "q": "demo"
}
"""

if __name__ == '__main__':
    uvicorn.run("查询参数和字符串校验:app", reload=True)
弃用参数

"""
如果假设你不在喜欢这个参数了,但是你又要保留一段时间,你可以引用弃用参数,它可以在文档中清晰的展示为已弃用
我们只需要传入一个参数 deprecated=True 传入Query即可
"""

import uvicornfrom typing import Optionalfrom fastapi import FastAPI, Queryapp = FastAPI()@app.get("/items/")async def read_items(    q: Optional[str] = Query(        None,        alias="item-query",        title="Query string",        description="Query string for the items to search in the database that have a good match",        min_length=3,        max_length=50,        regex="^fixedquery$",        deprecated=True,    )):    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}    if q:        results.update({"q": q})    return results# 在swagger 里面就会有个红色的标识if __name__ == '__main__':    uvicorn.run("查询参数和字符串校验:app", reload=True)

Swagger文档:

路径参数和数值校验

"""
Path为路径参数声明,路径参数是必需的
所以,声明的时候「...」将其标记为必填参数
"""

第一步:

导入Path

from fastapi import FastApi, Path, Query
第二步:
from typing import Optionalimport uvicornfrom fastapi import FastAPI, Path, Queryapp = FastAPI()@app.get("/items/{item_id}")async def read_items(        item_id: int = Path(..., title="获取项目ID"),        q: Optional[str] = Query(None, alies="item-query"),):    results = {"item_id": item_id}    if q:        results.update({"q": q})    return resultsif __name__ == '__main__':    uvicorn.run("路径参数和数据校验:app", reload=True)

todo:需要弄懂这个*号含义

数值校验:大于等于

"""
如果我们想要一个值大于100,我们可以声明数值约束,
只需要加一个参数「ge=x」,限制一个值大于或等于x
"""

from typing import Optionalimport uvicornfrom fastapi import FastAPI, Path, Queryapp = FastAPI()# 数值校验: 大于等于@app.get("/items/{item_id}")async def read_items(        *, item_id: int = Path(..., title="The ID of the item to get", ge=100), q: str):    results = {"item_id": item_id}    if q:        results.update({"q": q})    return resultsif __name__ == '__main__':    uvicorn.run("路径参数和数据校验:app", reload=True)
数据校验:大于和小于等于

"""
gt: 大于
le:小于等于
"""

from typing import Optionalimport uvicornfrom fastapi import FastAPI, Path, Queryapp = FastAPI()# 数值校验: 大于等于@app.get("/items/{item_id}")async def read_items(        *,     item_id: int = Path(..., title="The ID of the item to get", ge=100),     q: str):    results = {"item_id": item_id}    if q:        results.update({"q": q})    return resultsif __name__ == '__main__':    uvicorn.run("路径参数和数据校验:app", reload=True)
数据校验:浮点数、大于和小于

"""
例如:要求一个值必须大于0,小于1,因此,0.5将是有效的,但是0.0或0不是。
lt:小于
ge:大于等于
"""

from typing import Optionalimport uvicornfrom fastapi import FastAPI, Path, Queryapp = FastAPI()# 数值校验: 浮点数、大于和小于@app.get("/items6/{item_id}")async def read_items6(        *,        item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000),        # item_id: 大于0,小于等于1000        q: str,        size: float = Query(..., gt=0, lt=10.5)        # 范围: 大于0,小于10.5):    results = {"item_id": item_id}    if q:        results.update({"q": q})    return resultsif __name__ == '__main__':    uvicorn.run("路径参数和数据校验:app", reload=True)
总结:
  • gt:大于(greater than)
  • ge:大于等于(greater than or equal)
  • lt:小于(less than)
  • le:小于等于(less than or equal)

请求体-多个参数

"""
混合使用Path,Query和请求体参数
"""

import uvicornfrom fastapi import FastAPI, Pathfrom typing import Optionalfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel):    name: str    description: Optional[str] = None    price: float    tax: Optional[float] = None# 混合使用Path,Query和请求体参数@app.put("/items/{item_id}")async def updata_item(        *,        item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000),        q: Optional[str] = None,        item: Optional[Item] = None):    results = {"item_id": item_id}    if q:        results.update({"q": q})    if item:        results.update({"item": item})    return resultsif __name__ == '__main__':    uvicorn.run("请求体-多个参数:app", reload=True)
多个请求体参数
import uvicornfrom fastapi import FastAPI, Pathfrom typing import Optionalfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel):    name: str    description: Optional[str] = None    price: float    tax: Optional[float] = Noneclass User(BaseModel):    username: str    full_name: Optional[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 resultsif __name__ == '__main__':    uvicorn.run("请求体-多个参数:app", reload=True)
添加请求体的单一值:

"""
除了请求item和user之外,还想在单独请求另一个键 age,你可以添加请求体的单一值
"""

import uvicornfrom fastapi import FastAPI, Path, Bodyfrom typing import Optionalfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel):    name: str    description: Optional[str] = None    price: float    tax: Optional[float] = Noneclass User(BaseModel):    username: str    full_name: Optional[str] = None# 请求体的单一值@app.put("/items/{item_id}")async def updata_item(        item_id: int, item: Item, user: User, age: int = Body(...)):    results = {"item_id": item_id, "item": item, "user": user, "age": age}    return results# 这样就可以单独的请求就加载了json里"""{  "item": {    "name": "string",    "description": "string",    "price": 0,    "tax": 0  },  "user": {    "username": "string",    "full_name": "string"  },  "age": 0}"""if __name__ == '__main__':    uvicorn.run("请求体-多个参数:app", reload=True)
多个请求体参数和查询参数

"""
任何时候都可以声明额外的查询参数
"""

import uvicorn
from fastapi import FastAPI, Path, Body
from typing import Optional

from pydantic import BaseModel

app = FastAPI()


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


class User(BaseModel):
    username: str
    full_name: Optional[str] = None

# 多个请求体参数和查询参数
@app.put("/items/{item_id}")
async def updata_item(
        *,
        item_id: int,
        item: Item,
        user: User,
        importance: int = Body(..., gt=0),  # Body具有与Query、Path一样的相同的数值校验
        q: Optional[str] = None  # 等价于  q: Optional[str] = Query(None)
):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    if q:
        results.update({"q": q})
    return results

if __name__ == '__main__':
    uvicorn.run("请求体-多个参数:app", reload=True)
嵌入单个请求体参数

"""
希望在item键并在值中包含模型内容JSON
并需要利用Body方法的embed参数,方能解析出RequestBody 内容
item: Item = Body(..., embed=True)
"""

import uvicornfrom fastapi import FastAPI, Path, Bodyfrom typing import Optionalfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel):    name: str    description: Optional[str] = None    price: float    tax: Optional[float] = Noneclass User(BaseModel):    username: str    full_name: Optional[str] = None# 嵌入单个请求体参数@app.put("/items/{item_id}")async def updata_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}"""if __name__ == '__main__':    uvicorn.run("请求体-多个参数:app", reload=True)

请求体-字段

"""
Field 字段的意思其实就是类似上面Query,Path,也同样给给Body内的字段的信息添加相关的校验,通俗来说就是通过Field来规范提交Body参数信息
"""

第一步:

导入Field

from pydantic import BaseModel, Field
第二步:

举个例子

import uvicornfrom fastapi import FastAPI, Bodyfrom typing import Optionalfrom pydantic import BaseModel, Fieldapp = FastAPI()class Item(BaseModel):    name: str    description: Optional[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: Optional[float] = [email protected]("/items/{item_id}")async def update_item(item_id: int, item: Item = Body(..., embed=True)):    results = {"item_id": item_id, "item": item}    return resultsif __name__ == '__main__':    uvicorn.run("请求体-字段:app", reload=True)

请求体-嵌套模型

"""
定义、校验、记录文档并使用任意深度嵌套的模型
"""

List字段

"""
你可以将一个属性定义为拥有子元素的类型
"""

第一步:

从typing 导入 List

from typing import List, Optional
import uvicornfrom fastapi import FastAPI, Bodyfrom typing import Optional, Listfrom pydantic import BaseModel, Fieldapp = FastAPI()class Item(BaseModel):    name: str    description: Optional[str] = None    price: float    tax: Optional[float] = None    tags: List = []@app.put("/items/{item_id}")async def update_item(item_id: int, item: Item):    results = {"item_id": item_id, "item": item}    return resultsif __name__ == '__main__':    uvicorn.run("请求体-嵌套模型:app", reload=True)    
定义子模型

首先,我们要定义一个Image模型:

import uvicorn

from fastapi import FastAPI, Body
from typing import Optional, List, Set
from pydantic import BaseModel, Field

app = FastAPI()

# 定义一个Image模型
class Image(BaseModel):
    url: str
    name: str
 
class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None
    image: Optional[Image] = None   # 将其用作一个属性的类型

# 定义子模型    
@app.put("/items2/{item_id}")
async def updata_item2(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results

"""
这意味着FastApi类似于以下内容的请求体:
{
  "item_id": 1,
  "item": {
    "name": "Foo",
    "description": "The Pretender",
    "price": 43,
    "tax": 3.2,
    "image": {
      "url": "https://www.baidu.com",
      "name": "百度一下,你就知道"
    }
  }
}
"""

if __name__ == '__main__':
    uvicorn.run("请求体-嵌套模型:app", reload=True)
特殊的类型和校验

"""
这里的特殊指检测url是否合规
例如:在Image模型中我们又一个Url字段,我们可以把它声明为Pydantic的HttpUrl,而不是str
该字符串将被检查是否为有效的Url,并在JSON Schema/OpenApi文档中进行记录
"""

import uvicorn

from fastapi import FastAPI, Body
from typing import Optional, List, Set
from pydantic import BaseModel, Field, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl   # HttpUrl 回检查是否为有效的Url
    name: str


class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None
    image: Optional[Image] = None

 # 定义子模型
@app.put("/items2/{item_id}")
async def updata_item2(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results

# 返回
"""
这意味着FastApi类似于以下内容的请求体:
{
  "item_id": 1,
  "item": {
    "name": "Foo",
    "description": "The Pretender",
    "price": 43,
    "tax": 3.2,
    "image": {
      "url": "https://www.baidu.com",   # 正确的Url回返回正常的200
      "name": "百度一下,你就知道"
    }
  }
}
"""

if __name__ == '__main__':
    uvicorn.run("请求体-嵌套模型:app", reload=True)
深度嵌套模型

"""
可以自定义任意深度的嵌套模型
"""

import uvicorn
from typing import List, Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None
    tags: Set[str] = set()
    images: Optional[List[Image]] = None


class Offer(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    items: List[Item]


@app.post("/offers/")
async def create_offer(offer: Offer):
    return offer


if __name__ == '__main__':
    uvicorn.run("demo2:app", reload=True)


Swagger UI 上的效果

任意dict构成的请求体

"""
可以将请求声明为使用某类型的键和其他类型的值的dict
例如:接收任意键为int类型并且值为float类型的dict
"""

import uvicornfrom typing import List, Optional, Set, Dictfrom fastapi import FastAPIapp = FastAPI()@app.post("/index-weights/")async def create_index_weights(weights: Dict[int, float]): # 接收的名为weight的dict,key为int类型,value为float类型的JSon    return weights# 返回一个类似这样的一个JSON格式:“”“{  "1": 1.0,  "2": 2.0,  "3": 3.0}”“”if __name__ == '__main__':    uvicorn.run("demo2:app", reload=True)
总结:

JSON仅支持将str作为键
但是Pydantic具有自动转换数据的功能。
API客户端只能将字符串作为键发送,只要这些字符串内容仅包含整数,Pydantic就会进行转换并校验
然后你接收的名为weights的dict实际上具有int类型的键和floa类型的值

模型的额外信息
from typing import Optionalimport uvicornfrom fastapi import FastAPIfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel):    name: str    description: Optional[str] = None    price: float    tax: Optional[float] = None    class Config:        schema_extra = {            "example": {                "name": "ranyong",                "description": "A very nice Item",                "price": 35.4,                "tax": 3.2            }        }@app.put("/items/{item_id}")async def updata_item(item_id: int, item: Item):    results = {"item_id": item_id, "item": item}    return results# 返回:"""{  "item_id": 123,  "item": {    "name": "ranyong",    "description": "A very nice Item",    "price": 35.4,    "tax": 3.2  }}"""if __name__ == '__main__':    uvicorn.run("模式的额外信息-例子:app", reload=True)
使用Field参数给JSON声明额外信息
from typing import Optionalimport uvicornfrom fastapi import FastAPIfrom pydantic import BaseModel, Fieldapp = FastAPI()class Item(BaseModel):    name: str = Field(..., example="这是姓名")    description: Optional[str] = Field(None, example="这是一个描述")    price: float = Field(..., example=36.4)    tax: Optional[float] = Field(None, exapmle=3.2)    age: str = Field(None, example="这是年龄") @app.put("/items/{item_id}")async def updata_item(item_id: int, item: Item):    results = {"item_id": item_id, "item": item}    return resultsif __name__ == '__main__':    uvicorn.run("模式的额外信息-例子:app", reload=True)

Swagger UI展示(注意传递的参数不会添加任何验证,只会添加注释,用于文档的目的)

Body 额外参数

"""
可以将请求体的一个example传递给Body
"""

from typing import Optionalimport uvicornfrom fastapi import FastAPI, Bodyfrom pydantic import BaseModel, Fieldapp = FastAPI()class Item(BaseModel):    name: str = Field(..., example="这是姓名")    description: Optional[str] = Field(None, example="这是一个描述")    price: float = Field(..., example=36.4)    tax: Optional[float] = Field(None, exapmle=3.2)    age: int = Field(None, example="这是年龄")@app.put("/items/{item_id}")async def update_item(        item_id: int,        item: Item = Body(            ...,            example={                "name": "Foo",                "description": "A very nice Item",                "price": 35.4,                "tax": 3.2,                "age": 18            },        ),):    results = {"item_id": item_id, "item": item}    return resultsif __name__ == '__main__':    uvicorn.run("模式的额外信息-例子:app", reload=True)

额外数据类型

"""
其他数据类型

  • UUID

一种标准的“通用唯一标识符”,在请求和响应中将以str表示

  • datetime.datetime

在请求和响应中将表示为ISO 8601格式 例如:2021-07-14T14:36:35.171Z

  • datetime.date

在请求和响应中将表示为ISO 8601格式 例如:2021-07-15

  • datetime.time

在请求和响应中将表示为ISO 8601格式 例如:14:36:55.003

  • datetime.timedelta

在请求和响应中将表示为float代表总秒数

  • frozenset
  • bytes
  • Decimal

"""

第一步:

导包:

from datetime import datetime, time, timedeltafrom uuid import UUID
第二步:
import uvicorn
from fastapi import FastAPI, Body
from datetime import datetime, time, timedelta
from typing import Optional
from uuid import UUID

app = FastAPI()


@app.put("/items/{item_id}")
async def read_items(
        item_id: UUID,
        start_datetime: Optional[datetime] = Body(None),
        end_datetime: Optional[datetime] = Body(None),
        repeat_at: Optional[time] = Body(None),
        process_after: Optional[timedelta] = Body(None)
):
    start_datetime = start_datetime + process_after
    duration = end_datetime - start_datetime
    return {
        "item_id": item_id,
        "start_datetime": start_datetime,
        "end_datetime": end_datetime,
        "repeat_at": repeat_at,
        "process_after": process_after,
        "start_process": start_datetime,
        "duration": duration,
    }


if __name__ == '__main__':
    uvicorn.run("额外数据类型:app", reload=True)

在Swagger UI中显示

Cookie参数

"""
声明Cookie参数的结构于声明Query参数和Path参数相同

"""

第一步:

导入cookie

from fastapi import Cookie, FastApi
第二步:
from typing import Optionalfrom fastapi import Cookie, FastAPIapp = FastAPI()@app.get("/items/")async def read_items(ads_id: Optional[str] = Cookie(None)):    return {"ads_id": ads_id}

Header参数

第一步:

导入Header

from fastapi import FastApi, Header
第二步:

声明Header参数

import uvicornfrom typing import Optionalfrom fastapi import FastAPI, Header    # 导入Headerapp = FastAPI()@app.get("/items/")async def read_items(user_agent: Optional[str] = Header(None)):    return {"User-Agent": user_agent}# 输入:http://127.0.0.1:8000/items/# 返回:"""{"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"}"""if __name__ == '__main__':    uvicorn.run("Header参数:app", reload=True)

如果你需要禁用下划线到连字符的自动转换,设置Header参数convert_underscores=False

@app.get("/items/")async def read_items(user_agent: Optional[str] = Header(None, convert_underscores=False)):    # 禁用下划线到连字符的自动转换    return {"User-Agent": user_agent}

没添加:
 


添加了:
 


注意:在设置convert_underscores 为 False 之前,请确保一些HTTP代理和服务器不允许使用带有下划线的headers


重复的headers

"""
收到重复的headers,这意味着header具有很多个值
可以在类型声明中使用一个list来定义这些情况
"""

import uvicornfrom typing import Optional, Listfrom fastapi import FastAPI, Headerapp = FastAPI()# 重复的[email protected]("/items1")async def read_items1(x_token: Optional[List[str]] = Header(None)):    return {"X-Token Values": x_token}if __name__ == '__main__':    uvicorn.run("Header参数:app", reload=True)

如果发送了两个headers,响应就会是这样

响应模型

"""
你可以任意的路径操作中使用response_model参数来声明用于响应的模型
"""

import uvicornfrom typing import Optional, Listfrom fastapi import FastAPIfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel):    name: str    description: Optional[str] = None    price: float    tax: Optional[float] = None    tags: List[str] = []@app.post("/items/", response_model=Item)   # 声明响应模型async def create_item(item: Item):    return itemif __name__ == '__main__':    uvicorn.run("响应模型:app", reload=True)

这里有个坑,如果你按照pip执行不成功的话,你可以这样做
 


安装这个模块:
或者这样:

返回于输入相同的数据

"""
例如:要求输入账号密码邮箱名字等等,这边只需要return 返回 账号邮箱名字 密码不会返回回来
"""

import uvicorn
from typing import Optional, List
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: Optional[str] = None


class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: Optional[str] = None


# 返回于输入相同的数据
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
    return user


if __name__ == '__main__':
    uvicorn.run("响应模型:app", reload=True)

响应模型编码参数

"""
这个主要运用到你提前设定好数据,然后输入可以把提前设定好的数据自动填装
你需要设置路径操作装饰器的 response_model_exclude_unset=True 参数
"""

import uvicornfrom typing import Optional, Listfrom fastapi import FastAPIfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel):    name: str    description: Optional[str] = None    price: float    tax: float = 10.5    tags: List[str] = []items = {    "foo": {"name": "Foo", "price": 50.2},    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},}@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)async def read_item(item_id: str):    return items[item_id]# 输入:http://127.0.0.1:8000/items/foo# 返回:"""{  "name": "Foo",  "price": 50.2}"""# 输入:http://127.0.0.1:8000/items/bar# 返回:"""{  "name": "Bar",  "description": "The bartenders",  "price": 62,  "tax": 20.2}"""if __name__ == '__main__':    uvicorn.run("响应模型:app", reload=True)

具有与默认值相同值的数据

"""
支持模型包含和模型排除
简单来说就是你想需要什么值,不想要什么值
response_model_include或response_model_exclude 来省略某些属性
"""

import uvicornfrom typing import Optionalfrom fastapi import FastAPIfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel):    name: str    description: Optional[str] = None    price: float    tax: float = 10.5items = {    "foo": {"name": "Foo", "price": 50.2},    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},    "baz": {        "name": "Baz",        "description": "There goes my baz",        "price": 50.2,        "tax": 10.5    },}@app.get(    "/items/{item_id}/name",    response_model=Item,    # response_model_include 需要保留的属性    response_model_include={"name", "price"})async def read_item(item_id: str):    return items[item_id]@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"price"})# response_model_exclude 需要忽略的属性async def read_item_public_data(item_id: str):    return items[item_id]if __name__ == '__main__':    uvicorn.run("响应模型:app", reload=True)

response_model_include 需要保留的属性

response_model_exclude 需要忽略的属性


总结:
使用路径操作装饰器 response_model 参数来定义响应模型,特别是确保私有数据被过滤掉
使用response_model_exclude_unset来仅返回显示设定的值

额外的模型

"""
输入模型需要拥有密码属性
输出模型不应该包含密码
数据库模型很可能需要保存密码的哈希值
"""

多个模型
todo: 没学会!

响应状态码

"""
来声明用作与响应HTTP状态码
类似于:
@app.get()
@app.post()
@app.put()
@app.delete()
status_code 参数接收一个表示HTTP状态码的数字
"""

import uvicorn
from fastapi import FastAPI

app = FastAPI()


@app.post("/items/", status_code=201)
async def create_item(name: str):
    return {"name": name}


if __name__ == '__main__':
    uvicorn.run("响应状态码:app", reload=True)


注意:status_code 是 「装饰器」方法(get,post)的一个参数,不属于路径操作函数

介绍一种名称快捷的方法

第一步:
导包:

from fastapi import FastApi, status

第二步:

import uvicornfrom fastapi import FastAPI, statusapp = FastAPI()@app.post("/items/", status_code=status.HTTP_201_CREATED)  #有足够多的状态码供你选择async def create_item(name: str):    return {"name": name}if __name__ == '__main__':    uvicorn.run("响应状态码:app", reload=True)

表单数据

"""
接收的不是JSON,而是表单字段,要使用Form
如果使用到Form表单,需要安装库
pip install python-multipart
"""

# 导入Formimport uvicornfrom fastapi import FastAPI, Formapp = FastAPI()@app.post("/login/")async def login(username: str = Form(...), password: str = Form(...)):    return {"username": username}if __name__ == '__main__':    uvicorn.run("表单数据:app", reload=True)
特别注意的是:
  • username和password最好通过表单字段来传递,因为符合密码流,不能通过JSON
  • 在一个路径操作中声明多个Form参数,但不能同时声明要接收JSON的Body字段,因为此时请求体的编码是application/x-www-form-urlencoded,而不是application/json

请求文件

"""
File 用于定义客户端的上传文件
因为上传文件是以「表单数据」形式发送
需要安装库
pip install python-multipart
"""

# 导入 Fileimport uvicornfrom fastapi import FastAPI, File, UploadFile # 导包app = FastAPI()@app.post("/files/")async def create_file(file: bytes = File(...)):    return {"file_size": len(file)}# 更推荐这个方法@app.post("/uploadfile/")async def create_upload_file(file: UploadFile = File(...)):    return {"filename": file.filename}if __name__ == '__main__':    uvicorn.run("请求文件:app", reload=True)
特别注意的是:
  • File是直接继承自Form的类
  • 声明文件体必须使用File,否则,FastApi会把该参数当作查询参数或请求体(JSON)参数
  • 更推荐使用UploadFile
  • UploadFile 与 bytes 相比有更多优势:
  • 使用spooled文件:
  • 存储在内存的文件超出最大上限时,FastApi会把文件存储到磁盘
  • 这种方式更适于处理图像、视频、二进制文件等大型文件,好处是不会占用所有内存
  • 可获取上传文件的元数据
  • 自带async接口
  • **UploadFile 的属性如下:
  • filename:上传文件名字符串(str),例如:myimage.jpg
  • content-type:内容类型字符串(str) 例如:image/jepg
  • file:其实就是Python文件,可直接传递给其他预期file-like 对象的函数或支持库**
  • **UploadFile 支持的语法
  • write(data):把data写入文件
  • read(size):按指定数量的字节或字符读取文件内容
  • seek(offset):移动至文件offset 字节处的位置
    例如:await myfile.seek(0) 移动到文件开头
     执行 await mylife.read() 后,需要再次读取已读取内容时
  • close():关闭文件**

以上方法都是async方法,要搭配「await」使用
例如,在async路径操作函数内,要用一下方式读取文件内容:

contens = myfile.file.read()
什么是「表单数据」

不包含文件时,表单数据一般用application/x-www-form-urlencoded
但是表单包含文件时,编码为multipart/form-data。使用了File,FastApi就知道从请求体的正确获取文件

多文件上传

"""
同时上传多个文件
可用同一个「表单字段」发送含多个文件的「表单数据」
上传多个文件时,要声明含bytes或UploadFile的列表
"""

import uvicornfrom fastapi import FastAPI, File, UploadFilefrom typing import Listfrom starlette.responses import HTMLResponseapp = FastAPI()# 多文件上传@app.post("/files")async def create_files(files: List[bytes] = File(...)):  # 以byte形式    return {"file_sizes": [len(file) for file in files]}@app.post("/uploadfiles/")async def create_upload_files(files: List[UploadFile] = File(...)): # 以名字的形式    return {"filenames": [file.filename for file in files]}if __name__ == '__main__':    uvicorn.run("请求文件:app", reload=True)

以byte形式展示:
 


以名字的形式展示:

import uvicornfrom fastapi import FastAPI, File, UploadFilefrom typing import Listfrom starlette.responses import HTMLResponseapp = FastAPI()@app.get("/")async def main():    content = """<form action="/files/" enctype="multipart/form-data" method="post"><input name="files" type="file" multiple=""><input type="submit"></form><form action="/uploadfiles/" enctype="multipart/form-data" method="post"><input name="files" type="file" multiple=""><input type="submit"></form>    """    return HTMLResponse(content=content)if __name__ == '__main__':    uvicorn.run("请求文件:app", reload=True)

请求表单与文件

"""
FastApi 支持同时使用File和Form定义文件和表单字段
需要安装库
pip install python-multipart
"""

第一步:

导入 File 与 Form

from fastapi import FastAPI, File, Form, UploadFile
第二步:
import uvicorn
from fastapi import FastAPI, File, Form, UploadFile

app = FastAPI()


@app.post("/files")
async def create_file(
        # 定义File与Form参数
        file: bytes = File(...),
        fileb: UploadFile = File(...),
        token: str = Form(...)
):
    return {
        "file_size": len(file),
        "token": token,
        "fileb_content_type": fileb.content_type,
    }


if __name__ == '__main__':
    uvicorn.run("请求表单与文件:app", reload=True)


总结:

  • 在同一个请求中接收数据和文件时,应同时使用File和Form

处理错误

"""
向客户端返回HTTP错误响应,可以使用HTTPException
"""

第一步:

导入:

from fastapi import FastApi, HTTPException
第二步:
# 导入HTTPException
import uvicorn
from fastapi import FastAPI, HTTPException

app = FastAPI()
items = {"foo": "The Foo Wrestlers"}


class UnicornException(Exception):
    def __init__(self, name: str):
        self.name = name


@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item": items[item_id]}

# 输入:http://127.0.0.1:8000/items/foo1
# 返回:
"""
{
  "detail": "Item not found"
}
"""

if __name__ == '__main__':
    uvicorn.run("处理错误:app", reload=True)

异常处理器

"""
FastApi 自带了一些默认异常处理器
当我们接收到无效数据时,FastApi内部会触发RequestValidationError
该异常内置了默认异常处理器
"""

第一步:

导入RequestValidationError

from fastapi.exceptions import RequestValidationError

并用@app.excption_handler(RequestValidationError)装饰器处理异常

第二步:
import uvicornfrom fastapi import FastAPI, HTTPExceptionfrom fastapi.responses import PlainTextResponsefrom fastapi.exceptions import RequestValidationErrorfrom starlette.exceptions import HTTPException as StarletteHTTPExceptionapp = FastAPI()@app.exception_handler(StarletteHTTPException)async def http_exception_handler(request, exc):    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)@app.exception_handler(RequestValidationError)async def validation_exception_handler(request, exc):    return PlainTextResponse(str(exc), status_code=400)@app.get("/items/{item_id}")async def read_item(item_id: int):    if item_id == 3:        raise HTTPException(status_code=418, detail="Nope! I don't like 3.")    return {"item_id": item_id}# 输入:http://127.0.0.1:8000/items/3# 返回:"""{"detail": "Nope! I don't like 3."}"""if __name__ == '__main__':    uvicorn.run("处理错误:app", reload=True)

请求体-更新数据

"""
用PUT更新数据
"""

更新所有数据(也可更新部分数据)
import uvicorn
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from typing import List, Optional
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None
    price: Optional[float] = None
    tax: float = 10.5
    tags: List[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
    """获取数据"""
    return items[item_id]


@app.put("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
    """更新数据"""
    update_item_encoded = jsonable_encoder(item)
    items[item_id] = update_item_encoded
    return update_item_encoded


if __name__ == '__main__':
    uvicorn.run("请求体-更新数据:app", reload=True)

先PUT,后GET
 

只发送要更新的数据,其余数据不变
import uvicorn
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from typing import List, Optional
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None
    price: Optional[float] = None
    tax: float = 10.5
    tags: List[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}

"""
用PATCH进行部分更新
只发送要更新的数据,其余数据保持不变
"""

@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
    """获取数据"""
    return items[item_id]


@app.patch("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
    """部分更新数据"""
    stored_item_data = items[item_id]
    stored_item_model = Item(**stored_item_data)
    update_data = item.dict(exclude_unset=True)
    updated_item = stored_item_model.copy(update=update_data)
    items[item_id] = jsonable_encoder(updated_item)
    return updated_item


if __name__ == '__main__':
    uvicorn.run("请求体-更新数据:app", reload=True)
使用Pydantic的update参数

"""
.copy():为已有模型创建调用update参数的脚本,该参数为包含更新数据的dict
简而言之就是复制出一份后,以你这个为准,原先的被覆盖
"""

import uvicorn
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from typing import List, Optional
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None
    price: Optional[float] = None
    tax: float = 10.5
    tags: List[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}

"""
用 .copy() 为已有模型创建调用update参数的副本,该参数为包含更新数据的dict
例如:stored_item_model.copy(update=update_data):
"""

@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
    return items[item_id]


@app.patch("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
    stored_item_data = items[item_id]
    stored_item_model = Item(**stored_item_data)
    update_data = item.dict(exclude_unset=True)
    updated_item = stored_item_model.copy(update=update_data)
    items[item_id] = jsonable_encoder(updated_item)
    return updated_item


if __name__ == '__main__':
    uvicorn.run("请求体-更新数据:app", reload=True)


 

依赖注入

"""
相同的逻辑判断处理
共享数据库连接
用户身份鉴权,角色管理
业务逻辑复用
提高代码的复用,减少代码重复
"""

第一步:

导入Depends:

from fastapi import Depends, FastApi
第二步:

创建一个依赖项,或「被依赖项」

import uvicorn
from fastapi import FastAPI, Depends

app = FastAPI()

# 创建依赖 仅需要2行
async def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
    """
    common_parameters函数主要是负责接收函数,处理后返回一个字典
    :param q: 可选查询参数q那是一个str
    :param skip: 默认情况下是0
    :param limit: 默认情况下是100
    :return: 返回一个字典
    """
    return {"q": q, "skip": skip, "limit": limit}

# 声明依赖
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):  # 声明了一个依赖关系,表示的是接口参数请求依赖与common_parameters的函数
    commons.update({'小钟': '同学'})
    return commons

# 声明依赖
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
    return commons


if __name__ == '__main__':
    uvicorn.run("依赖注入:app", reload=True)

注意:您只需要给Depends一个参数,此参数必须类似于函数
 

把类当作被依赖对象
import uvicorn
from fastapi import FastAPI, Depends

app = FastAPI()

# 把类当作被依赖对象
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]


class CommonQueryParams:
    def __init__(self, q: str = None, name: str = None, skip: int = 0, limit: int = 100):
        self.q = q
        self.name = name
        self.skip = skip
        self.limit = limit


@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
    """
    :param commons: CommonQueryParams = Depends(CommonQueryParams)和 commons = Depends(CommonQueryParams)是等价的
    :return:
    """
    response = {}
    # 如果q存在
    if commons.q:
        # 我们就把q加到一个新字典
        response.update({"q": commons.q})
        response.update({"小钟": '同学'})
    elif commons.name:
        response.update({"name": commons.name})
    # 然后在我们的fake_items_db进行截取
    items = fake_items_db[commons.skip: commons.skip + commons.limit]
    response.update({"items": items})
    return response

# 输入:http://127.0.0.1:8000/items/?q=1&name=admin
# 返回:
"""
{
  "q": "q",
  "小钟": "同学",
  "name": "admin",
  "items": [
    {
      "item_name": "Foo"
    },
    {
      "item_name": "Bar"
    },
    {
      "item_name": "Baz"
    }
  ]
}
"""

if __name__ == '__main__':
    uvicorn.run("依赖注入:app", reload=True)

有q,name参数:
 


没有q,name参数:

多层嵌套依赖
import uvicorn
from fastapi import FastAPI, Depends, Cookie

app = FastAPI()


# 多层嵌套依赖
def query_extractor(q: str = None):
    return q


def query_or_cookie_extractor(
        q: str = Depends(query_extractor), last_query: str = Cookie(None)
):
    """
    query_or_cookie_extractor 依赖于 query_extractor
    然后 query_or_cookie_extractor被注入到接口上也被依赖的对象
    :param q:
    :param last_query:
    :return:
    """
    if not q:
        return last_query
    return q


@app.get("/items/")
async def read_query(query_or_default: str = Depends(query_or_cookie_extractor)):
    return {"q_or_cookie": query_or_default}

# 对于同一个依赖,如果处理的结果是一样的,就是返回值是一样的话,我们可以进行多次调用依赖,这时候可以对被依赖的对象设置是否使用缓存机制:
@app.get("/items1")
async def needy_dependency(fresh_value: str = Depends(query_or_cookie_extractor, use_cache=False)):
    """
    use_cache=False 不启动缓存机制
    """
    return {"fresh_value": fresh_value}

if __name__ == '__main__':
    uvicorn.run("依赖注入:app", reload=True)

官网给的例子:

list列表依赖

"""
list列表的依赖意思就是必须两条条件都成立才通过
"""

import uvicorn
from fastapi import FastAPI, Depends, Header, HTTPException

app = FastAPI()


# list列表依赖
async def verify_token(x_token: str = Header(...)):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")


async def verify_key(x_key: str = Header(...)):
    if x_key != "fake-super-secret-key":
        raise HTTPException(status_code=400, detail="X-Key header invalid")


@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
    return [{"item": "Foo"}, {"item": "Bar"}]


if __name__ == '__main__':
    uvicorn.run("依赖注入:app", reload=True)

多依赖对象注入
import uvicorn
from fastapi import FastAPI, Depends, Header, HTTPException

app = FastAPI()


# 多依赖对象注入
async def verify_token(x_token: str = Header(...)):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")


async def verify_key(x_key: str = Header(...)):
    if x_key != "fake-super-secret-key":
        raise HTTPException(status_code=400, detail="X-Key header invalid")

        
@app.get("/items2")
async def items2(xt: str = Depends(verify_token), xk: str = Depends(verify_key)):
    return {"xt": xt, "xk": xk}


if __name__ == '__main__':
    uvicorn.run("依赖注入:app", reload=True)

安全性

"""
/ todo 待补充
"""

第一步:

安装依赖库(因为OAuth2使用“form data”发送用户名和密码)

pip install python-multipart
第二步:
import uvicorn
from fastapi import FastAPI, Depends
from fastapi.security import OAuth2PasswordBearer

app = FastAPI()
"""
当我们在Tokenurl 参数中创建OAuth2PasswordBearer类的实例时,
此参数包含客户端(用户浏览器中运行的前端)将用与发送用户名和秘密啊以获取令牌的URL
"""
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


@app.get("/items/")
async def read_item(token: str = Depends(oauth2_scheme)):
    return {"token": token}


if __name__ == '__main__':
    uvicorn.run("安全性:app", reload=True)

在Swagger UI 中:
 


点击它:
 


如果没有输入username和password,直接try it out 会直接响应401状态码(未经授权)

案例一:获取当前用户
import uvicorn
from typing import Optional
from fastapi import FastAPI, Depends
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel

app = FastAPI()
"""
当我们在Tokenurl 参数中创建OAuth2PasswordBearer类的实例时,此参数包含客户端(用户浏览器中运行的前端)将用与发送用户名和秘密啊以获取令牌的URL
"""
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


# oauth2_scheme变量是OAuth2PasswordBearer的实例,但它也是一个"可调用的"

class User(BaseModel):
    username: str
    email: Optional[str] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None


# 创建(伪)工具函数,该函数接收str类型的令牌并返回到User模型
def fake_decode_token(token):
    return User(
        username=token + "fakedecoded", email="[email protected]", full_name="John Doe"
    )


# 创建一个get_current_user 依赖项
async def get_current_user(token: str = Depends(oauth2_scheme)):
    """
    依赖项get_current_user将从子依赖项oauth2_scheme中接收一个str类型的token
    :param token:
    :return:
    """
    user = fake_decode_token(token)
    return user


@app.get("/users/me")
# 注入当前用户
async def read_users_me(current_user: User = Depends(get_current_user)):
    return current_user


if __name__ == '__main__':
    uvicorn.run("安全性:app", reload=True)
案例二:使用密码和Bearer的简单OAuth2

导入OAuth2PasswordRequestForm,然后在token的路径操作中通过Depends将其作为依赖项使用

import uvicorn
from typing import Optional
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel

# 获取username和password的代码
fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "[email protected]",
        "hashed_password": "fakehashedsecret",
        "disabled": False,
    },
    "alice": {
        "username": "alice",
        "full_name": "Alice Wonderson",
        "email": "[email protected]",
        "hashed_password": "fakehashedsecret2",
        "disabled": True,
    },
}

app = FastAPI()


def fake_hash_password(password: str):
    return "fakehashed" + password


"""
当我们在Tokenurl 参数中创建OAuth2PasswordBearer类的实例时,此参数包含客户端(用户浏览器中运行的前端)将用与发送用户名和秘密啊以获取令牌的URL
"""
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


# oauth2_scheme变量是OAuth2PasswordBearer的实例,但它也是一个"可调用的"


class User(BaseModel):
    username: str
    email: Optional[str] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None


class UserInDB(User):
    hashed_password: str


def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


def fake_decode_token(token):
    user = get_user(fake_users_db, token)
    return user


async def get_current_user(token: str = Depends(oauth2_scheme)):
    user = fake_decode_token(token)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="无效身份验证",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return user


async def get_current_active_user(current_user: User = Depends(get_current_user)):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="无效用户")
    return current_user


@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user_dict = fake_users_db.get(form_data.username)
    if not user_dict:
        raise HTTPException(status_code=400, detail="账号密码不符")
    user = UserInDB(**user_dict)
    """
    UserInDB(**user_dict) ===
    UserInDB(
        username= user_dict["username"],
        email = user_dict["email"],
        full_name = user_dict["full_name"],
        disabled = user_dict["disabled"],
        hashed_password = user_dict["hashed_password"],
        )
    """
    hashed_password = fake_hash_password(form_data.password)
    if not hashed_password == user.hashed_password:
        raise HTTPException(status_code=400, detail="用户名和密码不符")
    return {"access_token": user.username, "token_type": "bearer"}


@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user


if __name__ == '__main__':
    uvicorn.run("安全性:app", reload=True)

在Swagger UI 中
 


点击Authorize
 


点击登陆后


如果未启用的用户(disabled=True),发送get("/users/me")请求就会得到一个「未启用的用户」错误

使用(哈希)密码和JWT Bearer 令牌的OAuth2

"""
什么是JWT?
简单来说就是
会给你一个时效性为一周的令牌,你拿到了令牌你一周可以免登陆,一周过后,你需要重新发起请求获取令牌
需要安装依赖库:
pip install python-jose

**什么是哈希密码?

**简单来说就是
将内容转换成一些像乱码的字节序列
每次传入完全相同的密码时,你就会得到完全相同的乱码
你只能转换成乱码,不能将乱码转换成字符
需要安装依赖库:
pip install passlib
"""
建议回头重看
OAuth2 实现密码哈希与 Bearer JWT 令牌验证 - FastAPI

中间件

// todo 后续回看

异步、后台任务

"""
需要在request执行之后继续操作,但终端并不需要等待这些操作完成才能收到response
例如:
1、执行完request之后发送邮件通知
2、收到文件之后对文件进行二次处理
我们可以通过定义后台任务BackgroundTasks来实现这个功能
"""

# 使用BackgroundTasks
import uvicorn
from fastapi import BackgroundTasks, Depends, FastAPI

app = FastAPI()


def write_log(message: str):
    with open("log.txt", mode="a") as log:
        log.write(message)


def get_query(background_tasks: BackgroundTasks, q: str = None):
    if q:
        message = f"发送内容: {q}\n"
        background_tasks.add_task(write_log, message)
    return q


@app.post("/send-notification/{email}")
async def send_notification(
        email: str, background_tasks: BackgroundTasks, q: str = Depends(get_query)
):
    message = f"邮箱来自: {email}\n"
    background_tasks.add_task(write_log, message)
        """
    .add_task()接收的参数
    - 一个在后台运行的任务函数(write_notification)
    - 按照顺序传递的一个系列参数(email)
    - 任何的关键字参数(essage="notification..)
    """
    return {"message": "Message sent", "q": q}


if __name__ == '__main__':
    uvicorn.run("后台任务:app", reload=True)   

;