FastAPI框架整理

news2025/2/26 11:40:07

文章目录

  • 一、Starlette、Pydanatic与FastAPI的关系
    • 1.Starlette
      • 1.1ASGI框架
    • 2.Pydantic
      • 2.1基础教程
      • 1.定义接受实体
      • 2.请求参数
      • 3.可以安装插件辅助数据定义和矫正
      • 4.处理校验失败的例子
      • 5.模型的属性和方法
      • 6.解析文件
      • 7.递归模型
      • 8.ORM模型:从类实例创建符合ORM对象的模型
  • 二、请求参数和验证
    • 1.第一个FastAPI
      • 1.1 创建app
        • 命令行启动
        • main启动
      • 1.2 创建实体
      • 1.4 创建api
    • 2.默认集成swagger-ui
    • 3.路由
      • 3.1路由定义
      • 3.2路由实现
    • 3.路径参数和数据的解析、验证
      • 3.1使用枚举类型
      • 3.2传递文件路径
      • 3.3校验路径参数
      • 3.4.查询参数和数据的解析、验证
      • 3.5.请求体和混合参数的使用
      • 3.6.混合参数的使用
      • 3.7.数据格式嵌套的请求体
      • 3.8.Cookie 和 Header 参数
    • 4. 响应处理和FastAPI配置
      • 1.处理返回响应实体
      • 2.响应状态码
      • 3.Form Data 表单数据处理
      • 4.Request Files 单文件、多文件上传及参数详解
      • 5.FASTAPI项目的静态文件配置
      • 6.url路径操作配置
      • 7.swagger-ui界面配置
      • 8.Handling Errors 错误处理
    • 5.依赖注入系统
      • 创建、导入、和声明依赖
        • 函数作为依赖
        • 类作为依赖
        • 子依赖
        • 路径操作装饰器中的多依赖
        • 全局依赖(token-head验证适用)
        • 带yield的依赖
    • 6.安全、认证和授权
      • 1.OAuth2验证
        • 第一步:声明和获取token
        • 第二步:模拟数据库
        • 第三步:写服务于校验
        • 第四步:写接口
      • 2.Bearer with JWT tokens 开发基于JSON Web Tokens的认证

一、Starlette、Pydanatic与FastAPI的关系

1.Starlette

就是处理前端请求的json工具包

Starlette 是一种轻量级的ASGI框架/工具包,是构建高性能Asyncio服务的理想选择

1.1ASGI框架

一种规范,和WSGI
在这里插入图片描述

2.Pydantic

Pydanatic是一个基于Python 类型提示来定义数据验证,序列化和文档(使用JSON 模式)库

2.1基础教程

定义给一个类,用于接受前端请求

1.定义接受实体

from pydantic import BaseModel, ValidationError
class User(BaseModel):
    id: int  # 必须字段
    name: str = "John Snow"  # 有默认值,选填字段
    signup_ts: Optional[datetime] = None
    friends: List[int] = []  # 列表中元素是int类型或者可以直接转换成int类型

2.请求参数

external_data = {
    "id": "123",
    "signup_ts": "2020-12-22 12:22",
    "friends": [1, 2, "3"],  # "3"是可以int("3")的
}
user = User(**external_data)
print(user.id, user.friends)  # 实例化后调用属性
print(repr(user.signup_ts))
print(user.dict())

3.可以安装插件辅助数据定义和矫正

在这里插入图片描述

4.处理校验失败的例子

try:
    User(id=1, signup_ts=datetime.today(), friends=[1, 2, "not number"])
except ValidationError as e:
    print(e.json())




》》》》结果返回
loc:报错的位置
msg:原因
type:类型


[
  {
    "loc": [
      "friends",
      2
    ],
    "msg": "value is not a valid integer",
    "type": "type_error.integer"
  }
]

5.模型的属性和方法

print(user.dict())# 转化成字典
print(user.json())# 转化成json
print(user.copy())  # 这里是浅拷贝
print(User.parse_obj(external_data))# 使用类直接解析external_data数据
print(User.parse_raw('{"id": "123", "signup_ts": "2020-12-22 12:22", "friends": [1, 2, "3"]}'))#使用类直接解析原生数据


》》》
{'id': 123, 'name': 'John Snow', 'signup_ts': datetime.datetime(2020, 12, 22, 12, 22), 'friends': [1, 2, 3]}
{"id": 123, "name": "John Snow", "signup_ts": "2020-12-22T12:22:00", "friends": [1, 2, 3]}
id=123 name='John Snow' signup_ts=datetime.datetime(2020, 12, 22, 12, 22) friends=[1, 2, 3]
id=123 name='John Snow' signup_ts=datetime.datetime(2020, 12, 22, 12, 22) friends=[1, 2, 3]
id=123 name='John Snow' signup_ts=datetime.datetime(2020, 12, 22, 12, 22) friends=[1, 2, 3]
#print(user.dict())# 转化成字典
#print(user.json())# 转化成json
# 和上面的区别是,下面的更详细,说了什么解析的方案
print(user.schema())
print(user.schema_json())
》》》
{'title': 'User', 'type': 'object', 'properties': {'id': {'title': 'Id', 'type': 'integer'}, 'name': {'title': 'Name', 'default': 'John Snow', 'type': 'string'}, 'signup_ts': {'title': 'Signup Ts', 'type': 'string', 'format': 'date-time'}, 'friends': {'title': 'Friends', 'default': [], 'type': 'array', 'items': {'type': 'integer'}}}, 'required': ['id']}
{"title": "User", "type": "object", "properties": {"id": {"title": "Id", "type": "integer"}, "name": {"title": "Name", "default": "John Snow", "type": "string"}, "signup_ts": {"title": "Signup Ts", "type": "string", "format": "date-time"}, "friends": {"title": "Friends", "default": [], "type": "array", "items": {"type": "integer"}}}, "required": ["id"]}

# 不做校验直接赋值

user_data = {"id": "error", "signup_ts": "2020-12-22 12 22", "friends": [1, 2, 3]}  # id是字符串 是错误的
print(User.construct(**user_data))  # 不检验数据直接创建模型类,不建议在construct方法中传入未经验证的数据
》》》
name='John Snow' signup_ts='2020-12-22 12 22' friends=[1, 2, 3] id='error'


print(User.__fields__.keys())  # 定义模型类的时候,所有字段都注明类型,字段顺序就不会乱
》》》
dict_keys(['id', 'name', 'signup_ts', 'friends'])

6.解析文件

from pathlib import Path
path = Path('pydantic_tutorial.json') #当前目录下的 pydantic_tutorial文件
path.write_text('{"id": "123", "signup_ts": "2020-12-22 12:22", "friends": [1, 2, "3"]}') #写入 
print(User.parse_file(path))  #读取

7.递归模型

class Sound(BaseModel):
    sound: str


class Dog(BaseModel):
    birthday: date
    weight: float = Optional[None]
    sound: List[Sound]  # 不同的狗有不同的叫声。递归模型(Recursive Models)就是指一个嵌套一个


dogs = Dog(birthday=date.today(), weight=6.66, sound=[{"sound": "wang wang ~"}, {"sound": "ying ying ~"}])
print(dogs.dict())

》》》

{'birthday': datetime.date(2023, 10, 28), 'sound': [{'sound': 'wang wang ~'}, {'sound': 'ying ying ~'}]}

8.ORM模型:从类实例创建符合ORM对象的模型

from typing import List
from typing import Optional

from pydantic import BaseModel, ValidationError
from pydantic import constr
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

# 先建立ORM数据表
class CompanyOrm(Base):
    __tablename__ = 'companies'
    id = Column(Integer, primary_key=True, nullable=False)
    public_key = Column(String(20), index=True, nullable=False, unique=True)
    name = Column(String(63), unique=True)
    domains = Column(ARRAY(String(255)))

# 继承ORM表
class CompanyModel(BaseModel):
    id: int
    public_key: constr(max_length=20)
    name: constr(max_length=63)
    domains: List[constr(max_length=255)]

    class Config:
        orm_mode = True

# 创建实例
co_orm = CompanyOrm(
    id=123,
    public_key='foobar',
    name='Testing',
    domains=['example.com', 'foobar.com'],
)
# 进行格式化
print(CompanyModel.from_orm(co_orm))

Pydantic支撑的字段类型官方文档

二、请求参数和验证

1.第一个FastAPI

1.1 创建app

http://localhost:8000/docs
默认8000端口和swagger-ui文档地址

命令行启动

启动命令:

uvicorn hello_world:app --reload
from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()  # 这里不一定是app,名字随意


main启动
import time
import uvicorn
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from coronavirus import application

app = FastAPI(
    title='FastAPI Tutorial and Coronavirus Tracker API Docs',
    description='FastAPI教程 描述',
    version='1.0.0',
    docs_url='/docs',
    redoc_url='/redocs',
)

if __name__ == '__main__':
    uvicorn.run('run:app', host='0.0.0.0', port=8000, reload=True, debug=True, workers=1)

1.2 创建实体

class CityInfo(BaseModel):
    province: str
    country: str
    is_affected: Optional[bool] = None  # 与bool的区别是可以不传,默认是null

1.4 创建api

async :使用异步的方式

@app.get('/')
async def hello_world():
    return {'hello': 'world'}

# 使用请求url的city的方式
@app.get('/city/{city}')
async def result(city: str, query_string: Optional[str] = None):
    return {'city': city, 'query_string': query_string}
# 使用URL 和请求实体类的方式
@app.put('/city/{city}')
async def result(city: str, city_info: CityInfo):
    return {'city': city, 'country': city_info.country, 'is_affected': city_info.is_affected}

# 启动命令:uvicorn hello_world:app --reload

2.默认集成swagger-ui

http://localhost:8000/docs
在这里插入图片描述
http://localhost:8000/redoc
只能看不能用
在这里插入图片描述

http://localhost:8000/openapi.json
是默认的信息

在这里插入图片描述

3.路由

3.1路由定义

子项目

在这里插入图片描述

在这里插入图片描述
主应用

引入子项目

tags:标题

wokers:进程的数量
在这里插入图片描述

#!/usr/bin/python3
# -*- coding:utf-8 -*-
# __author__ = '__Jack__'

import time
import uvicorn
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles

from coronavirus import application
from tutorial import app03, app04, app05, app06, app07, app08

# from fastapi.exceptions import RequestValidationError
# from fastapi.responses import PlainTextResponse
# from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI(
    title='FastAPI Tutorial and Coronavirus Tracker API Docs',
    description='FastAPI教程 新冠病毒疫情跟踪器API接口文档,项目代码:https://github.com/liaogx/fastapi-tutorial',
    version='1.0.0',
    docs_url='/docs',
    redoc_url='/redocs',
)

# mount表示将某个目录下一个完全独立的应用挂载过来,这个不会在API交互文档中显示
app.mount(path='/static', app=StaticFiles(directory='./coronavirus/static'), name='static')  # .mount()不要在分路由APIRouter().mount()调用,模板会报错


# @app.exception_handler(StarletteHTTPException)  # 重写HTTPException异常处理器
# async def http_exception_handler(request, exc):
#     """
#     :param request: 这个参数不能省
#     :param exc:
#     :return:
#     """
#     return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
#
#
# @app.exception_handler(RequestValidationError)  # 重写请求验证异常处理器
# async def validation_exception_handler(request, exc):
#     """
#     :param request: 这个参数不能省
#     :param exc:
#     :return:
#     """
#     return PlainTextResponse(str(exc), status_code=400)


@app.middleware('http')
async def add_process_time_header(request: Request, call_next):  # call_next将接收request请求做为参数
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    response.headers['X-Process-Time'] = str(process_time)  # 添加自定义的以“X-”开头的请求头
    return response


app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://127.0.0.1",
        "http://127.0.0.1:8080"
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(app03, prefix='/chapter03', tags=['第三章 请求参数和验证'])
app.include_router(app04, prefix='/chapter04', tags=['第四章 响应处理和FastAPI配置'])
app.include_router(app05, prefix='/chapter05', tags=['第五章 FastAPI的依赖注入系统'])
app.include_router(app06, prefix='/chapter06', tags=['第六章 安全、认证和授权'])
app.include_router(app07, prefix='/chapter07', tags=['第七章 FastAPI的数据库操作和多应用的目录结构设计'])
app.include_router(app08, prefix='/chapter08', tags=['第八章 中间件、CORS、后台任务、测试用例'])
app.include_router(application, prefix='/coronavirus', tags=['新冠病毒疫情跟踪器API'])

if __name__ == '__main__':
    uvicorn.run('run:app', host='0.0.0.0', port=8000, reload=True, debug=True, workers=1)

3.2路由实现

在这里插入图片描述

3.路径参数和数据的解析、验证

3.1使用枚举类型


class CityName(str, Enum):
    Beijing = "Beijing China"
    Shanghai = "Shanghai China"


@app03.get("/enum/{city}")  # 枚举类型的参数
async def latest(city: CityName):
    if city == CityName.Shanghai:
        return {"city_name": city, "confirmed": 1492, "death": 7}
    if city == CityName.Beijing:
        return {"city_name": city, "confirmed": 971, "death": 9}
    return {"city_name": city, "latest": "unknown"}

在这里插入图片描述

3.2传递文件路径

@app03.get("/files/{file_path:path}")  # 通过path parameters传递文件路径
def filepath(file_path: str):
    return f"The file path is {file_path}"

3.3校验路径参数

@app03.get("/path_/{num}")
def path_params_validate(
    num: int = Path(..., title="Your Number", description="不可描述", ge=1, le=10),
):
    return num

3.4.查询参数和数据的解析、验证

给了默认值就是选填的参数,没给默认值就是必填参数

from fastapi import APIRouter, Query, Path, Body, Cookie, Header

@app03.get("/query")
def page_limit(page: int = 1, limit: Optional[int] = None):  # 给了默认值就是选填的参数,没给默认值就是必填参数
    if limit:
        return {"page": page, "limit": limit}
    return {"page": page}


@app03.get("/query/bool/conversion")  # bool类型转换:yes on 1 True true会转换成true, 其它为false
def type_conversion(param: bool = False):
    return param


@app03.get("/query/validations")  # 长度+正则表达式验证,比如长度8-16位,以a开头。其它校验方法看Query类的源码
def query_params_validate(
    value: str = Query(..., min_length=8, max_length=16, regex="^a"),  # ...换成None就变成选填的参数
    values: List[str] = Query(["v1", "v2"], alias="alias_name")
):  # 多个查询参数的列表。参数别名
    return value, values

在这里插入图片描述
在这里插入图片描述

3.5.请求体和混合参数的使用

Request Body and Fields 请求体和字段

class CityInfo(BaseModel):
    name: str = Field(..., example="Beijing")  # example是注解的作用,值不会被验证
    country: str
    country_code: str = None  # 给一个默认值
    country_population: int = Field(default=800, title="人口数量", description="国家的人口数量", ge=800)

    class Config:
        schema_extra = {
            "example": {
                "name": "Shanghai",
                "country": "China",
                "country_code": "CN",
                "country_population": 1400000000,
            }
        }


@app03.post("/request_body/city")
def city_info(city: CityInfo):
    print(city.name, city.country)  # 当在IDE中输入city.的时候,属性会自动弹出
    return city.dict()

在这里插入图片描述

3.6.混合参数的使用

"""Request Body + Path parameters + Query parameters 多参数混合"""


@app03.put("/request_body/city/{name}")
def mix_city_info(
    name: str,
    city01: CityInfo,
    city02: CityInfo,  # Body可以是多个的
    confirmed: int = Query(ge=0, description="确诊数", default=0),
    death: int = Query(ge=0, description="死亡数", default=0),
):
    if name == "Shanghai":
        return {"Shanghai": {"confirmed": confirmed, "death": death}}
    return city01.dict(), city02.dict()




在这里插入图片描述


@app03.put("/request_body/multiple/parameters")
def body_multiple_parameters(
    city: CityInfo = Body(..., embed=True),  # 当只有一个Body参数的时候,embed=True表示请求体参数嵌套。多个Body参数默认就是嵌套的
    confirmed: int = Query(ge=0, description="确诊数", default=0),
    death: int = Query(ge=0, description="死亡数", default=0),
):
    print(f"{city.name} 确诊数:{confirmed} 死亡数:{death}")
    return city.dict()

3.7.数据格式嵌套的请求体

class Data(BaseModel):
    city: List[CityInfo] = None  # 这里就是定义数据格式嵌套的请求体
    date: date  # 额外的数据类型,还有uuid datetime bytes frozenset等,参考:https://fastapi.tiangolo.com/tutorial/extra-data-types/
    confirmed: int = Field(ge=0, description="确诊数", default=0)
    deaths: int = Field(ge=0, description="死亡数", default=0)
    recovered: int = Field(ge=0, description="痊愈数", default=0)


@app03.put("/request_body/nested")
def nested_models(data: Data):
    return data

在这里插入图片描述

3.8.Cookie 和 Header 参数

@app03.get("/cookie")  # 效果只能用Postman测试
def cookie(cookie_id: Optional[str] = Cookie(None)):  # 定义Cookie参数需要使用Cookie类,否则就是查询参数
    return {"cookie_id": cookie_id}


@app03.get("/header")
def header(user_agent: Optional[str] = Header(None, convert_underscores=True), x_token: List[str] = Header(None)):
    """
    有些HTTP代理和服务器是不允许在请求头中带有下划线的,所以Header提供convert_underscores属性让设置
    :param user_agent: convert_underscores=True 会把 user_agent 变成 user-agent
    :param x_token: x_token是包含多个值的列表
    :return:
    """
    return {"User-Agent": user_agent, "x_token": x_token}

在这里插入图片描述

这个是cookie
在这里插入图片描述
heard
在这里插入图片描述

4. 响应处理和FastAPI配置

1.处理返回响应实体

新建实体


# 接受
class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    mobile: str = "10086"
    address: str = None
    full_name: Optional[str] = None

# 输出
class UserOut(BaseModel):
    username: str
    email: EmailStr  # 用 EmailStr 需要 pip install pydantic[email]
    mobile: str = "10086"
    address: str = None
    full_name: Optional[str] = None

接受处理response_model指定返回类处理

users = {
    "user01": {"username": "user01", "password": "123123", "email": "user01@example.com"},
    "user02": {"username": "user02", "password": "123456", "email": "user02@example.com", "mobile": "110"}
}


@app04.post("/response_model/", response_model=UserOut, response_model_exclude_unset=True)
async def response_model(user: UserIn):
    """response_model_exclude_unset=True表示默认值不包含在响应中,仅包含实际给的值,如果实际给的值与默认值相同也会包含在响应中"""
    print(user.password)  # password不会被返回
    # return user
    return users["user01"]

进一步通过response_model相似的处理我们的返回类属性时都是必须的

@app04.post(
    "/response_model/attributes",
    response_model=UserOut,
    # response_model=Union[UserIn, UserOut],
    # response_model=List[UserOut],
    response_model_include=["username", "email", "mobile"],
    response_model_exclude=["mobile"]
)
async def response_model_attributes(user: UserIn):
    """response_model_include列出需要在返回结果中包含的字段;response_model_exclude列出需要在返回结果中排除的字段"""
    # del user.password  # Union[UserIn, UserOut]后,删除password属性也能返回成功
    return user
    # return [user, user]

在这里插入图片描述

2.响应状态码

@app04.post("/status_code", status_code=200)
async def status_code():
    return {"status_code": 200}


@app04.post("/status_attribute", status_code=status.HTTP_200_OK)
async def status_attribute():
    print(type(status.HTTP_200_OK))
    return {"status_code": status.HTTP_200_OK}


》》》
# 上面是状态码
# return是返回数据


{"status_code": 200}

3.Form Data 表单数据处理

@app04.post("/login/")
async def login(username: str = Form(...), password: str = Form(...)):  # 定义表单参数
    """用Form类需要pip install python-multipart; Form类的元数据和校验方法类似Body/Query/Path/Cookie"""
    return {"username": username}

4.Request Files 单文件、多文件上传及参数详解

上传

@app04.post("/file")
async def file_(file: bytes = File(...)):  # 如果要上传多个文件 files: List[bytes] = File(...)
    """使用File类 文件内容会以bytes的形式读入内存 适合于上传小文件"""
    return {"file_size": len(file)}

上传大文件

@app04.post("/upload_files")
async def upload_files(files: List[UploadFile] = File(...)):  # 如果要上传单个文件 file: UploadFile = File(...)
    """
    使用UploadFile类的优势:
    1.文件存储在内存中,使用的内存达到阈值后,将被保存在磁盘中
    2.适合于图片、视频大文件
    3.可以获取上传的文件的元数据,如文件名,创建时间等
    4.有文件对象的异步接口
    5.上传的文件是Python文件对象,可以使用write(), read(), seek(), close()操作
    """
    for file in files:
        contents = await file.read()
        print(contents)
    return {"filename": files[0].filename, "content_type": files[0].content_type}

5.FASTAPI项目的静态文件配置

path:api请求的前缀路径
StaticFiles:文件地址
name:名字

# mount表示将某个目录下一个完全独立的应用挂载过来,这个不会在API交互文档中显示
app.mount(path='/static', app=StaticFiles(directory='./coronavirus/static'), name='static')  # .mount()不要在分路由APIRouter().mount()调用,模板会报错

6.url路径操作配置

response_model:返回类
response_description:返回表述
status_code:返回状态码
summary:就是通常swagger-ui都是显示url调用的函数名称,这个可以自定义内容
tags:swagger-ui另起一个标题
deprecated=True:表示接口已经废了,但是可以调用
在这里插入图片描述

"""Path Operation Configuration 路径操作配置"""


@app04.post(
    "/path_operation_configuration",
    response_model=UserOut,
    # tags=["Path", "Operation", "Configuration"],
    summary="This is summary",
    description="This is description",
    response_description="This is response description",
    deprecated=True,
    status_code=status.HTTP_200_OK
)
async def path_operation_configuration(user: UserIn):
    """
    Path Operation Configuration 路径操作配置
    :param user: 用户信息
    :return: 返回结果
    """
    return user.dict()

7.swagger-ui界面配置

app = FastAPI(
    title='FastAPI Tutorial and Coronavirus Tracker API Docs',
    description='FastAPI教程 新冠病毒疫情跟踪器API接口文档,项目代码:https://github.com/liaogx/fastapi-tutorial',
    version='1.0.0',
    docs_url='/docs',
    redoc_url='/redocs',
)

8.Handling Errors 错误处理


@app04.get("/http_exception")
async def http_exception(city: str):
    if city != "Beijing":
        raise HTTPException(status_code=404, detail="City not found!", headers={"X-Error": "Error"})
    return {"city": city}


@app04.get("/http_exception/{city_id}")
async def override_http_exception(city_id: int):
    if city_id == 1:
        raise HTTPException(status_code=418, detail="Nope! I don't like 1.")
    return {"city_id": city_id}

全局处理

在run.py添加

from fastapi.exceptions import RequestValidationError
from fastapi.responses import PlainTextResponse
from starlette.exceptions import HTTPException as StarletteHTTPException


@app.exception_handler(StarletteHTTPException)  # 重写HTTPException异常处理器
async def http_exception_handler(request, exc):
    """
    :param request: 这个参数不能省
    :param exc:
    :return:
    """
    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)


@app.exception_handler(RequestValidationError)  # 重写请求验证异常处理器
async def validation_exception_handler(request, exc):
    """
    :param request: 这个参数不能省
    :param exc:
    :return:
    """
    return PlainTextResponse(str(exc), status_code=400)

5.依赖注入系统

在这里插入图片描述

在这里插入图片描述

创建、导入、和声明依赖

函数作为依赖

创建


async def common_parameters(q: Optional[str] = None, page: int = 1, limit: int = 100):
    return {"q": q, "page": page, "limit": limit}

使用Depends()

@app05.get("/dependency01")
async def dependency01(commons: dict = Depends(common_parameters)):
    return commons

@app05.get("/dependency02")
def dependency02(commons: dict = Depends(common_parameters)):  # 可以在async def中调用def依赖,也可以在def中导入async def依赖
    return commons

在这里插入图片描述

类作为依赖

fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]


class CommonQueryParams:
    def __init__(self, q: Optional[str] = None, page: int = 1, limit: int = 100):
        self.q = q
        self.page = page
        self.limit = limit


@app05.get("/classes_as_dependencies")
# async def classes_as_dependencies(commons: CommonQueryParams = Depends(CommonQueryParams)):
# async def classes_as_dependencies(commons: CommonQueryParams = Depends()):
async def classes_as_dependencies(commons=Depends(CommonQueryParams)):
    response = {}
    if commons.q:
        response.update({"q": commons.q})
    items = fake_items_db[commons.page: commons.page + commons.limit]
    response.update({"items": items})
    return response
子依赖
def query(q: Optional[str] = None):
    return q


def sub_query(q: str = Depends(query), last_query: Optional[str] = None):
    if not q:
        return last_query
    return q


@app05.get("/sub_dependency")
async def sub_dependency(final_query: str = Depends(sub_query, use_cache=True)):
    """use_cache默认是True, 表示当多个依赖有一个共同的子依赖时,每次request请求只会调用子依赖一次,多次调用将从缓存中获取"""
    return {"sub_dependency": final_query}

在这里插入图片描述

路径操作装饰器中的多依赖
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")
    return x_key


@app05.get("/dependency_in_path_operation", dependencies=[Depends(verify_token), Depends(verify_key)])  # 这时候不是在函数参数中调用依赖,而是在路径操作中
async def dependency_in_path_operation():
    return [{"user": "user01"}, {"user": "user02"}]

在这里插入图片描述
在这里插入图片描述

全局依赖(token-head验证适用)
app05 = APIRouter(dependencies=[Depends(verify_token), Depends(verify_key)])

或者
主app使用

在这里插入图片描述

带yield的依赖

# 这个需要Python3.7才支持,Python3.6需要pip install async-exit-stack async-generator
# 以下都是伪代码

async def get_db():
    db = "db_connection"
    try:
        yield db
    finally:
        db.endswith("db_close")


async def dependency_a():
    dep_a = "generate_dep_a()"
    try:
        yield dep_a
    finally:
        dep_a.endswith("db_close")


async def dependency_b(dep_a=Depends(dependency_a)):
    dep_b = "generate_dep_b()"
    try:
        yield dep_b
    finally:
        dep_b.endswith(dep_a)


async def dependency_c(dep_b=Depends(dependency_b)):
    dep_c = "generate_dep_c()"
    try:
        yield dep_c
    finally:
        dep_c.endswith(dep_b)

6.安全、认证和授权

1.OAuth2验证

在这里插入图片描述
在这里插入图片描述

第一步:声明和获取token
"""OAuth2 密码模式和 FastAPI 的 OAuth2PasswordBearer"""

"""
OAuth2PasswordBearer是接收URL作为参数的一个类:客户端会向该URL发送username和password参数,然后得到一个Token值
OAuth2PasswordBearer并不会创建相应的URL路径操作,只是指明客户端用来请求Token的URL地址
当请求到来的时候,FastAPI会检查请求的Authorization头信息,如果没有找到Authorization头信息,或者头信息的内容不是Bearer token,它会返回401状态码(UNAUTHORIZED)
"""
# 整理/chapter06/token 还没有实现
oauth2_schema = OAuth2PasswordBearer(tokenUrl="/chapter06/token")  # 请求Token的URL地址 http://127.0.0.1:8000/chapter06/token


# 用户拿着账户密码过来,返回token
@app06.get("/oauth2_password_bearer")
async def oauth2_password_bearer(token: str = Depends(oauth2_schema)):
    return {"token": token}


在这里插入图片描述

在这里插入图片描述

第二步:模拟数据库
"""基于 Password 和 Bearer token 的 OAuth2 认证"""

fake_users_db = {
    "john snow": {
        "username": "john snow",
        "full_name": "John Snow",
        "email": "johnsnow@example.com",
        "hashed_password": "fakehashedsecret",
        "disabled": False,
    },
    "alice": {
        "username": "alice",
        "full_name": "Alice Wonderson",
        "email": "alice@example.com",
        "hashed_password": "fakehashedsecret2",
        "disabled": True,
    },
}

#对密码进行加密
def fake_hash_password(password: str):
    return "fakehashed" + password


# 建立实体
class User(BaseModel):
    username: str
    email: Optional[str] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None


class UserInDB(User):
    hashed_password: str
第三步:写服务于校验

# 获取用户,在不在上面的模拟数据库里面,相当于java的DAO层
def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)

# 模拟,看token在不在数据库里面,相当于java的server层
def fake_decode_token(token: str):
    user = get_user(fake_users_db, token)
    return user

# 验证token在不在,同时返回给前端,相当于server层
async def get_current_user(token: str = Depends(oauth2_schema)):
    user = fake_decode_token(token)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication credentials",
            headers={"WWW-Authenticate": "Bearer"},  # OAuth2的规范,如果认证失败,请求头中返回“WWW-Authenticate”
        )
    return user

# 如果是活跃的,就返回活跃的用户信息,否则不返回
async def get_current_active_user(current_user: User = Depends(get_current_user)):
    if current_user.disabled:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive user")
    return current_user

第四步:写接口
# 获取token
@app06.post("/chapter06/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=status.HTTP_400_BAD_REQUEST, detail="Incorrect username or password")
    user = UserInDB(**user_dict)
    hashed_password = fake_hash_password(form_data.password)
    if not hashed_password == user.hashed_password:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect username or password")
    return {"access_token": user.username, "token_type": "bearer"}

# 获取活跃的信息
@app06.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user

直接请求
在这里插入图片描述

2.Bearer with JWT tokens 开发基于JSON Web Tokens的认证


fake_users_db.update({
    "john snow": {
        "username": "john snow",
        "full_name": "John Snow",
        "email": "johnsnow@example.com",
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
        "disabled": False,
    }
})

SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"  # 生成密钥 openssl rand -hex 32
ALGORITHM = "HS256"  # 算法
ACCESS_TOKEN_EXPIRE_MINUTES = 30  # 访问令牌过期分钟


class Token(BaseModel):
    """返回给用户的Token"""
    access_token: str
    token_type: str


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

oauth2_schema = OAuth2PasswordBearer(tokenUrl="/chapter06/jwt/token")


def verity_password(plain_password: str, hashed_password: str):
    """对密码进行校验"""
    return pwd_context.verify(plain_password, hashed_password)


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


def jwt_authenticate_user(db, username: str, password: str):
    user = jwt_get_user(db=db, username=username)
    if not user:
        return False
    if not verity_password(plain_password=password, hashed_password=user.hashed_password):
        return False
    return user


def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(claims=to_encode, key=SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


@app06.post("/jwt/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = jwt_authenticate_user(db=fake_users_db, username=form_data.username, password=form_data.password)
    if not user:
        raise HTTPException(
            status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}


async def jwt_get_current_user(token: str = Depends(oauth2_schema)):
    credentials_exception = HTTPException(
        status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token=token, key=SECRET_KEY, algorithms=[ALGORITHM])
        username = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    user = jwt_get_user(db=fake_users_db, username=username)
    if user is None:
        raise credentials_exception
    return user


async def jwt_get_current_active_user(current_user: User = Depends(jwt_get_current_user)):
    if current_user.disabled:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive user")
    return current_user


@app06.get("/jwt/users/me")
async def jwt_read_users_me(current_user: User = Depends(jwt_get_current_active_user)):
    return current_user

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1477393.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FPGA高端项目:FPGA基于GS2971的SDI视频接收转HDMI输出,提供3套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案的SDI图像缩放应用本方案的SDI纯verilog图像缩放视频拼接应用本方案的SDI HLS图像缩放视频拼接应用本方案的SDI视频编码动态字符叠加输出应用本方案的SDI视频编码多路视频融合视频叠加应用本方案的SDI视频…

追踪Jira中项目成本与工时,更符合国人使用习惯——TimeWise工时管理

近日,龙智联合Atlassian举办的DevSecOps研讨会年终专场“趋势展望与实战探讨:如何打好DevOps基础、赋能创新”在上海圆满落幕。龙智Atlassian技术与顾问咨询团队,以及清晖、JamaSoftware、CloudBees等生态伙伴的嘉宾发表了主题演讲&#xff0…

opencv中的rgb转gray的计算方法

转换原理 在opencv中,可以使用cv2.cvtColor函数将rgb图像转换为gray图像。示例代码如下, import cv2img_path "image.jpg" image cv2.imread(img_path) gray_image cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) mean gray_image.mean() pri…

Transformer之Residuals Decoder

The Residuals 我们需要提到的编码器架构中的一个细节是,每个编码器中的每个子层(self-attention,,ffnn)周围都有一个残余连接,然后是 layer-normalization 步骤。 如果我们要可视化向量和与 self attention 相关的 layer-norm 运算&#x…

计算机专业大学四年应该如何规划(Java方向)

计算机专业的学生,如何在大学四年内提高自己的竞争力,毕业之后直接进大厂工作? 以下将从大学四年计算机专业的学习规划、课程设置、能力提升、参考书籍等方面,为同学们提供一些建议和指导。 大一: 主攻技能学习并且达…

Excel中使用ROW函数自动更新行号或编号

操作步骤: 1、在编号“1”的单元格输入公式“ROW()-1”; 2、在上一步填好公式的单元格基础上下拉填充,即可批量得到编号,如果删掉其中的一行或几行,编号会自动进行更新。

nginx 日志,压缩,https功能介绍

一, 自定义访问日志 (一)日志位置存放 1,格式 2, 级别 level: debug, info, notice, warn, error, crit, alert, emerg 3,示例 服务机定义 错误日志存放位置 客户机错误访问 查看错误日志 4&#xff…

[云原生] k8s之pod容器

一、pod的相关知识 1.1 Pod基础概念 Pod是kubernetes中最小的资源管理组件,Pod也是最小化运行容器化应用的资源对象。一个Pod代表着集群中运行的一个进程。kubernetes中其他大多数组件都是围绕着Pod来进行支撑和扩展Pod功能的,例如,用于管理…

虚拟机安装+固定ip地址

一、下载CentOS 二、安装CentOS 1、打开你的VMware Workstation Pro,并点击“创建新的虚拟机” 2、点选典型(推荐)(T),并点击“下一步” 3、点选稍后安装操作系统(S),并点击“下一步” 4、点选Linux,并点击“下一步” 6、点击“…

【探索AI】十一 深度学习之第1周:深度学习概述与基础

深度学习概述与基础 深度学习的发展历史与现状神经网络的基本原理前向传播与反向传播算法常见的激活函数与优化算法深度学习框架(如TensorFlow或PyTorch)进行基础操作 深度学习的发展历史与现状 深度学习的发展历史可以追溯到上世纪40年代,当…

java上机编程题面试,记一次美团Java研发岗的面试经历

第一篇:SpringBoot面试篇 1.1 35常见SpringBoot知识点 问题一:Spring Boot、Spring MVC 和 Spring 有什么区别? 问题二:什么是自动配置? 问题三:什么是 Spring Boot Stater ? 问题四&#x…

uniapp生成app包引导用户开启通知权限和热更新

uniapp生成app包引导用户开启通知权限和热更新 引导用户开启通知权限 export function setPermissions() {// #ifdef APP-PLUS if (plus.os.name Android) {var main plus.android.runtimeMainActivity();var pkName main.getPackageName();var uid main.getApplicationI…

数据库JSON类型到映射JAVA上

Mysql存放JSON数据如何映射JAVA实体类 概述:最近写在写SKU模块中,需要表中字段存放JSON类型数据,mybatis-plus在查询的时候如何跟JSON类型所匹配呢?再次记录一下。 直接上代码,后面有解释到底如何映射上的。 Mysql表…

java 商机管理系统Myeclipse开发mysql数据库web结构jsp编程计算机网页项目

一、源码特点 java 商机管理系统是一套完善的java web信息管理系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发,数据库为Mysql5.0&…

跨越边界:Compose Multiplatform 跨平台开发的未来之路

跨越边界:Compose Multiplatform 跨平台开发的未来之路 1. 引言 在移动应用和软件开发领域,跨平台开发一直是一个备受关注的话题。随着移动设备多样化和用户需求的不断增长,开发者们迫切需要一种在多个平台上共享代码的解决方案。Jetpack C…

深入浅出JVM(十七)之并发垃圾收集器CMS

上篇文章介绍用户线程与GC线程并发执行时可能产生的问题以及使用三色标记法演示原始快照和增量更新两种解决方案 这篇文章将主要介绍并发垃圾收集器中的CMS,其中CMS使用增量更新来解决对象消失问题,如果不了解增量更新的同学可以查看上篇文章深入浅出JV…

oracle with check option 学习

with check option保证了通过视图进行的修改,必须也能通过该视图看到修改后的结果; 你插入,那么插入这条记录在刷新视图后必须可以看到; 如果修改,修改完的结果也必须能通过该视图看到; scott登录了以后创…

day04_拦截器Apifox角色管理(登录校验,API接口文档,权限管理说明,角色管理,添加角色,修改角色,删除角色)

文章目录 1. 登录校验1.1 需求说明1.2 实现思路1.3 ThreadLocal1.4 AuthContextUtil1.5 拦截器使用1.5.1 拦截器开发1.5.2 拦截器注册 1.6 代码优化1.6.1 配置优化1.6.2 代码优化1.6.3 前端修改 2. API接口文档2.1 Apifox接口管理平台2.1.1 接口管理平台简介2.1.2 Apifox简介2.…

minGW-64-win使用

本文适用于win7 win10。 下载 官网下载地址:MinGW-w64 - for 32 and 64 bit Windows - Browse /mingw-w64/mingw-w64-release at SourceForge.net 不过我下了exe安装版报错,如下图,所以最后选择了zip方式。 zip版本解压 配置环境变量 ;C…

自定义el-dialog的样式

实现效果: 样式代码如下:(可以写在common.scss文件夹中) .el-dialog__header {padding: 16px 20px;border-bottom: 1px solid #DCDFE6;display: flex;align-items: center;.el-dialog__title {font-size: 16px;position: relativ…