queueio 0.4.0__tar.gz → 0.6.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.
Files changed (98) hide show
  1. queueio-0.6.0/.github/workflows/docs.yml +31 -0
  2. {queueio-0.4.0 → queueio-0.6.0}/.gitignore +3 -0
  3. {queueio-0.4.0 → queueio-0.6.0}/CHANGELOG.md +31 -1
  4. queueio-0.6.0/CNAME +1 -0
  5. {queueio-0.4.0 → queueio-0.6.0}/PKG-INFO +4 -1
  6. queueio-0.6.0/docs_overrides/assets/images/favicon.svg +1 -0
  7. queueio-0.6.0/docs_overrides/assets/stylesheets/extra.css +7 -0
  8. queueio-0.6.0/docs_overrides/partials/javascripts/palette.html +47 -0
  9. queueio-0.6.0/logo.svg +1 -0
  10. queueio-0.6.0/mkdocs.yml +41 -0
  11. queueio-0.4.0/principles.md → queueio-0.6.0/philosophy.md +1 -1
  12. {queueio-0.4.0 → queueio-0.6.0}/pyproject.toml +15 -2
  13. {queueio-0.4.0 → queueio-0.6.0}/queueio/__init__.py +2 -1
  14. {queueio-0.4.0 → queueio-0.6.0}/queueio/consumer.py +3 -0
  15. {queueio-0.4.0 → queueio-0.6.0}/queueio/continuation.py +2 -0
  16. queueio-0.6.0/queueio/django/__init__.py +17 -0
  17. queueio-0.6.0/queueio/django/apps.py +6 -0
  18. queueio-0.6.0/queueio/django/djp.py +6 -0
  19. queueio-0.6.0/queueio/django/management/commands/runserver.py +41 -0
  20. queueio-0.6.0/queueio/django/management/commands/runserver_plus.py +9 -0
  21. queueio-0.6.0/queueio/django/runserver_test.py +63 -0
  22. queueio-0.6.0/queueio/django.md +82 -0
  23. {queueio-0.4.0 → queueio-0.6.0}/queueio/freethreading_test.py +1 -1
  24. {queueio-0.4.0 → queueio-0.6.0}/queueio/invocation.py +7 -15
  25. queueio-0.6.0/queueio/pika/__init__.py +0 -0
  26. {queueio-0.4.0 → queueio-0.6.0}/queueio/queueio.py +11 -2
  27. queueio-0.6.0/queueio/queuevar.md +102 -0
  28. queueio-0.6.0/queueio/queuevar.py +71 -0
  29. queueio-0.6.0/queueio/samples/__init__.py +0 -0
  30. queueio-0.6.0/queueio/samples/django/__init__.py +0 -0
  31. queueio-0.6.0/queueio/samples/django/apps.py +6 -0
  32. queueio-0.6.0/queueio/samples/django/job.py +29 -0
  33. queueio-0.6.0/queueio/samples/django/models.py +3 -0
  34. queueio-0.6.0/queueio/samples/django/settings.py +19 -0
  35. queueio-0.6.0/queueio/samples/django/settings_djp.py +24 -0
  36. queueio-0.6.0/queueio/samples/django/urls.py +7 -0
  37. queueio-0.6.0/queueio/samples/django_test.py +98 -0
  38. {queueio-0.4.0 → queueio-0.6.0}/queueio/samples/priority.py +3 -3
  39. queueio-0.6.0/queueio/samples/queuevar.py +21 -0
  40. queueio-0.6.0/queueio/samples/queuevar_test.py +42 -0
  41. queueio-0.6.0/queueio/stub/__init__.py +0 -0
  42. {queueio-0.4.0 → queueio-0.6.0}/queueio/worker.py +13 -6
  43. queueio-0.4.0/.claude/settings.local.json +0 -9
  44. {queueio-0.4.0 → queueio-0.6.0}/.devcontainer/devcontainer.json +0 -0
  45. {queueio-0.4.0 → queueio-0.6.0}/.devcontainer/docker-compose.yml +0 -0
  46. {queueio-0.4.0 → queueio-0.6.0}/.github/workflows/build.yml +0 -0
  47. {queueio-0.4.0 → queueio-0.6.0}/.github/workflows/publish.yml +0 -0
  48. {queueio-0.4.0 → queueio-0.6.0}/LICENSE +0 -0
  49. {queueio-0.4.0 → queueio-0.6.0}/README.md +0 -0
  50. {queueio-0.4.0 → queueio-0.6.0}/dill.pyi +0 -0
  51. {queueio-0.4.0 → queueio-0.6.0/docs_overrides/assets/images}/logo.svg +0 -0
  52. /queueio-0.4.0/queueio/pika/__init__.py → /queueio-0.6.0/docs_overrides/partials/footer.html +0 -0
  53. {queueio-0.4.0 → queueio-0.6.0}/pyrightconfig.json +0 -0
  54. {queueio-0.4.0 → queueio-0.6.0}/queueio/__main__.py +0 -0
  55. {queueio-0.4.0 → queueio-0.6.0}/queueio/broker.py +0 -0
  56. {queueio-0.4.0 → queueio-0.6.0}/queueio/broker_test.py +0 -0
  57. {queueio-0.4.0 → queueio-0.6.0}/queueio/conftest.py +0 -0
  58. {queueio-0.4.0/queueio/samples → queueio-0.6.0/queueio/django/management}/__init__.py +0 -0
  59. {queueio-0.4.0/queueio/stub → queueio-0.6.0/queueio/django/management/commands}/__init__.py +0 -0
  60. {queueio-0.4.0 → queueio-0.6.0}/queueio/event.py +0 -0
  61. {queueio-0.4.0 → queueio-0.6.0}/queueio/gather.py +0 -0
  62. {queueio-0.4.0 → queueio-0.6.0}/queueio/id.py +0 -0
  63. {queueio-0.4.0 → queueio-0.6.0/queueio}/invocation.md +0 -0
  64. {queueio-0.4.0 → queueio-0.6.0}/queueio/journal.py +0 -0
  65. {queueio-0.4.0 → queueio-0.6.0}/queueio/journal_test.py +0 -0
  66. {queueio-0.4.0 → queueio-0.6.0}/queueio/message.py +0 -0
  67. {queueio-0.4.0 → queueio-0.6.0}/queueio/monitor.py +0 -0
  68. {queueio-0.4.0 → queueio-0.6.0}/queueio/pause.py +0 -0
  69. {queueio-0.4.0 → queueio-0.6.0}/queueio/pika/broker.py +0 -0
  70. {queueio-0.4.0 → queueio-0.6.0}/queueio/pika/broker_test.py +0 -0
  71. {queueio-0.4.0 → queueio-0.6.0}/queueio/pika/journal.py +0 -0
  72. {queueio-0.4.0 → queueio-0.6.0}/queueio/pika/journal_test.py +0 -0
  73. {queueio-0.4.0 → queueio-0.6.0}/queueio/pika/receiver.py +0 -0
  74. {queueio-0.4.0 → queueio-0.6.0}/queueio/pika/threadsafe.py +0 -0
  75. {queueio-0.4.0 → queueio-0.6.0}/queueio/queue.py +0 -0
  76. {queueio-0.4.0 → queueio-0.6.0}/queueio/queue_test.py +0 -0
  77. {queueio-0.4.0 → queueio-0.6.0}/queueio/queueio_test.py +0 -0
  78. {queueio-0.4.0 → queueio-0.6.0}/queueio/queuespec.md +0 -0
  79. {queueio-0.4.0 → queueio-0.6.0}/queueio/queuespec.py +0 -0
  80. {queueio-0.4.0 → queueio-0.6.0}/queueio/queuespec_test.py +0 -0
  81. {queueio-0.4.0 → queueio-0.6.0}/queueio/receiver.py +0 -0
  82. {queueio-0.4.0 → queueio-0.6.0}/queueio/registry.py +0 -0
  83. {queueio-0.4.0 → queueio-0.6.0}/queueio/result.py +0 -0
  84. {queueio-0.4.0 → queueio-0.6.0}/queueio/routine.py +0 -0
  85. {queueio-0.4.0 → queueio-0.6.0}/queueio/samples/basic.py +0 -0
  86. {queueio-0.4.0 → queueio-0.6.0}/queueio/samples/basic_test.py +0 -0
  87. {queueio-0.4.0 → queueio-0.6.0}/queueio/samples/expanded.py +0 -0
  88. {queueio-0.4.0 → queueio-0.6.0}/queueio/samples/expanded_test.py +0 -0
  89. {queueio-0.4.0 → queueio-0.6.0}/queueio/samples/priority_test.py +0 -0
  90. {queueio-0.4.0 → queueio-0.6.0}/queueio/select.py +0 -0
  91. {queueio-0.4.0 → queueio-0.6.0}/queueio/stream.py +0 -0
  92. {queueio-0.4.0 → queueio-0.6.0}/queueio/stub/broker.py +0 -0
  93. {queueio-0.4.0 → queueio-0.6.0}/queueio/stub/broker_test.py +0 -0
  94. {queueio-0.4.0 → queueio-0.6.0}/queueio/stub/journal.py +0 -0
  95. {queueio-0.4.0 → queueio-0.6.0}/queueio/stub/journal_test.py +0 -0
  96. {queueio-0.4.0 → queueio-0.6.0}/queueio/stub/receiver.py +0 -0
  97. {queueio-0.4.0 → queueio-0.6.0}/queueio/suspension.py +0 -0
  98. {queueio-0.4.0 → queueio-0.6.0}/queueio/thread.py +0 -0
@@ -0,0 +1,31 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ permissions:
8
+ pages: write
9
+ id-token: write
10
+
11
+ concurrency:
12
+ group: pages
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ deploy:
17
+ runs-on: ubuntu-latest
18
+ environment:
19
+ name: github-pages
20
+ url: ${{ steps.deployment.outputs.page_url }}
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - uses: astral-sh/setup-uv@v5
24
+ - run: uv python install 3.14t
25
+ - run: uv sync --group dev
26
+ - run: uv run mkdocs build --strict
27
+ - uses: actions/upload-pages-artifact@v3
28
+ with:
29
+ path: site
30
+ - id: deployment
31
+ uses: actions/deploy-pages@v4
@@ -15,3 +15,6 @@ uv.lock
15
15
  .python-version
16
16
  .ruff_cache/
17
17
  .ropeproject/
18
+
19
+ # MkDocs
20
+ site/
@@ -8,6 +8,34 @@ The format is loosely based on [Keep a Changelog](https://keepachangelog.com).
8
8
  [Unreleased]
9
9
  ------------
10
10
 
11
+ [0.6.0] - 2026-02-16
12
+ --------------------
13
+
14
+ ### Added
15
+
16
+ - Django integration (`queueio.django`) with `runserver` lifecycle management.
17
+ - [djp](https://djp.readthedocs.io/) support for automatic `INSTALLED_APPS` registration.
18
+ - [django-cmd](https://pypi.org/project/django-cmd/) integration for settings discovery.
19
+ - Documentation site.
20
+
21
+ [0.5.0] - 2026-02-08
22
+ --------------------
23
+
24
+ ### Breaking Changes
25
+
26
+ - `invocation.priority` removed;
27
+ use `invocation.context[priority]` instead.
28
+ - `Invocation.Submitted.priority` replaced with `context`.
29
+ - Serialized invocations from previous versions are incompatible.
30
+
31
+ ### Added
32
+
33
+ - `QueueVar` for user-defined serializable context variables
34
+ that propagate across queue boundaries.
35
+ - `QueueContext` mapping to access captured context on invocations.
36
+ - `priority` is now a `QueueVar` instance
37
+ (same context manager syntax as before).
38
+
11
39
  [0.4.0] - 2026-02-07
12
40
  --------------------
13
41
 
@@ -78,7 +106,9 @@ Thank you to Nick Anderegg for allowing me to use the queueio name for this proj
78
106
  - The queuespec syntax to `queue run` to consume multiple queues with shared capacity.
79
107
  - `queueio monitor` command to monitor activity in the queueio system.
80
108
 
81
- [Unreleased]: https://github.com/ryanhiebert/queueio/compare/tag/0.4.0...HEAD
109
+ [Unreleased]: https://github.com/ryanhiebert/queueio/compare/tag/0.6.0...HEAD
110
+ [0.6.0]: https://github.com/ryanhiebert/queueio/compare/tag/0.5.0...tag/0.6.0
111
+ [0.5.0]: https://github.com/ryanhiebert/queueio/compare/tag/0.4.0...tag/0.5.0
82
112
  [0.4.0]: https://github.com/ryanhiebert/queueio/compare/tag/0.3.0...tag/0.4.0
83
113
  [0.3.0]: https://github.com/ryanhiebert/queueio/compare/tag/0.2.2...tag/0.3.0
84
114
  [0.2.2]: https://github.com/ryanhiebert/queueio/compare/tag/0.2.1...tag/0.2.2
queueio-0.6.0/CNAME ADDED
@@ -0,0 +1 @@
1
+ queueio.dev
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: queueio
3
- Version: 0.4.0
3
+ Version: 0.6.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
@@ -18,6 +18,9 @@ Requires-Dist: dill>=0.3.9
18
18
  Requires-Dist: pika>=1.3.2
19
19
  Requires-Dist: textual>=3.2.0
20
20
  Requires-Dist: typer>=0.15.4
21
+ Provides-Extra: django
22
+ Requires-Dist: django-cmd>=3.0; extra == 'django'
23
+ Requires-Dist: django>=6.0; extra == 'django'
21
24
  Description-Content-Type: text/markdown
22
25
 
23
26
  ![queueio](https://raw.githubusercontent.com/ryanhiebert/queueio/main/logo.svg)
@@ -0,0 +1 @@
1
+ <svg width="128" height="128" viewBox="100 400 520 720" xmlns="http://www.w3.org/2000/svg"><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" fill="#0384d3"/></svg>
@@ -0,0 +1,7 @@
1
+ .md-header__topic {
2
+ display: none;
3
+ }
4
+
5
+ .md-nav--primary > .md-nav__title {
6
+ display: none;
7
+ }
@@ -0,0 +1,47 @@
1
+ <script>
2
+ (function () {
3
+ // Remove any cached palette so Material always re-evaluates media queries
4
+ try {
5
+ var key = __md_scope.pathname + ".__palette";
6
+ localStorage.removeItem(key);
7
+ } catch (e) {}
8
+
9
+ // Prevent Material from persisting palette choices
10
+ var origSet = __md_set;
11
+ __md_set = function (k) {
12
+ if (k === "__palette") return;
13
+ return origSet.apply(this, arguments);
14
+ };
15
+
16
+ function apply() {
17
+ var inputs = document.querySelectorAll("[data-md-color-media]");
18
+ for (var i = 0; i < inputs.length; i++) {
19
+ var media = inputs[i].getAttribute("data-md-color-media");
20
+ if (media && matchMedia(media).matches) {
21
+ document.body.setAttribute(
22
+ "data-md-color-scheme",
23
+ inputs[i].getAttribute("data-md-color-scheme"),
24
+ );
25
+ document.body.setAttribute(
26
+ "data-md-color-primary",
27
+ inputs[i].getAttribute("data-md-color-primary"),
28
+ );
29
+ document.body.setAttribute(
30
+ "data-md-color-accent",
31
+ inputs[i].getAttribute("data-md-color-accent"),
32
+ );
33
+ break;
34
+ }
35
+ }
36
+ }
37
+
38
+ if (document.body) apply();
39
+ document.addEventListener("DOMContentLoaded", apply);
40
+
41
+ // Listen on both light and dark queries for maximum compatibility
42
+ var dark = matchMedia("(prefers-color-scheme: dark)");
43
+ var light = matchMedia("(prefers-color-scheme: light)");
44
+ dark.addEventListener("change", apply);
45
+ light.addEventListener("change", apply);
46
+ })();
47
+ </script>
queueio-0.6.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,41 @@
1
+ site_name: queueio
2
+ site_description: Python background queues with an async twist
3
+ site_url: https://queueio.dev/
4
+ repo_url: https://github.com/ryanhiebert/queueio
5
+ repo_name: ryanhiebert/queueio
6
+
7
+ docs_dir: .
8
+
9
+ theme:
10
+ name: material
11
+ custom_dir: docs_overrides
12
+ logo: assets/images/logo.svg
13
+ favicon: assets/images/favicon.svg
14
+ palette:
15
+ - media: "(prefers-color-scheme: light)"
16
+ scheme: default
17
+ primary: white
18
+ - media: "(prefers-color-scheme: dark)"
19
+ scheme: slate
20
+ primary: black
21
+
22
+ markdown_extensions:
23
+ - admonition
24
+ - pymdownx.highlight
25
+ - pymdownx.superfences
26
+
27
+ extra_css:
28
+ - assets/stylesheets/extra.css
29
+
30
+ plugins:
31
+ - search
32
+ - same-dir
33
+
34
+ nav:
35
+ - Home: README.md
36
+ - Philosophy: philosophy.md
37
+ - Invocation: queueio/invocation.md
38
+ - QueueSpec: queueio/queuespec.md
39
+ - QueueVar: queueio/queuevar.md
40
+ - Django: queueio/django.md
41
+ - Changelog: CHANGELOG.md
@@ -1,4 +1,4 @@
1
- Principles
1
+ Philosophy
2
2
  ==========
3
3
 
4
4
  There's been a great deal of momentum to embrace async functions for IO,
@@ -1,10 +1,10 @@
1
1
  [project]
2
2
  name = "queueio"
3
- version = "0.4.0"
3
+ version = "0.6.0"
4
4
  description = "Python background queues with an async twist"
5
5
  readme = "README.md"
6
6
  license = "MIT"
7
- homepage = "https://github.com/ryanhiebert/queueio"
7
+ homepage = "https://queueio.dev/"
8
8
  repository = "https://github.com/ryanhiebert/queueio"
9
9
  authors = [
10
10
  { name = "Ryan Hiebert", email = "ryan@ryanhiebert.com" },
@@ -27,12 +27,23 @@ dependencies = [
27
27
  "typer>=0.15.4",
28
28
  ]
29
29
 
30
+ [project.optional-dependencies]
31
+ django = ["django>=6.0", "django-cmd>=3.0"]
32
+
33
+ [project.entry-points.djp]
34
+ queueio = "queueio.django.djp"
35
+
30
36
  [project.scripts]
31
37
  queueio = "queueio.__main__:app"
32
38
 
33
39
  [dependency-groups]
34
40
  dev = [
35
41
  "basedpyright>=1.32.1",
42
+ "django-extensions",
43
+ "djp",
44
+ "queueio[django]",
45
+ "mkdocs-material>=9.7.1",
46
+ "mkdocs-same-dir>=0.1.3",
36
47
  "pytest>=8.4.1",
37
48
  "pytest-cov>=6.0.0",
38
49
  "pytest-timeout>=2.4.0",
@@ -52,11 +63,13 @@ register = [
52
63
  "queueio.samples.basic",
53
64
  "queueio.samples.expanded",
54
65
  "queueio.samples.priority",
66
+ "queueio.samples.queuevar",
55
67
  ]
56
68
  pika = "amqp://localhost:5672"
57
69
 
58
70
  [tool.pytest.ini_options]
59
71
  timeout = 30
72
+ norecursedirs = ["site"]
60
73
 
61
74
  [tool.coverage.run]
62
75
  branch = true
@@ -1,9 +1,10 @@
1
1
  from contextlib import contextmanager
2
2
 
3
3
  from .gather import gather as gather
4
- from .invocation import priority as priority
5
4
  from .pause import pause as pause
6
5
  from .queueio import QueueIO as RealQueueIO
6
+ from .queueio import priority as priority
7
+ from .queuevar import QueueVar as QueueVar
7
8
  from .registry import routine as routine
8
9
 
9
10
 
@@ -2,6 +2,7 @@ from collections.abc import Callable
2
2
  from collections.abc import Generator
3
3
  from collections.abc import Iterable
4
4
  from collections.abc import Iterator
5
+ from contextvars import Context
5
6
  from typing import Any
6
7
 
7
8
  from .invocation import Invocation
@@ -41,6 +42,7 @@ class Consumer(Iterable[Invocation]):
41
42
  invocation: Invocation,
42
43
  generator: Generator,
43
44
  suspension: Suspension | None,
45
+ context: Context,
44
46
  ):
45
47
  """Signal that the invocation has suspended."""
46
48
  if suspension:
@@ -51,6 +53,7 @@ class Consumer(Iterable[Invocation]):
51
53
  suspension=suspension,
52
54
  invocation=invocation,
53
55
  generator=generator,
56
+ context=context,
54
57
  )
55
58
  )
56
59
  self.__receiver.pause(self.__invocations[invocation])
@@ -1,5 +1,6 @@
1
1
  from collections.abc import Callable
2
2
  from collections.abc import Generator
3
+ from contextvars import Context
3
4
  from dataclasses import dataclass
4
5
  from dataclasses import field
5
6
  from typing import Any
@@ -17,6 +18,7 @@ class Continuation[T: Callable[..., Any] = Callable[..., Any]]:
17
18
  invocation: Invocation
18
19
  generator: Generator[Invocation, Any, Any]
19
20
  result: Result[Any, BaseException]
21
+ context: Context
20
22
 
21
23
  def resume(self) -> Any:
22
24
  match self.result:
@@ -0,0 +1,17 @@
1
+ import os
2
+
3
+
4
+ def setup():
5
+ try:
6
+ from django_cmd import configure
7
+
8
+ configure()
9
+ except ImportError:
10
+ pass
11
+
12
+ if "DJANGO_SETTINGS_MODULE" not in os.environ:
13
+ return
14
+
15
+ import django
16
+
17
+ django.setup()
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class QueueIOConfig(AppConfig):
5
+ name = "queueio.django"
6
+ label = "queueio"
@@ -0,0 +1,6 @@
1
+ import djp
2
+
3
+
4
+ @djp.hookimpl
5
+ def installed_apps():
6
+ return ["queueio.django"]
@@ -0,0 +1,41 @@
1
+ import tomllib
2
+ from importlib import import_module
3
+ from pathlib import Path
4
+
5
+
6
+ def _find_pyproject() -> Path | None:
7
+ for path in [cwd := Path.cwd(), *cwd.parents]:
8
+ candidate = path / "pyproject.toml"
9
+ if candidate.is_file():
10
+ return candidate
11
+ return None
12
+
13
+
14
+ def _queueio_django_config() -> dict:
15
+ if pyproject := _find_pyproject():
16
+ with pyproject.open("rb") as f:
17
+ config = tomllib.load(f)
18
+ return config.get("tool", {}).get("queueio", {}).get("django", {})
19
+ return {}
20
+
21
+
22
+ def _resolve_base_command(config: dict):
23
+ runserver_app = config.get("runserver", "django.core")
24
+ module = import_module(f"{runserver_app}.management.commands.runserver")
25
+ return module.Command
26
+
27
+
28
+ def _build_command():
29
+ base = _resolve_base_command(_queueio_django_config())
30
+
31
+ class Command(base):
32
+ def inner_run(self, *args, **options):
33
+ from queueio import activate
34
+
35
+ with activate():
36
+ super().inner_run(*args, **options)
37
+
38
+ return Command
39
+
40
+
41
+ Command = _build_command()
@@ -0,0 +1,9 @@
1
+ from django_extensions.management.commands.runserver_plus import Command as BaseCommand
2
+
3
+ from queueio import activate
4
+
5
+
6
+ class Command(BaseCommand):
7
+ def inner_run(self, options):
8
+ with activate():
9
+ super().inner_run(options)
@@ -0,0 +1,63 @@
1
+ import os
2
+ import sysconfig
3
+
4
+ import pytest
5
+
6
+ from .management.commands.runserver import _build_command
7
+
8
+
9
+ @pytest.mark.parametrize(
10
+ ("runserver_app", "expected_module"),
11
+ [
12
+ pytest.param(
13
+ None,
14
+ "django.core.management.commands.runserver",
15
+ id="default",
16
+ ),
17
+ pytest.param(
18
+ "django.contrib.staticfiles",
19
+ "django.contrib.staticfiles.management.commands.runserver",
20
+ id="staticfiles",
21
+ ),
22
+ pytest.param(
23
+ "daphne",
24
+ "daphne.management.commands.runserver",
25
+ id="daphne",
26
+ marks=pytest.mark.skipif(
27
+ bool(sysconfig.get_config_var("Py_GIL_DISABLED")),
28
+ reason="zope.interface re-enables the GIL on free-threaded builds",
29
+ ),
30
+ ),
31
+ ],
32
+ )
33
+ def test_runserver(tmp_path, runserver_app, expected_module):
34
+ config = "[tool.queueio]\n"
35
+ if runserver_app is not None:
36
+ config = f'[tool.queueio.django]\nrunserver = "{runserver_app}"\n'
37
+
38
+ pyproject = tmp_path / "pyproject.toml"
39
+ pyproject.write_text(config)
40
+
41
+ original_cwd = os.getcwd()
42
+ os.chdir(tmp_path)
43
+ try:
44
+ Cmd = _build_command()
45
+ from importlib import import_module
46
+
47
+ expected = import_module(expected_module)
48
+ assert issubclass(Cmd, expected.Command)
49
+ finally:
50
+ os.chdir(original_cwd)
51
+
52
+
53
+ def test_runserver_plus():
54
+ from django.conf import settings
55
+
56
+ if not settings.configured:
57
+ settings.configure()
58
+
59
+ from django_extensions.management.commands import runserver_plus as expected
60
+
61
+ from .management.commands.runserver_plus import Command
62
+
63
+ assert issubclass(Command, expected.Command)
@@ -0,0 +1,82 @@
1
+ # Django Integration
2
+
3
+ ## Settings
4
+
5
+ When running `queueio run`,
6
+ Django isn't started automatically.
7
+ If your routines use the Django ORM or other Django features,
8
+ queueio needs to call `django.setup()` before importing routine modules.
9
+
10
+ The `queueio[django]` extra includes
11
+ [django-cmd](https://pypi.org/project/django-cmd/),
12
+ which reads `tool.django.settings` from `pyproject.toml`
13
+ and sets `DJANGO_SETTINGS_MODULE` automatically:
14
+
15
+ ```toml
16
+ [tool.django]
17
+ settings = "myproject.settings"
18
+ ```
19
+
20
+ If `DJANGO_SETTINGS_MODULE` is set in the environment
21
+ (whether by `django-cmd` or directly),
22
+ queueio calls `django.setup()` before registering routines.
23
+
24
+ ## Runserver
25
+
26
+ !!! warning "Development server only"
27
+
28
+ The runserver integration only affects the `runserver` management command.
29
+ Production servers like gunicorn need their own lifecycle hooks.
30
+
31
+ queueio ships a Django app
32
+ that overrides the `runserver` management command
33
+ to wrap the server in `with activate():`,
34
+ giving you automatic lifecycle management
35
+ of the broker connection and background threads
36
+ during development.
37
+
38
+ ### Setup
39
+
40
+ If you use [djp](https://djp.readthedocs.io/),
41
+ `queueio.django` is added to `INSTALLED_APPS` automatically.
42
+
43
+ Otherwise, add it manually:
44
+
45
+ ```python
46
+ INSTALLED_APPS = [
47
+ "queueio.django",
48
+ # ...
49
+ ]
50
+ ```
51
+
52
+ This must come **after** whichever app provides the `runserver` command
53
+ you want to chain with
54
+ (e.g. `daphne`, `django.contrib.staticfiles`),
55
+ because Django resolves management commands by last-app-wins order.
56
+
57
+ ### Custom provider
58
+
59
+ If you use a custom `runserver` provider
60
+ like `django.contrib.staticfiles` or `daphne`,
61
+ tell queueio which app to chain with:
62
+
63
+ ```toml
64
+ [tool.queueio.django]
65
+ runserver = "daphne"
66
+ ```
67
+
68
+ The value is the app label (or dotted module path).
69
+ queueio resolves it to `{value}.management.commands.runserver`
70
+ and subclasses that command.
71
+ If omitted, it defaults to Django's built-in `runserver`.
72
+
73
+ ### `runserver_plus`
74
+
75
+ There is also a built-in `runserver_plus` command
76
+ that wraps [django-extensions](https://django-extensions.readthedocs.io/)' `runserver_plus`
77
+ with `activate()`.
78
+ This is available automatically
79
+ when `django-extensions` is installed
80
+ and `queueio.django` is in `INSTALLED_APPS`.
81
+
82
+ A complete working example is in `queueio/samples/django/`.
@@ -22,7 +22,7 @@ def test_all_queueio_modules_preserve_freethreading():
22
22
  for _, modname, _ in pkgutil.walk_packages(
23
23
  queueio.__path__, f"{queueio.__name__}."
24
24
  ):
25
- if not modname.endswith("_test"):
25
+ if not modname.endswith("_test") and ".django" not in modname:
26
26
  importlib.import_module(modname)
27
27
 
28
28
  assert sys._is_gil_enabled() is False
@@ -3,6 +3,7 @@ from collections.abc import Callable
3
3
  from collections.abc import Generator
4
4
  from concurrent.futures import Future
5
5
  from contextlib import contextmanager
6
+ from contextvars import Context
6
7
  from contextvars import ContextVar
7
8
  from dataclasses import dataclass
8
9
  from dataclasses import field
@@ -11,19 +12,9 @@ from typing import Self
11
12
 
12
13
  from .event import Event
13
14
  from .id import random_id
15
+ from .queuevar import QueueContext
14
16
  from .suspension import Suspension
15
17
 
16
- _priority = ContextVar[int]("priority", default=4)
17
-
18
-
19
- @contextmanager
20
- def priority(level: int, /):
21
- token = _priority.set(level)
22
- try:
23
- yield
24
- finally:
25
- _priority.reset(token)
26
-
27
18
 
28
19
  @dataclass(eq=False, kw_only=True)
29
20
  class Invocation[R](Suspension[R]):
@@ -31,7 +22,7 @@ class Invocation[R](Suspension[R]):
31
22
  routine: str
32
23
  args: tuple[Any, ...]
33
24
  kwargs: dict[str, Any]
34
- priority: int = field(default_factory=_priority.get)
25
+ context: QueueContext = field(default_factory=QueueContext.capture)
35
26
 
36
27
  __handler = ContextVar[Callable[[Self], Future] | None](
37
28
  "Invocation.handler", default=None
@@ -68,7 +59,7 @@ class Invocation[R](Suspension[R]):
68
59
  "routine": self.routine,
69
60
  "args": self.args,
70
61
  "kwargs": self.kwargs,
71
- "priority": self.priority,
62
+ "context": self.context.serialize(),
72
63
  }
73
64
  ).encode()
74
65
 
@@ -80,7 +71,7 @@ class Invocation[R](Suspension[R]):
80
71
  routine=data["routine"],
81
72
  args=data["args"],
82
73
  kwargs=data["kwargs"],
83
- priority=data["priority"],
74
+ context=QueueContext.deserialize(data.get("context", {})),
84
75
  )
85
76
 
86
77
  @dataclass(eq=False, kw_only=True, repr=False)
@@ -88,7 +79,7 @@ class Invocation[R](Suspension[R]):
88
79
  routine: str
89
80
  args: tuple[Any]
90
81
  kwargs: dict[str, Any]
91
- priority: int
82
+ context: QueueContext
92
83
 
93
84
  @dataclass(eq=False, kw_only=True)
94
85
  class Started(Event): ...
@@ -104,6 +95,7 @@ class Invocation[R](Suspension[R]):
104
95
  suspension: Suspension = field(repr=False)
105
96
  generator: Generator[Invocation, Any, Any] = field(repr=False)
106
97
  invocation: Invocation = field(repr=False)
98
+ context: Context = field(repr=False)
107
99
 
108
100
  @dataclass(eq=False, kw_only=True)
109
101
  class BaseContinued(Event):
File without changes