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 +2 -2
- aury/boot/application/app/base.py +8 -3
- aury/boot/application/config/settings.py +1 -2
- aury/boot/application/errors/handlers.py +23 -8
- aury/boot/commands/pkg.py +3 -3
- aury/boot/commands/templates/project/aury_docs/08-scheduler.md.tpl +77 -24
- aury/boot/commands/templates/project/modules/schedules.py.tpl +21 -4
- aury/boot/infrastructure/scheduler/manager.py +198 -37
- aury/boot/infrastructure/tasks/manager.py +37 -17
- {aury_boot-0.0.19.dist-info → aury_boot-0.0.21.dist-info}/METADATA +3 -8
- {aury_boot-0.0.19.dist-info → aury_boot-0.0.21.dist-info}/RECORD +13 -13
- {aury_boot-0.0.19.dist-info → aury_boot-0.0.21.dist-info}/WHEEL +0 -0
- {aury_boot-0.0.19.dist-info → aury_boot-0.0.21.dist-info}/entry_points.txt +0 -0
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.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
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(
|
|
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
|
-
|
|
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":
|
|
191
|
-
"
|
|
192
|
-
"
|
|
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=
|
|
211
|
+
message="参数校验失败",
|
|
212
|
+
code=422,
|
|
198
213
|
errors=errors,
|
|
199
214
|
)
|
|
200
215
|
|
|
201
216
|
return JSONResponse(
|
|
202
|
-
status_code=status.
|
|
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", "
|
|
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=["
|
|
109
|
+
deps=["pika"],
|
|
110
110
|
),
|
|
111
111
|
# 调度器
|
|
112
112
|
"scheduler": ModuleInfo(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 定时任务(Scheduler)
|
|
2
2
|
|
|
3
|
-
基于 APScheduler
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
62
|
+
支持两种语法:
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
74
|
+
@scheduler.scheduled_job("cron", hour=2, minute=30)
|
|
62
75
|
|
|
63
76
|
# 每小时整点
|
|
64
|
-
|
|
77
|
+
@scheduler.scheduled_job("cron", hour="*", minute=0)
|
|
65
78
|
|
|
66
79
|
# 工作日 9:00
|
|
67
|
-
|
|
80
|
+
@scheduler.scheduled_job("cron", day_of_week="mon-fri", hour=9)
|
|
68
81
|
|
|
69
82
|
# 每月 1 号
|
|
70
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
#
|
|
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(
|
|
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:
|
|
256
|
+
trigger: TriggerType | BaseTrigger,
|
|
207
257
|
*,
|
|
208
258
|
id: str | None = None,
|
|
209
259
|
**kwargs: Any,
|
|
210
260
|
) -> None:
|
|
211
261
|
"""添加任务。
|
|
212
262
|
|
|
213
|
-
|
|
263
|
+
支持两种触发器语法:
|
|
264
|
+
|
|
265
|
+
1. 字符串模式:trigger 为 "cron" 或 "interval",触发器参数通过 kwargs 传递
|
|
266
|
+
2. 原生对象模式:trigger 为 APScheduler 触发器对象
|
|
214
267
|
|
|
215
268
|
Args:
|
|
216
269
|
func: 任务函数
|
|
217
|
-
trigger:
|
|
218
|
-
-
|
|
219
|
-
- CronTrigger(
|
|
220
|
-
- CronTrigger.from_crontab("0 * * * *")
|
|
270
|
+
trigger: 触发器类型或触发器对象
|
|
271
|
+
- 字符串: "cron" 或 "interval"
|
|
272
|
+
- 对象: CronTrigger(...) 或 IntervalTrigger(...)
|
|
221
273
|
id: 任务ID(可选,默认使用函数完整路径)
|
|
222
|
-
**kwargs:
|
|
274
|
+
**kwargs: 触发器参数(字符串模式)或其他 APScheduler add_job 参数(对象模式)
|
|
223
275
|
|
|
224
276
|
示例:
|
|
225
|
-
|
|
226
|
-
|
|
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,
|
|
282
|
+
scheduler.add_job(my_task, "interval", minutes=30)
|
|
233
283
|
|
|
234
284
|
# 每天凌晨 2 点执行
|
|
235
|
-
scheduler.add_job(my_task,
|
|
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=
|
|
320
|
+
trigger=trigger_obj,
|
|
251
321
|
id=job_id,
|
|
252
|
-
**
|
|
322
|
+
**job_params,
|
|
253
323
|
)
|
|
254
324
|
|
|
255
|
-
logger.info(f"任务已注册: {job_id} | 触发器: {type(
|
|
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:
|
|
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:
|
|
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.
|
|
354
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
573
|
+
# === 字符串模式 ===
|
|
574
|
+
@scheduler.scheduled_job("interval", seconds=60)
|
|
431
575
|
async def my_task():
|
|
432
|
-
print("
|
|
576
|
+
print("每分钟执行")
|
|
433
577
|
|
|
434
|
-
@scheduler.scheduled_job(
|
|
578
|
+
@scheduler.scheduled_job("cron", hour="*", minute=0)
|
|
435
579
|
async def hourly_task():
|
|
436
|
-
print("
|
|
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("
|
|
592
|
+
print("每天 0 点执行")
|
|
441
593
|
|
|
442
594
|
Args:
|
|
443
|
-
trigger:
|
|
444
|
-
|
|
445
|
-
|
|
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
|
-
#
|
|
37
|
+
# 可选导入 Redis/RabbitMQ broker(不再使用 KombuBroker)
|
|
38
38
|
try:
|
|
39
|
-
from
|
|
40
|
-
|
|
41
|
-
except
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 #
|
|
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
|
-
# 使用
|
|
272
|
-
self._broker =
|
|
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"
|
|
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.
|
|
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:
|
|
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:
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
196
|
-
aury_boot-0.0.
|
|
197
|
-
aury_boot-0.0.
|
|
198
|
-
aury_boot-0.0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|