FastApi
fastapi
是一个用于构建API 的现代、快速(高性能)的web框架,它是建立在Starlette和Pydantic基础上的。
Pydantic是一个基于Python类型提示来定义数据验证、序列化和文档的库,Starlette是一种轻量级的ASGI框架/工具包,适合用Python构建异步Web服务。FastAPI 依赖 Python 3.8 及更高版本。
开始案例
先安装fastapi 和 ASGI服务器
pip install fastapi
pip install uvicorn
然后写一个FastAPI
from typing import Union
import uvicorn
from fastapi import FastAPI
# 创建一个FastAPI应用实例
app = FastAPI()
# 定义一个带有动态参数的GET请求处理器,用于打招呼
@app.get("/hello/{name}")
async def say_hello(name: str):
# 返回一个根据名字定制的打招呼消息
return {"message": f"Hello {name}"}
# 定义另一个根路由GET请求处理器,功能与前一个根路由处理器不同
@app.get("/")
def read_root():
# 返回一个简单的欢迎消息
return {"Hello": "World"}
# 定义一个获取物品信息的GET请求处理器,接受一个物品ID和一个可选参数q
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
# 返回物品ID和可选参数q的信息
return {"item_id": item_id, "q": q}
在命令行用uvicorn
运行
PS D:\Users\user\PycharmProjects\fastApiProject> uvicorn main:app --reload
INFO: Will watch for changes in these directories: ['D:\\Users\\user\\PycharmProjects\\fastApiProject']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [30468] using WatchFiles
INFO: Started server process [20408]
INFO: Waiting for application startup.
INFO: Application startup complete.
也可以在python文件中直接运行
if __name__ == "__main__":
uvicorn.run("main:app", host="127.0.0.1", port=8000,reload = True)
浏览器访问http://127.0.0.1:8000/
可以看到输出了欢迎消息
访问http://127.0.0.1:8000/items/5?q=runoob
可以看到输出
FastAPI 交互式 API 文档
FastAPI 提供了内置的交互式 API 文档,使开发者能够轻松了解和测试 API 的各个端点。这个文档是自动生成的,基于 OpenAPI 规范,支持 Swagger UI 和 ReDoc 两种交互式界面。通过 FastAPI 的交互式 API 文档,开发者能够更轻松地理解和使用 API,提高开发效率。在运行 FastAPI 应用时,Uvicorn 同时启动了交互式 API 文档服务。
默认情况下,可以通过访问 http://127.0.0.1:8000/docs
来打开 Swagger UI 风格的文档:
Swagger UI 提供了一个直观的用户界面,用于浏览 API 的各个端点、查看请求和响应的结构,并支持直接在文档中进行 API 请求测试。通过 Swagger UI,你可以轻松理解每个路由操作的输入参数、输出格式和请求示例。
通过 http://127.0.0.1:8000/redoc
来打开 ReDoc 风格的文档。
ReDoc 是另一种交互式文档界面,具有清晰简洁的外观。它使得开发者能够以可读性强的方式查看 API 的描述、请求和响应。与 Swagger UI 不同,ReDoc 的设计强调文档的可视化和用户体验。
交互式文档的优势
- 实时更新: 交互式文档会实时更新,反映出应用代码的最新更改。
- 自动验证: 输入参数的类型和格式会得到自动验证,降低了错误的可能性。
- 便于测试: 可以直接在文档中进行 API 请求测试,避免使用其他工具。
路径操作
FastAPI 支持多种常用的 HTTP 调用方式,包括:
- GET:用于获取资源,例如
@app.get("/items/{item_id}")
。 - POST:用于创建资源,例如
@app.post("/items/")
。 - PUT:用于更新资源,例如
@app.put("/items/{item_id}")
。 - DELETE:用于删除资源,例如
@app.delete("/items/{item_id}")
。 - PATCH:用于部分更新资源,例如
@app.patch("/items/{item_id}")
。
参数
路径操作装饰器的参数:
from typing import Union
import uvicorn
from fastapi import FastAPI
# 创建一个FastAPI应用实例
app = FastAPI()
# 定义一个post方法
@app.post("/items",tags=["这是items测试接口"],
summary="这是items接口的 summary",
description="这是items接口的description",
response_description="这是items接口返回参数的description"
)
def process_value(value: Union[int, float, str]) -> str:
return f"Processed value: {value}"
if __name__ == "__main__":
uvicorn.run("main:app", host="127.0.0.1", port=8000,reload = True)
include_router
include_router
用于将一个或多个路由器(APIRouter
实例)包含到主应用中。这使得你可以将路由组织成模块化的部分,方便管理和扩展。
比如目录结构如下的项目中
注意:main.py 与apps模块同级,都是在shopping下
appone.urls.py
from fastapi import APIRouter
app01 = APIRouter()
@app01.get("/food")
def get_food():
return {"food": "pizza"}
@app01.get("/drink")
def get_drink():
return {"drink": "water"}
apptwo.urls.py
from fastapi import APIRouter
app02 = APIRouter()
@app02.post("/login")
def user_login():
return {"user": "login"}
@app02.post("/register")
def user_register():
return {"user": "register"}
main.py
from typing import Union
import uvicorn
from fastapi import FastAPI
from apps.appone.urls import app01
from apps.apptwo.urls import app02
# 创建一个FastAPI应用实例
app = FastAPI()
# 将app01路由器包含进app路由器中,所有路径以"/shop"为前缀,这些路由将被标记为"商城中心接口"
app.include_router(app01, prefix="/shop",tags=["商城中心接口"])
# 将app02路由器包含进app路由器中,所有路径以"/user"为前缀,这些路由将被标记为"用户中心接口"
app.include_router(app02, prefix="/user",tags=["用户中心接口"])
if __name__ == "__main__":
uvicorn.run("main:app", host="127.0.0.1", port=8000,reload = True)
查看API文档,可以看到分成了两个部分,商城中心接口以及用户中心接口
请求与响应
路径参数
可以在路径后用{}
自定义参数传入函数内,路径上传入的参数都是作为str
字符串类型传入的,如果一定要传入某种类型,比如整型可以在函数参数内声明 类型def get_user(id:int)
声明后只能传入int类型的参数,如果类型错误会返回错误信息
@app01.get("/user/{id}")
def get_user(id:int):
print(f"Getting user id:{id},type:{type(id)}")
return {
"user_id" : id,
"name": "John Doe",
"age": 30
}
注意:路径匹配会根据代码从上到下匹配,如果有路径重名的情况,会执行在上面的函数,比如有路径/user/1
,/user/{id}
当id = 1 的时候,只会返回上面的信息
@app01.get("/user/1")
def get_user():
return {
"user_id" : "/user/1",
"name": "John Doe",
"age": 30
}
@app01.get("/user/{id}")
def get_user(id:int):
print(f"Getting user id:{id},type:{type(id)}")
return {
"user_id" : id,
"name": "John Doe",
"age": 30
}
查询参数
可以在函数方法内加入查询参数,查询参数和路径参数可以混合使用,
@app02.get("/jobs/{kd}") # 路径参数
async def get_jobs(kd,gj,xl = "本科"): # 查询参数,没设置默认值就必须传入参数
return {
"kd":kd,
"xl":xl,
"gj":gj
}
请求体数据
FastAPI 通过 Pydantic 来做类型强制检查(校验数据),不符合类型要求就会抛出异常。
from datetime import date
from typing import List, Optional, Union
from fastapi import APIRouter
from pydantic import BaseModel, Field, field_validator
app02 = APIRouter()
class Addr(BaseModel):
province: str
city: str
class User(BaseModel):
# 定义User类,继承自BaseModel
name:str = Field(title="用户名", description="用户名描述")
# name属性表示用户的姓名,类型为字符串
age:int = Field(default= 0,title="年龄",description="年龄描述",gt=0,lt=100) # gt:大于,lt:小于
# age属性表示用户的年龄,是整型,默认值为0
# 年龄的标题是"年龄",描述是"年龄描述"
# 年龄必须大于0且小于100
birth: Union[date, None] = None
# birth属性表示用户的出生日期,类型为日期或者None
# 默认值为None
friends: List[int]
# friends属性表示用户的朋友们的ID列表,类型为整型列表
description: Optional[str] = None
# description属性表示用户的描述,类型为可选的字符串
# 默认值为None
addr: Addr
@field_validator("name")
def name_must_be_alpha(cls, v):
"""
确保名称字段只包含字母。
此验证器确保传递给字段的值不包含任何非字母字符,
以保持名称数据的整洁和一致性。
参数:
cls: 类本身, conventional usage in class methods.
v: 待验证的名称字段值。
返回:
经验证只包含字母的字符串。
异常:
AssertionError: 如果字符串包含非字母字符。
"""
# 检查变量v是否只包含字母
assert v.isalpha(), "must only contain alphabetic characters"
# 如果变量v通过检查,将其原样返回
return v
class ResponseData(BaseModel):
data: List[User]
@app02.post("/user")
async def get_user(user:User):
print(type(user)) # <class 'apps.app02.User'>
print(user.name, user.age, user.birth) # string 88 2024-08-29
print(user.model_dump()) # {'name': 'string', 'age': 88, 'birth': datetime.date(2024, 8, 29), 'friends': [0], 'description': 'string', 'addr': {'province': 'string', 'city': 'string'}}
return {
"user":user
}
@app02.post("/data")
async def get_data(data : ResponseData):
return data
当我们编写了参数的数据限制之后,能够在接口文档中直观的查看到,以/data
接口为例
from表单数据
在 FastAPI 中,Form
模块用于处理表单数据的提交。它提供了一种便捷的方式来提取从 HTML 表单提交的数据。
具体来说,Form
主要用于处理 HTTP 请求中的表单数据。这些数据通常是通过 application/x-www-form-urlencoded
或 multipart/form-data
这样的内容类型提交的。以下是一些关键点:
- 处理表单数据:
Form
用于定义处理表单字段的模型。你可以在 FastAPI 路由中使用Form
对象来提取表单字段的值。 - 与 Pydantic 结合使用:虽然 FastAPI 的核心数据模型使用 Pydantic 来进行数据验证,但表单数据的提取和验证通常通过
Form
来实现。 - 如何使用:
- 在视图函数中,你可以将
Form
对象作为参数,FastAPI 会自动解析请求中的表单数据并将其传递给视图函数。 Form
支持设置默认值、标记字段为必填项等功能。
- 在视图函数中,你可以将
主要用途
- 处理用户输入的表单数据,例如在注册页面、登录页面等。
- 提供简单的表单验证和处理机制。
首先 需要先使用pip install python-multipart
命令进行安装。
from datetime import date
from typing import List, Optional, Union
from fastapi import APIRouter,Form
from pydantic import BaseModel, Field, field_validator
app03 = APIRouter()
@app03.post("/regin")
async def get_user(username: str = Form(), password: str = Form()):
print(f"username:{username},password:{password}")
return {
"username": username
}
文件上传
文件上传主要包括UploadFile
和 bytes
两种方式
首先介绍一下bytes方式上传文件
from typing import List
from fastapi import APIRouter,File
app04 = APIRouter()
@app04.post("/file")
async def get_file(file: bytes = File()):
# 适合小文件上传
print(file)
return {
"files": len(file)
}
@app04.post("/files")
async def get_file(files: List[bytes] = File()):
print(files)
return {
"files": len(files)
}
UploadFile,先创建imgs 文件夹用于接收我们上传的文件
# 使用路径操作装饰器定义一个POST请求的端点"/uploadFile"
@app04.post("/uploadFile")
async def upload_file(file: UploadFile):
"""
上传文件功能。
- 参数:
- file: 用户上传的文件,类型为UploadFile。
- 返回:
- 一个包含文件名的JSON响应。
此函数接收上传的文件,打印文件信息,然后将文件保存到"imgs"文件夹下。
"""
# 打印上传文件的信息
print(file)
# 拼接文件保存路径
path = os.path.join("imgs", file.filename)
# 以二进制写模式打开(或创建)目标文件
with open(path, "wb") as f:
# 遍历上传文件的每一行,写入到目标文件
for line in file.file:
f.write(line)
# 返回上传文件的文件名
return {
"filename": file.filename
}
@app04.post("/uploadFiles")
async def upload_file(files: List[UploadFile]):
"""
上传文件功能。
- 参数:
- file: 用户上传的文件,类型为UploadFile。
- 返回:
- 一个包含文件名的JSON响应。
此函数接收上传的文件,打印文件信息,然后将文件保存到"imgs"文件夹下。
"""
# 打印上传文件的信息
print(files)
# 拼接文件保存路径
for file in files:
path = os.path.join("imgs", file.filename)
# 以二进制写模式打开(或创建)目标文件
with open(path, "wb") as f:
# 遍历上传文件的每一行,写入到目标文件
for line in file.file:
f.write(line)
# 返回上传文件的文件名
return {
"files": [file.filename for file in files]
}
UploadFile
和 bytes
在 FastAPI 中用于处理文件上传,但它们有显著的区别:
UploadFile
:- 功能:提供对上传文件的更高层次的抽象,包括文件的元数据(如文件名和 MIME 类型)和文件内容的异步读取。
- 用法:适合处理大文件,因为它支持流式读取,不需要一次性将整个文件加载到内存中。
- 示例:
await file.read()
读取文件内容。
bytes
:- 功能:直接处理文件的二进制内容。上传的文件内容会一次性加载到内存中,适合处理小文件。
- 用法:更简单,但不适合大文件的上传和处理,因为内存消耗较大。
- 示例:
files = List[bytes]
。
Request
# 创建一个API路由器实例,用于处理API请求
app05 = APIRouter()
# 定义一个处理POST请求的端点:/items
# 使用路径操作装饰器post,指定URL路径为"/items"
@app05.post("/items")
# 定义异步函数items,接收一个Request类型的参数request
async def items(request: Request):
# 打印请求的URL
print("URL", request.url)
# 打印发起请求的客户端IP地址
print("IP", request.client.host)
# 打印请求的用户代理(User-Agent)
print("agent", request.headers["user-agent"])
# 打印请求携带的Cookies
print("cookies",request.cookies)
# 返回包含URL、IP、用户代理和Cookies信息的字典
return {
"url": request.url,
"ip": request.client.host,
"agent": request.headers["user-agent"],
"cookies": request.cookies
}
静态文件请求
# 将"/static"路径挂载到应用程序,用于提供静态文件服务
# 这里的StaticFiles是一个FastAPI框架中的类,它用于处理静态文件的HTTP请求
# 参数directory指定静态文件所在的目录,本例中为"statics"目录,指定后可以使用 /static/css/test.css访问静态文件
app.mount("/static",StaticFiles(directory="statics"))
响应模型参数
response_model
是 FastAPI 中一个重要的功能,用于定义 API 路由的响应数据模型。
# 定义用户输入模型,用于用户数据的接收
class UserIn(BaseModel):
# 用户名,必填
username: str
# 密码,必填
password: str
# 邮箱,必填,使用EmailStr类型确保邮箱格式
email: EmailStr
# 完整姓名,可选
full_name: Union[str, None] = None
# 定义用户输出模型,用于展示用户信息
class UserOut(BaseModel):
# 用户名,必填
username: str
# 邮箱,必填,使用EmailStr类型确保邮箱格式
email: EmailStr
# 完整姓名,可选
full_name: Union[str, None] = None
# 用户登录接口,接收UserIn类型数据,返回UserOut类型数据
@app06.post("/user/login", response_model=UserOut)
def create_user(user: UserIn):
# 直接返回接收到的用户数据,实际项目中会进行复杂的认证逻辑
return user
response_model
是 FastAPI 中的一个参数,专用于指定 API 响应的模型。除了基本的 response_model
之外,FastAPI 还提供了一些其他参数来控制响应的行为,具体包括:
response_model_exclude_unset
- 作用:如果设置为
True
,仅在响应中包括模型中显式设置的字段,而不包括默认值的字段。 - 用法:适用于只希望返回用户设置过的字段,而不返回默认值的场景。
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: int):
return {"name": "Item Name", "price": 10.5}
response_model_exclude_defaults
- 作用:如果设置为
True
,响应中将排除具有默认值的字段。 - 用法:用于只返回已修改的字段,而忽略那些未被修改的默认字段。
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_defaults=True)
async def read_item(item_id: int):
return {"name": "Item Name", "price": 10.5}
response_model_exclude
- 作用:指定一个字段名列表,这些字段将被从响应中排除。
- 用法:用于排除某些字段的响应数据,允许更精细的控制。
@app.get("/items/{item_id}", response_model=Item, response_model_exclude={"tax"})
async def read_item(item_id: int):
return {"name": "Item Name", "description": "Item Description", "price": 10.5, "tax": 1.5}
response_model_include
- 作用:指定一个字段名列表,仅包括这些字段在响应中。
- 用法:用于只返回特定的字段,而忽略其他字段。
@app.get("/items/{item_id}", response_model=Item, response_model_include={"name", "price"})
async def read_item(item_id: int):
return {"name": "Item Name", "description": "Item Description", "price": 10.5, "tax": 1.5}
这些参数使得 response_model
更加灵活,允许开发者根据需要定制响应的内容和格式。
async
和 await
简介
在 FastAPI 中,async
和 await
用于处理异步操作,以提高应用程序的性能和响应速度。了解它们的区别可以帮助你更有效地使用 FastAPI 来构建高性能的应用程序。
async def
: 这是定义一个异步函数的关键字。异步函数(也称为协程)允许你在函数内部使用await
关键字来等待其他协程完成。它们在 I/O 密集型任务中尤其有用,如处理网络请求或数据库操作。await
: 这是用于暂停协程的执行,直到另一个协程完成。这使得协程可以在等待期间释放控制权,从而允许其他协程继续运行。await
只能在异步函数内部使用。
同步 vs 异步
-
同步方法 (
def
方法):- 阻塞: 在处理某些操作时,当前请求会被阻塞,直到操作完成。
- 简单: 代码结构简单,但在处理大量并发请求时可能会导致性能瓶颈。
@app.get("/sync-endpoint") def sync_endpoint(): result = some_blocking_io_operation() return {"result": result}
-
异步方法 (
async def
方法):- 非阻塞: 使用
await
等待 I/O 操作,而不会阻塞整个请求处理过程。这样可以同时处理更多的请求,提高应用程序的吞吐量。 - 复杂: 代码结构稍微复杂一些,需要注意异步操作和异常处理。
@app.get("/async-endpoint") async def async_endpoint(): result = await some_non_blocking_io_operation() return {"result": result}
- 非阻塞: 使用
异步的优点
- 高性能: 异步处理允许应用程序在等待 I/O 操作时继续处理其他任务,从而提高整体性能。
- 更好地处理高并发: 在高并发场景下,异步方法可以更有效地管理资源,提高响应速度。
何时使用异步
- I/O 密集型操作: 当你的应用程序涉及到大量的网络请求、文件操作或数据库访问时,使用异步方法可以显著提高性能。
- 高并发: 如果你的应用程序需要处理大量并发请求,异步编程可以帮助你更好地利用系统资源。
jinja2模板
Jinja2 是一个用于 Python 的模板引擎,它的作用是帮助生成动态 HTML 内容。它允许将逻辑从代码中分离出来,将数据嵌入到 HTML 模板中,从而创建动态网页。
1. 变量输出
功能说明: 变量输出是 Jinja2 的基础功能之一,它允许你在模板中插入并显示动态数据。通过 {{ ... }}
语法,你可以直接引用传递给模板的变量,并将其渲染为 HTML。这使得模板能够动态展示数据内容,而不是硬编码静态文本。
示例:
<p>Hello, {{ user.name }}!</p>
在这个示例中,{{ user.name }}
语法用于插入 user
对象的 name
属性值。当模板被渲染时,这个表达式会被替换成实际的 name
值,例如 "Alice"
,最终输出 <p>Hello, Alice!</p>
。
2. 控制结构
功能说明: Jinja2 提供了强大的控制结构功能,使得模板能够执行逻辑判断和重复操作。通过 {% ... %}
语法,你可以实现条件判断(if
语句)和循环遍历(for
语句)。这些功能允许你根据不同条件动态地生成 HTML 内容或对数据集合进行迭代处理。
- 条件语句:
{% if ... %}
语法允许根据变量的值执行不同的代码块。例如,可以根据用户是否登录来显示不同的内容。 - 循环语句:
{% for ... %}
语法用于遍历列表或字典中的每一个元素,可以生成一组重复的 HTML 元素,如列表项或表格行。
示例:
条件语句:
{% if user.is_authenticated %}
<p>Welcome, {{ user.name }}!</p>
{% else %}
<p>Please log in.</p>
{% endif %}
如果 user.is_authenticated
为 True
,模板会显示欢迎信息;否则,会提示用户登录。
循环语句:
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% else %}
<li>No items found.</li>
{% endfor %}
</ul>
这个示例遍历 items
列表并生成每个项的列表项。如果列表为空,则显示 “No items found” 的提示。
3. 过滤器
功能说明: 过滤器允许你在渲染模板时对变量进行处理和格式化。通过 |
符号应用过滤器,可以在输出数据之前对其进行转换,例如改变文本的大小写、格式化日期等。过滤器可以简化模板中的数据处理过程,使得模板更简洁、逻辑更清晰。
示例:
基本用法:
<p>{{ name|capitalize }}</p>
{{ name|capitalize }}
会将 name
的值转换为首字母大写,例如 "john"
变成 "John"
。
其他常用过滤器:
<p>{{ name|lower }}</p> <!-- 将文本转换为小写 -->
<p>{{ name|upper }}</p> <!-- 将文本转换为大写 -->
<p>{{ list|length }}</p> <!-- 获取列表的长度 -->
这些过滤器用于对输出数据进行常见的转换和计算。
4. 宏
功能说明: 宏是 Jinja2 提供的一种机制,用于创建可重用的模板片段。类似于编程中的函数,宏可以封装模板代码,使其在多个地方复用,从而避免代码重复。宏可以接收参数,并在调用时根据参数生成不同的内容。
示例:
定义宏:
{% macro input(name, value='', type='text') %}
<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}
这个宏 input
定义了一个 HTML 输入框,并允许通过参数设置输入框的 name
、value
和 type
属性。
使用宏:
{{ input('username') }}
在模板中调用宏 input('username')
会生成一个 <input>
元素,name
属性为 username
,其他属性使用宏的默认值。
5. 模板继承
功能说明: 模板继承是一种强大的机制,允许你创建一个基础模板(父模板),并在子模板中扩展或重写父模板的内容。通过定义块(block
)区域,父模板可以提供一个结构化的框架,而子模板则可以在特定区域插入或替换内容。这种方式促进了模板代码的重用和结构的组织。
示例:
父模板:
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Default Title{% endblock %}</title>
</head>
<body>
<header>
<h1>{% block header %}Header{% endblock %}</h1>
</header>
<main>
{% block content %}Default content{% endblock %}
</main>
</body>
</html>
在父模板中定义了 title
、header
和 content
三个块,这些块可以在子模板中被重写。
子模板:
{% extends "base.html" %}
{% block title %}My Page Title{% endblock %}
{% block content %}
<p>This is the content of my page.</p>
{% endblock %}
子模板继承了 base.html
的结构,并重写了 title
和 content
块的内容,从而定制化页面内容。
6. 注释
功能说明: 注释功能允许在模板中添加注释文本,这些文本不会出现在最终的渲染结果中。注释用于解释代码的意图,提供额外的信息或提醒,帮助维护和理解模板代码。
示例:
{# This is a comment #}
注释在模板中以 {# ... #}
语法包围,这些注释会在渲染过程中被忽略,不会出现在生成的 HTML 中。
7. 测试和逻辑
功能说明: Jinja2 提供了测试和逻辑功能,使得模板可以进行复杂的数据验证和条件判断。你可以使用 is
运算符测试变量的类型或存在性,使用逻辑表达式执行更复杂的判断。这种功能使得模板能够动态处理数据,并根据不同情况生成相应的内容。
示例:
测试变量:
{% if user is defined %}
<p>User is defined.</p>
{% endif %}
{% if user is defined %}
语法检查 user
变量是否被定义,如果是,则显示相关内容。
逻辑表达式:
{% if user.age > 18 %}
<p>Adult</p>
{% else %}
<p>Not an adult</p>
{% endif %}
这个例子使用逻辑表达式检查 user.age
是否大于 18,根据结果显示不同的内容。
案例:
main.py
import uvicorn
from fastapi import FastAPI,Request
from fastapi.templating import Jinja2Templates
# 创建一个FastAPI应用实例
app = FastAPI()
templates = Jinja2Templates(directory="templates")
@app.get("/index")
def index(request:Request):
name = "root"
age = 18
books = ["python", "java", "c++"]
infos = {
"name": "root",
"age": 18,
"addr": "北京"
}
return templates.TemplateResponse(
"index.html",
{
"request": request,
"user": name,
"age": age,
"books": books,
"infos": infos
},
)
if __name__ == "__main__":
uvicorn.run("main:app", host="127.0.0.1", port=8888,reload = True)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p> 用户名:{{ user }}</p>
<p> 年龄:{{ age }}</p>
<ul>
{% for book in books %}
<li>{{ book }}</li>
{% endfor %}
</ul>
</body>
</html>
目录结构
输出:
ORM操作
ORM操作以SQLLite为例
SQLite 是一个开源的关系型数据库管理系统(RDBMS),具有以下几个显著特点:
- 轻量级和嵌入式
- 单文件存储:SQLite 的数据库以单一的文件形式存储所有数据,包括表、索引和事务日志。这使得 SQLite 易于部署和管理。
- 无服务器架构:SQLite 不需要一个独立的服务器进程来运行。它直接与应用程序进程一起运行,适合嵌入到应用程序中。
- 自包含
- 独立性:SQLite 不依赖于外部库或服务。它包括了完整的数据库引擎和所需的功能,所有的代码和数据都在一个文件中。
- SQL 支持
- 标准 SQL:SQLite 支持大部分标准 SQL 语法,包括数据定义语言(DDL)、数据操作语言(DML)和数据控制语言(DCL)。
- 事务支持:SQLite 支持 ACID(原子性、一致性、隔离性和持久性)事务,以确保数据的完整性。
- **原子性(Atomicity)**:确保事务内的操作要么全部完成,要么全部不完成,不会出现部分完成的情况。
- **一致性(Consistency)**:确保数据库从一个一致的状态转变到另一个一致的状态。
- **隔离性(Isolation)**:允许多个并发事务同时进行,而不会相互干扰。
- **持久性(Durability)**:一旦事务提交,其对数据库的更改就是永久的,即使系统崩溃也不会丢失。
- 跨平台
- 多平台支持:SQLite 支持多种操作系统,包括 Windows、macOS、Linux、iOS 和 Android。这使得它在不同环境中具有很好的兼容性。
- 性能
- 高效性:SQLite 设计为高效的读取性能和适度的写入性能,尤其适合轻量级或中等规模的应用程序。
- 零配置
- 无需配置:SQLite 不需要进行复杂的配置或管理。你只需将 SQLite 数据库文件放在适当的位置,就可以开始使用。
- 扩展性
- 扩展功能:SQLite 允许通过加载动态链接库(DLL)来扩展功能,包括自定义函数、虚拟表等。
- 广泛应用
- 使用场景:SQLite 被广泛应用于移动应用(如 iOS 和 Android)、桌面软件、嵌入式设备(如路由器、家电)以及浏览器(如 Firefox 和 Chrome)等各种场景。
- 社区和支持
- 开源项目:SQLite 是一个开源项目,代码和文档可以自由获取。社区活跃,提供广泛的支持和资源。
- 事务日志
- WAL 和 DELETE 模式:SQLite 提供两种事务日志模式,WAL(Write-Ahead Logging)和 DELETE,这两种模式具有不同的性能和存储特性。
SQLite 的设计理念是提供一个轻便、快速、可靠的数据库解决方案,非常适合需要在本地或嵌入式环境中存储和管理数据的应用。
1. 使用 SQLAlchemy
SQLAlchemy 是一个功能强大的数据库工具包和 ORM 库,支持同步和异步操作。
示例:FastAPI 与 SQLAlchemy 的集成
这是一个结合 FastAPI 和 SQLAlchemy 进行数据库操作的简单示例:
-
定义模型:
from sqlalchemy import create_engine, Column, Integer, String from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker DATABASE_URL = "sqlite:///./test.db" engine = create_engine(DATABASE_URL, echo=True) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() class Item(Base): __tablename__ = 'items' id = Column(Integer, primary_key=True, index=True) name = Column(String, index=True)
-
创建 FastAPI 应用和路由:
from fastapi import FastAPI, Depends from sqlalchemy.orm import Session from sqlalchemy.future import select app = FastAPI() def get_db(): db = SessionLocal() try: yield db finally: db.close() @app.get("/items/{item_id}") async def read_item(item_id: int, db: Session = Depends(get_db)): result = await db.execute(select(Item).filter(Item.id == item_id)) item = result.scalar_one_or_none() return item
SQLAlchemy 提供了两种主要的接口来与数据库交互:ORM(对象关系映射) 和 Core(核心)。它们各自有不同的使用场景和特点。
SQLAlchemy 的ORM与Core
ORM(对象关系映射)
特点
- 对象与数据库表的映射: ORM 通过将 Python 对象映射到数据库表,使得数据库操作可以通过对象方法和属性来完成。
- 高级抽象: 提供了更高层次的抽象,简化了数据库操作。
示例
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
age = Column(Integer)
# 设置数据库连接
engine = create_engine('sqlite:///example.db')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
# 创建一个新的用户
new_user = User(name='Alice', age=30)
session.add(new_user)
session.commit()
优缺点
- 优点:
- 通过对象操作简化了数据库操作。
- 自动处理复杂的关联关系和查询。
- 缺点:
- 性能开销相对较高,特别是在处理复杂查询时。
- 可能对数据库结构变化不够灵活。
Core(核心)
特点
- 更接近 SQL: Core 提供了对 SQL 的更直接控制,使用 SQL 表达式语言构建查询。
- 更灵活: 允许开发者在更底层控制 SQL 生成和执行。
示例
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData
engine = create_engine('sqlite:///example.db')
metadata = MetaData()
users = Table('users', metadata,
Column('id', Integer, primary_key=True),
Column('name', String),
Column('age', Integer)
)
metadata.create_all(engine)
# 插入数据
with engine.connect() as conn:
conn.execute(users.insert().values(name='Bob', age=25))
优缺点
- 优点:
- 提供了对 SQL 查询的更细粒度控制。
- 性能可能更好,特别是在执行复杂查询时。
- 缺点:
- 需要手动管理 SQL 和表结构。
- 可能需要编写更多的代码来实现相同的功能。
总结
- ORM 适合需要与数据库交互的对象导向应用,提供了更高层次的抽象和简化的操作。
- Core 适合需要精确控制 SQL 查询或执行复杂数据库操作的场景,提供了更直接的 SQL 接口和更好的性能。
中间件与CORS
在 FastAPI 中,中间件(middleware)是一个处理请求和响应过程的组件,它位于请求到达路由处理函数和响应返回客户端之间。中间件允许你在处理请求之前和处理响应之后执行一些通用的操作。它们可以用来处理各种任务,比如请求日志、请求修改、响应修改、错误处理、跨域资源共享(CORS)设置等。
中间件的用途
- 请求和响应处理:
- 中间件可以在请求到达路由处理函数之前进行预处理,比如验证请求头或添加日志。
- 它们也可以在响应发送给客户端之前进行处理,比如修改响应内容或添加自定义的响应头。
- 错误处理:
- 捕捉和处理应用程序中的异常,生成统一的错误响应格式。
- 身份验证和授权:
- 在请求到达路由之前,进行身份验证和授权检查,确保用户有权访问请求的资源。
- 日志记录:
- 记录每个请求的详细信息,比如请求路径、方法、响应时间等,以便于后续分析和调试。
- 跨域资源共享(CORS):
- 配置允许哪些来源可以访问你的 API,通过添加适当的 CORS 头部来控制跨域请求。
- 请求和响应转换:
- 在请求到达路由处理函数之前或在响应发送之前,转换请求体或响应体的格式。
在 FastAPI 中使用中间件
FastAPI 提供了一种简单的方法来添加中间件。你可以使用 Starlette
库中的中间件类,因为 FastAPI 是构建在 Starlette
之上的。以下是如何在 FastAPI 中使用中间件的一个示例:
示例:中间件
比如有两个中间件,会在访问路由处理函数前访问。中间件执行顺序是:m1 -> m2 -> 路由处理函数,(发起请求时访问的中间件的顺序是越在代码文件下方的中间件越先被访问)
INFO: 127.0.0.1:53571 - "GET /docs HTTP/1.1" 200 OK
m1 request
m2 request
m2 response
m1 response
import uvicorn
from fastapi import FastAPI, Request, Response
import time
app = FastAPI()
# 中间件执行顺序:m1 -> m2 -> 路由处理函数
# 定义一个HTTP请求的中间件m2
@app.middleware("http")
async def m2(request: Request, call_next):
# 处理请求前打印标识信息
print("m2 request")
# 调用下一个中间件或最终的应用处理器/控制器
response = await call_next(request)
# 处理响应前修改响应头,添加作者信息
response.headers["author"] = "hzp"
# 处理响应前打印标识信息
print("m2 response")
# 返回响应
return response
# 定义一个HTTP请求的中间件m1
@app.middleware("http")
async def m1(request: Request, call_next):
# 处理请求前打印标识信息
print("m1 request")
# # 检查请求客户端的主机是否在黑名单中
# if request.client.host in ["127.0.0.1"]:
# # 如果客户端主机在黑名单中,返回403禁止访问响应
# return Response(status_code=403,content="Blacklist!")
# 检查请求的URL路径是否为特定值
# if request.url.path in ["/user"]:
# # 如果路径匹配,则返回一个包含特定内容和状态码的响应
# return Response(status_code=403, content="/user forbidden!")
# 调用下一个中间件或最终的应用处理器/控制器
# 记录请求开始时间
start = time.time()
# 调用下一个请求处理函数,等待其执行结果
# 此处演示了异步调用,以提高程序的执行效率
response = await call_next(request)
# 记录请求结束时间,以便后续计算请求处理时间
end = time.time()
response.headers["ProcessTimer"] = str(end - start)
# 处理响应前打印标识信息
print("m1 response")
# 返回响应
return response
@app.get("/hello")
async def read_root():
time.sleep(2) # 模拟请求处理时间,睡眠两秒钟
return {"Hello": "World"}
if __name__ == "__main__":
uvicorn.run("main:app", host="127.0.0.1", port=8898, reload=True)
示例:CORS 中间件
跨域请求指的是在一个网站(或应用)中,向另一个不同域名、协议或端口的服务器发出的网络请求。由于浏览器的安全策略,通常会限制这种请求,防止潜在的恶意行为。这种安全机制叫做“同源策略”。跨域请求需要服务器设置特定的头部,如 Access-Control-Allow-Origin
,来允许来自其他域的请求访问其资源。
FastAPI 内置了 CORS 中间件,使用起来也非常简单:
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
app = FastAPI()
# 添加 CORS 中间件
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 允许所有来源
allow_credentials=True,
allow_methods=["*"], # 允许所有方法
allow_headers=["*"], # 允许所有头部
)
@app.get("/")
async def read_root():
return {"Hello": "World"}
return Response(status_code=403, content="/user forbidden!")
# 调用下一个中间件或最终的应用处理器/控制器
# 记录请求开始时间
start = time.time()
# 调用下一个请求处理函数,等待其执行结果
# 此处演示了异步调用,以提高程序的执行效率
response = await call_next(request)
# 记录请求结束时间,以便后续计算请求处理时间
end = time.time()
response.headers["ProcessTimer"] = str(end - start)
# 处理响应前打印标识信息
print("m1 response")
# 返回响应
return response
@app.get(“/hello”)
async def read_root():
time.sleep(2) # 模拟请求处理时间,睡眠两秒钟
return {“Hello”: “World”}
if name == “main”:
uvicorn.run(“main:app”, host=“127.0.0.1”, port=8898, reload=True)
#### 示例:CORS 中间件
跨域请求指的是在一个网站(或应用)中,向另一个不同域名、协议或端口的服务器发出的网络请求。由于浏览器的安全策略,通常会限制这种请求,防止潜在的恶意行为。这种安全机制叫做“同源策略”。跨域请求需要服务器设置特定的头部,如 `Access-Control-Allow-Origin`,来允许来自其他域的请求访问其资源。
FastAPI 内置了 CORS 中间件,使用起来也非常简单:
```python
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
app = FastAPI()
# 添加 CORS 中间件
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 允许所有来源
allow_credentials=True,
allow_methods=["*"], # 允许所有方法
allow_headers=["*"], # 允许所有头部
)
@app.get("/")
async def read_root():
return {"Hello": "World"}