当前位置: 首页 > article >正文

这个 Python 泛型仓库让你少写 80% 重复代码(附代码)

本文约4000字建议阅读5分钟本文介绍了用 Python 泛型和 SQLAlchemy 实现通用仓库告别重复 CRUD。你还在为每个实体手写CRUD这个Python泛型仓库模式让你一次编写随处复用一个真实场景刚接手一个FastAPI项目打开代码库UserRepository、ProductRepository、OrderRepository……每个文件都在重复同样的save、get、update、delete逻辑。复制粘贴了8次之后我开始怀疑人生——我们真的需要为每个数据表写一遍相同的代码吗如果你也有同样的困惑今天这篇文章会给你一个答案。我将带你用Python泛型和SQLAlchemy实现一个类型安全、可扩展、可复用的通用仓库模式让你从此告别重复的CRUD代码。重复的代码重复的痛苦在大多数FastAPI或SQLAlchemy项目中仓库层Repository长这样class UserRepository: def __init__(self, session: AsyncSession): self._session session asyncdef save(self, user: User) - User: model UserModel(nameuser.name, emailuser.email) self._session.add(model) await self._session.flush() await self._session.refresh(model) return self._to_entity(model) asyncdef get(self, user_id: UUID) - User | None: result await self._session.scalar( select(UserModel).where(UserModel.id user_id) ) return self._to_entity(result) if result elseNone # ... 更多方法然后你创建ProductRepository——复制粘贴。OrderRepository——再次复制粘贴。每个仓库都包含相同的CRUD逻辑相同的分页逻辑相同的错误处理相同的SQLAlchemy操作模式唯一变化的只有三样东西实体类型如UserORM模型类型如UserModel实体与模型之间的映射⚠️ 注意这种重复代码是“复制粘贴综合症”的典型表现90%的团队在这里踩坑——当业务逻辑需要修改时你要在8个仓库里改8遍漏改一个就是Bug。解决方案一个通用的抽象仓库一个设计良好的通用仓库应该做到实现所有常见CRUD操作支持分页、排序、存在性检查、计数通过Python泛型保证类型安全允许自定义实体与模型的映射允许每个仓库自定义过滤条件保持代码整洁、可扩展、易测试下面是一份生产级的实现代码。核心组件实体基类首先需要一个所有领域实体共享的基类保证统一的结构from dataclasses import dataclass, fieldfrom datetime import datetime, timezonefrom uuid import UUIDdataclass(kw_onlyTrue)class EntityBase: id: UUID | None None created_at: datetime field(default_factorylambda: datetime.now(timezone.utc)) updated_at: datetime field(default_factorylambda: datetime.now(timezone.utc))辅助工具异常与排序class DatabaseException(Exception): 数据库操作异常的统一包装 passfrom enum import StrEnumclass Ordering(StrEnum): 排序方向类型安全 asc asc desc desc通用仓库实现这是整个模式的核心。我把它拆成两部分讲解但你可以直接复制使用。from abc import ABC, abstractmethodfrom typing import Any, Generic, List, TypeVarimport sqlalchemyfrom sqlalchemy import asc, desc, func, selectfrom sqlalchemy.exc import IntegrityError, SQLAlchemyErrorfrom sqlalchemy.ext.asyncio import AsyncSession# 假设你的Base类在这里定义from .... import Basefrom domain.value_objects.ordering import Orderingfrom domain.entities.base import EntityBasefrom domain.exceptions.common import DatabaseExceptionEntity TypeVar(Entity, boundEntityBase)SqlAlchemyModel TypeVar(SqlAlchemyModel, boundBase)class SqlAlchemyAbstractRepository(ABC, Generic[Entity, SqlAlchemyModel]): # 子类必须指定具体的ORM模型类 model: type[SqlAlchemyModel] def __init__(self, session: AsyncSession) - None: self._session session asyncdef save(self, entity: Entity) - Entity: 保存实体返回包含数据库生成字段如ID的完整实体 model self._entity_to_model(entity) self._session.add(model) await self._session.flush() await self._session.refresh(model) return self._model_to_entity(model) asyncdef update( self, fields_to_update: dict[str, Any], **filters, ) - int: 根据过滤条件更新字段返回受影响的行数 try: filter_conditions self._get_filters(**filters) query ( sqlalchemy.update(self.model) .where(*filter_conditions) .values(fields_to_update) ) result await self._session.execute(query) await self._session.flush() return result.rowcount # type: ignore[attr-defined] except IntegrityError as exception: await self._session.rollback() raise exception except SQLAlchemyError as exception: await self._session.rollback() raise DatabaseException from exception asyncdef list_all( self, page: int 1, limit: int 10, order_by: str created_at, ordering: Ordering Ordering.asc, **filters, ) - List[Entity]: 分页列表查询支持排序和过滤 query select(self.model) filter_conditions self._get_filters(**filters) query query.where(*filter_conditions) # 排序 query query.order_by( self._get_order_expression(order_byorder_by, orderingordering) ) # 分页 offset (page - 1) * limit query query.offset(offset).limit(limit) result await self._session.execute(query) models result.scalars().all() return [self._model_to_entity(model) for model in models] asyncdef get( self, **filters, ) - Entity | None: 根据过滤条件获取单个实体 query select(self.model) filter_conditions self._get_filters(**filters) query query.where(*filter_conditions) model await self._session.scalar(query) return self._model_to_entity(model) if model elseNone asyncdef exists( self, **filters, ) - bool: 检查是否存在满足条件的记录 query select(self.model) filter_conditions self._get_filters(**filters) query query.where(*filter_conditions) result await self._session.scalar(query) return result isnotNone asyncdef delete( self, **filters, ) - int: 根据过滤条件删除记录返回删除的行数 try: query sqlalchemy.delete(self.model) filter_conditions self._get_filters(**filters) query query.where(*filter_conditions) result await self._session.execute(query) await self._session.flush() return result.rowcount # type: ignore[attr-defined] except SQLAlchemyError as e: await self._session.rollback() raise DatabaseException from e asyncdef count( self, **filters, ) - int: 统计满足条件的记录数 filter_conditions self._get_filters(**filters) return ( await self._session.scalar( select(func.count()).select_from(self.model).where(*filter_conditions) ) or0 ) staticmethod abstractmethod def _model_to_entity(model: SqlAlchemyModel) - Entity: 将ORM模型转换为领域实体——子类必须实现 raise NotImplementedError(Subclasses must implement _model_to_entity) staticmethod abstractmethod def _entity_to_model(entity: Entity) - SqlAlchemyModel: 将领域实体转换为ORM模型——子类必须实现 raise NotImplementedError(Subclasses must implement _entity_to_model) abstractmethod def _get_filters(self, **filters) - List[Any]: 将业务层过滤条件转换为SQLAlchemy查询条件——子类可重写 return [] staticmethod def _get_order_expression( order_by: str, ordering: Ordering ) - sqlalchemy.UnaryExpression[str]: 生成排序表达式 if ordering Ordering.asc: return asc(order_by) return desc(order_by)泛型解析用生活化类比理解如果上面这段代码让你有点晕我用一个类比帮你理清泛型就像订餐平台的模板Entity TypeVar(Entity, boundEntityBase) —— 这就像“我要一份饭”但具体是盖浇饭还是炒饭后面再定Model TypeVar(Model, boundBase) —— 这就像“我要一个餐具”具体是碗还是盘子也后面再定SqlAlchemyAbstractRepository[Entity, Model] —— 这个组合就像“我要一份某种饭搭配某种餐具的套餐”当你创建具体仓库时class UserRepository(SqlAlchemyAbstractRepository[User, UserModel]): ...就相当于说“我要一份User饭装在UserModel餐具里。”IDE现在就能准确知道save() 接收User返回User_model_to_entity() 必须把UserModel映射成User过滤条件只接受对User有效的字段⚠️ 关键点Python虽然是动态语言但通过类型提示和泛型你可以获得编译时类型检查的能力。这在多人协作时能避免无数“不小心传错参数”的Bug。实战创建具体的UserRepository现在创建一个用户仓库你会发现只需要写三件事指定model类实现映射逻辑定义支持的过滤条件class SqlAlchemyUserRepository( SqlAlchemyAbstractRepository[User, UserModel],): model UserModel def _entity_to_model(self, entity: User) - UserModel: model UserModel( nameentity.name, emailentity.email, roleentity.role, ) # 如果实体已有ID更新场景保持ID if entity.id: model.id entity.id return model def _model_to_entity(self, model: UserModel) - User: return User( idmodel.id, namemodel.name, emailmodel.email, rolemodel.role, created_atmodel.created_at, updated_atmodel.updated_at, ) def _get_filters(self, **filters): 支持三种过滤条件id、email、role conditions [] ifid_filterin filters: conditions.append(UserModel.id filters[id_filter]) ifemail_filterin filters: conditions.append(UserModel.email filters[email_filter]) ifrole_filterin filters: conditions.append(UserModel.role filters[role_filter]) return conditions看到没 整个仓库就这么点代码。CRUD已经处理好了分页已经处理好了错误处理已经处理好了你的仓库只需要关注领域特有的逻辑。为什么_get_filters这么重要它让你的查询API既干净又灵活# 查询管理员admins await user_repo.list_all( role_filteradmin, page1, limit20)# 按邮箱查找单个用户user await user_repo.get(email_filterjohnexample.com)# 检查用户是否存在exists await user_repo.exists(email_filterjohnexample.com)不需要为每个查询写单独的SQL所有过滤条件统一通过_get_filters转换为查询条件。自定义错误处理保留灵活扩展的空间需要处理特定业务的数据库错误只需覆盖方法class SqlAlchemyUserRepository(...): # ... 前面的代码 asyncdef save(self, entity: User) - User: try: returnawait super().save(entity) except IntegrityError as e: await self._session.rollback() # 检查是否是邮箱重复 ifix_users_emailin str(e): raise UserAlreadyExistsError(entity.email) raise⚠️ 注意这里的关键是await self._session.rollback()——忘记回滚会让session处于异常状态后续操作都会失败。这是90%的人踩过的坑。添加自定义方法通用 ≠ 不能定制通用仓库不代表不能添加特定查询class SqlAlchemyUserRepository(...): # ... 前面的代码 asyncdef get_by_email(self, email: str) - User | None: 按邮箱获取用户业务常用 returnawait self.get(email_filteremail) asyncdef get_active_admins(self) - List[User]: 获取活跃管理员业务特定 returnawait self.list_all( role_filteradmin, status_filteractive )通用 ≠ 限制而是从强大的基础上开始。真实项目效果对比在重构一个中等规模的FastAPI项目后数据是这样的维度重构前重构后仓库数量8个8个单个仓库代码量250-400行30-50行CRUD重复代码每个仓库重复0全部复用修改分页逻辑改8个地方改1个地方类型安全❌ 随意传参✅ 编译时检查核心洞察这种模式不仅减少了代码量更重要的是——逻辑集中在一处修改一次生效全局Bug率显著下降。为什么这个模式值得你采用1. DRY原则落地写一次修一次处处生效。2. 一致性保障所有仓库行为统一新人上手零学习成本。3. 类型安全告别Any和随意传递的字典IDE能给你准确的代码补全。4. 可测试性测试一次基类所有仓库都得到测试覆盖。5. 可维护性想加软删除在基类改一次所有仓库自动支持。6. 灵活性需要特殊行为覆盖方法即可基类不限制你。写在最后从复制粘贴8个仓库到用泛型基类一行行抽象出来这个过程让我意识到一件事好的抽象不是炫技而是当你需要修改代码时发现只需要改一个地方。通用仓库模式在Python生态中并不算新但它结合async、SQLAlchemy和泛型后能给你的代码质量带来质的飞跃。下次你再新建一个实体时不用再写那300行CRUD只需30行映射和过滤逻辑。如果你正在维护一个数据访问层臃肿的项目建议逐个仓库迁移而不是一次性全量替换。先迁移一个非核心的仓库验证无误后再逐步推进。核心内容原理泛型抽象基类让CRUD逻辑一次性实现类型安全有保障实践子类只需实现映射和过滤所有操作自动获得避坑记得处理事务回滚自定义过滤用_get_filters统一入口编辑于腾凯校对孙英杰关于我们数据派THU作为数据科学类公众号背靠清华大学大数据研究中心分享前沿数据科学与大数据技术创新研究动态、持续传播数据科学知识努力建设数据人才聚集平台、打造中国大数据最强集团军。新浪微博数据派THU微信视频号数据派THU今日头条数据派THU

相关文章:

这个 Python 泛型仓库让你少写 80% 重复代码(附代码)

本文约4000字,建议阅读5分钟本文介绍了用 Python 泛型和 SQLAlchemy 实现通用仓库,告别重复 CRUD。你还在为每个实体手写CRUD?这个Python泛型仓库模式让你一次编写,随处复用一个真实场景:刚接手一个FastAPI项目&#x…...

Home Assistant本地LLM集成指南:隐私与响应速度的双重提升

1. 项目概述:让智能家居的“大脑”真正本地化如果你正在使用Home Assistant(HA)来构建自己的智能家居系统,并且对其中那些需要调用云端API的“智能”功能(比如语音助手对话、意图理解)感到一丝不安——无论…...

OpenClaw 2.6.6 部署避坑与高效使用详解

OpenClaw 2.6.6 Windows 一站式部署教程|本地 AI 智能体搭建与使用全指南 OpenClaw(小龙虾)是一款能够在本地环境运行的 AI 智能操作工具,依托自然语言交互能力,可实现文件管理、办公自动化、浏览器操控、系统维护等多…...

视觉语言模型多步推理评估:V-REX基准解析

1. 项目背景与核心价值 视觉语言模型(Vision-Language Models, VLMs)近年来在单步感知任务上表现出色,但在需要多步推理的复杂场景中仍面临挑战。V-REX基准的提出,正是为了填补这一评估空白。传统基准测试往往停留在"看图说话…...

AI金融分析:市场微观结构MCP服务器实战指南

1. 项目概述:一个为AI代理提供市场微观结构分析的MCP服务器 如果你是一名量化研究员、对冲基金分析师,或者正在构建一个能进行深度金融推理的AI助手,那么你肯定遇到过这样的困境:想要分析市场的“反身性”效应、估算“知情交易概…...

别再死记硬背了!用这3个真实业务场景,彻底搞懂SAP ABAP里的AT NEW和AT END

3个真实业务场景解锁SAP ABAP控制级语句的精髓 每次看到ABAP代码里那些AT NEW、AT END控制块,是不是总觉得像在解数学题?明明知道语法规则,一到实际业务就手忙脚乱。今天我们不谈枯燥的理论,直接进入三个真实业务场景——从销售订…...

n8n与LLM集成实战:构建智能自动化工作流指南

1. 项目概述:当自动化遇上大语言模型如果你正在寻找一种方法,将日常繁琐的流程自动化,同时又希望这些流程能“理解”上下文、处理非结构化信息,甚至能进行简单的推理和决策,那么你很可能已经接触过 n8n 和各类大语言模…...

【官方官宣】Claude 全量限额调整详情:算力扩容落地,编程额度翻倍,API 速率最高涨 16 倍

本文完整拆解 2026 年 5 月 Anthropic Claude 限额调整的全部细节,覆盖免费版、Pro/Max 个人版、Team 团队版、企业版、API 开发者全场景,同时解析调整背后的算力支撑、用户争议与行业影响。 一、事件开篇:从限流吐槽到额度放开,C…...

WorldMM:动态多模态记忆系统在长视频分析中的应用

1. 项目概述:当视频理解遇上记忆宫殿去年处理一段30分钟的监控视频时,我深刻体会到传统视频分析工具的局限性——它们要么像金鱼一样只有7秒记忆,要么像老式录像带需要反复倒带检索。这正是WorldMM试图解决的问题:让AI像人类侦探一…...

PCEP-30-02认证一次过!我的60天备考计划与实战笔记(附免费资源)

PCEP-30-02认证60天通关秘籍:从零基础到满分的实战路线图 1. 为什么选择PCEP认证作为Python入门第一步? 在编程学习的海洋里,Python无疑是最友好的起点之一。而PCEP(Certified Entry-Level Python Programmer)认证作…...

5个简单步骤:用Windows Cleaner彻底解决C盘爆红问题

5个简单步骤:用Windows Cleaner彻底解决C盘爆红问题 【免费下载链接】WindowsCleaner Windows Cleaner——专治C盘爆红及各种不服! 项目地址: https://gitcode.com/gh_mirrors/wi/WindowsCleaner Windows Cleaner是一款完全免费的开源系统优化工具…...

别再一帧帧画框了!用CVAT的Track模式,5分钟搞定视频目标追踪标注

别再一帧帧画框了!用CVAT的Track模式,5分钟搞定视频目标追踪标注 视频标注是计算机视觉项目中最耗时的环节之一。想象一下,你需要标注一段30分钟的道路监控视频,其中包含数十辆移动的汽车和行人。如果采用传统逐帧标注的方法&…...

告别玄学调参:用STM32 CubeMX和逻辑分析仪调试SX1262 LoRa通信

告别玄学调参:用STM32 CubeMX和逻辑分析仪调试SX1262 LoRa通信 在物联网设备开发中,LoRa技术因其长距离、低功耗的特性成为热门选择。然而许多开发者在实际使用SX1262芯片时,常常陷入反复修改参数却收效甚微的困境。本文将分享如何通过STM32 …...

为AI智能体赋能视觉:zeuxis本地截图服务器的MCP协议实践

1. 项目概述:为AI智能体装上“眼睛”的本地截图服务器 如果你正在开发或使用基于MCP(Model Context Protocol)的AI智能体,并且希望它能“看见”你屏幕上的内容,那么 zeuxis 这个工具绝对值得你深入了解。简单来说&am…...

PotPlayer字幕翻译终极指南:免费实现实时双语字幕的完整教程

PotPlayer字幕翻译终极指南:免费实现实时双语字幕的完整教程 【免费下载链接】PotPlayer_Subtitle_Translate_Baidu PotPlayer 字幕在线翻译插件 - 百度平台 项目地址: https://gitcode.com/gh_mirrors/po/PotPlayer_Subtitle_Translate_Baidu 还在为观看外语…...

解锁碧蓝航线全自动游戏体验:你的智能航海助手

解锁碧蓝航线全自动游戏体验:你的智能航海助手 【免费下载链接】AzurLaneAutoScript Azur Lane bot (CN/EN/JP/TW) 碧蓝航线脚本 | 无缝委托科研,全自动大世界 项目地址: https://gitcode.com/gh_mirrors/az/AzurLaneAutoScript 还在为每天重复的…...

Qdrant向量数据库MCP服务器:AI智能体标准化工具集成指南

1. 项目概述:向量数据库的“翻译官”如果你最近在折腾AI应用,尤其是那些需要处理大量非结构化数据(比如文档、图片、音频)的智能体(Agent)或者RAG(检索增强生成)系统,那你…...

G-Helper终极指南:华硕笔记本轻量控制工具从入门到精通

G-Helper终极指南:华硕笔记本轻量控制工具从入门到精通 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zenbook, E…...

基于Tauri 2构建的AI编程桌面应用opcode:从源码构建到深度定制

1. 项目概述:重新定义AI辅助编程的桌面体验如果你和我一样,是Claude Code的深度用户,那你一定经历过这样的场景:在终端里敲着claude命令,看着一行行代码生成,但总觉得少了点什么。是的,少了那种…...

在自动化工作流中集成Taotoken实现多模型智能决策

在自动化工作流中集成Taotoken实现多模型智能决策 构建复杂的AI Agent或自动化流程时,单一模型的能力边界往往成为瓶颈。面对多样化的任务类型,开发者需要一种灵活、统一的方式来调度不同的模型资源。Taotoken作为大模型聚合分发平台,其Open…...

机器视觉(MV)与机器人视觉(RV)的本质区别(2)

重磅预告:本专栏将独家连载新书《AI视觉技术:从入门到进阶》精华内容。本书是《AI视觉技术:从进阶到专家》的权威前导篇,特邀美国 TypeOne 公司首席科学家、斯坦福大学博士 Bohan 担任技术顾问。Bohan先生师从美国三院院士、“AI教…...

Python 3.12+ 新变化:你的旧代码可能因‘无效转义序列’警告而需要更新了(附Matplotlib案例)

Python 3.12 版本升级必读:如何优雅处理"无效转义序列"警告 最近在升级到Python 3.12后,我的一个数据可视化项目突然开始抛出大量SyntaxWarning: invalid escape sequence警告。这些警告来自一些使用了LaTeX数学符号的Matplotlib标签代码&…...

如何3分钟将B站视频转为文字:免费开源工具bili2text完整指南

如何3分钟将B站视频转为文字:免费开源工具bili2text完整指南 【免费下载链接】bili2text Bilibili视频转文字,一步到位,输入链接即可使用 项目地址: https://gitcode.com/gh_mirrors/bi/bili2text 还在为手动记录B站视频内容而烦恼吗&…...

SAP ABAP开发避坑:BAPI_MATVAL_PRICE_CHANGE调用报‘估价未维护’的完整解决流程

SAP ABAP开发实战:BAPI_MATVAL_PRICE_CHANGE报错"估价未维护"的深度解析与系统化解决方案 在SAP物料管理模块中,价格变更操作是企业日常运营中的高频事务。作为ABAP开发人员,我们经常需要借助BAPI_MATVAL_PRICE_CHANGE函数模块实现…...

【稀缺资源】AISMM 2.1评估矩阵首次公开:12项技术品牌健康度诊断+即时生成个人IP升级路线图

更多请点击: https://intelliparadigm.com 第一章:AISMM模型与技术品牌塑造 AISMM(Artificial Intelligence Strategy Maturity Model)是一种面向AI驱动型组织的技术战略成熟度评估框架,它将技术品牌塑造视为组织能力…...

LLM动态干预技术:实时调控与合规实践

1. 项目概述 大型语言模型(LLM)正在重塑人机交互的边界,但如何让这些"黑箱"系统按照人类意图稳定输出,一直是业界痛点。去年我在参与某智能客服系统升级时,就遇到过模型突然输出不合规回复的棘手情况。动态干…...

Scroll Reverser终极指南:揭秘macOS滚动方向深度定制技术

Scroll Reverser终极指南:揭秘macOS滚动方向深度定制技术 【免费下载链接】Scroll-Reverser Per-device scrolling prefs on macOS. 项目地址: https://gitcode.com/gh_mirrors/sc/Scroll-Reverser 在macOS生态中,滚动方向冲突是许多用户面临的共…...

多终端命令历史实时同步工具multicli的设计与部署指南

1. 项目概述:一个命令,多端同步如果你和我一样,日常开发需要在多个终端环境之间频繁切换——比如本地的 macOS 终端、远程的 Linux 服务器,甚至 Windows 上的 WSL——那你一定对“命令历史不同步”这件事深恶痛绝。在服务器上敲了…...

【AISMM+ESG融合实践手册】:全球仅12家通过奇点认证的企业都在用的6步嵌入法(附ISO/IEC 42001映射表)

更多请点击: https://intelliparadigm.com 第一章:AISMM与ESG融合的战略必然性与奇点认证背景 人工智能系统成熟度模型(AISMM)正加速与环境、社会与治理(ESG)框架深度耦合,其动因不仅源于监管趋…...

开源工具token-usage-ui:可视化监控LLM API Token用量与成本

1. 项目概述:一个为AI开发者量身打造的Token用量监控利器如果你正在开发基于OpenAI、Anthropic、Azure OpenAI等主流大语言模型API的应用,那么“Token用量”这个指标,你一定不会陌生。它直接关联着你的API调用成本、应用性能,甚至…...