aury-boot 0.0.3__py3-none-any.whl → 0.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aury/boot/__init__.py +2 -2
- aury/boot/_version.py +2 -2
- aury/boot/application/__init__.py +45 -36
- aury/boot/application/app/__init__.py +12 -8
- aury/boot/application/app/base.py +12 -0
- aury/boot/application/app/components.py +137 -44
- aury/boot/application/app/middlewares.py +2 -0
- aury/boot/application/app/startup.py +249 -0
- aury/boot/application/config/__init__.py +36 -1
- aury/boot/application/config/multi_instance.py +200 -0
- aury/boot/application/config/settings.py +341 -12
- aury/boot/application/constants/components.py +6 -0
- aury/boot/application/errors/handlers.py +17 -3
- aury/boot/application/middleware/logging.py +8 -120
- aury/boot/application/rpc/__init__.py +2 -2
- aury/boot/commands/__init__.py +30 -10
- aury/boot/commands/app.py +131 -1
- aury/boot/commands/docs.py +104 -17
- aury/boot/commands/init.py +30 -9
- aury/boot/commands/server/app.py +2 -3
- aury/boot/commands/templates/project/AGENTS.md.tpl +217 -0
- aury/boot/commands/templates/project/README.md.tpl +2 -2
- aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl +59 -0
- aury/boot/commands/templates/project/aury_docs/01-model.md.tpl +183 -0
- aury/boot/commands/templates/project/aury_docs/02-repository.md.tpl +206 -0
- aury/boot/commands/templates/project/aury_docs/03-service.md.tpl +398 -0
- aury/boot/commands/templates/project/aury_docs/04-schema.md.tpl +95 -0
- aury/boot/commands/templates/project/aury_docs/05-api.md.tpl +116 -0
- aury/boot/commands/templates/project/aury_docs/06-exception.md.tpl +118 -0
- aury/boot/commands/templates/project/aury_docs/07-cache.md.tpl +122 -0
- aury/boot/commands/templates/project/aury_docs/08-scheduler.md.tpl +32 -0
- aury/boot/commands/templates/project/aury_docs/09-tasks.md.tpl +38 -0
- aury/boot/commands/templates/project/aury_docs/10-storage.md.tpl +115 -0
- aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl +92 -0
- aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl +56 -0
- aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +92 -0
- aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl +102 -0
- aury/boot/commands/templates/project/aury_docs/15-events.md.tpl +147 -0
- aury/boot/commands/templates/project/config.py.tpl +1 -1
- aury/boot/commands/templates/project/env.example.tpl +73 -5
- aury/boot/commands/templates/project/modules/tasks.py.tpl +1 -1
- aury/boot/contrib/admin_console/auth.py +2 -3
- aury/boot/contrib/admin_console/install.py +1 -1
- aury/boot/domain/models/mixins.py +48 -1
- aury/boot/domain/pagination/__init__.py +94 -0
- aury/boot/domain/repository/impl.py +1 -1
- aury/boot/domain/repository/interface.py +1 -1
- aury/boot/domain/transaction/__init__.py +8 -9
- aury/boot/infrastructure/__init__.py +86 -29
- aury/boot/infrastructure/cache/backends.py +102 -18
- aury/boot/infrastructure/cache/base.py +12 -0
- aury/boot/infrastructure/cache/manager.py +153 -91
- aury/boot/infrastructure/channel/__init__.py +24 -0
- aury/boot/infrastructure/channel/backends/__init__.py +9 -0
- aury/boot/infrastructure/channel/backends/memory.py +83 -0
- aury/boot/infrastructure/channel/backends/redis.py +88 -0
- aury/boot/infrastructure/channel/base.py +92 -0
- aury/boot/infrastructure/channel/manager.py +203 -0
- aury/boot/infrastructure/clients/__init__.py +22 -0
- aury/boot/infrastructure/clients/rabbitmq/__init__.py +9 -0
- aury/boot/infrastructure/clients/rabbitmq/config.py +46 -0
- aury/boot/infrastructure/clients/rabbitmq/manager.py +288 -0
- aury/boot/infrastructure/clients/redis/__init__.py +28 -0
- aury/boot/infrastructure/clients/redis/config.py +51 -0
- aury/boot/infrastructure/clients/redis/manager.py +264 -0
- aury/boot/infrastructure/database/config.py +1 -2
- aury/boot/infrastructure/database/manager.py +16 -38
- aury/boot/infrastructure/events/__init__.py +18 -21
- aury/boot/infrastructure/events/backends/__init__.py +11 -0
- aury/boot/infrastructure/events/backends/memory.py +86 -0
- aury/boot/infrastructure/events/backends/rabbitmq.py +193 -0
- aury/boot/infrastructure/events/backends/redis.py +162 -0
- aury/boot/infrastructure/events/base.py +127 -0
- aury/boot/infrastructure/events/manager.py +224 -0
- aury/boot/infrastructure/mq/__init__.py +24 -0
- aury/boot/infrastructure/mq/backends/__init__.py +9 -0
- aury/boot/infrastructure/mq/backends/rabbitmq.py +179 -0
- aury/boot/infrastructure/mq/backends/redis.py +167 -0
- aury/boot/infrastructure/mq/base.py +143 -0
- aury/boot/infrastructure/mq/manager.py +239 -0
- aury/boot/infrastructure/scheduler/manager.py +7 -3
- aury/boot/infrastructure/storage/__init__.py +9 -9
- aury/boot/infrastructure/storage/base.py +17 -5
- aury/boot/infrastructure/storage/factory.py +0 -1
- aury/boot/infrastructure/tasks/__init__.py +2 -2
- aury/boot/infrastructure/tasks/manager.py +47 -29
- aury/boot/testing/base.py +2 -2
- {aury_boot-0.0.3.dist-info → aury_boot-0.0.5.dist-info}/METADATA +19 -2
- aury_boot-0.0.5.dist-info/RECORD +176 -0
- aury/boot/commands/templates/project/DEVELOPMENT.md.tpl +0 -1397
- aury/boot/infrastructure/events/bus.py +0 -362
- aury/boot/infrastructure/events/config.py +0 -52
- aury/boot/infrastructure/events/consumer.py +0 -134
- aury/boot/infrastructure/events/models.py +0 -63
- aury_boot-0.0.3.dist-info/RECORD +0 -137
- /aury/boot/commands/templates/project/{CLI.md.tpl → aury_docs/99-cli.md.tpl} +0 -0
- {aury_boot-0.0.3.dist-info → aury_boot-0.0.5.dist-info}/WHEEL +0 -0
- {aury_boot-0.0.3.dist-info → aury_boot-0.0.5.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
> 这是给 AI 编程助手阅读的项目上下文文件。根据开发任务类型,阅读下方「开发任务文档索引」中对应的文档。
|
|
4
|
+
|
|
5
|
+
## 项目概述
|
|
6
|
+
|
|
7
|
+
- **项目名称**: {project_name}
|
|
8
|
+
- **包名**: {package_name}
|
|
9
|
+
- **框架**: [Aury Boot](https://github.com/AuriMyth/aury-boot)(基于 FastAPI + SQLAlchemy 2.0 的微服务框架)
|
|
10
|
+
- **Python 版本**: >= 3.13
|
|
11
|
+
- **包管理**: uv(推荐)或 pip
|
|
12
|
+
|
|
13
|
+
## 常用命令
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 开发服务器(热重载)
|
|
17
|
+
aury server dev
|
|
18
|
+
|
|
19
|
+
# 数据库迁移
|
|
20
|
+
aury migrate make -m "描述" # 生成迁移
|
|
21
|
+
aury migrate up # 执行迁移
|
|
22
|
+
aury migrate down # 回滚迁移
|
|
23
|
+
aury migrate status # 查看状态
|
|
24
|
+
aury migrate show # 查看历史
|
|
25
|
+
|
|
26
|
+
# 测试
|
|
27
|
+
pytest
|
|
28
|
+
pytest tests/test_xxx.py -v
|
|
29
|
+
|
|
30
|
+
# 代码检查
|
|
31
|
+
ruff check .
|
|
32
|
+
mypy {package_name}/
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
> ⚠️ **重要**:数据库迁移文件**必须**使用 `aury migrate make` 命令生成,**禁止**手写迁移文件!
|
|
36
|
+
|
|
37
|
+
## 项目结构
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
{project_name}/
|
|
41
|
+
├── {package_name}/ # 主代码包
|
|
42
|
+
│ ├── models/ # SQLAlchemy ORM 模型
|
|
43
|
+
│ ├── repositories/ # 数据访问层(Repository 模式)
|
|
44
|
+
│ ├── services/ # 业务逻辑层
|
|
45
|
+
│ ├── schemas/ # Pydantic 请求/响应模型
|
|
46
|
+
│ ├── api/ # FastAPI 路由
|
|
47
|
+
│ ├── exceptions/ # 业务异常
|
|
48
|
+
│ ├── tasks/ # 异步任务(Dramatiq)
|
|
49
|
+
│ └── schedules/ # 定时任务
|
|
50
|
+
├── aury_docs/ # 开发文档(由 aury docs dev 生成)
|
|
51
|
+
├── tests/ # 测试
|
|
52
|
+
├── migrations/ # 数据库迁移
|
|
53
|
+
└── main.py # 应用入口
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 重要提醒
|
|
57
|
+
|
|
58
|
+
> **修改代码前,必须先阅读对应的文档!**
|
|
59
|
+
>
|
|
60
|
+
> 框架的 API 可能与常见框架不同,不要猜测。请查看 `aury_docs/` 下对应的文档。
|
|
61
|
+
|
|
62
|
+
## 开发任务文档索引
|
|
63
|
+
|
|
64
|
+
根据你要开发的功能类型,**必须阅读**对应的文档:
|
|
65
|
+
|
|
66
|
+
### CRUD / 数据库相关(最常见)
|
|
67
|
+
|
|
68
|
+
开发 CRUD 功能时,按顺序阅读以下文档:
|
|
69
|
+
|
|
70
|
+
1. **[aury_docs/01-model.md](./aury_docs/01-model.md)** - Model 定义规范
|
|
71
|
+
- 基类选择(UUIDAuditableStateModel 等)
|
|
72
|
+
- 字段类型映射
|
|
73
|
+
- 约束定义(软删除模型的复合唯一约束)
|
|
74
|
+
|
|
75
|
+
2. **[aury_docs/02-repository.md](./aury_docs/02-repository.md)** - Repository 使用
|
|
76
|
+
- BaseRepository API
|
|
77
|
+
- Filters 语法(__gt, __like 等)
|
|
78
|
+
- 自动提交机制
|
|
79
|
+
|
|
80
|
+
3. **[aury_docs/03-service.md](./aury_docs/03-service.md)** - Service 编写与事务
|
|
81
|
+
- 事务装饰器 @transactional
|
|
82
|
+
- 跨 Service 调用
|
|
83
|
+
- 事务传播 / Savepoints / on_commit 回调
|
|
84
|
+
- SELECT FOR UPDATE
|
|
85
|
+
|
|
86
|
+
4. **[aury_docs/04-schema.md](./aury_docs/04-schema.md)** - Pydantic Schema
|
|
87
|
+
- 请求/响应模型
|
|
88
|
+
- 常用验证
|
|
89
|
+
|
|
90
|
+
5. **[aury_docs/05-api.md](./aury_docs/05-api.md)** - API 路由
|
|
91
|
+
- 路由编写示例
|
|
92
|
+
- 依赖注入模式
|
|
93
|
+
|
|
94
|
+
6. **[aury_docs/06-exception.md](./aury_docs/06-exception.md)** - 异常处理
|
|
95
|
+
- 内置异常
|
|
96
|
+
- 自定义异常
|
|
97
|
+
|
|
98
|
+
### 异步任务 / 定时任务
|
|
99
|
+
|
|
100
|
+
- **[aury_docs/08-scheduler.md](./aury_docs/08-scheduler.md)** - 定时任务(APScheduler)
|
|
101
|
+
- **[aury_docs/09-tasks.md](./aury_docs/09-tasks.md)** - 异步任务(Dramatiq)
|
|
102
|
+
|
|
103
|
+
### 基础设施
|
|
104
|
+
|
|
105
|
+
- **[aury_docs/07-cache.md](./aury_docs/07-cache.md)** - 缓存
|
|
106
|
+
- **[aury_docs/10-storage.md](./aury_docs/10-storage.md)** - 对象存储(S3/COS/OSS)
|
|
107
|
+
- **[aury_docs/11-logging.md](./aury_docs/11-logging.md)** - 日志
|
|
108
|
+
- **[aury_docs/12-admin.md](./aury_docs/12-admin.md)** - 管理后台(SQLAdmin)
|
|
109
|
+
- **[aury_docs/13-channel.md](./aury_docs/13-channel.md)** - 流式通道(SSE)
|
|
110
|
+
- **[aury_docs/14-mq.md](./aury_docs/14-mq.md)** - 消息队列
|
|
111
|
+
- **[aury_docs/15-events.md](./aury_docs/15-events.md)** - 事件总线
|
|
112
|
+
|
|
113
|
+
### 配置 / CLI / 环境变量
|
|
114
|
+
|
|
115
|
+
- **[aury_docs/00-overview.md](./aury_docs/00-overview.md)** - 项目概览与最佳实践
|
|
116
|
+
- **[aury_docs/99-cli.md](./aury_docs/99-cli.md)** - CLI 命令参考
|
|
117
|
+
- **[.env.example](./.env.example)** - 所有可用环境变量
|
|
118
|
+
|
|
119
|
+
## 代码规范
|
|
120
|
+
|
|
121
|
+
### Model 规范
|
|
122
|
+
|
|
123
|
+
- **必须**继承框架预定义基类,**不要**直接继承 `Base`
|
|
124
|
+
- **推荐**使用 `UUIDAuditableStateModel`(UUID 主键 + 时间戳 + 软删除)
|
|
125
|
+
- 软删除模型**必须**使用复合唯一约束(包含 `deleted_at`),不能单独使用 `unique=True`
|
|
126
|
+
- **不建议**使用数据库外键(`ForeignKey`),通过程序控制关系,便于分库分表和微服务拆分
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# ✅ 正确
|
|
130
|
+
from aury.boot.domain.models import UUIDAuditableStateModel
|
|
131
|
+
|
|
132
|
+
class User(UUIDAuditableStateModel):
|
|
133
|
+
__tablename__ = "users"
|
|
134
|
+
email: Mapped[str] = mapped_column(String(255), index=True)
|
|
135
|
+
__table_args__ = (
|
|
136
|
+
UniqueConstraint("email", "deleted_at", name="uq_users_email_deleted"),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# ❌ 错误:直接继承 Base
|
|
140
|
+
from aury.boot.domain.models.base import Base
|
|
141
|
+
class User(Base): ...
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Service 规范
|
|
145
|
+
|
|
146
|
+
- 写操作**必须**使用 `@transactional` 装饰器
|
|
147
|
+
- 只读操作可以不加事务装饰器
|
|
148
|
+
- 跨 Service 调用通过共享 session 实现事务共享
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from aury.boot.domain.transaction import transactional
|
|
152
|
+
|
|
153
|
+
class UserService(BaseService):
|
|
154
|
+
@transactional
|
|
155
|
+
async def create(self, data: UserCreate) -> User:
|
|
156
|
+
# 自动事务管理
|
|
157
|
+
return await self.repo.create(data.model_dump())
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Manager API 规范
|
|
161
|
+
|
|
162
|
+
所有基础设施 Manager 统一使用 `initialize()` 方法初始化:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
# ✅ 正确
|
|
166
|
+
from aury.boot.infrastructure.cache import CacheManager
|
|
167
|
+
cache = CacheManager.get_instance()
|
|
168
|
+
await cache.initialize(backend="redis", url="redis://localhost:6379")
|
|
169
|
+
|
|
170
|
+
from aury.boot.infrastructure.storage import StorageManager, StorageConfig
|
|
171
|
+
storage = StorageManager.get_instance()
|
|
172
|
+
await storage.initialize(StorageConfig(...))
|
|
173
|
+
|
|
174
|
+
from aury.boot.infrastructure.events import EventBusManager
|
|
175
|
+
events = EventBusManager.get_instance()
|
|
176
|
+
await events.initialize(backend="memory")
|
|
177
|
+
|
|
178
|
+
# ❗ 注意:没有 configure() 方法,配置直接传入 initialize()
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 异常规范
|
|
182
|
+
|
|
183
|
+
- **必须**继承框架异常类,**不要**直接继承 `Exception`
|
|
184
|
+
- 使用框架内置异常:`NotFoundError`, `AlreadyExistsError`, `UnauthorizedError` 等
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
# ✅ 正确
|
|
188
|
+
from aury.boot.application.errors import NotFoundError
|
|
189
|
+
raise NotFoundError("用户不存在", resource=user_id)
|
|
190
|
+
|
|
191
|
+
# ❌ 错误
|
|
192
|
+
raise Exception("用户不存在")
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 响应规范
|
|
196
|
+
|
|
197
|
+
- **不要**自定义通用响应 Schema
|
|
198
|
+
- 使用框架内置:`BaseResponse`, `PaginationResponse`
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from aury.boot.application.interfaces.egress import BaseResponse
|
|
202
|
+
return BaseResponse(code=200, message="成功", data=user)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## 禁止操作
|
|
206
|
+
|
|
207
|
+
- ❌ 不要直接修改 `aury/boot/` 框架代码
|
|
208
|
+
- ❌ 不要在 Model 中直接使用 `unique=True`(软删除模型)
|
|
209
|
+
- ❌ 不要自定义通用响应 Schema
|
|
210
|
+
- ❌ 不要在 Repository 之外直接操作 session
|
|
211
|
+
- ❌ 不要提交 `.env` 文件到版本库
|
|
212
|
+
|
|
213
|
+
## 需要确认的操作
|
|
214
|
+
|
|
215
|
+
- ⚠️ 添加新的 pip 依赖前请确认
|
|
216
|
+
- ⚠️ 修改数据库迁移前请确认
|
|
217
|
+
- ⚠️ 删除文件前请确认
|
|
@@ -106,6 +106,6 @@ aury worker
|
|
|
106
106
|
|
|
107
107
|
## 文档
|
|
108
108
|
|
|
109
|
-
- [
|
|
110
|
-
- [
|
|
109
|
+
- [AGENTS.md](./AGENTS.md) - AI 编程助手上下文
|
|
110
|
+
- [aury_docs/](./aury_docs/) - 开发文档(包含 Model/Service/API 等指南)
|
|
111
111
|
- [Aury Boot 文档](https://github.com/AuriMyth/aury-boot)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# {project_name} 开发指南
|
|
2
|
+
|
|
3
|
+
本文档基于 [Aury Boot](https://github.com/AuriMyth/aury-boot) 框架。
|
|
4
|
+
|
|
5
|
+
CLI 命令参考请查看 [99-cli.md](./99-cli.md)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 目录结构
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
{project_name}/
|
|
13
|
+
├── {package_name}/ # 代码包(默认 app,可通过 aury init <pkg> 自定义)
|
|
14
|
+
│ ├── models/ # SQLAlchemy ORM 模型
|
|
15
|
+
│ ├── repositories/ # 数据访问层
|
|
16
|
+
│ ├── services/ # 业务逻辑层
|
|
17
|
+
│ ├── schemas/ # Pydantic 请求/响应模型
|
|
18
|
+
│ ├── api/ # FastAPI 路由
|
|
19
|
+
│ ├── exceptions/ # 业务异常
|
|
20
|
+
│ ├── tasks/ # 异步任务(Dramatiq)
|
|
21
|
+
│ └── schedules/ # 定时任务(Scheduler)
|
|
22
|
+
├── tests/ # 测试
|
|
23
|
+
├── migrations/ # 数据库迁移
|
|
24
|
+
└── main.py # 应用入口
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 最佳实践
|
|
30
|
+
|
|
31
|
+
1. **分层架构**:API → Service → Repository → Model
|
|
32
|
+
2. **事务管理**:在 Service 层使用 `@transactional`,只读操作可不加
|
|
33
|
+
3. **错误处理**:使用框架异常类,全局异常处理器统一处理
|
|
34
|
+
4. **配置管理**:使用 `.env` 文件,不提交到版本库
|
|
35
|
+
5. **日志记录**:使用框架 logger,支持结构化日志和链路追踪
|
|
36
|
+
6. **多实例配置**:环境变量格式 `{{PREFIX}}_{{INSTANCE}}_{{FIELD}}`
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 文档索引
|
|
41
|
+
|
|
42
|
+
### CRUD / 数据库
|
|
43
|
+
- [01-model.md](./01-model.md) - Model 定义
|
|
44
|
+
- [02-repository.md](./02-repository.md) - Repository 使用
|
|
45
|
+
- [03-service.md](./03-service.md) - Service 与事务
|
|
46
|
+
- [04-schema.md](./04-schema.md) - Pydantic Schema
|
|
47
|
+
- [05-api.md](./05-api.md) - API 路由
|
|
48
|
+
- [06-exception.md](./06-exception.md) - 异常处理
|
|
49
|
+
|
|
50
|
+
### 基础设施
|
|
51
|
+
- [07-cache.md](./07-cache.md) - 缓存
|
|
52
|
+
- [08-scheduler.md](./08-scheduler.md) - 定时任务
|
|
53
|
+
- [09-tasks.md](./09-tasks.md) - 异步任务
|
|
54
|
+
- [10-storage.md](./10-storage.md) - 对象存储
|
|
55
|
+
- [11-logging.md](./11-logging.md) - 日志
|
|
56
|
+
- [12-admin.md](./12-admin.md) - 管理后台
|
|
57
|
+
- [13-channel.md](./13-channel.md) - 流式通道(SSE)
|
|
58
|
+
- [14-mq.md](./14-mq.md) - 消息队列
|
|
59
|
+
- [15-events.md](./15-events.md) - 事件总线
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Model(数据模型)
|
|
2
|
+
|
|
3
|
+
## 1.1 模型基类选择
|
|
4
|
+
|
|
5
|
+
框架提供多种预组合基类,按需选择:
|
|
6
|
+
|
|
7
|
+
| 基类 | 主键 | 时间戳 | 软删除 | 乐观锁 | 场景 |
|
|
8
|
+
|------|------|--------|--------|--------|------|
|
|
9
|
+
| `Model` | int | ✓ | ✗ | ✗ | 简单实体 |
|
|
10
|
+
| `AuditableStateModel` | int | ✓ | ✓ | ✗ | 需软删除 |
|
|
11
|
+
| `UUIDModel` | UUID | ✓ | ✗ | ✗ | 分布式 |
|
|
12
|
+
| `UUIDAuditableStateModel` | UUID | ✓ | ✓ | ✗ | **推荐** |
|
|
13
|
+
| `VersionedModel` | int | ✗ | ✗ | ✓ | 乐观锁 |
|
|
14
|
+
| `VersionedUUIDModel` | UUID | ✓ | ✗ | ✓ | UUID+乐观锁 |
|
|
15
|
+
| `FullFeaturedUUIDModel` | UUID | ✓ | ✓ | ✓ | 全功能 |
|
|
16
|
+
|
|
17
|
+
## 1.2 基类自动提供的字段
|
|
18
|
+
|
|
19
|
+
**IDMixin** (int 主键):
|
|
20
|
+
```python
|
|
21
|
+
id: Mapped[int] # 自增主键
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**UUIDMixin** (UUID 主键):
|
|
25
|
+
```python
|
|
26
|
+
import uuid
|
|
27
|
+
from sqlalchemy.types import Uuid as SQLAlchemyUuid
|
|
28
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
29
|
+
|
|
30
|
+
id: Mapped[uuid.UUID] = mapped_column(
|
|
31
|
+
SQLAlchemyUuid(as_uuid=True), # SQLAlchemy 2.0 自动适配 PG(uuid) 和 MySQL(char(36))
|
|
32
|
+
primary_key=True,
|
|
33
|
+
default=uuid.uuid4,
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> **关于 SQLAlchemyUuid**:
|
|
38
|
+
> - 使用 `sqlalchemy.types.Uuid`(导入为 `SQLAlchemyUuid`)而非直接使用 `uuid.UUID`
|
|
39
|
+
> - `as_uuid=True` 确保 Python 层面使用 UUID 对象而非字符串
|
|
40
|
+
> - 框架会自动适配不同数据库:PostgreSQL 使用原生 UUID 类型,MySQL/SQLite 使用 CHAR(36)
|
|
41
|
+
> - 如需手动定义 UUID 字段,请使用 `SQLAlchemyUuid(as_uuid=True)` 而非 `Uuid(as_uuid=True)`
|
|
42
|
+
|
|
43
|
+
**TimestampMixin** (时间戳):
|
|
44
|
+
```python
|
|
45
|
+
created_at: Mapped[datetime] # 创建时间,自动设置
|
|
46
|
+
updated_at: Mapped[datetime] # 更新时间,自动更新
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**AuditableStateMixin** (软删除):
|
|
50
|
+
```python
|
|
51
|
+
deleted_at: Mapped[int] # 删除时间戳,0=未删除,>0=删除时间
|
|
52
|
+
# 自动提供:is_deleted 属性、mark_deleted() 方法、restore() 方法
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> **注意**:使用软删除的模型不要单独使用 `unique=True`,否则删除后再插入相同值会报错。
|
|
56
|
+
> 应使用复合唯一索引:`UniqueConstraint("email", "deleted_at", name="uq_users_email_deleted")`
|
|
57
|
+
|
|
58
|
+
**VersionMixin** (乐观锁):
|
|
59
|
+
```python
|
|
60
|
+
version: Mapped[int] # 版本号,自动管理
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 1.3 Model 编写示例
|
|
64
|
+
|
|
65
|
+
**文件**: `{package_name}/models/user.py`
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
"""User 数据模型。"""
|
|
69
|
+
|
|
70
|
+
from sqlalchemy import String, Boolean, UniqueConstraint
|
|
71
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
72
|
+
|
|
73
|
+
from aury.boot.domain.models import UUIDAuditableStateModel
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class User(UUIDAuditableStateModel):
|
|
77
|
+
"""User 模型。
|
|
78
|
+
|
|
79
|
+
继承 UUIDAuditableStateModel 自动获得:
|
|
80
|
+
- id: UUID 主键(使用 SQLAlchemyUuid 自动适配数据库)
|
|
81
|
+
- created_at, updated_at: 时间戳
|
|
82
|
+
- deleted_at: 软删除支持
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
__tablename__ = "users"
|
|
86
|
+
|
|
87
|
+
name: Mapped[str] = mapped_column(String(100), nullable=False, comment="用户名")
|
|
88
|
+
email: Mapped[str] = mapped_column(String(255), nullable=False, index=True, comment="邮箱")
|
|
89
|
+
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否激活")
|
|
90
|
+
|
|
91
|
+
# 软删除模型必须使用复合唯一约束(包含 deleted_at),避免删除后无法插入相同值
|
|
92
|
+
# 注意:复合约束必须使用 __table_args__,这是 SQLAlchemy 的要求
|
|
93
|
+
__table_args__ = (
|
|
94
|
+
UniqueConstraint("email", "deleted_at", name="uq_users_email_deleted"),
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 1.4 字段类型映射
|
|
99
|
+
|
|
100
|
+
| Python 类型 | SQLAlchemy 类型 | 说明 |
|
|
101
|
+
|-------------|----------------|------|
|
|
102
|
+
| `str` | `String(length)` | 必须指定长度 |
|
|
103
|
+
| `int` | `Integer` | 整数 |
|
|
104
|
+
| `float` | `Float` | 浮点数 |
|
|
105
|
+
| `bool` | `Boolean` | 布尔值 |
|
|
106
|
+
| `datetime` | `DateTime(timezone=True)` | 带时区 |
|
|
107
|
+
| `date` | `Date` | 日期 |
|
|
108
|
+
| `Decimal` | `Numeric(precision, scale)` | 精确小数 |
|
|
109
|
+
| `dict`/`list` | `JSON` | JSON 数据 |
|
|
110
|
+
| `uuid.UUID` | `SQLAlchemyUuid(as_uuid=True)` | UUID(推荐使用 `from sqlalchemy.types import Uuid as SQLAlchemyUuid`) |
|
|
111
|
+
|
|
112
|
+
## 1.5 常用字段约束
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from sqlalchemy import String, Integer, Index, UniqueConstraint
|
|
116
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
117
|
+
import uuid
|
|
118
|
+
|
|
119
|
+
class Example(UUIDAuditableStateModel):
|
|
120
|
+
__tablename__ = "examples"
|
|
121
|
+
|
|
122
|
+
# 可选字段
|
|
123
|
+
description: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
124
|
+
|
|
125
|
+
# 带默认值
|
|
126
|
+
status: Mapped[int] = mapped_column(Integer, default=0, server_default="0", index=True)
|
|
127
|
+
|
|
128
|
+
# 单列索引:直接在 mapped_column 中使用 index=True(推荐)
|
|
129
|
+
code: Mapped[str] = mapped_column(String(50), index=True, comment="编码")
|
|
130
|
+
|
|
131
|
+
# 单列唯一约束:直接在 mapped_column 中使用 unique=True(仅非软删除模型)
|
|
132
|
+
# 注意:软删除模型不能单独使用 unique=True,必须使用复合唯一约束
|
|
133
|
+
|
|
134
|
+
# 关联字段(不使用数据库外键,通过程序控制关系)
|
|
135
|
+
category_id: Mapped[uuid.UUID | None] = mapped_column(index=True)
|
|
136
|
+
|
|
137
|
+
# 复合索引和复合唯一约束:必须使用 __table_args__(SQLAlchemy 要求)
|
|
138
|
+
# 软删除模型必须使用复合唯一约束(包含 deleted_at),避免删除后无法插入相同值
|
|
139
|
+
__table_args__ = (
|
|
140
|
+
Index("ix_examples_status_created", "status", "created_at"),
|
|
141
|
+
UniqueConstraint("code", "deleted_at", name="uq_examples_code_deleted"),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# 非软删除模型可以直接使用 unique=True 和 index=True
|
|
146
|
+
class Config(UUIDModel): # UUIDModel 不包含软删除
|
|
147
|
+
__tablename__ = "configs"
|
|
148
|
+
|
|
149
|
+
# 单列唯一约束:直接在 mapped_column 中使用(推荐)
|
|
150
|
+
key: Mapped[str] = mapped_column(String(100), unique=True, index=True, comment="配置键")
|
|
151
|
+
value: Mapped[str] = mapped_column(String(500), comment="配置值")
|
|
152
|
+
|
|
153
|
+
# 单列索引:直接在 mapped_column 中使用(推荐)
|
|
154
|
+
name: Mapped[str] = mapped_column(String(100), index=True, comment="配置名称")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**约束定义最佳实践**:
|
|
158
|
+
|
|
159
|
+
1. **单列索引**:使用 `index=True` 在 `mapped_column` 中(推荐)
|
|
160
|
+
```python
|
|
161
|
+
email: Mapped[str] = mapped_column(String(255), index=True)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
2. **单列唯一约束**:
|
|
165
|
+
- 非软删除模型:使用 `unique=True` 在 `mapped_column` 中(推荐)
|
|
166
|
+
- 软删除模型:必须使用复合唯一约束(包含 `deleted_at`)
|
|
167
|
+
|
|
168
|
+
3. **复合索引/唯一约束**:必须使用 `__table_args__`(SQLAlchemy 要求)
|
|
169
|
+
```python
|
|
170
|
+
__table_args__ = (
|
|
171
|
+
Index("ix_name", "col1", "col2"),
|
|
172
|
+
UniqueConstraint("col1", "col2", name="uq_name"),
|
|
173
|
+
)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
4. **关联关系**:**不建议使用数据库外键**,通过程序控制关系
|
|
177
|
+
- 便于分库分表、微服务拆分
|
|
178
|
+
- 避免级联操作影响性能
|
|
179
|
+
- 简化数据迁移
|
|
180
|
+
```python
|
|
181
|
+
# 只存储关联 ID,不使用 ForeignKey
|
|
182
|
+
category_id: Mapped[uuid.UUID | None] = mapped_column(index=True)
|
|
183
|
+
```
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Repository(数据访问层)
|
|
2
|
+
|
|
3
|
+
## 2.1 Repository 编写示例
|
|
4
|
+
|
|
5
|
+
**文件**: `{package_name}/repositories/user_repository.py`
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
"""User 数据访问层。"""
|
|
9
|
+
|
|
10
|
+
from aury.boot.domain.repository.impl import BaseRepository
|
|
11
|
+
|
|
12
|
+
from {package_name}.models.user import User
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class UserRepository(BaseRepository[User]):
|
|
16
|
+
"""User 仓储。
|
|
17
|
+
|
|
18
|
+
继承 BaseRepository 自动获得:
|
|
19
|
+
- get(id): 按 ID 获取
|
|
20
|
+
- get_by(**filters): 按条件获取单个
|
|
21
|
+
- list(skip, limit, **filters): 获取列表
|
|
22
|
+
- paginate(params, **filters): 分页获取
|
|
23
|
+
- count(**filters): 计数
|
|
24
|
+
- exists(**filters): 是否存在
|
|
25
|
+
- create(data): 创建
|
|
26
|
+
- update(entity, data): 更新
|
|
27
|
+
- delete(entity, soft=True): 删除(默认软删除)
|
|
28
|
+
- batch_create(data_list): 批量创建
|
|
29
|
+
- bulk_insert(data_list): 高性能批量插入
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
async def get_by_email(self, email: str) -> User | None:
|
|
33
|
+
"""按邮箱查询用户。"""
|
|
34
|
+
return await self.get_by(email=email)
|
|
35
|
+
|
|
36
|
+
async def list_active(self, skip: int = 0, limit: int = 100) -> list[User]:
|
|
37
|
+
"""获取激活用户列表。"""
|
|
38
|
+
return await self.list(skip=skip, limit=limit, is_active=True)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 2.2 BaseRepository 方法详解
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
45
|
+
|
|
46
|
+
# 初始化(默认 auto_commit=True)
|
|
47
|
+
repo = UserRepository(session, User)
|
|
48
|
+
|
|
49
|
+
# === 查询 ===
|
|
50
|
+
user = await repo.get(user_id) # 按 ID(支持 int/UUID)
|
|
51
|
+
user = await repo.get_by(email="a@b.com") # 按条件(简单 AND 过滤)
|
|
52
|
+
users = await repo.list(skip=0, limit=10) # 列表
|
|
53
|
+
users = await repo.list(is_active=True) # 带过滤
|
|
54
|
+
count = await repo.count(is_active=True) # 计数
|
|
55
|
+
exists = await repo.exists(email="a@b.com") # 是否存在
|
|
56
|
+
|
|
57
|
+
# === 分页 ===
|
|
58
|
+
from aury.boot.domain.pagination import PaginationParams, SortParams
|
|
59
|
+
|
|
60
|
+
result = await repo.paginate(
|
|
61
|
+
pagination_params=PaginationParams(page=1, page_size=20),
|
|
62
|
+
sort_params=SortParams.from_string("-created_at"),
|
|
63
|
+
is_active=True,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# PaginationResult 结构:
|
|
67
|
+
# - result.items: list[T] # 数据列表
|
|
68
|
+
# - result.total: int # 总记录数
|
|
69
|
+
# - result.page: int # 当前页码
|
|
70
|
+
# - result.page_size: int # 每页数量
|
|
71
|
+
# - result.total_pages: int # 总页数
|
|
72
|
+
# - result.has_next: bool # 是否有下一页
|
|
73
|
+
# - result.has_prev: bool # 是否有上一页
|
|
74
|
+
|
|
75
|
+
# === 创建 ===
|
|
76
|
+
user = await repo.create({{"name": "Alice", "email": "a@b.com"}})
|
|
77
|
+
users = await repo.batch_create([{{"name": "A"}}, {{"name": "B"}}]) # 返回实体
|
|
78
|
+
await repo.bulk_insert([{{"name": "A"}}, {{"name": "B"}}]) # 高性能,无返回
|
|
79
|
+
|
|
80
|
+
# === 更新 ===
|
|
81
|
+
user = await repo.update(user, {{"name": "Bob"}})
|
|
82
|
+
|
|
83
|
+
# === 删除 ===
|
|
84
|
+
await repo.delete(user) # 软删除
|
|
85
|
+
await repo.delete(user, soft=False) # 硬删除
|
|
86
|
+
await repo.hard_delete(user) # 硬删除别名
|
|
87
|
+
deleted = await repo.delete_by_id(user_id) # 按 ID 删除
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 2.2.1 Filters 语法(增强)
|
|
91
|
+
|
|
92
|
+
BaseRepository 的 `get_by/list/paginate/count/exists` 的 `**filters` 支持下列操作符(与 `QueryBuilder.filter` 对齐):
|
|
93
|
+
- `__gt`, `__lt`, `__gte`, `__lte`, `__in`, `__like`, `__ilike`, `__isnull`, `__ne`
|
|
94
|
+
|
|
95
|
+
示例:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# 模糊匹配 + 范围 + IN + 为空判断(条件之间为 AND 关系)
|
|
99
|
+
users = await repo.list(
|
|
100
|
+
name__ilike="%foo%",
|
|
101
|
+
age__gte=18,
|
|
102
|
+
id__in=[u1, u2, u3],
|
|
103
|
+
deleted_at__isnull=True,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# 单个实体(不等于)
|
|
107
|
+
user = await repo.get_by(status__ne="archived")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
> 注意:filters 条件之间用 AND 组合;如需 AND/OR/NOT 的复杂组合,请使用 `QueryBuilder`(见 2.4)。
|
|
111
|
+
|
|
112
|
+
### 2.2.2 排序参数(SortParams)
|
|
113
|
+
|
|
114
|
+
`SortParams.from_string()` 支持两种语法:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from aury.boot.domain.pagination import SortParams
|
|
118
|
+
|
|
119
|
+
# 简洁语法:"-" 前缀表示降序
|
|
120
|
+
sort_params = SortParams.from_string("-created_at")
|
|
121
|
+
sort_params = SortParams.from_string("-created_at,priority") # 多字段
|
|
122
|
+
|
|
123
|
+
# 完整语法:字段:方向
|
|
124
|
+
sort_params = SortParams.from_string("created_at:desc,priority:asc")
|
|
125
|
+
|
|
126
|
+
# 带字段白名单验证(防止 SQL 注入)
|
|
127
|
+
ALLOWED_FIELDS = {{"id", "created_at", "priority", "status"}}
|
|
128
|
+
sort_params = SortParams.from_string(
|
|
129
|
+
"-created_at",
|
|
130
|
+
allowed_fields=ALLOWED_FIELDS
|
|
131
|
+
)
|
|
132
|
+
# 传入非法字段会抛出 ValueError
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 2.2.3 查询全部(limit=None)
|
|
136
|
+
|
|
137
|
+
`list()` 支持 `limit=None` 返回全部记录(谨慎使用大表):
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
all_users = await repo.list(limit=None) # 全量
|
|
141
|
+
active_all = await repo.list(limit=None, is_active=True)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 2.3 自动提交机制
|
|
145
|
+
|
|
146
|
+
BaseRepository 支持智能的自动提交机制,优于 Django 的设计:
|
|
147
|
+
|
|
148
|
+
| 场景 | 行为 |
|
|
149
|
+
|------|------|
|
|
150
|
+
| 非事务中 + `auto_commit=True` | 写操作后自动 commit |
|
|
151
|
+
| 非事务中 + `auto_commit=False` | 只 flush,需手动管理或使用 `.with_commit()` |
|
|
152
|
+
| 在事务中(`@transactional` 等) | **永不自动提交**,由事务统一管理 |
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
# 默认行为:非事务中自动提交
|
|
156
|
+
repo = UserRepository(session, User) # auto_commit=True
|
|
157
|
+
await repo.create({{"name": "test"}}) # 自动 commit
|
|
158
|
+
|
|
159
|
+
# 禁用自动提交
|
|
160
|
+
repo = UserRepository(session, User, auto_commit=False)
|
|
161
|
+
await repo.create({{"name": "test"}}) # 只 flush,不 commit
|
|
162
|
+
|
|
163
|
+
# 单次强制提交(auto_commit=False 时)
|
|
164
|
+
await repo.with_commit().create({{"name": "test2"}}) # 强制 commit
|
|
165
|
+
|
|
166
|
+
# 在事务中:无论 auto_commit 是什么,都不会自动提交
|
|
167
|
+
@transactional
|
|
168
|
+
async def create_with_profile(session: AsyncSession):
|
|
169
|
+
repo = UserRepository(session, User) # auto_commit=True 但不生效
|
|
170
|
+
user = await repo.create({{"name": "a"}}) # 只 flush
|
|
171
|
+
profile = await profile_repo.create({{"user_id": user.id}}) # 只 flush
|
|
172
|
+
# 事务结束时统一 commit
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**设计优势**(对比 Django):
|
|
176
|
+
- Django:每个 `save()` 默认独立事务,容易无意识地失去原子性
|
|
177
|
+
- Aury Boot:默认自动提交,但**事务上下文自动接管**,更显式可控
|
|
178
|
+
|
|
179
|
+
## 2.4 复杂查询示例
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
async def search_users(
|
|
183
|
+
self,
|
|
184
|
+
keyword: str | None = None,
|
|
185
|
+
status: int | None = None,
|
|
186
|
+
) -> list[User]:
|
|
187
|
+
"""复杂搜索。"""
|
|
188
|
+
query = self.query() # 自动排除软删除
|
|
189
|
+
|
|
190
|
+
if keyword:
|
|
191
|
+
# 使用 QueryBuilder 的 or_ 与 filter_by 组合复杂条件
|
|
192
|
+
query = query.filter_by(
|
|
193
|
+
query.or_(
|
|
194
|
+
User.name.ilike(f"%{{keyword}}%"),
|
|
195
|
+
User.email.ilike(f"%{{keyword}}%"),
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
if status is not None:
|
|
200
|
+
# 简单等值可直接使用 filter(支持 **kwargs)
|
|
201
|
+
query = query.filter(status=status)
|
|
202
|
+
|
|
203
|
+
query = query.order_by("-created_at").limit(100)
|
|
204
|
+
result = await self.session.execute(query.build())
|
|
205
|
+
return list(result.scalars().all())
|
|
206
|
+
```
|