baqueue 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.
- baqueue-0.1.0/LICENSE +21 -0
- baqueue-0.1.0/MANIFEST.in +19 -0
- baqueue-0.1.0/PKG-INFO +609 -0
- baqueue-0.1.0/README.md +558 -0
- baqueue-0.1.0/baqueue/__init__.py +19 -0
- baqueue-0.1.0/baqueue/balancer.py +108 -0
- baqueue-0.1.0/baqueue/batch.py +159 -0
- baqueue-0.1.0/baqueue/cli.py +459 -0
- baqueue-0.1.0/baqueue/config.py +79 -0
- baqueue-0.1.0/baqueue/dashboard/__init__.py +1 -0
- baqueue-0.1.0/baqueue/dashboard/api.py +193 -0
- baqueue-0.1.0/baqueue/dashboard/server.py +263 -0
- baqueue-0.1.0/baqueue/dashboard/static/app.js +450 -0
- baqueue-0.1.0/baqueue/dashboard/static/index.html +580 -0
- baqueue-0.1.0/baqueue/dashboard/static/style.css +1415 -0
- baqueue-0.1.0/baqueue/drivers/__init__.py +1 -0
- baqueue-0.1.0/baqueue/drivers/base.py +212 -0
- baqueue-0.1.0/baqueue/drivers/memory_driver.py +318 -0
- baqueue-0.1.0/baqueue/drivers/postgres_driver.py +656 -0
- baqueue-0.1.0/baqueue/drivers/redis_driver.py +656 -0
- baqueue-0.1.0/baqueue/drivers/sqlite_driver.py +706 -0
- baqueue-0.1.0/baqueue/events.py +64 -0
- baqueue-0.1.0/baqueue/job.py +128 -0
- baqueue-0.1.0/baqueue/pruner.py +128 -0
- baqueue-0.1.0/baqueue/queue.py +225 -0
- baqueue-0.1.0/baqueue/retry.py +55 -0
- baqueue-0.1.0/baqueue/scheduler.py +101 -0
- baqueue-0.1.0/baqueue/serializer.py +124 -0
- baqueue-0.1.0/baqueue/supervisor.py +206 -0
- baqueue-0.1.0/baqueue/worker.py +165 -0
- baqueue-0.1.0/baqueue.egg-info/PKG-INFO +609 -0
- baqueue-0.1.0/baqueue.egg-info/SOURCES.txt +36 -0
- baqueue-0.1.0/baqueue.egg-info/dependency_links.txt +1 -0
- baqueue-0.1.0/baqueue.egg-info/entry_points.txt +2 -0
- baqueue-0.1.0/baqueue.egg-info/requires.txt +28 -0
- baqueue-0.1.0/baqueue.egg-info/top_level.txt +1 -0
- baqueue-0.1.0/pyproject.toml +91 -0
- baqueue-0.1.0/setup.cfg +4 -0
baqueue-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Basalam and BaQueue Contributors
|
|
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,19 @@
|
|
|
1
|
+
include README.md
|
|
2
|
+
include LICENSE
|
|
3
|
+
include pyproject.toml
|
|
4
|
+
|
|
5
|
+
# Ship the dashboard's bundled static assets in the source distribution.
|
|
6
|
+
recursive-include baqueue/dashboard/static *
|
|
7
|
+
|
|
8
|
+
# Keep build/test/runtime artifacts out of the sdist.
|
|
9
|
+
global-exclude *.py[cod]
|
|
10
|
+
global-exclude __pycache__/*
|
|
11
|
+
prune tests
|
|
12
|
+
prune examples
|
|
13
|
+
prune .github
|
|
14
|
+
exclude .baqueue.db
|
|
15
|
+
exclude AGENTS.md
|
|
16
|
+
global-exclude *.db
|
|
17
|
+
global-exclude *.db-shm
|
|
18
|
+
global-exclude *.db-wal
|
|
19
|
+
global-exclude *.db-journal
|
baqueue-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: baqueue
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A powerful Python queue management package inspired by Laravel Horizon
|
|
5
|
+
Author: Basalam, BaQueue Contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/basalam/baqueue
|
|
8
|
+
Project-URL: Repository, https://github.com/basalam/baqueue
|
|
9
|
+
Project-URL: Issues, https://github.com/basalam/baqueue/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/basalam/baqueue/releases
|
|
11
|
+
Keywords: queue,job,worker,scheduler,horizon,redis,postgres,async,task
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
23
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: pydantic>=2.0
|
|
28
|
+
Requires-Dist: click>=8.0
|
|
29
|
+
Requires-Dist: croniter>=2.0
|
|
30
|
+
Provides-Extra: redis
|
|
31
|
+
Requires-Dist: redis[hiredis]>=5.0; extra == "redis"
|
|
32
|
+
Provides-Extra: postgres
|
|
33
|
+
Requires-Dist: asyncpg>=0.29; extra == "postgres"
|
|
34
|
+
Provides-Extra: dashboard
|
|
35
|
+
Requires-Dist: fastapi>=0.110; extra == "dashboard"
|
|
36
|
+
Requires-Dist: uvicorn[standard]>=0.29; extra == "dashboard"
|
|
37
|
+
Requires-Dist: websockets>=12.0; extra == "dashboard"
|
|
38
|
+
Provides-Extra: all
|
|
39
|
+
Requires-Dist: redis[hiredis]>=5.0; extra == "all"
|
|
40
|
+
Requires-Dist: asyncpg>=0.29; extra == "all"
|
|
41
|
+
Requires-Dist: fastapi>=0.110; extra == "all"
|
|
42
|
+
Requires-Dist: uvicorn[standard]>=0.29; extra == "all"
|
|
43
|
+
Requires-Dist: websockets>=12.0; extra == "all"
|
|
44
|
+
Provides-Extra: dev
|
|
45
|
+
Requires-Dist: baqueue[all]; extra == "dev"
|
|
46
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
47
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
48
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
49
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
50
|
+
Dynamic: license-file
|
|
51
|
+
|
|
52
|
+
# BaQueue
|
|
53
|
+
|
|
54
|
+
[](https://pypi.org/project/baqueue/)
|
|
55
|
+
[](https://pypi.org/project/baqueue/)
|
|
56
|
+
[](https://github.com/basalam/baqueue/blob/master/LICENSE)
|
|
57
|
+
[](https://github.com/basalam/baqueue/actions/workflows/ci.yml)
|
|
58
|
+
|
|
59
|
+
A powerful Python queue management package. Multi-driver support, batch jobs, scheduling, auto-balancing, and a beautiful real-time monitoring dashboard.
|
|
60
|
+
|
|
61
|
+

|
|
62
|
+
|
|
63
|
+
### Jobs - Filterable job list with status, attempts, and duration
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+
### Queues - Per-queue detail cards with pending/processing/completed/failed
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
## Table of Contents
|
|
70
|
+
|
|
71
|
+
- [Features](#features)
|
|
72
|
+
- [Quick Start](#quick-start)
|
|
73
|
+
- [Define a Job](#define-a-job)
|
|
74
|
+
- [Dispatch Jobs](#dispatch-jobs)
|
|
75
|
+
- [Batch Jobs](#batch-jobs)
|
|
76
|
+
- [Run Workers](#run-workers)
|
|
77
|
+
- [Pruning](#pruning)
|
|
78
|
+
- [Auto-pruning](#auto-pruning-runs-alongside-baqueue-work)
|
|
79
|
+
- [Manual pruning](#manual-pruning)
|
|
80
|
+
- [Retry Failed Jobs](#retry-failed-jobs)
|
|
81
|
+
- [Dashboard](#dashboard)
|
|
82
|
+
- [Workers Tab (Supervisor/Worker Monitoring)](#workers-tab-supervisorworker-monitoring)
|
|
83
|
+
- [Drivers](#drivers)
|
|
84
|
+
- [Examples](#examples)
|
|
85
|
+
- [Testing](#testing)
|
|
86
|
+
- [CLI Commands](#cli-commands)
|
|
87
|
+
- [Benchmarks](#benchmarks)
|
|
88
|
+
- [Test 1: 1,000 jobs / 5 workers](#test-1-1000-jobs--5-workers)
|
|
89
|
+
- [Test 2: 5,000 jobs / 10 workers](#test-2-5000-jobs--10-workers)
|
|
90
|
+
- [Stress Test Options](#stress-test-options)
|
|
91
|
+
- [Run with Live Dashboard](#run-with-live-dashboard)
|
|
92
|
+
- [License](#license)
|
|
93
|
+
|
|
94
|
+
## Features
|
|
95
|
+
- **Multi-driver**: SQLite (default), Redis, PostgreSQL, or In-Memory
|
|
96
|
+
- **Auto-balancing**: Dynamically scale workers based on queue pressure
|
|
97
|
+
- **Auto-pruning**: Completed jobs are deleted about 5 seconds after they finish; failed/cancelled jobs are kept up to 1 day — all configurable
|
|
98
|
+
- **Disk-full cleanup**: Storage-full/OOM driver errors trigger emergency cleanup of terminal jobs and old metrics, then retry once
|
|
99
|
+
- **Pruning**: Remove old jobs by status, tag, or age
|
|
100
|
+
- **Monitoring Dashboard**: Real-time WebSocket-powered UI with date filtering
|
|
101
|
+
- **CLI**: Manage workers, scheduler, dashboard, and pruning from the command line
|
|
102
|
+
- **Cross-process**: SQLite driver shares state between dashboard and workers without external dependencies
|
|
103
|
+
|
|
104
|
+
## Quick Start
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Install (SQLite + in-memory drivers work out of the box, zero extra dependencies)
|
|
108
|
+
pip install baqueue
|
|
109
|
+
|
|
110
|
+
# With Redis support
|
|
111
|
+
pip install "baqueue[redis]"
|
|
112
|
+
|
|
113
|
+
# With PostgreSQL support
|
|
114
|
+
pip install "baqueue[postgres]"
|
|
115
|
+
|
|
116
|
+
# With dashboard
|
|
117
|
+
pip install "baqueue[dashboard]"
|
|
118
|
+
|
|
119
|
+
# Everything
|
|
120
|
+
pip install "baqueue[all]"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
> **Installing from source (development):**
|
|
124
|
+
> ```bash
|
|
125
|
+
> git clone https://github.com/basalam/baqueue.git
|
|
126
|
+
> cd baqueue
|
|
127
|
+
> pip install -e ".[dev]" # editable install with all extras + test/build tooling
|
|
128
|
+
> ```
|
|
129
|
+
|
|
130
|
+
### Define a Job
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from baqueue import Job
|
|
134
|
+
|
|
135
|
+
class SendEmail(Job):
|
|
136
|
+
queue = "emails"
|
|
137
|
+
max_attempts = 3
|
|
138
|
+
backoff = "exponential"
|
|
139
|
+
|
|
140
|
+
async def handle(self, to: str, subject: str, body: str):
|
|
141
|
+
await send_email(to, subject, body)
|
|
142
|
+
|
|
143
|
+
async def on_failure(self, error, payload):
|
|
144
|
+
print(f"Failed to send email: {error}")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Or use the decorator:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from baqueue import Job
|
|
151
|
+
|
|
152
|
+
@Job.as_job(queue="emails", max_attempts=3)
|
|
153
|
+
async def send_email(to, subject, body):
|
|
154
|
+
...
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Dispatch Jobs
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from baqueue import Queue, BaQueueConfig
|
|
161
|
+
from baqueue.config import DriverConfig
|
|
162
|
+
|
|
163
|
+
# Configure (SQLite driver by default - works across processes)
|
|
164
|
+
Queue.configure(BaQueueConfig(driver=DriverConfig(name="sqlite")))
|
|
165
|
+
await Queue.connect()
|
|
166
|
+
|
|
167
|
+
# Push a job
|
|
168
|
+
await Queue.push(SendEmail, to="user@example.com", subject="Hi", body="Hello!")
|
|
169
|
+
|
|
170
|
+
# Push with delay (60 seconds)
|
|
171
|
+
await Queue.later(SendEmail, delay=60, to="user@example.com", subject="Reminder", body="...")
|
|
172
|
+
|
|
173
|
+
# Bulk push (much faster for large volumes)
|
|
174
|
+
await Queue.bulk([
|
|
175
|
+
(SendEmail, {"to": "a@b.com", "subject": "Hi", "body": "A"}),
|
|
176
|
+
(SendEmail, {"to": "c@d.com", "subject": "Hi", "body": "B"}),
|
|
177
|
+
])
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Batch Jobs
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from baqueue import Batch
|
|
184
|
+
|
|
185
|
+
result = await Batch(driver, [
|
|
186
|
+
(SendEmail, {"to": "a@b.com", "subject": "Hi", "body": "Hey"}),
|
|
187
|
+
(SendEmail, {"to": "c@d.com", "subject": "Hi", "body": "Hey"}),
|
|
188
|
+
]).name("newsletter").then(OnAllDone).catch(OnAnyFailed).dispatch()
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Run Workers
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from baqueue.supervisor import Supervisor
|
|
195
|
+
from baqueue.config import SupervisorConfig
|
|
196
|
+
|
|
197
|
+
supervisor = Supervisor(
|
|
198
|
+
driver=Queue.get_driver(),
|
|
199
|
+
config=SupervisorConfig(
|
|
200
|
+
queues=["emails", "payments"],
|
|
201
|
+
min_workers=3,
|
|
202
|
+
max_workers=10,
|
|
203
|
+
balance="auto",
|
|
204
|
+
),
|
|
205
|
+
)
|
|
206
|
+
await supervisor.start()
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Or via CLI:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
baqueue work -q emails -q payments -w 3 -b auto
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Pruning
|
|
216
|
+
|
|
217
|
+
#### Auto-pruning (runs alongside `baqueue work`)
|
|
218
|
+
|
|
219
|
+
When `baqueue work` is running, a background pruner cycles every 5s and applies these defaults across every driver:
|
|
220
|
+
|
|
221
|
+
| Status | Default lifetime | Config field |
|
|
222
|
+
|---------------------|------------------|-----------------------------|
|
|
223
|
+
| `completed` | 5 seconds | `prune_completed_seconds` |
|
|
224
|
+
| `failed`, `cancelled` | 1 day | `prune_other_seconds` |
|
|
225
|
+
| metrics rows | 7 days | `prune_metrics_seconds` |
|
|
226
|
+
| pruner cycle | every 5s | `prune_interval_seconds` |
|
|
227
|
+
| enable/disable | `True` | `auto_prune` |
|
|
228
|
+
|
|
229
|
+
Override from a JSON config file (`baqueue -c config.json work`):
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
{
|
|
233
|
+
"auto_prune": true,
|
|
234
|
+
"prune_completed_seconds": 600,
|
|
235
|
+
"prune_other_seconds": 172800,
|
|
236
|
+
"prune_interval_seconds": 30,
|
|
237
|
+
"auto_cleanup_on_disk_full": true
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Or from Python:
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
from baqueue import BaQueueConfig
|
|
245
|
+
config = BaQueueConfig(
|
|
246
|
+
prune_completed_seconds=600, # 10 minutes
|
|
247
|
+
prune_other_seconds=172800, # 2 days
|
|
248
|
+
prune_interval_seconds=30,
|
|
249
|
+
auto_cleanup_on_disk_full=True, # enabled by default
|
|
250
|
+
)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Or from the CLI:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
baqueue work --prune-completed-seconds 600 --prune-other-seconds 172800
|
|
257
|
+
baqueue work --no-auto-prune # disable the background pruner
|
|
258
|
+
baqueue work --no-disk-full-cleanup # disable emergency storage cleanup
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The legacy hour-based fields (`prune_completed_hours`, `prune_failed_hours`, `prune_cancelled_hours`, `prune_metrics_hours`) are still respected for backward compatibility — when set to a positive value they override the corresponding `*_seconds` field.
|
|
262
|
+
|
|
263
|
+
#### Disk-full emergency cleanup
|
|
264
|
+
|
|
265
|
+
`auto_cleanup_on_disk_full` is enabled by default. When a driver write/update/delete operation sees a storage-full style error (SQLite disk full, PostgreSQL disk/memory exhausted, Redis OOM/maxmemory), BaQueue runs an emergency cleanup that removes terminal jobs (`completed`, `failed`, `cancelled`) and old metrics, then retries the failed operation once. If cleanup does not free enough space, the original driver error is still raised.
|
|
266
|
+
|
|
267
|
+
#### Manual pruning
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
# Remove completed jobs older than 24 hours
|
|
271
|
+
await Queue.prune(status="completed", hours=24)
|
|
272
|
+
|
|
273
|
+
# Remove jobs by tag
|
|
274
|
+
await Queue.prune(tag="batch:newsletter")
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Retry Failed Jobs
|
|
278
|
+
|
|
279
|
+
Bulk-retry failed jobs from the CLI, from Python, or from the dashboard.
|
|
280
|
+
|
|
281
|
+
**CLI:**
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
# Retry every failed job (asks for confirmation)
|
|
285
|
+
baqueue retry-failed
|
|
286
|
+
|
|
287
|
+
# Skip the confirmation prompt
|
|
288
|
+
baqueue retry-failed -y
|
|
289
|
+
|
|
290
|
+
# Limit to a specific queue
|
|
291
|
+
baqueue retry-failed -q emails
|
|
292
|
+
|
|
293
|
+
# Combine filters: queue + tag + age window
|
|
294
|
+
baqueue retry-failed -q emails -t campaign --hours 24
|
|
295
|
+
|
|
296
|
+
# Use a non-default driver
|
|
297
|
+
baqueue retry-failed -d redis --driver-url redis://localhost:6379/0
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Python:**
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
# Retry every failed job
|
|
304
|
+
count = await Queue.retry_failed()
|
|
305
|
+
|
|
306
|
+
# Retry only failed jobs in a queue
|
|
307
|
+
count = await Queue.retry_failed(queue="emails")
|
|
308
|
+
|
|
309
|
+
# Filter by tag and creation window
|
|
310
|
+
from baqueue.serializer import _now_ts
|
|
311
|
+
count = await Queue.retry_failed(
|
|
312
|
+
queue="emails",
|
|
313
|
+
tag="campaign",
|
|
314
|
+
created_from=_now_ts() - 24 * 3600,
|
|
315
|
+
)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Dashboard:** open the **Jobs** tab, set the Status filter to `Failed`, then click the amber **Retry All** button. The current Queue / Tag / date-range filters are respected.
|
|
319
|
+
|
|
320
|
+
Each matched job is released back onto its queue with `delay=0`, the same path used by single-job retry.
|
|
321
|
+
|
|
322
|
+
### Dashboard
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
# Start the dashboard (uses SQLite by default)
|
|
326
|
+
baqueue dashboard
|
|
327
|
+
|
|
328
|
+
# Open http://localhost:9100
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
The dashboard includes:
|
|
332
|
+
- Real-time overview with pending/processing/completed/failed counters sourced from live job state (not bounded metric logs)
|
|
333
|
+
- Date range filtering (custom range + presets: 1h, 24h, 7d, 30d)
|
|
334
|
+
- Job detail modal with timeline, payload data, and error trace
|
|
335
|
+
- Queue breakdown with progress bars
|
|
336
|
+
- Worker monitoring with active/idle status
|
|
337
|
+
- Dark/light theme toggle
|
|
338
|
+
- **Scheduled-job badge** with hover tooltip showing exact execution time, plus a "Scheduled For" entry in the job timeline
|
|
339
|
+
- **Bulk "Retry All"** button when the Jobs view is filtered to `failed` (respects the active queue/tag/date filters)
|
|
340
|
+
- **Queue filter as a dropdown** auto-populated from active queues (no manual typing)
|
|
341
|
+
- **Mobile-friendly** sidebar drawer with hamburger toggle on screens ≤900px
|
|
342
|
+
|
|
343
|
+
Run in one terminal:
|
|
344
|
+
```bash
|
|
345
|
+
baqueue dashboard
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Dispatch jobs in another terminal:
|
|
349
|
+
```bash
|
|
350
|
+
python examples/simple_job.py
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### Workers Tab (Supervisor/Worker Monitoring)
|
|
354
|
+
|
|
355
|
+
To see active supervisors/workers in the `Workers` tab, `work` and `dashboard`
|
|
356
|
+
must point to the same backend (same driver and same URL/path).
|
|
357
|
+
|
|
358
|
+
Example with SQLite:
|
|
359
|
+
|
|
360
|
+
Terminal 1:
|
|
361
|
+
```bash
|
|
362
|
+
baqueue work -d sqlite --driver-url .baqueue.db -q default -w 3
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Terminal 2:
|
|
366
|
+
```bash
|
|
367
|
+
baqueue dashboard -d sqlite --driver-url .baqueue.db
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Then open:
|
|
371
|
+
```text
|
|
372
|
+
http://localhost:9100
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Quick troubleshooting:
|
|
376
|
+
- Check `http://localhost:9100/api/supervisors` (should return a non-empty `supervisors` list while workers are running).
|
|
377
|
+
- If `api/supervisors` is empty, `work` and `dashboard` are likely using different driver URLs/paths.
|
|
378
|
+
- `memory` driver is single-process only, so separate `work` and `dashboard` processes will not share worker state.
|
|
379
|
+
|
|
380
|
+
Driver-specific CLI examples:
|
|
381
|
+
|
|
382
|
+
SQLite (shared local file):
|
|
383
|
+
```bash
|
|
384
|
+
baqueue work -d sqlite --driver-url .baqueue.db -q default -w 3
|
|
385
|
+
baqueue dashboard -d sqlite --driver-url .baqueue.db
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Redis (shared Redis DB):
|
|
389
|
+
```bash
|
|
390
|
+
baqueue work -d redis --driver-url redis://localhost:6379/0 -q default -w 3
|
|
391
|
+
baqueue dashboard -d redis --driver-url redis://localhost:6379/0
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
PostgreSQL (shared database/schema):
|
|
395
|
+
```bash
|
|
396
|
+
baqueue work -d postgres --driver-url postgresql://user:pass@localhost/dbname -q default -w 3
|
|
397
|
+
baqueue dashboard -d postgres --driver-url postgresql://user:pass@localhost/dbname
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Memory (single-process only):
|
|
401
|
+
```bash
|
|
402
|
+
# Use an in-process example to run workers + dashboard together.
|
|
403
|
+
python examples/dashboard_demo.py
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Drivers
|
|
407
|
+
|
|
408
|
+
**SQLite (default, zero-config, cross-process):**
|
|
409
|
+
```python
|
|
410
|
+
Queue.configure(BaQueueConfig(
|
|
411
|
+
driver=DriverConfig(name="sqlite")
|
|
412
|
+
))
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**Redis:**
|
|
416
|
+
```python
|
|
417
|
+
Queue.configure(BaQueueConfig(
|
|
418
|
+
driver=DriverConfig(name="redis", url="redis://localhost:6379/0")
|
|
419
|
+
))
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**PostgreSQL:**
|
|
423
|
+
```python
|
|
424
|
+
Queue.configure(BaQueueConfig(
|
|
425
|
+
driver=DriverConfig(name="postgres", url="postgresql://user:pass@localhost/dbname")
|
|
426
|
+
))
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
**Memory (single-process testing only):**
|
|
430
|
+
```python
|
|
431
|
+
Queue.configure(BaQueueConfig(
|
|
432
|
+
driver=DriverConfig(name="memory")
|
|
433
|
+
))
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Examples
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
# Simple job processing
|
|
440
|
+
python examples/simple_job.py
|
|
441
|
+
|
|
442
|
+
# Batch processing
|
|
443
|
+
python examples/batch_example.py
|
|
444
|
+
|
|
445
|
+
# Scheduled jobs
|
|
446
|
+
python examples/scheduled_example.py
|
|
447
|
+
|
|
448
|
+
# Dashboard demo (open http://localhost:9100)
|
|
449
|
+
python examples/dashboard_demo.py
|
|
450
|
+
|
|
451
|
+
# Delayed jobs demo — shows the "Scheduled" badge with varied delays
|
|
452
|
+
python examples/delayed_jobs_demo.py
|
|
453
|
+
|
|
454
|
+
# Stress test (see Benchmarks section below)
|
|
455
|
+
python examples/stress_test.py --jobs 1000 --workers 5 --bulk
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## Testing
|
|
459
|
+
|
|
460
|
+
The full test suite lives in `tests/` and runs with one command:
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
# Run everything
|
|
464
|
+
baqueue test
|
|
465
|
+
|
|
466
|
+
# Quiet output, stop at the first failure
|
|
467
|
+
baqueue test -q -x
|
|
468
|
+
|
|
469
|
+
# Run only retry-failed related tests
|
|
470
|
+
baqueue test -k "RetryFailed or retry_failed"
|
|
471
|
+
|
|
472
|
+
# Re-run just the tests that failed last time
|
|
473
|
+
baqueue test --last-failed
|
|
474
|
+
|
|
475
|
+
# Filter by marker (markers defined in pyproject.toml)
|
|
476
|
+
baqueue test -m "not slow"
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
`baqueue test` is a thin wrapper around `pytest`, so it picks up the project's
|
|
480
|
+
`tool.pytest.ini_options` config (asyncio mode, marker definitions, etc.).
|
|
481
|
+
You can also run pytest directly:
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
pip install baqueue[dev]
|
|
485
|
+
pytest tests/ -v
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
Coverage includes:
|
|
489
|
+
|
|
490
|
+
- Serializer / payload roundtrip (incl. `delay_until`)
|
|
491
|
+
- Backoff strategies (`fixed`, `linear`, `exponential`, explicit list)
|
|
492
|
+
- `Job` + `FunctionJob` + `@Job.as_job` decorator
|
|
493
|
+
- `Queue` facade — push / later / bulk / prune / `retry_failed`
|
|
494
|
+
- Cross-driver contract tests (memory + sqlite, parameterized)
|
|
495
|
+
- Worker lifecycle: success / failure / retry / timeout
|
|
496
|
+
- Supervisor pool + delayed-job promotion
|
|
497
|
+
- Scheduler interval dispatch
|
|
498
|
+
- Pruner by status / tag / age
|
|
499
|
+
- Batch builder + completion callbacks
|
|
500
|
+
- DashboardAPI (overview, jobs_list, retry, bulk retry-failed, prune, stats)
|
|
501
|
+
- CLI command surface (help text, validation, `retry-failed` abort flow)
|
|
502
|
+
|
|
503
|
+
## CLI Commands
|
|
504
|
+
|
|
505
|
+
```
|
|
506
|
+
baqueue work Start processing jobs
|
|
507
|
+
baqueue schedule Start the job scheduler
|
|
508
|
+
baqueue dashboard Launch the monitoring dashboard
|
|
509
|
+
baqueue prune Prune old jobs
|
|
510
|
+
baqueue retry-failed Retry all failed jobs (filter by queue/tag/age)
|
|
511
|
+
baqueue status Show queue status
|
|
512
|
+
baqueue test Run the test suite
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Use `-h` on any command for options:
|
|
516
|
+
```bash
|
|
517
|
+
baqueue -h
|
|
518
|
+
baqueue work -h
|
|
519
|
+
baqueue dashboard -h
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
## Benchmarks
|
|
523
|
+
|
|
524
|
+
Stress tests run on **Windows 10, Python 3.11, SQLite driver**, using `examples/stress_test.py`.
|
|
525
|
+
|
|
526
|
+
The stress test dispatches jobs across 5 queues (`fast`, `slow`, `flaky`, `heavy`, `notifications`) with varying execution times and a ~30% failure rate on the `flaky` queue, exercising retries and backoff.
|
|
527
|
+
|
|
528
|
+
### Test 1: 1,000 jobs / 5 workers
|
|
529
|
+
|
|
530
|
+
```bash
|
|
531
|
+
python examples/stress_test.py --jobs 1000 --workers 5 --bulk
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
```
|
|
535
|
+
============================================================
|
|
536
|
+
RESULTS
|
|
537
|
+
============================================================
|
|
538
|
+
Total time: 30.38s
|
|
539
|
+
Completed: 993
|
|
540
|
+
Failed: 7
|
|
541
|
+
Throughput: 32.9 jobs/s
|
|
542
|
+
Success rate: 99.3%
|
|
543
|
+
============================================================
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
| Metric | Value |
|
|
547
|
+
|-------------------|----------------|
|
|
548
|
+
| Dispatch speed | 28,426 jobs/s |
|
|
549
|
+
| Processing speed | 32.9 jobs/s |
|
|
550
|
+
| Total time | 30.4s |
|
|
551
|
+
| Success rate | 99.3% |
|
|
552
|
+
|
|
553
|
+
### Test 2: 5,000 jobs / 10 workers
|
|
554
|
+
|
|
555
|
+
```bash
|
|
556
|
+
python examples/stress_test.py --jobs 5000 --workers 10 --bulk
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
```
|
|
560
|
+
============================================================
|
|
561
|
+
RESULTS
|
|
562
|
+
============================================================
|
|
563
|
+
Total time: 49.95s
|
|
564
|
+
Completed: 4965
|
|
565
|
+
Failed: 35
|
|
566
|
+
Throughput: 100.1 jobs/s
|
|
567
|
+
Success rate: 99.3%
|
|
568
|
+
============================================================
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
| Metric | Value |
|
|
572
|
+
|-------------------|----------------|
|
|
573
|
+
| Dispatch speed | ~50,000 jobs/s |
|
|
574
|
+
| Processing speed | 100.1 jobs/s |
|
|
575
|
+
| Total time | 49.9s |
|
|
576
|
+
| Success rate | 99.3% |
|
|
577
|
+
|
|
578
|
+
### Stress Test Options
|
|
579
|
+
|
|
580
|
+
```bash
|
|
581
|
+
python examples/stress_test.py [OPTIONS]
|
|
582
|
+
|
|
583
|
+
Options:
|
|
584
|
+
--jobs, -j Number of jobs to dispatch (default: 1000)
|
|
585
|
+
--workers, -w Number of concurrent workers (default: 5)
|
|
586
|
+
--bulk Use bulk insert for faster dispatching
|
|
587
|
+
--dashboard Launch live dashboard on http://localhost:9100
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
**Job types used in the stress test:**
|
|
591
|
+
|
|
592
|
+
| Job | Queue | Latency | Failure Rate | Max Attempts |
|
|
593
|
+
|-----------|-----------------|----------------|--------------|--------------|
|
|
594
|
+
| FastJob | `fast` | 10-50ms | 0% | 3 |
|
|
595
|
+
| SlowJob | `slow` | 100-300ms | 0% | 2 |
|
|
596
|
+
| FlakyJob | `flaky` | 20-80ms | ~30% | 3 |
|
|
597
|
+
| HeavyJob | `heavy` | 50-150ms | 0% | 1 |
|
|
598
|
+
| Notify | `notifications` | 10-40ms | 0% | 2 |
|
|
599
|
+
|
|
600
|
+
### Run with Live Dashboard
|
|
601
|
+
|
|
602
|
+
```bash
|
|
603
|
+
python examples/stress_test.py --jobs 3000 --workers 8 --bulk --dashboard
|
|
604
|
+
# Open http://localhost:9100 to watch progress in real-time
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
## License
|
|
608
|
+
|
|
609
|
+
[MIT](./LICENSE) © Basalam and BaQueue Contributors
|