queueio 0.1.2__tar.gz → 0.2.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.
- queueio-0.2.0/.devcontainer/devcontainer.json +7 -0
- queueio-0.2.0/.devcontainer/docker-compose.yml +10 -0
- queueio-0.2.0/.github/workflows/build.yml +38 -0
- queueio-0.2.0/.github/workflows/publish.yml +17 -0
- queueio-0.2.0/.gitignore +17 -0
- queueio-0.2.0/CHANGELOG.md +32 -0
- queueio-0.2.0/LICENSE +21 -0
- queueio-0.2.0/PKG-INFO +118 -0
- queueio-0.2.0/README.md +96 -0
- queueio-0.2.0/dill.pyi +4 -0
- queueio-0.2.0/logo.svg +1 -0
- queueio-0.2.0/principles.md +28 -0
- queueio-0.2.0/pyproject.toml +66 -0
- queueio-0.2.0/pyrightconfig.json +4 -0
- queueio-0.2.0/queueio/__init__.py +12 -0
- queueio-0.2.0/queueio/__main__.py +155 -0
- queueio-0.2.0/queueio/broker.py +45 -0
- queueio-0.2.0/queueio/broker_test.py +499 -0
- queueio-0.2.0/queueio/conftest.py +32 -0
- queueio-0.2.0/queueio/consumer.py +92 -0
- queueio-0.2.0/queueio/continuation.py +26 -0
- queueio-0.2.0/queueio/event.py +15 -0
- queueio-0.2.0/queueio/freethreading_test.py +28 -0
- queueio-0.2.0/queueio/gather.py +77 -0
- queueio-0.2.0/queueio/id.py +8 -0
- queueio-0.2.0/queueio/invocation.py +119 -0
- queueio-0.2.0/queueio/journal.py +23 -0
- queueio-0.2.0/queueio/journal_test.py +155 -0
- queueio-0.2.0/queueio/message.py +8 -0
- queueio-0.2.0/queueio/monitor.py +120 -0
- queueio-0.2.0/queueio/pause.py +21 -0
- queueio-0.2.0/queueio/pika/__init__.py +0 -0
- queueio-0.2.0/queueio/pika/broker.py +53 -0
- queueio-0.2.0/queueio/pika/broker_test.py +23 -0
- queueio-0.2.0/queueio/pika/journal.py +82 -0
- queueio-0.2.0/queueio/pika/journal_test.py +20 -0
- queueio-0.2.0/queueio/pika/receiver.py +72 -0
- queueio-0.2.0/queueio/pika/threadsafe.py +199 -0
- queueio-0.2.0/queueio/queue.py +183 -0
- queueio-0.2.0/queueio/queue_test.py +729 -0
- queueio-0.2.0/queueio/queueio.py +196 -0
- queueio-0.2.0/queueio/queueio_test.py +558 -0
- queueio-0.2.0/queueio/queuespec.md +31 -0
- queueio-0.2.0/queueio/queuespec.py +59 -0
- queueio-0.2.0/queueio/queuespec_test.py +67 -0
- queueio-0.2.0/queueio/receiver.py +47 -0
- queueio-0.2.0/queueio/registry.py +19 -0
- queueio-0.2.0/queueio/result.py +14 -0
- queueio-0.2.0/queueio/routine.py +16 -0
- queueio-0.2.0/queueio/samples/__init__.py +0 -0
- queueio-0.2.0/queueio/samples/basic.py +22 -0
- queueio-0.2.0/queueio/samples/basic_test.py +39 -0
- queueio-0.2.0/queueio/samples/expanded.py +45 -0
- queueio-0.2.0/queueio/samples/expanded_test.py +42 -0
- queueio-0.2.0/queueio/select.py +170 -0
- queueio-0.2.0/queueio/stream.py +76 -0
- queueio-0.2.0/queueio/stub/__init__.py +0 -0
- queueio-0.2.0/queueio/stub/broker.py +69 -0
- queueio-0.2.0/queueio/stub/broker_test.py +22 -0
- queueio-0.2.0/queueio/stub/journal.py +36 -0
- queueio-0.2.0/queueio/stub/journal_test.py +19 -0
- queueio-0.2.0/queueio/stub/receiver.py +70 -0
- queueio-0.2.0/queueio/suspension.py +33 -0
- queueio-0.2.0/queueio/thread.py +50 -0
- queueio-0.2.0/queueio/worker.py +239 -0
- queueio-0.1.2/LICENSE +0 -674
- queueio-0.1.2/MANIFEST.in +0 -1
- queueio-0.1.2/PKG-INFO +0 -41
- queueio-0.1.2/README.rst +0 -20
- queueio-0.1.2/queueio/__init__.py +0 -147
- queueio-0.1.2/queueio.egg-info/PKG-INFO +0 -41
- queueio-0.1.2/queueio.egg-info/SOURCES.txt +0 -10
- queueio-0.1.2/queueio.egg-info/dependency_links.txt +0 -1
- queueio-0.1.2/queueio.egg-info/top_level.txt +0 -1
- queueio-0.1.2/setup.cfg +0 -8
- queueio-0.1.2/setup.py +0 -50
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Build
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
build:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
timeout-minutes: 5
|
|
9
|
+
services:
|
|
10
|
+
rabbitmq:
|
|
11
|
+
image: rabbitmq
|
|
12
|
+
env:
|
|
13
|
+
RABBITMQ_DEFAULT_USER: guest
|
|
14
|
+
RABBITMQ_DEFAULT_PASS: guest
|
|
15
|
+
ports:
|
|
16
|
+
- 5672:5672
|
|
17
|
+
options: >-
|
|
18
|
+
--health-cmd "rabbitmq-diagnostics -q ping"
|
|
19
|
+
--health-interval 10s
|
|
20
|
+
--health-timeout 5s
|
|
21
|
+
--health-retries 5
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@v3
|
|
26
|
+
- name: Set up Python
|
|
27
|
+
run: uv python install 3.14t
|
|
28
|
+
- name: Install dependencies
|
|
29
|
+
run: uv sync --all-groups
|
|
30
|
+
- name: Check formatting
|
|
31
|
+
run: uv run ruff format --check
|
|
32
|
+
- name: Run ruff linting
|
|
33
|
+
run: uv run ruff check
|
|
34
|
+
- name: Run basedpyright
|
|
35
|
+
run: uv run basedpyright
|
|
36
|
+
- name: Run tests
|
|
37
|
+
run: uv run pytest
|
|
38
|
+
timeout-minutes: 2
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types:
|
|
5
|
+
- published
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
permissions:
|
|
10
|
+
id-token: write
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v5
|
|
15
|
+
- run: uv build
|
|
16
|
+
- name: Publish package distributions to PyPI
|
|
17
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
queueio-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Changelog
|
|
2
|
+
=========
|
|
3
|
+
|
|
4
|
+
All notable changes to this project will be documented in this file.
|
|
5
|
+
|
|
6
|
+
The format is loosely based on [Keep a Changelog](https://keepachangelog.com).
|
|
7
|
+
|
|
8
|
+
[0.2.0] - 2025-11-22
|
|
9
|
+
--------------------
|
|
10
|
+
|
|
11
|
+
### Acknowledgement
|
|
12
|
+
|
|
13
|
+
Thank you to Nick Anderegg for allowing me to use the queueio name for this project.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `routine` decorator to declare sync or async functions as background routines.
|
|
18
|
+
- `activate` context manager to activate the queueio system.
|
|
19
|
+
- `pause` to coordinate a pause of a routine to queueio.
|
|
20
|
+
- `gather` to run multiple routines concurrently and gather the results.
|
|
21
|
+
- `Routine.submit()` method to submit a routine invocation to the queue.
|
|
22
|
+
- Configuration in the `tool.queueio` section of `pyproject.toml`.
|
|
23
|
+
- `pika` configures the pika library to connect to the AMQP broker.
|
|
24
|
+
- `register` configures the modules that declare routines.
|
|
25
|
+
- `QUEUEIO_PIKA` environment variable
|
|
26
|
+
to override the `pika` configuration in `pyproject.toml`.
|
|
27
|
+
- `queueio sync` command to synchronize queues to the broker.
|
|
28
|
+
- `queueio run` command to run the queueio worker.
|
|
29
|
+
- The queuespec syntax to `queue run` to consume multiple queues with shared capacity.
|
|
30
|
+
- `queueio monitor` command to monitor activity in the queueio system.
|
|
31
|
+
|
|
32
|
+
[0.2.0]: https://github.com/ryanhiebert/queueio/releases/tag/0.2
|
queueio-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ryan Hiebert
|
|
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.
|
queueio-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: queueio
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python background queues with an async twist
|
|
5
|
+
Author-email: Ryan Hiebert <ryan@ryanhiebert.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: async,background,queue,rabbitmq,tasks
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
16
|
+
Requires-Python: >=3.14
|
|
17
|
+
Requires-Dist: dill>=0.3.9
|
|
18
|
+
Requires-Dist: pika>=1.3.2
|
|
19
|
+
Requires-Dist: textual>=3.2.0
|
|
20
|
+
Requires-Dist: typer>=0.15.4
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
Python background queues with an async twist
|
|
26
|
+
============================================
|
|
27
|
+
|
|
28
|
+
Use async functions to manage complex background task workflows,
|
|
29
|
+
and keep using synchronous functions for everything else.
|
|
30
|
+
|
|
31
|
+
Getting Started
|
|
32
|
+
---------------
|
|
33
|
+
|
|
34
|
+
Install `queueio`:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
pip install queueio
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Create your routines:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
# basic.py
|
|
44
|
+
from time import sleep
|
|
45
|
+
|
|
46
|
+
from queueio import gather
|
|
47
|
+
from queueio import pause
|
|
48
|
+
from queueio import routine
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@routine(name="blocking", queue="queueio")
|
|
52
|
+
def blocking():
|
|
53
|
+
sleep(0.1) # Regular blocking call
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@routine(name="yielding", queue="queueio")
|
|
57
|
+
async def yielding(iterations: int):
|
|
58
|
+
# Do them two at a time
|
|
59
|
+
for _ in range(iterations // 2):
|
|
60
|
+
await gather(blocking(), blocking())
|
|
61
|
+
await pause(0.2) # Release processing capacity
|
|
62
|
+
if iterations % 2 == 1:
|
|
63
|
+
await blocking()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Add the configuration to your `pyproject.toml`:
|
|
67
|
+
|
|
68
|
+
```toml
|
|
69
|
+
[tool.queueio]
|
|
70
|
+
# Configure RabbitMQ
|
|
71
|
+
pika = "amqp://guest:guest@localhost:5672/"
|
|
72
|
+
# Register the modules that the worker should load to find your routines
|
|
73
|
+
register = ["basic"]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The pika configuration can be overridden with an environment variable
|
|
77
|
+
to allow a project to be deployed in multiple environments.
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
QUEUEIO_PIKA='amqp://guest:guest@localhost:5672/'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Sync the queues to the broker:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
queueio sync
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Submit the routine to run on a worker:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from queueio import activate
|
|
93
|
+
from basic import yielding
|
|
94
|
+
|
|
95
|
+
with activate():
|
|
96
|
+
yielding(7).submit()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Then run the worker to process submitted routines:
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
queueio run queueio=4
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Monitor the status of active routine invocations:
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
queueio monitor
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Stability
|
|
112
|
+
---------
|
|
113
|
+
|
|
114
|
+
The design of the public API is under active development
|
|
115
|
+
and is likely to change with any release.
|
|
116
|
+
Release notes will provide upgrade instructions,
|
|
117
|
+
but backward compatibility and deprecation warnings
|
|
118
|
+
will not generally be implemented.
|
queueio-0.2.0/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
Python background queues with an async twist
|
|
4
|
+
============================================
|
|
5
|
+
|
|
6
|
+
Use async functions to manage complex background task workflows,
|
|
7
|
+
and keep using synchronous functions for everything else.
|
|
8
|
+
|
|
9
|
+
Getting Started
|
|
10
|
+
---------------
|
|
11
|
+
|
|
12
|
+
Install `queueio`:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pip install queueio
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Create your routines:
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
# basic.py
|
|
22
|
+
from time import sleep
|
|
23
|
+
|
|
24
|
+
from queueio import gather
|
|
25
|
+
from queueio import pause
|
|
26
|
+
from queueio import routine
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@routine(name="blocking", queue="queueio")
|
|
30
|
+
def blocking():
|
|
31
|
+
sleep(0.1) # Regular blocking call
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@routine(name="yielding", queue="queueio")
|
|
35
|
+
async def yielding(iterations: int):
|
|
36
|
+
# Do them two at a time
|
|
37
|
+
for _ in range(iterations // 2):
|
|
38
|
+
await gather(blocking(), blocking())
|
|
39
|
+
await pause(0.2) # Release processing capacity
|
|
40
|
+
if iterations % 2 == 1:
|
|
41
|
+
await blocking()
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Add the configuration to your `pyproject.toml`:
|
|
45
|
+
|
|
46
|
+
```toml
|
|
47
|
+
[tool.queueio]
|
|
48
|
+
# Configure RabbitMQ
|
|
49
|
+
pika = "amqp://guest:guest@localhost:5672/"
|
|
50
|
+
# Register the modules that the worker should load to find your routines
|
|
51
|
+
register = ["basic"]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The pika configuration can be overridden with an environment variable
|
|
55
|
+
to allow a project to be deployed in multiple environments.
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
QUEUEIO_PIKA='amqp://guest:guest@localhost:5672/'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Sync the queues to the broker:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
queueio sync
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Submit the routine to run on a worker:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from queueio import activate
|
|
71
|
+
from basic import yielding
|
|
72
|
+
|
|
73
|
+
with activate():
|
|
74
|
+
yielding(7).submit()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then run the worker to process submitted routines:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
queueio run queueio=4
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Monitor the status of active routine invocations:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
queueio monitor
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Stability
|
|
90
|
+
---------
|
|
91
|
+
|
|
92
|
+
The design of the public API is under active development
|
|
93
|
+
and is likely to change with any release.
|
|
94
|
+
Release notes will provide upgrade instructions,
|
|
95
|
+
but backward compatibility and deprecation warnings
|
|
96
|
+
will not generally be implemented.
|
queueio-0.2.0/dill.pyi
ADDED
queueio-0.2.0/logo.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 3750 1250" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><path d="M338.012,467.371c55.9,-7.825 112.888,7.725 154.194,46.346l1.633,-34.906l110.412,0.348l0.429,621.113l-119.094,0.729l0.14,-84.292c0.581,-38.062 0.669,-76.146 0.262,-114.229c-26.765,20.792 -52.321,33.083 -86.125,38.792c-59.756,9.646 -120.892,-4.896 -169.906,-40.417c-123.606,-89.792 -124.921,-294.833 -5.21,-388.29c33.813,-26.398 71.26,-39.381 113.265,-45.194Zm19.171,112.15c-155.729,31.208 -124.015,270.292 35.14,252.375c31.623,-7.125 63.613,-22.396 78.979,-52c48.587,-93.646 0.254,-206.665 -114.119,-200.375Z" style="fill:#0384d3;"/><path d="M3268.646,460.052c134.458,-6.86 249.125,96.36 256.396,230.802c7.271,134.438 -95.604,249.417 -230.021,257.104c-135,7.708 -250.583,-95.708 -257.896,-230.729c-7.292,-135.021 96.458,-250.287 231.521,-257.177Zm2.479,120.469c-68.125,5.438 -118.958,65.021 -113.625,133.146c5.354,68.125 64.854,119.063 133,113.813c68.271,-5.271 119.312,-64.917 113.958,-133.167c-5.354,-68.271 -65.083,-119.229 -133.333,-113.792Z" style="fill:#f36c17;"/><path d="M2429.667,469.504c12.625,-1.277 25.333,-1.637 38,-1.077c58.917,2.092 114.5,27.933 154.062,71.644c52.979,58.929 65.167,125.804 61.271,202.013c-112.188,0.062 -236.083,-2.688 -347.25,0.688c33.104,99.271 122.188,114.187 212.771,76.75c14.417,-5.958 26.458,-14.292 39.333,-23.104l19.854,17.875c17.688,15.375 35.479,30.604 53.417,45.667c-45.021,45.646 -88.25,70.958 -151.542,80.979c-136.083,21.812 -272.917,-55.979 -289.583,-199.979c-16.167,-139.792 69.271,-254.075 209.667,-271.454Zm6.521,105.933c-53.875,11.125 -79.604,36.646 -97.854,87l131.833,0l93.062,-0.021c-20.5,-65.292 -58.521,-91.863 -127.042,-86.979Z" style="fill:#0384d3;"/><path d="M1393.312,469.496c13.946,-1.45 27.981,-1.844 41.985,-1.177c124.958,6.165 211.754,108.598 215.608,230.931c0.454,14.458 -0.396,28.729 -1.135,43.146c-113.421,0.146 -233.358,-1.979 -346.019,0.917c35.075,119.604 166.117,114.438 252.931,54.021l19.162,16.812l52.948,45.75c-93.298,94.875 -235.873,114.75 -348.002,38.292c-108.408,-73.938 -123.404,-237.167 -46.944,-338.815c39.348,-52.308 95.038,-80.494 159.465,-89.877Zm11.287,106.067c-52.808,10.479 -81.34,36.708 -98.487,87.188l128.262,-0.188l95.665,0.021c-20.037,-66.396 -57.262,-91.356 -125.44,-87.021Z" style="fill:#0384d3;"/><path d="M2024.729,479.54l116.958,-0.354l0.021,138.523l-0.167,311.458l-108.396,-0.312c-0.146,-11.854 -0.792,-24.396 -1.208,-36.292c-9.125,8.25 -18.333,16.771 -28.583,23.604c-67.708,45.271 -173.667,36.542 -231.077,-21.188c-20.035,-20.333 -34.302,-45.604 -41.333,-73.271c-11.271,-42.875 -8.121,-116.021 -8.058,-162.333l-0.331,-180.123l117.487,0.158c0.854,63.542 -0.438,127.694 0.25,191.319c0.729,34.896 -1.833,68.792 3.146,103.437c9.833,72.146 114.625,83.875 158.271,37.771c31.292,-33.042 23.438,-107.792 23.417,-150.979l-0.396,-181.419Z" style="fill:#0384d3;fill-rule:nonzero;"/><path d="M994.065,479.233l117.913,-0.075l0.004,138.404l0.092,311.167c-36.994,-0.792 -73.996,-1.208 -111,-1.229l-0.89,-34.312c-37.987,35.167 -67.31,49.958 -120.646,51.5c-48.3,1.417 -96.542,-13.354 -131.633,-47.625c-21.715,-21.229 -37.135,-48.062 -44.563,-77.542c-9.981,-38.771 -7.425,-111.354 -7.448,-154.083l-0.317,-186.194l116.79,0.156l0.242,182.517c0.017,35.979 -2.15,80.188 3.8,114.854c14.915,86.896 161.917,80.75 174.977,-4.979c5.375,-35.271 3.119,-75.542 3.006,-111.167l-0.327,-181.392Z" style="fill:#0384d3;fill-rule:nonzero;"/><path d="M2915.604,464.727c6.417,0.619 11.042,0.294 16.167,4.017c2.625,12.019 1.146,114.131 1.083,136.027l0.208,324.417l-125.042,0.062l-0.167,-39.146l0.188,-258.479c-20.562,0.042 -39.271,0.104 -59.875,1.292c30.375,-33.896 68,-66.692 100.604,-99.329c20.229,-20.242 46.75,-51.537 66.833,-68.86Z" style="fill:#f36c17;fill-rule:nonzero;"/><path d="M2859.583,240.567c42.292,-5.871 81.313,23.763 87.021,66.09c5.688,42.329 -24.104,81.225 -66.437,86.752c-42.125,5.496 -80.75,-24.088 -86.417,-66.175c-5.667,-42.085 23.771,-80.831 65.833,-86.667Z" style="fill:#f36c17;fill-rule:nonzero;"/></svg>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Principles
|
|
2
|
+
==========
|
|
3
|
+
|
|
4
|
+
There's been a great deal of momentum to embrace async functions for IO,
|
|
5
|
+
but they split the world and are confusing, especially to new developers.
|
|
6
|
+
Threads or green threads are often a better choice for concurrency
|
|
7
|
+
because it doesn't require learning about coroutines
|
|
8
|
+
before you even need concurrency.
|
|
9
|
+
|
|
10
|
+
Still, async functions are a powerful way to implement state machines.
|
|
11
|
+
Complex workflows can be represented as async functions
|
|
12
|
+
to allow thinking about things in a pull-based fashion,
|
|
13
|
+
while also scaling well for processes that must take a long time.
|
|
14
|
+
Ideally, running coroutines could be serialized and resumed,
|
|
15
|
+
perhaps even on a different machine.
|
|
16
|
+
|
|
17
|
+
This vision motivates queueio.
|
|
18
|
+
Routines need to coordinate with the worker
|
|
19
|
+
because queue-level concurrency must be carefully managed.
|
|
20
|
+
Instead of complex construction of pipelines to make this possible,
|
|
21
|
+
queueio allows you to write complex workflows
|
|
22
|
+
in a traditional imperative style.
|
|
23
|
+
|
|
24
|
+
queueio encourages you to use synchronous IO in routines.
|
|
25
|
+
The yield points in async functions are only needed
|
|
26
|
+
when a routine needs to coordinate with the worker,
|
|
27
|
+
such as to indicate that the worker can use
|
|
28
|
+
the capacity reserved for it to do other work.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "queueio"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Python background queues with an async twist"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
homepage = "https://github.com/ryanhiebert/queueio"
|
|
8
|
+
repository = "https://github.com/ryanhiebert/queueio"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Ryan Hiebert", email = "ryan@ryanhiebert.com" },
|
|
11
|
+
]
|
|
12
|
+
keywords = ["async", "queue", "background", "tasks", "rabbitmq"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.14",
|
|
19
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
20
|
+
"Topic :: System :: Distributed Computing",
|
|
21
|
+
]
|
|
22
|
+
requires-python = ">=3.14"
|
|
23
|
+
dependencies = [
|
|
24
|
+
"dill>=0.3.9",
|
|
25
|
+
"pika>=1.3.2",
|
|
26
|
+
"textual>=3.2.0",
|
|
27
|
+
"typer>=0.15.4",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
queueio = "queueio.__main__:app"
|
|
32
|
+
|
|
33
|
+
[dependency-groups]
|
|
34
|
+
dev = [
|
|
35
|
+
"basedpyright>=1.32.1",
|
|
36
|
+
"pytest>=8.4.1",
|
|
37
|
+
"pytest-cov>=6.0.0",
|
|
38
|
+
"pytest-timeout>=2.4.0",
|
|
39
|
+
"ruff>=0.9.6",
|
|
40
|
+
"textual-dev>=1.7.0",
|
|
41
|
+
"types-pika>=1.2.0b1",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.ruff.lint]
|
|
45
|
+
select = ["E", "F", "UP", "B", "SIM", "I"]
|
|
46
|
+
|
|
47
|
+
[tool.ruff.lint.isort]
|
|
48
|
+
force-single-line = true
|
|
49
|
+
|
|
50
|
+
[tool.queueio]
|
|
51
|
+
register = [
|
|
52
|
+
"queueio.samples.basic",
|
|
53
|
+
"queueio.samples.expanded",
|
|
54
|
+
]
|
|
55
|
+
pika = "amqp://localhost:5672"
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
timeout = 30
|
|
59
|
+
|
|
60
|
+
[tool.coverage.run]
|
|
61
|
+
branch = true
|
|
62
|
+
source = ["queueio"]
|
|
63
|
+
|
|
64
|
+
[build-system]
|
|
65
|
+
requires = ["hatchling"]
|
|
66
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
|
|
3
|
+
from .gather import gather as gather
|
|
4
|
+
from .pause import pause as pause
|
|
5
|
+
from .queueio import QueueIO as RealQueueIO
|
|
6
|
+
from .registry import routine as routine
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@contextmanager
|
|
10
|
+
def activate():
|
|
11
|
+
with RealQueueIO().activate():
|
|
12
|
+
yield
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
from typer import Argument
|
|
4
|
+
from typer import Typer
|
|
5
|
+
|
|
6
|
+
from .monitor import Monitor
|
|
7
|
+
from .queueio import QueueIO
|
|
8
|
+
from .queuespec import QueueSpec
|
|
9
|
+
from .worker import Worker
|
|
10
|
+
|
|
11
|
+
app = Typer()
|
|
12
|
+
|
|
13
|
+
routine_app = Typer()
|
|
14
|
+
queue_app = Typer()
|
|
15
|
+
|
|
16
|
+
app.add_typer(
|
|
17
|
+
routine_app,
|
|
18
|
+
name="routine",
|
|
19
|
+
help="A function to coordinate background execution.",
|
|
20
|
+
rich_help_panel="Entities",
|
|
21
|
+
)
|
|
22
|
+
app.add_typer(
|
|
23
|
+
queue_app,
|
|
24
|
+
name="queue",
|
|
25
|
+
help="An ordered collection of work items to process.",
|
|
26
|
+
rich_help_panel="Entities",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@routine_app.command("list")
|
|
31
|
+
def routine_list():
|
|
32
|
+
"""Show all registered routines."""
|
|
33
|
+
queueio = QueueIO()
|
|
34
|
+
try:
|
|
35
|
+
routines = queueio.routines()
|
|
36
|
+
|
|
37
|
+
if not routines:
|
|
38
|
+
print("No routines registered.")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
# Calculate column widths
|
|
42
|
+
name_width = max(len("Name"), max(len(routine.name) for routine in routines))
|
|
43
|
+
function_paths = []
|
|
44
|
+
for routine in routines:
|
|
45
|
+
module = routine.fn.__module__
|
|
46
|
+
qualname = routine.fn.__qualname__
|
|
47
|
+
function_paths.append(f"{module}.{qualname}")
|
|
48
|
+
path_width = max(len("Path"), max(len(path) for path in function_paths))
|
|
49
|
+
|
|
50
|
+
print(f"{'Name':<{name_width}} | {'Path':<{path_width}}")
|
|
51
|
+
print(f"{'-' * name_width}-+-{'-' * path_width}")
|
|
52
|
+
for routine, path in zip(routines, function_paths, strict=False):
|
|
53
|
+
print(f"{routine.name:<{name_width}} | {path:<{path_width}}")
|
|
54
|
+
finally:
|
|
55
|
+
queueio.shutdown()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@app.command(rich_help_panel="Commands")
|
|
59
|
+
def monitor(raw: bool = False):
|
|
60
|
+
"""Monitor queueio events.
|
|
61
|
+
|
|
62
|
+
Show a live view of queueio activity. Use --raw for detailed event output.
|
|
63
|
+
"""
|
|
64
|
+
if raw:
|
|
65
|
+
queueio = QueueIO()
|
|
66
|
+
events = queueio.subscribe({object})
|
|
67
|
+
try:
|
|
68
|
+
while True:
|
|
69
|
+
print(events.get())
|
|
70
|
+
except KeyboardInterrupt:
|
|
71
|
+
print("Shutting down gracefully.")
|
|
72
|
+
finally:
|
|
73
|
+
queueio.shutdown()
|
|
74
|
+
else:
|
|
75
|
+
Monitor().run()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@app.command(rich_help_panel="Commands")
|
|
79
|
+
def run(
|
|
80
|
+
queuespec: Annotated[
|
|
81
|
+
QueueSpec,
|
|
82
|
+
Argument(
|
|
83
|
+
parser=QueueSpec.parse,
|
|
84
|
+
help="Queue configuration in format 'queue=concurrency'. "
|
|
85
|
+
"Examples: 'production=10', 'api,background=5'",
|
|
86
|
+
metavar="QUEUE[,QUEUE2,...]=CONCURRENCY",
|
|
87
|
+
),
|
|
88
|
+
],
|
|
89
|
+
):
|
|
90
|
+
"""Run a worker to process from a queue.
|
|
91
|
+
|
|
92
|
+
The worker will process invocations from the specified queue,
|
|
93
|
+
as many at a time as specified by the concurrency.
|
|
94
|
+
"""
|
|
95
|
+
queueio = QueueIO()
|
|
96
|
+
Worker(queueio, queuespec)()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@app.command(rich_help_panel="Commands")
|
|
100
|
+
def sync():
|
|
101
|
+
"""Sync known queues to the broker."""
|
|
102
|
+
queueio = QueueIO()
|
|
103
|
+
try:
|
|
104
|
+
routines = queueio.routines()
|
|
105
|
+
|
|
106
|
+
if not routines:
|
|
107
|
+
print("No routines registered.")
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
queues = sorted({routine.queue for routine in routines})
|
|
111
|
+
|
|
112
|
+
print(f"Syncing queues for {len(routines)} routine(s):")
|
|
113
|
+
for queue in queues:
|
|
114
|
+
print(f" Ensuring queue exists: {queue}")
|
|
115
|
+
queueio.create(queue=queue)
|
|
116
|
+
|
|
117
|
+
print(f"Successfully synced {len(queues)} queue(s)")
|
|
118
|
+
finally:
|
|
119
|
+
queueio.shutdown()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@queue_app.command("purge")
|
|
123
|
+
def queue_purge(
|
|
124
|
+
queues: Annotated[
|
|
125
|
+
str,
|
|
126
|
+
Argument(
|
|
127
|
+
help="Comma-separated list of queues to purge. "
|
|
128
|
+
"Examples: 'queueio', 'production,background'",
|
|
129
|
+
metavar="QUEUE[,QUEUE2,...]",
|
|
130
|
+
),
|
|
131
|
+
],
|
|
132
|
+
):
|
|
133
|
+
"""Purge all messages from some queues.
|
|
134
|
+
|
|
135
|
+
This will remove all pending messages from the given queues.
|
|
136
|
+
Use with caution as this operation cannot be undone.
|
|
137
|
+
"""
|
|
138
|
+
queueio = QueueIO()
|
|
139
|
+
try:
|
|
140
|
+
queue_list = [q.strip() for q in queues.split(",") if q.strip()]
|
|
141
|
+
if not queue_list:
|
|
142
|
+
print("Error: No valid queue names provided")
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
for queue in queue_list:
|
|
146
|
+
print(f"Purging queue: {queue}")
|
|
147
|
+
queueio.purge(queue=queue)
|
|
148
|
+
|
|
149
|
+
print(f"Successfully purged {len(queue_list)} queue(s)")
|
|
150
|
+
finally:
|
|
151
|
+
queueio.shutdown()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
if __name__ == "__main__":
|
|
155
|
+
app()
|