第一章 FastApi 简介
1.1 什么是FastApi
1.1.1 定义与特点
1. 定义
FastAPI 是一个用于构建 Web API 的现代、快速(高性能)的 Python 框架,它基于标准的 Python 类型提示,使用了 Starlette 和 Pydantic 库。简单来说,它就像是一个高效的建筑工人,能帮助开发者快速搭建出功能强大的 Web API 🏗️。
2. 特点
- 快速:得益于异步编程和优化的代码,FastAPI 的性能非常出色,与 NodeJS 和 Go 相当。它就像一辆高性能跑车,能在短时间内完成大量的任务🚀。
- 易于使用:使用 Python 的类型提示,使得代码具有良好的可读性和可维护性,开发者可以快速上手。就像搭积木一样,按照规则轻松组合就能完成功能搭建🧩。
- 高效开发:自动生成交互式 API 文档(如 Swagger UI 和 ReDoc),方便测试和调试。这就好比给开发者配备了一个智能的助手,能随时帮助检查和调试代码👨💻。
- 类型检查:利用 Pydantic 进行数据验证,确保数据的准确性和完整性。就像一个严格的质量检查员,不放过任何一个不符合要求的数据🎯。
- 异步支持:支持异步编程,能处理高并发请求,提高应用的响应速度。就像一个多面手,能同时处理多个任务而不混乱👐。
1.1.2 与其他Web框架的对比
1. 与 Flask 的对比
- 性能:Flask 是一个轻量级的 Web 框架,性能相对较低;而 FastAPI 采用异步编程,性能更高。可以把 Flask 比作一辆普通的家用轿车,而 FastAPI 则是一辆赛车🏎️。
- 开发效率:Flask 需要手动编写很多代码来实现一些功能,如数据验证和 API 文档生成;FastAPI 则可以自动完成这些任务,开发效率更高。就像用传统工具手工制作东西和用先进的自动化机器制作东西的区别🧰。
- 类型检查:Flask 没有内置的类型检查机制;FastAPI 利用 Python 的类型提示进行类型检查,代码更健壮。这就好比一个是没有质量检测的工厂,一个是有严格质量检测的工厂🏭。
2. 与 Django 的对比
- 灵活性:Django 是一个功能齐全的 Web 框架,提供了很多内置的功能,但相对来说比较重,灵活性较差;FastAPI 则更加轻量级,开发者可以根据需要选择合适的组件,灵活性更高。可以把 Django 比作一个大型的综合商场,而 FastAPI 则是一个小型的精品店🛍️。
- 性能:Django 的性能相对较低,尤其是在处理高并发请求时;FastAPI 的性能更高,更适合处理高并发的场景。就像一个大型商场在节假日可能会出现拥挤的情况,而精品店则能更高效地服务顾客👨👩👧👦。
- 开发场景:Django 适合开发大型的 Web 应用,如电商网站、博客等;FastAPI 更适合开发 Web API,尤其是对性能要求较高的场景。这就好比不同的工具适用于不同的工作,锤子适合钉钉子,而螺丝刀适合拧螺丝🔨。
1.2 FastApi的应用场景
1.2.1 构建RESTful API
FastAPI 非常适合构建 RESTful API。RESTful API 是一种基于 HTTP 协议的 API 设计风格,广泛应用于前后端分离的开发中。FastAPI 可以利用其快速、高效的特点,为前端应用提供稳定、可靠的接口服务。例如,一个电商网站的后端可以使用 FastAPI 构建 RESTful API,为前端提供商品信息、订单管理等功能的接口。就像一个桥梁,连接着前端和后端,让它们能够顺畅地进行数据交互🌉。
1.2.2 数据处理与服务
在数据处理和服务方面,FastAPI 也能发挥重要作用。它可以与数据库(如 MySQL、PostgreSQL 等)进行交互,实现数据的增删改查操作。同时,它还可以对数据进行处理和分析,如数据清洗、统计分析等。例如,一个数据分析平台可以使用 FastAPI 构建数据处理服务,对用户上传的数据进行处理和分析,并将结果返回给用户。就像一个数据加工厂,能把原始数据加工成有价值的信息🧮。
1.2.3 实时应用
FastAPI 支持 WebSocket 协议,适合开发实时应用,如实时聊天、实时数据监控等。WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,能实现实时的数据传输。例如,一个在线聊天应用可以使用 FastAPI 构建后端服务,通过 WebSocket 协议实现用户之间的实时消息通信。就像一个即时通讯管道,让信息能够实时传递📨。
第二章 环境搭建与基础配置
2.1 安装FastApi
2.1.1 使用pip安装
在Python开发中,pip
是一个强大的包管理工具,我们可以使用它轻松地安装FastApi。以下是具体步骤:
- 打开命令行工具:在Windows系统中可以使用命令提示符(CMD)或者PowerShell;在Mac和Linux系统中可以使用终端。
- 检查Python和pip是否安装:输入以下命令来检查Python和pip的版本。
如果显示了Python和pip的版本信息,说明已经安装成功👍。如果没有安装,需要先安装Python,安装完成后pip会自动安装。python --version pip --version
- 安装FastApi:在命令行中输入以下命令来安装FastApi。
这个命令会从Python Package Index(PyPI)下载并安装FastApi及其依赖项。安装过程可能需要一些时间,取决于你的网络速度。pip install fastapi
- 安装Uvicorn:FastApi是一个基于ASGI的Web框架,需要一个ASGI服务器来运行,Uvicorn是一个常用的ASGI服务器,我们可以使用以下命令安装它。
pip install uvicorn
2.1.2 虚拟环境的创建与使用
虚拟环境可以为每个项目创建独立的Python环境,避免不同项目之间的依赖冲突。以下是创建和使用虚拟环境的步骤:
- 创建虚拟环境:使用Python自带的
venv
模块来创建虚拟环境。在命令行中输入以下命令。
这里的python -m venv myenv
myenv
是虚拟环境的名称,你可以根据自己的喜好来命名。执行这个命令后,会在当前目录下创建一个名为myenv
的文件夹,里面包含了虚拟环境的相关文件。 - 激活虚拟环境:
- Windows系统:在命令行中输入以下命令来激活虚拟环境。
myenv\Scripts\activate
- Mac和Linux系统:在终端中输入以下命令来激活虚拟环境。
source myenv/bin/activate
- Windows系统:在命令行中输入以下命令来激活虚拟环境。
- 在虚拟环境中安装FastApi和Uvicorn:激活虚拟环境后,按照前面介绍的使用
pip
安装FastApi和Uvicorn的步骤进行安装。这样安装的包只会存在于当前虚拟环境中,不会影响其他项目。 - 退出虚拟环境:当你完成项目开发后,可以在命令行中输入以下命令来退出虚拟环境。
deactivate
2.2 第一个FastApi应用
2.2.1 编写简单的API示例
现在我们来编写一个简单的FastApi应用。创建一个名为main.py
的文件,在文件中输入以下代码:
from fastapi import FastAPI
# 创建FastApi应用实例
app = FastAPI()
# 定义一个路由,当访问根路径时返回一个JSON响应
@app.get("/")
def read_root():
return {"Hello": "World"}
这段代码的解释如下:
from fastapi import FastAPI
:导入FastApi类。app = FastAPI()
:创建一个FastApi应用实例。@app.get("/")
:使用装饰器定义一个GET请求的路由,当访问根路径/
时会执行下面的函数。def read_root():
:定义一个函数,返回一个包含Hello
和World
的JSON对象。
2.2.2 运行与测试应用
-
运行应用:在命令行中输入以下命令来运行FastApi应用。
uvicorn main:app --reload
main
:表示文件名main.py
。app
:表示main.py
文件中创建的FastApi应用实例。--reload
:开启热重载功能,当代码发生变化时,服务器会自动重启。
-
测试应用:打开浏览器,访问
http://127.0.0.1:8000
,你会看到浏览器显示{"Hello": "World"}
,这说明你的FastApi应用已经成功运行🎉。
你还可以访问http://127.0.0.1:8000/docs
,这里会显示FastApi自动生成的交互式API文档,你可以在文档中测试你的API接口。
2.3 项目结构与配置
2.3.1 推荐的项目目录结构
一个良好的项目目录结构可以提高代码的可维护性和可扩展性。以下是一个推荐的FastApi项目目录结构:
project/
│
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── routers/
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ ├── models/
│ │ ├── __init__.py
│ │ └── item.py
│ └── schemas/
│ ├── __init__.py
│ └── item.py
├── tests/
│ ├── __init__.py
│ └── test_main.py
├── config/
│ └── settings.py
└── requirements.txt
app/
:存放应用的核心代码。main.py
:应用的入口文件。routers/
:存放路由文件,将不同的路由逻辑分开管理。models/
:存放数据库模型。schemas/
:存放数据验证和序列化的模型。
tests/
:存放测试代码。config/
:存放配置文件。requirements.txt
:列出项目依赖的所有包。
2.3.2 配置文件的使用
配置文件可以将一些常量和配置信息集中管理,方便修改和维护。以下是一个简单的配置文件示例:
- 创建配置文件:在
config
目录下创建一个名为settings.py
的文件,输入以下代码:
from pydantic import BaseSettings
class Settings(BaseSettings):
APP_NAME: str = "My FastApi App"
DEBUG: bool = False
DATABASE_URL: str = "sqlite:///./test.db"
class Config:
env_file = ".env"
settings = Settings()
这段代码使用pydantic
库来定义配置类,APP_NAME
、DEBUG
和DATABASE_URL
是配置项,你可以根据需要添加更多的配置项。env_file = ".env"
表示可以从.env
文件中加载环境变量。
- 在应用中使用配置文件:在
main.py
文件中导入配置文件并使用。
from fastapi import FastAPI
from config.settings import settings
app = FastAPI(title=settings.APP_NAME, debug=settings.DEBUG)
@app.get("/")
def read_root():
return {"Hello": "World"}
这样,你就可以在应用中方便地使用配置文件中的配置项了。同时,你可以在.env
文件中设置环境变量来覆盖配置文件中的默认值。例如,在.env
文件中添加以下内容:
APP_NAME="My New FastApi App"
DEBUG=true
这样,应用启动时会使用.env
文件中的配置值。
第三章 路由与请求处理
3.1 路由基础
3.1.1 定义路由
在 Web 开发中,路由就像是交通规则,它规定了客户端的请求应该由哪个函数或处理程序来响应😃。定义路由就是将特定的 URL 路径与对应的处理函数进行绑定。
比如在 FastAPI 这个 Python 的 Web 框架中,我们可以这样定义路由:
from fastapi import FastAPI
app = FastAPI()
# 定义一个根路径的路由
@app.get("/")
def read_root():
return {"Hello": "World"}
在这个例子中,@app.get("/")
就是定义了一个处理 GET 请求的路由,当客户端访问根路径(也就是 http://127.0.0.1:8000/
)时,就会调用 read_root
函数并返回 {"Hello": "World"}
这个 JSON 数据🤗。
3.1.2 路由方法(GET、POST等)
不同的 HTTP 请求方法代表了不同的操作意图,常见的路由方法有:
- GET:用于从服务器获取资源,就像是去图书馆借书📚。例如,在一个博客网站中,用户通过 GET 请求获取文章列表或某一篇具体的文章。
@app.get("/articles")
def get_articles():
# 模拟返回文章列表
return [{"title": "Article 1"}, {"title": "Article 2"}]
- POST:用于向服务器提交数据,就像是给图书馆捐书📖。比如用户在注册新账号时,会通过 POST 请求将注册信息发送给服务器。
@app.post("/register")
def register_user(user: dict):
# 处理用户注册逻辑
return {"message": "User registered successfully"}
- PUT:用于更新服务器上的资源,类似于修改图书馆里书籍的信息。
- DELETE:用于删除服务器上的资源,就像从图书馆移除一本不再需要的书。
3.1.3 路由参数
路由参数允许我们在 URL 中传递动态数据。有两种常见的路由参数:
- 路径参数:在 URL 路径中直接包含参数。例如,我们要获取某一篇具体文章的信息,可以这样定义路由:
@app.get("/articles/{article_id}")
def get_article(article_id: int):
# 根据 article_id 获取文章信息
return {"article_id": article_id, "title": f"Article {article_id}"}
这里的 {article_id}
就是路径参数,当用户访问 /articles/1
时,article_id
的值就是 1。
- 查询参数:在 URL 的问号后面通过键值对的形式传递参数。比如,我们要对文章进行分页查询:
@app.get("/articles")
def get_articles(page: int = 1, limit: int = 10):
# 根据 page 和 limit 返回相应的文章列表
return [{"title": f"Article {i}"} for i in range((page - 1) * limit, page * limit)]
当用户访问 /articles?page=2&limit=5
时,page
的值就是 2,limit
的值就是 5。
3.2 请求体处理
3.2.1 使用 Pydantic 模型
Pydantic 是一个用于数据验证和序列化的 Python 库,在处理请求体时非常有用。我们可以定义一个 Pydantic 模型来描述请求体的数据结构,这样可以确保接收到的数据符合我们的预期😎。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
# 定义一个 Pydantic 模型
class User(BaseModel):
name: str
age: int
@app.post("/users")
def create_user(user: User):
# user 是一个 User 类型的对象
return {"name": user.name, "age": user.age}
在这个例子中,User
类就是一个 Pydantic 模型,它定义了请求体中应该包含 name
和 age
两个字段。当客户端发送 POST 请求时,FastAPI 会自动将请求体的数据转换为 User
对象,并进行数据验证。如果数据不符合模型的定义,会返回错误信息。
3.2.2 处理不同类型的请求体
请求体可以是不同类型的数据,常见的有 JSON、表单数据等。
- JSON 请求体:这是最常见的请求体类型,我们可以直接使用 Pydantic 模型来处理。就像上面的例子,客户端发送的请求体应该是一个 JSON 对象,例如:
{
"name": "John",
"age": 30
}
- 表单数据:当客户端通过表单提交数据时,我们可以使用
Form
来处理。
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login")
def login(username: str = Form(...), password: str = Form(...)):
# 处理登录逻辑
return {"message": "Login successful"}
这里的 Form(...)
表示这个参数是必需的。客户端发送的请求体应该是表单数据,类似于 HTML 表单提交的数据。
3.3 响应处理
3.3.1 返回不同类型的响应
服务器可以返回不同类型的响应,常见的有:
- JSON 响应:这是最常见的响应类型,用于返回结构化的数据。在 FastAPI 中,我们只需要返回一个 Python 字典,FastAPI 会自动将其转换为 JSON 格式。
@app.get("/data")
def get_data():
return {"key": "value"}
- HTML 响应:用于返回 HTML 页面。
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/page")
def get_page():
html_content = "<html><body><h1>Hello, World!</h1></body></html>"
return Response(content=html_content, media_type="text/html")
- 文件响应:用于返回文件,例如图片、视频等。
from fastapi import FastAPI, FileResponse
app = FastAPI()
@app.get("/image")
def get_image():
return FileResponse("path/to/image.jpg")
3.3.2 响应状态码与头信息
- 响应状态码:用于表示请求的处理结果,常见的状态码有 200 表示成功,404 表示未找到资源,500 表示服务器内部错误等。在 FastAPI 中,我们可以通过
status_code
参数来设置响应状态码。
from fastapi import FastAPI, status
app = FastAPI()
@app.post("/create", status_code=status.HTTP_201_CREATED)
def create_item():
return {"message": "Item created successfully"}
- 响应头信息:用于传递一些额外的信息,例如缓存控制、跨域请求等。在 FastAPI 中,我们可以通过
headers
参数来设置响应头信息。
from fastapi import FastAPI
app = FastAPI()
@app.get("/data")
def get_data():
headers = {"Cache-Control": "no-cache"}
return {"key": "value"}, headers
通过设置响应状态码和头信息,我们可以更好地控制服务器的响应行为,提高用户体验👍。
第四章 数据验证与序列化
4.1 Pydantic模型
4.1.1 模型定义与使用
1. 什么是Pydantic模型
Pydantic是一个用于数据验证和设置类型提示的Python库。Pydantic模型就像是数据的蓝图,它定义了数据应该遵循的结构和类型。通过定义模型,我们可以确保输入的数据符合特定的规则。
2. 如何定义一个简单的Pydantic模型
from pydantic import BaseModel
# 定义一个简单的用户模型
class User(BaseModel):
name: str
age: int
在这个例子中,我们定义了一个名为User
的模型,它有两个字段:name
(字符串类型)和age
(整数类型)。
3. 如何使用Pydantic模型
# 创建一个用户实例
user = User(name="Alice", age=25)
# 访问模型的属性
print(user.name) # 输出: Alice
print(user.age) # 输出: 25
我们可以通过传递符合模型定义的数据来创建模型实例,然后像访问普通Python对象的属性一样访问模型的字段。
4.1.2 数据验证规则
1. 基本的数据类型验证
Pydantic会自动验证输入的数据是否符合模型定义的类型。例如:
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
# 尝试创建一个不符合类型的实例
try:
user = User(name="Bob", age="not an integer")
except ValueError as e:
print(f"验证错误: {e}") # 输出验证错误信息
在这个例子中,由于age
字段应该是整数类型,但我们传入了一个字符串,Pydantic会抛出ValueError
异常。
2. 自定义验证规则
除了基本的类型验证,我们还可以使用@validator
装饰器来定义自定义的验证规则。例如:
from pydantic import BaseModel, validator
class User(BaseModel):
name: str
age: int
@validator('age')
def validate_age(cls, value):
if value < 0:
raise ValueError('年龄不能为负数')
return value
# 尝试创建一个年龄为负数的实例
try:
user = User(name="Charlie", age=-5)
except ValueError as e:
print(f"验证错误: {e}") # 输出: 验证错误: 年龄不能为负数
在这个例子中,我们定义了一个名为validate_age
的验证函数,用于验证age
字段的值是否为负数。
4.1.3 模型嵌套与继承
1. 模型嵌套
模型嵌套是指在一个模型中包含另一个模型。例如:
from pydantic import BaseModel
class Address(BaseModel):
street: str
city: str
zip_code: str
class User(BaseModel):
name: str
age: int
address: Address
# 创建一个包含嵌套模型的实例
address = Address(street="123 Main St", city="Anytown", zip_code="12345")
user = User(name="David", age=30, address=address)
print(user.address.street) # 输出: 123 Main St
在这个例子中,User
模型包含了一个Address
模型的实例。
2. 模型继承
模型继承允许我们创建一个新的模型,它继承了另一个模型的所有字段和验证规则。例如:
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
class Employee(Person):
employee_id: str
# 创建一个Employee实例
employee = Employee(name="Eve", age=35, employee_id="E123")
print(employee.name) # 输出: Eve
print(employee.employee_id) # 输出: E123
在这个例子中,Employee
模型继承了Person
模型的name
和age
字段,并添加了一个新的employee_id
字段。
4.2 数据序列化与反序列化
4.2.1 将数据转换为JSON
1. 什么是数据序列化
数据序列化是指将Python对象转换为一种可以存储或传输的格式,如JSON。Pydantic模型提供了方便的方法来将模型实例转换为JSON字符串。
2. 如何将Pydantic模型实例转换为JSON
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
user = User(name="Frank", age=40)
# 将模型实例转换为JSON字符串
json_data = user.json()
print(json_data) # 输出: {"name": "Frank", "age": 40}
在这个例子中,我们使用json()
方法将User
模型实例转换为JSON字符串。
4.2.2 从JSON数据创建对象
1. 什么是数据反序列化
数据反序列化是指将存储或传输的格式(如JSON)转换为Python对象。Pydantic模型提供了方法来从JSON数据创建模型实例。
2. 如何从JSON数据创建Pydantic模型实例
from pydantic import BaseModel
import json
class User(BaseModel):
name: str
age: int
json_data = '{"name": "Grace", "age": 45}'
# 从JSON数据创建模型实例
user = User.parse_raw(json_data)
print(user.name) # 输出: Grace
print(user.age) # 输出: 45
在这个例子中,我们使用parse_raw()
方法从JSON字符串创建User
模型实例。🎉
通过Pydantic模型和数据序列化/反序列化,我们可以更方便地处理和验证数据,确保数据的正确性和一致性。👏
第五章 依赖注入
5.1 依赖注入基础
5.1.1 什么是依赖注入
依赖注入(Dependency Injection,简称 DI)是一种设计模式,它是实现控制反转(Inversion of Control,简称 IoC)的一种方式。简单来说,依赖注入就是将对象所依赖的其他对象通过外部传递的方式提供给它,而不是让对象自己去创建这些依赖对象。
想象一下,你要开一家咖啡店☕。咖啡店需要咖啡豆、牛奶等原料才能制作咖啡。如果没有依赖注入,咖啡店就得自己去种植咖啡豆、养奶牛挤牛奶,这显然很麻烦。而有了依赖注入,就会有专门的供应商把咖啡豆和牛奶送到咖啡店,咖啡店只需要专注于制作咖啡就行啦。
在编程中,依赖注入可以让代码更加灵活、可测试和可维护。它将对象之间的依赖关系从代码中解耦出来,使得代码的各个部分可以独立开发、测试和修改。
5.1.2 简单依赖注入示例
下面我们用 Python 代码来演示一个简单的依赖注入示例。假设我们有一个 Printer
类,它依赖于一个 Message
类来获取要打印的消息。
# 定义 Message 类
class Message:
def get_message(self):
return "Hello, World!"
# 定义 Printer 类
class Printer:
def __init__(self, message):
self.message = message
def print_message(self):
print(self.message.get_message())
# 创建 Message 对象
message = Message()
# 创建 Printer 对象,并将 Message 对象作为依赖注入
printer = Printer(message)
# 调用打印方法
printer.print_message()
在这个示例中,Printer
类通过构造函数接收一个 Message
对象作为依赖。这样,Printer
类就不需要自己去创建 Message
对象,而是由外部提供,这就是简单的依赖注入😃。
5.2 复杂依赖注入场景
5.2.1 依赖链
在实际开发中,可能会出现依赖链的情况。也就是说,一个对象依赖于另一个对象,而这个对象又依赖于其他对象,形成一条依赖链。
还是以咖啡店为例,咖啡店依赖于咖啡豆供应商,而咖啡豆供应商又依赖于咖啡豆种植户。这就形成了一条依赖链:咖啡店 -> 咖啡豆供应商 -> 咖啡豆种植户。
在编程中,我们来看一个简单的 Python 示例:
# 定义咖啡豆种植户类
class CoffeeFarmer:
def grow_coffee(self):
return "Fresh coffee beans"
# 定义咖啡豆供应商类,依赖于咖啡豆种植户
class CoffeeSupplier:
def __init__(self, farmer):
self.farmer = farmer
def supply_coffee(self):
return self.farmer.grow_coffee()
# 定义咖啡店类,依赖于咖啡豆供应商
class CoffeeShop:
def __init__(self, supplier):
self.supplier = supplier
def make_coffee(self):
coffee_beans = self.supplier.supply_coffee()
return f"Making coffee with {coffee_beans}"
# 创建咖啡豆种植户对象
farmer = CoffeeFarmer()
# 创建咖啡豆供应商对象,并将咖啡豆种植户对象作为依赖注入
supplier = CoffeeSupplier(farmer)
# 创建咖啡店对象,并将咖啡豆供应商对象作为依赖注入
coffee_shop = CoffeeShop(supplier)
# 调用制作咖啡的方法
print(coffee_shop.make_coffee())
在这个示例中,CoffeeShop
依赖于 CoffeeSupplier
,而 CoffeeSupplier
又依赖于 CoffeeFarmer
,形成了一条依赖链。
5.2.2 全局依赖与子依赖
全局依赖是指在整个应用程序中都可以使用的依赖,而子依赖是指某个特定对象或模块所依赖的对象。
还是用咖啡店的例子,咖啡店所在的城市的水电供应可以看作是全局依赖,因为整个城市的咖啡店都依赖于水电供应。而咖啡店自己购买的咖啡机就是子依赖,它只属于这家咖啡店。
在编程中,我们可以通过不同的方式来管理全局依赖和子依赖。例如,使用全局变量来管理全局依赖,使用构造函数或方法参数来注入子依赖。
# 全局依赖:水电供应类
class UtilitySupply:
def provide_water_and_electricity(self):
return "Water and electricity are provided"
# 子依赖:咖啡机类
class CoffeeMachine:
def brew_coffee(self):
return "Brewing coffee"
# 咖啡店类,依赖于全局依赖和子依赖
class CoffeeShop:
def __init__(self, utility_supply, coffee_machine):
self.utility_supply = utility_supply
self.coffee_machine = coffee_machine
def operate(self):
utility = self.utility_supply.provide_water_and_electricity()
coffee = self.coffee_machine.brew_coffee()
return f"{utility}, and {coffee}"
# 创建全局依赖对象
utility = UtilitySupply()
# 创建子依赖对象
machine = CoffeeMachine()
# 创建咖啡店对象,并注入全局依赖和子依赖
coffee_shop = CoffeeShop(utility, machine)
# 调用咖啡店的操作方法
print(coffee_shop.operate())
在这个示例中,UtilitySupply
是全局依赖,CoffeeMachine
是子依赖,CoffeeShop
类通过构造函数接收这两个依赖。
5.3 依赖注入的使用场景
5.3.1 身份验证
在 Web 应用程序中,身份验证是一个常见的功能。不同的用户可能需要不同的身份验证方式,如用户名密码验证、OAuth 验证等。使用依赖注入可以方便地切换不同的身份验证方式。
# 定义身份验证接口
class Authentication:
def authenticate(self):
pass
# 实现用户名密码验证
class UsernamePasswordAuth(Authentication):
def authenticate(self):
return "Authenticated with username and password"
# 实现 OAuth 验证
class OAuthAuth(Authentication):
def authenticate(self):
return "Authenticated with OAuth"
# 定义用户服务类,依赖于身份验证
class UserService:
def __init__(self, auth):
self.auth = auth
def login(self):
return self.auth.authenticate()
# 使用用户名密码验证
username_auth = UsernamePasswordAuth()
user_service1 = UserService(username_auth)
print(user_service1.login())
# 使用 OAuth 验证
oauth_auth = OAuthAuth()
user_service2 = UserService(oauth_auth)
print(user_service2.login())
在这个示例中,UserService
类依赖于 Authentication
接口,通过注入不同的实现类,可以实现不同的身份验证方式。
5.3.2 数据库连接
在开发应用程序时,通常需要与数据库进行交互。不同的环境可能需要连接不同的数据库,如开发环境使用 SQLite,生产环境使用 MySQL。使用依赖注入可以方便地切换不同的数据库连接。
# 定义数据库连接接口
class DatabaseConnection:
def connect(self):
pass
# 实现 SQLite 数据库连接
class SQLiteConnection(DatabaseConnection):
def connect(self):
return "Connected to SQLite database"
# 实现 MySQL 数据库连接
class MySQLConnection(DatabaseConnection):
def connect(self):
return "Connected to MySQL database"
# 定义数据服务类,依赖于数据库连接
class DataService:
def __init__(self, db_connection):
self.db_connection = db_connection
def fetch_data(self):
connection = self.db_connection.connect()
return f"Fetching data from {connection}"
# 使用 SQLite 数据库连接
sqlite_conn = SQLiteConnection()
data_service1 = DataService(sqlite_conn)
print(data_service1.fetch_data())
# 使用 MySQL 数据库连接
mysql_conn = MySQLConnection()
data_service2 = DataService(mysql_conn)
print(data_service2.fetch_data())
在这个示例中,DataService
类依赖于 DatabaseConnection
接口,通过注入不同的实现类,可以连接不同的数据库。
通过依赖注入,我们可以让代码更加灵活、可维护和可测试,在不同的场景中发挥重要作用👍。
第六章 中间件与错误处理
6.1 中间件
6.1.1 中间件的概念与作用
1. 概念
想象一下,在一个餐厅里,顾客点了菜后,菜品需要经过厨师烹饪、服务员传菜等一系列流程才能到顾客桌上。中间件就像是餐厅里的这些流程环节,在 Web 应用中,当一个请求到达服务器后,会经过一系列的中间件处理,每个中间件都可以对请求或响应进行一些特定的操作,然后再将其传递给下一个中间件或者最终的处理程序😋。
简单来说,中间件是一个函数,它接收请求对象(req
)、响应对象(res
)和下一个中间件函数(next
)作为参数。它可以执行任何代码,修改请求和响应对象,也可以结束请求 - 响应周期,或者调用下一个中间件函数。
2. 作用
- 日志记录:就像餐厅里记录每一桌顾客的点餐信息一样,中间件可以记录每个请求的详细信息,如请求的 URL、请求方法、时间等。这对于调试和监控服务器非常有帮助📝。
- 身份验证:类似于餐厅门口的保安检查顾客的身份,中间件可以验证用户的身份,确保只有合法的用户才能访问特定的资源🔒。
- 数据解析:如果顾客点的菜是用特殊语言描述的,需要有人将其翻译成厨师能看懂的信息。中间件可以对请求中的数据进行解析,比如将 JSON 格式的数据转换为 JavaScript 对象,方便后续处理📑。
- 错误处理:当餐厅里出现问题(如菜品出错)时,需要有专人来处理。中间件可以捕获并处理请求过程中出现的错误,给用户一个友好的错误提示😢。
6.1.2 自定义中间件
1. 基本结构
自定义中间件其实很简单,就是创建一个函数,这个函数接收三个参数:req
、res
和 next
。以下是一个简单的示例:
const customMiddleware = (req, res, next) => {
// 在这里可以执行一些操作,比如记录日志
console.log(`Received a ${req.method} request to ${req.url}`);
// 调用 next 函数,将控制权传递给下一个中间件或路由处理程序
next();
};
2. 使用自定义中间件
在 Express 应用中,可以使用 app.use()
方法来使用自定义中间件:
const express = require('express');
const app = express();
// 使用自定义中间件
app.use(customMiddleware);
app.get('/', (req, res) => {
res.send('Hello, World!');
});
const port = 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
在这个例子中,每次收到请求时,自定义中间件都会先记录请求信息,然后将控制权传递给处理根路径的路由处理程序。
6.1.3 使用内置中间件
1. express.json()
这个中间件用于解析 JSON 格式的请求体。当客户端发送的请求体是 JSON 数据时,使用这个中间件可以将其解析为 JavaScript 对象。示例代码如下:
const express = require('express');
const app = express();
// 使用 express.json() 中间件
app.use(express.json());
app.post('/data', (req, res) => {
// 可以直接访问解析后的 JSON 数据
const data = req.body;
res.send(`Received data: ${JSON.stringify(data)}`);
});
const port = 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
2. express.urlencoded()
这个中间件用于解析 URL 编码的请求体,通常用于处理表单提交的数据。示例代码如下:
const express = require('express');
const app = express();
// 使用 express.urlencoded() 中间件
app.use(express.urlencoded({ extended: true }));
app.post('/form', (req, res) => {
// 可以直接访问解析后的表单数据
const formData = req.body;
res.send(`Received form data: ${JSON.stringify(formData)}`);
});
const port = 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
6.2 错误处理
6.2.1 捕获与处理异常
1. 同步代码中的错误处理
在同步代码中,可以使用 try...catch
语句来捕获和处理异常。例如:
const express = require('express');
const app = express();
app.get('/sync-error', (req, res) => {
try {
// 模拟一个错误
throw new Error('This is a sync error');
} catch (error) {
// 处理错误
res.status(500).send(`Error: ${error.message}`);
}
});
const port = 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
2. 异步代码中的错误处理
在异步代码中,如使用 Promise
或 async/await
,可以使用 .catch()
方法或 try...catch
语句来处理错误。示例如下:
const express = require('express');
const app = express();
app.get('/async-error', async (req, res) => {
try {
// 模拟一个异步错误
await new Promise((resolve, reject) => {
reject(new Error('This is an async error'));
});
} catch (error) {
// 处理错误
res.status(500).send(`Error: ${error.message}`);
}
});
const port = 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
3. 全局错误处理中间件
可以创建一个全局的错误处理中间件来捕获和处理所有未被处理的错误。这个中间件需要接收四个参数:err
、req
、res
和 next
。示例代码如下:
const express = require('express');
const app = express();
// 模拟一个会抛出错误的路由
app.get('/error', (req, res) => {
throw new Error('This is a global error');
});
// 全局错误处理中间件
app.use((err, req, res, next) => {
console.error(err);
res.status(500).send('Internal Server Error');
});
const port = 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
6.2.2 自定义错误响应
1. 自定义错误类型
可以创建自定义的错误类型,以便在不同的情况下抛出不同的错误。例如:
class CustomError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
}
}
2. 使用自定义错误响应
在路由处理程序中,可以抛出自定义错误,并在全局错误处理中间件中处理这些错误,返回自定义的错误响应。示例代码如下:
const express = require('express');
const app = express();
class CustomError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
}
}
app.get('/custom-error', (req, res, next) => {
// 抛出自定义错误
next(new CustomError('This is a custom error', 400));
});
// 全局错误处理中间件
app.use((err, req, res, next) => {
if (err instanceof CustomError) {
res.status(err.statusCode).send(`Custom Error: ${err.message}`);
} else {
res.status(500).send('Internal Server Error');
}
});
const port = 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
这样,当出现自定义错误时,就可以返回特定的状态码和错误信息,给用户更友好的错误提示😃。
第七章 安全与认证
在当今数字化的时代,安全与认证是应用程序不可或缺的一部分。它就像城堡的守卫,保护着系统的资源不被非法访问。接下来,我们将详细了解几种常见的安全认证方式。
7.1 基本认证
基本认证是一种简单直接的认证方式,就像你进入一个房间需要出示钥匙一样,在网络中,客户端需要提供正确的用户名和密码才能访问受保护的资源。
7.1.1 HTTP基本认证实现
HTTP基本认证是一种在HTTP协议层面实现的认证机制。它的工作流程如下:
- 客户端请求资源:当客户端向服务器请求受保护的资源时,服务器会返回一个
401 Unauthorized
状态码,并在响应头中包含WWW-Authenticate
字段,提示客户端进行认证。例如:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Access to the staging site"
这里的 realm
是一个描述性的字符串,用于提示用户需要认证的区域。
- 客户端发送认证信息:客户端收到响应后,会弹出一个对话框,要求用户输入用户名和密码。用户输入后,客户端会将用户名和密码进行Base64编码,并在后续的请求头中添加
Authorization
字段,格式为Basic <Base64编码后的用户名:密码>
。例如:
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
- 服务器验证:服务器接收到请求后,会对
Authorization
字段进行解码,提取出用户名和密码,并与服务器端存储的合法用户信息进行比对。如果匹配成功,则返回请求的资源;否则,返回401 Unauthorized
状态码。
以下是一个使用Python的Flask框架实现HTTP基本认证的示例代码:
from flask import Flask, request, abort
app = Flask(__name__)
# 模拟存储的合法用户信息
users = {
"user1": "password1",
"user2": "password2"
}
@app.route('/protected')
def protected():
auth = request.authorization
if not auth or not (auth.username in users and auth.password == users[auth.username]):
return abort(401, description="Unauthorized")
return "This is a protected resource."
if __name__ == '__main__':
app.run(debug=True)
7.1.2 认证中间件的使用
认证中间件是一种可以在请求处理流程中插入的组件,用于统一处理认证逻辑。它就像一个门卫,在请求进入应用程序的核心逻辑之前,先对请求进行认证检查。
使用认证中间件的好处是可以将认证逻辑与业务逻辑分离,提高代码的可维护性和复用性。以下是一个使用Python的FastAPI框架实现认证中间件的示例代码:
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
app = FastAPI()
# 模拟存储的合法用户信息
users = {
"user1": "password1",
"user2": "password2"
}
# 认证中间件
@app.middleware("http")
async def authentication_middleware(request: Request, call_next):
auth = request.headers.get("Authorization")
if not auth or not auth.startswith("Basic "):
raise HTTPException(status_code=401, detail="Unauthorized")
encoded_credentials = auth.split(" ")[1]
import base64
decoded_credentials = base64.b64decode(encoded_credentials).decode("utf-8")
username, password = decoded_credentials.split(":")
if not (username in users and password == users[username]):
raise HTTPException(status_code=401, detail="Unauthorized")
response = await call_next(request)
return response
@app.get("/protected")
def protected():
return {"message": "This is a protected resource."}
7.2 JWT认证
JWT(JSON Web Token)是一种基于JSON的开放标准(RFC 7519),用于在网络应用中安全地传输声明。它就像一个通行证,包含了用户的身份信息和权限信息,并且可以通过数字签名来保证信息的完整性和真实性。
7.2.2 JWT的原理与结构
JWT由三部分组成,通过点(.)分隔,格式为 header.payload.signature
。
- Header(头部):包含两部分信息,令牌的类型(通常是JWT)和使用的签名算法,例如HMAC SHA256或RSA。头部信息经过Base64编码后作为JWT的第一部分。示例:
{
"alg": "HS256",
"typ": "JWT"
}
编码后:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
- Payload(负载):包含声明(Claims),声明是关于实体(通常是用户)和其他数据的声明。声明分为三种类型:注册声明、公开声明和私有声明。负载信息经过Base64编码后作为JWT的第二部分。示例:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
编码后:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
- Signature(签名):为了创建签名部分,需要使用编码后的头部、编码后的负载、一个密钥(secret)和头部中指定的签名算法。签名用于验证消息在传输过程中没有被更改,并且在使用私钥签名的情况下,还可以验证JWT的发送者的身份。示例:
import hmac
import hashlib
import base64
header = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
payload = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ"
secret = "your-256-bit-secret"
signing_input = f"{header}.{payload}".encode()
signature = hmac.new(secret.encode(), signing_input, hashlib.sha256).digest()
encoded_signature = base64.urlsafe_b64encode(signature).rstrip(b"=").decode()
jwt = f"{header}.{payload}.{encoded_signature}"
7.2.2 在FastApi中实现JWT认证
以下是一个使用Python的FastAPI框架实现JWT认证的示例代码:
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
from typing import Optional
app = FastAPI()
# 密钥和算法
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# 模拟用户数据库
fake_users_db = {
"user1": {
"username": "user1",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW" # 密码为password1
}
}
# 密码哈希
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# OAuth2密码验证
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# 验证密码
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
# 获取用户信息
def get_user(username: str):
if username in fake_users_db:
return fake_users_db[username]
return None
# 认证用户
def authenticate_user(username: str, password: str):
user = get_user(username)
if not user:
return False
if not verify_password(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(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# 获取当前用户
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=401,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = get_user(username)
if user is None:
raise credentials_exception
return user
# 登录接口
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=401,
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"}
# 受保护的接口
@app.get("/protected")
async def protected_route(current_user: dict = Depends(get_current_user)):
return {"message": "This is a protected resource.", "user": current_user["username"]}
7.3 权限管理
权限管理是安全认证的重要组成部分,它用于控制用户对不同资源的访问权限。就像不同级别的员工可以进入不同的办公区域一样,不同角色的用户可以访问不同的系统资源。
7.3.1 基于角色的权限控制
基于角色的权限控制(RBAC,Role-Based Access Control)是一种常见的权限管理模型,它通过定义角色和角色的权限,将用户分配到不同的角色中,从而实现对用户访问权限的管理。
例如,一个系统可能有以下角色:
- 管理员(Admin):拥有系统的所有权限,可以进行用户管理、系统配置等操作。
- 普通用户(User):只能访问自己的个人信息和进行一些基本的操作。
- 访客(Guest):只能访问公开的信息。
在实现RBAC时,通常需要以下几个步骤:
- 定义角色:确定系统中需要的角色。
- 定义权限:确定每个角色可以访问的资源和操作。
- 分配角色:将用户分配到不同的角色中。
- 权限验证:在用户访问资源时,验证用户的角色是否具有相应的权限。
以下是一个简单的Python示例代码,演示了如何实现基于角色的权限控制:
# 定义角色和权限
roles = {
"admin": ["user_management", "system_configuration"],
"user": ["view_profile", "edit_profile"],
"guest": ["view_public_info"]
}
# 模拟用户角色分配
user_roles = {
"user1": "admin",
"user2": "user",
"user3": "guest"
}
# 权限验证函数
def has_permission(user, permission):
role = user_roles.get(user)
if role:
return permission in roles.get(role, [])
return False
# 示例使用
user = "user1"
permission = "user_management"
if has_permission(user, permission):
print(f"User {user} has permission to {permission}.")
else:
print(f"User {user} does not have permission to {permission}.")
7.3.2 权限验证中间件
权限验证中间件是一种可以在请求处理流程中插入的组件,用于统一处理权限验证逻辑。它可以在请求进入应用程序的核心逻辑之前,先对请求进行权限检查。
以下是一个使用Python的FastAPI框架实现权限验证中间件的示例代码:
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
# 定义角色和权限
roles = {
"admin": ["user_management", "system_configuration"],
"user": ["view_profile", "edit_profile"],
"guest": ["view_public_info"]
}
# 模拟用户角色分配
user_roles = {
"user1": "admin",
"user2": "user",
"user3": "guest"
}
# 权限验证中间件
@app.middleware("http")
async def permission_middleware(request: Request, call_next):
user = request.headers.get("X-User")
required_permission = request.headers.get("X-Permission")
if user and required_permission:
role = user_roles.get(user)
if role:
if required_permission not in roles.get(role, []):
raise HTTPException(status_code=403, detail="Forbidden")
else:
raise HTTPException(status_code=403, detail="Forbidden")
response = await call_next(request)
return response
@app.get("/user_management")
def user_management():
return {"message": "This is a user management resource."}
通过以上的安全与认证机制,可以有效地保护应用程序的资源,确保只有合法的用户才能访问相应的资源。🎉
第八章 数据库集成
在现代应用开发中,数据库集成是至关重要的一环,它能帮助我们高效地存储、管理和检索数据。本章将详细介绍 SQL 数据库和 NoSQL 数据库的集成方法。
8.1 SQL数据库集成
SQL 数据库以其强大的结构化数据管理能力和成熟的事务处理机制,在企业级应用中广泛使用。下面我们将从使用 SQLAlchemy、数据库模型定义与操作以及数据库迁移这几个方面进行介绍。
8.1.1 使用SQLAlchemy
SQLAlchemy 是一个强大的 Python SQL 工具包和对象关系映射(ORM)库,它允许我们使用 Python 代码与多种 SQL 数据库进行交互,而无需编写复杂的 SQL 语句。
1. 安装 SQLAlchemy
可以使用 pip 来安装 SQLAlchemy:
pip install sqlalchemy
2. 连接数据库
以下是一个连接 SQLite 数据库的示例:
from sqlalchemy import create_engine
# 创建数据库引擎
engine = create_engine('sqlite:///example.db')
3. 建立会话
会话是 SQLAlchemy 中进行数据库操作的主要接口:
from sqlalchemy.orm import sessionmaker
# 创建会话工厂
Session = sessionmaker(bind=engine)
# 创建会话实例
session = Session()
8.1.2 数据库模型定义与操作
1. 定义数据库模型
使用 SQLAlchemy 的 ORM 功能,我们可以通过 Python 类来定义数据库表结构:
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
# 创建基类
Base = declarative_base()
# 定义用户模型
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
age = Column(Integer)
2. 创建表
在定义好模型后,我们可以使用以下代码创建数据库表:
Base.metadata.create_all(engine)
3. 插入数据
# 创建用户对象
new_user = User(name='John', age=25)
# 将用户对象添加到会话中
session.add(new_user)
# 提交会话,将数据插入数据库
session.commit()
4. 查询数据
# 查询所有用户
users = session.query(User).all()
for user in users:
print(f"ID: {user.id}, Name: {user.name}, Age: {user.age}")
# 根据条件查询用户
user = session.query(User).filter_by(name='John').first()
print(f"ID: {user.id}, Name: {user.name}, Age: {user.age}")
8.1.3 数据库迁移
数据库迁移是指在数据库结构发生变化时,能够安全、方便地更新数据库表结构。SQLAlchemy 通常与 Alembic 一起使用来实现数据库迁移。
1. 安装 Alembic
pip install alembic
2. 初始化 Alembic
在项目根目录下执行以下命令:
alembic init alembic
3. 配置 Alembic
打开 alembic.ini
文件,配置数据库连接信息:
sqlalchemy.url = sqlite:///example.db
4. 创建迁移脚本
alembic revision --autogenerate -m "Create users table"
5. 应用迁移
alembic upgrade head
8.2 NoSQL数据库集成
NoSQL 数据库以其灵活的数据模型和高可扩展性,在处理大规模数据和高并发场景中表现出色。下面我们将介绍如何集成 MongoDB 以及进行数据操作与查询。
8.2.1 集成MongoDB
1. 安装 PyMongo
PyMongo 是 Python 与 MongoDB 交互的官方驱动程序,使用 pip 进行安装:
pip install pymongo
2. 连接 MongoDB
from pymongo import MongoClient
# 连接 MongoDB 服务器
client = MongoClient('mongodb://localhost:27017/')
# 获取数据库实例
db = client['example_db']
# 获取集合实例
collection = db['users']
8.2.2 数据操作与查询
1. 插入数据
# 插入单条数据
user = {'name': 'Alice', 'age': 30}
result = collection.insert_one(user)
print(f"Inserted document ID: {result.inserted_id}")
# 插入多条数据
users = [
{'name': 'Bob', 'age': 35},
{'name': 'Charlie', 'age': 40}
]
result = collection.insert_many(users)
print(f"Inserted document IDs: {result.inserted_ids}")
2. 查询数据
# 查询所有文档
for user in collection.find():
print(user)
# 根据条件查询文档
query = {'age': {'$gt': 30}}
for user in collection.find(query):
print(user)
3. 更新数据
# 更新单条数据
query = {'name': 'Alice'}
new_values = {'$set': {'age': 31}}
result = collection.update_one(query, new_values)
print(f"Matched count: {result.matched_count}, Modified count: {result.modified_count}")
4. 删除数据
# 删除单条数据
query = {'name': 'Bob'}
result = collection.delete_one(query)
print(f"Deleted count: {result.deleted_count}")
通过以上内容,我们详细介绍了 SQL 数据库和 NoSQL 数据库的集成方法,希望能帮助你在项目中更好地使用数据库。😃
第九章 测试与部署
9.1 测试FastApi应用
9.1.1 使用Pytest进行单元测试
1. 什么是单元测试
单元测试是对软件中的最小可测试单元进行检查和验证。在FastAPI应用中,单元测试通常针对单个函数或视图进行,确保它们在给定输入时能产生预期的输出😃。
2. 安装Pytest
要使用Pytest进行单元测试,首先需要安装它。可以使用pip命令进行安装:
pip install pytest
3. 编写单元测试示例
假设我们有一个简单的FastAPI应用:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
我们可以编写如下的单元测试:
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_read_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"Hello": "World"}
在这个示例中,我们使用TestClient
来模拟对FastAPI应用的请求,并使用assert
语句来验证响应的状态码和内容。
4. 运行单元测试
在终端中,进入包含测试文件的目录,然后运行以下命令:
pytest
Pytest会自动发现并运行所有以test_
开头的函数,并输出测试结果🎉。
9.1.2 测试客户端的使用
1. 测试客户端的作用
FastAPI提供了TestClient
,它是一个用于测试FastAPI应用的客户端。使用TestClient
,我们可以模拟HTTP请求,而无需启动实际的服务器,这样可以更方便地进行测试🧪。
2. 创建测试客户端
在编写测试代码时,我们需要创建一个TestClient
实例,并传入FastAPI应用实例:
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
3. 模拟不同的HTTP请求
TestClient
支持模拟各种HTTP请求,如GET、POST、PUT、DELETE等。以下是一些示例:
- GET请求:
response = client.get("/")
- POST请求:
data = {"key": "value"}
response = client.post("/", json=data)
- PUT请求:
data = {"key": "new_value"}
response = client.put("/item/1", json=data)
- DELETE请求:
response = client.delete("/item/1")
4. 验证响应
在发送请求后,我们可以使用assert
语句来验证响应的状态码、内容等:
assert response.status_code == 200
assert response.json() == {"key": "value"}
9.1.3 集成测试
1. 什么是集成测试
集成测试是将多个模块组合在一起进行测试,以确保它们之间的交互正常。在FastAPI应用中,集成测试可以测试多个路由、中间件等之间的协同工作情况🤝。
2. 集成测试示例
假设我们的FastAPI应用有多个路由,并且它们之间存在依赖关系。我们可以编写集成测试来验证这些路由的协同工作:
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_integration():
# 先创建一个资源
create_response = client.post("/items/", json={"name": "test_item"})
assert create_response.status_code == 201
item_id = create_response.json()["id"]
# 然后获取该资源
get_response = client.get(f"/items/{item_id}")
assert get_response.status_code == 200
assert get_response.json()["name"] == "test_item"
# 最后删除该资源
delete_response = client.delete(f"/items/{item_id}")
assert delete_response.status_code == 204
在这个示例中,我们模拟了创建、获取和删除资源的过程,确保这些操作之间的协同工作正常。
9.2 部署FastApi应用
9.2.1 使用Uvicorn与Gunicorn
1. Uvicorn简介
Uvicorn是一个基于Python的ASGI(异步服务器网关接口)服务器,它非常适合用于运行FastAPI应用。Uvicorn支持异步处理,能够高效地处理大量并发请求🚀。
2. 安装Uvicorn
可以使用pip命令安装Uvicorn:
pip install uvicorn
3. 运行FastAPI应用
在终端中,进入包含FastAPI应用代码的目录,然后运行以下命令:
uvicorn main:app --reload
其中,main
是包含FastAPI应用实例的Python文件名,app
是FastAPI应用实例的名称。--reload
参数表示在代码发生变化时自动重新加载应用。
4. Gunicorn简介
Gunicorn是一个Python WSGI(Web服务器网关接口)服务器,它可以与Uvicorn结合使用,以提高应用的性能和稳定性。Gunicorn可以管理多个Uvicorn工作进程,从而更好地处理并发请求。
5. 安装Gunicorn
可以使用pip命令安装Gunicorn:
pip install gunicorn
6. 使用Gunicorn运行FastAPI应用
在终端中,运行以下命令:
gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app
其中,-w 4
表示启动4个工作进程,-k uvicorn.workers.UvicornWorker
表示使用Uvicorn工作进程,main:app
是FastAPI应用的入口。
9.2.2 部署到云平台(如Heroku、AWS等)
1. 部署到Heroku
- 创建Heroku账户:访问Heroku官网,注册并登录账户。
- 安装Heroku CLI:根据Heroku官方文档,安装Heroku命令行工具。
- 初始化项目:在项目根目录下,使用以下命令初始化Heroku项目:
heroku create
- 配置Procfile:在项目根目录下创建一个
Procfile
文件,内容如下:
web: gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app
- 部署应用:使用以下命令将应用部署到Heroku:
git push heroku main
- 打开应用:使用以下命令在浏览器中打开部署好的应用:
heroku open
2. 部署到AWS
- 创建AWS账户:访问AWS官网,注册并登录账户。
- 选择合适的服务:可以选择AWS Elastic Beanstalk、AWS Lambda等服务来部署FastAPI应用。
- 配置环境:根据所选服务的文档,配置相应的环境和参数。
- 上传代码:将FastAPI应用代码上传到AWS服务中。
- 启动应用:启动部署好的应用,并进行测试。
通过以上步骤,我们可以将FastAPI应用部署到云平台,让更多的用户可以访问我们的应用👏。
第十章 高级特性
10.1 异步编程
10.1.1 Python 异步编程基础
1. 异步编程概念
在传统的同步编程中,程序是按照顺序依次执行的,当一个操作(如网络请求、文件读写)需要等待时,程序会被阻塞,直到该操作完成才能继续执行后续代码。而异步编程则允许程序在等待某个操作完成时,去执行其他任务,从而提高程序的执行效率。
例如,在一个爬虫程序中,如果使用同步编程,每次请求网页都需要等待响应返回才能继续请求下一个网页;而使用异步编程,在等待某个网页响应的同时,可以去发起其他网页的请求。
2. 核心概念
- 协程(Coroutine):协程是一种轻量级的线程,它可以在一个线程中实现多个任务的切换。在 Python 中,协程是通过
async/await
语法来定义和使用的。
import asyncio
async def hello():
print("Hello")
await asyncio.sleep(1)
print("World")
async def main():
await hello()
asyncio.run(main())
在这个例子中,hello
函数是一个协程函数,使用 async
关键字定义。await
关键字用于暂停协程的执行,直到等待的操作完成。
- 事件循环(Event Loop):事件循环是异步编程的核心,它负责调度和执行协程。Python 的
asyncio
库提供了事件循环的实现。在上面的例子中,asyncio.run(main())
会创建一个事件循环,并运行main
协程。
3. 异步 I/O 操作
异步编程通常用于处理 I/O 密集型任务,如网络请求、文件读写等。Python 的 asyncio
库提供了一些异步 I/O 操作的支持,例如异步的 TCP 和 UDP 套接字。
import asyncio
async def tcp_echo_client(message):
reader, writer = await asyncio.open_connection(
'127.0.0.1', 8888
)
print(f'Send: {message!r}')
writer.write(message.encode())
await writer.drain()
data = await reader.read(100)
print(f'Received: {data.decode()!r}')
print('Close the connection')
writer.close()
await writer.wait_closed()
asyncio.run(tcp_echo_client('Hello World!'))
这个例子展示了一个简单的异步 TCP 客户端,它可以发送消息并接收服务器的响应。
10.1.2 在 FastApi 中使用异步函数
1. FastApi 简介
FastApi 是一个基于 Python 的现代、快速(高性能)的 Web 框架,它使用类型提示来提高代码的可读性和可维护性,并且支持异步编程。
2. 定义异步路由处理函数
在 FastApi 中,可以使用 async def
来定义异步路由处理函数。这样,在处理请求时,如果有 I/O 密集型操作,FastApi 可以在等待操作完成的同时处理其他请求。
from fastapi import FastAPI
app = FastAPI()
async def some_async_task():
await asyncio.sleep(1)
return "Task completed"
@app.get("/")
async def read_root():
result = await some_async_task()
return {"Hello": result}
在这个例子中,read_root
是一个异步路由处理函数,它调用了一个异步任务 some_async_task
。
3. 异步数据库操作
FastApi 可以与异步数据库驱动程序(如 asyncpg
用于 PostgreSQL)结合使用,实现异步的数据库操作。
import asyncpg
from fastapi import FastAPI
app = FastAPI()
async def get_db_connection():
conn = await asyncpg.connect(user='user', password='password',
database='mydb', host='127.0.0.1')
return conn
@app.get("/items/")
async def read_items():
conn = await get_db_connection()
rows = await conn.fetch('SELECT * FROM items')
await conn.close()
return [dict(row) for row in rows]
这个例子展示了如何在 FastApi 中使用异步的 PostgreSQL 数据库操作。
10.2 后台任务
10.2.1 定义与执行后台任务
1. 后台任务的概念
后台任务是指在处理请求的同时,在后台异步执行的任务。这些任务通常是一些耗时的操作,如发送邮件、生成报告等,不会阻塞请求的响应。
2. 在 FastApi 中定义和执行后台任务
FastApi 提供了 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"}
在这个例子中,write_notification
是一个后台任务函数,通过 BackgroundTasks
的 add_task
方法将其添加到后台任务队列中。当请求 /send-notification/{email}
时,会立即返回响应,而 write_notification
函数会在后台异步执行。
10.2.2 任务调度
1. 任务调度的概念
任务调度是指按照一定的时间规则或条件来执行任务。例如,每天凌晨 2 点执行数据备份任务,或者每隔 5 分钟检查一次系统状态。
2. 使用 apscheduler
进行任务调度
apscheduler
是一个 Python 的任务调度库,可以与 FastApi 结合使用。
from fastapi import FastAPI
from apscheduler.schedulers.background import BackgroundScheduler
app = FastAPI()
def job():
print("This is a scheduled job")
scheduler = BackgroundScheduler()
scheduler.add_job(job, 'interval', minutes=5)
scheduler.start()
@app.get("/")
async def read_root():
return {"Hello": "World"}
在这个例子中,使用 apscheduler
创建了一个后台调度器,并添加了一个每隔 5 分钟执行一次的任务。调度器在应用启动时开始运行。
10.3 静态文件与模板
10.3.1 提供静态文件服务
1. 静态文件的概念
静态文件是指那些不需要动态生成的文件,如 CSS 文件、JavaScript 文件、图片等。在 Web 应用中,通常需要提供这些静态文件的访问服务。
2. 在 FastApi 中提供静态文件服务
FastApi 可以使用 StaticFiles
类来提供静态文件服务。
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.get("/")
async def read_root():
return {"Hello": "World"}
在这个例子中,将 static
目录下的文件挂载到 /static
路径下,这样客户端就可以通过 /static
路径访问这些静态文件。
10.3.2 使用模板引擎(如 Jinja2)
1. 模板引擎的概念
模板引擎是一种将数据和模板结合生成动态 HTML 页面的工具。它允许开发者将 HTML 页面的结构和数据分离,提高代码的可维护性。
2. 在 FastApi 中使用 Jinja2 模板引擎
FastApi 可以与 Jinja2 模板引擎结合使用。
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
app = FastAPI()
templates = Jinja2Templates(directory="templates")
@app.get("/items/{id}", response_class=HTMLResponse)
async def read_item(request: Request, id: str):
return templates.TemplateResponse("item.html", {"request": request, "id": id})
在这个例子中,首先创建了一个 Jinja2Templates
对象,指定模板文件所在的目录为 templates
。然后在路由处理函数中,使用 TemplateResponse
来渲染模板文件 item.html
,并传递数据给模板。模板文件可以使用 Jinja2 的语法来动态生成 HTML 内容。
🎈通过这些高级特性的学习,你可以让你的 Python Web 应用更加高效、功能更加强大!