项目结构有很多种,但最好的结构是一致、直观且没有意外的。
许多示例项目和教程按文件类型(如crud、routers、models)划分项目,这种方式对于微服务或范围较小的项目很有效。但是,这种方法并不适合我们这个包含许多领域和模块的单体应用。
我发现对于这类情况,更具可扩展性和可演进性的结构是受Netflix的Dispatch启发,并做了一些小修改。
fastapi-project
├── alembic/
├── src
│ ├── auth
│ │ ├── router.py
│ │ ├── schemas.py # pydantic模型
│ │ ├── models.py # 数据库模型
│ │ ├── dependencies.py
│ │ ├── config.py # 本地配置
│ │ ├── constants.py
│ │ ├── exceptions.py
│ │ ├── service.py
│ │ └── utils.py
│ ├── aws
│ │ ├── client.py # 用于外部服务通信的客户端模型
│ │ ├── schemas.py
│ │ ├── config.py
│ │ ├── constants.py
│ │ ├── exceptions.py
│ │ └── utils.py
│ └── posts
│ │ ├── router.py
│ │ ├── schemas.py
│ │ ├── models.py
│ │ ├── dependencies.py
│ │ ├── constants.py
│ │ ├── exceptions.py
│ │ ├── service.py
│ │ └── utils.py
│ ├── config.py # 全局配置
│ ├── models.py # 全局模型
│ ├── exceptions.py # 全局异常
│ ├── pagination.py # 全局模块,如分页
│ ├── database.py # 数据库连接相关内容
│ └── main.py
├── tests/
│ ├── auth
│ ├── aws
│ └── posts
├── templates/
│ └── index.html
├── requirements
│ ├── base.txt
│ ├── dev.txt
│ └── prod.txt
├── .env
├── .gitignore
├── logging.ini
└── alembic.ini
src文件夹中
src/ - 应用的最高级别,包含通用模型、配置和常量等。src/main.py - 项目的根文件,用于初始化FastAPI应用router.py - 每个模块的核心,包含所有端点schemas.py - 用于pydantic模型models.py - 用于数据库模型service.py - 模块特定的业务逻辑dependencies.py - 路由依赖项constants.py - 模块特定的常量和错误代码config.py - 例如环境变量utils.py - 非业务逻辑函数,例如响应规范化、数据丰富等exceptions.py - 模块特定的异常,例如PostNotFound、InvalidUserDatafrom src.auth import constants as auth_constants
from src.notifications import service as notification_service
from src.posts.constants import ErrorCode as PostsErrorCode # 以防每个包的constants模块中都有标准的ErrorCode
FastAPI首先是一个异步框架。它设计用于处理异步I/O操作,这也是它如此快速的原因。
然而,FastAPI并不限制你只能使用async路由,开发者也可以使用同步路由。这可能会让初学者误以为它们是一样的,但实际上并非如此。
在底层,FastAPI可以有效地处理异步和同步I/O操作。
async,那么它会通过await正常调用,FastAPI相信你只会执行非阻塞的I/O操作。需要注意的是,如果你违反了这种信任,在异步路由中执行阻塞操作,事件循环将无法在阻塞操作完成之前运行后续任务。
import asyncio
import time
from fastapi import APIRouter
router = APIRouter()
@router.get("/terrible-ping")
async def terrible_ping():
time.sleep(10) # 10秒的I/O阻塞操作,整个进程都会被阻塞
return {"pong": True}
@router.get("/good-ping")
def good_ping():
time.sleep(10) # 10秒的I/O阻塞操作,但在单独的线程中运行整个`good_ping`路由
return {"pong": True}
@router.get("/perfect-ping")
async def perfect_ping():
await asyncio.sleep(10) # 非阻塞I/O操作
return {"pong": True}