queueio 0.4.0__tar.gz → 0.5.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 (73) hide show
  1. {queueio-0.4.0 → queueio-0.5.0}/CHANGELOG.md +20 -1
  2. {queueio-0.4.0 → queueio-0.5.0}/PKG-INFO +1 -1
  3. {queueio-0.4.0 → queueio-0.5.0}/pyproject.toml +2 -1
  4. {queueio-0.4.0 → queueio-0.5.0}/queueio/__init__.py +2 -1
  5. {queueio-0.4.0 → queueio-0.5.0}/queueio/consumer.py +3 -0
  6. {queueio-0.4.0 → queueio-0.5.0}/queueio/continuation.py +2 -0
  7. {queueio-0.4.0 → queueio-0.5.0}/queueio/invocation.py +7 -15
  8. {queueio-0.4.0 → queueio-0.5.0}/queueio/queueio.py +7 -2
  9. queueio-0.5.0/queueio/queuevar.md +102 -0
  10. queueio-0.5.0/queueio/queuevar.py +71 -0
  11. {queueio-0.4.0 → queueio-0.5.0}/queueio/samples/priority.py +3 -3
  12. queueio-0.5.0/queueio/samples/queuevar.py +21 -0
  13. queueio-0.5.0/queueio/samples/queuevar_test.py +42 -0
  14. {queueio-0.4.0 → queueio-0.5.0}/queueio/worker.py +13 -6
  15. {queueio-0.4.0 → queueio-0.5.0}/.claude/settings.local.json +0 -0
  16. {queueio-0.4.0 → queueio-0.5.0}/.devcontainer/devcontainer.json +0 -0
  17. {queueio-0.4.0 → queueio-0.5.0}/.devcontainer/docker-compose.yml +0 -0
  18. {queueio-0.4.0 → queueio-0.5.0}/.github/workflows/build.yml +0 -0
  19. {queueio-0.4.0 → queueio-0.5.0}/.github/workflows/publish.yml +0 -0
  20. {queueio-0.4.0 → queueio-0.5.0}/.gitignore +0 -0
  21. {queueio-0.4.0 → queueio-0.5.0}/LICENSE +0 -0
  22. {queueio-0.4.0 → queueio-0.5.0}/README.md +0 -0
  23. {queueio-0.4.0 → queueio-0.5.0}/dill.pyi +0 -0
  24. {queueio-0.4.0 → queueio-0.5.0}/invocation.md +0 -0
  25. {queueio-0.4.0 → queueio-0.5.0}/logo.svg +0 -0
  26. {queueio-0.4.0 → queueio-0.5.0}/principles.md +0 -0
  27. {queueio-0.4.0 → queueio-0.5.0}/pyrightconfig.json +0 -0
  28. {queueio-0.4.0 → queueio-0.5.0}/queueio/__main__.py +0 -0
  29. {queueio-0.4.0 → queueio-0.5.0}/queueio/broker.py +0 -0
  30. {queueio-0.4.0 → queueio-0.5.0}/queueio/broker_test.py +0 -0
  31. {queueio-0.4.0 → queueio-0.5.0}/queueio/conftest.py +0 -0
  32. {queueio-0.4.0 → queueio-0.5.0}/queueio/event.py +0 -0
  33. {queueio-0.4.0 → queueio-0.5.0}/queueio/freethreading_test.py +0 -0
  34. {queueio-0.4.0 → queueio-0.5.0}/queueio/gather.py +0 -0
  35. {queueio-0.4.0 → queueio-0.5.0}/queueio/id.py +0 -0
  36. {queueio-0.4.0 → queueio-0.5.0}/queueio/journal.py +0 -0
  37. {queueio-0.4.0 → queueio-0.5.0}/queueio/journal_test.py +0 -0
  38. {queueio-0.4.0 → queueio-0.5.0}/queueio/message.py +0 -0
  39. {queueio-0.4.0 → queueio-0.5.0}/queueio/monitor.py +0 -0
  40. {queueio-0.4.0 → queueio-0.5.0}/queueio/pause.py +0 -0
  41. {queueio-0.4.0 → queueio-0.5.0}/queueio/pika/__init__.py +0 -0
  42. {queueio-0.4.0 → queueio-0.5.0}/queueio/pika/broker.py +0 -0
  43. {queueio-0.4.0 → queueio-0.5.0}/queueio/pika/broker_test.py +0 -0
  44. {queueio-0.4.0 → queueio-0.5.0}/queueio/pika/journal.py +0 -0
  45. {queueio-0.4.0 → queueio-0.5.0}/queueio/pika/journal_test.py +0 -0
  46. {queueio-0.4.0 → queueio-0.5.0}/queueio/pika/receiver.py +0 -0
  47. {queueio-0.4.0 → queueio-0.5.0}/queueio/pika/threadsafe.py +0 -0
  48. {queueio-0.4.0 → queueio-0.5.0}/queueio/queue.py +0 -0
  49. {queueio-0.4.0 → queueio-0.5.0}/queueio/queue_test.py +0 -0
  50. {queueio-0.4.0 → queueio-0.5.0}/queueio/queueio_test.py +0 -0
  51. {queueio-0.4.0 → queueio-0.5.0}/queueio/queuespec.md +0 -0
  52. {queueio-0.4.0 → queueio-0.5.0}/queueio/queuespec.py +0 -0
  53. {queueio-0.4.0 → queueio-0.5.0}/queueio/queuespec_test.py +0 -0
  54. {queueio-0.4.0 → queueio-0.5.0}/queueio/receiver.py +0 -0
  55. {queueio-0.4.0 → queueio-0.5.0}/queueio/registry.py +0 -0
  56. {queueio-0.4.0 → queueio-0.5.0}/queueio/result.py +0 -0
  57. {queueio-0.4.0 → queueio-0.5.0}/queueio/routine.py +0 -0
  58. {queueio-0.4.0 → queueio-0.5.0}/queueio/samples/__init__.py +0 -0
  59. {queueio-0.4.0 → queueio-0.5.0}/queueio/samples/basic.py +0 -0
  60. {queueio-0.4.0 → queueio-0.5.0}/queueio/samples/basic_test.py +0 -0
  61. {queueio-0.4.0 → queueio-0.5.0}/queueio/samples/expanded.py +0 -0
  62. {queueio-0.4.0 → queueio-0.5.0}/queueio/samples/expanded_test.py +0 -0
  63. {queueio-0.4.0 → queueio-0.5.0}/queueio/samples/priority_test.py +0 -0
  64. {queueio-0.4.0 → queueio-0.5.0}/queueio/select.py +0 -0
  65. {queueio-0.4.0 → queueio-0.5.0}/queueio/stream.py +0 -0
  66. {queueio-0.4.0 → queueio-0.5.0}/queueio/stub/__init__.py +0 -0
  67. {queueio-0.4.0 → queueio-0.5.0}/queueio/stub/broker.py +0 -0
  68. {queueio-0.4.0 → queueio-0.5.0}/queueio/stub/broker_test.py +0 -0
  69. {queueio-0.4.0 → queueio-0.5.0}/queueio/stub/journal.py +0 -0
  70. {queueio-0.4.0 → queueio-0.5.0}/queueio/stub/journal_test.py +0 -0
  71. {queueio-0.4.0 → queueio-0.5.0}/queueio/stub/receiver.py +0 -0
  72. {queueio-0.4.0 → queueio-0.5.0}/queueio/suspension.py +0 -0
  73. {queueio-0.4.0 → queueio-0.5.0}/queueio/thread.py +0 -0
@@ -8,6 +8,24 @@ The format is loosely based on [Keep a Changelog](https://keepachangelog.com).
8
8
  [Unreleased]
9
9
  ------------
10
10
 
11
+ [0.5.0] - 2026-02-08
12
+ --------------------
13
+
14
+ ### Breaking Changes
15
+
16
+ - `invocation.priority` removed;
17
+ use `invocation.context[priority]` instead.
18
+ - `Invocation.Submitted.priority` replaced with `context`.
19
+ - Serialized invocations from previous versions are incompatible.
20
+
21
+ ### Added
22
+
23
+ - `QueueVar` for user-defined serializable context variables
24
+ that propagate across queue boundaries.
25
+ - `QueueContext` mapping to access captured context on invocations.
26
+ - `priority` is now a `QueueVar` instance
27
+ (same context manager syntax as before).
28
+
11
29
  [0.4.0] - 2026-02-07
12
30
  --------------------
13
31
 
@@ -78,7 +96,8 @@ Thank you to Nick Anderegg for allowing me to use the queueio name for this proj
78
96
  - The queuespec syntax to `queue run` to consume multiple queues with shared capacity.
79
97
  - `queueio monitor` command to monitor activity in the queueio system.
80
98
 
81
- [Unreleased]: https://github.com/ryanhiebert/queueio/compare/tag/0.4.0...HEAD
99
+ [Unreleased]: https://github.com/ryanhiebert/queueio/compare/tag/0.5.0...HEAD
100
+ [0.5.0]: https://github.com/ryanhiebert/queueio/compare/tag/0.4.0...tag/0.5.0
82
101
  [0.4.0]: https://github.com/ryanhiebert/queueio/compare/tag/0.3.0...tag/0.4.0
83
102
  [0.3.0]: https://github.com/ryanhiebert/queueio/compare/tag/0.2.2...tag/0.3.0
84
103
  [0.2.2]: https://github.com/ryanhiebert/queueio/compare/tag/0.2.1...tag/0.2.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: queueio
3
- Version: 0.4.0
3
+ Version: 0.5.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "queueio"
3
- version = "0.4.0"
3
+ version = "0.5.0"
4
4
  description = "Python background queues with an async twist"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -52,6 +52,7 @@ register = [
52
52
  "queueio.samples.basic",
53
53
  "queueio.samples.expanded",
54
54
  "queueio.samples.priority",
55
+ "queueio.samples.queuevar",
55
56
  ]
56
57
  pika = "amqp://localhost:5672"
57
58
 
@@ -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:
@@ -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):
@@ -17,6 +17,7 @@ from .message import Message
17
17
  from .queue import Queue
18
18
  from .queue import ShutDown
19
19
  from .queuespec import QueueSpec
20
+ from .queuevar import QueueVar
20
21
  from .registry import ROUTINE_REGISTRY
21
22
  from .result import Err
22
23
  from .result import Ok
@@ -24,6 +25,8 @@ from .routine import Routine
24
25
  from .stream import Stream
25
26
  from .thread import Thread
26
27
 
28
+ priority: QueueVar[int] = QueueVar("priority", default=4)
29
+
27
30
 
28
31
  class QueueIO:
29
32
  __active = ContextVar[Self | None]("active", default=None)
@@ -181,12 +184,14 @@ class QueueIO:
181
184
  routine=invocation.routine,
182
185
  args=invocation.args,
183
186
  kwargs=invocation.kwargs,
184
- priority=invocation.priority,
187
+ context=invocation.context,
185
188
  )
186
189
  )
187
190
  queue = routine.queue
188
191
  self.__broker.enqueue(
189
- invocation.serialize(), queue=queue, priority=invocation.priority
192
+ invocation.serialize(),
193
+ queue=queue,
194
+ priority=invocation.context.get(priority, priority.get()),
190
195
  )
191
196
 
192
197
  def consume(self, queuespec: QueueSpec, /) -> Consumer:
@@ -0,0 +1,102 @@
1
+ # QueueVar
2
+
3
+ Queue variables propagate context across queue boundaries.
4
+ Like Python's `contextvars.ContextVar`,
5
+ but values automatically serialize with invocations
6
+ and are restored when the worker executes them.
7
+
8
+ ## Defining a QueueVar
9
+
10
+ ```python
11
+ from queueio import QueueVar
12
+
13
+ tenant_id = QueueVar("tenant_id", default=None)
14
+ ```
15
+
16
+ Creating a `QueueVar` registers it globally.
17
+ All registered queue variables are captured
18
+ when an `Invocation` is created,
19
+ and restored when the worker runs it.
20
+
21
+ ## Setting and reading values
22
+
23
+ Set a value with the context manager:
24
+
25
+ ```python
26
+ with tenant_id("acme"):
27
+ # tenant_id.get() returns "acme" here
28
+ invocation = my_routine()
29
+ # tenant_id is captured in invocation.context
30
+ ```
31
+
32
+ Read the current value with `.get()`:
33
+
34
+ ```python
35
+ @routine(name="greet", queue="default")
36
+ def greet():
37
+ tid = tenant_id.get()
38
+ print(f"Hello from tenant {tid}")
39
+ ```
40
+
41
+ ## Propagation
42
+
43
+ When an invocation is created,
44
+ the current values of all registered queue variables are captured
45
+ into `invocation.context`.
46
+ The worker restores these values before executing the routine,
47
+ so any invocations created during execution inherit the parent's context.
48
+
49
+ ```python
50
+ @routine(name="inner", queue="work")
51
+ def inner():
52
+ return tenant_id.get()
53
+
54
+ @routine(name="outer", queue="work")
55
+ async def outer():
56
+ with tenant_id("acme"):
57
+ result = await inner()
58
+ assert result == "acme"
59
+ ```
60
+
61
+ ## Accessing context on an invocation
62
+
63
+ The `QueueVar` instance is a key,
64
+ and `QueueContext` behaves like a mapping:
65
+
66
+ ```python
67
+ with tenant_id("acme"):
68
+ invocation = my_routine()
69
+ print(invocation.context[tenant_id]) # "acme"
70
+ print(tenant_id in invocation.context) # True
71
+
72
+ invocation = my_routine()
73
+ # invocation.context[tenant_id] would raise KeyError
74
+ print(tenant_id in invocation.context) # False
75
+ print(invocation.context.get(tenant_id, "unknown")) # "unknown"
76
+ ```
77
+
78
+ ## Priority
79
+
80
+ Priority is a built-in queue variable:
81
+
82
+ ```python
83
+ from queueio import priority
84
+
85
+ with priority(2):
86
+ invocation = my_routine()
87
+ print(invocation.context[priority]) # 2
88
+ print(invocation.context.get(priority, priority.default)) # 2
89
+ ```
90
+
91
+ ## Context variables
92
+
93
+ Regular `contextvars.ContextVar` values
94
+ do not propagate across queue boundaries.
95
+ Use `QueueVar` for any context that needs to travel
96
+ with invocations.
97
+
98
+ ## Serialization
99
+
100
+ Values must be JSON-serializable.
101
+ The context is included in the serialized invocation
102
+ and restored on deserialization.
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import contextmanager
4
+ from contextvars import Context
5
+ from contextvars import ContextVar
6
+ from contextvars import copy_context
7
+ from typing import Any
8
+
9
+ _registry: dict[ContextVar, QueueVar] = {}
10
+
11
+
12
+ class QueueVar[T]:
13
+ def __init__(self, name: str, *, default: T):
14
+ self.__default = default
15
+ self.__var: ContextVar[T] = ContextVar(name, default=default)
16
+ _registry[self.__var] = self
17
+
18
+ @property
19
+ def name(self) -> str:
20
+ return self.__var.name
21
+
22
+ def get(self) -> T:
23
+ return self.__var.get()
24
+
25
+ @contextmanager
26
+ def __call__(self, value: T, /):
27
+ token = self.__var.set(value)
28
+ try:
29
+ yield
30
+ finally:
31
+ self.__var.reset(token)
32
+
33
+
34
+ class QueueContext:
35
+ def __init__(self, data: dict[str, Any]):
36
+ self.__data = data
37
+
38
+ def __getitem__[T](self, var: QueueVar[T]) -> T:
39
+ return self.__data[var.name]
40
+
41
+ def get[T, Default](self, var: QueueVar[T], default: Default = None) -> T | Default:
42
+ return self.__data.get(var.name, default)
43
+
44
+ def __contains__(self, var: object) -> bool:
45
+ if not isinstance(var, QueueVar):
46
+ return False
47
+ return var.name in self.__data
48
+
49
+ def load(self, ctx: Context) -> None:
50
+ def initialize():
51
+ for contextvar, queuevar in _registry.items():
52
+ if queuevar.name in self.__data:
53
+ contextvar.set(self.__data[queuevar.name])
54
+
55
+ ctx.run(initialize)
56
+
57
+ def serialize(self) -> dict[str, Any]:
58
+ return dict(self.__data)
59
+
60
+ @classmethod
61
+ def deserialize(cls, data: dict[str, Any]) -> QueueContext:
62
+ return cls(data)
63
+
64
+ @classmethod
65
+ def capture(cls) -> QueueContext:
66
+ data: dict[str, Any] = {}
67
+ ctx = copy_context()
68
+ for contextvar in ctx:
69
+ if contextvar in _registry:
70
+ data[_registry[contextvar].name] = ctx[contextvar]
71
+ return cls(data)
@@ -4,16 +4,16 @@ from queueio import routine
4
4
 
5
5
  @routine(name="inherited_priority", queue="priority")
6
6
  def inherited_priority():
7
- return inherited_priority().priority
7
+ return priority.get()
8
8
 
9
9
 
10
10
  @routine(name="demonstrate_priority_inheritance", queue="priority")
11
11
  async def demonstrate_priorities():
12
12
  invocation = inherited_priority()
13
- assert invocation.priority == 4
13
+ assert priority not in invocation.context
14
14
  assert await invocation == 4
15
15
 
16
16
  with priority(2):
17
17
  invocation = inherited_priority()
18
- assert invocation.priority == 2
18
+ assert invocation.context[priority] == 2
19
19
  assert await invocation == 2
@@ -0,0 +1,21 @@
1
+ from queueio import QueueVar
2
+ from queueio import routine
3
+
4
+ tenant_id = QueueVar[str | None]("tenant_id", default=None)
5
+
6
+
7
+ @routine(name="get_tenant", queue="queuevar")
8
+ def get_tenant():
9
+ return tenant_id.get()
10
+
11
+
12
+ @routine(name="demonstrate_queuevar", queue="queuevar")
13
+ async def demonstrate_queuevar():
14
+ invocation = get_tenant()
15
+ assert tenant_id not in invocation.context
16
+ assert await invocation is None
17
+
18
+ with tenant_id("acme"):
19
+ invocation = get_tenant()
20
+ assert invocation.context[tenant_id] == "acme"
21
+ assert await invocation == "acme"
@@ -0,0 +1,42 @@
1
+ import subprocess
2
+ import sys
3
+
4
+ import pytest
5
+
6
+ from queueio.invocation import Invocation
7
+ from queueio.queueio import QueueIO
8
+ from queueio.result import Ok
9
+
10
+ from .queuevar import demonstrate_queuevar
11
+
12
+
13
+ @pytest.mark.timeout(10)
14
+ def test_queuevar_propagation():
15
+ """QueueVar values propagate through the full invocation chain."""
16
+ queueio = QueueIO()
17
+
18
+ with queueio.activate():
19
+ queueio.create(queue="queuevar")
20
+ queueio.purge(queue="queuevar")
21
+ events = queueio.subscribe({Invocation.Completed})
22
+ invocation = demonstrate_queuevar()
23
+ queueio.submit(invocation)
24
+
25
+ proc = subprocess.Popen(
26
+ [sys.executable, "-m", "queueio", "run", "queuevar=2"],
27
+ stdout=subprocess.PIPE,
28
+ stderr=subprocess.PIPE,
29
+ )
30
+ try:
31
+ while event := events.get():
32
+ if event.id == invocation.id:
33
+ assert isinstance(event.result, Ok)
34
+ break
35
+ finally:
36
+ if proc.poll() is None:
37
+ proc.terminate()
38
+ try:
39
+ proc.wait(timeout=5)
40
+ except subprocess.TimeoutExpired:
41
+ proc.kill()
42
+ proc.wait()
@@ -3,11 +3,11 @@ from concurrent.futures import FIRST_COMPLETED
3
3
  from concurrent.futures import Future
4
4
  from concurrent.futures import wait
5
5
  from contextlib import suppress
6
+ from contextvars import copy_context
6
7
  from threading import Timer
7
8
 
8
9
  from .continuation import Continuation
9
10
  from .invocation import Invocation
10
- from .invocation import priority
11
11
  from .queue import Queue
12
12
  from .queue import ShutDown
13
13
  from .queueio import QueueIO
@@ -105,6 +105,7 @@ class Worker:
105
105
  invocation=event.invocation,
106
106
  generator=event.generator,
107
107
  result=Ok(None),
108
+ context=event.context,
108
109
  )
109
110
  # Replace ``new`` before setting the result
110
111
  # to avoid short busy wait loops.
@@ -144,6 +145,7 @@ class Worker:
144
145
  invocation=continuation.invocation,
145
146
  generator=continuation.generator,
146
147
  result=Err(exception),
148
+ context=continuation.context,
147
149
  )
148
150
  )
149
151
  else:
@@ -156,6 +158,7 @@ class Worker:
156
158
  invocation=continuation.invocation,
157
159
  generator=continuation.generator,
158
160
  result=Ok(value),
161
+ context=continuation.context,
159
162
  ),
160
163
  )
161
164
 
@@ -190,9 +193,10 @@ class Worker:
190
193
  def __run_invocation(self, invocation: Invocation):
191
194
  """Process an invocation task."""
192
195
  routine = self.__queueio.routine(invocation.routine)
196
+ ctx = copy_context()
197
+ invocation.context.load(ctx)
193
198
  try:
194
- with priority(invocation.priority):
195
- result = routine.fn(*invocation.args, **invocation.kwargs)
199
+ result = ctx.run(routine.fn, *invocation.args, **invocation.kwargs)
196
200
  except Exception as exception:
197
201
  self.__consumer.error(invocation, exception)
198
202
  else:
@@ -203,6 +207,7 @@ class Worker:
203
207
  invocation=invocation,
204
208
  generator=generator,
205
209
  result=Ok(None),
210
+ context=ctx,
206
211
  )
207
212
  )
208
213
  else:
@@ -213,15 +218,17 @@ class Worker:
213
218
  method = continuation.resume
214
219
 
215
220
  try:
216
- with priority(continuation.invocation.priority):
217
- suspension = method()
221
+ suspension = continuation.context.run(method)
218
222
  except StopIteration as stop:
219
223
  self.__consumer.succeed(continuation.invocation, stop.value)
220
224
  except Exception as exception:
221
225
  self.__consumer.error(continuation.invocation, exception)
222
226
  else:
223
227
  self.__consumer.suspend(
224
- continuation.invocation, continuation.generator, suspension
228
+ continuation.invocation,
229
+ continuation.generator,
230
+ suspension,
231
+ continuation.context,
225
232
  )
226
233
 
227
234
  def stop(self):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes