读后笔记 -- FastAPI 构建Python微服务 Chapter1:设置 FastAPI
1.2 设置开发环境
1 # fastapi 构建需要的主要安装包 2 pip install fastapi[all] 3 pip install uvicorn 4 pip install python-multipart
FastAPI 是一个用于构建高效、现代且易于维护的 Web API 的高性能 Python 框架。由 Sebastián Ramírez 开发,并深受开发者喜爱,因为它结合了易用性和先进的技术特性。关键特性包括: 1. 类型提示与智能路由: 利用了 Python 3.6+ 引入的类型注解,使得接口定义清晰明了,同时利用这些类型提示自动生成详细的 OpenAPI 规范(Swagger),进而提供交互式的 API 文档。 2. 异步支持: 原生支持异步编程模型,意味着它可以充分利用 Python 的异步 I/O 功能(如通过 asyncio 库)以实现高并发性能,特别适合构建需要大规模并行处理请求的应用。 3. 高性能表现: 在实际应用中,展现出卓越的性能,其吞吐量和响应时间在同类框架中名列前茅,非常适合大型系统的后端服务开发。 4. 简洁与模块化: 接口定义方式类似于 Flask,但更加强调简洁和一致性,允许开发者以最少的代码创建清晰的路由和逻辑。 5. 数据验证与序列化: 内置了 Pydantic 库来进行数据验证和模式定义,确保输入数据正确性的同时简化了数据模型的管理和转换过程。 6. 依赖注入: 提供了一套完善的依赖注入系统,使得诸如数据库连接、认证服务等组件的管理变得简单易用。 7. 安全性与认证: 集成了 OAuth2 和 JWT 等标准认证方法,为 API 安全提供了便捷的支持。 8. 实时通信支持: 支持 WebSocket 协议,便于构建实时通讯功能。 9. 无缝集成: 可与众多第三方库和服务轻松集成,例如数据库 ORM、JWT 认证库、CORS 设置等。
总之,FastAPI 结合了易学易用、代码质量高、文档自动生成以及高性能等特点,是现代 Python Web 开发的理想选择,尤其适用于构建微服务架构中的 API 接口层。
Python 中的 uvicorn 是一个高性能的异步 ASGI (Asynchronous Server Gateway Interface) 服务器,主要用于运行符合 ASGI 规范的 Web 应用程序,特别是与现代 Python Web 框架(如 FastAPI、Starlette 等)配合使用时效果最佳。
uvicorn 主要作用包括: 1. 高性能:
Uvicorn 基于 uvloop(一个优化过的事件循环)和 httptools(用于解析 HTTP 请求头和体的高性能库)构建,这使得它能够处理大量的并发连接,从而提高应用的响应速度和吞吐量。 2. 异步处理:
由于支持 ASGI,Uvicorn 能够有效地处理异步请求,充分利用非阻塞I/O,减少资源消耗,特别是在涉及大量IO操作或者网络延迟较高的场景下,能显著提升应用效率。 3. 启动和部署便捷:
Uvicorn 提供了一个简洁易用的命令行接口,用户可以通过简单的命令快速启动应用,并支持与 Gunicorn、Docker 或 Kubernetes 等工具集成,方便部署到生产环境。 4. 自动重载:
当源代码发生变化时,Uvicorn 可以自动检测并重新加载应用,这对于开发阶段非常有用,可以实时查看代码改动的效果。 5. OpenAPI / Swagger 文档:
配合像 FastAPI 这样的框架时,Uvicorn 可以自动生成详细的 API 文档,为开发者和用户提供可视化的接口说明。 总结来说,Uvicorn 作为 ASGI 服务器的核心角色在于为构建异步的 Python Web 应用提供高速、稳定的服务支撑平台。
1.3 初始化和配置 FastAPI
1 # sample, main.py 2 from fastapi import FastAPI 3 4 app = FastAPI() 5 6 # first sample API 7 @app.get("/ch01/index") 8 def index(): 9 return {"message": "Welcome FastAPI Nerds"}
启动 service: uvicorn main:app
访问 swagger: http://127.0.0.1:8000/docs#/
1.4 设计和实现 REST API
REST (representation state transfer) 代表性状态传输
API 优先(最流行和最有效的微服务设计策略之一):首先关注客户的需求,然后确定要为这些客户需求实现哪些 API 服务方法。
8 种 HTTP 请求:get(), post(), delete(), put(), head(), patch(), trace(), options()
1 # PUT 方法用于完全替换资源。该方法将查找到 {username} 对应的 profile 更新成传入的 new_profile 2 @app.put("/ch01/account/profile/update/{username}") 3 def update_profile(username: str, user_id: UUID, new_profile: UserProfile): 4 if valid_users.get(username) is None: 5 return {"message": "user does not exist"} 6 else: 7 user = valid_users.get(username) 8 if user.user_id == user_id: 9 valid_profiles[username] = new_profile 10 return {"message": "successfully updated"} 11 else: 12 return {"message": "user does not exist"} 13 14 15 # PATCH 方法用于对资源部分更新。该方法将查找到 {username} 对应的 profile 进行部分属性更新 16 @app.patch("/ch01/account/profile/update/names/{username}") 17 def update_profile_names(user_id: UUID, username: str = "", new_names: Optional[Dict[str, str]] = None): 18 if valid_users.get(username) is None: 19 return {"message": "user does not exist"} 20 elif new_names is None: 21 return {"message": "new names are required"} 22 else: 23 user = valid_users.get(username) 24 if user.user_id == user_id: 25 profile = valid_profiles[username] 26 profile.firstname = new_names['fname'] 27 profile.lastname = new_names['lname'] 28 profile.middle_initial = new_names['mi'] 29 valid_profiles[username] = profile 30 return {"message": "successfully updated"} 31 else: 32 return {"message": "user does not exist"}
1.5 管理用户请求和服务器响应
1. FastAPI 支持 3 大类型,遵循 PEP 484 标准:
- 常见类型:如 None、bool、int、float
- container 类型:如 list、tuple、dict、set、frozenset 和 deque
- 复杂的 python 类型: datatime.date、datetime.time、datetime.datetime、datetime.delta、UUID、bytes、Decimal、typing 模块的数据类型(如 Optional、List、Iterable 等)
2. 路径参数(路径中包含参数,用 {} 包含作为 URL 的一部分)
@app.put("/ch01/account/profile/update/{username}") # step1: {username} 为路径参数 def update_profile(username: str, id: UUID, new_profile: UserProfile): # step2: username: str,方法中声明参数类型
Notice:固定路径的声明 应处于 带有路径参数的动态端点 URL 之前,否则 报 422 Unprocessable Entity。访问 http://127.0.0.1:8000/ch01/login/details/info 将 422 Unprocessable Entity 报错
1 # 错误顺序 2 @app.get("/ch01/login/{username}/{password}") 3 def login_with_token(username: str, password: str, id: UUID): 4 if valid_users.get(username) is None: 5 return {"message": "user does not exist"} 6 else: 7 user = valid_users[username] 8 if user.id == id and checkpw(password.encode(), user.passphrase): 9 return user 10 else: 11 return {"message": "invalid user"} 12 13 14 @app.get("/ch01/login/details/info") 15 def login_info(): 16 return {"message": "username and password are needed"}
正确的方式
1 @app.get("/ch01/login/details/info") # 将该固定路径的 endpoint 放在带参的动态 endpoint 之前 2 def login_info(): 3 return {"message": "username and password are needed"} 4 5 @app.get("/ch01/login/{username}/{password}") 6 def login_with_token(username: str, password: str, id: UUID): 7 if valid_users.get(username) is None: 8 return {"message": "user does not exist"} 9 else: 10 user = valid_users[username] 11 if user.id == id and checkpw(password.encode(), user.passphrase): 12 return user 13 else: 14 return {"message": "invalid user"}
3. 查询参数
1)常见类型:
1 # 常见类型 2 @app.get("/ch01/login/password/change") 3 def change_password(username: str, old_passw: str = '', new_passw: str = ''): 4 5 # 对象类型 6 @app.post("/ch01/login/validate", response_model=ValidUser) 7 def approve_user(user: User) -> ValidUser:
2)复杂类型
@app.delete("/ch01/login/show/query") def show_query(usernames: List[str]): # 必须对 List 集合进行类型声明,否则无法编译 for username in usernames: print(username) return {"message": f"{usernames}"}
4. 混合类型参数的顺序
# 对于混合参数类型,参数声明的顺序必须是:必要参数 -> 默认参数 -> 可选参数 def update_profile_names(user_id: UUID, username: str = "", new_names: Optional[Dict[str, str]] = None):
5. fastAPI 的 response_model 和 函数 -> 的区别:
在 FastAPI 中,response_model 和函数返回类型的注解(即 -> 后面的部分)有不同的用途,但它们都服务于提高 API 的清晰性和可维护性,同时也帮助生成文档。
1. response_model
是一个属性,通常用于路径操作函数(如 @app.get、@app.post 等装饰器)中,用来指定响应数据的结构。这可以帮助 FastAPI 自动生成 API 文档,并且在返回响应时自动序列化数据。
1 from fastapi import FastAPI
2 from pydantic import BaseModel
3
4 app = FastAPI()
5
6 class Item(BaseModel):
7 name: str
8 description: str | None = None
9 price: float
10 tax: float | None = None
11
12 @app.get("/items/", response_model=List[Item])
13 async def read_items():
14 return [
15 {"name": "Foo", "price": 50.2},
16 {"name": "Bar", "price": 62, "description": "The Bar fighters"},
17 {"name": "Baz", "price": 50.2, "tax": 3.5},
18 ]
在这个例子中,response_model=List[Item] 指定了返回的数据应该是一个 Item 模型的列表。FastAPI 会检查返回的数据是否符合这个模型,并自动将其序列化为 JSON 格式。
2. 函数返回类型的注解 (->)
主要用于类型检查,告诉 Python 解释器(以及像 PyCharm 或 VSCode 这样的 IDE)函数预期返回什么类型的对象。这对于静态分析工具是有用的,也可以帮助开发者理解函数的预期行为。
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int) -> dict:
return {"item_id": item_id}
在这里,-> dict 声明了 read_item 函数应该返回一个字典。这主要是为了开发者的便利,以及类型检查。
通常情况下,会同时使用两者来增强 API 的健壮性和可用性。例如,你可以指定一个 response_model 来确保返回的数据格式正确,并且使用函数返回类型的注解来表明预期的数据类型。这有助于确保代码的一致性和清晰性,同时也提高了 API 的质量。
6. 请求标头
1 from fastapi import Header, 2 3 request_headers = dict() 4 5 # 声明中如果不包含 Header() 会导致 FastAPI 将变量视为查询参数 6 @app.get("/ch01/headers/verify") 7 def verify_headers(host: Optional[str] = Header(None), 8 accept: Optional[str] = Header(None), 9 accept_language: Optional[str] = Header(None), 10 accept_encoding: Optional[str] = Header(None), 11 user_agent: Optional[str] = Header(None) 12 ): 13 # 从客户端请求的 Header 获取相关属性并赋值 request_headers 14 request_headers["Host"] = host 15 request_headers["Accept"] = accept 16 request_headers["Accept-Language"] = accept_language 17 request_headers["Accept-Encoding"] = accept_encoding 18 request_headers["User-Agent"] = user_agent 19 return request_headers
请求:
7. 所有 FastAPI 的 API 服务都应该返回 JSON 数据。
8. 处理表单参数
1 # 处理表单数据,可以使用 Form(...) 2 # 其中,Form() 里面的 ... 表示 该表单参数是必需的 3 @app.post("/ch01/account/profile/add", response_model=UserProfile) 4 def add_profile(uname: str, 5 fname: str = Form(...), 6 lname: str = Form(...), 7 mid_init: str = Form(...), 8 user_age: int = Form(...), 9 sal: float = Form(...), 10 bday: str = Form(...), 11 utype: UserType = Form(...) 12 ): 13 if valid_users.get(uname) is None:
9. 管理 Cookie
# 管理 Cookie 使用 Response 库 from fastapi import Response
@app.post("/ch01/login/rememberme/create") # Response 必须作为服务的第一个本地参数出现,且不要向它传参 def create_cookies(resp: Response, user_id: UUID, username: str = ''): # 设置 cookie 名称 resp.set_cookie(key='userkey', value=username) # 存储 cookie 的值 resp.set_cookie(key='identity', value=str(user_id)) return {"message": "remember-me tokens created"}
读取 cookie
1 from fastapi import Cookie 2 3 @app.get("/ch01/login/cookies") 4 def access_cookie(userkey: Optional[str] = Cookie(None), identity: Optional[str] = Cookie(None)): 5 cookies['userkey'] = userkey 6 cookies['identity'] = identity 7 return cookies