aury-boot 0.0.4__py3-none-any.whl → 0.0.7__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 +60 -36
- aury/boot/application/adapter/__init__.py +112 -0
- aury/boot/application/adapter/base.py +511 -0
- aury/boot/application/adapter/config.py +242 -0
- aury/boot/application/adapter/decorators.py +259 -0
- aury/boot/application/adapter/exceptions.py +202 -0
- aury/boot/application/adapter/http.py +325 -0
- 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 +9 -4
- aury/boot/application/app/startup.py +249 -0
- aury/boot/application/config/__init__.py +36 -1
- aury/boot/application/config/multi_instance.py +216 -0
- aury/boot/application/config/settings.py +398 -149
- aury/boot/application/constants/components.py +6 -0
- aury/boot/application/errors/handlers.py +17 -3
- aury/boot/application/middleware/logging.py +21 -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/generate.py +22 -22
- aury/boot/commands/init.py +68 -17
- aury/boot/commands/server/app.py +2 -3
- aury/boot/commands/templates/project/AGENTS.md.tpl +221 -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 +184 -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 +131 -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 +104 -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/aury_docs/16-adapter.md.tpl +403 -0
- aury/boot/commands/templates/project/{CLI.md.tpl → aury_docs/99-cli.md.tpl} +19 -19
- aury/boot/commands/templates/project/config.py.tpl +10 -10
- aury/boot/commands/templates/project/env_templates/_header.tpl +10 -0
- aury/boot/commands/templates/project/env_templates/admin.tpl +49 -0
- aury/boot/commands/templates/project/env_templates/cache.tpl +14 -0
- aury/boot/commands/templates/project/env_templates/database.tpl +22 -0
- aury/boot/commands/templates/project/env_templates/log.tpl +18 -0
- aury/boot/commands/templates/project/env_templates/messaging.tpl +46 -0
- aury/boot/commands/templates/project/env_templates/rpc.tpl +28 -0
- aury/boot/commands/templates/project/env_templates/scheduler.tpl +18 -0
- aury/boot/commands/templates/project/env_templates/service.tpl +18 -0
- aury/boot/commands/templates/project/env_templates/storage.tpl +38 -0
- aury/boot/commands/templates/project/env_templates/third_party.tpl +43 -0
- aury/boot/commands/templates/project/modules/tasks.py.tpl +1 -1
- aury/boot/common/logging/__init__.py +26 -674
- aury/boot/common/logging/context.py +132 -0
- aury/boot/common/logging/decorators.py +118 -0
- aury/boot/common/logging/format.py +315 -0
- aury/boot/common/logging/setup.py +214 -0
- 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 +7 -16
- 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/config.py +5 -13
- aury/boot/infrastructure/tasks/manager.py +55 -33
- {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/METADATA +20 -2
- aury_boot-0.0.7.dist-info/RECORD +197 -0
- aury/boot/commands/templates/project/DEVELOPMENT.md.tpl +0 -1397
- aury/boot/commands/templates/project/env.example.tpl +0 -213
- 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.4.dist-info/RECORD +0 -137
- {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/WHEEL +0 -0
- {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Model(数据模型)
|
|
2
|
+
|
|
3
|
+
## 1.1 模型基类选择
|
|
4
|
+
|
|
5
|
+
框架提供多种预组合基类,按需选择:
|
|
6
|
+
|
|
7
|
+
| 基类 | 主键 | 时间戳 | 软删除 | 乐观锁 | 场景 |
|
|
8
|
+
||------|------|--------|--------|--------|------|
|
|
9
|
+
|| `Model` | int | ✓ | ✗ | ✗ | 简单实体 |
|
|
10
|
+
|| `AuditableStateModel` | int | ✓ | ✓ | ✗ | **推荐** |
|
|
11
|
+
|| `FullFeaturedModel` | int | ✓ | ✓ | ✓ | 全功能 |
|
|
12
|
+
|| `UUIDModel` | UUID | ✓ | ✗ | ✗ | UUID主键 |
|
|
13
|
+
|| `UUIDAuditableStateModel` | UUID | ✓ | ✓ | ✗ | UUID+软删除 |
|
|
14
|
+
|| `VersionedModel` | int | ✗ | ✗ | ✓ | 乐观锁 |
|
|
15
|
+
|| `VersionedUUIDModel` | UUID | ✓ | ✗ | ✓ | UUID+乐观锁 |
|
|
16
|
+
|| `FullFeaturedUUIDModel` | UUID | ✓ | ✓ | ✓ | UUID全功能 |
|
|
17
|
+
|
|
18
|
+
## 1.2 基类自动提供的字段
|
|
19
|
+
|
|
20
|
+
**IDMixin** (int 主键):
|
|
21
|
+
```python
|
|
22
|
+
id: Mapped[int] # 自增主键
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**UUIDMixin** (UUID 主键):
|
|
26
|
+
```python
|
|
27
|
+
import uuid
|
|
28
|
+
from sqlalchemy.types import Uuid as SQLAlchemyUuid
|
|
29
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
30
|
+
|
|
31
|
+
id: Mapped[uuid.UUID] = mapped_column(
|
|
32
|
+
SQLAlchemyUuid(as_uuid=True), # SQLAlchemy 2.0 自动适配 PG(uuid) 和 MySQL(char(36))
|
|
33
|
+
primary_key=True,
|
|
34
|
+
default=uuid.uuid4,
|
|
35
|
+
)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> **关于 SQLAlchemyUuid**:
|
|
39
|
+
> - 使用 `sqlalchemy.types.Uuid`(导入为 `SQLAlchemyUuid`)而非直接使用 `uuid.UUID`
|
|
40
|
+
> - `as_uuid=True` 确保 Python 层面使用 UUID 对象而非字符串
|
|
41
|
+
> - 框架会自动适配不同数据库:PostgreSQL 使用原生 UUID 类型,MySQL/SQLite 使用 CHAR(36)
|
|
42
|
+
> - 如需手动定义 UUID 字段,请使用 `SQLAlchemyUuid(as_uuid=True)` 而非 `Uuid(as_uuid=True)`
|
|
43
|
+
|
|
44
|
+
**TimestampMixin** (时间戳):
|
|
45
|
+
```python
|
|
46
|
+
created_at: Mapped[datetime] # 创建时间,自动设置
|
|
47
|
+
updated_at: Mapped[datetime] # 更新时间,自动更新
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**AuditableStateMixin** (软删除):
|
|
51
|
+
```python
|
|
52
|
+
deleted_at: Mapped[int] # 删除时间戳,0=未删除,>0=删除时间
|
|
53
|
+
# 自动提供:is_deleted 属性、mark_deleted() 方法、restore() 方法
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
> **注意**:使用软删除的模型不要单独使用 `unique=True`,否则删除后再插入相同值会报错。
|
|
57
|
+
> 应使用复合唯一索引:`UniqueConstraint("email", "deleted_at", name="uq_users_email_deleted")`
|
|
58
|
+
|
|
59
|
+
**VersionMixin** (乐观锁):
|
|
60
|
+
```python
|
|
61
|
+
version: Mapped[int] # 版本号,自动管理
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 1.3 Model 编写示例
|
|
65
|
+
|
|
66
|
+
**文件**: `{package_name}/models/user.py`
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
"""User 数据模型。"""
|
|
70
|
+
|
|
71
|
+
from sqlalchemy import String, Boolean, UniqueConstraint
|
|
72
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
73
|
+
|
|
74
|
+
from aury.boot.domain.models import AuditableStateModel
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class User(AuditableStateModel):
|
|
78
|
+
"""User 模型。
|
|
79
|
+
|
|
80
|
+
继承 AuditableStateModel 自动获得:
|
|
81
|
+
- id: int 自增主键
|
|
82
|
+
- created_at, updated_at: 时间戳
|
|
83
|
+
- deleted_at: 软删除支持
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
__tablename__ = "users"
|
|
87
|
+
|
|
88
|
+
name: Mapped[str] = mapped_column(String(100), nullable=False, comment="用户名")
|
|
89
|
+
email: Mapped[str] = mapped_column(String(255), nullable=False, index=True, comment="邮箱")
|
|
90
|
+
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否激活")
|
|
91
|
+
|
|
92
|
+
# 软删除模型必须使用复合唯一约束(包含 deleted_at),避免删除后无法插入相同值
|
|
93
|
+
# 注意:复合约束必须使用 __table_args__,这是 SQLAlchemy 的要求
|
|
94
|
+
__table_args__ = (
|
|
95
|
+
UniqueConstraint("email", "deleted_at", name="uq_users_email_deleted"),
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 1.4 字段类型映射
|
|
100
|
+
|
|
101
|
+
| Python 类型 | SQLAlchemy 类型 | 说明 |
|
|
102
|
+
|-------------|----------------|------|
|
|
103
|
+
| `str` | `String(length)` | 必须指定长度 |
|
|
104
|
+
| `int` | `Integer` | 整数 |
|
|
105
|
+
| `float` | `Float` | 浮点数 |
|
|
106
|
+
| `bool` | `Boolean` | 布尔值 |
|
|
107
|
+
| `datetime` | `DateTime(timezone=True)` | 带时区 |
|
|
108
|
+
| `date` | `Date` | 日期 |
|
|
109
|
+
| `Decimal` | `Numeric(precision, scale)` | 精确小数 |
|
|
110
|
+
| `dict`/`list` | `JSON` | JSON 数据 |
|
|
111
|
+
| `uuid.UUID` | `SQLAlchemyUuid(as_uuid=True)` | UUID(推荐使用 `from sqlalchemy.types import Uuid as SQLAlchemyUuid`) |
|
|
112
|
+
|
|
113
|
+
## 1.5 常用字段约束
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from sqlalchemy import String, Integer, Index, UniqueConstraint
|
|
117
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
118
|
+
import uuid
|
|
119
|
+
|
|
120
|
+
class Example(AuditableStateModel):
|
|
121
|
+
__tablename__ = "examples"
|
|
122
|
+
|
|
123
|
+
# 可选字段
|
|
124
|
+
description: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
|
125
|
+
|
|
126
|
+
# 带默认值
|
|
127
|
+
status: Mapped[int] = mapped_column(Integer, default=0, server_default="0", index=True)
|
|
128
|
+
|
|
129
|
+
# 单列索引:直接在 mapped_column 中使用 index=True(推荐)
|
|
130
|
+
code: Mapped[str] = mapped_column(String(50), index=True, comment="编码")
|
|
131
|
+
|
|
132
|
+
# 单列唯一约束:直接在 mapped_column 中使用 unique=True(仅非软删除模型)
|
|
133
|
+
# 注意:软删除模型不能单独使用 unique=True,必须使用复合唯一约束
|
|
134
|
+
|
|
135
|
+
# 关联字段(不使用数据库外键,通过程序控制关系)
|
|
136
|
+
category_id: Mapped[int | None] = mapped_column(index=True)
|
|
137
|
+
|
|
138
|
+
# 复合索引和复合唯一约束:必须使用 __table_args__(SQLAlchemy 要求)
|
|
139
|
+
# 软删除模型必须使用复合唯一约束(包含 deleted_at),避免删除后无法插入相同值
|
|
140
|
+
__table_args__ = (
|
|
141
|
+
Index("ix_examples_status_created", "status", "created_at"),
|
|
142
|
+
UniqueConstraint("code", "deleted_at", name="uq_examples_code_deleted"),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# 非软删除模型可以直接使用 unique=True 和 index=True
|
|
147
|
+
class Config(Model): # Model 不包含软删除
|
|
148
|
+
__tablename__ = "configs"
|
|
149
|
+
|
|
150
|
+
# 单列唯一约束:直接在 mapped_column 中使用(推荐)
|
|
151
|
+
key: Mapped[str] = mapped_column(String(100), unique=True, index=True, comment="配置键")
|
|
152
|
+
value: Mapped[str] = mapped_column(String(500), comment="配置值")
|
|
153
|
+
|
|
154
|
+
# 单列索引:直接在 mapped_column 中使用(推荐)
|
|
155
|
+
name: Mapped[str] = mapped_column(String(100), index=True, comment="配置名称")
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**约束定义最佳实践**:
|
|
159
|
+
|
|
160
|
+
1. **单列索引**:使用 `index=True` 在 `mapped_column` 中(推荐)
|
|
161
|
+
```python
|
|
162
|
+
email: Mapped[str] = mapped_column(String(255), index=True)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
2. **单列唯一约束**:
|
|
166
|
+
- 非软删除模型:使用 `unique=True` 在 `mapped_column` 中(推荐)
|
|
167
|
+
- 软删除模型:必须使用复合唯一约束(包含 `deleted_at`)
|
|
168
|
+
|
|
169
|
+
3. **复合索引/唯一约束**:必须使用 `__table_args__`(SQLAlchemy 要求)
|
|
170
|
+
```python
|
|
171
|
+
__table_args__ = (
|
|
172
|
+
Index("ix_name", "col1", "col2"),
|
|
173
|
+
UniqueConstraint("col1", "col2", name="uq_name"),
|
|
174
|
+
)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
4. **关联关系**:**不建议使用数据库外键**,通过程序控制关系
|
|
178
|
+
- 便于分库分表、微服务拆分
|
|
179
|
+
- 避免级联操作影响性能
|
|
180
|
+
- 简化数据迁移
|
|
181
|
+
```python
|
|
182
|
+
# 只存储关联 ID,不使用 ForeignKey
|
|
183
|
+
category_id: Mapped[int | None] = mapped_column(index=True)
|
|
184
|
+
```
|
|
@@ -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
|
+
```
|