beanqueue 2.0.0rc0__tar.gz → 2.0.0rc1__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.
- beanqueue-2.0.0rc0/README.md → beanqueue-2.0.0rc1/PKG-INFO +65 -15
- beanqueue-2.0.0rc0/PKG-INFO → beanqueue-2.0.0rc1/README.md +45 -34
- beanqueue-2.0.0rc1/bq/app.py +508 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/cmds/create_tables.py +8 -1
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/cmds/process.py +3 -1
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/cmds/submit.py +20 -10
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/config.py +63 -9
- beanqueue-2.0.0rc1/bq/db/session.py +6 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/metrics.py +25 -20
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/processors/processor.py +63 -12
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/processors/registry.py +7 -5
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/processors/retry_policies.py +8 -5
- beanqueue-2.0.0rc1/bq/services/dispatch.py +135 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/services/worker.py +26 -24
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/pyproject.toml +11 -4
- beanqueue-2.0.0rc0/bq/app.py +0 -484
- beanqueue-2.0.0rc0/bq/db/session.py +0 -5
- beanqueue-2.0.0rc0/bq/services/dispatch.py +0 -117
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/.gitignore +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/LICENSE +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/__init__.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/cmds/__init__.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/cmds/cli.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/cmds/environment.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/cmds/main.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/cmds/utils.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/constants.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/db/__init__.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/db/base.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/events.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/models/__init__.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/models/event.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/models/helpers.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/models/task.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/models/worker.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/processors/__init__.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/services/__init__.py +0 -0
- {beanqueue-2.0.0rc0 → beanqueue-2.0.0rc1}/bq/utils.py +0 -0
|
@@ -1,6 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: beanqueue
|
|
3
|
+
Version: 2.0.0rc1
|
|
4
|
+
Summary: BeanQueue or BQ for short, PostgreSQL SKIP LOCK and SQLAlchemy based worker queue library
|
|
5
|
+
Author-email: Fang-Pen Lin <fangpen@launchplatform.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: <4,>=3.11
|
|
9
|
+
Requires-Dist: blinker<2,>=1.8.2
|
|
10
|
+
Requires-Dist: click<9,>=8.1.7
|
|
11
|
+
Requires-Dist: psycopg[binary]<4,>=3.1
|
|
12
|
+
Requires-Dist: pydantic-settings<3,>=2.2.1
|
|
13
|
+
Requires-Dist: rich<14,>=13.7.1
|
|
14
|
+
Requires-Dist: sqlalchemy[asyncio]<3,>=2.0.30
|
|
15
|
+
Requires-Dist: venusian<4,>=3.1.0
|
|
16
|
+
Provides-Extra: metrics
|
|
17
|
+
Requires-Dist: starlette<2,>=0.27; extra == 'metrics'
|
|
18
|
+
Requires-Dist: uvicorn<1,>=0.30.0; extra == 'metrics'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
1
21
|
# BeanQueue [](https://dl.circleci.com/status-badge/redirect/gh/LaunchPlatform/bq/tree/master)
|
|
2
22
|
|
|
3
|
-
BeanQueue, a lightweight Python task queue framework based on [SQLAlchemy](https://www.sqlalchemy.org/), PostgreSQL [SKIP LOCKED queries](https://www.2ndquadrant.com/en/blog/what-is-select-skip-locked-for-in-postgresql-9-5/) and [NOTIFY](https://www.postgresql.org/docs/current/sql-notify.html) / [LISTEN](https://www.postgresql.org/docs/current/sql-listen.html) statements.
|
|
23
|
+
BeanQueue, a lightweight Python task queue framework based on [SQLAlchemy](https://www.sqlalchemy.org/) 2.0 asyncio, [psycopg3](https://www.psycopg.org/psycopg3/), PostgreSQL [SKIP LOCKED queries](https://www.2ndquadrant.com/en/blog/what-is-select-skip-locked-for-in-postgresql-9-5/) and [NOTIFY](https://www.postgresql.org/docs/current/sql-notify.html) / [LISTEN](https://www.postgresql.org/docs/current/sql-listen.html) statements.
|
|
4
24
|
|
|
5
25
|
**Notice**: Still in its early stage, we built this for [BeanHub](https://beanhub.io)'s internal usage. May change rapidly. Use at your own risk for now.
|
|
6
26
|
|
|
@@ -29,20 +49,39 @@ To enable the optional metrics HTTP server (currently `/healthz` only), install
|
|
|
29
49
|
pip install "beanqueue[metrics]"
|
|
30
50
|
```
|
|
31
51
|
|
|
52
|
+
## Testing
|
|
53
|
+
|
|
54
|
+
Unit and in-process acceptance tests:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
uv run python -m pytest tests
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
End-to-end tests start PostgreSQL and three worker containers with Docker Compose, enqueue real tasks, apply load, kill a worker, and check graceful shutdown cleanup:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv run python -m pytest tests/e2e -svvvv
|
|
64
|
+
```
|
|
65
|
+
|
|
32
66
|
## Upgrading to 2.0
|
|
33
67
|
|
|
34
|
-
BeanQueue 2.0
|
|
68
|
+
BeanQueue 2.0 is asyncio-first (SQLAlchemy `AsyncSession` + psycopg3) and includes breaking changes:
|
|
35
69
|
|
|
70
|
+
- **Workers use asyncio SQLAlchemy.** `BeanQueue.make_session()` returns an `AsyncSession`, `engine` is an `AsyncEngine`, and `process_tasks()` is `async`. Run it with `asyncio.run(app.process_tasks(...))` or `await` it from your own loop.
|
|
71
|
+
- **Database URLs use the psycopg3 driver.** Prefer `postgresql+psycopg://...`. Bare `postgresql://...` URLs are normalized to `postgresql+psycopg://...`.
|
|
72
|
+
- **`MAX_WORKER_THREADS` is now `MAX_CONCURRENT_TASKS`.** `BQ_MAX_WORKER_THREADS` and the old keyword still work as a deprecated alias.
|
|
73
|
+
- **Processors may be `async def` or `def`.** Async processors receive `db: AsyncSession`. Sync processors that take `db` still receive a sync `Session` via `AsyncSession.run_sync()`.
|
|
36
74
|
- **`METRICS_HTTP_SERVER_ENABLED` defaults to `False`** (it was `True` in 1.x). Set `BQ_METRICS_HTTP_SERVER_ENABLED=true` to turn the server back on.
|
|
37
75
|
- **The metrics server requires optional dependencies.** Install `beanqueue[metrics]` (`starlette` and `uvicorn`). Without them, enabling the server raises an error at startup.
|
|
38
|
-
- **Custom health checks use the `healthz_check` event** (`bq.events.healthz_check`) instead of a `healthz_check` argument on `bq.BeanQueue`.
|
|
76
|
+
- **Custom health checks use the `healthz_check` event** (`bq.events.healthz_check`) instead of a `healthz_check` argument on `bq.BeanQueue`. Receivers that query the database should use `AsyncSession` (`await session.execute(...)`).
|
|
39
77
|
|
|
40
78
|
## Usage
|
|
41
79
|
|
|
42
80
|
You can define a basic task processor like this
|
|
43
81
|
|
|
44
82
|
```python
|
|
45
|
-
from sqlalchemy
|
|
83
|
+
from sqlalchemy import select
|
|
84
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
46
85
|
|
|
47
86
|
import bq
|
|
48
87
|
from .. import models
|
|
@@ -50,15 +89,20 @@ from .. import image_utils
|
|
|
50
89
|
|
|
51
90
|
app = bq.BeanQueue()
|
|
52
91
|
|
|
92
|
+
|
|
53
93
|
@app.processor(channel="images")
|
|
54
|
-
def resize_image(db:
|
|
55
|
-
image =
|
|
94
|
+
async def resize_image(db: AsyncSession, task: bq.Task, width: int, height: int):
|
|
95
|
+
image = (
|
|
96
|
+
await db.execute(select(models.Image).where(models.Image.task == task))
|
|
97
|
+
).scalar_one()
|
|
56
98
|
image_utils.resize(image, size=(width, height))
|
|
57
99
|
db.add(image)
|
|
58
100
|
# by default the `processor` decorator has `auto_complete` flag turns on,
|
|
59
101
|
# so it will commit the db changes for us automatically
|
|
60
102
|
```
|
|
61
103
|
|
|
104
|
+
Synchronous processors are still supported. If the function takes a `db` argument, BeanQueue injects the sync `Session` behind the `AsyncSession`. Processors that do not use the database can stay as plain `def` functions and are run in a worker thread so they do not block the event loop.
|
|
105
|
+
|
|
62
106
|
The `db` and `task` keyword arguments are optional.
|
|
63
107
|
If you don't need to access the task object, you can simply define the function without these two parameters.
|
|
64
108
|
We also provide an optional `savepoint` argument in case if you want to rollback database changes you made.
|
|
@@ -152,13 +196,14 @@ To automatically retry a task after failure, you can specify a retry policy to t
|
|
|
152
196
|
```python
|
|
153
197
|
import datetime
|
|
154
198
|
import bq
|
|
155
|
-
from sqlalchemy.
|
|
199
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
156
200
|
|
|
157
201
|
app = bq.BeanQueue()
|
|
158
202
|
delay_retry = bq.DelayRetry(delay=datetime.timedelta(seconds=120))
|
|
159
203
|
|
|
204
|
+
|
|
160
205
|
@app.processor(channel="images", retry_policy=delay_retry)
|
|
161
|
-
def resize_image(db:
|
|
206
|
+
async def resize_image(db: AsyncSession, task: bq.Task, width: int, height: int):
|
|
162
207
|
# resize image here ...
|
|
163
208
|
pass
|
|
164
209
|
```
|
|
@@ -178,8 +223,9 @@ To cap how many attempts are allowed, you can also use `LimitAttempt` like this:
|
|
|
178
223
|
delay_retry = bq.DelayRetry(delay=datetime.timedelta(seconds=120))
|
|
179
224
|
capped_delay_retry = bq.LimitAttempt(3, delay_retry)
|
|
180
225
|
|
|
226
|
+
|
|
181
227
|
@app.processor(channel="images", retry_policy=capped_delay_retry)
|
|
182
|
-
def resize_image(db:
|
|
228
|
+
async def resize_image(db: AsyncSession, task: bq.Task, width: int, height: int):
|
|
183
229
|
# Resize image here ...
|
|
184
230
|
pass
|
|
185
231
|
```
|
|
@@ -192,7 +238,7 @@ You can also retry only for specific exception classes with the `retry_exception
|
|
|
192
238
|
retry_policy=delay_retry,
|
|
193
239
|
retry_exceptions=ValueError,
|
|
194
240
|
)
|
|
195
|
-
def resize_image(db:
|
|
241
|
+
async def resize_image(db: AsyncSession, task: bq.Task, width: int, height: int):
|
|
196
242
|
# resize image here ...
|
|
197
243
|
pass
|
|
198
244
|
```
|
|
@@ -228,7 +274,9 @@ bq -a my_pkgs.bq.app process images
|
|
|
228
274
|
Or if you prefer to define your own process command, you can also call `process_tasks` of the `BeanQueue` object directly like this:
|
|
229
275
|
|
|
230
276
|
```python
|
|
231
|
-
|
|
277
|
+
import asyncio
|
|
278
|
+
|
|
279
|
+
asyncio.run(app.process_tasks(channels=("images",)))
|
|
232
280
|
```
|
|
233
281
|
|
|
234
282
|
### Health check and metrics HTTP server
|
|
@@ -294,7 +342,7 @@ Receivers may be synchronous or asynchronous; both can be mixed on the same sign
|
|
|
294
342
|
|
|
295
343
|
```python
|
|
296
344
|
from sqlalchemy import text
|
|
297
|
-
from sqlalchemy.
|
|
345
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
298
346
|
|
|
299
347
|
import bq
|
|
300
348
|
from bq import events
|
|
@@ -303,12 +351,12 @@ app = bq.BeanQueue()
|
|
|
303
351
|
|
|
304
352
|
|
|
305
353
|
@events.healthz_check.connect
|
|
306
|
-
def check_database(sender: bq.BeanQueue, worker, session:
|
|
307
|
-
session.execute(text("SELECT 1"))
|
|
354
|
+
async def check_database(sender: bq.BeanQueue, worker, session: AsyncSession):
|
|
355
|
+
await session.execute(text("SELECT 1"))
|
|
308
356
|
|
|
309
357
|
|
|
310
358
|
@events.healthz_check.connect
|
|
311
|
-
async def check_external_service(sender: bq.BeanQueue, worker, session:
|
|
359
|
+
async def check_external_service(sender: bq.BeanQueue, worker, session: AsyncSession):
|
|
312
360
|
# async HTTP call, etc.
|
|
313
361
|
...
|
|
314
362
|
```
|
|
@@ -361,6 +409,7 @@ class Task(bq.TaskModelMixin, Base):
|
|
|
361
409
|
"Worker", back_populates="tasks", uselist=False
|
|
362
410
|
)
|
|
363
411
|
|
|
412
|
+
|
|
364
413
|
listen_events(Task)
|
|
365
414
|
```
|
|
366
415
|
|
|
@@ -393,6 +442,7 @@ With the model class ready, you only need to change the `TASK_MODEL`, `WORKER_MO
|
|
|
393
442
|
|
|
394
443
|
```python
|
|
395
444
|
import bq
|
|
445
|
+
|
|
396
446
|
config = bq.Config(
|
|
397
447
|
TASK_MODEL="my_pkgs.models.Task",
|
|
398
448
|
WORKER_MODEL="my_pkgs.models.Worker",
|
|
@@ -1,25 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: beanqueue
|
|
3
|
-
Version: 2.0.0rc0
|
|
4
|
-
Summary: BeanQueue or BQ for short, PostgreSQL SKIP LOCK and SQLAlchemy based worker queue library
|
|
5
|
-
Author-email: Fang-Pen Lin <fangpen@launchplatform.com>
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
License-File: LICENSE
|
|
8
|
-
Requires-Python: <4,>=3.11
|
|
9
|
-
Requires-Dist: blinker<2,>=1.8.2
|
|
10
|
-
Requires-Dist: click<9,>=8.1.7
|
|
11
|
-
Requires-Dist: pydantic-settings<3,>=2.2.1
|
|
12
|
-
Requires-Dist: rich<14,>=13.7.1
|
|
13
|
-
Requires-Dist: sqlalchemy<3,>=2.0.30
|
|
14
|
-
Requires-Dist: venusian<4,>=3.1.0
|
|
15
|
-
Provides-Extra: metrics
|
|
16
|
-
Requires-Dist: starlette<2,>=0.27; extra == 'metrics'
|
|
17
|
-
Requires-Dist: uvicorn<1,>=0.30.0; extra == 'metrics'
|
|
18
|
-
Description-Content-Type: text/markdown
|
|
19
|
-
|
|
20
1
|
# BeanQueue [](https://dl.circleci.com/status-badge/redirect/gh/LaunchPlatform/bq/tree/master)
|
|
21
2
|
|
|
22
|
-
BeanQueue, a lightweight Python task queue framework based on [SQLAlchemy](https://www.sqlalchemy.org/), PostgreSQL [SKIP LOCKED queries](https://www.2ndquadrant.com/en/blog/what-is-select-skip-locked-for-in-postgresql-9-5/) and [NOTIFY](https://www.postgresql.org/docs/current/sql-notify.html) / [LISTEN](https://www.postgresql.org/docs/current/sql-listen.html) statements.
|
|
3
|
+
BeanQueue, a lightweight Python task queue framework based on [SQLAlchemy](https://www.sqlalchemy.org/) 2.0 asyncio, [psycopg3](https://www.psycopg.org/psycopg3/), PostgreSQL [SKIP LOCKED queries](https://www.2ndquadrant.com/en/blog/what-is-select-skip-locked-for-in-postgresql-9-5/) and [NOTIFY](https://www.postgresql.org/docs/current/sql-notify.html) / [LISTEN](https://www.postgresql.org/docs/current/sql-listen.html) statements.
|
|
23
4
|
|
|
24
5
|
**Notice**: Still in its early stage, we built this for [BeanHub](https://beanhub.io)'s internal usage. May change rapidly. Use at your own risk for now.
|
|
25
6
|
|
|
@@ -48,20 +29,39 @@ To enable the optional metrics HTTP server (currently `/healthz` only), install
|
|
|
48
29
|
pip install "beanqueue[metrics]"
|
|
49
30
|
```
|
|
50
31
|
|
|
32
|
+
## Testing
|
|
33
|
+
|
|
34
|
+
Unit and in-process acceptance tests:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uv run python -m pytest tests
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
End-to-end tests start PostgreSQL and three worker containers with Docker Compose, enqueue real tasks, apply load, kill a worker, and check graceful shutdown cleanup:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uv run python -m pytest tests/e2e -svvvv
|
|
44
|
+
```
|
|
45
|
+
|
|
51
46
|
## Upgrading to 2.0
|
|
52
47
|
|
|
53
|
-
BeanQueue 2.0
|
|
48
|
+
BeanQueue 2.0 is asyncio-first (SQLAlchemy `AsyncSession` + psycopg3) and includes breaking changes:
|
|
54
49
|
|
|
50
|
+
- **Workers use asyncio SQLAlchemy.** `BeanQueue.make_session()` returns an `AsyncSession`, `engine` is an `AsyncEngine`, and `process_tasks()` is `async`. Run it with `asyncio.run(app.process_tasks(...))` or `await` it from your own loop.
|
|
51
|
+
- **Database URLs use the psycopg3 driver.** Prefer `postgresql+psycopg://...`. Bare `postgresql://...` URLs are normalized to `postgresql+psycopg://...`.
|
|
52
|
+
- **`MAX_WORKER_THREADS` is now `MAX_CONCURRENT_TASKS`.** `BQ_MAX_WORKER_THREADS` and the old keyword still work as a deprecated alias.
|
|
53
|
+
- **Processors may be `async def` or `def`.** Async processors receive `db: AsyncSession`. Sync processors that take `db` still receive a sync `Session` via `AsyncSession.run_sync()`.
|
|
55
54
|
- **`METRICS_HTTP_SERVER_ENABLED` defaults to `False`** (it was `True` in 1.x). Set `BQ_METRICS_HTTP_SERVER_ENABLED=true` to turn the server back on.
|
|
56
55
|
- **The metrics server requires optional dependencies.** Install `beanqueue[metrics]` (`starlette` and `uvicorn`). Without them, enabling the server raises an error at startup.
|
|
57
|
-
- **Custom health checks use the `healthz_check` event** (`bq.events.healthz_check`) instead of a `healthz_check` argument on `bq.BeanQueue`.
|
|
56
|
+
- **Custom health checks use the `healthz_check` event** (`bq.events.healthz_check`) instead of a `healthz_check` argument on `bq.BeanQueue`. Receivers that query the database should use `AsyncSession` (`await session.execute(...)`).
|
|
58
57
|
|
|
59
58
|
## Usage
|
|
60
59
|
|
|
61
60
|
You can define a basic task processor like this
|
|
62
61
|
|
|
63
62
|
```python
|
|
64
|
-
from sqlalchemy
|
|
63
|
+
from sqlalchemy import select
|
|
64
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
65
65
|
|
|
66
66
|
import bq
|
|
67
67
|
from .. import models
|
|
@@ -69,15 +69,20 @@ from .. import image_utils
|
|
|
69
69
|
|
|
70
70
|
app = bq.BeanQueue()
|
|
71
71
|
|
|
72
|
+
|
|
72
73
|
@app.processor(channel="images")
|
|
73
|
-
def resize_image(db:
|
|
74
|
-
image =
|
|
74
|
+
async def resize_image(db: AsyncSession, task: bq.Task, width: int, height: int):
|
|
75
|
+
image = (
|
|
76
|
+
await db.execute(select(models.Image).where(models.Image.task == task))
|
|
77
|
+
).scalar_one()
|
|
75
78
|
image_utils.resize(image, size=(width, height))
|
|
76
79
|
db.add(image)
|
|
77
80
|
# by default the `processor` decorator has `auto_complete` flag turns on,
|
|
78
81
|
# so it will commit the db changes for us automatically
|
|
79
82
|
```
|
|
80
83
|
|
|
84
|
+
Synchronous processors are still supported. If the function takes a `db` argument, BeanQueue injects the sync `Session` behind the `AsyncSession`. Processors that do not use the database can stay as plain `def` functions and are run in a worker thread so they do not block the event loop.
|
|
85
|
+
|
|
81
86
|
The `db` and `task` keyword arguments are optional.
|
|
82
87
|
If you don't need to access the task object, you can simply define the function without these two parameters.
|
|
83
88
|
We also provide an optional `savepoint` argument in case if you want to rollback database changes you made.
|
|
@@ -171,13 +176,14 @@ To automatically retry a task after failure, you can specify a retry policy to t
|
|
|
171
176
|
```python
|
|
172
177
|
import datetime
|
|
173
178
|
import bq
|
|
174
|
-
from sqlalchemy.
|
|
179
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
175
180
|
|
|
176
181
|
app = bq.BeanQueue()
|
|
177
182
|
delay_retry = bq.DelayRetry(delay=datetime.timedelta(seconds=120))
|
|
178
183
|
|
|
184
|
+
|
|
179
185
|
@app.processor(channel="images", retry_policy=delay_retry)
|
|
180
|
-
def resize_image(db:
|
|
186
|
+
async def resize_image(db: AsyncSession, task: bq.Task, width: int, height: int):
|
|
181
187
|
# resize image here ...
|
|
182
188
|
pass
|
|
183
189
|
```
|
|
@@ -197,8 +203,9 @@ To cap how many attempts are allowed, you can also use `LimitAttempt` like this:
|
|
|
197
203
|
delay_retry = bq.DelayRetry(delay=datetime.timedelta(seconds=120))
|
|
198
204
|
capped_delay_retry = bq.LimitAttempt(3, delay_retry)
|
|
199
205
|
|
|
206
|
+
|
|
200
207
|
@app.processor(channel="images", retry_policy=capped_delay_retry)
|
|
201
|
-
def resize_image(db:
|
|
208
|
+
async def resize_image(db: AsyncSession, task: bq.Task, width: int, height: int):
|
|
202
209
|
# Resize image here ...
|
|
203
210
|
pass
|
|
204
211
|
```
|
|
@@ -211,7 +218,7 @@ You can also retry only for specific exception classes with the `retry_exception
|
|
|
211
218
|
retry_policy=delay_retry,
|
|
212
219
|
retry_exceptions=ValueError,
|
|
213
220
|
)
|
|
214
|
-
def resize_image(db:
|
|
221
|
+
async def resize_image(db: AsyncSession, task: bq.Task, width: int, height: int):
|
|
215
222
|
# resize image here ...
|
|
216
223
|
pass
|
|
217
224
|
```
|
|
@@ -247,7 +254,9 @@ bq -a my_pkgs.bq.app process images
|
|
|
247
254
|
Or if you prefer to define your own process command, you can also call `process_tasks` of the `BeanQueue` object directly like this:
|
|
248
255
|
|
|
249
256
|
```python
|
|
250
|
-
|
|
257
|
+
import asyncio
|
|
258
|
+
|
|
259
|
+
asyncio.run(app.process_tasks(channels=("images",)))
|
|
251
260
|
```
|
|
252
261
|
|
|
253
262
|
### Health check and metrics HTTP server
|
|
@@ -313,7 +322,7 @@ Receivers may be synchronous or asynchronous; both can be mixed on the same sign
|
|
|
313
322
|
|
|
314
323
|
```python
|
|
315
324
|
from sqlalchemy import text
|
|
316
|
-
from sqlalchemy.
|
|
325
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
317
326
|
|
|
318
327
|
import bq
|
|
319
328
|
from bq import events
|
|
@@ -322,12 +331,12 @@ app = bq.BeanQueue()
|
|
|
322
331
|
|
|
323
332
|
|
|
324
333
|
@events.healthz_check.connect
|
|
325
|
-
def check_database(sender: bq.BeanQueue, worker, session:
|
|
326
|
-
session.execute(text("SELECT 1"))
|
|
334
|
+
async def check_database(sender: bq.BeanQueue, worker, session: AsyncSession):
|
|
335
|
+
await session.execute(text("SELECT 1"))
|
|
327
336
|
|
|
328
337
|
|
|
329
338
|
@events.healthz_check.connect
|
|
330
|
-
async def check_external_service(sender: bq.BeanQueue, worker, session:
|
|
339
|
+
async def check_external_service(sender: bq.BeanQueue, worker, session: AsyncSession):
|
|
331
340
|
# async HTTP call, etc.
|
|
332
341
|
...
|
|
333
342
|
```
|
|
@@ -380,6 +389,7 @@ class Task(bq.TaskModelMixin, Base):
|
|
|
380
389
|
"Worker", back_populates="tasks", uselist=False
|
|
381
390
|
)
|
|
382
391
|
|
|
392
|
+
|
|
383
393
|
listen_events(Task)
|
|
384
394
|
```
|
|
385
395
|
|
|
@@ -412,6 +422,7 @@ With the model class ready, you only need to change the `TASK_MODEL`, `WORKER_MO
|
|
|
412
422
|
|
|
413
423
|
```python
|
|
414
424
|
import bq
|
|
425
|
+
|
|
415
426
|
config = bq.Config(
|
|
416
427
|
TASK_MODEL="my_pkgs.models.Task",
|
|
417
428
|
WORKER_MODEL="my_pkgs.models.Worker",
|