aury-boot 0.0.19__py3-none-any.whl → 0.0.21__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.19'
32
- __version_tuple__ = version_tuple = (0, 0, 19)
31
+ __version__ = version = '0.0.21'
32
+ __version_tuple__ = version_tuple = (0, 0, 21)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -11,8 +11,10 @@ from contextlib import asynccontextmanager
11
11
  import sys
12
12
  from typing import Any, ClassVar
13
13
 
14
- from fastapi import FastAPI, Request, status
14
+ from fastapi import FastAPI, HTTPException, Request, status
15
+ from fastapi.exceptions import RequestValidationError
15
16
  from fastapi.responses import JSONResponse
17
+ from starlette.exceptions import HTTPException as StarletteHTTPException
16
18
  from starlette.middleware import Middleware as StarletteMiddleware
17
19
 
18
20
  from aury.boot.application.config import BaseConfig
@@ -279,8 +281,11 @@ class FoundationApp(FastAPI):
279
281
  **kwargs,
280
282
  )
281
283
 
282
- # 异常处理
283
- self.add_exception_handler(Exception, global_exception_handler)
284
+ # 异常处理:显式注册以覆盖 FastAPI/Starlette 默认处理器,确保统一响应格式
285
+ self.add_exception_handler(RequestValidationError, global_exception_handler) # 422
286
+ self.add_exception_handler(HTTPException, global_exception_handler) # 4xx/5xx
287
+ self.add_exception_handler(StarletteHTTPException, global_exception_handler) # Starlette 异常
288
+ self.add_exception_handler(Exception, global_exception_handler) # 其他未处理异常
284
289
 
285
290
  # 设置路由
286
291
  self.setup_routes()
@@ -507,10 +507,9 @@ class MessageQueueSettings(BaseModel):
507
507
  - Task: 基于 Dramatiq,用于异步任务处理(API + Worker 模式)
508
508
  - MQ: 通用消息队列,用于服务间通信、事件驱动架构
509
509
 
510
- 支持的后端(通过 Kombu):
510
+ 支持的后端:
511
511
  - Redis: redis://localhost:6379/0
512
512
  - RabbitMQ: amqp://guest:guest@localhost:5672//
513
- - Amazon SQS: sqs://
514
513
  """
515
514
 
516
515
  enabled: bool = Field(
@@ -174,6 +174,7 @@ class ValidationErrorHandler(ErrorHandler):
174
174
  """验证异常处理器。
175
175
 
176
176
  处理 Pydantic ValidationError 和 FastAPI RequestValidationError。
177
+ 返回 422 Unprocessable Entity(符合 FastAPI 规范)。
177
178
  """
178
179
 
179
180
  def can_handle(self, exception: Exception) -> bool:
@@ -182,24 +183,38 @@ class ValidationErrorHandler(ErrorHandler):
182
183
 
183
184
  async def handle(self, exception: Exception, request: Request) -> JSONResponse:
184
185
  """处理验证异常。"""
185
- logger.warning(f"数据验证失败: {exception}")
186
-
187
186
  errors = []
188
187
  for error in exception.errors():
188
+ # 构建友好的字段路径:跳过 body 前缀
189
+ loc = error.get("loc", ())
190
+ # FastAPI 会在 loc 前加 'body'/'query'/'path' 等,保留第一个作为来源
191
+ source = str(loc[0]) if loc else ""
192
+ field_path = ".".join(str(part) for part in loc[1:]) if len(loc) > 1 else str(loc[0]) if loc else ""
193
+
189
194
  errors.append({
190
- "field": ".".join(str(loc) for loc in error["loc"]),
191
- "message": error["msg"],
192
- "type": error["type"],
195
+ "field": field_path,
196
+ "source": source, # body / query / path / header
197
+ "message": error.get("msg", ""),
198
+ "type": error.get("type", ""),
199
+ "input": error.get("input"), # 实际输入值(便于调试)
193
200
  })
194
201
 
202
+ # 详细日志:方便开发调试
203
+ error_summary = "; ".join(
204
+ f"{e['source']}.{e['field']}({e['type']}): {e['message']}" for e in errors
205
+ )
206
+ logger.warning(
207
+ f"参数校验失败 [{request.method} {request.url.path}]: {error_summary}"
208
+ )
209
+
195
210
  response = ResponseBuilder.fail(
196
- message="数据验证失败",
197
- code=400,
211
+ message="参数校验失败",
212
+ code=422,
198
213
  errors=errors,
199
214
  )
200
215
 
201
216
  return JSONResponse(
202
- status_code=status.HTTP_400_BAD_REQUEST,
217
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
203
218
  content=response.model_dump(mode="json"),
204
219
  )
205
220
 
aury/boot/commands/pkg.py CHANGED
@@ -97,16 +97,16 @@ MODULES: dict[str, ModuleInfo] = {
97
97
  "tasks": ModuleInfo(
98
98
  name="tasks",
99
99
  desc="Dramatiq 任务队列",
100
- usage="TaskManager 异步任务时需要",
100
+ usage="TaskManager 异步任务时需要(默认使用 Redis Broker)",
101
101
  category=Category.TASK,
102
- deps=["dramatiq", "kombu", "dramatiq-kombu-broker"],
102
+ deps=["dramatiq", "redis"],
103
103
  ),
104
104
  "rabbitmq": ModuleInfo(
105
105
  name="rabbitmq",
106
106
  desc="RabbitMQ 消息队列后端",
107
107
  usage="TaskManager/EventBus 使用 RabbitMQ 时需要(需配合 tasks)",
108
108
  category=Category.TASK,
109
- deps=["amqp"],
109
+ deps=["pika"],
110
110
  ),
111
111
  # 调度器
112
112
  "scheduler": ModuleInfo(
@@ -1,6 +1,6 @@
1
1
  # 定时任务(Scheduler)
2
2
 
3
- 基于 APScheduler,完全透传原生 API。
3
+ 基于 APScheduler,支持两种触发器语法:字符串模式和原生对象模式。
4
4
 
5
5
  ## 基本用法
6
6
 
@@ -9,31 +9,38 @@
9
9
  ```python
10
10
  """定时任务模块。"""
11
11
 
12
- from apscheduler.triggers.cron import CronTrigger
13
- from apscheduler.triggers.interval import IntervalTrigger
14
-
15
12
  from aury.boot.common.logging import logger
16
13
  from aury.boot.infrastructure.scheduler import SchedulerManager
17
14
 
18
15
  scheduler = SchedulerManager.get_instance()
19
16
 
20
17
 
21
- @scheduler.scheduled_job(IntervalTrigger(seconds=60))
18
+ # === 字符串模式(推荐,简洁)===
19
+ @scheduler.scheduled_job("interval", seconds=60)
22
20
  async def every_minute():
23
21
  """每 60 秒执行。"""
24
22
  logger.info("定时任务执行中...")
25
23
 
26
24
 
27
- @scheduler.scheduled_job(CronTrigger(hour=0, minute=0))
25
+ @scheduler.scheduled_job("cron", hour=0, minute=0)
28
26
  async def daily_task():
29
27
  """每天凌晨执行。"""
30
28
  logger.info("每日任务执行中...")
31
29
 
32
30
 
33
- @scheduler.scheduled_job(CronTrigger(day_of_week="mon", hour=9))
31
+ @scheduler.scheduled_job("cron", day_of_week="mon", hour=9)
34
32
  async def weekly_report():
35
33
  """每周一 9 点执行。"""
36
34
  logger.info("周报任务执行中...")
35
+
36
+
37
+ # === 原生对象模式(完整功能)===
38
+ from apscheduler.triggers.cron import CronTrigger
39
+
40
+ @scheduler.scheduled_job(CronTrigger.from_crontab("0 2 * * *"))
41
+ async def crontab_task():
42
+ """每天凌晨 2 点执行(使用 crontab 表达式)。"""
43
+ logger.info("每日任务执行中...")
37
44
  ```
38
45
 
39
46
  启用方式:配置 `SCHEDULER__ENABLED=true`,框架自动加载 `{package_name}/schedules/` 模块。
@@ -50,40 +57,57 @@ SCHEDULER__MISFIRE_GRACE_TIME=60 # 错过容忍时间(秒)
50
57
  SCHEDULER__JOBSTORE_URL=redis://localhost:6379/0 # 分布式存储(可选)
51
58
  ```
52
59
 
53
- ## 触发器类型
60
+ ## 触发器语法
54
61
 
55
- ### CronTrigger - 定时触发
62
+ 支持两种语法:
56
63
 
57
- ```python
58
- from apscheduler.triggers.cron import CronTrigger
64
+ | 语法 | 适用场景 | 示例 |
65
+ |------|---------|------|
66
+ | 字符串模式 | 日常使用,简洁 | `"cron", hour="*", minute=0` |
67
+ | 原生对象 | crontab 表达式、复杂配置 | `CronTrigger.from_crontab("0 * * * *")` |
59
68
 
69
+ ### cron - 定时触发
70
+
71
+ ```python
72
+ # === 字符串模式 ===
60
73
  # 每天凌晨 2:30
61
- CronTrigger(hour=2, minute=30)
74
+ @scheduler.scheduled_job("cron", hour=2, minute=30)
62
75
 
63
76
  # 每小时整点
64
- CronTrigger(hour="*", minute=0)
77
+ @scheduler.scheduled_job("cron", hour="*", minute=0)
65
78
 
66
79
  # 工作日 9:00
67
- CronTrigger(day_of_week="mon-fri", hour=9)
80
+ @scheduler.scheduled_job("cron", day_of_week="mon-fri", hour=9)
68
81
 
69
82
  # 每月 1 号
70
- CronTrigger(day=1, hour=0)
83
+ @scheduler.scheduled_job("cron", day=1, hour=0)
84
+
85
+ # === 原生对象模式 ===
86
+ from apscheduler.triggers.cron import CronTrigger
71
87
 
72
88
  # 使用 crontab 表达式
73
- CronTrigger.from_crontab("0 2 * * *") # 每天 2:00
89
+ @scheduler.scheduled_job(CronTrigger.from_crontab("0 2 * * *")) # 每天 2:00
74
90
  ```
75
91
 
76
- ### IntervalTrigger - 间隔触发
92
+ **cron 参数**:`year`, `month`, `day`, `week`, `day_of_week`, `hour`, `minute`, `second`, `start_date`, `end_date`, `timezone`, `jitter`
93
+
94
+ ### interval - 间隔触发
77
95
 
78
96
  ```python
97
+ # === 字符串模式 ===
98
+ @scheduler.scheduled_job("interval", seconds=30) # 每 30 秒
99
+ @scheduler.scheduled_job("interval", minutes=5) # 每 5 分钟
100
+ @scheduler.scheduled_job("interval", hours=1) # 每小时
101
+ @scheduler.scheduled_job("interval", days=1) # 每天
102
+
103
+ # === 原生对象模式 ===
79
104
  from apscheduler.triggers.interval import IntervalTrigger
80
105
 
81
- IntervalTrigger(seconds=30) # 30
82
- IntervalTrigger(minutes=5) # 每 5 分钟
83
- IntervalTrigger(hours=1) # 每小时
84
- IntervalTrigger(days=1) # 每天
106
+ @scheduler.scheduled_job(IntervalTrigger(hours=1, jitter=60)) # 每小时,随机抖动 60
85
107
  ```
86
108
 
109
+ **interval 参数**:`weeks`, `days`, `hours`, `minutes`, `seconds`, `start_date`, `end_date`, `timezone`, `jitter`
110
+
87
111
  ### DateTrigger - 一次性触发
88
112
 
89
113
  ```python
@@ -91,9 +115,31 @@ from apscheduler.triggers.date import DateTrigger
91
115
  from datetime import datetime, timedelta
92
116
 
93
117
  # 10 秒后执行
94
- DateTrigger(run_date=datetime.now() + timedelta(seconds=10))
118
+ scheduler.add_job(my_task, DateTrigger(run_date=datetime.now() + timedelta(seconds=10)))
95
119
  ```
96
120
 
121
+ ## 条件加载
122
+
123
+ 通过 `enabled` 参数控制任务是否注册:
124
+
125
+ ```python
126
+ from config import settings
127
+
128
+ # 根据配置开关决定是否启用
129
+ @scheduler.scheduled_job("cron", hour=2, enabled=settings.ENABLE_REPORT)
130
+ async def daily_report():
131
+ """仅在 ENABLE_REPORT=true 时注册。"""
132
+ ...
133
+
134
+ # 区分环境
135
+ @scheduler.scheduled_job("interval", minutes=5, enabled=settings.ENV == "production")
136
+ async def prod_only_task():
137
+ """仅生产环境执行。"""
138
+ ...
139
+ ```
140
+
141
+ `enabled=False` 时任务不会注册,日志记录:`任务已禁用,跳过注册: xxx`
142
+
97
143
  ## 多实例支持
98
144
 
99
145
  支持不同业务线使用独立的调度器实例:
@@ -136,7 +182,11 @@ scheduler = SchedulerManager.get_instance(
136
182
  ## 任务管理
137
183
 
138
184
  ```python
139
- # 添加任务
185
+ # 添加任务(字符串模式)
186
+ scheduler.add_job(my_task, "cron", hour=2, minute=0, id="my_task")
187
+
188
+ # 添加任务(原生对象模式)
189
+ from apscheduler.triggers.cron import CronTrigger
140
190
  scheduler.add_job(my_task, CronTrigger(hour=2), id="my_task")
141
191
 
142
192
  # 获取任务
@@ -150,7 +200,10 @@ scheduler.resume_job("my_task")
150
200
  # 移除
151
201
  scheduler.remove_job("my_task")
152
202
 
153
- # 重新调度
203
+ # 重新调度(字符串模式)
204
+ scheduler.reschedule_job("my_task", "cron", hour=3)
205
+
206
+ # 重新调度(原生对象模式)
154
207
  scheduler.reschedule_job("my_task", CronTrigger(hour=3))
155
208
  ```
156
209
 
@@ -4,18 +4,35 @@
4
4
 
5
5
  框架会自动发现并加载本模块,无需在 main.py 中手动导入。
6
6
  也可通过 SCHEDULER_SCHEDULE_MODULES 环境变量指定自定义模块。
7
+
8
+ 触发器支持两种语法:
9
+ - 字符串模式(推荐):@scheduler.scheduled_job("cron", hour="*", minute=0)
10
+ - 原生对象模式:@scheduler.scheduled_job(CronTrigger(hour="*"))
7
11
  """
8
12
 
9
- # from apscheduler.triggers.cron import CronTrigger
10
- # from apscheduler.triggers.interval import IntervalTrigger
11
- #
12
13
  # from aury.boot.common.logging import logger
13
14
  # from aury.boot.infrastructure.scheduler import SchedulerManager
14
15
  #
15
16
  # scheduler = SchedulerManager.get_instance()
16
17
  #
17
18
  #
18
- # @scheduler.scheduled_job(IntervalTrigger(seconds=60))
19
+ # # === 字符串模式(推荐,简洁)===
20
+ # @scheduler.scheduled_job("interval", seconds=60)
19
21
  # async def example_job():
20
22
  # """示例定时任务,每 60 秒执行一次。"""
21
23
  # logger.info("定时任务执行中...")
24
+ #
25
+ #
26
+ # @scheduler.scheduled_job("cron", hour="*", minute=0)
27
+ # async def hourly_job():
28
+ # """每小时整点执行。"""
29
+ # logger.info("整点任务执行")
30
+ #
31
+ #
32
+ # # === 原生对象模式(完整功能)===
33
+ # # from apscheduler.triggers.cron import CronTrigger
34
+ # #
35
+ # # @scheduler.scheduled_job(CronTrigger.from_crontab("0 2 * * *"))
36
+ # # async def daily_job():
37
+ # # """每天凌晨 2 点执行(使用 crontab 表达式)。"""
38
+ # # logger.info("每日任务执行")
@@ -7,6 +7,7 @@
7
7
  - 自动设置日志上下文(调度器任务日志自动写入 scheduler_xxx.log)
8
8
  - 支持多个命名实例
9
9
  - 支持 APScheduler 完整配置(jobstores、executors、job_defaults、timezone)
10
+ - 支持两种触发器语法:字符串模式和原生对象模式
10
11
  """
11
12
 
12
13
  from __future__ import annotations
@@ -14,20 +15,32 @@ from __future__ import annotations
14
15
  import asyncio
15
16
  from collections.abc import Callable
16
17
  from functools import wraps
17
- from typing import TYPE_CHECKING, Any
18
+ from typing import TYPE_CHECKING, Any, Literal
18
19
 
19
20
  from aury.boot.common.logging import logger, set_service_context
20
21
 
21
22
  # 延迟导入 apscheduler(可选依赖)
22
23
  try:
23
24
  from apscheduler.schedulers.asyncio import AsyncIOScheduler
25
+ from apscheduler.triggers.base import BaseTrigger
26
+ from apscheduler.triggers.cron import CronTrigger
27
+ from apscheduler.triggers.interval import IntervalTrigger
24
28
  _APSCHEDULER_AVAILABLE = True
25
29
  except ImportError:
26
30
  _APSCHEDULER_AVAILABLE = False
27
31
  if TYPE_CHECKING:
28
32
  from apscheduler.schedulers.asyncio import AsyncIOScheduler
33
+ from apscheduler.triggers.base import BaseTrigger
34
+ from apscheduler.triggers.cron import CronTrigger
35
+ from apscheduler.triggers.interval import IntervalTrigger
29
36
  else:
30
37
  AsyncIOScheduler = None
38
+ BaseTrigger = None
39
+ CronTrigger = None
40
+ IntervalTrigger = None
41
+
42
+ # 触发器类型别名
43
+ TriggerType = Literal["cron", "interval"]
31
44
 
32
45
 
33
46
  class SchedulerManager:
@@ -39,6 +52,16 @@ class SchedulerManager:
39
52
  - job_defaults: 任务默认配置(coalesce/max_instances/misfire_grace_time)
40
53
  - timezone: 时区
41
54
 
55
+ 触发器支持两种语法:
56
+
57
+ 1. 字符串模式(简洁):
58
+ @scheduler.scheduled_job("cron", hour="*", minute=0)
59
+ @scheduler.scheduled_job("interval", seconds=60)
60
+
61
+ 2. 原生对象模式(完整功能):
62
+ @scheduler.scheduled_job(CronTrigger(hour="*"))
63
+ @scheduler.scheduled_job(IntervalTrigger(seconds=60))
64
+
42
65
  使用示例:
43
66
  from apscheduler.triggers.cron import CronTrigger
44
67
  from apscheduler.triggers.interval import IntervalTrigger
@@ -57,11 +80,16 @@ class SchedulerManager:
57
80
  timezone="Asia/Shanghai",
58
81
  )
59
82
 
60
- # 注册任务
61
- @scheduler.scheduled_job(IntervalTrigger(seconds=60))
83
+ # 字符串模式注册任务
84
+ @scheduler.scheduled_job("interval", seconds=60)
62
85
  async def my_task():
63
86
  ...
64
87
 
88
+ # 原生对象模式
89
+ @scheduler.scheduled_job(CronTrigger.from_crontab("0 * * * *"))
90
+ async def hourly_task():
91
+ ...
92
+
65
93
  # 启动调度器
66
94
  scheduler.start()
67
95
  """
@@ -200,46 +228,88 @@ class SchedulerManager:
200
228
  raise RuntimeError("调度器未初始化,请先调用 initialize()")
201
229
  return self._scheduler
202
230
 
231
+ def _build_trigger(self, trigger: TriggerType | BaseTrigger, **trigger_kwargs: Any) -> BaseTrigger:
232
+ """构建触发器对象。
233
+
234
+ Args:
235
+ trigger: 触发器类型字符串("cron"/"interval")或原生触发器对象
236
+ **trigger_kwargs: 触发器参数(仅字符串模式时有效)
237
+
238
+ Returns:
239
+ APScheduler 触发器对象
240
+ """
241
+ # 如果已经是触发器对象,直接返回
242
+ if isinstance(trigger, BaseTrigger):
243
+ return trigger
244
+
245
+ # 字符串模式,构建触发器
246
+ if trigger == "cron":
247
+ return CronTrigger(**trigger_kwargs)
248
+ elif trigger == "interval":
249
+ return IntervalTrigger(**trigger_kwargs)
250
+ else:
251
+ raise ValueError(f"不支持的触发器类型: {trigger},支持 'cron' 或 'interval'")
252
+
203
253
  def add_job(
204
254
  self,
205
255
  func: Callable,
206
- trigger: Any,
256
+ trigger: TriggerType | BaseTrigger,
207
257
  *,
208
258
  id: str | None = None,
209
259
  **kwargs: Any,
210
260
  ) -> None:
211
261
  """添加任务。
212
262
 
213
- 直接使用 APScheduler 原生 trigger 对象,不做任何封装。
263
+ 支持两种触发器语法:
264
+
265
+ 1. 字符串模式:trigger 为 "cron" 或 "interval",触发器参数通过 kwargs 传递
266
+ 2. 原生对象模式:trigger 为 APScheduler 触发器对象
214
267
 
215
268
  Args:
216
269
  func: 任务函数
217
- trigger: APScheduler 触发器对象,如:
218
- - IntervalTrigger(seconds=60)
219
- - CronTrigger(hour="*")
220
- - CronTrigger.from_crontab("0 * * * *")
270
+ trigger: 触发器类型或触发器对象
271
+ - 字符串: "cron" 或 "interval"
272
+ - 对象: CronTrigger(...) 或 IntervalTrigger(...)
221
273
  id: 任务ID(可选,默认使用函数完整路径)
222
- **kwargs: 其他 APScheduler add_job 参数
274
+ **kwargs: 触发器参数(字符串模式)或其他 APScheduler add_job 参数(对象模式)
223
275
 
224
276
  示例:
225
- from apscheduler.triggers.cron import CronTrigger
226
- from apscheduler.triggers.interval import IntervalTrigger
227
-
228
- # 每小时执行
229
- scheduler.add_job(my_task, CronTrigger(hour="*"))
277
+ # === 字符串模式 ===
278
+ # 每小时整点执行
279
+ scheduler.add_job(my_task, "cron", hour="*", minute=0)
230
280
 
231
281
  # 每 30 分钟执行
232
- scheduler.add_job(my_task, IntervalTrigger(minutes=30))
282
+ scheduler.add_job(my_task, "interval", minutes=30)
233
283
 
234
284
  # 每天凌晨 2 点执行
235
- scheduler.add_job(my_task, CronTrigger(hour=2, minute=0))
285
+ scheduler.add_job(my_task, "cron", hour=2, minute=0)
286
+
287
+ # 每周一 9:00 执行
288
+ scheduler.add_job(my_task, "cron", day_of_week="mon", hour=9, minute=0)
289
+
290
+ # === 原生对象模式 ===
291
+ from apscheduler.triggers.cron import CronTrigger
292
+ from apscheduler.triggers.interval import IntervalTrigger
236
293
 
237
294
  # 使用 crontab 表达式
238
295
  scheduler.add_job(my_task, CronTrigger.from_crontab("0 2 * * *"))
296
+
297
+ # 原生触发器对象
298
+ scheduler.add_job(my_task, IntervalTrigger(seconds=60))
239
299
  """
240
300
  if not self._initialized:
241
301
  raise RuntimeError("调度器未初始化")
242
302
 
303
+ # 分离触发器参数和其他 add_job 参数
304
+ if isinstance(trigger, str):
305
+ # 字符串模式:需要从 kwargs 中分离触发器参数
306
+ trigger_params, job_params = self._separate_trigger_params(trigger, kwargs)
307
+ trigger_obj = self._build_trigger(trigger, **trigger_params)
308
+ else:
309
+ # 对象模式:kwargs 全部是 add_job 参数
310
+ trigger_obj = trigger
311
+ job_params = kwargs
312
+
243
313
  # 包装任务函数,自动设置日志上下文
244
314
  wrapped_func = self._wrap_with_context(func)
245
315
 
@@ -247,12 +317,52 @@ class SchedulerManager:
247
317
  job_id = id or f"{func.__module__}.{func.__name__}"
248
318
  self._scheduler.add_job(
249
319
  func=wrapped_func,
250
- trigger=trigger,
320
+ trigger=trigger_obj,
251
321
  id=job_id,
252
- **kwargs,
322
+ **job_params,
253
323
  )
254
324
 
255
- logger.info(f"任务已注册: {job_id} | 触发器: {type(trigger).__name__}")
325
+ logger.info(f"任务已注册: {job_id} | 触发器: {type(trigger_obj).__name__}")
326
+
327
+ def _separate_trigger_params(
328
+ self,
329
+ trigger_type: str,
330
+ kwargs: dict[str, Any]
331
+ ) -> tuple[dict[str, Any], dict[str, Any]]:
332
+ """分离触发器参数和 add_job 参数。
333
+
334
+ Args:
335
+ trigger_type: 触发器类型
336
+ kwargs: 混合参数
337
+
338
+ Returns:
339
+ (trigger_params, job_params) 元组
340
+ """
341
+ # CronTrigger 支持的参数
342
+ cron_params = {
343
+ "year", "month", "day", "week", "day_of_week",
344
+ "hour", "minute", "second", "start_date", "end_date",
345
+ "timezone", "jitter"
346
+ }
347
+ # IntervalTrigger 支持的参数
348
+ interval_params = {
349
+ "weeks", "days", "hours", "minutes", "seconds",
350
+ "start_date", "end_date", "timezone", "jitter"
351
+ }
352
+
353
+ # 根据触发器类型选择参数集
354
+ trigger_param_names = cron_params if trigger_type == "cron" else interval_params
355
+
356
+ trigger_params: dict[str, Any] = {}
357
+ job_params: dict[str, Any] = {}
358
+
359
+ for key, value in kwargs.items():
360
+ if key in trigger_param_names:
361
+ trigger_params[key] = value
362
+ else:
363
+ job_params[key] = value
364
+
365
+ return trigger_params, job_params
256
366
 
257
367
  def _wrap_with_context(self, func: Callable) -> Callable:
258
368
  """包装任务函数,自动设置 scheduler 日志上下文。"""
@@ -339,19 +449,35 @@ class SchedulerManager:
339
449
  def reschedule_job(
340
450
  self,
341
451
  job_id: str,
342
- trigger: Any,
452
+ trigger: TriggerType | BaseTrigger,
453
+ **trigger_kwargs: Any,
343
454
  ) -> None:
344
455
  """重新调度任务。
345
456
 
457
+ 支持两种触发器语法:
458
+
459
+ 1. 字符串模式:trigger 为 "cron" 或 "interval",触发器参数通过 kwargs 传递
460
+ 2. 原生对象模式:trigger 为 APScheduler 触发器对象
461
+
346
462
  Args:
347
463
  job_id: 任务ID
348
- trigger: APScheduler 触发器对象
464
+ trigger: 触发器类型或触发器对象
465
+ **trigger_kwargs: 触发器参数(仅字符串模式时有效)
466
+
467
+ 示例:
468
+ # 字符串模式
469
+ scheduler.reschedule_job("my_job", "cron", hour="*/2")
470
+ scheduler.reschedule_job("my_job", "interval", minutes=15)
471
+
472
+ # 原生对象模式
473
+ scheduler.reschedule_job("my_job", CronTrigger(hour="*/2"))
349
474
  """
350
475
  if not self._scheduler:
351
476
  raise RuntimeError("调度器未初始化")
352
477
 
353
- self._scheduler.reschedule_job(job_id, trigger=trigger)
354
- logger.info(f"任务已重新调度: {job_id} | 触发器: {type(trigger).__name__}")
478
+ trigger_obj = self._build_trigger(trigger, **trigger_kwargs)
479
+ self._scheduler.reschedule_job(job_id, trigger=trigger_obj)
480
+ logger.info(f"任务已重新调度: {job_id} | 触发器: {type(trigger_obj).__name__}")
355
481
 
356
482
  def pause_job(self, job_id: str) -> None:
357
483
  """暂停单个任务。
@@ -392,7 +518,16 @@ class SchedulerManager:
392
518
 
393
519
  self._scheduler.start()
394
520
  self._started = True
395
- logger.info("调度器已启动")
521
+
522
+ # 打印已加载的任务列表
523
+ jobs = self._scheduler.get_jobs()
524
+ if jobs:
525
+ logger.info(f"调度器已启动,共加载 {len(jobs)} 个定时任务:")
526
+ for job in jobs:
527
+ next_run = job.next_run_time.strftime("%Y-%m-%d %H:%M:%S") if job.next_run_time else "已暂停"
528
+ logger.info(f" - {job.id} | 触发器: {type(job.trigger).__name__} | 下次执行: {next_run}")
529
+ else:
530
+ logger.info("调度器已启动,无定时任务")
396
531
 
397
532
  def shutdown(self) -> None:
398
533
  """关闭调度器。"""
@@ -414,40 +549,66 @@ class SchedulerManager:
414
549
 
415
550
  def scheduled_job(
416
551
  self,
417
- trigger: Any,
552
+ trigger: TriggerType | BaseTrigger,
418
553
  *,
419
554
  id: str | None = None,
555
+ enabled: bool = True,
420
556
  **kwargs: Any,
421
557
  ) -> Callable[[Callable], Callable]:
422
558
  """任务注册装饰器。
423
559
 
560
+ 支持两种触发器语法:
561
+
562
+ 1. 字符串模式(推荐,简洁):
563
+ @scheduler.scheduled_job("cron", hour="*", minute=0)
564
+ @scheduler.scheduled_job("interval", seconds=60)
565
+
566
+ 2. 原生对象模式(完整功能):
567
+ @scheduler.scheduled_job(CronTrigger(hour="*"))
568
+ @scheduler.scheduled_job(IntervalTrigger(seconds=60))
569
+
424
570
  使用示例:
425
- from apscheduler.triggers.cron import CronTrigger
426
- from apscheduler.triggers.interval import IntervalTrigger
427
-
428
571
  scheduler = SchedulerManager.get_instance()
429
572
 
430
- @scheduler.scheduled_job(IntervalTrigger(seconds=60))
573
+ # === 字符串模式 ===
574
+ @scheduler.scheduled_job("interval", seconds=60)
431
575
  async def my_task():
432
- print("Task executed")
576
+ print("每分钟执行")
433
577
 
434
- @scheduler.scheduled_job(CronTrigger(hour="*"))
578
+ @scheduler.scheduled_job("cron", hour="*", minute=0)
435
579
  async def hourly_task():
436
- print("Hourly task")
580
+ print("每小时整点执行")
581
+
582
+ # === 条件加载 ===
583
+ @scheduler.scheduled_job("cron", hour=2, enabled=settings.ENABLE_REPORT)
584
+ async def daily_report():
585
+ print("每日报告")
586
+
587
+ # === 原生对象模式 ===
588
+ from apscheduler.triggers.cron import CronTrigger
437
589
 
438
590
  @scheduler.scheduled_job(CronTrigger.from_crontab("0 0 * * *"))
439
591
  async def daily_task():
440
- print("Daily task")
592
+ print("每天 0 点执行")
441
593
 
442
594
  Args:
443
- trigger: APScheduler 触发器对象
444
- id: 任务ID
445
- **kwargs: 其他 APScheduler add_job 参数
595
+ trigger: 触发器类型或触发器对象
596
+ - 字符串: "cron" 或 "interval"
597
+ - 对象: CronTrigger(...) IntervalTrigger(...)
598
+ id: 任务ID(可选,默认使用函数完整路径)
599
+ enabled: 是否启用任务,默认 True。设为 False 时跳过注册
600
+ **kwargs: 触发器参数(字符串模式)或其他 APScheduler add_job 参数
446
601
 
447
602
  Returns:
448
603
  装饰器函数
449
604
  """
450
605
  def decorator(func: Callable) -> Callable:
606
+ # enabled=False 时跳过注册
607
+ if not enabled:
608
+ job_id = id or f"{func.__module__}.{func.__name__}"
609
+ logger.debug(f"任务已禁用,跳过注册: {job_id}")
610
+ return func
611
+
451
612
  job_config = {
452
613
  "func": func,
453
614
  "trigger": trigger,
@@ -34,16 +34,40 @@ except ImportError:
34
34
  CurrentMessage = None
35
35
  TimeLimit = None
36
36
 
37
- # 延迟导入 kombu broker(可选依赖)
37
+ # 可选导入 Redis/RabbitMQ broker(不再使用 KombuBroker)
38
38
  try:
39
- from dramatiq_kombu_broker import KombuBroker
40
- _KOMBU_BROKER_AVAILABLE = True
41
- except ImportError:
42
- _KOMBU_BROKER_AVAILABLE = False
43
- if TYPE_CHECKING:
44
- from dramatiq_kombu_broker import KombuBroker
45
- else:
46
- KombuBroker = None
39
+ from dramatiq.brokers.redis import RedisBroker # type: ignore
40
+ _REDIS_BROKER_AVAILABLE = True
41
+ except Exception:
42
+ RedisBroker = None # type: ignore
43
+ _REDIS_BROKER_AVAILABLE = False
44
+
45
+ try:
46
+ from dramatiq.brokers.rabbitmq import RabbitmqBroker # type: ignore
47
+ _RABBIT_BROKER_AVAILABLE = True
48
+ except Exception:
49
+ RabbitmqBroker = None # type: ignore
50
+ _RABBIT_BROKER_AVAILABLE = False
51
+
52
+
53
+ def _create_broker(url: str, middleware_list: list) -> Any:
54
+ """根据 URL 创建 Dramatiq 原生 Broker,并挂载中间件。"""
55
+ scheme = url.split(":", 1)[0].lower() if url else ""
56
+ if scheme.startswith("redis"):
57
+ if not _REDIS_BROKER_AVAILABLE:
58
+ raise ImportError("未安装 redis 或 dramatiq 的 RedisBroker 不可用,请安装: pip install dramatiq redis")
59
+ broker = RedisBroker(url=url) # type: ignore[call-arg]
60
+ for m in middleware_list:
61
+ broker.add_middleware(m)
62
+ return broker
63
+ if scheme in {"amqp", "amqps"}:
64
+ if not _RABBIT_BROKER_AVAILABLE:
65
+ raise ImportError("RabbitMQ broker 不可用,请安装: pip install 'dramatiq[rabbitmq]'")
66
+ broker = RabbitmqBroker(url=url) # type: ignore[call-arg]
67
+ for m in middleware_list:
68
+ broker.add_middleware(m)
69
+ return broker
70
+ raise ValueError(f"不支持的任务队列 URL: {url}")
47
71
 
48
72
 
49
73
  class TaskProxy:
@@ -170,7 +194,7 @@ class TaskManager:
170
194
  name: 实例名称
171
195
  """
172
196
  self.name = name
173
- self._broker: Any = None # KombuBroker | None
197
+ self._broker: Any = None # Dramatiq Broker | None
174
198
  self._initialized: bool = False
175
199
  self._task_config: TaskConfig | None = None
176
200
  self._run_mode: TaskRunMode = TaskRunMode.WORKER # 默认 Worker 模式(调度者)
@@ -255,10 +279,6 @@ class TaskManager:
255
279
  "dramatiq 未安装。请安装可选依赖: pip install 'aury-boot[queue-dramatiq]'"
256
280
  )
257
281
 
258
- if not _KOMBU_BROKER_AVAILABLE:
259
- raise ImportError(
260
- "dramatiq-kombu-broker 未安装。请安装可选依赖: pip install 'aury-boot[queue-dramatiq-kombu]'"
261
- )
262
282
 
263
283
  try:
264
284
  # 使用函数式编程创建默认中间件(如果未提供)
@@ -268,11 +288,11 @@ class TaskManager:
268
288
 
269
289
  middleware_list = middleware if middleware is not None else create_default_middleware()
270
290
 
271
- # 使用 KombuBroker,支持多种后端(RedisRabbitMQ、SQS 等)
272
- self._broker = KombuBroker(url=url, middleware=middleware_list)
291
+ # 使用 Dramatiq 原生 Broker(Redis/RabbitMQ),不再依赖 KombuBroker
292
+ self._broker = _create_broker(url, middleware_list)
273
293
  dramatiq.set_broker(self._broker)
274
294
  self._initialized = True
275
- logger.info(f"任务管理器初始化完成(使用 Kombu Broker: {url})")
295
+ logger.info(f"任务管理器初始化完成(broker={type(self._broker).__name__}, url={url})")
276
296
  except Exception as exc:
277
297
  logger.error(f"任务队列初始化失败: {exc}")
278
298
  raise
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aury-boot
3
- Version: 0.0.19
3
+ Version: 0.0.21
4
4
  Summary: Aury Boot - 基于 FastAPI 生态的企业级 API 开发框架
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: alembic>=1.17.2
@@ -24,13 +24,11 @@ Requires-Dist: sqladmin>=0.16.0; extra == 'admin'
24
24
  Provides-Extra: all
25
25
  Requires-Dist: aiomysql>=0.3.2; extra == 'all'
26
26
  Requires-Dist: aiosqlite>=0.21.0; extra == 'all'
27
- Requires-Dist: amqp>=5.3.1; extra == 'all'
28
27
  Requires-Dist: apscheduler>=3.11.1; extra == 'all'
29
28
  Requires-Dist: asyncpg>=0.31.0; extra == 'all'
30
29
  Requires-Dist: aury-sdk-storage[aws]>=0.0.1; extra == 'all'
31
- Requires-Dist: dramatiq-kombu-broker>=0.2.2; extra == 'all'
32
30
  Requires-Dist: dramatiq>=1.18.0; extra == 'all'
33
- Requires-Dist: kombu>=5.6.1; extra == 'all'
31
+ Requires-Dist: pika>=1.3.2; extra == 'all'
34
32
  Requires-Dist: redis>=7.1.0; extra == 'all'
35
33
  Provides-Extra: dev
36
34
  Requires-Dist: mypy>=1.19.0; extra == 'dev'
@@ -53,9 +51,7 @@ Requires-Dist: aiosqlite>=0.21.0; extra == 'recommended'
53
51
  Requires-Dist: apscheduler>=3.11.1; extra == 'recommended'
54
52
  Requires-Dist: asyncpg>=0.31.0; extra == 'recommended'
55
53
  Requires-Dist: aury-sdk-storage[aws]>=0.0.1; extra == 'recommended'
56
- Requires-Dist: dramatiq-kombu-broker>=0.2.2; extra == 'recommended'
57
54
  Requires-Dist: dramatiq>=1.18.0; extra == 'recommended'
58
- Requires-Dist: kombu>=5.6.1; extra == 'recommended'
59
55
  Requires-Dist: redis>=7.1.0; extra == 'recommended'
60
56
  Provides-Extra: redis
61
57
  Requires-Dist: redis>=7.1.0; extra == 'redis'
@@ -66,9 +62,8 @@ Requires-Dist: apscheduler>=3.11.1; extra == 'scheduler'
66
62
  Provides-Extra: sqlite
67
63
  Requires-Dist: aiosqlite>=0.21.0; extra == 'sqlite'
68
64
  Provides-Extra: tasks
69
- Requires-Dist: dramatiq-kombu-broker>=0.2.2; extra == 'tasks'
70
65
  Requires-Dist: dramatiq>=1.18.0; extra == 'tasks'
71
- Requires-Dist: kombu>=5.6.1; extra == 'tasks'
66
+ Requires-Dist: redis>=7.1.0; extra == 'tasks'
72
67
  Description-Content-Type: text/markdown
73
68
 
74
69
  # Aury Boot
@@ -1,5 +1,5 @@
1
1
  aury/boot/__init__.py,sha256=pCno-EInnpIBa1OtxNYF-JWf9j95Cd2h6vmu0xqa_-4,1791
2
- aury/boot/_version.py,sha256=2t1q5JYEYkNwbHa29phdM-U8VWR1m6EwW48aFKWPj_w,706
2
+ aury/boot/_version.py,sha256=G-x3V64IYobhRYKCOse2hTj6uYLM7_2kyUMOZMY7cf4,706
3
3
  aury/boot/application/__init__.py,sha256=0o_XmiwFCeAu06VHggS8I1e7_nSMoRq0Hcm0fYfCywU,3071
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=wBF7q_kZp5uWUeWy95oElBAsc43xJ4RZ1s6tMGIiCac,17253
11
+ aury/boot/application/app/base.py,sha256=PYYUC_Yz3U885aU2uG5EAbbKOj_oUB4VSYMD_KS5i1s,17789
12
12
  aury/boot/application/app/components.py,sha256=-blDjMF87GYFmFyk4DHm2KJDcm27HpMxGpNaEFluwEE,22403
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=WILVLfZCMyximKKZ6uuzxa82mcdhPNqa2G5iX90L1Wc,30619
17
+ aury/boot/application/config/settings.py,sha256=o0SAwEpFqoHE7zB3lYyaSCm0cX2NZnTVqUWiSJ-Qdtk,30576
18
18
  aury/boot/application/constants/__init__.py,sha256=DCXs13_VVaQWHqO-qpJoZwRd7HIexiirtw_nu8msTXE,340
19
19
  aury/boot/application/constants/components.py,sha256=hDRs3YxpnfIFcGaUa1DYqBRwmV2_dceOlcCXabHE3fk,1006
20
20
  aury/boot/application/constants/scheduler.py,sha256=S77FBIvHlyruvlabRWZJ2J1YAs2xWXPQI2yuGdGUDNA,471
@@ -23,7 +23,7 @@ aury/boot/application/errors/__init__.py,sha256=aYhGqjHayYr7sv9kM22y0sOo9R-8RK0r
23
23
  aury/boot/application/errors/chain.py,sha256=DSLZvPH7oNcxE26j3hiXG_1W7-3fdKyu0QMEEi1c5CY,2306
24
24
  aury/boot/application/errors/codes.py,sha256=CBR8umCSNp1zUslwK9tboQ4s753qBwA1OpefaQTPdjE,1431
25
25
  aury/boot/application/errors/exceptions.py,sha256=5gHC9cQsuvNpO5V9GlFkb-QqH0xNqQaOKBmTBM5wYEI,7303
26
- aury/boot/application/errors/handlers.py,sha256=wPYiiDkJGNxJjXL442KhPAg4rLTq-GSISmVEa6nHmiE,11129
26
+ aury/boot/application/errors/handlers.py,sha256=Q9U1YK_tzO2lWtLhJ4Tuq973vyuCm_zElYEO07DoYlk,11948
27
27
  aury/boot/application/errors/response.py,sha256=fqOO3bNTnRRjoN3gK0-6xBVAYfSqyPvUbONowAecq9k,3107
28
28
  aury/boot/application/interfaces/__init__.py,sha256=EGbiCL8IoGseylLVZO29Lkt3luygG7JknTgtAxeb48U,1438
29
29
  aury/boot/application/interfaces/egress.py,sha256=t8FK17V649rsm65uAeBruYr2mhfcqJiIzkS8UPsOzlc,5346
@@ -48,7 +48,7 @@ aury/boot/commands/docker.py,sha256=7mKorZCPZgxH1XFslzo6W-uzpe61hGXz86JKOhOeBlo,
48
48
  aury/boot/commands/docs.py,sha256=7VaZyEuezmL28aM9y13ni-y-VYP-eNKff5-qb-9q1RQ,13066
49
49
  aury/boot/commands/generate.py,sha256=WZieSXuofxJOC7NBiVGpBigB9NZ4GMcF2F1ReTNun1I,44420
50
50
  aury/boot/commands/init.py,sha256=vHg2Zhdoar1ANug9J8pT1tNGdH5S0GO19Rhsy-04mXQ,32262
51
- aury/boot/commands/pkg.py,sha256=SyHpeSh0vuGVHNmv97-0ZX7AjuSttfZkOs7T86Pq-NQ,14489
51
+ aury/boot/commands/pkg.py,sha256=bw0QPptKscNgQ4I1SfSehTio9Q5KrvxgvkYx4tbZ7Vs,14495
52
52
  aury/boot/commands/scheduler.py,sha256=BCIGQcGryXpsYNF-mncP6v5kNoz6DZ10DMzMKVDiXxA,3516
53
53
  aury/boot/commands/worker.py,sha256=qAcPdoKpMBLYoi45X_y2-nobuYKxscJpooEB_0HhM4o,4163
54
54
  aury/boot/commands/migrate/__init__.py,sha256=W9OhkX8ILdolySofgdP2oYoJGG9loQd5FeSwkniU3qM,455
@@ -76,7 +76,7 @@ aury/boot/commands/templates/project/aury_docs/04-schema.md.tpl,sha256=ZwwKhUbLI
76
76
  aury/boot/commands/templates/project/aury_docs/05-api.md.tpl,sha256=oPzda3V6ZPDDEW-5MwyzmsMRuu5mXrsRGEq3lj0M-58,2997
77
77
  aury/boot/commands/templates/project/aury_docs/06-exception.md.tpl,sha256=Tv_Q5lsScHzvtcaFWmuQzN4YqvpcWZIdXS8jw99K29E,3340
78
78
  aury/boot/commands/templates/project/aury_docs/07-cache.md.tpl,sha256=EQMI7vJIwJT-VdG4p1GMCDEo58DCO1n6V-MvUzGSaS0,3411
79
- aury/boot/commands/templates/project/aury_docs/08-scheduler.md.tpl,sha256=tmLuQwqUoegV-721pDWC9K4A2pSMZ0BIWR-54gqz5aQ,4448
79
+ aury/boot/commands/templates/project/aury_docs/08-scheduler.md.tpl,sha256=NZpe9j1mNXyornCvTJQ1YmwhMuzL2H0yEGPPc1O2GJ0,6510
80
80
  aury/boot/commands/templates/project/aury_docs/09-tasks.md.tpl,sha256=swHOQ_pWPtW8Bsy1arPu2OeIgs1FoKsJ2AsVSYUWPHY,931
81
81
  aury/boot/commands/templates/project/aury_docs/10-storage.md.tpl,sha256=mhe0j0S51ndPJLjaQ6yD8OPYBEO02NHumJVbBvz2qkw,4320
82
82
  aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl,sha256=bwxFCGQsO9cTEbwqJF1xcjsZKP82HRWhIMRUS0c9_ZI,2435
@@ -99,7 +99,7 @@ aury/boot/commands/templates/project/env_templates/storage.tpl,sha256=x983u0Y2AF
99
99
  aury/boot/commands/templates/project/env_templates/third_party.tpl,sha256=w5lcKLYRHrA_iB-FmqMM_0WMs6bvxpf3ZwgicthFxgU,1959
100
100
  aury/boot/commands/templates/project/modules/api.py.tpl,sha256=G_IE-UC_pRhN7oOxy3dl_VLmR_omlKmHhWYi-AlyZIQ,471
101
101
  aury/boot/commands/templates/project/modules/exceptions.py.tpl,sha256=TKY3XaQU50Z-sDHWi3_Ns-A4v50PFru08H2lzmKxAUw,2646
102
- aury/boot/commands/templates/project/modules/schedules.py.tpl,sha256=CizmYzeuibWBsa_XxJTycr_WuzLPKtjJE1Vc78Lhdic,752
102
+ aury/boot/commands/templates/project/modules/schedules.py.tpl,sha256=TrIlwYyQUjWagDSytdQgHF1gYKCN4CPeInFLedzmstY,1350
103
103
  aury/boot/commands/templates/project/modules/tasks.py.tpl,sha256=w16VsW0K1_ukZe1Md2A_DnNPCAQUTNuo1JYfHOb7ZTI,564
104
104
  aury/boot/common/__init__.py,sha256=MhNP3c_nwx8CyDkDF6p1f4DcTZ1CZZScg66FWdbdaZI,629
105
105
  aury/boot/common/exceptions/__init__.py,sha256=aS3rIXWc5qNNJbfMs_PNmBlFsyNdKUMErziNMd1yoB8,3176
@@ -176,7 +176,7 @@ aury/boot/infrastructure/mq/backends/rabbitmq.py,sha256=0NWgPKEwtbmI63EVvKINdfXX
176
176
  aury/boot/infrastructure/mq/backends/redis.py,sha256=i8KECToIFEZ6CnHyNCk34_xdff5ioK172_knOy6EeUU,5279
177
177
  aury/boot/infrastructure/scheduler/__init__.py,sha256=eTRJ5dSPcKvyFvLVtraoQteXTTDDGwIrmw06J2hoNdA,323
178
178
  aury/boot/infrastructure/scheduler/exceptions.py,sha256=ROltrhSctVWA-6ulnjuYeHAk3ZF-sykDoesuierYzew,634
179
- aury/boot/infrastructure/scheduler/manager.py,sha256=xXSFdzliLoFKVsKkpriig5PJjyro9CMT0SQGaMnTAec,16290
179
+ aury/boot/infrastructure/scheduler/manager.py,sha256=OHQOHQlcoN8yFnky4kfuhsEIk39qX6nLZ7xJ51tfg68,23130
180
180
  aury/boot/infrastructure/storage/__init__.py,sha256=bA-n3v2S1FX6XsQbLqt0hkgx512MjUn_b8kJo53G6gA,1238
181
181
  aury/boot/infrastructure/storage/base.py,sha256=X9aswSMWtKZ6TdG5Rrh6qJpqIVLt1QcFkQAKdyUWPi0,5039
182
182
  aury/boot/infrastructure/storage/exceptions.py,sha256=Av1r94bRkeeeDo6vgAD9e_9YA9Ge6D7F2U1qzUs-8FE,622
@@ -185,14 +185,14 @@ aury/boot/infrastructure/tasks/__init__.py,sha256=Ne3VRVj4Y7x51UUkJH8nVrbxucCbal
185
185
  aury/boot/infrastructure/tasks/config.py,sha256=qOWjWZ7uZAr2bKc2xx7kdoiWisFqmBqw2uAcaA6Ud6s,747
186
186
  aury/boot/infrastructure/tasks/constants.py,sha256=6lo5jGLPItntVKLgrz6uh4tz_F1a-ckEO97MlP5aGcA,836
187
187
  aury/boot/infrastructure/tasks/exceptions.py,sha256=v6hlBbfs-oI_HbUZCxR3T8_c-U83s4_I0SvM7GHDUWE,605
188
- aury/boot/infrastructure/tasks/manager.py,sha256=AGWCZwO3uFQhNZ-reyR2KinlkUsXF8z1bsJ8UmWPcTk,17328
188
+ aury/boot/infrastructure/tasks/manager.py,sha256=CTmqCS1okYBznTe9LEvM_scGmjQ60T-W_OgRWSStgxM,18271
189
189
  aury/boot/testing/__init__.py,sha256=WbUSXcICNpr9RvrA2JzxRPcjMuTWDPTKOwXl0l4697U,703
190
190
  aury/boot/testing/base.py,sha256=BQOA6V4RIecVJM9t7kto9YxL0Ij_jEsFBdpKceWBe3U,3725
191
191
  aury/boot/testing/client.py,sha256=KOg1EemuIVsBG68G5y0DjSxZGcIQVdWQ4ASaHE3o1R0,4484
192
192
  aury/boot/testing/factory.py,sha256=8GvwX9qIDu0L65gzJMlrWB0xbmJ-7zPHuwk3eECULcg,5185
193
193
  aury/boot/toolkit/__init__.py,sha256=AcyVb9fDf3CaEmJPNkWC4iGv32qCPyk4BuFKSuNiJRQ,334
194
194
  aury/boot/toolkit/http/__init__.py,sha256=zIPmpIZ9Qbqe25VmEr7jixoY2fkRbLm7NkCB9vKpg6I,11039
195
- aury_boot-0.0.19.dist-info/METADATA,sha256=dY0ECLQ3Pse_zl86ktmyqWP_BWWEuX5m72YU9d5Etwk,7981
196
- aury_boot-0.0.19.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
197
- aury_boot-0.0.19.dist-info/entry_points.txt,sha256=f9KXEkDIGc0BGkgBvsNx_HMz9VhDjNxu26q00jUpDwQ,49
198
- aury_boot-0.0.19.dist-info/RECORD,,
195
+ aury_boot-0.0.21.dist-info/METADATA,sha256=a7scICE4EqlEjn1ZsnemOzEscg8IySfO6WV6VQKO-_s,7695
196
+ aury_boot-0.0.21.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
197
+ aury_boot-0.0.21.dist-info/entry_points.txt,sha256=f9KXEkDIGc0BGkgBvsNx_HMz9VhDjNxu26q00jUpDwQ,49
198
+ aury_boot-0.0.21.dist-info/RECORD,,