Python 后端开发
参考资料
Pydantic
定义模型, 以供类型检查
模型还具有许多高级特性, 例如:
from pydantic import BaseModel
class User ( BaseModel ):
id : int # 类型
name : str = 'Jane Doe' # 默认值
key = Column ( String ( 63 ), unique = True ) # 从实例属性中获取类型
user = User ( id = '123' , name = 'John Doe' )
User . model_validate ({ 'id' : 123 , 'name' : 'John Doe' }) # 类型检查
User . model_validate_json ( '{"id": 123, "name": "John Doe"}' ) # 类型检查
User . model_dump () # 转换为字典
User . model_dump_json () # 转换为 json
FastAPI
依赖 OpenAPI 与 Pydantic 库实现
数据验证
自动生成交互式文档(可以自定义任何基于 OpenAPI 的文档)
可使用 async
与 await
实现异步
可以定义元数据, 提供给文档
集成 HTTPX 库进行测试
from fastapi import FastAPI
app = FastAPI () # 创建一个 FastAPI 实例
@app . get ( "/" ) # 路由
async def root (): # 异步函数
return { "message" : "Hello World" } # 返回 json
一个 url 协议://主机:端口/路径
路径亦称为端点 / 路由
使用 HTTP 方法来区分不同的操作
GET - 查询
POST - 创建
PUT - 更新
DELETE - 删除
路径参数
对于路径参数函数的类型重载, 注意声明顺序决定检查顺序
可以使用 Path
对象来声明路径参数类型, 默认值等
@app . get ( "/items/ {id} " ) # 路径参数
async def read_item ( id : int ): # fastapi 会进行类型检查
return { "item_id" : id }
from enum import Enum
# 声明一个枚举类进行检查
class ModelName ( str , Enum ): # 继承 str 的原因是为了兼容生成文档
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
查询参数
形如 /items/?skip=0&limit=10
/items/
会使用默认值
可使用 None
类型表示可选参数
也可以是 bool
类型, 会自动转换为 True
和 False
这回不依赖声明顺序了, 检查查询参数来决定使用哪个函数
可以使用 Query
对象来声明查询参数类型, 默认值等
Annotated[FilterParams, Query()]
用一个模型声明查询参数类型
from fastapi import FastAPI
app = 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 ): # 没同名, 代表查询参数
return fake_items_db [ skip : skip + limit ]
请求体
直接实现类型检查 + JSON
和上面的可以叠加
也可以用多个单独的参数来实现
Annotated[class, Cookie()]
声明 Cookie 参数
Header / Cookie / Query / Path 同理
请求体不是 JSON 格式的, 而是表单格式的
使用 Form
对象来声明表单参数
Annotated[class, From()]
亦可
也可以使用 File
对象来声明文件参数
也可以使用 UploadFile
对象来声明文件参数类型
from fastapi import FastAPI
from pydantic import BaseModel
class Item ( BaseModel ):
name : str
description : str | None = None
price : float
tax : float | None = None
wsl -- set - default - version 2
1
app = FastAPI ()
@app . post ( "/items/" )
async def create_item ( item : Item ): # 请求体
return item
更新
可以单独定义需要更新的部分的模型, 这样就不会覆盖所有字段
响应
jsonable_encoder
可以将任意类型转换为 JSON 格式兼容
@app . post ( "/user/" , response_model = UserOut ) # 用装饰器声明响应模型
@app . post ( "/items/" , status_code = 201 ) # 声明状态码
后台任务
可以使用 BackgroundTasks
对象来声明后台任务
from fastapi import BackgroundTasks , FastAPI
app = FastAPI ()
def write_notification ( email : str , message = "" ): # 后台任务
with open ( "log.txt" , mode = "w" ) as email_file :
content = f "notification for { email } : { message } "
email_file . write ( content )
@app . post ( "/send-notification/ {email} " )
async def send_notification ( email : str , background_tasks : BackgroundTasks ):
background_tasks . add_task ( write_notification , email , message = "some notification" ) # 进行后台任务
return { "message" : "Notification sent in the background" }
静态文件
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI ()
app . mount ( "/static" , StaticFiles ( directory = "static" ), name = "static" ) # 挂载静态文件
# 指定路径, 实例, 名称
依赖注入
可以使用 Depends
对象来声明依赖
依赖可以是任何可调用对象, 包括函数, 类, 协程函数
可以嵌套依赖, 菱形依赖自动处理
路径装饰器也可以声明依赖, 依赖会被调用 (比如用来判断请求头), 但依赖的值不会被传递给响应函数
相应的 app
实例也可以声明依赖, 相当于为所有路由声明依赖
async def get_db (): # 这么声明的依赖会停在 yield 处, 并将 db 传递给响应函数
db = DBSession ()
try :
yield db
finally : # 直到响应函数返回后, 才会执行 finally 块
db . close ()
from typing import Union
from fastapi import Depends , FastAPI
app = FastAPI ()
async def common_parameters (
q : Union [ str , None ] = None , skip : int = 0 , limit : int = 100
):
return { "q" : q , "skip" : skip , "limit" : limit }
@app . get ( "/items/" )
async def read_items ( commons : dict = Depends ( common_parameters )):
return commons
@app . get ( "/users/" )
async def read_users ( commons : dict = Depends ( common_parameters )):
return commons
中间件
import time
from fastapi import FastAPI , Request
app = FastAPI ()
@app . middleware ( "http" )
async def add_process_time_header ( request : Request , call_next ):
start_time = time . perf_counter ()
response = await call_next ( request )
process_time = time . perf_counter () - start_time
response . headers [ "X-Process-Time" ] = str ( process_time )
return response
安全
可以使用 CORSMiddleware
来添加 CORS 支持 (指定允许的域名, 方法等)
基于 OAuth2 规范实现安全认证
fastapi 会接收账号密码, 验证后返回 token
前端保存 token, 每次请求时在请求头添加 Authorization : "Bearer+token"
后端验证 token, 并返回用户信息
使用 passlib 库来哈希密码
使用 pyJWT 库来生成 JWT 令牌
from typing import Union
from fastapi import Depends , FastAPI
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel
app = FastAPI ()
oauth2_scheme = OAuth2PasswordBearer ( tokenUrl = "token" ) # 验证器
class User ( BaseModel ): # 声明用户模型
username : str
email : Union [ str , None ] = None
full_name : Union [ str , None ] = None
disabled : Union [ bool , None ] = None
def fake_decode_token ( token ): # 返回用户
return User (
username = token + "fakedecoded" , email = "john@example.com" , full_name = "John Doe"
)
async def get_current_user ( token : str = Depends ( oauth2_scheme )): # 验证函数, 依赖于验证器
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
@app . post ( "/token" )
async def login ( form_data : OAuth2PasswordRequestForm = Depends ()): # 登陆函数, 表单依赖于 OAuth2PasswordRequestForm
# 注意, 表单模型不能自定义, 是 OAuth2 规范约定的模型
user_dict = fake_users_db . get ( form_data . username )
if not user_dict :
raise HTTPException ( status_code = 400 , detail = "Incorrect username or password" )
user = UserInDB ( ** user_dict )
hashed_password = fake_hash_password ( form_data . password ) # 哈希密码, 一般使用 passlib 库
if not hashed_password == user . hashed_password :
raise HTTPException ( status_code = 400 , detail = "Incorrect username or password" )
return { "access_token" : user . username , "token_type" : "bearer" }
架构
可以使用 APIRouter
来声明本文件的路由组 (app 的分身)
router = APIRouter()
@router.get("/")
app 用 include_router
来包含路由组
可以使用 prefix
和 tags
来声明路由组的前缀和标签
router = APIRouter(prefix="/users", tags=["users"])
亦可 app.include_router(router, prefix="/admin", tags=["admin"])
from fastapi import Depends , FastAPI
from .dependencies import get_query_token , get_token_header
from .internal import admin
from .routers import items , users
app = FastAPI ( dependencies = [ Depends ( get_query_token )])
app . include_router ( users . router )
app . include_router ( items . router )
app . include_router (
admin . router ,
prefix = "/admin" ,
tags = [ "admin" ],
dependencies = [ Depends ( get_token_header )],
responses = { 418 : { "description" : "I'm a teapot" }},
)
@app . get ( "/" )
async def root ():
return { "message" : "Hello Bigger Applications!" }
mysql-connector-python
一个 mysql 数据库的 python 驱动程序
几乎是 sql 语句的一对一实现
SQLAlchemy
连接
from sqlalchemy import create_engine
engine = create_engine ( "sqlite+pysqlite:///:memory:" , echo = True ) # 内存中的 sqlite
# 数据库+驱动://用户名:密码@主机:端口/数据库名
# echo=True 打印 sql 语句
# 不多介绍文本 SQL
with engine . connect () as conn : # 通过连接对数据库进行操作
conn . execute (
text ( "INSERT INTO some_table (x, y) VALUES (:x, :y)" ),
[{ "x" : 1 , "y" : 1 }, { "x" : 2 , "y" : 4 }],
) # 操作会被缓存, 直到 commit 才会被执行
conn . commit ()
result = conn . execute ( ... ) . all # 结果作为元组返回
with Session ( engine ) as session : # 对 connect 的封装
result = session . execute ( ... )
session . commit () # 提交事务
对象关系映射
from sqlalchemy import MetaData
metadata_obj = MetaData () # 元数据对象
from sqlalchemy import Table , Column , Integer , String
user_table = Table ( # 声明表结构
"user_account" , # 表名
metadata_obj , # 元数据对象
Column ( "id" , Integer , primary_key = True ), # 声明列
Column ( "name" , String ( 30 )),
Column ( "fullname" , String ),
)
print ( user_table . c . keys ) # 列名
Column ( "user_id" , ForeignKey ( "user_account.id" ), nullable = False ) # 外键
# 更好的方法
from sqlalchemy.orm import DeclarativeBase
class Base ( DeclarativeBase ): # 声明基类
pass
class User ( Base ): # 声明模型
__tablename__ = "user_account" # 表名
id = Column ( Integer , primary_key = True )
name = Column ( String ( 30 ))
fullname = Column ( String )
addresses = relationship (
"Address" , back_populates = "user" , cascade = "all, delete-orphan"
) # 关系
some_table = Table ( "some_table" , metadata_obj , autoload_with = engine ) # 自动加载表结构
操作
from sqlalchemy import insert , select , update , delete
stmt = insert ( user_table ) . values ( x = 6 , y = 8 , z = 10 ) # 插入语句
stmt = select ( user_table ) . where ( ... ) # 查询语句
stmt = select ( user_table ) . where ( ... ) . join_from ( another_table ) # 查询语句
stmt = update ( user_table ) . where ( ... ) . values ( x = 6 , y = 8 ) # 更新语句
stmt = delete ( user_table ) . where ( ... ) # 删除语句
# 还有很多, 大体与 SQL 语句对应
with Session ( engine ) as session : # 操作
session . execute ( stmt )
session . commit ()
ORM 操作
from sqlalchemy.orm import Session
with Session ( engine ) as session : # 操作
session . add_all ([ User ( name = "spongebob" , fullname = "Spongebob Squarepants" ), ... ]) # 插入
session . flush () # 缓存
session . commit () # 提交
session . query ( User ) . filter ( User . name . in_ ([ "sandy" , "susan" ])) . all () # 查询
session . query ( User ) . filter ( User . name == "sandy" ) . first () # 查询
session . query ( User ) . filter ( User . name . like ( " %e d" )) . all () # 查询
session . query ( User ) . filter ( User . name == "sandy" ) . name = "666" # 更新
session . delete ( result ) # 删除
session . commit () # 提交
session . rollback () # 回滚
session . close () # 关闭
relationship
可以定义延迟加载, 预加载, 级联删除等特性
cascade="all, delete-orphan"
级联删除
正常声明一对多
uselist=False
表示单个对象而非列表
辅以外键约束, 实现其它关系
class Parent ( Base ):
__tablename__ = 'parents'
id = Column ( Integer , primary_key = True )
children = relationship ( "Child" , back_populates = "parent" )
class Child ( Base ):
__tablename__ = 'children'
id = Column ( Integer , primary_key = True )
parent_id = Column ( Integer , ForeignKey ( 'parents.id' ))
parent = relationship ( "Parent" , back_populates = "children" )
class Enrollment ( Base ):
__tablename__ = 'enrollments'
student_id = Column ( Integer , ForeignKey ( 'students.id' ), primary_key = True )
course_id = Column ( Integer , ForeignKey ( 'courses.id' ), primary_key = True )
enrollment_date = Column ( DateTime )
# 定义与 Student 和 Course 的关系
student = relationship ( "Student" , back_populates = "enrollments" )
course = relationship ( "Course" , back_populates = "enrollments" )
class Student ( Base ):
__tablename__ = 'students'
id = Column ( Integer , primary_key = True )
name = Column ( String )
enrollments = relationship ( "Enrollment" , back_populates = "student" )
class Course ( Base ):
__tablename__ = 'courses'
id = Column ( Integer , primary_key = True )
title = Column ( String )
enrollments = relationship ( "Enrollment" , back_populates = "course" )
Uvicorn
基于 ASGI 协议的异步 web 服务器 (不同于 WSGI)
uvicorn filename:objname # 启动服务器
# --reload 热重载
# --port 端口
# --host IP
# --workers 进程数
# --log-level 日志级别
# --log-config 日志文件位置
# --ssl-keyfile=SSL密钥文件 --ssl-certfile=SSL证书文件