queueio 0.6.0__tar.gz → 0.8.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.6.0 → queueio-0.8.0}/.devcontainer/docker-compose.yml +4 -1
- queueio-0.8.0/.github/workflows/build.yml +92 -0
- {queueio-0.6.0 → queueio-0.8.0}/.github/workflows/docs.yml +1 -1
- {queueio-0.6.0 → queueio-0.8.0}/.gitignore +2 -0
- {queueio-0.6.0 → queueio-0.8.0}/CHANGELOG.md +40 -1
- {queueio-0.6.0 → queueio-0.8.0}/PKG-INFO +19 -8
- {queueio-0.6.0 → queueio-0.8.0}/README.md +12 -4
- {queueio-0.6.0 → queueio-0.8.0}/mkdocs.yml +1 -1
- {queueio-0.6.0 → queueio-0.8.0}/pyproject.toml +15 -7
- queueio-0.8.0/pyrightconfig.json +5 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/__init__.py +2 -2
- {queueio-0.6.0 → queueio-0.8.0}/queueio/__main__.py +27 -48
- queueio-0.8.0/queueio/backend.py +18 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/broker.py +7 -13
- {queueio-0.6.0 → queueio-0.8.0}/queueio/broker_test.py +29 -24
- queueio-0.8.0/queueio/conftest.py +98 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/freethreading_test.py +5 -1
- {queueio-0.6.0 → queueio-0.8.0}/queueio/journal.py +0 -6
- {queueio-0.6.0 → queueio-0.8.0}/queueio/monitor.py +3 -3
- queueio-0.8.0/queueio/pika/__init__.py +31 -0
- queueio-0.8.0/queueio/pika/broker.py +83 -0
- queueio-0.8.0/queueio/pika/broker_test.py +38 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/pika/journal.py +13 -12
- queueio-0.8.0/queueio/pika/journal_test.py +16 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/pika/receiver.py +1 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/pika/threadsafe.py +40 -1
- queueio-0.8.0/queueio/pika/threadsafe_test.py +20 -0
- queueio-0.8.0/queueio/psycopg/README.md +136 -0
- queueio-0.8.0/queueio/psycopg/broker-queries.md +135 -0
- queueio-0.8.0/queueio/psycopg/journal-queries.md +172 -0
- queueio-0.8.0/queueio/psycopg/journal.py +97 -0
- queueio-0.8.0/queueio/psycopg/journal_test.py +40 -0
- queueio-0.8.0/queueio/psycopg/receiver-queries.md +254 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/queueio.py +52 -42
- queueio-0.8.0/queueio/queueio_test.py +330 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/queuespec.md +4 -4
- {queueio-0.6.0 → queueio-0.8.0}/queueio/queuevar.md +1 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/basic_test.py +2 -4
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/django_test.py +3 -1
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/expanded_test.py +2 -4
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/priority_test.py +2 -4
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/queuevar_test.py +2 -4
- {queueio-0.6.0 → queueio-0.8.0}/queueio/stream.py +2 -1
- queueio-0.8.0/queueio/stub/__init__.py +21 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/stub/broker.py +21 -13
- queueio-0.8.0/queueio/stub/broker_test.py +15 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/stub/journal.py +14 -6
- queueio-0.8.0/queueio/stub/journal_test.py +12 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/stub/receiver.py +5 -4
- {queueio-0.6.0 → queueio-0.8.0}/queueio/worker.py +3 -2
- queueio-0.6.0/.github/workflows/build.yml +0 -42
- queueio-0.6.0/pyrightconfig.json +0 -4
- queueio-0.6.0/queueio/conftest.py +0 -32
- queueio-0.6.0/queueio/pika/broker.py +0 -68
- queueio-0.6.0/queueio/pika/broker_test.py +0 -23
- queueio-0.6.0/queueio/pika/journal_test.py +0 -20
- queueio-0.6.0/queueio/queue.py +0 -183
- queueio-0.6.0/queueio/queue_test.py +0 -729
- queueio-0.6.0/queueio/queueio_test.py +0 -558
- queueio-0.6.0/queueio/select.py +0 -170
- queueio-0.6.0/queueio/stub/__init__.py +0 -0
- queueio-0.6.0/queueio/stub/broker_test.py +0 -22
- queueio-0.6.0/queueio/stub/journal_test.py +0 -19
- {queueio-0.6.0 → queueio-0.8.0}/.devcontainer/devcontainer.json +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/.github/workflows/publish.yml +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/CNAME +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/LICENSE +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/dill.pyi +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/docs_overrides/assets/images/favicon.svg +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/docs_overrides/assets/images/logo.svg +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/docs_overrides/assets/stylesheets/extra.css +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/docs_overrides/partials/footer.html +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/docs_overrides/partials/javascripts/palette.html +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/logo.svg +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/philosophy.md +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/consumer.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/continuation.py +0 -0
- /queueio-0.6.0/queueio/django.md → /queueio-0.8.0/queueio/django/README.md +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/django/__init__.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/django/apps.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/django/djp.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/django/management/__init__.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/django/management/commands/__init__.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/django/management/commands/runserver.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/django/management/commands/runserver_plus.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/django/runserver_test.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/event.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/gather.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/id.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/invocation.md +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/invocation.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/journal_test.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/message.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/pause.py +0 -0
- {queueio-0.6.0/queueio/pika → queueio-0.8.0/queueio/psycopg}/__init__.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/queuespec.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/queuespec_test.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/queuevar.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/receiver.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/registry.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/result.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/routine.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/__init__.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/basic.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/django/__init__.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/django/apps.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/django/job.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/django/models.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/django/settings.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/django/settings_djp.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/django/urls.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/expanded.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/priority.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/samples/queuevar.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/suspension.py +0 -0
- {queueio-0.6.0 → queueio-0.8.0}/queueio/thread.py +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
name: Build
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
build:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
timeout-minutes: 5
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
id-token: write
|
|
12
|
+
services:
|
|
13
|
+
rabbitmq:
|
|
14
|
+
image: rabbitmq
|
|
15
|
+
env:
|
|
16
|
+
RABBITMQ_DEFAULT_USER: guest
|
|
17
|
+
RABBITMQ_DEFAULT_PASS: guest
|
|
18
|
+
RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: "-rabbit permit_deprecated_features #{global_qos=>true}"
|
|
19
|
+
ports:
|
|
20
|
+
- 5672/tcp
|
|
21
|
+
options: >-
|
|
22
|
+
--health-cmd "rabbitmq-diagnostics -q ping"
|
|
23
|
+
--health-interval 10s
|
|
24
|
+
--health-timeout 5s
|
|
25
|
+
--health-retries 5
|
|
26
|
+
rabbitmq_no_global_qos:
|
|
27
|
+
image: rabbitmq
|
|
28
|
+
env:
|
|
29
|
+
RABBITMQ_DEFAULT_USER: guest
|
|
30
|
+
RABBITMQ_DEFAULT_PASS: guest
|
|
31
|
+
RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: "-rabbit permit_deprecated_features #{global_qos=>false}"
|
|
32
|
+
ports:
|
|
33
|
+
- 5672/tcp
|
|
34
|
+
options: >-
|
|
35
|
+
--health-cmd "rabbitmq-diagnostics -q ping"
|
|
36
|
+
--health-interval 10s
|
|
37
|
+
--health-timeout 5s
|
|
38
|
+
--health-retries 5
|
|
39
|
+
lavinmq:
|
|
40
|
+
# 2.10.3 and later starve consumers sharing a channel with global prefetch.
|
|
41
|
+
# https://github.com/cloudamqp/lavinmq/issues/2628
|
|
42
|
+
image: cloudamqp/lavinmq:2.10.2
|
|
43
|
+
ports:
|
|
44
|
+
- 5672/tcp
|
|
45
|
+
options: >-
|
|
46
|
+
--health-cmd "lavinmqctl status"
|
|
47
|
+
--health-interval 10s
|
|
48
|
+
--health-timeout 5s
|
|
49
|
+
--health-retries 5
|
|
50
|
+
steps:
|
|
51
|
+
- uses: actions/checkout@v4
|
|
52
|
+
- name: Install uv
|
|
53
|
+
uses: astral-sh/setup-uv@v3
|
|
54
|
+
- name: Set up Python
|
|
55
|
+
run: uv python install 3.15t
|
|
56
|
+
- name: Install dependencies
|
|
57
|
+
run: uv sync --all-groups
|
|
58
|
+
- name: Check formatting
|
|
59
|
+
run: uv run ruff format --check
|
|
60
|
+
- name: Run ruff linting
|
|
61
|
+
run: uv run ruff check
|
|
62
|
+
- name: Run basedpyright
|
|
63
|
+
run: uv run basedpyright
|
|
64
|
+
- name: Run tests
|
|
65
|
+
env:
|
|
66
|
+
QUEUEIO_BROKER: amqp://guest:guest@localhost:${{ job.services.rabbitmq.ports['5672'] }}/
|
|
67
|
+
run: uv run pytest --cov --cov-report=
|
|
68
|
+
timeout-minutes: 2
|
|
69
|
+
- name: Test broker without channel-wide QoS
|
|
70
|
+
env:
|
|
71
|
+
QUEUEIO_BROKER: amqp://guest:guest@localhost:${{ job.services.rabbitmq_no_global_qos.ports['5672'] }}/
|
|
72
|
+
run: uv run pytest -m no_global_qos --cov --cov-append --cov-report=
|
|
73
|
+
- name: Test LavinMQ
|
|
74
|
+
env:
|
|
75
|
+
QUEUEIO_BROKER: amqp://guest:guest@localhost:${{ job.services.lavinmq.ports['5672'] }}/
|
|
76
|
+
run: uv run pytest --cov --cov-append --cov-report=
|
|
77
|
+
timeout-minutes: 2
|
|
78
|
+
- name: Report coverage
|
|
79
|
+
run: |
|
|
80
|
+
uv run coverage report
|
|
81
|
+
uv run coverage xml
|
|
82
|
+
- name: Upload coverage to Codecov
|
|
83
|
+
uses: codecov/codecov-action@v5
|
|
84
|
+
with:
|
|
85
|
+
files: coverage.xml
|
|
86
|
+
disable_search: true
|
|
87
|
+
use_oidc: true
|
|
88
|
+
fail_ci_if_error: true
|
|
89
|
+
- name: Verify built package
|
|
90
|
+
run: |
|
|
91
|
+
uv build
|
|
92
|
+
uvx --from dist/queueio-*.whl queueio --help
|
|
@@ -21,7 +21,7 @@ jobs:
|
|
|
21
21
|
steps:
|
|
22
22
|
- uses: actions/checkout@v4
|
|
23
23
|
- uses: astral-sh/setup-uv@v5
|
|
24
|
-
- run: uv python install 3.
|
|
24
|
+
- run: uv python install 3.15t
|
|
25
25
|
- run: uv sync --group dev
|
|
26
26
|
- run: uv run mkdocs build --strict
|
|
27
27
|
- uses: actions/upload-pages-artifact@v3
|
|
@@ -8,6 +8,43 @@ The format is loosely based on [Keep a Changelog](https://keepachangelog.com).
|
|
|
8
8
|
[Unreleased]
|
|
9
9
|
------------
|
|
10
10
|
|
|
11
|
+
[0.8.0] - 2026-11-04
|
|
12
|
+
--------------------
|
|
13
|
+
|
|
14
|
+
### Breaking Changes
|
|
15
|
+
|
|
16
|
+
- Python 3.15 or later is now required; Python 3.14 is no longer supported.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Load the monitor UI lazily so other CLI commands avoid importing Textual.
|
|
21
|
+
- Use [noio](https://pypi.org/project/noio/) for internal queues and selection,
|
|
22
|
+
replacing `queueio.queue` and `queueio.select`.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- A failed connection to the broker no longer leaves a thread running
|
|
27
|
+
that prevents the process from exiting.
|
|
28
|
+
|
|
29
|
+
[0.7.0] - 2026-03-09
|
|
30
|
+
--------------------
|
|
31
|
+
|
|
32
|
+
### Breaking Changes
|
|
33
|
+
|
|
34
|
+
- The `pika` configuration key in `[tool.queueio]` has been renamed to `broker`.
|
|
35
|
+
- The `QUEUEIO_PIKA` environment variable has been renamed to `QUEUEIO_BROKER`.
|
|
36
|
+
- Removed `Broker.create(queue)`, in favor of `Broker.sync([queue])`.
|
|
37
|
+
- Removed `Broker.delete(queue)`, without replacement.
|
|
38
|
+
- Removed `QueueIO.create(queue)`, in favor of `QueueIO.sync([queue])`.
|
|
39
|
+
- Removed `QueueIO.delete(queue)`, without replacement.
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- Added `Broker.sync()` to back `queueio sync`.
|
|
44
|
+
- Added `QueueIO.sync()` to back `queueio sync`.
|
|
45
|
+
- `Backend` ABC with `broker()` and `journal()` methods
|
|
46
|
+
that return independent context managers with their own lifecycles.
|
|
47
|
+
|
|
11
48
|
[0.6.0] - 2026-02-16
|
|
12
49
|
--------------------
|
|
13
50
|
|
|
@@ -106,7 +143,9 @@ Thank you to Nick Anderegg for allowing me to use the queueio name for this proj
|
|
|
106
143
|
- The queuespec syntax to `queue run` to consume multiple queues with shared capacity.
|
|
107
144
|
- `queueio monitor` command to monitor activity in the queueio system.
|
|
108
145
|
|
|
109
|
-
[Unreleased]: https://github.com/ryanhiebert/queueio/compare/tag/0.
|
|
146
|
+
[Unreleased]: https://github.com/ryanhiebert/queueio/compare/tag/0.8.0...HEAD
|
|
147
|
+
[0.8.0]: https://github.com/ryanhiebert/queueio/compare/tag/0.7.0...tag/0.8.0
|
|
148
|
+
[0.7.0]: https://github.com/ryanhiebert/queueio/compare/tag/0.6.0...tag/0.7.0
|
|
110
149
|
[0.6.0]: https://github.com/ryanhiebert/queueio/compare/tag/0.5.0...tag/0.6.0
|
|
111
150
|
[0.5.0]: https://github.com/ryanhiebert/queueio/compare/tag/0.4.0...tag/0.5.0
|
|
112
151
|
[0.4.0]: https://github.com/ryanhiebert/queueio/compare/tag/0.3.0...tag/0.4.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: queueio
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Python background queues with an async twist
|
|
5
5
|
Author-email: Ryan Hiebert <ryan@ryanhiebert.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -10,17 +10,20 @@ Classifier: Development Status :: 3 - Alpha
|
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
14
14
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
15
|
Classifier: Topic :: System :: Distributed Computing
|
|
16
|
-
Requires-Python: >=3.
|
|
16
|
+
Requires-Python: >=3.15
|
|
17
17
|
Requires-Dist: dill>=0.3.9
|
|
18
|
+
Requires-Dist: noio>=0.3
|
|
18
19
|
Requires-Dist: pika>=1.3.2
|
|
19
20
|
Requires-Dist: textual>=3.2.0
|
|
20
21
|
Requires-Dist: typer>=0.15.4
|
|
21
22
|
Provides-Extra: django
|
|
22
23
|
Requires-Dist: django-cmd>=3.0; extra == 'django'
|
|
23
24
|
Requires-Dist: django>=6.0; extra == 'django'
|
|
25
|
+
Provides-Extra: psycopg
|
|
26
|
+
Requires-Dist: psycopg>=3.3; extra == 'psycopg'
|
|
24
27
|
Description-Content-Type: text/markdown
|
|
25
28
|
|
|
26
29
|

|
|
@@ -71,17 +74,25 @@ Add the configuration to your `pyproject.toml`:
|
|
|
71
74
|
|
|
72
75
|
```toml
|
|
73
76
|
[tool.queueio]
|
|
74
|
-
# Configure
|
|
75
|
-
|
|
77
|
+
# Configure the broker (currently supports amqp:// URIs)
|
|
78
|
+
broker = "amqp://guest:guest@localhost:5672/"
|
|
76
79
|
# Register the modules that the worker should load to find your routines
|
|
77
80
|
register = ["basic"]
|
|
78
81
|
```
|
|
79
82
|
|
|
80
|
-
The
|
|
83
|
+
The broker configuration can be overridden with an environment variable
|
|
81
84
|
to allow a project to be deployed in multiple environments.
|
|
82
85
|
|
|
83
86
|
```sh
|
|
84
|
-
|
|
87
|
+
QUEUEIO_BROKER='amqp://guest:guest@localhost:5672/'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
QueueIO requires RabbitMQ's global QoS for shared concurrency limits and
|
|
91
|
+
suspending tasks. On RabbitMQ 4.3, enable it in `rabbitmq.conf` and restart
|
|
92
|
+
the broker:
|
|
93
|
+
|
|
94
|
+
```ini
|
|
95
|
+
deprecated_features.permit.global_qos = true
|
|
85
96
|
```
|
|
86
97
|
|
|
87
98
|
Sync the queues to the broker:
|
|
@@ -46,17 +46,25 @@ Add the configuration to your `pyproject.toml`:
|
|
|
46
46
|
|
|
47
47
|
```toml
|
|
48
48
|
[tool.queueio]
|
|
49
|
-
# Configure
|
|
50
|
-
|
|
49
|
+
# Configure the broker (currently supports amqp:// URIs)
|
|
50
|
+
broker = "amqp://guest:guest@localhost:5672/"
|
|
51
51
|
# Register the modules that the worker should load to find your routines
|
|
52
52
|
register = ["basic"]
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
The
|
|
55
|
+
The broker configuration can be overridden with an environment variable
|
|
56
56
|
to allow a project to be deployed in multiple environments.
|
|
57
57
|
|
|
58
58
|
```sh
|
|
59
|
-
|
|
59
|
+
QUEUEIO_BROKER='amqp://guest:guest@localhost:5672/'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
QueueIO requires RabbitMQ's global QoS for shared concurrency limits and
|
|
63
|
+
suspending tasks. On RabbitMQ 4.3, enable it in `rabbitmq.conf` and restart
|
|
64
|
+
the broker:
|
|
65
|
+
|
|
66
|
+
```ini
|
|
67
|
+
deprecated_features.permit.global_qos = true
|
|
60
68
|
```
|
|
61
69
|
|
|
62
70
|
Sync the queues to the broker:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "queueio"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.0"
|
|
4
4
|
description = "Python background queues with an async twist"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -15,13 +15,14 @@ classifiers = [
|
|
|
15
15
|
"Intended Audience :: Developers",
|
|
16
16
|
"License :: OSI Approved :: MIT License",
|
|
17
17
|
"Programming Language :: Python :: 3",
|
|
18
|
-
"Programming Language :: Python :: 3.
|
|
18
|
+
"Programming Language :: Python :: 3.15",
|
|
19
19
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
20
20
|
"Topic :: System :: Distributed Computing",
|
|
21
21
|
]
|
|
22
|
-
requires-python = ">=3.
|
|
22
|
+
requires-python = ">=3.15"
|
|
23
23
|
dependencies = [
|
|
24
24
|
"dill>=0.3.9",
|
|
25
|
+
"noio>=0.3",
|
|
25
26
|
"pika>=1.3.2",
|
|
26
27
|
"textual>=3.2.0",
|
|
27
28
|
"typer>=0.15.4",
|
|
@@ -29,6 +30,7 @@ dependencies = [
|
|
|
29
30
|
|
|
30
31
|
[project.optional-dependencies]
|
|
31
32
|
django = ["django>=6.0", "django-cmd>=3.0"]
|
|
33
|
+
psycopg = ["psycopg>=3.3"]
|
|
32
34
|
|
|
33
35
|
[project.entry-points.djp]
|
|
34
36
|
queueio = "queueio.django.djp"
|
|
@@ -38,18 +40,20 @@ queueio = "queueio.__main__:app"
|
|
|
38
40
|
|
|
39
41
|
[dependency-groups]
|
|
40
42
|
dev = [
|
|
41
|
-
"basedpyright>=1.
|
|
43
|
+
"basedpyright>=1.40.2",
|
|
42
44
|
"django-extensions",
|
|
43
45
|
"djp",
|
|
44
46
|
"queueio[django]",
|
|
47
|
+
"queueio[psycopg]",
|
|
45
48
|
"mkdocs-material>=9.7.1",
|
|
46
49
|
"mkdocs-same-dir>=0.1.3",
|
|
47
50
|
"pytest>=8.4.1",
|
|
48
|
-
"pytest-cov>=
|
|
51
|
+
"pytest-cov>=7.0.0",
|
|
49
52
|
"pytest-timeout>=2.4.0",
|
|
50
|
-
"ruff>=0.
|
|
53
|
+
"ruff>=0.17.0",
|
|
51
54
|
"textual-dev>=1.7.0",
|
|
52
55
|
"types-pika>=1.2.0b1",
|
|
56
|
+
"psycopg>=3.3.3",
|
|
53
57
|
]
|
|
54
58
|
|
|
55
59
|
[tool.ruff.lint]
|
|
@@ -65,15 +69,19 @@ register = [
|
|
|
65
69
|
"queueio.samples.priority",
|
|
66
70
|
"queueio.samples.queuevar",
|
|
67
71
|
]
|
|
68
|
-
|
|
72
|
+
broker = "amqp://localhost:5672"
|
|
69
73
|
|
|
70
74
|
[tool.pytest.ini_options]
|
|
75
|
+
markers = ["no_global_qos: requires a broker without channel-wide QoS"]
|
|
76
|
+
addopts = "-m 'not no_global_qos'"
|
|
71
77
|
timeout = 30
|
|
72
78
|
norecursedirs = ["site"]
|
|
73
79
|
|
|
74
80
|
[tool.coverage.run]
|
|
75
81
|
branch = true
|
|
76
82
|
source = ["queueio"]
|
|
83
|
+
patch = ["subprocess"]
|
|
84
|
+
sigterm = true
|
|
77
85
|
|
|
78
86
|
[build-system]
|
|
79
87
|
requires = ["hatchling"]
|
|
@@ -2,7 +2,7 @@ from contextlib import contextmanager
|
|
|
2
2
|
|
|
3
3
|
from .gather import gather as gather
|
|
4
4
|
from .pause import pause as pause
|
|
5
|
-
from .queueio import QueueIO as
|
|
5
|
+
from .queueio import QueueIO as QueueIO
|
|
6
6
|
from .queueio import priority as priority
|
|
7
7
|
from .queuevar import QueueVar as QueueVar
|
|
8
8
|
from .registry import routine as routine
|
|
@@ -10,5 +10,5 @@ from .registry import routine as routine
|
|
|
10
10
|
|
|
11
11
|
@contextmanager
|
|
12
12
|
def activate():
|
|
13
|
-
with
|
|
13
|
+
with QueueIO.default() as queueio, queueio.activate():
|
|
14
14
|
yield
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
from contextlib import suppress
|
|
2
1
|
from typing import Annotated
|
|
3
2
|
|
|
4
3
|
import typer
|
|
5
4
|
from typer import Argument
|
|
6
5
|
from typer import Typer
|
|
7
6
|
|
|
8
|
-
from .monitor import Monitor
|
|
9
7
|
from .queueio import QueueIO
|
|
10
8
|
from .queuespec import QueueSpec
|
|
11
9
|
from .worker import Worker
|
|
10
|
+
lazy from .monitor import Monitor
|
|
12
11
|
|
|
13
12
|
app = Typer()
|
|
14
13
|
|
|
@@ -32,8 +31,7 @@ app.add_typer(
|
|
|
32
31
|
@routine_app.command("list")
|
|
33
32
|
def routine_list():
|
|
34
33
|
"""Show all registered routines."""
|
|
35
|
-
|
|
36
|
-
try:
|
|
34
|
+
with QueueIO.default() as queueio:
|
|
37
35
|
routines = queueio.routines()
|
|
38
36
|
|
|
39
37
|
if not routines:
|
|
@@ -53,8 +51,6 @@ def routine_list():
|
|
|
53
51
|
print(f"{'-' * name_width}-+-{'-' * path_width}")
|
|
54
52
|
for routine, path in zip(routines, function_paths, strict=False):
|
|
55
53
|
print(f"{routine.name:<{name_width}} | {path:<{path_width}}")
|
|
56
|
-
finally:
|
|
57
|
-
queueio.shutdown()
|
|
58
54
|
|
|
59
55
|
|
|
60
56
|
@app.command(rich_help_panel="Commands")
|
|
@@ -64,17 +60,16 @@ def monitor(raw: bool = False):
|
|
|
64
60
|
Show a live view of queueio activity. Use --raw for detailed event output.
|
|
65
61
|
"""
|
|
66
62
|
if raw:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
finally:
|
|
75
|
-
queueio.shutdown()
|
|
63
|
+
with QueueIO.default() as queueio:
|
|
64
|
+
events = queueio.subscribe({object})
|
|
65
|
+
try:
|
|
66
|
+
while True:
|
|
67
|
+
print(events.get())
|
|
68
|
+
except KeyboardInterrupt:
|
|
69
|
+
print("Shutting down gracefully.")
|
|
76
70
|
else:
|
|
77
|
-
|
|
71
|
+
with QueueIO.default() as queueio:
|
|
72
|
+
Monitor(queueio).run()
|
|
78
73
|
|
|
79
74
|
|
|
80
75
|
@app.command(rich_help_panel="Commands")
|
|
@@ -94,8 +89,8 @@ def run(
|
|
|
94
89
|
The worker will process invocations from the specified queue,
|
|
95
90
|
as many at a time as specified by the concurrency.
|
|
96
91
|
"""
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
with QueueIO.default() as queueio:
|
|
93
|
+
Worker(queueio, queuespec)()
|
|
99
94
|
|
|
100
95
|
|
|
101
96
|
@app.command(rich_help_panel="Commands")
|
|
@@ -103,14 +98,13 @@ def sync(
|
|
|
103
98
|
recreate: Annotated[
|
|
104
99
|
bool,
|
|
105
100
|
typer.Option(
|
|
106
|
-
help="
|
|
107
|
-
"WARNING: This will lose
|
|
101
|
+
help="Recreate all resources for the broker and journal. "
|
|
102
|
+
"WARNING: This will lose pending messages.",
|
|
108
103
|
),
|
|
109
104
|
] = False,
|
|
110
105
|
):
|
|
111
|
-
"""Sync
|
|
112
|
-
|
|
113
|
-
try:
|
|
106
|
+
"""Sync resources for the broker and journal."""
|
|
107
|
+
with QueueIO.default() as queueio:
|
|
114
108
|
routines = queueio.routines()
|
|
115
109
|
|
|
116
110
|
if not routines:
|
|
@@ -119,33 +113,21 @@ def sync(
|
|
|
119
113
|
|
|
120
114
|
queues = sorted({routine.queue for routine in routines})
|
|
121
115
|
|
|
122
|
-
print(f"
|
|
123
|
-
if recreate:
|
|
124
|
-
for queue in queues:
|
|
125
|
-
print(f" Recreating queue: {queue}")
|
|
126
|
-
with suppress(Exception):
|
|
127
|
-
queueio.delete(queue=queue)
|
|
128
|
-
|
|
129
|
-
failed = []
|
|
116
|
+
print(f"Discovered queues for {len(routines)} routine(s):")
|
|
130
117
|
for queue in queues:
|
|
131
|
-
print(f"
|
|
132
|
-
try:
|
|
133
|
-
queueio.create(queue=queue)
|
|
134
|
-
except Exception:
|
|
135
|
-
failed.append(queue)
|
|
118
|
+
print(f" {queue}")
|
|
136
119
|
|
|
137
|
-
|
|
120
|
+
try:
|
|
121
|
+
queueio.sync(queues, recreate=recreate)
|
|
122
|
+
except Exception:
|
|
138
123
|
print(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
f"WARNING: This will lose any pending messages in those queues."
|
|
124
|
+
"\nError: Failed to sync resources.\n"
|
|
125
|
+
"Re-run with --recreate to recreate them.\n"
|
|
126
|
+
"WARNING: This will lose pending messages."
|
|
143
127
|
)
|
|
144
|
-
raise typer.Exit(1)
|
|
128
|
+
raise typer.Exit(1) from None
|
|
145
129
|
|
|
146
130
|
print(f"Successfully synced {len(queues)} queue(s)")
|
|
147
|
-
finally:
|
|
148
|
-
queueio.shutdown()
|
|
149
131
|
|
|
150
132
|
|
|
151
133
|
@queue_app.command("purge")
|
|
@@ -164,8 +146,7 @@ def queue_purge(
|
|
|
164
146
|
This will remove all pending messages from the given queues.
|
|
165
147
|
Use with caution as this operation cannot be undone.
|
|
166
148
|
"""
|
|
167
|
-
|
|
168
|
-
try:
|
|
149
|
+
with QueueIO.default() as queueio:
|
|
169
150
|
queue_list = [q.strip() for q in queues.split(",") if q.strip()]
|
|
170
151
|
if not queue_list:
|
|
171
152
|
print("Error: No valid queue names provided")
|
|
@@ -176,8 +157,6 @@ def queue_purge(
|
|
|
176
157
|
queueio.purge(queue=queue)
|
|
177
158
|
|
|
178
159
|
print(f"Successfully purged {len(queue_list)} queue(s)")
|
|
179
|
-
finally:
|
|
180
|
-
queueio.shutdown()
|
|
181
160
|
|
|
182
161
|
|
|
183
162
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from abc import abstractmethod
|
|
3
|
+
from contextlib import AbstractContextManager
|
|
4
|
+
|
|
5
|
+
from .broker import Broker
|
|
6
|
+
from .journal import Journal
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Backend(ABC):
|
|
10
|
+
"""A backend provides context-managed access to a broker and journal."""
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def broker(self) -> AbstractContextManager[Broker]:
|
|
14
|
+
raise NotImplementedError("Subclasses must implement this method.")
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def journal(self) -> AbstractContextManager[Journal]:
|
|
18
|
+
raise NotImplementedError("Subclasses must implement this method.")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from abc import ABC
|
|
2
2
|
from abc import abstractmethod
|
|
3
|
+
from collections.abc import Iterable
|
|
3
4
|
|
|
4
5
|
from .queuespec import QueueSpec
|
|
5
6
|
from .receiver import Receiver
|
|
@@ -13,10 +14,13 @@ class Broker(ABC):
|
|
|
13
14
|
at-least-once delivery.
|
|
14
15
|
"""
|
|
15
16
|
|
|
16
|
-
@classmethod
|
|
17
17
|
@abstractmethod
|
|
18
|
-
def
|
|
19
|
-
"""
|
|
18
|
+
def sync(self, queues: Iterable[str], *, recreate: bool = False):
|
|
19
|
+
"""Ensure the given queues are ready to use.
|
|
20
|
+
|
|
21
|
+
If recreate is True, destroy and recreate all resources,
|
|
22
|
+
losing any pending messages.
|
|
23
|
+
"""
|
|
20
24
|
raise NotImplementedError("Subclasses must implement this method.")
|
|
21
25
|
|
|
22
26
|
@abstractmethod
|
|
@@ -24,16 +28,6 @@ class Broker(ABC):
|
|
|
24
28
|
"""Enqueue a message."""
|
|
25
29
|
raise NotImplementedError("Subclasses must implement this method.")
|
|
26
30
|
|
|
27
|
-
@abstractmethod
|
|
28
|
-
def create(self, *, queue: str):
|
|
29
|
-
"""Create a queue if it doesn't exist."""
|
|
30
|
-
raise NotImplementedError("Subclasses must implement this method.")
|
|
31
|
-
|
|
32
|
-
@abstractmethod
|
|
33
|
-
def delete(self, *, queue: str):
|
|
34
|
-
"""Delete a queue."""
|
|
35
|
-
raise NotImplementedError("Subclasses must implement this method.")
|
|
36
|
-
|
|
37
31
|
@abstractmethod
|
|
38
32
|
def purge(self, *, queue: str):
|
|
39
33
|
"""Purge all messages from the queue."""
|