读后笔记 -- FastAPI 构建Python微服务 Chapter1:设置 FastAPI

1.2 设置开发环境

# fastapi 构建需要的主要安装包
pip install fastapi[all] pip install uvicorn pip install python
-multipart
FastAPI 是一个用于构建高效、现代且易于维护的 Web API 的高性能 Python 框架。由 Sebastián Ramírez 开发,并深受开发者喜爱,因为它结合了易用性和先进的技术特性。关键特性包括:

1. 类型提示与智能路由:
  FastAPI 利用了 Python 3.6+ 引入的类型注解,使得接口定义清晰明了,同时利用这些类型提示自动生成详细的 OpenAPI 规范(Swagger),进而提供交互式的 API 文档。
2. 异步支持:
  FastAPI 原生支持异步编程模型,这意味着它可以充分利用 Python 的异步 I/O 功能(如通过 asyncio 库)以实现高并发性能,特别适合构建需要大规模并行处理请求的应用。
3. 高性能表现:
  在实际应用中,FastAPI 展现出卓越的性能,其吞吐量和响应时间在同类框架中名列前茅,非常适合大型系统的后端服务开发。
4. 简洁与模块化:
  FastAPI 的接口定义方式类似于 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

# sample, main.py
from fastapi import FastAPI

app = FastAPI()

# first sample API
@app.get("/ch01/index")
def index():
    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 服务方法。

PUT v.s. PATCH 的区别

@app.put("/ch01/account/profile/update/{username}"): 用于完全替换资源

@app.patch("/ch01/account/profile/update/names/{username}"):用于对资源进行细粒度的、部分的更新

 


1.5 管理用户请求和服务器响应

1. FastAPI 支持 3 大类型:

  • 常见类型:如 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}")    # {username} 为路径参数
def update_profile(username: str, id: UUID, new_profile: UserProfile):    # FastAPI  要求通过应用类型提示来声明这些参数

Notice:固定路径的声明 应处于 带有路径参数的动态端点 URL 之前。

@app.get("/ch01/login/{username}/{password}")
def login_with_token(username: str, password: str, id: UUID):
    if valid_users.get(username) is None:
        return {"message": "user does not exist"}
    else:
        user = valid_users[username]
        if user.id == id and checkpw(password.encode(), user.passphrase):
            return user
        else:
            return {"message": "invalid user"}


@app.get("/ch01/login/details/info")
def login_info():
    return {"message": "username and password are needed"}

# 访问 http://127.0.0.1:8000/ch01/login/details/info 将 422 Unprocessable Entity 报错。正确的方式应该是:

@app.get("/ch01/login/details/info")  # 将该固定路径的 endpoint 放在带参的动态 endpoint 之前
def login_info():
    return {"message": "username and password are needed"}

@app.get("/ch01/login/{username}/{password}")
def login_with_token(username: str, password: str, id: UUID):
    if valid_users.get(username) is None:
        return {"message": "user does not exist"}
    else:
        user = valid_users[username]
        if user.id == id and checkpw(password.encode(), user.passphrase):
            return user
        else:
            return {"message": "invalid user"}

 

3. 混合类型参数的顺序

# 对于混合参数类型, 应先声明所有必需的参数,然后是默认参数,最后是可选参数

def update_profile_names(user_id: UUID, username: str = "", new_names: Optional[Dict[str, str]] = None):

 

4. fastAPI 的 response_model 和 函数 -> 的区别:

在FastAPI中,response_model 是一个用于指定API端点返回数据结构的参数,它通常出现在路由装饰器如 @app.get()、@app.post() 等中。当你设置了一个响应模型,FastAPI会按照这个模型来验证并转换即将返回给客户端的数据,
并且确保返回值符合模型定义的JSON Schema结构。这样做的好处包括:   数据校验:FastAPI会检查实际返回的数据是否符合模型定义的字段类型、要求等。   序列化:将Python对象自动序列化为JSON或其他格式,根据你使用的响应类(如JSONResponse)。   文档生成:在自动生成的API文档(通过Swagger UI或者Redoc)中显示预期的响应结构。   一致性:保证API响应始终遵循预定义的结构。
另一方面,
-> 符号在FastAPI(以及Python函数签名中)用于表示函数的返回类型注解。在FastAPI上下文中,如果你在路由处理函数的返回类型前使用了 -> 注解,并指定了一个Pydantic模型,那么这也会起到类似于response_model的作用,
  即约束函数返回值的类型。 例如:
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str description: str @app.get("/items/{item_id}", response_model=Item) async def read_item(item_id: int) -> Item: # 假设有一个函数去获取item数据 item_data = fetch_item(item_id) return Item(**item_data)
在这里,response_model
=Item 和函数返回类型注解 -> Item 都表明了同一个信息:read_item 函数的响应体应当符合 Item 模型的结构。 然而,二者之间有个微妙的区别:   使用 response_model 参数可以直接影响OpenAPI规范的生成,并对所有HTTP响应进行统一的验证和转换。   如果只使用函数返回类型的 -> Item 注解而不设置 response_model,虽然FastAPI仍然会利用类型注解来进行类型检查和部分文档生成,但它不会强制执行JSON Schema验证,并且可能不会以最完整的方式体现在API的OpenAPI规格文档中。
为了确保完整的响应数据验证和文档生成,推荐同时在装饰器级别明确指定 response_model

 

5. 所有 FastAPI 的 API 服务都应该返回 JSON 数据。

 

6. 处理表单参数

# 处理表单数据,可以使用 Form(...)
# 其中,Form() 里面的 ...  表示 该表单参数是必需的
@app.post("/ch01/account/profile/add", response_model=UserProfile)
def add_profile(uname: str,
                fname: str = Form(...),
                lname: str = Form(...),
                mid_init: str = Form(...),
                user_age: int = Form(...),
                sal: float = Form(...),
                bday: str = Form(...),
                utype: UserType = Form(...)
                ):
    if valid_users.get(uname) is None:

 

7. 管理 Cookie

# 管理 Cookie 使用 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"}

 

posted on 2024-02-27 20:57  bruce_he  阅读(405)  评论(0编辑  收藏  举报