FastAPI清洁架构实践:从分层设计到可维护项目搭建

news2026/5/15 8:02:24
1. 项目概述一个为FastAPI项目设立的“洁净室”当你开始一个新的FastAPI项目时面对的是一个空白的画布。理论上你可以自由地绘制任何架构但现实往往是随着第一个路由、第一个数据库模型、第一个业务逻辑的加入代码便开始以一种难以预料的方式“生长”。几周后你可能会发现业务逻辑和数据库查询在路由处理函数里纠缠不清单元测试变得举步维艰添加新功能时总担心会碰坏旧代码。这种“面条式”代码的蔓延几乎是每个后端项目都会经历的阵痛。fastapi-clean-example这个项目就是针对这一痛点的一剂“预防针”。它不是一个功能完备的生产级应用而是一个架构范本或项目脚手架。其核心价值在于它预先定义并实现了一套清晰、可维护的代码组织结构即所谓的“清洁架构”Clean Architecture或“六边形架构”Hexagonal Architecture思想在FastAPI中的实践。它为你展示了一个FastAPI项目“应该长什么样”而不是“能做什么”。简单来说这个项目回答了以下几个关键问题代码应该放在哪里是全部堆在main.py里还是按功能模块分目录fastapi-clean-example给出了一个明确的目录结构。依赖关系应该如何流动是路由直接调用数据库还是通过中间层该项目清晰地展示了“依赖倒置”原则即高层模块如API接口不依赖于低层模块如数据库二者都依赖于抽象如接口。如何编写可测试的代码通过将业务逻辑与框架FastAPI、数据库SQLAlchemy解耦使得核心逻辑可以脱离Web框架和数据库进行单元测试。如何管理配置、依赖注入和异常项目提供了这些横切关注点Cross-Cutting Concerns的标准处理方式。它适合的人群非常明确已经熟悉FastAPI基础但希望提升项目结构、代码质量和长期可维护性的开发者。对于初学者它是一个极佳的学习样板对于有经验的开发者它是一个可以快速借鉴并应用于自己项目的参考实现。2. 架构核心依赖流向与层间解耦理解fastapi-clean-example的关键在于理解其各层之间的职责划分与依赖关系。这不是简单的“分几个文件夹”而是一套有严格规则的通信协议。2.1 经典分层解析该项目通常采用经典的四层结构依赖关系是单向的从外向内。第一层API / 表现层 (Presentation Layer)位置api/或web/目录下的路由文件。职责接收HTTP请求解析参数路径、查询、体验证数据格式通常借助Pydantic调用下一层服务层的业务逻辑并将业务层的返回结果转换为HTTP响应JSON。它不应该包含任何业务规则或数据访问逻辑。关键实现这里大量使用FastAPI的Depends进行依赖注入。例如一个路由处理函数依赖于一个“服务”类这个服务类的实例由依赖注入容器在请求生命周期内自动提供。# 示例api/v1/items.py from fastapi import APIRouter, Depends from app.services.item_service import ItemService from app.schemas.item import ItemCreate, ItemResponse router APIRouter(prefix/items, tags[items]) router.post(/, response_modelItemResponse) async def create_item( item_in: ItemCreate, item_service: ItemService Depends(get_item_service) # 依赖注入服务 ): # 仅做参数接收和响应转换业务逻辑交给 service return await item_service.create(item_in)第二层服务 / 应用层 (Service / Application Layer)位置services/目录。职责包含核心业务逻辑和用例。它协调多个“仓库”Repository来完成一个完整的业务操作并实施业务规则如权限检查、数据验证、工作流控制。这一层是框架无关的它不应该知道HTTP或数据库的具体细节。关键实现服务类的方法接收简单的数据对象来自Pydantic Schema调用仓库接口获取或存储数据执行业务计算最后返回结果。它依赖于抽象的仓库接口而不是具体的ORM。# 示例services/item_service.py class ItemService: def __init__(self, item_repo: AbstractItemRepository): # 依赖抽象接口 self.item_repo item_repo async def create(self, item_create: ItemCreate) - ItemResponse: # 业务逻辑例如检查名称是否唯一 existing await self.item_repo.get_by_name(item_create.name) if existing: raise ItemAlreadyExistsError(...) # 创建领域实体如果需要或直接转换为数据库模型 db_item ItemModel(**item_create.dict()) created await self.item_repo.create(db_item) # 返回给上层的响应模型 return ItemResponse.from_orm(created)第三层仓库 / 数据访问层 (Repository / Data Access Layer)位置repositories/目录通常包含一个抽象接口模块interfaces.py或abc.py和一个具体实现模块如sqlalchemy_repo.py。职责提供数据存储的抽象。它定义了一系列方法如create,get_by_id,list服务层通过这些接口与数据交互而无需关心数据是存在PostgreSQL、MongoDB还是内存里。关键实现这是依赖倒置原则的核心体现。定义抽象基类ABC然后为每种数据库实现具体的仓库类。# 示例repositories/interfaces.py from abc import ABC, abstractmethod from typing import Optional, List from app.models.item import ItemModel class AbstractItemRepository(ABC): abstractmethod async def create(self, item: ItemModel) - ItemModel: ... abstractmethod async def get_by_id(self, item_id: int) - Optional[ItemModel]: ... abstractmethod async def get_by_name(self, name: str) - Optional[ItemModel]: ... # 示例repositories/sqlalchemy_repo.py from sqlalchemy.ext.asyncio import AsyncSession from .interfaces import AbstractItemRepository class ItemRepository(AbstractItemRepository): def __init__(self, session: AsyncSession): self.session session async def create(self, item: ItemModel) - ItemModel: self.session.add(item) await self.session.flush() await self.session.refresh(item) return item第四层模型 / 领域层 (Model / Domain Layer)位置models/目录SQLAlchemy等ORM模型和schemas/目录Pydantic模型。职责models/定义与数据库表映射的ORM模型。它们只关心数据结构不包含业务逻辑。schemas/定义API请求和响应的数据格式Pydantic Schema。用于输入验证和输出序列化。通常会有CreateSchema、UpdateSchema、ResponseSchema等变体。关键实现清晰的模型与Schema分离。ORM模型用于数据库操作Pydantic Schema用于API边界。二者通过from_orm等方法进行转换。实操心得依赖注入的“连接器”如何将具体的ItemRepository实例注入到ItemService中这通常在dependencies.py或容器设置模块中完成。你会看到一个类似get_item_service的函数它负责实例化ItemRepository需要数据库会话然后用它来实例化ItemService。FastAPI的Depends系统会递归地解析这些依赖并在每个请求中提供全新的实例或共享的单例根据你的配置。这是让整个架构运转起来的“粘合剂”理解它至关重要。2.2 为什么选择这种架构权衡与考量你可能会问一个简单的CRUD应用需要这么复杂吗确实对于微型或一次性项目这可能显得“过度设计”。但fastapi-clean-example的预设场景是中大型、需要长期维护、业务逻辑复杂且可能变更频繁的应用。其优势在于可测试性服务层的业务逻辑可以轻松进行单元测试只需模拟Mock掉仓库接口无需启动数据库或Web服务器。测试速度快、隔离性好。可维护性每层职责单一修改数据库如从SQLAlchemy换到Tortoise-ORM只需重写仓库实现服务层和API层几乎不动。添加新功能时代码应该放在哪里非常明确。可扩展性当需要引入缓存、消息队列、外部API调用时可以自然地将其作为新的“适配器”接入到服务层而不会污染核心逻辑。团队协作清晰的边界有利于团队分工前端开发者可以专注于Schema定义后端开发者可以分层并行开发。当然代价是前期的认知负担和稍多的样板代码。你需要编写接口、实现类、依赖注入函数。但对于追求长期价值的项目这个投资是值得的。3. 项目结构深度拆解与配置要点让我们打开fastapi-clean-example的典型目录树看看每个文件和文件夹的具体作用。fastapi-clean-example/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI应用工厂和主入口 │ ├── core/ # 核心配置与基础设施 │ │ ├── __init__.py │ │ ├── config.py # 配置管理Pydantic Settings │ │ ├── database.py # 数据库连接池、引擎、会话工厂 │ │ ├── dependencies.py # 依赖注入定义如get_db, get_service │ │ └── exceptions.py # 自定义异常及全局异常处理器 │ ├── models/ # SQLAlchemy ORM 模型 │ │ ├── __init__.py │ │ └── item.py │ ├── schemas/ # Pydantic 数据验证模型 │ │ ├── __init__.py │ │ └── item.py # ItemCreate, ItemUpdate, ItemResponse │ ├── repositories/ # 数据访问层 │ │ ├── __init__.py │ │ ├── interfaces.py # 抽象仓库接口 │ │ └── sqlalchemy_repo.py # 基于SQLAlchemy的具体实现 │ ├── services/ # 业务逻辑层 │ │ ├── __init__.py │ │ └── item_service.py │ ├── api/ # API路由层 │ │ ├── __init__.py │ │ ├── dependencies.py # API层特定的依赖如权限检查 │ │ └── v1/ # API版本化 │ │ ├── __init__.py │ │ ├── endpoints/ # 各个端点的路由 │ │ │ ├── __init__.py │ │ │ └── items.py │ │ └── router.py # 聚合所有v1路由 │ └── tests/ # 测试目录通常与app同级 │ ├── __init__.py │ ├── conftest.py # Pytest全局配置、Fixture │ ├── unit/ # 单元测试测试services, repositories │ └── integration/ # 集成测试测试API端点 ├── alembic/ # 数据库迁移如果使用Alembic │ ├── versions/ │ └── env.py ├── requirements/ │ ├── base.txt # 基础依赖 │ ├── dev.txt # 开发依赖测试、代码检查 │ └── prod.txt # 生产依赖 ├── .env.example # 环境变量示例 ├── .pre-commit-config.yaml # Git提交前钩子配置 ├── docker-compose.yml # 开发环境容器编排 ├── Dockerfile └── pyproject.toml # 项目元数据、构建配置现代Python项目标准3.1 关键文件详解与配置1.app/core/config.py配置管理的艺术现代应用配置应来自环境变量。fastapi-clean-example通常会使用pydantic-settings来管理配置。from pydantic_settings import BaseSettings from pydantic import PostgresDsn, validator class Settings(BaseSettings): PROJECT_NAME: str My Clean API API_V1_STR: str /api/v1 # 数据库配置 POSTGRES_SERVER: str POSTGRES_USER: str POSTGRES_PASSWORD: str POSTGRES_DB: str DATABASE_URI: Optional[PostgresDsn] None validator(DATABASE_URI, preTrue) def assemble_db_connection(cls, v: Optional[str], values: dict) - Any: if isinstance(v, str): return v # 如果未直接提供URI则从各部分拼接 return PostgresDsn.build( schemepostgresqlasyncpg, usernamevalues.get(POSTGRES_USER), passwordvalues.get(POSTGRES_PASSWORD), hostvalues.get(POSTGRES_SERVER), pathf{values.get(POSTGRES_DB) or }, ) class Config: env_file .env case_sensitive True settings Settings()注意事项validator的使用让配置非常灵活。你可以直接设置DATABASE_URI也可以分别设置各个部分。生产环境通常使用完整的连接字符串可能包含SSL参数而开发环境则使用分拆的变量更方便。2.app/core/database.py异步数据库会话管理使用asyncpg驱动和SQLAlchemy的异步模式是当前最佳实践。from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker from app.core.config import settings # 创建异步引擎echoTrue在开发时很有用可以看SQL日志 engine create_async_engine( settings.DATABASE_URI, echoTrue, pool_pre_pingTrue, # 连接前ping防止数据库断开导致的错误 pool_recycle3600, # 连接回收时间 ) # 创建异步会话工厂 AsyncSessionLocal async_sessionmaker( bindengine, class_AsyncSession, expire_on_commitFalse, # 重要避免commit后对象属性访问延迟加载问题 ) # 依赖注入获取数据库会话 async def get_db() - AsyncSession: async with AsyncSessionLocal() as session: try: yield session await session.commit() # 请求成功提交事务 except Exception: await session.rollback() # 发生异常回滚 raise finally: await session.close() # 确保会话关闭实操心得expire_on_commitFalse的重要性在异步上下文中默认的expire_on_commitTrue会导致在事务提交后会话中所有对象的属性都会过期。如果你在服务层提交事务后还试图访问对象的某个属性比如返回给API层前SQLAlchemy会尝试发起新的查询但此时会话可能已经关闭或处于错误状态导致DetachedInstanceError或ResourceClosedError。设置为False可以避免这个问题但你需要更主动地管理对象的生命周期或者在需要时手动刷新refresh。3.app/core/dependencies.py依赖注入的枢纽这里是连接各层的“接线图”。from fastapi import Depends from sqlalchemy.ext.asyncio import AsyncSession from app.repositories.interfaces import AbstractItemRepository from app.repositories.sqlalchemy_repo import ItemRepository from app.services.item_service import ItemService from app.core.database import get_db # 依赖项获取具体的仓库实现 def get_item_repository( db: AsyncSession Depends(get_db) ) - AbstractItemRepository: # 这里返回的是具体实现但类型注解是抽象接口 return ItemRepository(sessiondb) # 依赖项获取服务它依赖于仓库 def get_item_service( repo: AbstractItemRepository Depends(get_item_repository) ) - ItemService: return ItemService(item_reporepo)这样在API路由中你只需要Depends(get_item_service)FastAPI会自动帮你构建出完整的对象链。4. 从零开始实现一个完整模块的实操流程理论说再多不如亲手实现一个模块。假设我们要在示例项目基础上增加一个User模块包含用户注册和登录功能。4.1 第一步定义数据模型与Schema1. 创建ORM模型 (app/models/user.py):from sqlalchemy import Column, Integer, String, Boolean, DateTime from sqlalchemy.sql import func from app.core.database import Base # 假设Base在database.py中定义 class UserModel(Base): __tablename__ users id Column(Integer, primary_keyTrue, indexTrue) email Column(String(255), uniqueTrue, indexTrue, nullableFalse) hashed_password Column(String(255), nullableFalse) full_name Column(String(100)) is_active Column(Boolean, defaultTrue) is_superuser Column(Boolean, defaultFalse) created_at Column(DateTime(timezoneTrue), server_defaultfunc.now()) updated_at Column(DateTime(timezoneTrue), onupdatefunc.now())2. 创建Pydantic Schema (app/schemas/user.py): 这里通常需要多个Schema对应不同场景。from pydantic import BaseModel, EmailStr, validator from typing import Optional from datetime import datetime # 基础属性 class UserBase(BaseModel): email: Optional[EmailStr] None full_name: Optional[str] None is_active: Optional[bool] True # 创建用户时的输入需要密码 class UserCreate(UserBase): email: EmailStr password: str full_name: str validator(password) def password_strength(cls, v): if len(v) 8: raise ValueError(密码至少8位) # 可添加更多复杂度检查 return v # 更新用户时的输入密码可选 class UserUpdate(UserBase): password: Optional[str] None # 数据库中的用户不含密码 class UserInDB(UserBase): id: int is_superuser: bool created_at: datetime updated_at: Optional[datetime] None class Config: from_attributes True # 替代旧的 orm_mode True # API响应模型 class UserResponse(UserInDB): pass # 用于登录的模型 class UserLogin(BaseModel): email: EmailStr password: str注意事项UserCreate和UserInDB/UserResponse的关键区别在于密码字段。密码永远不应该出现在响应或普通的数据库查询模型中。hashed_password只存在于ORM模型和用于验证的内部逻辑中。4.2 第二步实现数据访问层仓库1. 定义抽象接口 (app/repositories/interfaces.py中新增):class AbstractUserRepository(AbstractItemRepository): # 可以继承一个公共的基类 abstractmethod async def get_by_email(self, email: str) - Optional[UserModel]: ... abstractmethod async def create(self, user: UserModel) - UserModel: ... abstractmethod async def update(self, user: UserModel, update_data: dict) - UserModel: ...2. 实现SQLAlchemy仓库 (app/repositories/sqlalchemy_repo.py中新增类):class UserRepository(AbstractUserRepository): def __init__(self, session: AsyncSession): self.session session async def get_by_email(self, email: str) - Optional[UserModel]: result await self.session.execute( select(UserModel).where(UserModel.email email) ) return result.scalar_one_or_none() async def create(self, user: UserModel) - UserModel: self.session.add(user) await self.session.flush() await self.session.refresh(user) return user async def update(self, user: UserModel, update_data: dict) - UserModel: for key, value in update_data.items(): setattr(user, key, value) self.session.add(user) await self.session.flush() return user4.3 第三步实现业务逻辑层服务创建app/services/user_service.py:from datetime import datetime, timedelta from typing import Optional from jose import JWTError, jwt from passlib.context import CryptContext from app.core.config import settings from app.core.exceptions import CredentialsException, DuplicateEntryException from app.models.user import UserModel from app.schemas.user import UserCreate, UserUpdate, UserResponse from app.repositories.interfaces import AbstractUserRepository # 密码哈希上下文 pwd_context CryptContext(schemes[bcrypt], deprecatedauto) # JWT配置 SECRET_KEY settings.SECRET_KEY ALGORITHM HS256 ACCESS_TOKEN_EXPIRE_MINUTES 30 class UserService: def __init__(self, user_repo: AbstractUserRepository): self.user_repo user_repo staticmethod def verify_password(plain_password: str, hashed_password: str) - bool: return pwd_context.verify(plain_password, hashed_password) staticmethod def get_password_hash(password: str) - str: return pwd_context.hash(password) async def register(self, user_create: UserCreate) - UserResponse: # 1. 检查邮箱是否已存在 existing_user await self.user_repo.get_by_email(user_create.email) if existing_user: raise DuplicateEntryException(detail该邮箱已被注册) # 2. 创建ORM模型密码哈希 hashed_password self.get_password_hash(user_create.password) db_user UserModel( emailuser_create.email, hashed_passwordhashed_password, full_nameuser_create.full_name, ) # 3. 保存到数据库 created_user await self.user_repo.create(db_user) return UserResponse.from_orm(created_user) async def authenticate(self, email: str, password: str) - Optional[UserModel]: user await self.user_repo.get_by_email(email) if not user: return None if not self.verify_password(password, user.hashed_password): return None return user staticmethod 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(minutesACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({exp: expire}) encoded_jwt jwt.encode(to_encode, SECRET_KEY, algorithmALGORITHM) return encoded_jwt实操心得密码哈希与JWT密码哈希永远不要明文存储密码。passlib的bcrypt是当前行业标准。CryptContext可以方便地支持未来更换算法。JWT Token在服务层生成Token是合适的因为它属于业务逻辑用户认证。Token的payload通常包含用户ID和过期时间。密钥SECRET_KEY必须足够复杂并从环境变量读取。4.4 第四步实现API路由层创建app/api/v1/endpoints/users.py:from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm from app.schemas.user import UserCreate, UserResponse, Token from app.services.user_service import UserService from app.api.dependencies import get_current_user, get_user_service router APIRouter(prefix/users, tags[users]) router.post(/register, response_modelUserResponse, status_codestatus.HTTP_201_CREATED) async def register( user_in: UserCreate, user_service: UserService Depends(get_user_service) ): 用户注册 return await user_service.register(user_in) router.post(/login, response_modelToken) async def login( form_data: OAuth2PasswordRequestForm Depends(), user_service: UserService Depends(get_user_service) ): 用户登录OAuth2兼容格式 user await user_service.authenticate(form_data.username, form_data.password) if not user: raise HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detail用户名或密码错误, headers{WWW-Authenticate: Bearer}, ) access_token user_service.create_access_token(data{sub: str(user.id)}) return {access_token: access_token, token_type: bearer} router.get(/me, response_modelUserResponse) async def read_users_me( current_user: UserModel Depends(get_current_user) ): 获取当前用户信息需要认证 return current_user同时需要在app/api/dependencies.py中实现get_current_user依赖项用于解析JWT Token并获取当前用户。4.5 第五步注册依赖与路由1. 在app/core/dependencies.py中添加get_user_service:def get_user_service( repo: AbstractUserRepository Depends(get_user_repository) ) - UserService: return UserService(user_reporepo)2. 在app/api/v1/router.py中引入用户路由:from fastapi import APIRouter from app.api.v1.endpoints import items, users # 导入新的users模块 api_router APIRouter() api_router.include_router(items.router) api_router.include_router(users.router) # 包含用户路由至此一个完整的、遵循清洁架构的User模块就搭建完毕了。你可以看到每一层的职责都非常清晰修改任何一层比如换用不同的哈希算法或Token机制对其他层的影响都是最小化的。5. 测试策略与常见问题排查一个健壮的项目离不开测试。fastapi-clean-example的架构天生有利于测试。5.1 分层测试策略1. 单元测试测试Services和Repositories:目标快速验证业务逻辑和数据访问逻辑的正确性。工具pytestpytest-asynciounittest.mock。示例测试UserService.register:# tests/unit/services/test_user_service.py import pytest from unittest.mock import AsyncMock, MagicMock from app.services.user_service import UserService from app.schemas.user import UserCreate from app.core.exceptions import DuplicateEntryException pytest.mark.asyncio async def test_register_user_success(): # 1. 创建Mock仓库 mock_repo AsyncMock() # 模拟仓库返回None表示邮箱不存在 mock_repo.get_by_email.return_value None mock_repo.create.return_value MagicMock(id1, emailtestexample.com, hashed_passwordhashed) # 2. 实例化服务注入Mock仓库 service UserService(user_repomock_repo) # 3. 调用被测方法 user_create UserCreate(emailtestexample.com, passwordstrongpass, full_nameTest User) result await service.register(user_create) # 4. 断言 assert result.id 1 assert result.email testexample.com # 验证仓库方法被正确调用 mock_repo.get_by_email.assert_called_once_with(testexample.com) mock_repo.create.assert_called_once() # 验证传入create的模型的密码是哈希后的非明文 call_args mock_repo.create.call_args created_user call_args[0][0] assert created_user.hashed_password ! strongpass assert created_user.hashed_password.startswith($2b$) # bcrypt哈希前缀 pytest.mark.asyncio async def test_register_user_duplicate_email(): mock_repo AsyncMock() # 模拟仓库返回一个用户表示邮箱已存在 mock_repo.get_by_email.return_value MagicMock() service UserService(user_repomock_repo) user_create UserCreate(emailexistsexample.com, passwordpass, full_nameTest) # 断言会抛出特定异常 with pytest.raises(DuplicateEntryException): await service.register(user_create)实操心得单元测试的核心是“隔离”。使用Mock对象模拟掉所有外部依赖数据库、网络请求、文件系统。这样测试运行极快且只关注业务逻辑本身。2. 集成测试测试API端点:目标验证API层与下层服务、仓库的集成以及HTTP层面的行为状态码、响应格式。工具pytesthttpx 测试数据库如SQLite内存库。关键使用FastAPI的TestClient并重写应用的依赖项使其连接到测试数据库。# tests/conftest.py import pytest from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker from app.main import app from app.core.database import get_db, Base from httpx import AsyncClient # 创建测试数据库引擎使用SQLite内存库 TEST_DATABASE_URL sqliteaiosqlite:///:memory: test_engine create_async_engine(TEST_DATABASE_URL, echoFalse) TestingSessionLocal async_sessionmaker(bindtest_engine, class_AsyncSession, expire_on_commitFalse) # 覆盖主应用的get_db依赖 async def override_get_db(): async with TestingSessionLocal() as session: yield session app.dependency_overrides[get_db] override_get_db pytest.fixture(scopesession) async def test_db_setup(): # 创建所有表 async with test_engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield # 测试结束后可删除表可选 # async with test_engine.begin() as conn: # await conn.run_sync(Base.metadata.drop_all) pytest.fixture async def async_client(test_db_setup): async with AsyncClient(appapp, base_urlhttp://test) as ac: yield ac # tests/integration/api/test_users.py pytest.mark.asyncio async def test_register_endpoint(async_client: AsyncClient): payload { email: newusertest.com, password: testpassword123, full_name: New User } response await async_client.post(/api/v1/users/register, jsonpayload) assert response.status_code 201 data response.json() assert data[email] payload[email] assert hashed_password not in data # 确保密码没有泄露5.2 常见问题与排查技巧即使遵循了最佳实践开发中仍会遇到问题。以下是一些常见坑点问题1AttributeError: NoneType object has no attribute X或DetachedInstanceError原因最常见的原因是SQLAlchemy异步会话和对象状态管理问题。在expire_on_commitFalse的情况下如果你在提交后从另一个会话或没有会话的上下文中访问一个关系属性relationship就会出错。排查检查你是否在正确的会话生命周期内访问数据库对象。确保在依赖注入的get_db会话上下文中完成所有数据库操作。对于需要跨会话使用的对象考虑使用session.refresh(obj)重新加载或者更佳实践是不要在层之间传递ORM模型对象。在服务层将ORM模型转换为Pydantic Schema简单的数据对象再返回给API层。这样完全解耦了数据与会话。# 在服务层返回前转换 return UserResponse.from_orm(created_user)问题2依赖注入循环Circular Dependency原因当A依赖BB又依赖A时发生。例如在dependencies.py中get_user_service需要get_user_repository而get_user_repository又需要导入UserService中用到的某些东西。解决延迟导入在函数内部导入而不是在模块顶部。# dependencies.py def get_user_service(...): from app.services.user_service import UserService # 延迟导入 return UserService(...)重构代码检查依赖关系是否合理。有时循环依赖意味着职责划分不清需要将公共部分提取到第三个模块。问题3异步上下文管理错误现象RuntimeError: Task Task pending ... got Future Future pending attached to a different loop原因在错误的异步事件循环中创建了资源如数据库引擎、会话。常见于在全局作用域创建了异步对象但事件循环后来发生了变化例如在测试时。解决使用FastAPI的lifespan事件或启动/关闭事件来管理异步资源的生命周期确保它们在正确的事件循环中创建和销毁。# main.py from contextlib import asynccontextmanager from fastapi import FastAPI from app.core.database import engine asynccontextmanager async def lifespan(app: FastAPI): # 启动时 async with engine.begin() as conn: # 可以在这里运行一些启动SQL如检查连接 pass yield # 关闭时 await engine.dispose() app FastAPI(lifespanlifespan)问题4Pydantic验证与ORM模型冲突现象使用ResponseModel时返回的ORM对象中有None值字段导致验证错误。原因Pydantic默认所有字段都是必需的除非设置为Optional。数据库查询可能返回某些字段为None。解决在Pydantic Schema中将所有可能为None的数据库字段明确设置为Optional。或者使用response_model_exclude_noneTrue参数但更好的做法是明确定义Schema。遵循fastapi-clean-example的架构模式并理解其背后的原理能让你在构建复杂FastAPI应用时保持代码清晰、可维护和可测试。它提供的不是一条必须遵循的“金科玉律”而是一个经过验证的、可扩展的思考框架。你可以根据项目的实际规模和复杂度对这个架构进行裁剪或增强但其核心思想——关注点分离和依赖倒置——在任何规模的项目中都是有益的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2608374.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…