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样式,你就可以向我这样,你会发现样式就被引入进来了

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

    如果你想实现这样的例子

    # 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文档:image.png

    路径参数和数值校验

    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] = 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 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 上的效果

    image.png任意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)
    

    额外数据类型

    其他数据类型

    一种标准的“通用唯一标识符”,在请求和响应中将以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()# 重复的headers@app.get("/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执行不成功的话,你可以这样做
    安装这个模块:
    或者这样:image.png

    返回于输入相同的数据

    例如:要求输入账号密码邮箱名字等等,这边只需要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 需要保留的属性image.png

    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形式展示:
    以名字的形式展示:image.png

    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)
    # 创建依赖 仅需要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="john@example.com", 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": "johndoe@example.com",
            "hashed_password": "fakehashedsecret",
            "disabled": False,
        "alice": {
            "username": "alice",
            "full_name": "Alice Wonderson",
            "email": "alice@example.com",
            "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
    建议回头重看
    https://fastapi.tiangolo.com/zh/tutorial/security/oauth2-jwt/

    // 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)