flask-async-celery 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.
- flask_async_celery-0.1.0/PKG-INFO +597 -0
- flask_async_celery-0.1.0/README.md +564 -0
- flask_async_celery-0.1.0/pyproject.toml +66 -0
- flask_async_celery-0.1.0/setup.cfg +4 -0
- flask_async_celery-0.1.0/src/flask_async_celery/__init__.py +9 -0
- flask_async_celery-0.1.0/src/flask_async_celery/bootstep.py +84 -0
- flask_async_celery-0.1.0/src/flask_async_celery/executor.py +283 -0
- flask_async_celery-0.1.0/src/flask_async_celery/extension.py +111 -0
- flask_async_celery-0.1.0/src/flask_async_celery/pool.py +235 -0
- flask_async_celery-0.1.0/src/flask_async_celery/task.py +63 -0
- flask_async_celery-0.1.0/src/flask_async_celery.egg-info/PKG-INFO +597 -0
- flask_async_celery-0.1.0/src/flask_async_celery.egg-info/SOURCES.txt +20 -0
- flask_async_celery-0.1.0/src/flask_async_celery.egg-info/dependency_links.txt +1 -0
- flask_async_celery-0.1.0/src/flask_async_celery.egg-info/requires.txt +15 -0
- flask_async_celery-0.1.0/src/flask_async_celery.egg-info/top_level.txt +1 -0
- flask_async_celery-0.1.0/test/test_backpressure.py +102 -0
- flask_async_celery-0.1.0/test/test_celery_pool.py +77 -0
- flask_async_celery-0.1.0/test/test_executor.py +54 -0
- flask_async_celery-0.1.0/test/test_extension.py +68 -0
- flask_async_celery-0.1.0/test/test_extension_integration.py +46 -0
- flask_async_celery-0.1.0/test/test_tasks.py +85 -0
- flask_async_celery-0.1.0/test/test_worker_integration.py +134 -0
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flask-async-celery
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AsyncIO execution pool for Celery with Flask integration
|
|
5
|
+
Author: Mazhar Ali
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: celery,asyncio,flask,async,tasks,redis
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Framework :: Flask
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: celery<5.7,>=5.6
|
|
22
|
+
Requires-Dist: Flask>=2.3
|
|
23
|
+
Provides-Extra: redis
|
|
24
|
+
Requires-Dist: redis>=5.0; extra == "redis"
|
|
25
|
+
Provides-Extra: test
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == "test"
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
|
|
31
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
32
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
33
|
+
|
|
34
|
+
# Flask Async Celery
|
|
35
|
+
|
|
36
|
+
Run `async def` Celery tasks on a persistent asyncio event loop with bounded concurrency, Flask integration, and Celery consumer backpressure.
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
* Persistent `asyncio` event loop in a dedicated thread per Celery worker process.
|
|
41
|
+
* Run native `async def` Celery tasks.
|
|
42
|
+
* Bounded asynchronous concurrency with `max_tasks`.
|
|
43
|
+
* Celery request context propagation into the asyncio execution thread.
|
|
44
|
+
* `self.retry()` support for async tasks.
|
|
45
|
+
* Normal synchronous Celery tasks continue to work.
|
|
46
|
+
* Redis consumer-side backpressure through Celery's `worker_disable_prefetch`.
|
|
47
|
+
* Flask extension with simple configuration.
|
|
48
|
+
* Graceful asyncio executor shutdown.
|
|
49
|
+
* Compatible with Celery 5.6.x and Python 3.10+.
|
|
50
|
+
|
|
51
|
+
## Architecture
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
Redis
|
|
55
|
+
│
|
|
56
|
+
▼
|
|
57
|
+
Celery Consumer
|
|
58
|
+
│
|
|
59
|
+
worker_disable_prefetch
|
|
60
|
+
│
|
|
61
|
+
▼
|
|
62
|
+
AsyncIOPool
|
|
63
|
+
max_tasks = N
|
|
64
|
+
│
|
|
65
|
+
▼
|
|
66
|
+
AsyncExecutor
|
|
67
|
+
asyncio.Semaphore(N)
|
|
68
|
+
│
|
|
69
|
+
Persistent loop
|
|
70
|
+
│
|
|
71
|
+
┌────────────┼────────────┐
|
|
72
|
+
▼ ▼ ▼
|
|
73
|
+
Async task Async task Async task
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The package separates Celery's worker execution from asyncio execution:
|
|
77
|
+
|
|
78
|
+
1. Celery receives and traces the task.
|
|
79
|
+
2. `AsyncIOPool` bridges Celery execution into the asyncio executor.
|
|
80
|
+
3. `AsyncExecutor` owns a persistent asyncio event loop.
|
|
81
|
+
4. A semaphore limits active async tasks.
|
|
82
|
+
5. Celery's Redis `worker_disable_prefetch` option can prevent the consumer from reserving work beyond the available pool capacity.
|
|
83
|
+
|
|
84
|
+
## Requirements
|
|
85
|
+
|
|
86
|
+
* Python 3.10+
|
|
87
|
+
* Celery 5.6.x
|
|
88
|
+
* Flask 2.3+
|
|
89
|
+
* Redis when using the Redis broker/result backend and consumer backpressure.
|
|
90
|
+
|
|
91
|
+
## Installation
|
|
92
|
+
|
|
93
|
+
From PyPI:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install flask-async-celery
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
For Redis support:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install "flask-async-celery[redis]"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
For development and testing:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install "flask-async-celery[test]"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Basic Flask Setup
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
import asyncio
|
|
115
|
+
|
|
116
|
+
from flask import Flask
|
|
117
|
+
|
|
118
|
+
from flask_async_celery import AsyncCelery, AsyncTask
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
app = Flask(__name__)
|
|
122
|
+
|
|
123
|
+
celery = AsyncCelery(
|
|
124
|
+
app,
|
|
125
|
+
broker_url="redis://127.0.0.1:6379/0",
|
|
126
|
+
result_backend="redis://127.0.0.1:6379/1",
|
|
127
|
+
max_tasks=5,
|
|
128
|
+
disable_prefetch=True,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@celery.task(base=AsyncTask)
|
|
133
|
+
async def my_task(value):
|
|
134
|
+
await asyncio.sleep(1)
|
|
135
|
+
return value * 2
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Send the task normally:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
result = my_task.delay(10)
|
|
142
|
+
|
|
143
|
+
print(result.get(timeout=30))
|
|
144
|
+
# 20
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Flask Configuration
|
|
148
|
+
|
|
149
|
+
Configuration can be supplied through Flask:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
app.config["ASYNC_CELERY_MAX_TASKS"] = 10
|
|
153
|
+
app.config["ASYNC_CELERY_DISABLE_PREFETCH"] = True
|
|
154
|
+
|
|
155
|
+
celery = AsyncCelery(
|
|
156
|
+
app,
|
|
157
|
+
broker_url="redis://127.0.0.1:6379/0",
|
|
158
|
+
result_backend="redis://127.0.0.1:6379/1",
|
|
159
|
+
)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Available settings:
|
|
163
|
+
|
|
164
|
+
| Setting | Default | Description |
|
|
165
|
+
| ------------------------------- | ------: | ----------------------------------------------------- |
|
|
166
|
+
| `ASYNC_CELERY_MAX_TASKS` | `20` | Maximum number of concurrently executing async tasks. |
|
|
167
|
+
| `ASYNC_CELERY_DISABLE_PREFETCH` | `True` | Enables Celery consumer-side backpressure. |
|
|
168
|
+
|
|
169
|
+
Constructor arguments can also be used directly:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
celery = AsyncCelery(
|
|
173
|
+
app,
|
|
174
|
+
max_tasks=10,
|
|
175
|
+
disable_prefetch=True,
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Flask configuration takes precedence over the constructor defaults when the extension is initialized.
|
|
180
|
+
|
|
181
|
+
## Worker Configuration
|
|
182
|
+
|
|
183
|
+
Run the worker using the package's custom pool:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
celery -A your_app.celery worker \
|
|
187
|
+
-P flask_async_celery.pool:AsyncIOPool \
|
|
188
|
+
-c 5 \
|
|
189
|
+
--loglevel=INFO
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
For example:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
celery -A your_app.celery worker \
|
|
196
|
+
-P flask_async_celery.pool:AsyncIOPool \
|
|
197
|
+
-c 10 \
|
|
198
|
+
--loglevel=INFO
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The `-c` value should match the desired async concurrency.
|
|
202
|
+
|
|
203
|
+
The custom pool exposes its configured concurrency through `num_processes`, allowing Celery's consumer to use the same capacity when consumer-side prefetch is disabled.
|
|
204
|
+
|
|
205
|
+
## Concurrency
|
|
206
|
+
|
|
207
|
+
Set the maximum number of simultaneously executing async tasks with:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
celery = AsyncCelery(
|
|
211
|
+
app,
|
|
212
|
+
max_tasks=5,
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
With:
|
|
217
|
+
|
|
218
|
+
```text
|
|
219
|
+
max_tasks = 5
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
the asyncio executor allows at most five active coroutines at once.
|
|
223
|
+
|
|
224
|
+
Additional work waits for an available execution slot.
|
|
225
|
+
|
|
226
|
+
This is different from simply creating more threads. The package uses one persistent asyncio event loop and runs async coroutines concurrently on that loop.
|
|
227
|
+
|
|
228
|
+
## Consumer Backpressure
|
|
229
|
+
|
|
230
|
+
For Redis, Celery 5.6 supports:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
worker_disable_prefetch = True
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The extension enables this by default:
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
celery = AsyncCelery(
|
|
240
|
+
app,
|
|
241
|
+
max_tasks=5,
|
|
242
|
+
disable_prefetch=True,
|
|
243
|
+
)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
This provides two levels of protection:
|
|
247
|
+
|
|
248
|
+
```text
|
|
249
|
+
Celery Consumer
|
|
250
|
+
│
|
|
251
|
+
│ Don't reserve beyond available capacity
|
|
252
|
+
▼
|
|
253
|
+
AsyncIOPool
|
|
254
|
+
│
|
|
255
|
+
│ max_tasks
|
|
256
|
+
▼
|
|
257
|
+
AsyncExecutor
|
|
258
|
+
│
|
|
259
|
+
│ semaphore
|
|
260
|
+
▼
|
|
261
|
+
asyncio tasks
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
You can disable the consumer-side behavior:
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
celery = AsyncCelery(
|
|
268
|
+
app,
|
|
269
|
+
max_tasks=5,
|
|
270
|
+
disable_prefetch=False,
|
|
271
|
+
)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
When disabled, the asyncio executor still enforces its own concurrency limit.
|
|
275
|
+
|
|
276
|
+
### Redis Requirement
|
|
277
|
+
|
|
278
|
+
`worker_disable_prefetch` is intended for supported Redis worker configurations. If you use another broker, verify that your Celery version and broker transport support this feature before relying on consumer-side backpressure.
|
|
279
|
+
|
|
280
|
+
The executor-level concurrency limit remains independent of consumer prefetch behavior.
|
|
281
|
+
|
|
282
|
+
## Async Tasks
|
|
283
|
+
|
|
284
|
+
Use `AsyncTask` as the base class for native async tasks:
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
from flask_async_celery import AsyncTask
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@celery.task(base=AsyncTask)
|
|
291
|
+
async def fetch_data():
|
|
292
|
+
await some_async_operation()
|
|
293
|
+
return "done"
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
The task can use normal Celery task features:
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
@celery.task(
|
|
300
|
+
base=AsyncTask,
|
|
301
|
+
bind=True,
|
|
302
|
+
max_retries=3,
|
|
303
|
+
)
|
|
304
|
+
async def process_item(self, item_id):
|
|
305
|
+
try:
|
|
306
|
+
return await process(item_id)
|
|
307
|
+
except TemporaryError as exc:
|
|
308
|
+
raise self.retry(
|
|
309
|
+
exc=exc,
|
|
310
|
+
countdown=5,
|
|
311
|
+
)
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The Celery request context is propagated from the Celery worker thread into the asyncio execution thread, so task information such as the task ID, retry count, delivery information, and retry context remains available.
|
|
315
|
+
|
|
316
|
+
## Synchronous Tasks
|
|
317
|
+
|
|
318
|
+
Normal synchronous tasks can still be used:
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
@celery.task
|
|
322
|
+
def sync_task(value):
|
|
323
|
+
return value * 2
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
The package does not require every task to be asynchronous.
|
|
327
|
+
|
|
328
|
+
## Retries
|
|
329
|
+
|
|
330
|
+
Async `self.retry()` is supported:
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
@celery.task(
|
|
334
|
+
base=AsyncTask,
|
|
335
|
+
bind=True,
|
|
336
|
+
max_retries=3,
|
|
337
|
+
)
|
|
338
|
+
async def retrying_task(self):
|
|
339
|
+
if should_retry():
|
|
340
|
+
raise self.retry(countdown=5)
|
|
341
|
+
|
|
342
|
+
return "success"
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
The Celery task request is preserved when execution moves from the Celery worker thread to the asyncio event loop. This allows Celery retry metadata and delivery information to remain available to the async task.
|
|
346
|
+
|
|
347
|
+
## Exceptions
|
|
348
|
+
|
|
349
|
+
Exceptions raised by an async task propagate through the Celery execution path:
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
@celery.task(base=AsyncTask)
|
|
353
|
+
async def failing_task():
|
|
354
|
+
raise RuntimeError("something went wrong")
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Celery remains responsible for task failure state, result handling, retry behavior, and worker-level task tracing.
|
|
358
|
+
|
|
359
|
+
## Graceful Shutdown
|
|
360
|
+
|
|
361
|
+
The asyncio executor runs in a dedicated daemon thread.
|
|
362
|
+
|
|
363
|
+
When the pool stops, the executor:
|
|
364
|
+
|
|
365
|
+
1. Stops accepting new work.
|
|
366
|
+
2. Stops the asyncio event loop.
|
|
367
|
+
3. Cancels pending asyncio tasks.
|
|
368
|
+
4. Waits for the loop thread to terminate.
|
|
369
|
+
5. Closes the event loop.
|
|
370
|
+
|
|
371
|
+
## Public API
|
|
372
|
+
|
|
373
|
+
The main public API is intentionally small:
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
from flask_async_celery import AsyncCelery, AsyncTask
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### `AsyncCelery`
|
|
380
|
+
|
|
381
|
+
Flask integration and Celery configuration.
|
|
382
|
+
|
|
383
|
+
```python
|
|
384
|
+
AsyncCelery(
|
|
385
|
+
app=None,
|
|
386
|
+
*,
|
|
387
|
+
celery=None,
|
|
388
|
+
broker_url=None,
|
|
389
|
+
result_backend=None,
|
|
390
|
+
max_tasks=20,
|
|
391
|
+
disable_prefetch=True,
|
|
392
|
+
)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### `AsyncTask`
|
|
396
|
+
|
|
397
|
+
Base class for asynchronous Celery tasks:
|
|
398
|
+
|
|
399
|
+
```python
|
|
400
|
+
@celery.task(base=AsyncTask)
|
|
401
|
+
async def my_task():
|
|
402
|
+
...
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## Development
|
|
406
|
+
|
|
407
|
+
Clone the repository and install the project in editable mode:
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
git clone <repository-url>
|
|
411
|
+
cd flask-async-celery
|
|
412
|
+
|
|
413
|
+
pip install -e ".[test]"
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Run the test suite:
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
pytest -v
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
The test suite covers:
|
|
423
|
+
|
|
424
|
+
* asyncio executor concurrency
|
|
425
|
+
* AsyncIOPool execution
|
|
426
|
+
* async task exceptions
|
|
427
|
+
* async retries
|
|
428
|
+
* synchronous retries
|
|
429
|
+
* real Celery worker integration
|
|
430
|
+
* Redis consumer backpressure
|
|
431
|
+
* Flask extension configuration
|
|
432
|
+
* end-to-end Flask/Celery/async execution
|
|
433
|
+
|
|
434
|
+
## Project Structure
|
|
435
|
+
|
|
436
|
+
```text
|
|
437
|
+
flask-async-celery/
|
|
438
|
+
├── pyproject.toml
|
|
439
|
+
├── README.md
|
|
440
|
+
├── src/
|
|
441
|
+
│ └── flask_async_celery/
|
|
442
|
+
│ ├── __init__.py
|
|
443
|
+
│ ├── extension.py
|
|
444
|
+
│ ├── executor.py
|
|
445
|
+
│ ├── bootstep.py
|
|
446
|
+
│ ├── pool.py
|
|
447
|
+
│ └── task.py
|
|
448
|
+
└── test/
|
|
449
|
+
├── conftest.py
|
|
450
|
+
├── test_executor.py
|
|
451
|
+
├── test_tasks.py
|
|
452
|
+
├── test_backpressure.py
|
|
453
|
+
├── test_celery_pool.py
|
|
454
|
+
├── test_worker_integration.py
|
|
455
|
+
├── test_extension.py
|
|
456
|
+
└── test_extension_integration.py
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## Design Notes
|
|
460
|
+
|
|
461
|
+
This package does not replace Celery's task tracing and lifecycle handling.
|
|
462
|
+
|
|
463
|
+
Celery remains responsible for:
|
|
464
|
+
|
|
465
|
+
* task delivery
|
|
466
|
+
* task acknowledgment
|
|
467
|
+
* retries
|
|
468
|
+
* result state
|
|
469
|
+
* task IDs
|
|
470
|
+
* worker lifecycle
|
|
471
|
+
* task tracing
|
|
472
|
+
|
|
473
|
+
The package provides the asyncio execution layer and integrates it with Celery's pool interface.
|
|
474
|
+
|
|
475
|
+
The asyncio event loop is persistent for the lifetime of the worker process rather than creating a new event loop for every task.
|
|
476
|
+
|
|
477
|
+
### Why a Persistent Event Loop?
|
|
478
|
+
|
|
479
|
+
Creating a new event loop for every task adds unnecessary setup and teardown overhead.
|
|
480
|
+
|
|
481
|
+
Instead, each worker process owns one persistent asyncio event loop:
|
|
482
|
+
|
|
483
|
+
```text
|
|
484
|
+
Celery Worker Process
|
|
485
|
+
│
|
|
486
|
+
├── Celery Consumer
|
|
487
|
+
├── Celery task execution
|
|
488
|
+
├── bridge threads
|
|
489
|
+
│
|
|
490
|
+
└── asyncio event loop thread
|
|
491
|
+
├── Task A
|
|
492
|
+
├── Task B
|
|
493
|
+
└── Task C
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
Async tasks can therefore share the same event loop while still being bounded by `max_tasks`.
|
|
497
|
+
|
|
498
|
+
### Why Request Propagation?
|
|
499
|
+
|
|
500
|
+
Celery's request context is associated with the worker execution context.
|
|
501
|
+
|
|
502
|
+
The asyncio event loop runs in a separate thread, so the package explicitly transfers the current Celery request into that execution context.
|
|
503
|
+
|
|
504
|
+
This preserves information required by features such as:
|
|
505
|
+
|
|
506
|
+
```python
|
|
507
|
+
self.request.id
|
|
508
|
+
self.request.retries
|
|
509
|
+
self.request.delivery_info
|
|
510
|
+
self.retry()
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
The request is pushed before async execution and removed afterward.
|
|
514
|
+
|
|
515
|
+
## Limitations
|
|
516
|
+
|
|
517
|
+
### Broker-specific Backpressure
|
|
518
|
+
|
|
519
|
+
Consumer-side `worker_disable_prefetch` support depends on Celery and the broker transport.
|
|
520
|
+
|
|
521
|
+
The asyncio executor's own `max_tasks` limit remains the final execution boundary.
|
|
522
|
+
|
|
523
|
+
### Worker Pool
|
|
524
|
+
|
|
525
|
+
The worker must use:
|
|
526
|
+
|
|
527
|
+
```text
|
|
528
|
+
flask_async_celery.pool:AsyncIOPool
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
for the package's asyncio execution model.
|
|
532
|
+
|
|
533
|
+
### One Event Loop Per Worker Process
|
|
534
|
+
|
|
535
|
+
Each worker process owns its own asyncio event loop and concurrency limit.
|
|
536
|
+
|
|
537
|
+
For example:
|
|
538
|
+
|
|
539
|
+
```bash
|
|
540
|
+
celery -A your_app.celery worker \
|
|
541
|
+
-P flask_async_celery.pool:AsyncIOPool \
|
|
542
|
+
-c 5
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
creates a worker configuration with five execution slots.
|
|
546
|
+
|
|
547
|
+
If you run multiple worker processes, each process has its own pool and event loop.
|
|
548
|
+
|
|
549
|
+
## Testing
|
|
550
|
+
|
|
551
|
+
Run the complete test suite:
|
|
552
|
+
|
|
553
|
+
```bash
|
|
554
|
+
pytest -v
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
The project currently tests the execution model with real Celery workers in addition to unit-level executor and pool tests.
|
|
558
|
+
|
|
559
|
+
A successful test run should show all tests passing.
|
|
560
|
+
|
|
561
|
+
## Building the Package
|
|
562
|
+
|
|
563
|
+
Install the build tooling:
|
|
564
|
+
|
|
565
|
+
```bash
|
|
566
|
+
python -m pip install build
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
Build the source distribution and wheel:
|
|
570
|
+
|
|
571
|
+
```bash
|
|
572
|
+
python -m build
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
The generated files will be placed in:
|
|
576
|
+
|
|
577
|
+
```text
|
|
578
|
+
dist/
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
You can inspect the generated wheel with:
|
|
582
|
+
|
|
583
|
+
```bash
|
|
584
|
+
python -m pip install dist/*.whl
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
## Version
|
|
588
|
+
|
|
589
|
+
Current version:
|
|
590
|
+
|
|
591
|
+
```text
|
|
592
|
+
0.1.0
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
## License
|
|
596
|
+
|
|
597
|
+
Add the project's license information here.
|