async-task-kit 0.1.0__tar.gz
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.
- async_task_kit-0.1.0/LICENSE +21 -0
- async_task_kit-0.1.0/PKG-INFO +186 -0
- async_task_kit-0.1.0/README.md +171 -0
- async_task_kit-0.1.0/async_task_kit/__init__.py +19 -0
- async_task_kit-0.1.0/async_task_kit.egg-info/PKG-INFO +186 -0
- async_task_kit-0.1.0/async_task_kit.egg-info/SOURCES.txt +9 -0
- async_task_kit-0.1.0/async_task_kit.egg-info/dependency_links.txt +1 -0
- async_task_kit-0.1.0/async_task_kit.egg-info/requires.txt +2 -0
- async_task_kit-0.1.0/async_task_kit.egg-info/top_level.txt +1 -0
- async_task_kit-0.1.0/pyproject.toml +25 -0
- async_task_kit-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Your Name
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: async-task-kit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A powerful async task processing kit based on RabbitMQ with Coroutine, Thread, and Process support.
|
|
5
|
+
Author-email: realwrtoff <realwrtoff@gmail.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: aio-pika>=9.4.0
|
|
13
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# async-task-kit
|
|
17
|
+
|
|
18
|
+
A powerful async task processing kit based on RabbitMQ with Coroutine, Thread, and Process support.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install async-task-kit
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- **RabbitMQ Client**: Robust connection pooling and delay/dead-letter queue support.
|
|
29
|
+
- **Multiple Consumer Models**: Support for Coroutine (asyncio), Thread, and Process consumers depending on your workload (I/O-bound vs CPU-bound).
|
|
30
|
+
- **Extensible Processor**: Easily define your task logic by inheriting `TaskProcessor`.
|
|
31
|
+
- **Built-in Logger & EnvLoader**: Useful utilities for production-ready applications.
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### 1. Configuration (`.env`)
|
|
36
|
+
|
|
37
|
+
Use the built-in `EnvLoader` to manage your environment variables. Create a `.env` file:
|
|
38
|
+
|
|
39
|
+
```env
|
|
40
|
+
RABBITMQ_URL=amqp://guest:guest@localhost/
|
|
41
|
+
TASK_IDS=demo_task
|
|
42
|
+
|
|
43
|
+
# You can also configure specific task settings using the {TASK_ID}_ prefix
|
|
44
|
+
DEMO_TASK_QUEUE_NAME=my_demo_queue
|
|
45
|
+
DEMO_TASK_CONCURRENCY=3
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Define your Task Processor (`demo_processor.py`)
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import logging
|
|
52
|
+
from async_task_kit import TaskProcessor
|
|
53
|
+
|
|
54
|
+
logger = logging.getLogger(__name__)
|
|
55
|
+
|
|
56
|
+
class DemoProcessor(TaskProcessor):
|
|
57
|
+
async def process(self, task: dict):
|
|
58
|
+
logger.info(f"Processing task: {task}")
|
|
59
|
+
# Return any truthy value (e.g., dict, object, True) for success and pass to callback.
|
|
60
|
+
# Return None or False to trigger retry.
|
|
61
|
+
return {"status": "ok", "processed_data": task}
|
|
62
|
+
|
|
63
|
+
async def callback(self, task: dict, result: any):
|
|
64
|
+
logger.info(f"Task completed with result: {result}")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 3. Main Consumer Application (`main.py`)
|
|
68
|
+
|
|
69
|
+
A production-ready setup with signal handling for graceful shutdown.
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import asyncio
|
|
73
|
+
import logging
|
|
74
|
+
import signal
|
|
75
|
+
from typing import List, Type
|
|
76
|
+
|
|
77
|
+
from demo_processor import DemoProcessor
|
|
78
|
+
|
|
79
|
+
# -------------- 只需要改这一行来切换并发模型 --------------
|
|
80
|
+
from async_task_kit import CoroutineConsumer as Consumer
|
|
81
|
+
# from async_task_kit import ThreadConsumer as Consumer
|
|
82
|
+
# from async_task_kit import ProcessConsumer as Consumer
|
|
83
|
+
# --------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
from async_task_kit import TaskProcessor, EnvLoader, setup_logger
|
|
86
|
+
|
|
87
|
+
# Initialize logger
|
|
88
|
+
setup_logger()
|
|
89
|
+
logger = logging.getLogger(__name__)
|
|
90
|
+
|
|
91
|
+
# Register your processors
|
|
92
|
+
TASK_REGISTRY: dict[str, Type[TaskProcessor]] = {
|
|
93
|
+
"demo_task": DemoProcessor,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
consumers: List[Consumer] = []
|
|
97
|
+
|
|
98
|
+
async def run_all_consumers(amqp_url: str, task_ids: List[str]):
|
|
99
|
+
tasks = []
|
|
100
|
+
for task_id in task_ids:
|
|
101
|
+
if task_id not in TASK_REGISTRY:
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
processor_cls = TASK_REGISTRY[task_id]
|
|
105
|
+
processor = processor_cls(task_id=task_id)
|
|
106
|
+
|
|
107
|
+
consumer = Consumer(
|
|
108
|
+
amqp_url=amqp_url,
|
|
109
|
+
queue_name=processor.queue_name,
|
|
110
|
+
processor=processor,
|
|
111
|
+
concurrency=processor.concurrency,
|
|
112
|
+
)
|
|
113
|
+
consumers.append(consumer)
|
|
114
|
+
tasks.append(consumer.start())
|
|
115
|
+
|
|
116
|
+
logger.info(f"🚀 启动任务 [{task_id}] | queue={processor.queue_name} | 并发={processor.concurrency}")
|
|
117
|
+
|
|
118
|
+
await asyncio.gather(*tasks)
|
|
119
|
+
|
|
120
|
+
async def shutdown_all():
|
|
121
|
+
logger.info("🛑 优雅关闭所有消费者...")
|
|
122
|
+
for consumer in consumers:
|
|
123
|
+
await consumer.stop()
|
|
124
|
+
logger.info("✅ 所有消费者已关闭")
|
|
125
|
+
|
|
126
|
+
def handle_exit_signal(*args, **kwargs):
|
|
127
|
+
asyncio.create_task(shutdown_all())
|
|
128
|
+
|
|
129
|
+
async def main():
|
|
130
|
+
env = EnvLoader()
|
|
131
|
+
amqp_url = env.get("RABBITMQ_URL")
|
|
132
|
+
task_ids_str = env.get("TASK_IDS", "").strip()
|
|
133
|
+
|
|
134
|
+
if not task_ids_str:
|
|
135
|
+
logger.warning("⚠️ 未配置 TASK_IDS")
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
task_ids = [t.strip() for t in task_ids_str.split(",") if t.strip()]
|
|
139
|
+
valid_tasks = [t for t in task_ids if t in TASK_REGISTRY]
|
|
140
|
+
|
|
141
|
+
loop = asyncio.get_running_loop()
|
|
142
|
+
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
143
|
+
loop.add_signal_handler(sig, handle_exit_signal)
|
|
144
|
+
|
|
145
|
+
await run_all_consumers(amqp_url, valid_tasks)
|
|
146
|
+
|
|
147
|
+
if __name__ == "__main__":
|
|
148
|
+
try:
|
|
149
|
+
asyncio.run(main())
|
|
150
|
+
except KeyboardInterrupt:
|
|
151
|
+
logger.info("👋 服务已安全退出")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 4. Publishing Tasks (`publisher.py`)
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
import asyncio
|
|
158
|
+
import logging
|
|
159
|
+
from async_task_kit import RabbitMQ, setup_logger
|
|
160
|
+
|
|
161
|
+
setup_logger()
|
|
162
|
+
logger = logging.getLogger(__name__)
|
|
163
|
+
|
|
164
|
+
async def publish():
|
|
165
|
+
rmq = RabbitMQ("amqp://guest:guest@localhost/")
|
|
166
|
+
await rmq.init()
|
|
167
|
+
|
|
168
|
+
await rmq.push("my_demo_queue", {"message": "Hello from async-task-kit!"})
|
|
169
|
+
logger.info("Task published successfully.")
|
|
170
|
+
|
|
171
|
+
await rmq.close()
|
|
172
|
+
|
|
173
|
+
if __name__ == "__main__":
|
|
174
|
+
asyncio.run(publish())
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
|
180
|
+
|
|
181
|
+
## Contact & Support
|
|
182
|
+
|
|
183
|
+
If you have any questions, suggestions, or need help with this library, feel free to reach out!
|
|
184
|
+
|
|
185
|
+
**WeChat (微信)**: `realwrtoff`
|
|
186
|
+
**Email**: `realwrtoff@gmail.com`
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# async-task-kit
|
|
2
|
+
|
|
3
|
+
A powerful async task processing kit based on RabbitMQ with Coroutine, Thread, and Process support.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install async-task-kit
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **RabbitMQ Client**: Robust connection pooling and delay/dead-letter queue support.
|
|
14
|
+
- **Multiple Consumer Models**: Support for Coroutine (asyncio), Thread, and Process consumers depending on your workload (I/O-bound vs CPU-bound).
|
|
15
|
+
- **Extensible Processor**: Easily define your task logic by inheriting `TaskProcessor`.
|
|
16
|
+
- **Built-in Logger & EnvLoader**: Useful utilities for production-ready applications.
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### 1. Configuration (`.env`)
|
|
21
|
+
|
|
22
|
+
Use the built-in `EnvLoader` to manage your environment variables. Create a `.env` file:
|
|
23
|
+
|
|
24
|
+
```env
|
|
25
|
+
RABBITMQ_URL=amqp://guest:guest@localhost/
|
|
26
|
+
TASK_IDS=demo_task
|
|
27
|
+
|
|
28
|
+
# You can also configure specific task settings using the {TASK_ID}_ prefix
|
|
29
|
+
DEMO_TASK_QUEUE_NAME=my_demo_queue
|
|
30
|
+
DEMO_TASK_CONCURRENCY=3
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Define your Task Processor (`demo_processor.py`)
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import logging
|
|
37
|
+
from async_task_kit import TaskProcessor
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
class DemoProcessor(TaskProcessor):
|
|
42
|
+
async def process(self, task: dict):
|
|
43
|
+
logger.info(f"Processing task: {task}")
|
|
44
|
+
# Return any truthy value (e.g., dict, object, True) for success and pass to callback.
|
|
45
|
+
# Return None or False to trigger retry.
|
|
46
|
+
return {"status": "ok", "processed_data": task}
|
|
47
|
+
|
|
48
|
+
async def callback(self, task: dict, result: any):
|
|
49
|
+
logger.info(f"Task completed with result: {result}")
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. Main Consumer Application (`main.py`)
|
|
53
|
+
|
|
54
|
+
A production-ready setup with signal handling for graceful shutdown.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import asyncio
|
|
58
|
+
import logging
|
|
59
|
+
import signal
|
|
60
|
+
from typing import List, Type
|
|
61
|
+
|
|
62
|
+
from demo_processor import DemoProcessor
|
|
63
|
+
|
|
64
|
+
# -------------- 只需要改这一行来切换并发模型 --------------
|
|
65
|
+
from async_task_kit import CoroutineConsumer as Consumer
|
|
66
|
+
# from async_task_kit import ThreadConsumer as Consumer
|
|
67
|
+
# from async_task_kit import ProcessConsumer as Consumer
|
|
68
|
+
# --------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
from async_task_kit import TaskProcessor, EnvLoader, setup_logger
|
|
71
|
+
|
|
72
|
+
# Initialize logger
|
|
73
|
+
setup_logger()
|
|
74
|
+
logger = logging.getLogger(__name__)
|
|
75
|
+
|
|
76
|
+
# Register your processors
|
|
77
|
+
TASK_REGISTRY: dict[str, Type[TaskProcessor]] = {
|
|
78
|
+
"demo_task": DemoProcessor,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
consumers: List[Consumer] = []
|
|
82
|
+
|
|
83
|
+
async def run_all_consumers(amqp_url: str, task_ids: List[str]):
|
|
84
|
+
tasks = []
|
|
85
|
+
for task_id in task_ids:
|
|
86
|
+
if task_id not in TASK_REGISTRY:
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
processor_cls = TASK_REGISTRY[task_id]
|
|
90
|
+
processor = processor_cls(task_id=task_id)
|
|
91
|
+
|
|
92
|
+
consumer = Consumer(
|
|
93
|
+
amqp_url=amqp_url,
|
|
94
|
+
queue_name=processor.queue_name,
|
|
95
|
+
processor=processor,
|
|
96
|
+
concurrency=processor.concurrency,
|
|
97
|
+
)
|
|
98
|
+
consumers.append(consumer)
|
|
99
|
+
tasks.append(consumer.start())
|
|
100
|
+
|
|
101
|
+
logger.info(f"🚀 启动任务 [{task_id}] | queue={processor.queue_name} | 并发={processor.concurrency}")
|
|
102
|
+
|
|
103
|
+
await asyncio.gather(*tasks)
|
|
104
|
+
|
|
105
|
+
async def shutdown_all():
|
|
106
|
+
logger.info("🛑 优雅关闭所有消费者...")
|
|
107
|
+
for consumer in consumers:
|
|
108
|
+
await consumer.stop()
|
|
109
|
+
logger.info("✅ 所有消费者已关闭")
|
|
110
|
+
|
|
111
|
+
def handle_exit_signal(*args, **kwargs):
|
|
112
|
+
asyncio.create_task(shutdown_all())
|
|
113
|
+
|
|
114
|
+
async def main():
|
|
115
|
+
env = EnvLoader()
|
|
116
|
+
amqp_url = env.get("RABBITMQ_URL")
|
|
117
|
+
task_ids_str = env.get("TASK_IDS", "").strip()
|
|
118
|
+
|
|
119
|
+
if not task_ids_str:
|
|
120
|
+
logger.warning("⚠️ 未配置 TASK_IDS")
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
task_ids = [t.strip() for t in task_ids_str.split(",") if t.strip()]
|
|
124
|
+
valid_tasks = [t for t in task_ids if t in TASK_REGISTRY]
|
|
125
|
+
|
|
126
|
+
loop = asyncio.get_running_loop()
|
|
127
|
+
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
128
|
+
loop.add_signal_handler(sig, handle_exit_signal)
|
|
129
|
+
|
|
130
|
+
await run_all_consumers(amqp_url, valid_tasks)
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
try:
|
|
134
|
+
asyncio.run(main())
|
|
135
|
+
except KeyboardInterrupt:
|
|
136
|
+
logger.info("👋 服务已安全退出")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 4. Publishing Tasks (`publisher.py`)
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import asyncio
|
|
143
|
+
import logging
|
|
144
|
+
from async_task_kit import RabbitMQ, setup_logger
|
|
145
|
+
|
|
146
|
+
setup_logger()
|
|
147
|
+
logger = logging.getLogger(__name__)
|
|
148
|
+
|
|
149
|
+
async def publish():
|
|
150
|
+
rmq = RabbitMQ("amqp://guest:guest@localhost/")
|
|
151
|
+
await rmq.init()
|
|
152
|
+
|
|
153
|
+
await rmq.push("my_demo_queue", {"message": "Hello from async-task-kit!"})
|
|
154
|
+
logger.info("Task published successfully.")
|
|
155
|
+
|
|
156
|
+
await rmq.close()
|
|
157
|
+
|
|
158
|
+
if __name__ == "__main__":
|
|
159
|
+
asyncio.run(publish())
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
MIT
|
|
165
|
+
|
|
166
|
+
## Contact & Support
|
|
167
|
+
|
|
168
|
+
If you have any questions, suggestions, or need help with this library, feel free to reach out!
|
|
169
|
+
|
|
170
|
+
**WeChat (微信)**: `realwrtoff`
|
|
171
|
+
**Email**: `realwrtoff@gmail.com`
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .consumer.coroutine import CoroutineConsumer
|
|
2
|
+
from .consumer.thread import ThreadConsumer
|
|
3
|
+
from .consumer.process import ProcessConsumer
|
|
4
|
+
from .consumer.base import BaseConsumer
|
|
5
|
+
from .core.processor import TaskProcessor
|
|
6
|
+
from .core.rabbitmq import RabbitMQ
|
|
7
|
+
from .utils.env_loader import EnvLoader
|
|
8
|
+
from .utils.logger import setup_logger
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"BaseConsumer",
|
|
12
|
+
"CoroutineConsumer",
|
|
13
|
+
"ThreadConsumer",
|
|
14
|
+
"ProcessConsumer",
|
|
15
|
+
"TaskProcessor",
|
|
16
|
+
"RabbitMQ",
|
|
17
|
+
"EnvLoader",
|
|
18
|
+
"setup_logger",
|
|
19
|
+
]
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: async-task-kit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A powerful async task processing kit based on RabbitMQ with Coroutine, Thread, and Process support.
|
|
5
|
+
Author-email: realwrtoff <realwrtoff@gmail.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: aio-pika>=9.4.0
|
|
13
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# async-task-kit
|
|
17
|
+
|
|
18
|
+
A powerful async task processing kit based on RabbitMQ with Coroutine, Thread, and Process support.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install async-task-kit
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- **RabbitMQ Client**: Robust connection pooling and delay/dead-letter queue support.
|
|
29
|
+
- **Multiple Consumer Models**: Support for Coroutine (asyncio), Thread, and Process consumers depending on your workload (I/O-bound vs CPU-bound).
|
|
30
|
+
- **Extensible Processor**: Easily define your task logic by inheriting `TaskProcessor`.
|
|
31
|
+
- **Built-in Logger & EnvLoader**: Useful utilities for production-ready applications.
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### 1. Configuration (`.env`)
|
|
36
|
+
|
|
37
|
+
Use the built-in `EnvLoader` to manage your environment variables. Create a `.env` file:
|
|
38
|
+
|
|
39
|
+
```env
|
|
40
|
+
RABBITMQ_URL=amqp://guest:guest@localhost/
|
|
41
|
+
TASK_IDS=demo_task
|
|
42
|
+
|
|
43
|
+
# You can also configure specific task settings using the {TASK_ID}_ prefix
|
|
44
|
+
DEMO_TASK_QUEUE_NAME=my_demo_queue
|
|
45
|
+
DEMO_TASK_CONCURRENCY=3
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Define your Task Processor (`demo_processor.py`)
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import logging
|
|
52
|
+
from async_task_kit import TaskProcessor
|
|
53
|
+
|
|
54
|
+
logger = logging.getLogger(__name__)
|
|
55
|
+
|
|
56
|
+
class DemoProcessor(TaskProcessor):
|
|
57
|
+
async def process(self, task: dict):
|
|
58
|
+
logger.info(f"Processing task: {task}")
|
|
59
|
+
# Return any truthy value (e.g., dict, object, True) for success and pass to callback.
|
|
60
|
+
# Return None or False to trigger retry.
|
|
61
|
+
return {"status": "ok", "processed_data": task}
|
|
62
|
+
|
|
63
|
+
async def callback(self, task: dict, result: any):
|
|
64
|
+
logger.info(f"Task completed with result: {result}")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 3. Main Consumer Application (`main.py`)
|
|
68
|
+
|
|
69
|
+
A production-ready setup with signal handling for graceful shutdown.
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import asyncio
|
|
73
|
+
import logging
|
|
74
|
+
import signal
|
|
75
|
+
from typing import List, Type
|
|
76
|
+
|
|
77
|
+
from demo_processor import DemoProcessor
|
|
78
|
+
|
|
79
|
+
# -------------- 只需要改这一行来切换并发模型 --------------
|
|
80
|
+
from async_task_kit import CoroutineConsumer as Consumer
|
|
81
|
+
# from async_task_kit import ThreadConsumer as Consumer
|
|
82
|
+
# from async_task_kit import ProcessConsumer as Consumer
|
|
83
|
+
# --------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
from async_task_kit import TaskProcessor, EnvLoader, setup_logger
|
|
86
|
+
|
|
87
|
+
# Initialize logger
|
|
88
|
+
setup_logger()
|
|
89
|
+
logger = logging.getLogger(__name__)
|
|
90
|
+
|
|
91
|
+
# Register your processors
|
|
92
|
+
TASK_REGISTRY: dict[str, Type[TaskProcessor]] = {
|
|
93
|
+
"demo_task": DemoProcessor,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
consumers: List[Consumer] = []
|
|
97
|
+
|
|
98
|
+
async def run_all_consumers(amqp_url: str, task_ids: List[str]):
|
|
99
|
+
tasks = []
|
|
100
|
+
for task_id in task_ids:
|
|
101
|
+
if task_id not in TASK_REGISTRY:
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
processor_cls = TASK_REGISTRY[task_id]
|
|
105
|
+
processor = processor_cls(task_id=task_id)
|
|
106
|
+
|
|
107
|
+
consumer = Consumer(
|
|
108
|
+
amqp_url=amqp_url,
|
|
109
|
+
queue_name=processor.queue_name,
|
|
110
|
+
processor=processor,
|
|
111
|
+
concurrency=processor.concurrency,
|
|
112
|
+
)
|
|
113
|
+
consumers.append(consumer)
|
|
114
|
+
tasks.append(consumer.start())
|
|
115
|
+
|
|
116
|
+
logger.info(f"🚀 启动任务 [{task_id}] | queue={processor.queue_name} | 并发={processor.concurrency}")
|
|
117
|
+
|
|
118
|
+
await asyncio.gather(*tasks)
|
|
119
|
+
|
|
120
|
+
async def shutdown_all():
|
|
121
|
+
logger.info("🛑 优雅关闭所有消费者...")
|
|
122
|
+
for consumer in consumers:
|
|
123
|
+
await consumer.stop()
|
|
124
|
+
logger.info("✅ 所有消费者已关闭")
|
|
125
|
+
|
|
126
|
+
def handle_exit_signal(*args, **kwargs):
|
|
127
|
+
asyncio.create_task(shutdown_all())
|
|
128
|
+
|
|
129
|
+
async def main():
|
|
130
|
+
env = EnvLoader()
|
|
131
|
+
amqp_url = env.get("RABBITMQ_URL")
|
|
132
|
+
task_ids_str = env.get("TASK_IDS", "").strip()
|
|
133
|
+
|
|
134
|
+
if not task_ids_str:
|
|
135
|
+
logger.warning("⚠️ 未配置 TASK_IDS")
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
task_ids = [t.strip() for t in task_ids_str.split(",") if t.strip()]
|
|
139
|
+
valid_tasks = [t for t in task_ids if t in TASK_REGISTRY]
|
|
140
|
+
|
|
141
|
+
loop = asyncio.get_running_loop()
|
|
142
|
+
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
143
|
+
loop.add_signal_handler(sig, handle_exit_signal)
|
|
144
|
+
|
|
145
|
+
await run_all_consumers(amqp_url, valid_tasks)
|
|
146
|
+
|
|
147
|
+
if __name__ == "__main__":
|
|
148
|
+
try:
|
|
149
|
+
asyncio.run(main())
|
|
150
|
+
except KeyboardInterrupt:
|
|
151
|
+
logger.info("👋 服务已安全退出")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 4. Publishing Tasks (`publisher.py`)
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
import asyncio
|
|
158
|
+
import logging
|
|
159
|
+
from async_task_kit import RabbitMQ, setup_logger
|
|
160
|
+
|
|
161
|
+
setup_logger()
|
|
162
|
+
logger = logging.getLogger(__name__)
|
|
163
|
+
|
|
164
|
+
async def publish():
|
|
165
|
+
rmq = RabbitMQ("amqp://guest:guest@localhost/")
|
|
166
|
+
await rmq.init()
|
|
167
|
+
|
|
168
|
+
await rmq.push("my_demo_queue", {"message": "Hello from async-task-kit!"})
|
|
169
|
+
logger.info("Task published successfully.")
|
|
170
|
+
|
|
171
|
+
await rmq.close()
|
|
172
|
+
|
|
173
|
+
if __name__ == "__main__":
|
|
174
|
+
asyncio.run(publish())
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
|
180
|
+
|
|
181
|
+
## Contact & Support
|
|
182
|
+
|
|
183
|
+
If you have any questions, suggestions, or need help with this library, feel free to reach out!
|
|
184
|
+
|
|
185
|
+
**WeChat (微信)**: `realwrtoff`
|
|
186
|
+
**Email**: `realwrtoff@gmail.com`
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
async_task_kit/__init__.py
|
|
5
|
+
async_task_kit.egg-info/PKG-INFO
|
|
6
|
+
async_task_kit.egg-info/SOURCES.txt
|
|
7
|
+
async_task_kit.egg-info/dependency_links.txt
|
|
8
|
+
async_task_kit.egg-info/requires.txt
|
|
9
|
+
async_task_kit.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
async_task_kit
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "async-task-kit"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A powerful async task processing kit based on RabbitMQ with Coroutine, Thread, and Process support."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "realwrtoff", email = "realwrtoff@gmail.com" }
|
|
13
|
+
]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"aio-pika>=9.4.0",
|
|
16
|
+
"python-dotenv>=1.0.0"
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[tool.setuptools]
|
|
25
|
+
packages = ["async_task_kit"]
|