fastAPI

FastAPI

FastAPi是一个用于构建api的现代,快速(高性能)的web框架,使用python并基于标准的python类型提示

特点

  • 高性能: 基于Starlette和Pydantic,利用异步(asynchronous)编程,提供出色的性能。最快的python web框架之一
  • 自动文档生成: 自动生成交互式API文档,支持Swagger UI和ReDoc,让API的理解和测试更加直观。
  • 类型注解支持: 利用Python的类型提示,提供更严格的输入验证和更好的代码提示。
  • 异步支持: 支持异步请求处理,使得处理IO密集型任务更加高效。

适用场景

  • 构建API后端: 用于构建RESTful API,支持前后端分离的Web应用。
  • 微服务架构: 可以作为微服务的后端框架,支持快速开发和部署。
  • 数据处理API: 适用于处理数据,接收和返回JSON数据。
  • 实时通信: 支持WebSocket,适用于实时通信场景。

为什么选择FastAPI

  • Pythonic: 使用Python的自然语法和类型提示,降低学习曲线。
  • 性能优越: 利用异步编程和底层的Starlette框架,提供卓越的性能。
  • 文档友好: 自动生成交互式文档,减少文档维护的工作量。
  • 生态系统: 基于Python生态系统,可以方便地集成各种库和工具。

安装FastAPI

FastAPI 依赖 Python 3.8 及更高版本。

安装 FastAPI 很简单,这里我们使用 pip 命令来安装。

pip install fastapi

另外我们还需要一个 ASGI 服务器,生产环境可以使用 Uvicorn 或者 Hypercorn:

pip install "uvicorn[standard]"

运行第一个FastAPI应用

创建一个名为 main.py 的文件,添加以下代码:

from typing import Union

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}

在命令行中运行以下命令以启动应用:

uvicorn main:app --reload

现在,打开浏览器并访问 http://127.0.0.1:8000/docs,你应该能够看到 FastAPI 自动生成的交互式文档,并在根路径 ("/") 返回的 JSON 响应。

代码解析:

  • from fastapi import FastAPI: 这行代码从< code>fastapi 模块中导入了 FastAPI 类。FastAPI 类是 FastAPI 框架的核心,用于创建 FastAPI 应用程序实例。
  • app = FastAPI():这行代码创建了一个 FastAPI 应用实例。与 Flask 不同,FastAPI 不需要传递 __name__ 参数,因为它默认使用当前模块。
  • @app.get("/"): 这是一个装饰器,用于告诉 FastAPI 哪个 URL 应该触发下面的函数,并且指定了 HTTP 方法为 GET。在这个例子中,它指定了根 URL(即网站的主页)。
  • def read_root():: 这是定义了一个名为 read_root 的函数,它将被调用当用户使用 GET 方法访问根 URL 时。
  • return {"Hello": "World"}: 这行代码是 read_root 函数的返回值。当用户使用 GET 方法访问根 URL 时,这个 JSON 对象将被发送回用户的浏览器或 API 客户端。

第一个FastAPI应用

创建一个名为 main.py 的文件,添加以下代码:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

在命令行中运行以下命令以启动应用:

uvicorn main:app --reload

接下来我们来丰富下代码功能,并做具体说明。

以下的 FastAPI 应用,使用了两个路由操作(//items/{item_id}):

from typing import Union

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}

1、创建 FastAPI 实例:

app = FastAPI()

在这一步,创建了一个 FastAPI 应用的实例,它将用于定义和管理应用的各个组件,包括路由。

FastAPI 是 FastAPI 框架的主要类。

2、定义根路径 / 的路由操作:

@app.get("/")
def read_root():
    return {"Hello": "World"}

这个路由操作使用了 @app.get("/") 装饰器,表示当用户通过 HTTP GET 请求访问根路径时,将执行 read_root 函数。函数返回一个包含 {"Hello": "World"} 的字典,这个字典会被 FastAPI 自动转换为 JSON 格式并返回给用户。

3、定义带路径参数和查询参数的路由操作:

@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}

这个路由操作使用了 @app.get("/items/{item_id}") 装饰器,表示当用户通过 HTTP GET 请求访问 /items/{item_id} 路径时,将执行 read_item 函数。

函数接受两个参数:

  • item_id --是路径参数,指定为整数类型。
  • q -- 是查询参数,指定为字符串类型或空(None)。

函数返回一个字典,包含传入的 item_id 和 q 参数。

q 参数通过 Union[str, None] 表示可以是字符串类型或空,这样就允许在请求中不提供 q 参数。

使用浏览器访问 http://127.0.0.1:8000/items/5?q=runoob,你将会看到如下 JSON 响应:

{"item_id": 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 请求测试,避免使用其他工具。

升级实例

接下来我们修改 main.py 文件来从 PUT 请求中接收请求体。

我们借助 Pydantic 来使用标准的 Python 类型声明请求体。

from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    price: float
    is_offer: Union[bool, None] = None


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}


@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
    return {"item_name": item.name, "item_id": item_id}

修改完代码后,我们不需要重启服务器,因为服务器将会自动重载,因为在前面的章节中 uvicorn 命令添加了 --reload 选项。

这时候我们再访问 http://127.0.0.1:8000/docs,交互式 API 文档将会自动更新,并加入新的请求体:

python类型提示符

示例:

def get_full_name(first_name: str, last_name: str):
    full_name = first_name.title() + " " + last_name.title()
    return full_name


print(get_full_name("john", "doe"))
  • 简单类型

    不只是str,你能够声明所有的标准python类型

    • int
    • float
    • bool
    • bytes
    def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes):
        return item_a, item_b, item_c, item_d, item_d, item_e
    
  • 嵌套类型

    有些容器数据结构可以包含其他的值,比如:dictlistsettuple。它们内部的值也会拥有自己的类型

    你可以使用python的typing标准库来申明这些类型以及子类型

    • 列表

      定义一个由str组成的list变量

      typing模块导入List(注意是大写L):

      from typing import List
      
      def process_items(items: List[str]):
          # 表示:变量"items"是一个list,并且这个列表里的每一个元素都是str
          for item in items:
              print(item)
      
    • 元组和集合

      声明tupleset的方法也是一样的:

      from typing import Set, Tuple
      
      def process_items(items_t: Tuple[int, int, str], items_s: Set[bytes]):
          # 变量 items_t 是一个 tuple,其中的前两个元素都是 int 类型, 最后一个元素是 str 类型。
          # 变量 items_s 是一个 set,其中的每个元素都是 bytes 类型
          return items_t, items_s
      
    • 字典

      定义dict时,需要传入两个子类型,用逗号进行分隔

      • 第一个子类型声明dict的所有键
      • 第二个子类型声明dict的所有值
      from typing import Dict
      
      def process_items(prices: Dict[str, float]):
          '''
          	变量 prices 是一个 dict:
      			这个 dict 的所有键为 str 类型(可以看作是字典内每个元素的名称)。
      			这个 dict 的所有值为 float 类型(可以看作是字典内每个元素的价格)。
      	'''
          for item_name, item_price in prices.items():
              print(item_name)
              print(item_price)
      
  • 类作为类型

    也可以将类声明为变量的类型

    假设有一个名为Person的类,拥有name属性:

    class Person:
        def __init__(self, name: str):
            self.name = name
    
    def get_person_name(one_person: Person):
        return one_person.name
    

FastAPI 基本路由

在 FastAPI 中,基本路由是定义 API 端点的关键。每个路由都映射到应用程序中的一个函数,用于处理特定的 HTTP 请求,并返回相应的响应。

FastAPI 请求和响应

在 FastAPI 中,请求(Request)和响应(Response)是与客户端交互的核心。

FastAPI 提供了强大的工具来解析请求数据,并根据需要生成规范的响应。

接下来我们来详细看下 FastAPI 的请求和响应。

请求数据

查询参数

以下实例中我们定义了一个 /items/ 路由,接受两个查询参数 skiplimit,它们的类型均为整数,默认值分别为 010

传递 GET 请求的参数 http://127.0.0.1:8000/items/?skip=1&limit=5

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
def read_item(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

在命令行中运行以下命令以启动应用:

uvicorn main:app --reload
查询参数和字符串校验

FastAPI允许你为参数声明额外的信息和校验

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
async def read_items(q: str | None = None):
    # 查询参数q的;类型为str,默认值为None,所有是可选的
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

额外的校验

路径参数

我们可以把参数设置在路径上,这样 URL 看起来更美观一些。

传递 GET 请求的参数 http://127.0.0.1:8000/items/5/?q=runoob

以下实例我们定义了一个带有路径参数 item_id 和查询参数 q 的路由。

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}

注意:

有时,路径操作中的路径是写死的

比如要使用/user/me获取当前用户的数据

然后还要使用/user/{user_id}, 通过用户id获取指定用户的数据

由于路径操作是按顺序依次运行的,因此,一定要在/user/{user_id}之前声明/user/me

否则,/users/{user_id} 将匹配 /users/me,FastAPI 会认为正在接收值为 "me"user_id 参数。

from fastapi import FastAPI

app = FastAPI()

@app.get("/users/me")
async def read_user_me():
    return {"user_id": "the current user"}

@app.get("/users/{user_id}")
async def read_user(user_id: str):
    return {"user_id": user_id}
预设值

路径操作使用python的Enum类型接收预设的路径参数

创建Enum类型

  • 导入 Enum 并创建继承自 strEnum 的子类。

  • 通过从 str 继承,API 文档就能把值的类型定义为字符串,并且能正确渲染。

  • 然后,创建包含固定值的类属性,这些固定值是可用的有效值:

    from enum import Enum
    
    from fastapi import FastAPI
    
    class ModelName(str, Enum):
        alexnet = "alexnet"
        resnet = "resnet"
        lenet = "lenet"
    
    app = FastAPI()
    
    @app.get("/models/{model_name}")
    async def get_model(model_name: ModelName):
        if model_name is ModelName.alexnet:
            return {"model_name": model_name, "message": "Deep Learning FTW!"}
    
        if model_name.value == "lenet":
            return {"model_name": model_name, "message": "LeCNN all the images"}
    
        return {"model_name": model_name, "message": "Have some residuals"}
    

响应数据

返回JSON数据

路由处理函数返回一个字典,该字典将被 FastAPI 自动转换为 JSON 格式,并作为响应发送给客户端:

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
def read_item(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

返回Pydantic模型

路由处理函数返回一个 Pydantic 模型实例,FastAPI 将自动将其转换为 JSON 格式,并作为响应发送给客户端:

from pydantic import BaseModel
from fastapi import FastAPI

app = FastAPI()
class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None

@app.post("/items/")
def create_item(item: Item):
    return item

POST 请求,返回的数据格式如下所示:

{
  "name": "runoob",
  "description": "菜鸟教程 POST 测试",
  "price": 12,
  "tax": 1
}

使用 Header 和 Cookie 类型注解获取请求头和 Cookie 数据。

from fastapi import Header, Cookie
from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
def read_item(user_agent: str = Header(None), session_token: str = Cookie(None)):
    return {"User-Agent": user_agent, "Session-Token": session_token}

以上代码在浏览器访问 http://127.0.0.1:8000/items/42/ 页面显示如下,可以看到我们自定义的响应头:

FastAPI Pydantic 模型

Pydantic 是一个用于数据验证和序列化的 Python 模型库。

它在 FastAPI 中广泛使用,用于定义请求体、响应体和其他数据模型,提供了强大的类型检查和自动文档生成功能。

以下是关于 Pydantic 模型的详细介绍:

1. 定义 Pydantic 模型

使用 Pydantic 定义一个模型非常简单,只需创建一个继承自 pydantic.BaseModel 的类,并在其中定义字段。字段的类型可以是任何有效的 Python 类型,也可以是 Pydantic 内置的类型。

from pydantic import BaseModel

class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None

以上代码中中,我们定义了一个名为 Item 的 Pydantic 模型,包含了四个字段:name、description、price 和 tax,name 和 price 是必需的字段,而 description 和 tax 是可选的字段,其默认值为 None。

2. 使用 Pydantic 模型

2.1 请求体验证

在 FastAPI 中,可以将 Pydantic 模型用作请求体(Request Body),以自动验证和解析客户端发送的数据。

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None

@app.post("/items/")
def create_item(item: Item):
    return item

2.2 查询参数验证

Pydantic 模型还可以用于验证查询参数、路径参数等。

from fastapi import FastAPI, Query
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None

@app.get("/items/")
def read_item(item: Item, q: str = Query(..., max_length=10)):
    return {"item": item, "q": q}

以上代码中,read_item 路由处理函数接受一个 Item 模型的实例作为查询参数,以及一个名为 q 的字符串查询参数。通过使用 Query 函数,我们还可以为查询参数指定更多的验证规则,如最大长度限制。

3. 自动文档生成

使用 Pydantic 模型的一个重要优势是,它能够自动为 FastAPI 生成交互式 API 文档。文档会包括模型的字段、类型、验证规则等信息,让开发者和 API 使用者能够清晰地了解如何正确使用 API。

打开 http://127.0.0.1:8000/docs,API 文档显示如下:

4. 数据转换和序列化

Pydantic 模型不仅提供了验证功能,还可以用于将数据转换为特定类型(例如 JSON)或反向序列化。在 FastAPI 中,这种能力是自动的,你无需手动处理。

通过使用 Pydantic 模型,你可以更轻松地定义和验证数据,使得代码更清晰、更健壮,并通过自动生成的文档提供更好的 API 交互体验。

接下来我们可以打开 http://127.0.0.1:8000/docs 来进行 POST 测试:

填写请求参数:

FastAPI 路径操作依赖项

FastAPI 提供了简单易用,但功能强大的依赖注入系统,这个依赖系统设计的简单易用,可以让开发人员轻松地把组件集成至 FastAPI。

FastAPI 提供了路径操作依赖项(Path Operation Dependencies)的机制,允许你在路由处理函数执行之前或之后运行一些额外的逻辑。

依赖项就是一个函数,且可以使用与路径操作函数相同的参数。

路径操作依赖项提供了一种灵活的方式来组织代码、验证输入、进行身份验证等。

接下来我们会具体介绍 FastAPI 路径操作依赖项的相关知识点。


1、依赖项(Dependencies)

依赖项是在路由操作函数执行前或后运行的可复用的函数或对象。

它们被用于执行一些通用的逻辑,如验证、身份验证、数据库连接等。在 FastAPI 中,依赖项通常用于两个方面:

  • 预处理(Before)依赖项: 在路由操作函数执行前运行,用于预处理输入数据,验证请求等。
  • 后处理(After)依赖项: 在路由操作函数执行后运行,用于执行一些后处理逻辑,如日志记录、清理等。

1.1 依赖注入

依赖注入是将依赖项注入到路由操作函数中的过程。

在 FastAPI 中,通过在路由操作函数参数中声明依赖项来实现依赖注入。

FastAPI 将负责解析依赖项的参数,并确保在执行路由操作函数之前将其传递给函数。

1.2 依赖项的使用

定义依赖项:

from fastapi import Depends, FastAPI

app = FastAPI()

\# 依赖项函数
def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
  return {"q": q, "skip": skip, "limit": limit}

在这个例子中,common_parameters 是一个依赖项函数,用于预处理查询参数。

在路由中使用依赖项:

from fastapi import Depends

\# 路由操作函数
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
  return commons

在这个例子中,read_items 路由操作函数中的参数 commons 使用了 Depends(common_parameters),表示 common_parameters 是一个依赖项。FastAPI 将在执行路由操作函数之前运行 common_parameters 函数,并将其返回的结果传递给 read_items 函数。


2、路径操作依赖项的基本使用

2.1 预处理(Before)

以下实例中,common_parameters 是一个依赖项函数,它接受查询参数 q、skip 和 limit,并返回一个包含这些参数的字典。

在路由操作函数 read_items 中,通过传入 Depends(common_parameters),我们使用了这个依赖项函数,实现了在路由执行前预处理输入数据的功能。

from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()

# 依赖项函数
def common_parameters(q: str = 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

2.2 后处理(After)

以下例子中,after_request 是一个后处理函数,用于在路由执行后执行一些逻辑。

在路由操作函数 read_items_after 中,通过传入 Depends(after_request),我们使用了这个后处理依赖项,实现了在路由执行后进行额外操作的功能。

from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()

# 依赖项函数
def common_parameters(q: str = 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

# 后处理函数
async def after_request():
    # 这里可以执行一些后处理逻辑,比如记录日志
    pass

# 后处理依赖项
@app.get("/items/", response_model=dict)
async def read_items_after(request: dict = Depends(after_request)):
    return {"message": "Items returned successfully"}

3、多个依赖项的组合

以下例子中,common_parametersverify_token 是两个不同的依赖项函数,verify_token 依赖于 common_parameters,这种组合依赖项的方式允许我们在路由执行前先验证一些参数,然后在进行身份验证。

from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()

# 依赖项函数1
def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}

# 依赖项函数2
def verify_token(token: str = Depends(common_parameters)):
    if token is None:
        raise HTTPException(status_code=400, detail="Token required")
    return token

# 路由操作函数
@app.get("/items/")
async def read_items(token: dict = Depends(verify_token)):
    return token

4、异步依赖项

依赖项函数和后处理函数可以是异步的,允许在它们内部执行异步操作。

以下例子中,get_token 是一个异步的依赖项函数,模拟了一个异步操作。

在路由操作函数 read_items 中,我们使用了这个异步依赖项函数。

from fastapi import Depends, FastAPI, HTTPException
from typing import Optional
import asyncio

app = FastAPI()

# 异步依赖项函数
async def get_token():
    # 模拟异步操作
    await asyncio.sleep(2)
    return "fake-token"

# 异步路由操作函数
@app.get("/items/")
async def read_items(token: Optional[str] = Depends(get_token)):
    return {"token": token}

通过使用路径操作依赖项,你可以在路由执行前或后执行额外的逻辑,从而实现更灵活、可组合的代码组织方式。

FastAPI 表单数据

在 FastAPI 中,接收表单数据是一种常见的操作,通常用于处理用户通过 HTML 表单提交的数据。

FastAPI 提供了 Form 类型,可以用于声明和验证表单数据。

1、声明表单数据模型

接下来我们设计一个接收一个登陆的表单数据,要使用表单,需预先安装 python-multipart:

pip install python-multipart。
from fastapi import FastAPI, Form

app = FastAPI()


@app.post("/login/")
async def login(username: str = Form(), password: str = Form()):
    return {"username": username}

接下来我们可以进入 API 文档 http://127.0.0.1:8000/docs 进行测验:

使用 Pydantic 模型来声明表单数据模型。

在模型中,使用 Field 类型声明每个表单字段,并添加必要的验证规则。

from pydantic import BaseModel, Field

class Item(BaseModel):
    name: str = Field(..., title="Item Name", max_length=100)
    description: str = Field(None, title="Item Description", max_length=255)
    price: float = Field(..., title="Item Price", gt=0)

以上例子中,Item 是一个 Pydantic 模型,用于表示表单数据。

模型中的字段 name、description 和 price 分别对应表单中的不同输入项,并设置了相应的验证规则。

除了可以在 API 文档中测验,另外我们也可以自己创建 html 来测试:

<form action="http://localhost:8000/items/" method="post">
    <label for="name">Name:</label>
    <input type="text" id="name" name="name" required>
    <br>
    <label for="description">Description:</label>
    <textarea id="description" name="description"></textarea>
    <br>
    <label for="price">Price:</label>
    <input type="number" id="price" name="price" required min="0">
    <br>
    <button type="submit">Submit</button>
</form>

2、在路由中接收表单数据

在路由操作函数中,可以使用 Form 类型来接收表单数据。

Form 类型的参数可以与 Pydantic 模型的字段一一对应,以实现表单数据的验证和转换。

from fastapi import FastAPI, Form

app = FastAPI()

# 路由操作函数
@app.post("/items/")
async def create_item(
    name: str = Form(...),
    description: str = Form(None),
    price: float = Form(..., gt=0),
):
    return {"name": name, "description": description, "price": price}

以上例子中,create_item 路由操作函数接收了三个表单字段:name、description 和 price,这些字段与 Item 模型的相应字段一致,FastAPI 将自动根据验证规则验证表单数据。

接下来我们可以进入 API 文档 http://127.0.0.1:8000/docs 进行测验:

3、处理文件上传

如果表单包含文件上传,可以使用 UploadFile 类型处理。

以下是一个处理文件上传的实例:

from fastapi import FastAPI, File, UploadFile

app = FastAPI()

# 路由操作函数
@app.post("/files/")
async def create_file(file: UploadFile = File(...)):
    return {"filename": file.filename}

在这个例子中,create_file 路由操作函数接收了一个 UploadFile 类型的文件参数。

FastAPI 将负责处理文件上传,并将文件的相关信息包装在 UploadFile 对象中,可以轻松地获取文件名、内容类型等信息。

通过上述方式,FastAPI 提供了一种简单而强大的方法来接收和处理表单数据,同时保持了代码的清晰性和可维护性。

FastAPI开发项目的目录结构

使用fastAPI开发项目时,良好的目录结构可以帮助我们更好的组织底阿妈,提高可维护性和扩展性。同样,对基类的封装,也可以进一步减少开发代码,提供便利,并减少出错率。

my_fastapi_project/
├── app/
│   ├── __init__.py
│   ├── main.py            # 入口文件
│   ├── core/
│   │   ├── __init__.py
│   │   ├── config.py      # 配置文件
│   │   ├── security.py    # 安全相关
│   │   └── ...            # 其他核心功能
│   ├── api/
│   │   ├── __init__.py
│   │   ├── v1/
│   │   │   ├── __init__.py
│   │   │   ├── endpoints/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── users.py     # 用户相关接口
│   │   │   │   ├── items.py     # 其他接口
│   │   │   │   └── ...
│   │   │   └── ...              # 其他版本的API
│   ├── models/
│   │   ├── __init__.py
│   │   ├── user.py         # 用户模型
│   │   ├── item.py         # 其他模型
│   │   └── ...
│   ├── schemas/
│   │   ├── __init__.py
│   │   ├── user.py         # 用户数据模型
│   │   ├── item.py         # 其他数据模型
│   │   └── ...
│   ├── crud/
│   │   ├── __init__.py
│   │   ├── user.py         # 用户CRUD操作
│   │   ├── item.py         # 其他CRUD操作
│   │   └── ...
│   ├── db/
│   │   ├── __init__.py
│   │   ├── base.py         # 数据库基础设置
│   │   ├── session.py      # 数据库会话
│   │   └── ...
│   ├── tests/
│   │   ├── __init__.py
│   │   ├── test_main.py    # 测试主文件
│   │   ├── test_users.py   # 用户相关测试
│   │   └── ...
│   └── utils/
│       ├── __init__.py
│       ├── utils.py        # 工具函数
│       └── ...
├── .env                    # 环境变量文件
├── alembic/                # 数据库迁移工具目录
│   ├── env.py
│   ├── script.py.mako
│   └── versions/
│       └── ...
├── alembic.ini             # Alembic 配置文件
├── requirements.txt        # 项目依赖
├── Dockerfile              # Docker 配置文件
└── README.md               # 项目说明文件

目录结构说明

  • app/: 项目的主目录,包含所有应用相关代码。
    • main.py: 项目的入口文件,启动FastAPI应用。
    • core/: 核心功能,如配置、安全等。
    • api/: API路由和视图,分版本管理。
    • models/: 数据库模型。
    • schemas/: 数据模型,用于请求和响应的验证。
    • crud/: 数据库操作(CRUD:创建、读取、更新、删除)。
    • db/: 数据库相关设置和会话管理。
    • tests/: 测试代码。
    • utils/: 工具函数和公用模块。
  • .env: 环境变量文件,用于存储敏感信息,如数据库连接字符串。
  • alembic/: 数据库迁移工具Alembic的配置目录。
  • requirements.txt: 项目依赖列表。
  • Dockerfile: Docker配置文件,用于容器化部署。
  • README.md: 项目说明文件。

这个结构可以根据需求进行调整,但保持清晰和模块化是良好的实践。

python项目总的__init__.py,有意义吗?

在python项目中,__init__.py文件的主要作用是将目录标识为一个python包。它使得目录中的模块可以被导入和使用,在一下情况下__init__.py可以不仅仅是一个空文件,还可以包含一些初始化代码。

__init__.py的意义:

  • 将目录标识为包:

    任何包含__init__.py的目录都会被python解释器认为是一个包,这样就可使用导入语法,如

    import mypackage.module

  • 初始化代码:

    可以在__init__.py中包含一些初始化代码,如导入包内的子模块,设置包级别的变量或函数,配置日志等记录等。例如:

    # mypackage/__init__.py
    from .submodule1 import func1
    from .submodule2 import func2
    
    __all__ = ["func1", "func2"]
    
  • 简化导入

    通过在__init__.py中导入子模块,可以简化包的导入路径,使得用户可以直接从包中导入函数或类,而不必知道具体的模块结构

    # mypackage/__init__.py
    from .submodule import MyClass
    
    # Now you can do
    from mypackage import MyClass
    

对于Python 3.3及以上版本,__init__.py 文件不是强制性的,即使没有 __init__.py 文件,Python解释器也可以识别包。然而,添加 __init__.py 文件仍然是一个良好的习惯,可以避免某些情况下的意外行为,并且明确表示该目录是一个包。

Fast API项目的开发处理过程

在FastAPI 项目中,,CRUD操作通常在一个专门的 crud 模块中实现。这个模块会调用SQLAlchemy模型对象来进行数据库操作。

  1. 定义模型(model/user.py)

    from sqlalchemy import Column, Integer, String
    from app.db.base_class import Base
    
    class User(Base):
        __tablename__ = "users"
        
        id = Column(Integer, primary_key=True, index=True)
        email = Column(String, unique=True, index=True, nullable=False)
        hashed_password = Column(String, nullable=False)
        full_name = Column(String, index=True)
    
  2. 创建数据库会话(db/session.py)

    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker
    
    DATABASE_URL = "sqlite:///./test.db"  # 使用SQLite数据库作为示例
    
    engine = create_engine(DATABASE_URL)
    SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
    
  3. 定义CRUD操作

    from sqlalchemy.orm import Session
    from app.models.user import User
    from app.schemas.user import UserCreate, UserUpdate
    
    def get_user(db: Session, user_id: int):
        return db.query(User).filter(User.id == user_id).first()
    
    def get_user_by_email(db: Session, email: str):
        return db.query(User).filter(User.email == email).first()
    
    def get_users(db: Session, skip: int = 0, limit: int = 10):
        return db.query(User).offset(skip).limit(limit).all()
    
    def create_user(db: Session, user: UserCreate):
        db_user = User(
            email=user.email,
            hashed_password=user.hashed_password,  # 在实际应用中应该对密码进行哈希处理
            full_name=user.full_name
        )
        db.add(db_user)
        db.commit()
        db.refresh(db_user)
        return db_user
    
    def update_user(db: Session, user_id: int, user: UserUpdate):
        db_user = get_user(db, user_id)
        if db_user:
            db_user.email = user.email
            db_user.full_name = user.full_name
            db.commit()
            db.refresh(db_user)
        return db_user
    
    def delete_user(db: Session, user_id: int):
        db_user = get_user(db, user_id)
        if db_user:
            db.delete(db_user)
            db.commit()
        return db_user
    
  4. 定义数据模型(schemas/user.py)

    from pydantic import BaseModel
    
    class UserBase(BaseModel):
        email: str
        full_name: str = None
    
    class UserCreate(UserBase):
        hashed_password: str
    
    class UserUpdate(UserBase):
        pass
    
    class User(UserBase):
        id: int
    
        class Config:
            orm_mode = True
    
  5. 在API端点中使用CRUD操作(api/v1/endpoints/users.py)

    from fastapi import APIRouter, Depends, HTTPException
    from sqlalchemy.orm import Session
    from app import crud, models, schemas
    from app.db.session import SessionLocal
    
    router = APIRouter()
    
    def get_db():
        db = SessionLocal()
        try:
            yield db
        finally:
            db.close()
    
    @router.post("/users/", response_model=schemas.User)
    def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
        db_user = crud.get_user_by_email(db, email=user.email)
        if db_user:
            raise HTTPException(status_code=400, detail="Email already registered")
        return crud.create_user(db=db, user=user)
    
    @router.get("/users/{user_id}", response_model=schemas.User)
    def read_user(user_id: int, db: Session = Depends(get_db)):
        db_user = crud.get_user(db, user_id=user_id)
        if db_user is None:
            raise HTTPException(status_code=404, detail="User not found")
        return db_user
    
    @router.put("/users/{user_id}", response_model=schemas.User)
    def update_user(user_id: int, user: schemas.UserUpdate, db: Session = Depends(get_db)):
        db_user = crud.update_user(db=db, user_id=user_id, user=user)
        if db_user is None:
            raise HTTPException(status_code=404, detail="User not found")
        return db_user
    
    @router.delete("/users/{user_id}", response_model=schemas.User)
    def delete_user(user_id: int, db: Session = Depends(get_db)):
        db_user = crud.delete_user(db=db, user_id=user_id)
        if db_user is None:
            raise HTTPException(status_code=404, detail="User not found")
        return db_user
    
    @router.get("/users/", response_model=List[schemas.User])
    def read_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
        users = crud.get_users(db, skip=skip, limit=limit)
        return users
    
  6. 注册路由(main.py)

    from fastapi import FastAPI
    from app.api.v1.endpoints import users
    
    app = FastAPI()
    
    app.include_router(users.router, prefix="/api/v1", tags=["users"])
    
    if __name__ == "__main__":
        import uvicorn
        uvicorn.run(app, host="0.0.0.0", port=8000)
    
  7. 初始化数据库(db/base.py)

    from app.db.session import engine
    from app.models import user
    
    user.Base.metadata.create_all(bind=engine)
    
  8. 运行应用

    在根目录下运行

    uvicorn app.main:app --reload
    

实际FastAPI项目对基类的封装

可以通过创建一个通用的CURD基类来封装常规的CRUD操作,然后让特定的CRUD继承这个基类。这样可以减少重复代码,提高代码的可维护性和可复用性。

  1. 创建通用CRUD基类(crud/base.py)

    from typing import Generic, Type, TypeVar, Optional, List
    from pydantic import BaseModel
    from sqlalchemy.orm import Session
    from app.db.base_class import Base
    
    ModelType = TypeVar("ModelType", bound=Base)
    CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
    UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)
    
    class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
        def __init__(self, model: Type[ModelType]):
            self.model = model
    
        def get(self, db: Session, id: int) -> Optional[ModelType]:
            return db.query(self.model).filter(self.model.id == id).first()
    
        def get_multi(self, db: Session, skip: int = 0, limit: int = 100) -> List[ModelType]:
            return db.query(self.model).offset(skip).limit(limit).all()
    
        def create(self, db: Session, obj_in: CreateSchemaType) -> ModelType:
            obj_in_data = obj_in.dict()
            db_obj = self.model(**obj_in_data)  # type: ignore
            db.add(db_obj)
            db.commit()
            db.refresh(db_obj)
            return db_obj
    
        def update(self, db: Session, db_obj: ModelType, obj_in: UpdateSchemaType) -> ModelType:
            obj_data = db_obj.dict()
            update_data = obj_in.dict(skip_defaults=True)
            for field in obj_data:
                if field in update_data:
                    setattr(db_obj, field, update_data[field])
            db.commit()
            db.refresh(db_obj)
            return db_obj
    
        def remove(self, db: Session, id: int) -> ModelType:
            obj = db.query(self.model).get(id)
            db.delete(obj)
            db.commit()
            return obj
    
  2. 定义用户CRUD操作(crud/user.py)

    from typing import Any
    from sqlalchemy.orm import Session
    from app.crud.base import CRUDBase
    from app.models.user import User
    from app.schemas.user import UserCreate, UserUpdate
    
    class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
        def get_by_email(self, db: Session, email: str) -> Any:
            return db.query(self.model).filter(self.model.email == email).first()
    
    user = CRUDUser(User)
    
  3. 定义数据类型(schemas/user.py)

    from pydantic import BaseModel
    
    class UserBase(BaseModel):
        email: str
        full_name: str = None
    
    class UserCreate(UserBase):
        hashed_password: str
    
    class UserUpdate(UserBase):
        pass
    
    class User(UserBase):
        id: int
    
        class Config:
            orm_mode = True
    
  4. 在API端点中使用CRUD操作(api/v1/endpoints/users.py)

    from fastapi import APIRouter, Depends, HTTPException
    from sqlalchemy.orm import Session
    from typing import List
    from app import crud, schemas
    from app.db.session import SessionLocal
    from app.models.user import User
    
    router = APIRouter()
    
    def get_db():
        db = SessionLocal()
        try:
            yield db
        finally:
            db.close()
    
    @router.post("/users/", response_model=schemas.User)
    def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
        db_user = crud.user.get_by_email(db, email=user.email)
        if db_user:
            raise HTTPException(status_code=400, detail="Email already registered")
        return crud.user.create(db=db, obj_in=user)
        
    
  5. 在API端点中使用CRUD操作(api/v1/endpoints/users.py)

    from fastapi import APIRouter, Depends, HTTPException
    from sqlalchemy.orm import Session
    from typing import List
    from app import crud, schemas
    from app.db.session import SessionLocal
    from app.models.user import User
    
    router = APIRouter()
    
    def get_db():
        db = SessionLocal()
        try:
            yield db
        finally:
            db.close()
    
    @router.post("/users/", response_model=schemas.User)
    def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
        db_user = crud.user.get_by_email(db, email=user.email)
        if db_user:
            raise HTTPException(status_code=400, detail="Email already registered")
        return crud.user.create(db=db, obj_in=user)
       
    @router.get("/users/{user_id}", response_model=schemas.User)
    def read_user(user_id: int, db: Session = Depends(get_db)):
        db_user = crud.user.get(db, id=user_id)
        if db_user is None:
            raise HTTPException(status_code=404, detail="User not found")
        return db_user
    
    @router.put("/users/{user_id}", response_model=schemas.User)
    def update_user(user_id: int, user: schemas.UserUpdate, db: Session = Depends(get_db)):
        db_user = crud.user.get(db=db, id=user_id)
        if db_user is None:
            raise HTTPException(status_code=404, detail="User not found")
        return crud.user.update(db=db, db_obj=db_user, obj_in=user)
    
    @router.delete("/users/{user_id}", response_model=schemas.User)
    def delete_user(user_id: int, db: Session = Depends(get_db)):
        db_user = crud.user.get(db=db, id=user_id)
        if db_user is None:
            raise HTTPException(status_code=404, detail="User not found")
        return crud.user.remove(db=db, id=user_id)
    
    @router.get("/users/", response_model=List[schemas.User])
    def read_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
        users = crud.user.get_multi(db, skip=skip, limit=limit)
        return users
    

其他的就是类似前面的做法了。

通过这种方式,你可以在通用的CRUD基类中封装常规的CRUD操作,而特定的CRUD类(如 CRUDUser)只需要继承这个基类并添加特定的操作方法。这样不仅减少了重复代码,也提高了代码的可维护性和可复用性。

如果你希望可以通过定义一个通用的API基类来封装常规的CRUD操作方法,然后在具体的端点文件中继承这个基类。这样可以进一步减少重复代码,提高代码的可维护性和可复用性。

创建通用API基类 (api/deps.py)

from typing import Type, TypeVar, Generic, List
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from pydantic import BaseModel
from app.crud.base import CRUDBase
from app.db.session import SessionLocal

ModelType = TypeVar("ModelType", bound=BaseModel)
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)

class CRUDRouter(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
    def __init__(self, crud: CRUDBase[ModelType, CreateSchemaType, UpdateSchemaType]):
        self.crud = crud
        self.router = APIRouter()

        self.router.post("/", response_model=ModelType)(self.create_item)
        self.router.get("/{item_id}", response_model=ModelType)(self.read_item)
        self.router.put("/{item_id}", response_model=ModelType)(self.update_item)
        self.router.delete("/{item_id}", response_model=ModelType)(self.delete_item)
        self.router.get("/", response_model=List[ModelType])(self.read_items)

    def get_db(self):
        db = SessionLocal()
        try:
            yield db
        finally:
            db.close()

    async def create_item(self, item_in: CreateSchemaType, db: Session = Depends(self.get_db)):
        db_item = self.crud.create(db=db, obj_in=item_in)
        return db_item

    async def read_item(self, item_id: int, db: Session = Depends(self.get_db)):
        db_item = self.crud.get(db=db, id=item_id)
        if not db_item:
            raise HTTPException(status_code=404, detail="Item not found")
        return db_item


    async def update_item(self, item_id: int, item_in: UpdateSchemaType, db: Session = Depends(self.get_db)):
        db_item = self.crud.get(db=db, id=item_id)
        if not db_item:
            raise HTTPException(status_code=404, detail="Item not found")
        return self.crud.update(db=db, db_obj=db_item, obj_in=item_in)

    async def delete_item(self, item_id: int, db: Session = Depends(self.get_db)):
        db_item = self.crud.get(db=db, id=item_id)
        if not db_item:
            raise HTTPException(status_code=404, detail="Item not found")
        return self.crud.remove(db=db, id=item_id)

    async def read_items(self, skip: int = 0, limit: int = 10, db: Session = Depends(self.get_db)):
        items = self.crud.get_multi(db=db, skip=skip, limit=limit)
        return items

使用通用API基类定义用户端点(api/v1/endpoints/users.py)

from fastapi import APIRouter
from app.crud.user import user as user_crud
from app.schemas.user import User, UserCreate, UserUpdate
from app.api.deps import CRUDRouter

user_router = CRUDRouter[User, UserCreate, UserUpdate](user_crud)
router = user_router.router

注册路由 (main.py)

rom fastapi import FastAPI
from app.api.v1.endpoints import users

app = FastAPI()

app.include_router(users.router, prefix="/api/v1/users", tags=["users"])

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

通过这种方式,你可以在 CRUDRouter 基类中封装常规的CRUD操作方法,然后在具体的端点文件中继承这个基类并传递相应的CRUD对象。这样可以进一步减少重复代码,提高代码的可维护性和可复用性。

SQLAlchemy模型的基类定义

app.db.base_class 通常是用于定义SQLAlchemy模型基类的文件。在这个文件中,我们会定义一个基本的Base类,这个类是所有SQLAlchemy模型的基类。下面是一个实现示例:

定义 Base 类 (db/base_class.py)

from sqlalchemy.ext.declarative import as_declarative, declared_attr

@as_declarative()
class Base:
    id: int
    __name__: str

    @declared_attr
    def __tablename__(cls) -> str:
        return cls.__name__.lower()

详细解释

  1. @as_declarative(): 这是SQLAlchemy提供的一个装饰器,它会将类装饰为一个声明性基类。所有继承自这个类的子类都会自动成为声明性类。
  2. id: int: 这是一个类型注释,表示每个模型类都会有一个 id 属性。具体的字段定义(例如 Column(Integer, primary_key=True))会在每个具体的模型类中定义。
  3. __name__: str: 这是另一个类型注释,表示每个模型类都会有一个 __name__ 属性。
  4. @declared_attr: 这是SQLAlchemy提供的一个装饰器,允许我们为声明性基类定义一些通用的属性。在这个例子中,它用于自动生成 __tablename__ 属性。这个属性的值是模型类的名称的小写形式。

这样定义的 Base 类可以作为所有SQLAlchemy模型的基类,简化模型的定义。

完整示例项目结构:为了更好地理解,这里展示一个包含 Base 类定义的完整项目结构:

.
├── app
│   ├── __init__.py
│   ├── api
│   │   ├── __init__.py
│   │   └── v1
│   │       ├── __init__.py
│   │       └── endpoints
│   │           ├── __init__.py
│   │           └── users.py
│   ├── crud
│   │   ├── __init__.py
│   │   ├── base.py
│   │   └── user.py
│   ├── db
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── base_class.py
│   │   └── session.py
│   ├── models
│   │   ├── __init__.py
│   │   └── user.py
│   ├── schemas
│   │   ├── __init__.py
│   │   └── user.py
│   └── main.py

models/user.py 类文件如下定义

from sqlalchemy import Column, Integer, String
from app.db.base_class import Base

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    email = Column(String, unique=True, index=True, nullable=False)
    hashed_password = Column(String, nullable=False)
    full_name = Column(String, index=True)
posted @ 2024-12-25 22:33  小郑[努力版]  阅读(209)  评论(0)    收藏  举报