aqworker 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.
- aqworker-0.1.0/PKG-INFO +615 -0
- aqworker-0.1.0/README.md +595 -0
- aqworker-0.1.0/pyproject.toml +91 -0
- aqworker-0.1.0/src/aqworker/__init__.py +18 -0
- aqworker-0.1.0/src/aqworker/__main__.py +0 -0
- aqworker-0.1.0/src/aqworker/cli/__init__.py +3 -0
- aqworker-0.1.0/src/aqworker/cli/cli.py +180 -0
- aqworker-0.1.0/src/aqworker/cli/commands.py +67 -0
- aqworker-0.1.0/src/aqworker/cli/loader.py +97 -0
- aqworker-0.1.0/src/aqworker/constants.py +31 -0
- aqworker-0.1.0/src/aqworker/core.py +264 -0
- aqworker-0.1.0/src/aqworker/handler/__init__.py +7 -0
- aqworker-0.1.0/src/aqworker/handler/base.py +24 -0
- aqworker-0.1.0/src/aqworker/handler/registry.py +32 -0
- aqworker-0.1.0/src/aqworker/job/__init__.py +25 -0
- aqworker-0.1.0/src/aqworker/job/models.py +101 -0
- aqworker-0.1.0/src/aqworker/job/queue.py +588 -0
- aqworker-0.1.0/src/aqworker/job/service.py +365 -0
- aqworker-0.1.0/src/aqworker/logger.py +64 -0
- aqworker-0.1.0/src/aqworker/worker/__init__.py +3 -0
- aqworker-0.1.0/src/aqworker/worker/base.py +386 -0
- aqworker-0.1.0/src/aqworker/worker/dispatcher.py +35 -0
- aqworker-0.1.0/src/aqworker/worker/registry.py +88 -0
aqworker-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: aqworker
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async Redis-backed job runner for Python apps. AQWorker orchestrates workers, handlers, and queues with auto-discovery, retries, CLI tooling, and FastAPI-friendly async processing while staying framework agnostic.
|
|
5
|
+
Keywords:
|
|
6
|
+
Author: Do Quoc Vuong
|
|
7
|
+
Author-email: Do Quoc Vuong <vuongtlt13@gmail.com>
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Dist: pydantic>=2.12.4
|
|
12
|
+
Requires-Dist: pytest-asyncio>=1.3.0
|
|
13
|
+
Requires-Dist: redis>=7.1.0
|
|
14
|
+
Requires-Dist: typer>=0.20.0
|
|
15
|
+
Requires-Python: >=3.10, <4.0
|
|
16
|
+
Project-URL: Homepage, https://github.com/vuongtlt13/aqworker
|
|
17
|
+
Project-URL: Issues, https://github.com/vuongtlt13/aqworker/issues
|
|
18
|
+
Project-URL: License, https://opensource.org/licenses/MIT
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# AQWorker - Async Queue Worker
|
|
22
|
+
|
|
23
|
+
## Overview
|
|
24
|
+
|
|
25
|
+
AQWorker is a Redis-based background job processing system for Python applications. It provides a simple, efficient way to handle asynchronous tasks with support for multiple worker types, queue isolation, and job status tracking. While it works seamlessly with FastAPI, it's a standalone package that can be used with any Python application.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- **Framework Agnostic**: Works with FastAPI, Django, Flask, or any Python application
|
|
30
|
+
- **Async/Await Support**: Built with async/await for modern Python applications
|
|
31
|
+
- **Redis-based**: Uses Redis for reliable job queue management
|
|
32
|
+
- **Worker Registry**: Centralized worker management with automatic discovery
|
|
33
|
+
- **Handler Registry**: Organized handler system for job processing
|
|
34
|
+
- **Handler Auto-Discovery**: Import handler packages automatically via `include_packages`
|
|
35
|
+
- **CLI Tools**: Command-line interface for managing workers and jobs
|
|
36
|
+
- **Queue Isolation**: Separate queues for different job types
|
|
37
|
+
- **Job Status Tracking**: Track job lifecycle (PENDING, PROCESSING, COMPLETED, FAILED, CANCELLED)
|
|
38
|
+
- **Automatic Retries**: Built-in retry mechanism with configurable delays
|
|
39
|
+
|
|
40
|
+
## Architecture
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
┌─────────────┐ get_next_job() ┌─────────────┐ dequeue() ┌─────────────┐
|
|
44
|
+
│ Worker │ ────────────────────►│ JobService │ ──────────────► │ JobQueue │
|
|
45
|
+
│ │ │ │ │ │
|
|
46
|
+
│ │ ◄────────────────────│ │ ◄────────────── │ │
|
|
47
|
+
└─────────────┘ complete_job() └─────────────┘ complete_job()└─────────────┘
|
|
48
|
+
|
|
49
|
+
┌─────────────┐ enqueue_job() ┌─────────────┐ enqueue() ┌─────────────┐
|
|
50
|
+
│ Application │ ────────────────────►│ AQWorker │ ──────────────► │ JobQueue │
|
|
51
|
+
│ (FastAPI/ │ │ │ │ │
|
|
52
|
+
│ Django/ │ │ - Worker │ │ │
|
|
53
|
+
│ Flask/ │ │ Registry │ │ │
|
|
54
|
+
│ Script) │ │ - Handler │ │ │
|
|
55
|
+
│ │ │ Registry │ │ │
|
|
56
|
+
└─────────────┘ │ - Job │ └─────────────┘
|
|
57
|
+
│ Service │
|
|
58
|
+
└─────────────┘
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Components
|
|
62
|
+
|
|
63
|
+
### 1. AQWorker (`core.py`)
|
|
64
|
+
- **Purpose**: Main orchestrator class that manages workers, handlers, and job service
|
|
65
|
+
- **Features**:
|
|
66
|
+
- Worker and handler registration
|
|
67
|
+
- Worker instance creation
|
|
68
|
+
- Job service integration
|
|
69
|
+
- Centralized configuration
|
|
70
|
+
|
|
71
|
+
### 2. WorkerRegistry (`worker/registry.py`)
|
|
72
|
+
- **Purpose**: Manages worker class registrations
|
|
73
|
+
- **Features**:
|
|
74
|
+
- Register worker classes with names
|
|
75
|
+
- List available workers
|
|
76
|
+
- Get worker definitions
|
|
77
|
+
|
|
78
|
+
### 3. HandlerRegistry (`handler/registry.py`)
|
|
79
|
+
- **Purpose**: Manages handler class registrations
|
|
80
|
+
- **Features**:
|
|
81
|
+
- Register handler classes
|
|
82
|
+
- Lookup handlers by name
|
|
83
|
+
- Snapshot of all registered handlers
|
|
84
|
+
|
|
85
|
+
### 4. JobService (`job/service.py`)
|
|
86
|
+
- **Purpose**: High-level service layer for job operations
|
|
87
|
+
- **Features**:
|
|
88
|
+
- Enqueue jobs
|
|
89
|
+
- Dequeue jobs for workers
|
|
90
|
+
- Get job status
|
|
91
|
+
- Queue statistics
|
|
92
|
+
- Job lifecycle management
|
|
93
|
+
|
|
94
|
+
### 5. JobQueue (`job/queue.py`)
|
|
95
|
+
- **Purpose**: Low-level Redis operations for job management
|
|
96
|
+
- **Features**:
|
|
97
|
+
- FIFO queue processing
|
|
98
|
+
- Async/await support
|
|
99
|
+
- Job status tracking
|
|
100
|
+
- Redis operations: enqueue, dequeue, complete_job
|
|
101
|
+
|
|
102
|
+
### 6. BaseWorker (`worker/base.py`)
|
|
103
|
+
- **Purpose**: Base class for all workers
|
|
104
|
+
- **Features**:
|
|
105
|
+
- Automatic job polling
|
|
106
|
+
- Concurrent job processing
|
|
107
|
+
- Error handling and retries
|
|
108
|
+
- Health monitoring
|
|
109
|
+
|
|
110
|
+
### 7. BaseHandler (`handler/base.py`)
|
|
111
|
+
- **Purpose**: Base class for all job handlers
|
|
112
|
+
- **Features**:
|
|
113
|
+
- Async/sync handler support
|
|
114
|
+
- Standardized job processing interface
|
|
115
|
+
|
|
116
|
+
## Installation
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pip install aqworker
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Or install from source:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
git clone <repository>
|
|
126
|
+
cd aqworker
|
|
127
|
+
pip install -e .
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Quick Start
|
|
131
|
+
|
|
132
|
+
### 1. Define Handlers
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
# handlers.py
|
|
136
|
+
from aqworker import BaseHandler
|
|
137
|
+
|
|
138
|
+
class EmailHandler(BaseHandler):
|
|
139
|
+
name = "email"
|
|
140
|
+
|
|
141
|
+
async def handle(self, data: dict) -> bool:
|
|
142
|
+
recipient = data.get("recipient")
|
|
143
|
+
subject = data.get("subject")
|
|
144
|
+
body = data.get("body")
|
|
145
|
+
|
|
146
|
+
# Send email logic here
|
|
147
|
+
print(f"Sending email to {recipient}: {subject}")
|
|
148
|
+
return True
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
You can still use the `@aq_worker.handler` decorator for function-based handlers—just make
|
|
152
|
+
sure the module containing the decorator is imported so the handler gets registered.
|
|
153
|
+
|
|
154
|
+
### 2. Define Workers
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
# workers.py
|
|
158
|
+
from aqworker import BaseWorker, WorkerConfig
|
|
159
|
+
|
|
160
|
+
class EmailWorker(BaseWorker):
|
|
161
|
+
worker_name = "email"
|
|
162
|
+
worker_config = WorkerConfig(
|
|
163
|
+
queue_names=["emails"],
|
|
164
|
+
max_concurrent_jobs=3,
|
|
165
|
+
poll_interval=0.5,
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 3. Initialize AQWorker
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
# aq_worker.py
|
|
173
|
+
from aqworker import AQWorker
|
|
174
|
+
from aqworker.job.service import JobService
|
|
175
|
+
from workers import EmailWorker
|
|
176
|
+
from handlers import EmailHandler # optional when using include_packages
|
|
177
|
+
|
|
178
|
+
# Create JobService
|
|
179
|
+
job_service = JobService()
|
|
180
|
+
|
|
181
|
+
# Initialize AQWorker and auto-import handlers package
|
|
182
|
+
aq_worker = AQWorker(include_packages=["handlers"])
|
|
183
|
+
|
|
184
|
+
# Register workers and handlers
|
|
185
|
+
aq_worker.register_worker(EmailWorker)
|
|
186
|
+
# Handlers inside "handlers" will be discovered automatically, but you can still
|
|
187
|
+
# register manually if desired:
|
|
188
|
+
# aq_worker.register_handler(EmailHandler)
|
|
189
|
+
|
|
190
|
+
# Connect job service
|
|
191
|
+
aq_worker.listen(job_service)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### Automatic handler discovery
|
|
195
|
+
|
|
196
|
+
If your handlers are spread across multiple modules, you can tell `AQWorker` to
|
|
197
|
+
import them automatically by passing `include_packages`. Every module inside
|
|
198
|
+
those packages will be imported once at startup, so any `@aq_worker.handler`
|
|
199
|
+
decorators (or subclasses of `BaseHandler`) run and register themselves:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
aq_worker = AQWorker(include_packages=[
|
|
203
|
+
"my_project.workers",
|
|
204
|
+
"examples.simple.aq_worker",
|
|
205
|
+
])
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
You can also call `aq_worker.autodiscover_handlers([...])` later if you need to
|
|
209
|
+
load additional packages dynamically. After discovery completes AQWorker logs the
|
|
210
|
+
full list of available handlers so you can confirm everything loaded correctly.
|
|
211
|
+
|
|
212
|
+
### 4. Enqueue Jobs
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
# In your application
|
|
216
|
+
from worker import aq_worker
|
|
217
|
+
|
|
218
|
+
# Enqueue a job
|
|
219
|
+
job = await aq_worker.job_service.enqueue_job(
|
|
220
|
+
queue_name="emails",
|
|
221
|
+
handler="email",
|
|
222
|
+
data={
|
|
223
|
+
"recipient": "user@example.com",
|
|
224
|
+
"subject": "Welcome!",
|
|
225
|
+
"body": "Welcome to our service!"
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 5. Run Workers
|
|
231
|
+
|
|
232
|
+
**Using CLI:**
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
# Option 1: Pass file path
|
|
236
|
+
aqworker start email aq_worker.py
|
|
237
|
+
|
|
238
|
+
# Option 2: Use environment variable
|
|
239
|
+
export AQWORKER_FILE=aq_worker.py
|
|
240
|
+
aqworker start email
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Using Python:**
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
import asyncio
|
|
247
|
+
from worker import aq_worker
|
|
248
|
+
|
|
249
|
+
async def main():
|
|
250
|
+
worker = aq_worker.create_worker("email")
|
|
251
|
+
await worker.run()
|
|
252
|
+
|
|
253
|
+
asyncio.run(main())
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Usage Examples
|
|
257
|
+
|
|
258
|
+
### Standalone Python Application
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
# main.py
|
|
262
|
+
import asyncio
|
|
263
|
+
from worker import aq_worker
|
|
264
|
+
|
|
265
|
+
async def main():
|
|
266
|
+
# Enqueue jobs
|
|
267
|
+
for i in range(10):
|
|
268
|
+
job = await aq_worker.job_service.enqueue_job(
|
|
269
|
+
queue_name="emails",
|
|
270
|
+
handler="email",
|
|
271
|
+
data={"recipient": f"user{i}@example.com", "subject": f"Email {i}"}
|
|
272
|
+
)
|
|
273
|
+
print(f"Enqueued job: {job.id}")
|
|
274
|
+
|
|
275
|
+
# Start aq_worker
|
|
276
|
+
worker = aq_worker.create_worker("email")
|
|
277
|
+
await worker.run()
|
|
278
|
+
|
|
279
|
+
asyncio.run(main())
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### FastAPI Integration
|
|
283
|
+
|
|
284
|
+
The repo ships with a complete FastAPI + AQWorker demo under `examples/simple_fastapi/`.
|
|
285
|
+
Key files:
|
|
286
|
+
|
|
287
|
+
- `worker.py` – configures `AQWorker`, registers the example workers, and wires up the
|
|
288
|
+
shared `JobService`.
|
|
289
|
+
- `main.py` – FastAPI app that exposes `/jobs/email`, `/jobs/notification`, queue stats,
|
|
290
|
+
and discovery endpoints.
|
|
291
|
+
- `client.py` – small CLI client that calls the HTTP endpoints so you can watch jobs
|
|
292
|
+
flow through the queues.
|
|
293
|
+
|
|
294
|
+
Minimal excerpt of the worker wiring:
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
# examples/simple_fastapi/worker.py
|
|
298
|
+
from aqworker import AQWorker
|
|
299
|
+
from workers import EmailWorker, NotificationWorker
|
|
300
|
+
from job_service import job_service
|
|
301
|
+
|
|
302
|
+
aq_worker = AQWorker(include_packages=["handlers"])
|
|
303
|
+
aq_worker.register_worker(EmailWorker)
|
|
304
|
+
aq_worker.register_worker(NotificationWorker)
|
|
305
|
+
aq_worker.listen(job_service)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
To run the demo API and fire sample requests:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
cd examples/simple_fastapi
|
|
312
|
+
uv run python main.py # start FastAPI
|
|
313
|
+
|
|
314
|
+
# in another shell: send a job via the bundled client
|
|
315
|
+
uv run python client.py --recipient user@example.com --subject Test --body "Hello!"
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Django Integration
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
# views.py
|
|
322
|
+
from django.http import JsonResponse
|
|
323
|
+
from worker import aq_worker
|
|
324
|
+
import asyncio
|
|
325
|
+
|
|
326
|
+
async def send_email_view(request):
|
|
327
|
+
job = await aq_worker.job_service.enqueue_job(
|
|
328
|
+
queue_name="emails",
|
|
329
|
+
handler="email",
|
|
330
|
+
data={
|
|
331
|
+
"recipient": request.POST.get("recipient"),
|
|
332
|
+
"subject": request.POST.get("subject"),
|
|
333
|
+
"body": request.POST.get("body")
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
return JsonResponse({"job_id": job.id})
|
|
337
|
+
|
|
338
|
+
# Use asyncio.run or Django's async support
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## CLI Commands
|
|
342
|
+
|
|
343
|
+
AQWorker provides a CLI tool for managing workers and jobs:
|
|
344
|
+
|
|
345
|
+
### List Commands
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
# List available workers
|
|
349
|
+
aqworker list:aq_worker [file_path]
|
|
350
|
+
|
|
351
|
+
# List registered handlers
|
|
352
|
+
aqworker list:handlers [file_path]
|
|
353
|
+
|
|
354
|
+
# List queues
|
|
355
|
+
aqworker list:queue [file_path]
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Worker Commands
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
# Start a aq_worker
|
|
362
|
+
aqworker start <worker_name> [file_path]
|
|
363
|
+
|
|
364
|
+
# Get queue statistics
|
|
365
|
+
aqworker stats <queue_name> [file_path]
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Using Environment Variable
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
# Set AQWorker file path
|
|
372
|
+
export AQWORKER_FILE=aq_worker.py
|
|
373
|
+
|
|
374
|
+
# Use commands without file path
|
|
375
|
+
aqworker list:aq_worker
|
|
376
|
+
aqworker start email
|
|
377
|
+
aqworker stats emails
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**Note**: The file path should point to a Python file containing an `AQWorker` instance. The CLI automatically finds the instance (looking for variables named `aq_worker`, `worker`, `aqworker`, or `aq`).
|
|
381
|
+
|
|
382
|
+
## Configuration
|
|
383
|
+
|
|
384
|
+
### Environment Variables
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
# Redis Configuration (used when creating JobService)
|
|
388
|
+
REDIS_HOST=localhost
|
|
389
|
+
REDIS_PORT=6379
|
|
390
|
+
REDIS_DB=0
|
|
391
|
+
|
|
392
|
+
# AQWorker CLI
|
|
393
|
+
AQWORKER_FILE=aq_worker.py # Path to file containing AQWorker instance
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Worker Configuration
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
from aqworker import WorkerConfig
|
|
400
|
+
|
|
401
|
+
class MyWorker(BaseWorker):
|
|
402
|
+
worker_name = "my_worker"
|
|
403
|
+
worker_config = WorkerConfig(
|
|
404
|
+
queue_names=["my_queue"],
|
|
405
|
+
max_concurrent_jobs=5, # Max jobs processed simultaneously
|
|
406
|
+
poll_interval=0.5, # Seconds between queue polls
|
|
407
|
+
job_timeout=300, # Job timeout in seconds
|
|
408
|
+
)
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Job Lifecycle
|
|
412
|
+
|
|
413
|
+
```
|
|
414
|
+
1. ENQUEUE: Job created via JobService.enqueue_job()
|
|
415
|
+
2. QUEUE: Job added to Redis queue (aqw:{queue_name})
|
|
416
|
+
3. DEQUEUE: Worker gets job via JobService.get_next_job()
|
|
417
|
+
4. PROCESS: Job moved to processing queue, handler executed
|
|
418
|
+
5. COMPLETE: Job marked as completed/failed
|
|
419
|
+
6. CLEANUP: Old jobs cleaned up automatically
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## Queue System
|
|
423
|
+
|
|
424
|
+
### Redis key prefixes
|
|
425
|
+
|
|
426
|
+
AQWorker keeps every Redis key namespaced with the `aqw` prefix so multiple apps can
|
|
427
|
+
share the same Redis without collisions. The most important keys are:
|
|
428
|
+
|
|
429
|
+
- `aqw:{queue_name}` – pending FIFO queue for a specific worker queue
|
|
430
|
+
- `aqw:processing`, `aqw:completed`, `aqw:failed` – global processing/completed/failed lists
|
|
431
|
+
- `aqw:job:{job_id}` – job status hash (timestamps, error state, etc.)
|
|
432
|
+
- `aqw:jl:{job_id}` – per-job lock key used to ensure only one worker processes the job
|
|
433
|
+
|
|
434
|
+
All public APIs use helpers from `aqworker.constants` (`get_queue_name`,
|
|
435
|
+
`get_job_status_key`, `get_job_lock_key`) so you rarely need to construct these strings
|
|
436
|
+
manually.
|
|
437
|
+
|
|
438
|
+
### Queue Structure
|
|
439
|
+
|
|
440
|
+
```
|
|
441
|
+
Redis Keys:
|
|
442
|
+
├── aqw:emails # Email jobs (FIFO)
|
|
443
|
+
├── aqw:notifications # Notification jobs (FIFO)
|
|
444
|
+
├── aqw:processing # Jobs currently being processed
|
|
445
|
+
├── aqw:completed # Successfully completed jobs
|
|
446
|
+
└── aqw:failed # Failed jobs
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Queue Isolation
|
|
450
|
+
|
|
451
|
+
- Each worker processes specific queue names
|
|
452
|
+
- Jobs are isolated by queue name
|
|
453
|
+
- No cross-queue interference
|
|
454
|
+
- Use different queues for different job types
|
|
455
|
+
|
|
456
|
+
### FIFO Processing
|
|
457
|
+
|
|
458
|
+
- Simple first-in-first-out queue processing
|
|
459
|
+
- Jobs processed in order of arrival
|
|
460
|
+
- All jobs in a queue are processed equally
|
|
461
|
+
- Use different queue names for different priority levels
|
|
462
|
+
|
|
463
|
+
## Advanced Usage
|
|
464
|
+
|
|
465
|
+
### Custom Job Data
|
|
466
|
+
|
|
467
|
+
```python
|
|
468
|
+
job = await aq_worker.job_service.enqueue_job(
|
|
469
|
+
queue_name="emails",
|
|
470
|
+
handler="email",
|
|
471
|
+
data={
|
|
472
|
+
"recipient": "user@example.com",
|
|
473
|
+
"subject": "Welcome",
|
|
474
|
+
"body": "Welcome!",
|
|
475
|
+
"attachments": ["file1.pdf", "file2.jpg"],
|
|
476
|
+
"priority": "high"
|
|
477
|
+
},
|
|
478
|
+
metadata={
|
|
479
|
+
"source": "api",
|
|
480
|
+
"user_id": "12345",
|
|
481
|
+
"campaign_id": "summer2024"
|
|
482
|
+
},
|
|
483
|
+
max_retries=5,
|
|
484
|
+
retry_delay=60
|
|
485
|
+
)
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Multiple Workers
|
|
489
|
+
|
|
490
|
+
```python
|
|
491
|
+
# Register multiple workers
|
|
492
|
+
aq_worker.register_worker(EmailWorker)
|
|
493
|
+
aq_worker.register_worker(NotificationWorker)
|
|
494
|
+
aq_worker.register_worker(ReportWorker)
|
|
495
|
+
|
|
496
|
+
# Start specific aq_worker
|
|
497
|
+
worker = aq_worker.create_worker("email")
|
|
498
|
+
await worker.run()
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Handler with Async Support
|
|
502
|
+
|
|
503
|
+
```python
|
|
504
|
+
class AsyncHandler(BaseHandler):
|
|
505
|
+
name = "async_task"
|
|
506
|
+
|
|
507
|
+
async def handle(self, data: dict) -> bool:
|
|
508
|
+
# Async operations
|
|
509
|
+
result = await some_async_operation(data)
|
|
510
|
+
return result is not None
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
## Best Practices
|
|
514
|
+
|
|
515
|
+
### 1. Job Design
|
|
516
|
+
- Keep jobs small and focused
|
|
517
|
+
- Use descriptive handler names
|
|
518
|
+
- Include all necessary data in `data` parameter
|
|
519
|
+
- Use `metadata` for tracking/debugging information
|
|
520
|
+
|
|
521
|
+
### 2. Worker Design
|
|
522
|
+
- Implement proper error handling in handlers
|
|
523
|
+
- Use appropriate concurrency levels
|
|
524
|
+
- Monitor job processing time
|
|
525
|
+
- Log important events
|
|
526
|
+
|
|
527
|
+
### 3. Queue Management
|
|
528
|
+
- Use descriptive queue names
|
|
529
|
+
- Monitor queue depths
|
|
530
|
+
- Clean up old jobs regularly
|
|
531
|
+
- Balance load across workers
|
|
532
|
+
|
|
533
|
+
### 4. Handler Design
|
|
534
|
+
- Make handlers idempotent when possible
|
|
535
|
+
- Handle errors gracefully
|
|
536
|
+
- Return `True` on success, `False` on failure
|
|
537
|
+
- Use async handlers for I/O operations
|
|
538
|
+
|
|
539
|
+
## Troubleshooting
|
|
540
|
+
|
|
541
|
+
### Common Issues
|
|
542
|
+
|
|
543
|
+
1. **Jobs not processing**
|
|
544
|
+
- Check worker is running
|
|
545
|
+
- Verify queue names match between enqueue and worker
|
|
546
|
+
- Check Redis connection
|
|
547
|
+
- Verify handler is registered
|
|
548
|
+
|
|
549
|
+
2. **High memory usage**
|
|
550
|
+
- Clean up old jobs: `await job_service.cleanup_old_jobs(days=7)`
|
|
551
|
+
- Reduce queue depths
|
|
552
|
+
- Optimize job data size
|
|
553
|
+
|
|
554
|
+
3. **Slow processing**
|
|
555
|
+
- Increase worker concurrency (`max_concurrent_jobs`)
|
|
556
|
+
- Optimize handler logic
|
|
557
|
+
- Check Redis performance
|
|
558
|
+
- Consider multiple workers
|
|
559
|
+
|
|
560
|
+
### Debugging
|
|
561
|
+
|
|
562
|
+
```python
|
|
563
|
+
# Check queue statistics
|
|
564
|
+
stats = await aq_worker.job_service.get_queue_stats(["emails"])
|
|
565
|
+
print(f"Queue stats: {stats}")
|
|
566
|
+
|
|
567
|
+
# Get job status
|
|
568
|
+
job = await aq_worker.job_service.get_job(job_id)
|
|
569
|
+
print(f"Job status: {job.status}")
|
|
570
|
+
|
|
571
|
+
# List registered workers
|
|
572
|
+
workers = aq_worker.get_available_workers()
|
|
573
|
+
print(f"Available workers: {workers}")
|
|
574
|
+
|
|
575
|
+
# List registered handlers
|
|
576
|
+
handlers = aq_worker.handler_registry.snapshot()
|
|
577
|
+
print(f"Registered handlers: {list(handlers.keys())}")
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
## Examples
|
|
581
|
+
|
|
582
|
+
The `examples/` directory contains end-to-end demos:
|
|
583
|
+
|
|
584
|
+
### `examples/simple/`
|
|
585
|
+
- Pure Python example with a background thread enqueuing email jobs while a worker
|
|
586
|
+
(created via `create_worker("email")`) processes them.
|
|
587
|
+
- Handlers live in `examples.simple.aq_worker.handlers` and are picked up through
|
|
588
|
+
`AQWorker(include_packages=["examples.simple.aq_worker"])`, so you can keep job logic
|
|
589
|
+
in separate modules without manual registration.
|
|
590
|
+
- Run `python -m examples.simple.main` to start the enqueue loop and worker concurrently
|
|
591
|
+
or use the CLI (`aqworker start email examples/simple/worker.py`) in separate shells.
|
|
592
|
+
|
|
593
|
+
### `examples/simple_fastapi/`
|
|
594
|
+
- FastAPI service exposing REST endpoints for enqueuing jobs plus health/metadata
|
|
595
|
+
routes (`/handlers`, `/workers`, `/jobs/queues/{queue}/stats`).
|
|
596
|
+
- Worker definition in `examples/simple_fastapi/worker.py` registers worker classes and
|
|
597
|
+
auto-discovers handlers via `include_packages=["examples.simple_fastapi.handlers"]`.
|
|
598
|
+
- `examples/simple_fastapi/handlers.py` simulates real work with 0.5–2 s `asyncio.sleep`
|
|
599
|
+
delays so you can observe concurrent processing.
|
|
600
|
+
- `examples/simple_fastapi/client.py` is an `httpx` script that continuously enqueues
|
|
601
|
+
email + notification jobs every ~0.2 s—perfect for smoke-testing the API while
|
|
602
|
+
watching worker logs scroll.
|
|
603
|
+
|
|
604
|
+
## Contributing
|
|
605
|
+
|
|
606
|
+
When adding new features:
|
|
607
|
+
1. Follow async/await patterns
|
|
608
|
+
2. Add proper error handling
|
|
609
|
+
3. Update documentation
|
|
610
|
+
4. Add tests
|
|
611
|
+
5. Consider performance impact
|
|
612
|
+
|
|
613
|
+
## License
|
|
614
|
+
|
|
615
|
+
This project is licensed under the terms specified in the LICENSE file.
|