toms-fast 0.2.1__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.
- toms_fast-0.2.1.dist-info/METADATA +467 -0
- toms_fast-0.2.1.dist-info/RECORD +60 -0
- toms_fast-0.2.1.dist-info/WHEEL +4 -0
- toms_fast-0.2.1.dist-info/entry_points.txt +2 -0
- tomskit/__init__.py +0 -0
- tomskit/celery/README.md +693 -0
- tomskit/celery/__init__.py +4 -0
- tomskit/celery/celery.py +306 -0
- tomskit/celery/config.py +377 -0
- tomskit/cli/__init__.py +207 -0
- tomskit/cli/__main__.py +8 -0
- tomskit/cli/scaffold.py +123 -0
- tomskit/cli/templates/__init__.py +42 -0
- tomskit/cli/templates/base.py +348 -0
- tomskit/cli/templates/celery.py +101 -0
- tomskit/cli/templates/extensions.py +213 -0
- tomskit/cli/templates/fastapi.py +400 -0
- tomskit/cli/templates/migrations.py +281 -0
- tomskit/cli/templates_config.py +122 -0
- tomskit/logger/README.md +466 -0
- tomskit/logger/__init__.py +4 -0
- tomskit/logger/config.py +106 -0
- tomskit/logger/logger.py +290 -0
- tomskit/py.typed +0 -0
- tomskit/redis/README.md +462 -0
- tomskit/redis/__init__.py +6 -0
- tomskit/redis/config.py +85 -0
- tomskit/redis/redis_pool.py +87 -0
- tomskit/redis/redis_sync.py +66 -0
- tomskit/server/__init__.py +47 -0
- tomskit/server/config.py +117 -0
- tomskit/server/exceptions.py +412 -0
- tomskit/server/middleware.py +371 -0
- tomskit/server/parser.py +312 -0
- tomskit/server/resource.py +464 -0
- tomskit/server/server.py +276 -0
- tomskit/server/type.py +263 -0
- tomskit/sqlalchemy/README.md +590 -0
- tomskit/sqlalchemy/__init__.py +20 -0
- tomskit/sqlalchemy/config.py +125 -0
- tomskit/sqlalchemy/database.py +125 -0
- tomskit/sqlalchemy/pagination.py +359 -0
- tomskit/sqlalchemy/property.py +19 -0
- tomskit/sqlalchemy/sqlalchemy.py +131 -0
- tomskit/sqlalchemy/types.py +32 -0
- tomskit/task/README.md +67 -0
- tomskit/task/__init__.py +4 -0
- tomskit/task/task_manager.py +124 -0
- tomskit/tools/README.md +63 -0
- tomskit/tools/__init__.py +18 -0
- tomskit/tools/config.py +70 -0
- tomskit/tools/warnings.py +37 -0
- tomskit/tools/woker.py +81 -0
- tomskit/utils/README.md +666 -0
- tomskit/utils/README_SERIALIZER.md +644 -0
- tomskit/utils/__init__.py +35 -0
- tomskit/utils/fields.py +434 -0
- tomskit/utils/marshal_utils.py +137 -0
- tomskit/utils/response_utils.py +13 -0
- tomskit/utils/serializers.py +447 -0
tomskit/celery/README.md
ADDED
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
# Celery Module Guide
|
|
2
|
+
|
|
3
|
+
该模块提供了基于 Celery 的异步任务执行框架,支持在 Celery 任务中运行异步函数,并自动管理数据库会话。
|
|
4
|
+
|
|
5
|
+
## 模块概述
|
|
6
|
+
|
|
7
|
+
Celery 模块扩展了标准 Celery 应用,提供了完整的异步任务支持。主要特性包括:
|
|
8
|
+
|
|
9
|
+
- ⚡ **异步任务支持**:在 Celery 任务中运行异步函数
|
|
10
|
+
- 🔄 **自动会话管理**:自动创建和关闭数据库会话
|
|
11
|
+
- 🛠️ **配置管理**:支持通过 `from_mapping` 方法配置 Celery
|
|
12
|
+
- 🔧 **上下文管理**:使用 `ContextVar` 管理 Celery 应用上下文
|
|
13
|
+
- 📦 **资源初始化**:在 worker 启动时初始化数据库连接池和 Redis 客户端
|
|
14
|
+
|
|
15
|
+
**Import Path:**
|
|
16
|
+
```python
|
|
17
|
+
from tomskit.celery import (
|
|
18
|
+
AsyncCelery,
|
|
19
|
+
AsyncTaskRunner,
|
|
20
|
+
CeleryConfig
|
|
21
|
+
)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 核心类和函数
|
|
25
|
+
|
|
26
|
+
### AsyncCelery
|
|
27
|
+
|
|
28
|
+
异步 Celery 应用类,继承自 `Celery`,负责配置管理。该类不自动初始化资源,资源初始化应该由业务代码在 worker 启动时完成。
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
class AsyncCelery(Celery):
|
|
32
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
|
33
|
+
|
|
34
|
+
config: dict[str, Any]
|
|
35
|
+
app_root_path: Optional[str] = None
|
|
36
|
+
|
|
37
|
+
def set_app_root_path(self, app_root_path: str) -> None: ...
|
|
38
|
+
|
|
39
|
+
def from_mapping(
|
|
40
|
+
self,
|
|
41
|
+
mapping: Mapping[str, Any] | None = None,
|
|
42
|
+
**kwargs: Any
|
|
43
|
+
) -> bool: ...
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**功能特性:**
|
|
47
|
+
- 继承自标准 Celery 类,兼容所有 Celery 功能
|
|
48
|
+
- 使用 `ContextVar` 管理应用上下文,确保线程安全
|
|
49
|
+
- 支持通过 `from_mapping` 方法配置 Celery(自动提取大写配置项)
|
|
50
|
+
- 不自动初始化资源,需要在 worker 启动时手动初始化
|
|
51
|
+
|
|
52
|
+
**属性说明:**
|
|
53
|
+
- `config`: 配置字典,存储所有大写配置项
|
|
54
|
+
- `app_root_path`: 应用根路径
|
|
55
|
+
|
|
56
|
+
**使用示例:**
|
|
57
|
+
```python
|
|
58
|
+
from tomskit.celery import AsyncCelery
|
|
59
|
+
|
|
60
|
+
# 创建 AsyncCelery 实例
|
|
61
|
+
celery_app = AsyncCelery(
|
|
62
|
+
'myapp',
|
|
63
|
+
broker='redis://localhost:6379/0',
|
|
64
|
+
backend='redis://localhost:6379/0'
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# 配置 Celery(只提取大写配置项)
|
|
68
|
+
celery_app.from_mapping(
|
|
69
|
+
# Celery 配置
|
|
70
|
+
CELERY_TASK_SERIALIZER='json',
|
|
71
|
+
CELERY_RESULT_SERIALIZER='json',
|
|
72
|
+
CELERY_ACCEPT_CONTENT=['json'],
|
|
73
|
+
CELERY_TIMEZONE='UTC',
|
|
74
|
+
CELERY_ENABLE_UTC=True,
|
|
75
|
+
|
|
76
|
+
# 数据库配置(用于 worker 初始化)
|
|
77
|
+
SQLALCHEMY_DATABASE_URI='mysql+aiomysql://user:pass@localhost/db',
|
|
78
|
+
SQLALCHEMY_ENGINE_OPTIONS={
|
|
79
|
+
'pool_size': 300,
|
|
80
|
+
'max_overflow': 10,
|
|
81
|
+
'pool_recycle': 3600,
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
# Redis 配置(用于 worker 初始化)
|
|
85
|
+
REDIS_HOST='localhost',
|
|
86
|
+
REDIS_PORT=6379,
|
|
87
|
+
REDIS_DB=0,
|
|
88
|
+
REDIS_PASSWORD='',
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# 设置应用根路径
|
|
92
|
+
celery_app.set_app_root_path('/path/to/app')
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### AsyncTaskRunner
|
|
96
|
+
|
|
97
|
+
异步任务运行器,用于在 Celery 任务中执行异步函数。只负责运行异步函数和自动创建/关闭数据库 session。前提是数据库连接池和 Redis 客户端应该由业务代码在 worker 启动时初始化。
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
class AsyncTaskRunner:
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
async_task: Callable[..., Awaitable[Any]],
|
|
104
|
+
use_db: bool = True,
|
|
105
|
+
use_redis: bool = False
|
|
106
|
+
) -> None: ...
|
|
107
|
+
|
|
108
|
+
def run(self, *args: Any, **kwargs: Any) -> Any: ...
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**功能特性:**
|
|
112
|
+
- 在 Celery 任务中运行异步函数
|
|
113
|
+
- 自动创建和关闭数据库会话(如果启用)
|
|
114
|
+
- 检查 Redis 客户端是否已初始化(如果启用)
|
|
115
|
+
- 使用 `asyncio.run` 执行异步任务
|
|
116
|
+
- 确保资源正确释放,即使发生异常
|
|
117
|
+
|
|
118
|
+
**参数说明:**
|
|
119
|
+
- `async_task`: 要执行的异步任务函数(必须是协程函数)
|
|
120
|
+
- `use_db`: 是否启用数据库 session 管理,默认为 `True`
|
|
121
|
+
- `use_redis`: 是否检查 Redis 客户端,默认为 `False`(仅检查,不管理)
|
|
122
|
+
|
|
123
|
+
**使用场景:**
|
|
124
|
+
在需要执行异步数据库操作或其他异步 I/O 操作的 Celery 任务中使用。
|
|
125
|
+
|
|
126
|
+
**使用示例:**
|
|
127
|
+
```python
|
|
128
|
+
from celery import shared_task
|
|
129
|
+
from tomskit.celery import AsyncTaskRunner
|
|
130
|
+
from tomskit.sqlalchemy.database import db
|
|
131
|
+
from tomskit.sqlalchemy import User
|
|
132
|
+
|
|
133
|
+
@shared_task(name="user_task_created", queue="mail", ignore_result=False)
|
|
134
|
+
def user_task_created():
|
|
135
|
+
task = AsyncTaskRunner(async_user_task_created)
|
|
136
|
+
return task.run()
|
|
137
|
+
|
|
138
|
+
async def async_user_task_created():
|
|
139
|
+
print("user_task_created running.")
|
|
140
|
+
new_user = User(
|
|
141
|
+
name="username",
|
|
142
|
+
email="username@gmail.com"
|
|
143
|
+
)
|
|
144
|
+
try:
|
|
145
|
+
db.session.add(new_user)
|
|
146
|
+
await db.session.commit()
|
|
147
|
+
print(f"add new user_id = {new_user.name}")
|
|
148
|
+
await db.session.refresh(new_user)
|
|
149
|
+
return f"This is new user id {new_user.id}"
|
|
150
|
+
except Exception as e:
|
|
151
|
+
await db.session.rollback()
|
|
152
|
+
return f"This task is fail. {str(e)}"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## 完整使用示例
|
|
156
|
+
|
|
157
|
+
### 初始化 Celery 应用和 Worker
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from celery.signals import worker_process_init
|
|
161
|
+
from tomskit.celery import AsyncCelery, AsyncTaskRunner
|
|
162
|
+
from tomskit.sqlalchemy.database import db
|
|
163
|
+
from tomskit.redis.redis_pool import RedisClientWrapper
|
|
164
|
+
from tomskit.redis.config import RedisConfig
|
|
165
|
+
|
|
166
|
+
# 创建 AsyncCelery 实例
|
|
167
|
+
celery_app = AsyncCelery(
|
|
168
|
+
'myapp',
|
|
169
|
+
broker='redis://localhost:6379/0',
|
|
170
|
+
backend='redis://localhost:6379/0'
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# 配置 Celery
|
|
174
|
+
celery_app.from_mapping(
|
|
175
|
+
# Celery 配置
|
|
176
|
+
CELERY_TASK_SERIALIZER='json',
|
|
177
|
+
CELERY_RESULT_SERIALIZER='json',
|
|
178
|
+
CELERY_ACCEPT_CONTENT=['json'],
|
|
179
|
+
CELERY_TIMEZONE='UTC',
|
|
180
|
+
CELERY_ENABLE_UTC=True,
|
|
181
|
+
CELERY_TASK_TRACK_STARTED=True,
|
|
182
|
+
CELERY_TASK_TIME_LIMIT=30 * 60, # 30 分钟
|
|
183
|
+
CELERY_TASK_SOFT_TIME_LIMIT=25 * 60, # 25 分钟
|
|
184
|
+
|
|
185
|
+
# 数据库配置
|
|
186
|
+
SQLALCHEMY_DATABASE_URI='mysql+aiomysql://user:pass@localhost/db',
|
|
187
|
+
SQLALCHEMY_ENGINE_OPTIONS={
|
|
188
|
+
'pool_size': 300,
|
|
189
|
+
'max_overflow': 10,
|
|
190
|
+
'pool_recycle': 3600,
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
# Redis 配置
|
|
194
|
+
REDIS_HOST='localhost',
|
|
195
|
+
REDIS_PORT=6379,
|
|
196
|
+
REDIS_DB=0,
|
|
197
|
+
REDIS_PASSWORD='',
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Worker 启动时初始化资源
|
|
201
|
+
@worker_process_init.connect
|
|
202
|
+
def init_worker(sender=None, **kwargs):
|
|
203
|
+
"""在 worker 进程启动时初始化数据库连接池和 Redis 客户端"""
|
|
204
|
+
# 初始化数据库连接池
|
|
205
|
+
db.initialize_session_pool(
|
|
206
|
+
celery_app.config["SQLALCHEMY_DATABASE_URI"],
|
|
207
|
+
celery_app.config.get("SQLALCHEMY_ENGINE_OPTIONS", {})
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# 初始化 Redis 客户端
|
|
211
|
+
redis_config = RedisConfig(
|
|
212
|
+
REDIS_HOST=celery_app.config.get("REDIS_HOST", "localhost"),
|
|
213
|
+
REDIS_PORT=celery_app.config.get("REDIS_PORT", 6379),
|
|
214
|
+
REDIS_DB=celery_app.config.get("REDIS_DB", 0),
|
|
215
|
+
REDIS_PASSWORD=celery_app.config.get("REDIS_PASSWORD", ""),
|
|
216
|
+
)
|
|
217
|
+
RedisClientWrapper.initialize(redis_config.model_dump())
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 定义异步任务
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from celery import shared_task
|
|
224
|
+
from tomskit.celery import AsyncTaskRunner
|
|
225
|
+
from tomskit.sqlalchemy.database import db
|
|
226
|
+
from tomskit.sqlalchemy import User
|
|
227
|
+
|
|
228
|
+
@celery_app.task(name="create_user", queue="default")
|
|
229
|
+
def create_user_task(name: str, email: str):
|
|
230
|
+
"""创建用户的 Celery 任务"""
|
|
231
|
+
task = AsyncTaskRunner(async_create_user)
|
|
232
|
+
return task.run(name, email)
|
|
233
|
+
|
|
234
|
+
async def async_create_user(name: str, email: str):
|
|
235
|
+
"""异步创建用户函数"""
|
|
236
|
+
new_user = User(name=name, email=email)
|
|
237
|
+
try:
|
|
238
|
+
db.session.add(new_user)
|
|
239
|
+
await db.session.commit()
|
|
240
|
+
await db.session.refresh(new_user)
|
|
241
|
+
return {
|
|
242
|
+
"success": True,
|
|
243
|
+
"user_id": new_user.id,
|
|
244
|
+
"message": f"User {name} created successfully"
|
|
245
|
+
}
|
|
246
|
+
except Exception as e:
|
|
247
|
+
await db.session.rollback()
|
|
248
|
+
return {
|
|
249
|
+
"success": False,
|
|
250
|
+
"error": str(e)
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### 使用 Redis 的任务
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
from celery import shared_task
|
|
258
|
+
from tomskit.celery import AsyncTaskRunner
|
|
259
|
+
from tomskit.redis.redis_pool import redis_client
|
|
260
|
+
from tomskit.sqlalchemy.database import db
|
|
261
|
+
from tomskit.sqlalchemy import User
|
|
262
|
+
|
|
263
|
+
@celery_app.task(name="cache_user_data", queue="cache")
|
|
264
|
+
def cache_user_data_task(user_id: int):
|
|
265
|
+
"""缓存用户数据的 Celery 任务"""
|
|
266
|
+
# 启用 Redis 检查
|
|
267
|
+
task = AsyncTaskRunner(async_cache_user_data, use_db=True, use_redis=True)
|
|
268
|
+
return task.run(user_id)
|
|
269
|
+
|
|
270
|
+
async def async_cache_user_data(user_id: int):
|
|
271
|
+
"""异步缓存用户数据函数"""
|
|
272
|
+
# 从数据库获取用户
|
|
273
|
+
user = await db.session.get(User, user_id)
|
|
274
|
+
if user:
|
|
275
|
+
# 缓存到 Redis
|
|
276
|
+
await redis_client.setex(
|
|
277
|
+
f"user:{user_id}",
|
|
278
|
+
3600, # 1 小时过期
|
|
279
|
+
str(user.id) # 需要序列化
|
|
280
|
+
)
|
|
281
|
+
return f"User {user_id} cached successfully"
|
|
282
|
+
return f"User {user_id} not found"
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### 不使用数据库的任务
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
@celery_app.task(name="simple_task", queue="default")
|
|
289
|
+
def simple_task(message: str):
|
|
290
|
+
"""简单的异步任务,不使用数据库"""
|
|
291
|
+
task = AsyncTaskRunner(async_simple_task, use_db=False)
|
|
292
|
+
return task.run(message)
|
|
293
|
+
|
|
294
|
+
async def async_simple_task(message: str):
|
|
295
|
+
"""简单的异步函数"""
|
|
296
|
+
# 只使用 Redis,不使用数据库
|
|
297
|
+
await redis_client.set("message", message)
|
|
298
|
+
return f"Message '{message}' stored"
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### 带参数和返回值的任务
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
@celery_app.task(name="process_data", queue="processing")
|
|
305
|
+
def process_data_task(data_id: int, options: dict = None):
|
|
306
|
+
"""处理数据的 Celery 任务"""
|
|
307
|
+
task = AsyncTaskRunner(async_process_data)
|
|
308
|
+
return task.run(data_id, options=options or {})
|
|
309
|
+
|
|
310
|
+
async def async_process_data(data_id: int, options: dict):
|
|
311
|
+
"""异步处理数据函数"""
|
|
312
|
+
# 处理逻辑
|
|
313
|
+
processed_count = 0
|
|
314
|
+
|
|
315
|
+
# 查询数据
|
|
316
|
+
result = await db.session.execute(
|
|
317
|
+
db.select(DataItem).where(DataItem.data_id == data_id)
|
|
318
|
+
)
|
|
319
|
+
items = result.scalars().all()
|
|
320
|
+
|
|
321
|
+
for item in items:
|
|
322
|
+
# 处理每个项目
|
|
323
|
+
await process_item(item, options)
|
|
324
|
+
processed_count += 1
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
"data_id": data_id,
|
|
328
|
+
"processed_count": processed_count,
|
|
329
|
+
"status": "completed"
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### 使用环境变量配置
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
import os
|
|
337
|
+
from tomskit.celery import AsyncCelery
|
|
338
|
+
from tomskit.sqlalchemy.config import DatabaseConfig
|
|
339
|
+
from tomskit.redis.config import RedisConfig
|
|
340
|
+
|
|
341
|
+
# 从环境变量创建配置
|
|
342
|
+
db_config = DatabaseConfig()
|
|
343
|
+
redis_config = RedisConfig()
|
|
344
|
+
|
|
345
|
+
# 创建 Celery 应用
|
|
346
|
+
celery_app = AsyncCelery(
|
|
347
|
+
'myapp',
|
|
348
|
+
broker=os.getenv('CELERY_BROKER_URL', 'redis://localhost:6379/0'),
|
|
349
|
+
backend=os.getenv('CELERY_RESULT_BACKEND', 'redis://localhost:6379/0')
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
# 配置 Celery
|
|
353
|
+
celery_app.from_mapping(
|
|
354
|
+
CELERY_TASK_SERIALIZER='json',
|
|
355
|
+
CELERY_RESULT_SERIALIZER='json',
|
|
356
|
+
CELERY_ACCEPT_CONTENT=['json'],
|
|
357
|
+
CELERY_TIMEZONE='UTC',
|
|
358
|
+
CELERY_ENABLE_UTC=True,
|
|
359
|
+
|
|
360
|
+
# 使用配置对象
|
|
361
|
+
SQLALCHEMY_DATABASE_URI=db_config.SQLALCHEMY_DATABASE_URI,
|
|
362
|
+
SQLALCHEMY_ENGINE_OPTIONS=db_config.SQLALCHEMY_ENGINE_OPTIONS,
|
|
363
|
+
|
|
364
|
+
REDIS_HOST=redis_config.REDIS_HOST,
|
|
365
|
+
REDIS_PORT=redis_config.REDIS_PORT,
|
|
366
|
+
REDIS_DB=redis_config.REDIS_DB,
|
|
367
|
+
REDIS_PASSWORD=redis_config.REDIS_PASSWORD,
|
|
368
|
+
)
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## 配置说明
|
|
372
|
+
|
|
373
|
+
### Celery 配置项
|
|
374
|
+
|
|
375
|
+
通过 `from_mapping` 方法可以配置所有标准的 Celery 配置项:
|
|
376
|
+
|
|
377
|
+
- `CELERY_TASK_SERIALIZER`: 任务序列化格式(如 'json', 'pickle')
|
|
378
|
+
- `CELERY_RESULT_SERIALIZER`: 结果序列化格式
|
|
379
|
+
- `CELERY_ACCEPT_CONTENT`: 接受的内容类型
|
|
380
|
+
- `CELERY_TIMEZONE`: 时区设置
|
|
381
|
+
- `CELERY_ENABLE_UTC`: 是否启用 UTC
|
|
382
|
+
- `CELERY_TASK_TRACK_STARTED`: 是否跟踪任务开始
|
|
383
|
+
- `CELERY_TASK_TIME_LIMIT`: 任务硬时间限制(秒)
|
|
384
|
+
- `CELERY_TASK_SOFT_TIME_LIMIT`: 任务软时间限制(秒)
|
|
385
|
+
- `CELERY_BROKER_URL`: Broker URL(如果未在初始化时指定)
|
|
386
|
+
- `CELERY_RESULT_BACKEND`: 结果后端 URL(如果未在初始化时指定)
|
|
387
|
+
|
|
388
|
+
### 数据库配置项
|
|
389
|
+
|
|
390
|
+
用于在 worker 启动时初始化数据库连接池:
|
|
391
|
+
|
|
392
|
+
- `SQLALCHEMY_DATABASE_URI`: 数据库连接 URI
|
|
393
|
+
- `SQLALCHEMY_ENGINE_OPTIONS`: SQLAlchemy 引擎选项字典
|
|
394
|
+
|
|
395
|
+
### Redis 配置项
|
|
396
|
+
|
|
397
|
+
用于在 worker 启动时初始化 Redis 客户端:
|
|
398
|
+
|
|
399
|
+
- `REDIS_HOST`: Redis 主机地址
|
|
400
|
+
- `REDIS_PORT`: Redis 端口
|
|
401
|
+
- `REDIS_DB`: Redis 数据库编号
|
|
402
|
+
- `REDIS_PASSWORD`: Redis 密码
|
|
403
|
+
|
|
404
|
+
**配置示例:**
|
|
405
|
+
```python
|
|
406
|
+
celery_app.from_mapping(
|
|
407
|
+
# 序列化配置
|
|
408
|
+
CELERY_TASK_SERIALIZER='json',
|
|
409
|
+
CELERY_RESULT_SERIALIZER='json',
|
|
410
|
+
CELERY_ACCEPT_CONTENT=['json'],
|
|
411
|
+
|
|
412
|
+
# 时区配置
|
|
413
|
+
CELERY_TIMEZONE='Asia/Shanghai',
|
|
414
|
+
CELERY_ENABLE_UTC=True,
|
|
415
|
+
|
|
416
|
+
# 任务配置
|
|
417
|
+
CELERY_TASK_TRACK_STARTED=True,
|
|
418
|
+
CELERY_TASK_TIME_LIMIT=30 * 60,
|
|
419
|
+
CELERY_TASK_SOFT_TIME_LIMIT=25 * 60,
|
|
420
|
+
|
|
421
|
+
# 路由配置
|
|
422
|
+
CELERY_TASK_ROUTES={
|
|
423
|
+
'tasks.send_email': {'queue': 'mail'},
|
|
424
|
+
'tasks.process_data': {'queue': 'processing'},
|
|
425
|
+
},
|
|
426
|
+
|
|
427
|
+
# 数据库配置
|
|
428
|
+
SQLALCHEMY_DATABASE_URI='mysql+aiomysql://user:pass@localhost/db',
|
|
429
|
+
SQLALCHEMY_ENGINE_OPTIONS={
|
|
430
|
+
'pool_size': 300,
|
|
431
|
+
'max_overflow': 10,
|
|
432
|
+
'pool_recycle': 3600,
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
# Redis 配置
|
|
436
|
+
REDIS_HOST='localhost',
|
|
437
|
+
REDIS_PORT=6379,
|
|
438
|
+
REDIS_DB=0,
|
|
439
|
+
)
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
## 注意事项
|
|
443
|
+
|
|
444
|
+
1. **资源初始化**:
|
|
445
|
+
- 数据库连接池和 Redis 客户端必须在 worker 启动时初始化
|
|
446
|
+
- 使用 `worker_process_init` 信号处理器来初始化资源
|
|
447
|
+
- 不要在任务中初始化资源,这会导致性能问题和资源泄漏
|
|
448
|
+
|
|
449
|
+
2. **会话管理**:
|
|
450
|
+
- `AsyncTaskRunner` 会自动创建和关闭数据库会话
|
|
451
|
+
- 在异步函数中直接使用 `db.session`,不需要手动创建会话
|
|
452
|
+
- 会话会在任务完成后自动关闭,即使发生异常也会正确清理
|
|
453
|
+
|
|
454
|
+
3. **异步函数要求**:
|
|
455
|
+
- `AsyncTaskRunner` 的 `async_task` 参数必须是协程函数(使用 `async def` 定义)
|
|
456
|
+
- 在异步函数中必须使用 `await` 调用异步操作
|
|
457
|
+
|
|
458
|
+
4. **上下文管理**:
|
|
459
|
+
- `AsyncCelery` 使用 `ContextVar` 管理应用上下文
|
|
460
|
+
- 确保在创建 `AsyncTaskRunner` 之前已经初始化了 `AsyncCelery` 实例
|
|
461
|
+
|
|
462
|
+
5. **性能考虑**:
|
|
463
|
+
- `AsyncTaskRunner.run()` 使用 `asyncio.run()` 执行异步任务
|
|
464
|
+
- 每个任务都会创建新的事件循环,适合在 Celery worker 中使用
|
|
465
|
+
- 数据库连接池在 worker 启动时创建,所有任务共享连接池
|
|
466
|
+
|
|
467
|
+
6. **错误处理**:
|
|
468
|
+
- 如果 Celery 应用未初始化,会抛出 `RuntimeError`
|
|
469
|
+
- 如果数据库连接池未初始化,会抛出 `RuntimeError`
|
|
470
|
+
- 如果 Redis 客户端未初始化且 `use_redis=True`,会抛出 `RuntimeError`
|
|
471
|
+
- 建议在任务函数中捕获和处理异常,返回错误信息而不是抛出异常
|
|
472
|
+
|
|
473
|
+
7. **配置项提取**:
|
|
474
|
+
- `from_mapping` 方法只提取大写的配置项
|
|
475
|
+
- 小写配置项会被忽略,确保配置项名称使用大写
|
|
476
|
+
|
|
477
|
+
8. **Redis 使用**:
|
|
478
|
+
- `use_redis` 参数仅用于检查 Redis 客户端是否已初始化
|
|
479
|
+
- Redis 客户端本身不需要在任务中管理,它在 worker 启动时初始化
|
|
480
|
+
- 在异步函数中直接使用 `redis_client` 进行操作
|
|
481
|
+
|
|
482
|
+
## 工作流程
|
|
483
|
+
|
|
484
|
+
1. **应用启动**:创建 `AsyncCelery` 实例并配置
|
|
485
|
+
2. **Worker 启动**:在 `worker_process_init` 信号处理器中初始化数据库连接池和 Redis 客户端
|
|
486
|
+
3. **任务执行**:创建 `AsyncTaskRunner` 实例并调用 `run()` 方法
|
|
487
|
+
4. **资源管理**:`AsyncTaskRunner` 自动创建数据库会话,执行异步函数,然后关闭会话
|
|
488
|
+
5. **Worker 关闭**:资源会在进程结束时自动清理
|
|
489
|
+
|
|
490
|
+
## 相关文档
|
|
491
|
+
|
|
492
|
+
- [Async Task Guide](../../docs/specs/async_task_guide.md) - 详细的异步任务使用指南
|
|
493
|
+
- [Celery 官方文档](https://docs.celeryq.dev/) - Celery 官方文档
|
|
494
|
+
- [Database Guide](../../docs/specs/database_guide.md) - 数据库使用指南
|
|
495
|
+
- [Redis Guide](../../docs/specs/redis_guide.md) - Redis 使用指南
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
## Celery 配置
|
|
499
|
+
|
|
500
|
+
### CeleryConfig
|
|
501
|
+
|
|
502
|
+
Celery 配置类,继承自 `pydantic_settings.BaseSettings`,用于管理 Celery 应用的完整配置。支持 Redis 作为 broker 和 backend,以及将结果存储到数据库。
|
|
503
|
+
|
|
504
|
+
```python
|
|
505
|
+
class CeleryConfig(BaseSettings):
|
|
506
|
+
# Redis Broker 配置
|
|
507
|
+
CELERY_BROKER_REDIS_HOST: str = Field(default="localhost", ...)
|
|
508
|
+
CELERY_BROKER_REDIS_PORT: PositiveInt = Field(default=6379, ...)
|
|
509
|
+
CELERY_BROKER_REDIS_USERNAME: Optional[str] = Field(default=None, ...)
|
|
510
|
+
CELERY_BROKER_REDIS_PASSWORD: Optional[str] = Field(default=None, ...)
|
|
511
|
+
CELERY_BROKER_REDIS_DB: NonNegativeInt = Field(default=0, ...)
|
|
512
|
+
|
|
513
|
+
# Result Backend 配置
|
|
514
|
+
CELERY_RESULT_BACKEND_TYPE: str = Field(default="redis", ...) # 'redis' 或 'database'
|
|
515
|
+
|
|
516
|
+
# Redis Backend 配置(当 CELERY_RESULT_BACKEND_TYPE='redis' 时使用)
|
|
517
|
+
CELERY_RESULT_BACKEND_REDIS_HOST: str = Field(default="localhost", ...)
|
|
518
|
+
CELERY_RESULT_BACKEND_REDIS_PORT: PositiveInt = Field(default=6379, ...)
|
|
519
|
+
CELERY_RESULT_BACKEND_REDIS_USERNAME: Optional[str] = Field(default=None, ...)
|
|
520
|
+
CELERY_RESULT_BACKEND_REDIS_PASSWORD: Optional[str] = Field(default=None, ...)
|
|
521
|
+
CELERY_RESULT_BACKEND_REDIS_DB: NonNegativeInt = Field(default=1, ...)
|
|
522
|
+
|
|
523
|
+
# Database Backend 配置(当 CELERY_RESULT_BACKEND_TYPE='database' 时使用)
|
|
524
|
+
CELERY_RESULT_BACKEND_DATABASE_URI_SCHEME: str = Field(default="mysql", ...)
|
|
525
|
+
|
|
526
|
+
# Celery 任务配置
|
|
527
|
+
CELERY_TASK_SERIALIZER: str = Field(default="json", ...)
|
|
528
|
+
CELERY_RESULT_SERIALIZER: str = Field(default="json", ...)
|
|
529
|
+
CELERY_ACCEPT_CONTENT: list[str] = Field(default=["json"], ...)
|
|
530
|
+
CELERY_TIMEZONE: str = Field(default="UTC", ...)
|
|
531
|
+
CELERY_ENABLE_UTC: bool = Field(default=True, ...)
|
|
532
|
+
CELERY_TASK_TRACK_STARTED: bool = Field(default=True, ...)
|
|
533
|
+
CELERY_TASK_TIME_LIMIT: Optional[NonNegativeInt] = Field(default=None, ...)
|
|
534
|
+
CELERY_TASK_SOFT_TIME_LIMIT: Optional[NonNegativeInt] = Field(default=None, ...)
|
|
535
|
+
CELERY_TASK_IGNORE_RESULT: bool = Field(default=False, ...)
|
|
536
|
+
CELERY_RESULT_EXPIRES: Optional[NonNegativeInt] = Field(default=None, ...)
|
|
537
|
+
|
|
538
|
+
# 数据库配置(用于 worker 和结果存储)
|
|
539
|
+
DB_HOST: str = Field(default="localhost", ...)
|
|
540
|
+
DB_PORT: PositiveInt = Field(default=5432, ...)
|
|
541
|
+
DB_USERNAME: str = Field(default="", ...)
|
|
542
|
+
DB_PASSWORD: str = Field(default="", ...)
|
|
543
|
+
DB_DATABASE: str = Field(default="tomskitdb", ...)
|
|
544
|
+
# ... 更多数据库配置项
|
|
545
|
+
|
|
546
|
+
# Redis 配置(用于 worker)
|
|
547
|
+
REDIS_HOST: str = Field(default="localhost", ...)
|
|
548
|
+
REDIS_PORT: PositiveInt = Field(default=6379, ...)
|
|
549
|
+
# ... 更多 Redis 配置项
|
|
550
|
+
|
|
551
|
+
@computed_field
|
|
552
|
+
@property
|
|
553
|
+
def CELERY_BROKER_URL(self) -> str: ...
|
|
554
|
+
|
|
555
|
+
@computed_field
|
|
556
|
+
@property
|
|
557
|
+
def CELERY_RESULT_BACKEND(self) -> str: ...
|
|
558
|
+
|
|
559
|
+
@computed_field
|
|
560
|
+
@property
|
|
561
|
+
def SQLALCHEMY_DATABASE_URI(self) -> str: ...
|
|
562
|
+
|
|
563
|
+
def get_celery_config_dict(self) -> dict[str, Any]: ...
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
**功能特性:**
|
|
567
|
+
- 支持 Redis 作为 broker 和 backend
|
|
568
|
+
- 支持数据库作为结果后端(将结果存储到数据库)
|
|
569
|
+
- 自动生成 broker 和 backend URL
|
|
570
|
+
- 提供完整的 Celery 配置字典
|
|
571
|
+
- 支持通过环境变量配置
|
|
572
|
+
|
|
573
|
+
**使用示例:**
|
|
574
|
+
|
|
575
|
+
**使用 Redis 作为结果后端:**
|
|
576
|
+
```python
|
|
577
|
+
from tomskit.celery import AsyncCelery, CeleryConfig
|
|
578
|
+
|
|
579
|
+
# 创建配置
|
|
580
|
+
config = CeleryConfig(
|
|
581
|
+
# Redis Broker
|
|
582
|
+
CELERY_BROKER_REDIS_HOST='localhost',
|
|
583
|
+
CELERY_BROKER_REDIS_PORT=6379,
|
|
584
|
+
CELERY_BROKER_REDIS_DB=0,
|
|
585
|
+
|
|
586
|
+
# Redis Backend
|
|
587
|
+
CELERY_RESULT_BACKEND_TYPE='redis',
|
|
588
|
+
CELERY_RESULT_BACKEND_REDIS_HOST='localhost',
|
|
589
|
+
CELERY_RESULT_BACKEND_REDIS_PORT=6379,
|
|
590
|
+
CELERY_RESULT_BACKEND_REDIS_DB=1,
|
|
591
|
+
|
|
592
|
+
# 数据库配置(用于 worker)
|
|
593
|
+
DB_USERNAME='user',
|
|
594
|
+
DB_PASSWORD='password',
|
|
595
|
+
DB_HOST='localhost',
|
|
596
|
+
DB_PORT=3306,
|
|
597
|
+
DB_DATABASE='mydb',
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
# 创建 Celery 应用
|
|
601
|
+
celery_app = AsyncCelery(
|
|
602
|
+
'myapp',
|
|
603
|
+
broker=config.CELERY_BROKER_URL,
|
|
604
|
+
backend=config.CELERY_RESULT_BACKEND
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
# 应用配置
|
|
608
|
+
celery_app.from_mapping(config.get_celery_config_dict())
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
**使用数据库作为结果后端:**
|
|
612
|
+
```python
|
|
613
|
+
from tomskit.celery import AsyncCelery, CeleryConfig
|
|
614
|
+
|
|
615
|
+
# 创建配置
|
|
616
|
+
config = CeleryConfig(
|
|
617
|
+
# Redis Broker
|
|
618
|
+
CELERY_BROKER_REDIS_HOST='localhost',
|
|
619
|
+
CELERY_BROKER_REDIS_PORT=6379,
|
|
620
|
+
CELERY_BROKER_REDIS_DB=0,
|
|
621
|
+
|
|
622
|
+
# Database Backend(将结果存储到数据库)
|
|
623
|
+
CELERY_RESULT_BACKEND_TYPE='database',
|
|
624
|
+
CELERY_RESULT_BACKEND_DATABASE_URI_SCHEME='mysql',
|
|
625
|
+
|
|
626
|
+
# 数据库配置
|
|
627
|
+
DB_USERNAME='user',
|
|
628
|
+
DB_PASSWORD='password',
|
|
629
|
+
DB_HOST='localhost',
|
|
630
|
+
DB_PORT=3306,
|
|
631
|
+
DB_DATABASE='mydb',
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
# 创建 Celery 应用
|
|
635
|
+
celery_app = AsyncCelery(
|
|
636
|
+
'myapp',
|
|
637
|
+
broker=config.CELERY_BROKER_URL,
|
|
638
|
+
backend=config.CELERY_RESULT_BACKEND # 自动生成 db+mysql://...
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
# 应用配置
|
|
642
|
+
celery_app.from_mapping(config.get_celery_config_dict())
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
**使用环境变量配置:**
|
|
646
|
+
```python
|
|
647
|
+
from tomskit.celery import AsyncCelery, CeleryConfig
|
|
648
|
+
|
|
649
|
+
# 从环境变量加载配置
|
|
650
|
+
config = CeleryConfig()
|
|
651
|
+
|
|
652
|
+
# 创建 Celery 应用
|
|
653
|
+
celery_app = AsyncCelery(
|
|
654
|
+
'myapp',
|
|
655
|
+
broker=config.CELERY_BROKER_URL,
|
|
656
|
+
backend=config.CELERY_RESULT_BACKEND
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
# 应用配置
|
|
660
|
+
celery_app.from_mapping(config.get_celery_config_dict())
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
**配置属性说明:**
|
|
664
|
+
|
|
665
|
+
**Redis Broker 配置:**
|
|
666
|
+
- `CELERY_BROKER_REDIS_HOST`: Redis broker 主机地址
|
|
667
|
+
- `CELERY_BROKER_REDIS_PORT`: Redis broker 端口
|
|
668
|
+
- `CELERY_BROKER_REDIS_USERNAME`: Redis broker 用户名(可选)
|
|
669
|
+
- `CELERY_BROKER_REDIS_PASSWORD`: Redis broker 密码(可选)
|
|
670
|
+
- `CELERY_BROKER_REDIS_DB`: Redis broker 数据库编号
|
|
671
|
+
|
|
672
|
+
**Result Backend 配置:**
|
|
673
|
+
- `CELERY_RESULT_BACKEND_TYPE`: 结果后端类型,`'redis'` 或 `'database'`
|
|
674
|
+
- Redis Backend 相关配置(当 `CELERY_RESULT_BACKEND_TYPE='redis'` 时)
|
|
675
|
+
- Database Backend 相关配置(当 `CELERY_RESULT_BACKEND_TYPE='database'` 时)
|
|
676
|
+
|
|
677
|
+
**Celery 任务配置:**
|
|
678
|
+
- `CELERY_TASK_SERIALIZER`: 任务序列化格式
|
|
679
|
+
- `CELERY_RESULT_SERIALIZER`: 结果序列化格式
|
|
680
|
+
- `CELERY_ACCEPT_CONTENT`: 接受的内容类型
|
|
681
|
+
- `CELERY_TIMEZONE`: 时区设置
|
|
682
|
+
- `CELERY_ENABLE_UTC`: 是否启用 UTC
|
|
683
|
+
- `CELERY_TASK_TRACK_STARTED`: 是否跟踪任务开始
|
|
684
|
+
- `CELERY_TASK_TIME_LIMIT`: 任务硬时间限制(秒)
|
|
685
|
+
- `CELERY_TASK_SOFT_TIME_LIMIT`: 任务软时间限制(秒)
|
|
686
|
+
- `CELERY_TASK_IGNORE_RESULT`: 是否忽略任务结果
|
|
687
|
+
- `CELERY_RESULT_EXPIRES`: 结果过期时间(秒)
|
|
688
|
+
|
|
689
|
+
**注意事项:**
|
|
690
|
+
1. 使用数据库作为结果后端时,Celery 会自动创建 `celery_taskmeta` 表来存储任务结果
|
|
691
|
+
2. 确保数据库用户有创建表的权限
|
|
692
|
+
3. Redis broker 和 backend 可以使用不同的 Redis 实例和数据库
|
|
693
|
+
4. 所有配置项都支持通过环境变量设置
|