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.
Files changed (122) hide show
  1. aury/boot/__init__.py +2 -2
  2. aury/boot/_version.py +2 -2
  3. aury/boot/application/__init__.py +60 -36
  4. aury/boot/application/adapter/__init__.py +112 -0
  5. aury/boot/application/adapter/base.py +511 -0
  6. aury/boot/application/adapter/config.py +242 -0
  7. aury/boot/application/adapter/decorators.py +259 -0
  8. aury/boot/application/adapter/exceptions.py +202 -0
  9. aury/boot/application/adapter/http.py +325 -0
  10. aury/boot/application/app/__init__.py +12 -8
  11. aury/boot/application/app/base.py +12 -0
  12. aury/boot/application/app/components.py +137 -44
  13. aury/boot/application/app/middlewares.py +9 -4
  14. aury/boot/application/app/startup.py +249 -0
  15. aury/boot/application/config/__init__.py +36 -1
  16. aury/boot/application/config/multi_instance.py +216 -0
  17. aury/boot/application/config/settings.py +398 -149
  18. aury/boot/application/constants/components.py +6 -0
  19. aury/boot/application/errors/handlers.py +17 -3
  20. aury/boot/application/middleware/logging.py +21 -120
  21. aury/boot/application/rpc/__init__.py +2 -2
  22. aury/boot/commands/__init__.py +30 -10
  23. aury/boot/commands/app.py +131 -1
  24. aury/boot/commands/docs.py +104 -17
  25. aury/boot/commands/generate.py +22 -22
  26. aury/boot/commands/init.py +68 -17
  27. aury/boot/commands/server/app.py +2 -3
  28. aury/boot/commands/templates/project/AGENTS.md.tpl +221 -0
  29. aury/boot/commands/templates/project/README.md.tpl +2 -2
  30. aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl +59 -0
  31. aury/boot/commands/templates/project/aury_docs/01-model.md.tpl +184 -0
  32. aury/boot/commands/templates/project/aury_docs/02-repository.md.tpl +206 -0
  33. aury/boot/commands/templates/project/aury_docs/03-service.md.tpl +398 -0
  34. aury/boot/commands/templates/project/aury_docs/04-schema.md.tpl +95 -0
  35. aury/boot/commands/templates/project/aury_docs/05-api.md.tpl +116 -0
  36. aury/boot/commands/templates/project/aury_docs/06-exception.md.tpl +118 -0
  37. aury/boot/commands/templates/project/aury_docs/07-cache.md.tpl +122 -0
  38. aury/boot/commands/templates/project/aury_docs/08-scheduler.md.tpl +32 -0
  39. aury/boot/commands/templates/project/aury_docs/09-tasks.md.tpl +38 -0
  40. aury/boot/commands/templates/project/aury_docs/10-storage.md.tpl +115 -0
  41. aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl +131 -0
  42. aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl +56 -0
  43. aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +104 -0
  44. aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl +102 -0
  45. aury/boot/commands/templates/project/aury_docs/15-events.md.tpl +147 -0
  46. aury/boot/commands/templates/project/aury_docs/16-adapter.md.tpl +403 -0
  47. aury/boot/commands/templates/project/{CLI.md.tpl → aury_docs/99-cli.md.tpl} +19 -19
  48. aury/boot/commands/templates/project/config.py.tpl +10 -10
  49. aury/boot/commands/templates/project/env_templates/_header.tpl +10 -0
  50. aury/boot/commands/templates/project/env_templates/admin.tpl +49 -0
  51. aury/boot/commands/templates/project/env_templates/cache.tpl +14 -0
  52. aury/boot/commands/templates/project/env_templates/database.tpl +22 -0
  53. aury/boot/commands/templates/project/env_templates/log.tpl +18 -0
  54. aury/boot/commands/templates/project/env_templates/messaging.tpl +46 -0
  55. aury/boot/commands/templates/project/env_templates/rpc.tpl +28 -0
  56. aury/boot/commands/templates/project/env_templates/scheduler.tpl +18 -0
  57. aury/boot/commands/templates/project/env_templates/service.tpl +18 -0
  58. aury/boot/commands/templates/project/env_templates/storage.tpl +38 -0
  59. aury/boot/commands/templates/project/env_templates/third_party.tpl +43 -0
  60. aury/boot/commands/templates/project/modules/tasks.py.tpl +1 -1
  61. aury/boot/common/logging/__init__.py +26 -674
  62. aury/boot/common/logging/context.py +132 -0
  63. aury/boot/common/logging/decorators.py +118 -0
  64. aury/boot/common/logging/format.py +315 -0
  65. aury/boot/common/logging/setup.py +214 -0
  66. aury/boot/contrib/admin_console/auth.py +2 -3
  67. aury/boot/contrib/admin_console/install.py +1 -1
  68. aury/boot/domain/models/mixins.py +48 -1
  69. aury/boot/domain/pagination/__init__.py +94 -0
  70. aury/boot/domain/repository/impl.py +1 -1
  71. aury/boot/domain/repository/interface.py +1 -1
  72. aury/boot/domain/transaction/__init__.py +8 -9
  73. aury/boot/infrastructure/__init__.py +86 -29
  74. aury/boot/infrastructure/cache/backends.py +102 -18
  75. aury/boot/infrastructure/cache/base.py +12 -0
  76. aury/boot/infrastructure/cache/manager.py +153 -91
  77. aury/boot/infrastructure/channel/__init__.py +24 -0
  78. aury/boot/infrastructure/channel/backends/__init__.py +9 -0
  79. aury/boot/infrastructure/channel/backends/memory.py +83 -0
  80. aury/boot/infrastructure/channel/backends/redis.py +88 -0
  81. aury/boot/infrastructure/channel/base.py +92 -0
  82. aury/boot/infrastructure/channel/manager.py +203 -0
  83. aury/boot/infrastructure/clients/__init__.py +22 -0
  84. aury/boot/infrastructure/clients/rabbitmq/__init__.py +9 -0
  85. aury/boot/infrastructure/clients/rabbitmq/config.py +46 -0
  86. aury/boot/infrastructure/clients/rabbitmq/manager.py +288 -0
  87. aury/boot/infrastructure/clients/redis/__init__.py +28 -0
  88. aury/boot/infrastructure/clients/redis/config.py +51 -0
  89. aury/boot/infrastructure/clients/redis/manager.py +264 -0
  90. aury/boot/infrastructure/database/config.py +7 -16
  91. aury/boot/infrastructure/database/manager.py +16 -38
  92. aury/boot/infrastructure/events/__init__.py +18 -21
  93. aury/boot/infrastructure/events/backends/__init__.py +11 -0
  94. aury/boot/infrastructure/events/backends/memory.py +86 -0
  95. aury/boot/infrastructure/events/backends/rabbitmq.py +193 -0
  96. aury/boot/infrastructure/events/backends/redis.py +162 -0
  97. aury/boot/infrastructure/events/base.py +127 -0
  98. aury/boot/infrastructure/events/manager.py +224 -0
  99. aury/boot/infrastructure/mq/__init__.py +24 -0
  100. aury/boot/infrastructure/mq/backends/__init__.py +9 -0
  101. aury/boot/infrastructure/mq/backends/rabbitmq.py +179 -0
  102. aury/boot/infrastructure/mq/backends/redis.py +167 -0
  103. aury/boot/infrastructure/mq/base.py +143 -0
  104. aury/boot/infrastructure/mq/manager.py +239 -0
  105. aury/boot/infrastructure/scheduler/manager.py +7 -3
  106. aury/boot/infrastructure/storage/__init__.py +9 -9
  107. aury/boot/infrastructure/storage/base.py +17 -5
  108. aury/boot/infrastructure/storage/factory.py +0 -1
  109. aury/boot/infrastructure/tasks/__init__.py +2 -2
  110. aury/boot/infrastructure/tasks/config.py +5 -13
  111. aury/boot/infrastructure/tasks/manager.py +55 -33
  112. {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/METADATA +20 -2
  113. aury_boot-0.0.7.dist-info/RECORD +197 -0
  114. aury/boot/commands/templates/project/DEVELOPMENT.md.tpl +0 -1397
  115. aury/boot/commands/templates/project/env.example.tpl +0 -213
  116. aury/boot/infrastructure/events/bus.py +0 -362
  117. aury/boot/infrastructure/events/config.py +0 -52
  118. aury/boot/infrastructure/events/consumer.py +0 -134
  119. aury/boot/infrastructure/events/models.py +0 -63
  120. aury_boot-0.0.4.dist-info/RECORD +0 -137
  121. {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/WHEEL +0 -0
  122. {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
+ ```