aury-boot 0.0.33__py3-none-any.whl → 0.0.35__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/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.0.33'
32
- __version_tuple__ = version_tuple = (0, 0, 33)
31
+ __version__ = version = '0.0.35'
32
+ __version_tuple__ = version_tuple = (0, 0, 35)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -280,7 +280,7 @@ class FoundationApp(FastAPI):
280
280
  title: str = "Aury Service",
281
281
  version: str = "1.0.0",
282
282
  description: str | None = None,
283
- intercept_loggers: list[str] | None = None,
283
+ logger_levels: list[tuple[str, str]] | None = None,
284
284
  **kwargs: Any,
285
285
  ) -> None:
286
286
  """初始化应用。
@@ -290,9 +290,8 @@ class FoundationApp(FastAPI):
290
290
  title: 应用标题
291
291
  version: 应用版本
292
292
  description: 应用描述
293
- intercept_loggers: 额外需要拦截的标准 logging logger 名称列表,
294
- 会追加到框架默认列表 (uvicorn, sqlalchemy.engine )
295
- 也可通过配置 LOG__INTERCEPT_LOGGERS 设置。
293
+ logger_levels: 需要设置特定级别的 logger 列表,格式: [("name", "LEVEL"), ...]
294
+ 例如: [("sse_starlette", "WARNING"), ("httpx", "INFO")]
296
295
  **kwargs: 传递给 FastAPI 的其他参数
297
296
  """
298
297
  # 加载配置
@@ -304,11 +303,6 @@ class FoundationApp(FastAPI):
304
303
  frame = sys._getframe(1)
305
304
  self._caller_module = frame.f_globals.get("__name__", "__main__")
306
305
 
307
- # 合并 intercept_loggers:参数 + 配置
308
- merged_intercept = list(config.log.intercept_loggers)
309
- if intercept_loggers:
310
- merged_intercept.extend(intercept_loggers)
311
-
312
306
  # 初始化日志(必须在其他操作之前)
313
307
  setup_logging(
314
308
  log_level=config.log.level,
@@ -318,7 +312,7 @@ class FoundationApp(FastAPI):
318
312
  retention_days=config.log.retention_days,
319
313
  enable_file_rotation=config.log.enable_file_rotation,
320
314
  enable_console=config.log.enable_console,
321
- intercept_loggers=merged_intercept,
315
+ logger_levels=logger_levels,
322
316
  )
323
317
 
324
318
  # 注册 access 日志(HTTP 请求日志)
@@ -401,14 +401,6 @@ class LogSettings(BaseModel):
401
401
  default=False,
402
402
  description="是否记录 WebSocket 消息内容(注意性能和敏感数据)"
403
403
  )
404
- intercept_loggers: list[str] = Field(
405
- default_factory=list,
406
- description=(
407
- "额外需要由 loguru 接管的标准 logging logger 名称列表。"
408
- "框架默认已拦截 uvicorn、uvicorn.error、uvicorn.access、sqlalchemy.engine,"
409
- "此处配置会追加到默认列表。"
410
- ),
411
- )
412
404
 
413
405
 
414
406
  class ServiceSettings(BaseModel):
@@ -84,9 +84,12 @@ def run_scheduler(
84
84
  module = __import__(module_path, fromlist=[app_name])
85
85
  application = getattr(module, app_name)
86
86
 
87
- # 设置日志上下文
88
- from aury.boot.common.logging import set_service_context
89
- set_service_context("scheduler")
87
+ # 设置日志(必须在其他操作之前)
88
+ from aury.boot.common.logging import setup_logging
89
+ setup_logging(
90
+ log_level=getattr(application, "_config", None) and application._config.log.level or "INFO",
91
+ service_type="scheduler",
92
+ )
90
93
 
91
94
  # 获取调度器
92
95
  from aury.boot.infrastructure.scheduler import SchedulerManager
@@ -11,8 +11,9 @@ class {class_name}Repository(BaseRepository[{class_name}]):
11
11
  继承 BaseRepository 自动获得:
12
12
  - get(id): 按 ID 获取
13
13
  - get_by(**filters): 按条件获取单个
14
- - list(skip, limit, **filters): 获取列表
15
- - paginate(params, **filters): 分页获取
14
+ - list(skip, limit, sort, **filters): 获取列表
15
+ - paginate(pagination, sort, **filters): 分页获取
16
+ - stream(batch_size, sort, **filters): 流式查询
16
17
  - create(data): 创建
17
18
  - update(entity, data): 更新
18
19
  - delete(entity, soft=True): 删除
@@ -5,7 +5,7 @@
5
5
  **文件**: `{package_name}/repositories/user_repository.py`
6
6
 
7
7
  ```python
8
- """User 数据访问层。"""
8
+ """User 数据访问层。"""
9
9
 
10
10
  from aury.boot.domain.repository.impl import BaseRepository
11
11
 
@@ -18,13 +18,14 @@ class UserRepository(BaseRepository[User]):
18
18
  继承 BaseRepository 自动获得:
19
19
  - get(id): 按 ID 获取
20
20
  - get_by(**filters): 按条件获取单个
21
- - list(skip, limit, **filters): 获取列表
22
- - paginate(params, **filters): 分页获取
21
+ - list(skip, limit, sort, **filters): 获取列表
22
+ - paginate(pagination, sort, **filters): 分页获取
23
23
  - count(**filters): 计数
24
24
  - exists(**filters): 是否存在
25
25
  - create(data): 创建
26
26
  - update(entity, data): 更新
27
27
  - delete(entity, soft=True): 删除(默认软删除)
28
+ - stream(batch_size, sort, **filters): 流式查询
28
29
  - batch_create(data_list): 批量创建
29
30
  - bulk_insert(data_list): 高性能批量插入
30
31
  """
@@ -35,7 +36,7 @@ class UserRepository(BaseRepository[User]):
35
36
 
36
37
  async def list_active(self, skip: int = 0, limit: int = 100) -> list[User]:
37
38
  """获取激活用户列表。"""
38
- return await self.list(skip=skip, limit=limit, is_active=True)
39
+ return await self.list(skip=skip, limit=limit, sort="-created_at", is_active=True)
39
40
  ```
40
41
 
41
42
  ## 2.2 BaseRepository 方法详解
@@ -50,25 +51,36 @@ repo = UserRepository(session, User)
50
51
  user = await repo.get(user_id) # 按 ID(支持 int/UUID)
51
52
  user = await repo.get_by(email="a@b.com") # 按条件(简单 AND 过滤)
52
53
  users = await repo.list(skip=0, limit=10) # 列表
54
+ users = await repo.list(sort="-created_at") # 带排序
53
55
  users = await repo.list(is_active=True) # 带过滤
54
56
  count = await repo.count(is_active=True) # 计数
55
57
  exists = await repo.exists(email="a@b.com") # 是否存在
56
58
 
57
59
  # === 分页 ===
58
- from aury.boot.domain.pagination import PaginationParams, SortParams
60
+ from aury.boot.domain.pagination import PaginationParams
59
61
 
62
+ # 方式1: offset/limit(与 SQL 语义一致)
60
63
  result = await repo.paginate(
61
- pagination_params=PaginationParams(page=1, page_size=20),
62
- sort_params=SortParams.from_string("-created_at"),
64
+ pagination=PaginationParams(offset=0, limit=20),
65
+ sort="-created_at",
66
+ is_active=True,
67
+ )
68
+
69
+ # 方式2: page/size(UI 风格)
70
+ result = await repo.paginate(
71
+ pagination=PaginationParams.of(page=1, size=20),
72
+ sort="-created_at",
63
73
  is_active=True,
64
74
  )
65
75
 
66
76
  # PaginationResult 结构:
67
77
  # - result.items: list[T] # 数据列表
68
78
  # - result.total: int # 总记录数
69
- # - result.page: int # 当前页码
70
- # - result.page_size: int # 每页数量
71
- # - result.total_pages: int # 总页数
79
+ # - result.offset: int # 当前偏移量
80
+ # - result.limit: int # 每页数量
81
+ # - result.page: int # 当前页码(计算属性)
82
+ # - result.size: int # limit 别名
83
+ # - result.total_pages: int # 总页数(计算属性)
72
84
  # - result.has_next: bool # 是否有下一页
73
85
  # - result.has_prev: bool # 是否有上一页
74
86
 
@@ -96,11 +108,11 @@ result = await repo.cursor_paginate(
96
108
 
97
109
  # === 流式查询(大数据处理) ===
98
110
  # 逐条流式处理,不会一次性加载到内存
99
- async for user in repo.stream(batch_size=1000, is_active=True):
111
+ async for user in repo.stream(batch_size=1000, sort="-created_at", is_active=True):
100
112
  await process(user)
101
113
 
102
114
  # 批量流式处理
103
- async for batch in repo.stream_batches(batch_size=1000):
115
+ async for batch in repo.stream_batches(batch_size=1000, sort="id"):
104
116
  await bulk_sync_to_es(batch)
105
117
 
106
118
  # === 创建 ===
@@ -140,27 +152,28 @@ user = await repo.get_by(status__ne="archived")
140
152
 
141
153
  > 注意:filters 条件之间用 AND 组合;如需 AND/OR/NOT 的复杂组合,请使用 `QueryBuilder`(见 2.4)。
142
154
 
143
- ### 2.2.2 排序参数(SortParams
155
+ ### 2.2.2 排序参数(sort
144
156
 
145
- `SortParams.from_string()` 支持两种语法:
157
+ 所有查询方法的 `sort` 参数支持多种格式:
146
158
 
147
159
  ```python
148
- from aury.boot.domain.pagination import SortParams
160
+ # 字符串(推荐)
161
+ await repo.list(sort="-created_at") # 简洁语法
162
+ await repo.list(sort="-created_at,priority") # 多字段
163
+ await repo.list(sort="created_at:desc,priority:asc") # 完整语法
149
164
 
150
- # 简洁语法:"-" 前缀表示降序
151
- sort_params = SortParams.from_string("-created_at")
152
- sort_params = SortParams.from_string("-created_at,priority") # 多字段
165
+ # 字符串列表
166
+ await repo.list(sort=["-created_at", "name"])
153
167
 
154
- # 完整语法:字段:方向
155
- sort_params = SortParams.from_string("created_at:desc,priority:asc")
168
+ # SortParams 对象(需要白名单验证时)
169
+ from aury.boot.domain.pagination import SortParams
156
170
 
157
- # 带字段白名单验证(防止 SQL 注入)
158
171
  ALLOWED_FIELDS = {{"id", "created_at", "priority", "status"}}
159
172
  sort_params = SortParams.from_string(
160
173
  "-created_at",
161
- allowed_fields=ALLOWED_FIELDS
174
+ allowed_fields=ALLOWED_FIELDS # 传入非法字段抛 ValueError
162
175
  )
163
- # 传入非法字段会抛出 ValueError
176
+ await repo.list(sort=sort_params)
164
177
  ```
165
178
 
166
179
  ### 2.2.3 查询全部(limit=None)
@@ -25,15 +25,17 @@ config = AppConfig()
25
25
  # - HEALTH_CHECK_PATH: 健康检查路径(默认 /api/health)
26
26
  # - HEALTH_CHECK_ENABLED: 是否启用(默认 true)
27
27
  #
28
- # 日志拦截:
29
- # 框架默认拦截 uvicorn/sqlalchemy.engine,可通过 intercept_loggers 追加额外的 logger
28
+ # 日志:
29
+ # 框架自动全局接管所有 logging,无需配置
30
+ # 要查看 TRACE 级别日志,设置 LOG__LEVEL=TRACE
31
+ # 要屏蔽某些库的 DEBUG 日志,使用 logger_levels 参数
30
32
  #
31
33
  app = FoundationApp(
32
34
  title="{project_name}",
33
35
  version="0.1.0",
34
36
  description="{project_name} - 基于 Aury Boot",
35
37
  config=config,
36
- intercept_loggers=[],
38
+ # logger_levels=[("sse_starlette", "WARNING")], # 可选:设置特定库的日志级别
37
39
  )
38
40
 
39
41
  # 注册 API 路由
@@ -100,9 +100,12 @@ def run_worker(
100
100
  module = __import__(module_path, fromlist=[app_name])
101
101
  application = getattr(module, app_name)
102
102
 
103
- # 设置日志上下文
104
- from aury.boot.common.logging import set_service_context
105
- set_service_context("worker")
103
+ # 设置日志(必须在其他操作之前)
104
+ from aury.boot.common.logging import setup_logging
105
+ setup_logging(
106
+ log_level=getattr(application, "_config", None) and application._config.log.level or "INFO",
107
+ service_type="worker",
108
+ )
106
109
 
107
110
  # 尝试导入 dramatiq
108
111
  try:
@@ -41,14 +41,13 @@ from aury.boot.common.logging.format import (
41
41
  log_exception,
42
42
  )
43
43
  from aury.boot.common.logging.setup import (
44
- DEFAULT_INTERCEPT_LOGGERS,
44
+ TRACE,
45
45
  register_log_sink,
46
- setup_intercept,
47
46
  setup_logging,
48
47
  )
49
48
 
50
49
  __all__ = [
51
- "DEFAULT_INTERCEPT_LOGGERS",
50
+ "TRACE",
52
51
  "ServiceContext",
53
52
  "format_exception_java_style",
54
53
  "get_class_logger",
@@ -61,7 +60,6 @@ __all__ = [
61
60
  "register_log_sink",
62
61
  "set_service_context",
63
62
  "set_trace_id",
64
- "setup_intercept",
65
63
  "setup_logging",
66
64
  ]
67
65
 
@@ -11,6 +11,25 @@ from typing import Any
11
11
 
12
12
  from loguru import logger
13
13
 
14
+ # =============================================================================
15
+ # TRACE Level 支持
16
+ # =============================================================================
17
+ # 标准 logging 没有 TRACE,需要手动添加
18
+ # TRACE (5) < DEBUG (10),用于超细粒度调试(如每个 streaming chunk)
19
+ TRACE = 5
20
+ logging.addLevelName(TRACE, "TRACE")
21
+
22
+
23
+ def _add_trace_method() -> None:
24
+ """为标准 logging.Logger 添加 trace() 方法。"""
25
+ def trace(self: logging.Logger, msg: str, *args: Any, **kwargs: Any) -> None:
26
+ if self.isEnabledFor(TRACE):
27
+ self._log(TRACE, msg, args, **kwargs)
28
+
29
+ logging.Logger.trace = trace # type: ignore[attr-defined]
30
+
31
+ _add_trace_method()
32
+
14
33
  from aury.boot.common.logging.context import (
15
34
  ServiceContext,
16
35
  _to_service_context,
@@ -89,31 +108,27 @@ def register_log_sink(
89
108
  logger.debug(f"注册日志 sink: {name} (filter_key={filter_key})")
90
109
 
91
110
 
92
- # 默认拦截的标准 logging 日志记录器
93
- # - uvicorn: Uvicorn 服务器日志
94
- # - uvicorn.error: Uvicorn 错误日志
95
- # - sqlalchemy.engine: SQLAlchemy SQL 语句日志
96
- # 注意:uvicorn.access 不拦截,因为框架有自己的 RequestLoggingMiddleware
97
- DEFAULT_INTERCEPT_LOGGERS = [
98
- "uvicorn",
99
- "uvicorn.error",
100
- "sqlalchemy.engine",
101
- ]
102
-
103
111
 
104
112
  class _InterceptHandler(logging.Handler):
105
113
  """将标准 logging 日志转发到 loguru 的处理器。"""
106
114
 
107
115
  def emit(self, record: logging.LogRecord) -> None:
108
116
  # 获取对应的 loguru 级别
117
+ # loguru 原生支持 TRACE,标准 logging 的 TRACE(5) 会自动映射
109
118
  try:
110
119
  level = logger.level(record.levelname).name
111
120
  except ValueError:
112
121
  level = record.levelno
113
122
 
114
123
  # 查找调用者的帧深度
115
- frame, depth = logging.currentframe(), 2
116
- while frame and frame.f_code.co_filename == logging.__file__:
124
+ # 跳过 logging 模块和本文件的所有帧
125
+ frame = logging.currentframe()
126
+ depth = 0
127
+ while frame is not None:
128
+ filename = frame.f_code.co_filename
129
+ # 跳过 logging 模块、本文件、loguru 内部
130
+ if "logging" not in filename and "loguru" not in filename:
131
+ break
117
132
  frame = frame.f_back
118
133
  depth += 1
119
134
 
@@ -122,35 +137,22 @@ class _InterceptHandler(logging.Handler):
122
137
  )
123
138
 
124
139
 
125
- def _setup_intercept(logger_names: list[str]) -> None:
126
- """ loguru 接管指定的标准 logging 日志记录器。"""
127
- handler = _InterceptHandler()
128
- for name in logger_names:
129
- std_logger = logging.getLogger(name)
130
- std_logger.handlers = [handler]
131
- std_logger.setLevel(logging.DEBUG)
132
- std_logger.propagate = False
133
-
134
-
135
- def setup_intercept(logger_names: list[str] | None = None) -> None:
136
- """拦截标准 logging 日志记录器并转发到 loguru。
137
-
138
- 用于独立脚本/CLI 入口点(不使用 FoundationApp 时)。
139
- FoundationApp 会自动调用此函数,无需手动调用。
140
-
140
+ def _setup_global_intercept(logger_levels: list[tuple[str, str]] | None = None) -> None:
141
+ """全局接管所有标准 logging,转发到 loguru。
142
+
143
+ 这样任何使用 logging.getLogger() 的库都会自动被接管。
144
+
141
145
  Args:
142
- logger_names: 额外需要拦截的 logger 名称列表,
143
- 会追加到默认列表 (uvicorn, sqlalchemy.engine )
144
-
145
- 使用示例::
146
-
147
- from aury.boot.common.logging import setup_logging, setup_intercept
148
-
149
- setup_logging(log_level="DEBUG")
150
- setup_intercept(["my_package", "third_party_lib"])
146
+ logger_levels: 需要设置特定级别的 logger 列表,格式: [("name", "LEVEL"), ...]
147
+ 例如: [("sse_starlette", "WARNING"), ("httpx", "INFO")]
151
148
  """
152
- to_intercept = DEFAULT_INTERCEPT_LOGGERS + (logger_names or [])
153
- _setup_intercept(to_intercept)
149
+ logging.root.handlers = [_InterceptHandler()]
150
+ logging.root.setLevel(TRACE) # 接收所有级别,包括 TRACE
151
+
152
+ # 对指定的 logger 设置特定级别
153
+ if logger_levels:
154
+ for name, level in logger_levels:
155
+ logging.getLogger(name).setLevel(level.upper())
154
156
 
155
157
 
156
158
  def setup_logging(
@@ -162,7 +164,7 @@ def setup_logging(
162
164
  retention_days: int = 7,
163
165
  rotation_size: str = "50 MB",
164
166
  enable_console: bool = True,
165
- intercept_loggers: list[str] | None = None,
167
+ logger_levels: list[tuple[str, str]] | None = None,
166
168
  ) -> None:
167
169
  """设置日志配置。
168
170
 
@@ -177,7 +179,7 @@ def setup_logging(
177
179
  可通过 register_log_sink() 注册额外的日志文件(如 access.log)。
178
180
 
179
181
  Args:
180
- log_level: 日志级别(DEBUG/INFO/WARNING/ERROR/CRITICAL)
182
+ log_level: 日志级别(DEBUG/INFO/WARNING/ERROR/CRITICAL/TRACE
181
183
  log_dir: 日志目录(默认:./logs)
182
184
  service_type: 服务类型(app/scheduler/worker)
183
185
  enable_file_rotation: 是否启用日志轮转
@@ -185,8 +187,8 @@ def setup_logging(
185
187
  retention_days: 日志保留天数(默认:7 天)
186
188
  rotation_size: 单文件大小上限(默认:50 MB)
187
189
  enable_console: 是否输出到控制台
188
- intercept_loggers: 额外需要拦截的标准 logging logger 名称列表,
189
- 会追加到默认列表 (uvicorn, sqlalchemy.engine )
190
+ logger_levels: 需要设置特定级别的 logger 列表,格式: [("name", "LEVEL"), ...]
191
+ 例如: [("sse_starlette", "WARNING"), ("httpx", "INFO")]
190
192
  """
191
193
  log_level = log_level.upper()
192
194
  log_dir = log_dir or "logs"
@@ -273,16 +275,15 @@ def setup_logging(
273
275
  filter=lambda record, c=ctx: record["extra"].get("service") == c,
274
276
  )
275
277
 
276
- # 拦截标准 logging 日志并转发到 loguru
277
- to_intercept = DEFAULT_INTERCEPT_LOGGERS + (intercept_loggers or [])
278
- _setup_intercept(to_intercept)
278
+ # 全局拦截标准 logging 日志并转发到 loguru
279
+ # 所有使用 logging.getLogger() 的库自动被接管
280
+ _setup_global_intercept(logger_levels=logger_levels)
279
281
 
280
- logger.info(f"日志系统初始化完成 | 服务: {service_type} | 级别: {log_level} | 目录: {log_dir}")
282
+ logger.info
281
283
 
282
284
 
283
285
  __all__ = [
284
- "DEFAULT_INTERCEPT_LOGGERS",
286
+ "TRACE",
285
287
  "register_log_sink",
286
- "setup_intercept",
287
288
  "setup_logging",
288
289
  ]
@@ -14,61 +14,49 @@ from pydantic import BaseModel, Field, field_validator
14
14
  class PaginationParams(BaseModel):
15
15
  """分页参数。
16
16
 
17
- 定义分页查询的参数,包括页码和每页记录数。
18
- """
19
-
20
- page: int = Field(default=1, ge=1, description="页码,从1开始")
21
- page_size: int = Field(default=20, ge=1, le=100, description="每页记录数,最大100")
17
+ 使用 offset/limit 模式,与 SQL 语义一致。
18
+ 提供 `of()` 方法支持 page/size 风格的输入。
22
19
 
23
- @property
24
- def offset(self) -> int:
25
- """计算偏移量。
20
+ 示例:
21
+ # 方式1: offset/limit(底层/API风格)
22
+ params = PaginationParams(offset=20, limit=10)
26
23
 
27
- Returns:
28
- int: 偏移量
29
- """
30
- return (self.page - 1) * self.page_size
24
+ # 方式2: page/size(UI风格)
25
+ params = PaginationParams.of(page=3, size=10)
26
+ """
31
27
 
32
- @property
33
- def limit(self) -> int:
34
- """获取限制数量。
35
-
36
- Returns:
37
- int: 限制数量
38
- """
39
- return self.page_size
28
+ offset: int = Field(default=0, ge=0, description="偏移量")
29
+ limit: int = Field(default=20, ge=1, le=100, description="每页记录数,最大100")
40
30
 
41
- @field_validator("page")
42
31
  @classmethod
43
- def validate_page(cls, v: int) -> int:
44
- """验证页码。
32
+ def of(cls, page: int = 1, size: int = 20) -> "PaginationParams":
33
+ """从 page/size 构造分页参数。
45
34
 
46
35
  Args:
47
- v: 页码值
36
+ page: 页码,从 1 开始
37
+ size: 每页记录数
48
38
 
49
39
  Returns:
50
- int: 验证后的页码
51
- """
52
- if v < 1:
53
- raise ValueError("页码必须大于0")
54
- return v
55
-
56
- @field_validator("page_size")
57
- @classmethod
58
- def validate_page_size(cls, v: int) -> int:
59
- """验证每页记录数。
60
-
61
- Args:
62
- v: 每页记录数
40
+ PaginationParams: 分页参数对象
63
41
 
64
- Returns:
65
- int: 验证后的每页记录数
42
+ Raises:
43
+ ValueError: 当 page < 1 或 size 无效时
66
44
  """
67
- if v < 1:
68
- raise ValueError("每页记录数必须大于0")
69
- if v > 100:
70
- raise ValueError("每页记录数不能超过100")
71
- return v
45
+ if page < 1:
46
+ raise ValueError("页码必须大于 0")
47
+ return cls(offset=(page - 1) * size, limit=size)
48
+
49
+ @property
50
+ def page(self) -> int:
51
+ """当前页码(从 1 开始)。"""
52
+ if self.limit == 0:
53
+ return 1
54
+ return self.offset // self.limit + 1
55
+
56
+ @property
57
+ def size(self) -> int:
58
+ """每页记录数(limit 的别名)。"""
59
+ return self.limit
72
60
 
73
61
 
74
62
  class SortParams(BaseModel):
@@ -103,7 +91,7 @@ class SortParams(BaseModel):
103
91
  sort_str: str | None,
104
92
  *,
105
93
  allowed_fields: set[str] | None = None,
106
- default_direction: str = "desc",
94
+ default_direction: str = "asc",
107
95
  ) -> SortParams:
108
96
  """从字符串解析排序参数。
109
97
 
@@ -124,7 +112,7 @@ class SortParams(BaseModel):
124
112
 
125
113
  示例:
126
114
  >>> SortParams.from_string("-created_at,priority")
127
- SortParams(sorts=[('created_at', 'desc'), ('priority', 'desc')])
115
+ SortParams(sorts=[('created_at', 'desc'), ('priority', 'asc')])
128
116
 
129
117
  >>> SortParams.from_string("created_at:desc,priority:asc")
130
118
  SortParams(sorts=[('created_at', 'desc'), ('priority', 'asc')])
@@ -233,66 +221,76 @@ class PaginationResult[ModelType](BaseModel):
233
221
  """分页结果。
234
222
 
235
223
  封装分页查询的结果,包括数据列表和分页信息。
224
+ 同时提供 offset/limit 和 page/size 两种风格的字段。
236
225
  """
237
226
 
238
227
  items: list[ModelType] = Field(description="数据列表")
239
228
  total: int = Field(ge=0, description="总记录数")
240
- page: int = Field(ge=1, description="当前页码")
241
- page_size: int = Field(ge=1, description="每页记录数")
242
- total_pages: int = Field(ge=0, description="总页数")
229
+ offset: int = Field(ge=0, description="当前偏移量")
230
+ limit: int = Field(ge=1, description="每页记录数")
243
231
  has_next: bool = Field(description="是否有下一页")
244
232
  has_prev: bool = Field(description="是否有上一页")
245
233
 
234
+ @property
235
+ def page(self) -> int:
236
+ """当前页码(从 1 开始)。"""
237
+ if self.limit == 0:
238
+ return 1
239
+ return self.offset // self.limit + 1
240
+
241
+ @property
242
+ def size(self) -> int:
243
+ """每页记录数(limit 的别名)。"""
244
+ return self.limit
245
+
246
+ @property
247
+ def total_pages(self) -> int:
248
+ """总页数。"""
249
+ if self.limit == 0:
250
+ return 0
251
+ return (self.total + self.limit - 1) // self.limit
252
+
246
253
  @classmethod
247
254
  def create(
248
255
  cls,
249
256
  items: list[ModelType],
250
257
  total: int,
251
- pagination_params: PaginationParams
252
- ) -> PaginationResult[ModelType]:
258
+ pagination: PaginationParams,
259
+ ) -> "PaginationResult[ModelType]":
253
260
  """创建分页结果。
254
261
 
255
262
  Args:
256
263
  items: 数据列表
257
264
  total: 总记录数
258
- pagination_params: 分页参数
265
+ pagination: 分页参数
259
266
 
260
267
  Returns:
261
268
  PaginationResult[ModelType]: 分页结果
262
269
  """
263
- total_pages = (total + pagination_params.page_size - 1) // pagination_params.page_size
264
- has_next = pagination_params.page < total_pages
265
- has_prev = pagination_params.page > 1
270
+ has_next = pagination.offset + pagination.limit < total
271
+ has_prev = pagination.offset > 0
266
272
 
267
273
  return cls(
268
274
  items=items,
269
275
  total=total,
270
- page=pagination_params.page,
271
- page_size=pagination_params.page_size,
272
- total_pages=total_pages,
276
+ offset=pagination.offset,
277
+ limit=pagination.limit,
273
278
  has_next=has_next,
274
279
  has_prev=has_prev,
275
280
  )
276
281
 
277
282
  def get_next_params(self) -> PaginationParams | None:
278
- """获取下一页的分页参数。
279
-
280
- Returns:
281
- PaginationParams | None: 下一页的分页参数,如果没有下一页则返回None
282
- """
283
+ """获取下一页的分页参数。"""
283
284
  if not self.has_next:
284
285
  return None
285
- return PaginationParams(page=self.page + 1, page_size=self.page_size)
286
+ return PaginationParams(offset=self.offset + self.limit, limit=self.limit)
286
287
 
287
288
  def get_prev_params(self) -> PaginationParams | None:
288
- """获取上一页的分页参数。
289
-
290
- Returns:
291
- PaginationParams | None: 上一页的分页参数,如果没有上一页则返回None
292
- """
289
+ """获取上一页的分页参数。"""
293
290
  if not self.has_prev:
294
291
  return None
295
- return PaginationParams(page=self.page - 1, page_size=self.page_size)
292
+ new_offset = max(0, self.offset - self.limit)
293
+ return PaginationParams(offset=new_offset, limit=self.limit)
296
294
 
297
295
 
298
296
  class CursorPaginationParams(BaseModel):
@@ -224,45 +224,113 @@ class BaseRepository[ModelType: Base](IRepository[ModelType]):
224
224
  result = await self._session.execute(query)
225
225
  return result.scalar_one_or_none()
226
226
 
227
- async def list(self, skip: int = 0, limit: int | None = 100, **filters) -> list[ModelType]:
227
+ async def list(
228
+ self,
229
+ skip: int = 0,
230
+ limit: int | None = None,
231
+ sort: str | SortParams | list[str] | None = None,
232
+ **filters
233
+ ) -> list[ModelType]:
234
+ """获取实体列表。
235
+
236
+ Args:
237
+ skip: 跳过记录数
238
+ limit: 返回记录数限制,None 表示不限制(默认)
239
+ sort: 排序参数,支持多种格式:
240
+ - 字符串: "-created_at" 或 "created_at:desc" 或 "-created_at,name"
241
+ - SortParams 对象
242
+ - 字符串列表: ["-created_at", "name"]
243
+ **filters: 过滤条件
244
+
245
+ Returns:
246
+ list[ModelType]: 实体列表
247
+ """
228
248
  query = self._build_base_query()
229
249
  query = self._apply_filters(query, **filters)
250
+ query = self._apply_sort(query, sort)
230
251
  query = query.offset(skip)
231
252
  if limit is not None:
232
253
  query = query.limit(limit)
233
254
  result = await self._session.execute(query)
234
255
  return list(result.scalars().all())
235
256
 
257
+ def _apply_sort(
258
+ self,
259
+ query: Select,
260
+ sort: str | SortParams | list[str] | None,
261
+ ) -> Select:
262
+ """应用排序参数到查询。
263
+
264
+ Args:
265
+ query: SQLAlchemy 查询对象
266
+ sort: 排序参数,支持:
267
+ - 字符串: "-created_at" 或 "created_at:desc" 或 "-created_at,name"
268
+ - SortParams 对象
269
+ - 字符串列表: ["-created_at", "name"]
270
+
271
+ Returns:
272
+ Select: 应用排序后的查询对象
273
+ """
274
+ if sort is None:
275
+ return query
276
+
277
+ # 统一转换为 SortParams
278
+ sort_params: SortParams
279
+ if isinstance(sort, SortParams):
280
+ sort_params = sort
281
+ elif isinstance(sort, str):
282
+ sort_params = SortParams.from_string(sort)
283
+ elif isinstance(sort, list):
284
+ # 列表格式: ["-created_at", "name"]
285
+ sort_params = SortParams.from_string(",".join(sort))
286
+ else:
287
+ return query
288
+
289
+ order_by_list = []
290
+ for field, direction in sort_params.sorts:
291
+ field_attr = getattr(self._model_class, field, None)
292
+ if field_attr is not None:
293
+ if direction == "desc":
294
+ order_by_list.append(field_attr.desc())
295
+ else:
296
+ order_by_list.append(field_attr)
297
+
298
+ if order_by_list:
299
+ query = query.order_by(*order_by_list)
300
+
301
+ return query
302
+
236
303
  async def paginate(
237
304
  self,
238
- pagination_params: PaginationParams,
239
- sort_params: SortParams | None = None,
305
+ pagination: PaginationParams,
306
+ sort: str | SortParams | list[str] | None = None,
240
307
  **filters
241
308
  ) -> PaginationResult[ModelType]:
242
- query = self._build_base_query()
243
- query = self._apply_filters(query, **filters)
309
+ """分页查询(list 的语法糖)。
244
310
 
245
- if sort_params:
246
- order_by_list = []
247
- for field, direction in sort_params.sorts:
248
- field_attr = getattr(self._model_class, field, None)
249
- if field_attr is not None:
250
- if direction == "desc":
251
- order_by_list.append(field_attr.desc())
252
- else:
253
- order_by_list.append(field_attr)
254
- if order_by_list:
255
- query = query.order_by(*order_by_list)
256
-
257
- query = query.offset(pagination_params.offset).limit(pagination_params.limit)
258
- result = await self._session.execute(query)
259
- items = list(result.scalars().all())
260
- total_count = await self.count(**filters)
311
+ Args:
312
+ pagination: 分页参数
313
+ sort: 排序参数,支持多种格式:
314
+ - 字符串: "-created_at" 或 "created_at:desc" 或 "-created_at,name"
315
+ - SortParams 对象
316
+ - 字符串列表: ["-created_at", "name"]
317
+ **filters: 过滤条件
318
+
319
+ Returns:
320
+ PaginationResult[ModelType]: 分页结果
321
+ """
322
+ items = await self.list(
323
+ skip=pagination.offset,
324
+ limit=pagination.limit,
325
+ sort=sort,
326
+ **filters
327
+ )
328
+ total = await self.count(**filters)
261
329
 
262
330
  return PaginationResult.create(
263
331
  items=items,
264
- total=total_count,
265
- pagination_params=pagination_params,
332
+ total=total,
333
+ pagination=pagination,
266
334
  )
267
335
 
268
336
  async def cursor_paginate(
@@ -374,6 +442,7 @@ class BaseRepository[ModelType: Base](IRepository[ModelType]):
374
442
  async def stream(
375
443
  self,
376
444
  batch_size: int = 1000,
445
+ sort: str | SortParams | list[str] | None = None,
377
446
  **filters
378
447
  ):
379
448
  """流式查询,使用数据库原生 server-side cursor。
@@ -382,17 +451,19 @@ class BaseRepository[ModelType: Base](IRepository[ModelType]):
382
451
 
383
452
  Args:
384
453
  batch_size: 每批次获取的记录数,默认 1000
454
+ sort: 排序参数(同 list)
385
455
  **filters: 过滤条件
386
456
 
387
457
  Yields:
388
458
  ModelType: 模型实例
389
459
 
390
460
  示例:
391
- async for user in repo.stream(batch_size=500, status="active"):
461
+ async for user in repo.stream(batch_size=500, sort="-created_at", status="active"):
392
462
  process(user)
393
463
  """
394
464
  query = self._build_base_query()
395
465
  query = self._apply_filters(query, **filters)
466
+ query = self._apply_sort(query, sort)
396
467
 
397
468
  async with self._session.stream_scalars(
398
469
  query.execution_options(yield_per=batch_size)
@@ -403,23 +474,26 @@ class BaseRepository[ModelType: Base](IRepository[ModelType]):
403
474
  async def stream_batches(
404
475
  self,
405
476
  batch_size: int = 1000,
477
+ sort: str | SortParams | list[str] | None = None,
406
478
  **filters
407
479
  ):
408
480
  """批量流式查询,每次返回一批数据。
409
481
 
410
482
  Args:
411
483
  batch_size: 每批次的记录数,默认 1000
484
+ sort: 排序参数(同 list)
412
485
  **filters: 过滤条件
413
486
 
414
487
  Yields:
415
488
  list[ModelType]: 一批模型实例
416
489
 
417
490
  示例:
418
- async for batch in repo.stream_batches(batch_size=500):
491
+ async for batch in repo.stream_batches(batch_size=500, sort="id"):
419
492
  bulk_process(batch)
420
493
  """
421
494
  query = self._build_base_query()
422
495
  query = self._apply_filters(query, **filters)
496
+ query = self._apply_sort(query, sort)
423
497
 
424
498
  async with self._session.stream_scalars(
425
499
  query.execution_options(yield_per=batch_size)
@@ -437,7 +511,10 @@ class BaseRepository[ModelType: Base](IRepository[ModelType]):
437
511
  return result.scalar_one()
438
512
 
439
513
  async def exists(self, **filters) -> bool:
440
- return await self.count(**filters) > 0
514
+ """检查是否存在匹配的记录(比 count > 0 更高效)。"""
515
+ query = self._apply_filters(self._build_base_query(), **filters).limit(1)
516
+ result = await self._session.execute(query)
517
+ return result.scalar_one_or_none() is not None
441
518
 
442
519
  async def add(self, entity: ModelType) -> ModelType:
443
520
  self._session.add(entity)
@@ -35,22 +35,41 @@ class IRepository[ModelType: Base](ABC):
35
35
  pass
36
36
 
37
37
  @abstractmethod
38
- async def list(self, skip: int = 0, limit: int | None = 100, **filters) -> list[ModelType]:
39
- """获取实体列表。limit=None 时返回全部记录(谨慎使用)。"""
38
+ async def list(
39
+ self,
40
+ skip: int = 0,
41
+ limit: int | None = None,
42
+ sort: str | SortParams | list[str] | None = None,
43
+ **filters
44
+ ) -> list[ModelType]:
45
+ """获取实体列表。
46
+
47
+ Args:
48
+ skip: 跳过记录数
49
+ limit: 返回记录数限制,None 表示不限制(默认)
50
+ sort: 排序参数,支持多种格式:
51
+ - 字符串: "-created_at" 或 "created_at:desc" 或 "-created_at,name"
52
+ - SortParams 对象
53
+ - 字符串列表: ["-created_at", "name"]
54
+ **filters: 过滤条件
55
+
56
+ Returns:
57
+ list[ModelType]: 实体列表
58
+ """
40
59
  pass
41
60
 
42
61
  @abstractmethod
43
62
  async def paginate(
44
63
  self,
45
- pagination_params: PaginationParams,
46
- sort_params: SortParams | None = None,
64
+ pagination: PaginationParams,
65
+ sort: str | SortParams | list[str] | None = None,
47
66
  **filters
48
67
  ) -> PaginationResult[ModelType]:
49
- """分页获取实体列表。
68
+ """分页查询(list 的语法糖)。
50
69
 
51
70
  Args:
52
- pagination_params: 分页参数
53
- sort_params: 排序参数
71
+ pagination: 分页参数
72
+ sort: 排序参数,支持多种格式(同 list)
54
73
  **filters: 过滤条件
55
74
 
56
75
  Returns:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aury-boot
3
- Version: 0.0.33
3
+ Version: 0.0.35
4
4
  Summary: Aury Boot - 基于 FastAPI 生态的企业级 API 开发框架
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: alembic>=1.17.2
@@ -1,5 +1,5 @@
1
1
  aury/boot/__init__.py,sha256=pCno-EInnpIBa1OtxNYF-JWf9j95Cd2h6vmu0xqa_-4,1791
2
- aury/boot/_version.py,sha256=xrs8Qn_OZ3B9ijkPtzIwzsKzRxeJuFan8EOwBgLmnxE,706
2
+ aury/boot/_version.py,sha256=w77E3DIE0tp22UaCyYn9461JfWMpEPhsv91t-4n4Bjw,706
3
3
  aury/boot/application/__init__.py,sha256=I2KqNVdYg2q5nlOXr0TtFGyHmhj4oWdaR6ZB73Mwg7Y,3041
4
4
  aury/boot/application/adapter/__init__.py,sha256=e1bcSb1bxUMfofTwiCuHBZJk5-STkMCWPF2EJXHQ7UU,3976
5
5
  aury/boot/application/adapter/base.py,sha256=Ar_66fiHPDEmV-1DKnqXKwc53p3pozG31bgTJTEUriY,15763
@@ -8,13 +8,13 @@ aury/boot/application/adapter/decorators.py,sha256=yyGu_16bWWUiO36gxCeQWgG0DN19p
8
8
  aury/boot/application/adapter/exceptions.py,sha256=Kzm-ytRxdUnSMIcWCSOHPxo4Jh_A6YbyxlOVIUs-5F4,6183
9
9
  aury/boot/application/adapter/http.py,sha256=4TADsSzdSRU63307dmmo-2U_JpVP12mwTFy66B5Ps-w,10759
10
10
  aury/boot/application/app/__init__.py,sha256=I8FfCKDuDQsGzAK6BevyfdtAwieMUVYu6qgVQzBazpE,830
11
- aury/boot/application/app/base.py,sha256=7n7uFAVmIylr9YZannWKeQSOLSifNosIJUMbL-nmJe4,21897
11
+ aury/boot/application/app/base.py,sha256=kyuNm3wOr8cnrPlKJOLJUBQY2-91q5LZABtoUGkmg7g,21634
12
12
  aury/boot/application/app/components.py,sha256=Ub7NlfxSPXSDcxUajQ5ed42kNmsBSol-UttcBfnx64Y,33473
13
13
  aury/boot/application/app/middlewares.py,sha256=BXe2H14FHzJUVpQM6DZUm-zfZRXSXIi1QIZ4_3izfHw,3306
14
14
  aury/boot/application/app/startup.py,sha256=DHKt3C2G7V5XfFr1SQMl14tNzcuDd9MqUVAxi274HDQ,7873
15
15
  aury/boot/application/config/__init__.py,sha256=Dd-myRSBCM18DXXsi863h0cJG5VFrI10xMRtjnvelGo,1894
16
16
  aury/boot/application/config/multi_instance.py,sha256=RXSp-xP8-bKMDEhq3SeL7T3lS8-vpRlvBEVBuZVjVK4,6475
17
- aury/boot/application/config/settings.py,sha256=MPOjUyPxvWOrw878FOs0gnUPkqSUMyLpQ-MZz9yMwls,37866
17
+ aury/boot/application/config/settings.py,sha256=XGPoA8qFYxmnStmsboCBRHn1OAuniXCWzH7jAtrq4ho,37517
18
18
  aury/boot/application/constants/__init__.py,sha256=DCXs13_VVaQWHqO-qpJoZwRd7HIexiirtw_nu8msTXE,340
19
19
  aury/boot/application/constants/components.py,sha256=I4SlsF2DpSzMiLsi1wVrEmdHn4yV5J2h3ikMQqufPmM,1120
20
20
  aury/boot/application/constants/scheduler.py,sha256=S77FBIvHlyruvlabRWZJ2J1YAs2xWXPQI2yuGdGUDNA,471
@@ -49,8 +49,8 @@ aury/boot/commands/docs.py,sha256=Hz1W-2TW8DzaPxARqEF4UncPhGMI9h97jJ962dlox3U,14
49
49
  aury/boot/commands/generate.py,sha256=WZieSXuofxJOC7NBiVGpBigB9NZ4GMcF2F1ReTNun1I,44420
50
50
  aury/boot/commands/init.py,sha256=W_eCL3wydWaMSLqTpadREDnzC0w-LGgNnj3IBjuQAfA,32348
51
51
  aury/boot/commands/pkg.py,sha256=bw0QPptKscNgQ4I1SfSehTio9Q5KrvxgvkYx4tbZ7Vs,14495
52
- aury/boot/commands/scheduler.py,sha256=BCIGQcGryXpsYNF-mncP6v5kNoz6DZ10DMzMKVDiXxA,3516
53
- aury/boot/commands/worker.py,sha256=qAcPdoKpMBLYoi45X_y2-nobuYKxscJpooEB_0HhM4o,4163
52
+ aury/boot/commands/scheduler.py,sha256=XO3Gq7PqNxXNz5Gw0xNUHa_bEnAKZ9AkzLc062QJ3j8,3669
53
+ aury/boot/commands/worker.py,sha256=OEvfDiiM_pV3Mj73HKhSm1RNqFPuS125iNM0qNCTHFY,4316
54
54
  aury/boot/commands/migrate/__init__.py,sha256=W9OhkX8ILdolySofgdP2oYoJGG9loQd5FeSwkniU3qM,455
55
55
  aury/boot/commands/migrate/app.py,sha256=phCMKW6cuFYW2wr6PSMSCq0K2uUCiYo3UiFd0_UvA_o,1327
56
56
  aury/boot/commands/migrate/commands.py,sha256=892htS_pTtpejLGqRP8bc3xXJPG92WwAejHlY74oI3o,9950
@@ -58,7 +58,7 @@ aury/boot/commands/server/__init__.py,sha256=aP3bPNGn6wT8dHa_OmKw1Dexnxuvf0BhrGA
58
58
  aury/boot/commands/server/app.py,sha256=-A52dLgerab98IM50a-_ptFb0xlMvbdbhYjqoJIIIpU,15795
59
59
  aury/boot/commands/templates/generate/api.py.tpl,sha256=xTbk9uzn5IMtJ-SPMadjmOUNHoM3WoE6g-TIEsGHFUA,3153
60
60
  aury/boot/commands/templates/generate/model.py.tpl,sha256=knFwMyGZ7wMpzH4_bQD_V1hFTvmCb2H04G8p3s2xvyA,312
61
- aury/boot/commands/templates/generate/repository.py.tpl,sha256=xoEg6lPAaLIRDeFy4I0FBsPPVLSy91h6xosAlaCL_mM,590
61
+ aury/boot/commands/templates/generate/repository.py.tpl,sha256=Uj9jNEI9Zn8W061FGFlRaIfAy9IhdassYH6noEjG0z0,662
62
62
  aury/boot/commands/templates/generate/schema.py.tpl,sha256=HIaY5B0UG_S188nQLrZDEJ0q73WPdb7BmCdc0tseZA4,545
63
63
  aury/boot/commands/templates/generate/service.py.tpl,sha256=2hwQ8e4a5d_bIMx_jGDobdmKPMFLBlfQrQVQH4Ym5k4,1842
64
64
  aury/boot/commands/templates/project/AGENTS.md.tpl,sha256=sp5qyzU-SGhgQCobpMW4EXRzpGsEsVdmJvspnKAP4AQ,10059
@@ -68,10 +68,10 @@ aury/boot/commands/templates/project/alert_rules.example.yaml.tpl,sha256=QZH6SC5
68
68
  aury/boot/commands/templates/project/config.py.tpl,sha256=H_B05FypBJxTjb7qIL91zC1C9e37Pk7C9gO0-b3CqNs,1009
69
69
  aury/boot/commands/templates/project/conftest.py.tpl,sha256=chbETK81Hy26cWz6YZ2cFgy7HbnABzYCqeyMzgpa3eI,726
70
70
  aury/boot/commands/templates/project/gitignore.tpl,sha256=OI0nt9u2E9EC-jAMoh3gpqamsWo18uDgyPybgee_snQ,3053
71
- aury/boot/commands/templates/project/main.py.tpl,sha256=Q61ve3o1VkNPv8wcQK7lUosne18JWYeItxoXVNNoYJM,1070
71
+ aury/boot/commands/templates/project/main.py.tpl,sha256=6uiXv8KuGl24qZfzgFI2twB6WYCWHXCGMfwirezF8L4,1217
72
72
  aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl,sha256=eOjtqMeKqZ8OgijrOwcpfpHhrhUvt_CiHPUtRG0dilA,2251
73
73
  aury/boot/commands/templates/project/aury_docs/01-model.md.tpl,sha256=1mQ3hGDxqEZjev4CD5-3dzYRFVonPNcAaStI1UBEUyM,6811
74
- aury/boot/commands/templates/project/aury_docs/02-repository.md.tpl,sha256=JfUVdrIgW7_J6JGCcB-_uP_x-gCtjKiewwGv4Xr44QI,7803
74
+ aury/boot/commands/templates/project/aury_docs/02-repository.md.tpl,sha256=Pn3pT9RoBponTcc4tvepFkVcE8EKxu2F9JaHPJ_mDk8,8345
75
75
  aury/boot/commands/templates/project/aury_docs/03-service.md.tpl,sha256=SgfPAgLVi_RbMjSAe-m49jQOIr6vfUT8VF2V1E-qa3w,15098
76
76
  aury/boot/commands/templates/project/aury_docs/04-schema.md.tpl,sha256=ZwwKhUbLI--PEEmwnuo2fIZrhCEZagBN6fRNDTFCnNk,2891
77
77
  aury/boot/commands/templates/project/aury_docs/05-api.md.tpl,sha256=oPzda3V6ZPDDEW-5MwyzmsMRuu5mXrsRGEq3lj0M-58,2997
@@ -108,11 +108,11 @@ aury/boot/common/__init__.py,sha256=MhNP3c_nwx8CyDkDF6p1f4DcTZ1CZZScg66FWdbdaZI,
108
108
  aury/boot/common/exceptions/__init__.py,sha256=aS3rIXWc5qNNJbfMs_PNmBlFsyNdKUMErziNMd1yoB8,3176
109
109
  aury/boot/common/i18n/__init__.py,sha256=2cy4kteU-1YsAHkuMDTr2c5o4G33fvtYUGKtzEy1Q6c,394
110
110
  aury/boot/common/i18n/translator.py,sha256=_vEDL2SjEI1vwMNHbnJb0xErKUPLm7VmhyOuMBeCqRM,8412
111
- aury/boot/common/logging/__init__.py,sha256=Z6pMdjXZMWXz6w-1ev0h6QN3G3c8Cz7EpOqZh42kgQ0,1612
111
+ aury/boot/common/logging/__init__.py,sha256=SNuqbEKaraqYwB8qM6mQUl55lXJNPb1tLujPexnogi4,1528
112
112
  aury/boot/common/logging/context.py,sha256=ndml3rUokEIt5-845E5aW8jI8b4N93ZtukyqsjqzuNE,2566
113
113
  aury/boot/common/logging/decorators.py,sha256=UaGMhRJdARNJ2VgCuRwaNX0DD5wIc1gAl6NDj7u8K2c,3354
114
114
  aury/boot/common/logging/format.py,sha256=ZEqLagTdyGadywTamybcEh1fAZng3Wfx7DC952TFU30,9782
115
- aury/boot/common/logging/setup.py,sha256=ZPxOHJD8Fw4KxKPgsf8ZHQ2mWuXxKh4EJtXXvGY7Hgo,9422
115
+ aury/boot/common/logging/setup.py,sha256=gPzappMVB372rlEIZvWR8QMOhyv0S2r70WB7LaonRNY,9619
116
116
  aury/boot/contrib/__init__.py,sha256=fyk_St9VufIx64hsobv9EsOYzb_T5FbJHxjqtPds4g8,198
117
117
  aury/boot/contrib/admin_console/__init__.py,sha256=HEesLFrtYtBFWTDrh5H3mR-4V4LRg5N4a2a1C4-Whgs,445
118
118
  aury/boot/contrib/admin_console/auth.py,sha256=_goyjZ8Clssvmy8g84svenGfBqCe9OC5pIvCjIzt42g,4706
@@ -125,11 +125,11 @@ aury/boot/domain/models/__init__.py,sha256=f-atwliNjWZ3nfO3JJIi9RZae6umtQCg1ddST
125
125
  aury/boot/domain/models/base.py,sha256=hZHadZaOyTYMOVEteXudQJBqlLnE_HPyXV5rRvrMXJ0,2051
126
126
  aury/boot/domain/models/mixins.py,sha256=7s4m4fzt0vWX71aTHgsoagjxSZZZ4_xSea_m0D84P0Q,5309
127
127
  aury/boot/domain/models/models.py,sha256=hNze58wPZkZ8QG2_pyszDsyKNjz2UgiRDzmneiCWLQs,2728
128
- aury/boot/domain/pagination/__init__.py,sha256=HSU_NyLP-ij7ZDUi-ARSSvNkvhW1_wON2Zvu2QlF6HM,11890
128
+ aury/boot/domain/pagination/__init__.py,sha256=TfGO_yY8sBEy6-wfKAjieDDURWWglU8bzNPjjT11ST4,11987
129
129
  aury/boot/domain/repository/__init__.py,sha256=dnmN8xFu1ASbLnzL6vx8gMoch7xBGxkJkxs9G1iGLGg,490
130
- aury/boot/domain/repository/impl.py,sha256=BeWe6pMBP_OvxX91FmckxAZlM2qejol06If_UBy8FtU,22016
130
+ aury/boot/domain/repository/impl.py,sha256=Z0WSz__UDM5nmvI27YSw_atbAfi2KsnNiD_z8i6c9BA,24586
131
131
  aury/boot/domain/repository/interceptors.py,sha256=SCTjRmBYwevAMlJ8U1uw-_McsDetNNQ7q0Da5lmfj_E,1238
132
- aury/boot/domain/repository/interface.py,sha256=CmkiqVhhHPx_xcpuBCz11Vr26-govwYBxFsQ8myEVyw,2904
132
+ aury/boot/domain/repository/interface.py,sha256=CQ3Wm3IREQ4SpmVvIgWhaIfsxVydcOZ_7OEG8NoG-SI,3472
133
133
  aury/boot/domain/repository/query_builder.py,sha256=pFErMzsBql-T6gBX0S4FxIheCkNaGjpSewzcJ2DxrUU,10890
134
134
  aury/boot/domain/service/__init__.py,sha256=ZRotaBlqJXn7ebPTQjjoHtorpQREk8AgTD69UCcRd1k,118
135
135
  aury/boot/domain/service/base.py,sha256=6sN0nf8r5yUZsE6AcZOiOXFCqzb61oCxTfrWlqjIo9I,2035
@@ -209,7 +209,7 @@ aury/boot/testing/client.py,sha256=KOg1EemuIVsBG68G5y0DjSxZGcIQVdWQ4ASaHE3o1R0,4
209
209
  aury/boot/testing/factory.py,sha256=8GvwX9qIDu0L65gzJMlrWB0xbmJ-7zPHuwk3eECULcg,5185
210
210
  aury/boot/toolkit/__init__.py,sha256=AcyVb9fDf3CaEmJPNkWC4iGv32qCPyk4BuFKSuNiJRQ,334
211
211
  aury/boot/toolkit/http/__init__.py,sha256=zIPmpIZ9Qbqe25VmEr7jixoY2fkRbLm7NkCB9vKpg6I,11039
212
- aury_boot-0.0.33.dist-info/METADATA,sha256=pJ1jUTF2kmZXGKLj-gC0fF2i5Jj1Uq_tkRUjdqJMu-8,8694
213
- aury_boot-0.0.33.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
214
- aury_boot-0.0.33.dist-info/entry_points.txt,sha256=f9KXEkDIGc0BGkgBvsNx_HMz9VhDjNxu26q00jUpDwQ,49
215
- aury_boot-0.0.33.dist-info/RECORD,,
212
+ aury_boot-0.0.35.dist-info/METADATA,sha256=fyQTvIB-zPVWMQblf2xQC-bPoTyALLCe5Snkc9WG1fA,8694
213
+ aury_boot-0.0.35.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
214
+ aury_boot-0.0.35.dist-info/entry_points.txt,sha256=f9KXEkDIGc0BGkgBvsNx_HMz9VhDjNxu26q00jUpDwQ,49
215
+ aury_boot-0.0.35.dist-info/RECORD,,