taraqueue 0.0.2.dev0__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.
Files changed (45) hide show
  1. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/.github/actions/setup-uv-env/action.yml +5 -0
  2. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/.github/workflows/publish.yml +5 -4
  3. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/.github/workflows/test.yml +1 -1
  4. taraqueue-0.2.0/CHANGES.rst +13 -0
  5. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/PKG-INFO +1 -2
  6. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/docs/taraqueue.rst +18 -2
  7. taraqueue-0.2.0/docs/taraqueue.testing.rst +37 -0
  8. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/pyproject.toml +3 -3
  9. taraqueue-0.2.0/taraqueue/__init__.py +51 -0
  10. taraqueue-0.2.0/taraqueue/memory.py +50 -0
  11. taraqueue-0.2.0/taraqueue/redis.py +67 -0
  12. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/taraqueue/testing/compose.py +2 -2
  13. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/taraqueue/testing/queue.py +1 -1
  14. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/tests/test_queue.py +1 -1
  15. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/uv.lock +0 -2
  16. taraqueue-0.0.2.dev0/CHANGES.rst +0 -6
  17. taraqueue-0.0.2.dev0/taraqueue/queue.py +0 -149
  18. taraqueue-0.0.2.dev0/taraqueue/testing/__init__.py +0 -0
  19. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/.editorconfig +0 -0
  20. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/.gitattributes +0 -0
  21. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/.github/CODEOWNERS +0 -0
  22. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/.github/renovate.json +0 -0
  23. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/.github/workflows/renovate.yaml +0 -0
  24. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/.gitignore +0 -0
  25. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/.vscode/extensions.json +0 -0
  26. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/.vscode/launch.json +0 -0
  27. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/.vscode/settings.json +0 -0
  28. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/CONTRIBUTING.rst +0 -0
  29. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/LICENSE.rst +0 -0
  30. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/Makefile +0 -0
  31. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/README.rst +0 -0
  32. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/STYLE_GUIDE.rst +0 -0
  33. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/compose.yml +0 -0
  34. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/docs/changes.rst +0 -0
  35. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/docs/conf.py +0 -0
  36. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/docs/contributing.rst +0 -0
  37. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/docs/index.rst +0 -0
  38. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/docs/license.rst +0 -0
  39. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/docs/modules.rst +0 -0
  40. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/docs/style_guide.rst +0 -0
  41. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/taraqueue/registry.py +0 -0
  42. {taraqueue-0.0.2.dev0/taraqueue → taraqueue-0.2.0/taraqueue/testing}/__init__.py +0 -0
  43. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/taraqueue/testing/services.py +0 -0
  44. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/tests/test_compose.py +0 -0
  45. {taraqueue-0.0.2.dev0 → taraqueue-0.2.0}/tests/test_registry.py +0 -0
@@ -14,6 +14,10 @@ inputs:
14
14
  required: false
15
15
  description: "Include optional dependencies"
16
16
  default: ""
17
+ sync:
18
+ required: false
19
+ description: "Run uv sync"
20
+ default: "true"
17
21
  working-directory:
18
22
  required: false
19
23
  description: "Working directory"
@@ -36,6 +40,7 @@ runs:
36
40
  enable-cache: true
37
41
 
38
42
  - name: Install dependencies
43
+ if: ${{ inputs.sync == 'true' }}
39
44
  shell: bash
40
45
  working-directory: ${{ inputs.working-directory }}
41
46
  run: |
@@ -1,10 +1,9 @@
1
1
  name: Publish
2
2
 
3
3
  on:
4
- release:
5
- types: [published]
6
- branches: [main]
7
- workflow_dispatch:
4
+ push:
5
+ tags:
6
+ - "v*"
8
7
 
9
8
  permissions:
10
9
  contents: write
@@ -21,6 +20,8 @@ jobs:
21
20
 
22
21
  - name: Set up the environment
23
22
  uses: ./.github/actions/setup-uv-env
23
+ with:
24
+ sync: "false"
24
25
 
25
26
  - name: Build
26
27
  run: uv build
@@ -88,7 +88,7 @@ jobs:
88
88
  - name: Set up the environment
89
89
  uses: ./.github/actions/setup-uv-env
90
90
  with:
91
- extras: docs
91
+ extras: docs,test
92
92
 
93
93
  - name: Run docs target
94
94
  run: make docs
@@ -0,0 +1,13 @@
1
+ Version 0.2.0
2
+ -------------
3
+
4
+ Released 2026-05-04
5
+
6
+ - Split implementations.
7
+
8
+ Version 0.1.0
9
+ -------------
10
+
11
+ Released 2026-05-01
12
+
13
+ - Initial release.
@@ -1,12 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: taraqueue
3
- Version: 0.0.2.dev0
3
+ Version: 0.2.0
4
4
  Summary: Python queue abstraction layer
5
5
  Project-URL: Repository, https://github.com/taradix/taraqueue
6
6
  Author-email: Marc Tardif <marc@taram.ca>
7
7
  License-File: LICENSE.rst
8
8
  Requires-Python: <4.0,>=3.12
9
- Requires-Dist: attrs>=26.1.0
10
9
  Requires-Dist: yarl>=1.23.0
11
10
  Provides-Extra: check
12
11
  Requires-Dist: ruff<1.0.0,>=0.15.0; extra == 'check'
@@ -1,13 +1,29 @@
1
1
  taraqueue package
2
2
  =================
3
3
 
4
+ Subpackages
5
+ -----------
6
+
7
+ .. toctree::
8
+ :maxdepth: 4
9
+
10
+ taraqueue.testing
11
+
4
12
  Submodules
5
13
  ----------
6
14
 
7
- taraqueue.queue module
15
+ taraqueue.memory module
16
+ -----------------------
17
+
18
+ .. automodule:: taraqueue.memory
19
+ :members:
20
+ :show-inheritance:
21
+ :undoc-members:
22
+
23
+ taraqueue.redis module
8
24
  ----------------------
9
25
 
10
- .. automodule:: taraqueue.queue
26
+ .. automodule:: taraqueue.redis
11
27
  :members:
12
28
  :show-inheritance:
13
29
  :undoc-members:
@@ -0,0 +1,37 @@
1
+ taraqueue.testing package
2
+ =========================
3
+
4
+ Submodules
5
+ ----------
6
+
7
+ taraqueue.testing.compose module
8
+ --------------------------------
9
+
10
+ .. automodule:: taraqueue.testing.compose
11
+ :members:
12
+ :show-inheritance:
13
+ :undoc-members:
14
+
15
+ taraqueue.testing.queue module
16
+ ------------------------------
17
+
18
+ .. automodule:: taraqueue.testing.queue
19
+ :members:
20
+ :show-inheritance:
21
+ :undoc-members:
22
+
23
+ taraqueue.testing.services module
24
+ ---------------------------------
25
+
26
+ .. automodule:: taraqueue.testing.services
27
+ :members:
28
+ :show-inheritance:
29
+ :undoc-members:
30
+
31
+ Module contents
32
+ ---------------
33
+
34
+ .. automodule:: taraqueue.testing
35
+ :members:
36
+ :show-inheritance:
37
+ :undoc-members:
@@ -8,7 +8,6 @@ authors = [
8
8
  readme = "README.rst"
9
9
  requires-python = ">=3.12,<4.0"
10
10
  dependencies = [
11
- "attrs>=26.1.0",
12
11
  "yarl>=1.23.0",
13
12
  ]
14
13
 
@@ -46,8 +45,8 @@ taraqueue-services = "taraqueue.testing.services"
46
45
  taraqueue-queue = "taraqueue.testing.queue"
47
46
 
48
47
  [project.entry-points."taraqueue"]
49
- memory = "taraqueue.queue:MemoryQueue"
50
- redis = "taraqueue.queue:RedisQueue"
48
+ memory = "taraqueue.memory:MemoryQueue"
49
+ redis = "taraqueue.redis:RedisQueue"
51
50
 
52
51
  [build-system]
53
52
  requires = ["hatchling", "hatch-vcs"]
@@ -58,6 +57,7 @@ source = "vcs"
58
57
 
59
58
  [tool.hatch.version.raw-options]
60
59
  local_scheme = "no-local-version"
60
+ tag_regex = "^v(?P<version>\\d+\\.\\d+\\.\\d+)$"
61
61
 
62
62
  [tool.ruff]
63
63
  target-version = "py312"
@@ -0,0 +1,51 @@
1
+ """Queue abstraction."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from contextlib import asynccontextmanager
5
+ from dataclasses import dataclass
6
+
7
+ from yarl import URL
8
+
9
+ from taraqueue.registry import registry_load
10
+
11
+
12
+ class QueueEmpty(Exception):
13
+ """Raised when the queue is empty."""
14
+
15
+
16
+ @dataclass
17
+ class Queue(ABC):
18
+ """Base queue class."""
19
+
20
+ @classmethod
21
+ def from_url(cls, url: URL | str, registry=None) -> "Queue":
22
+ if registry is None:
23
+ registry = registry_load("taraqueue")
24
+ scheme = URL(url).scheme
25
+ queue_cls = registry["taraqueue"][scheme]
26
+ return queue_cls.from_url(url)
27
+
28
+ @abstractmethod
29
+ async def subscribe(self, topic: str) -> None:
30
+ """Subscribe to a topic before receiving messages."""
31
+
32
+ @abstractmethod
33
+ async def unsubscribe(self, topic: str) -> None:
34
+ """Unsubscribe from a topic after receiving messages."""
35
+
36
+ @abstractmethod
37
+ async def receive(self, timeout=None) -> str:
38
+ """Listen for messages on the subscribed topics."""
39
+
40
+ @abstractmethod
41
+ async def publish(self, topic: str, message: str) -> None:
42
+ """Publish a message to a topic."""
43
+
44
+ @asynccontextmanager
45
+ async def connect(self, topic: str):
46
+ """Context manager that subscribes on entry and unsubscribes on exit."""
47
+ await self.subscribe(topic)
48
+ try:
49
+ yield self
50
+ finally:
51
+ await self.unsubscribe(topic)
@@ -0,0 +1,50 @@
1
+ """Memory queue implementation."""
2
+
3
+ from collections import defaultdict
4
+ from contextlib import suppress
5
+ from dataclasses import dataclass, field
6
+
7
+ from yarl import URL
8
+
9
+ from taraqueue import Queue, QueueEmpty
10
+
11
+ _global_memory_queues = defaultdict(list)
12
+
13
+
14
+ @dataclass
15
+ class MemoryQueue(Queue):
16
+
17
+ topics: list = field(default_factory=list)
18
+ queues: dict = field(default_factory=lambda: _global_memory_queues)
19
+
20
+ @classmethod
21
+ def from_url(cls, url: URL) -> "MemoryQueue":
22
+ return cls()
23
+
24
+ async def subscribe(self, topic: str) -> None:
25
+ """See `Queue.subscribe`."""
26
+ self.topics.append(topic)
27
+
28
+ async def unsubscribe(self, topic: str) -> None:
29
+ """See `Queue.unsubscribe`."""
30
+ with suppress(ValueError):
31
+ self.topics.remove(topic)
32
+
33
+ async def receive(self, timeout=None) -> str:
34
+ """See `Queue.receive`."""
35
+ for topic in self.topics[:]:
36
+ # Cycle through topics.
37
+ self.topics.append(self.topics.pop(0))
38
+ queue = self.queues[topic]
39
+ with suppress(IndexError):
40
+ return queue.pop(0)
41
+
42
+ raise QueueEmpty("Queue is empty")
43
+
44
+ async def publish(self, topic: str, message: str) -> None:
45
+ """See `Queue.publish`."""
46
+ queue = self.queues[topic]
47
+ queue.append(message)
48
+
49
+
50
+
@@ -0,0 +1,67 @@
1
+ """Redis queue implementation."""
2
+
3
+ import os
4
+ from dataclasses import dataclass, field
5
+ from time import time
6
+
7
+ from redis.asyncio import Redis
8
+ from redis.asyncio.client import PubSub
9
+ from yarl import URL
10
+
11
+ from taraqueue import Queue, QueueEmpty
12
+
13
+
14
+ @dataclass
15
+ class RedisQueue(Queue):
16
+
17
+ client: Redis = field()
18
+ pubsub: PubSub = field()
19
+
20
+ @classmethod
21
+ def from_env(cls, env=os.environ) -> "RedisQueue":
22
+ host = env.get("REDIS_SLAVEOF_IP", "") or env.get("IPV4_NETWORK", "172.22.1") + ".249"
23
+ port = int(env.get("REDIS_SLAVEOF_PORT", "") or "6379")
24
+ password = env.get("REDISPASS")
25
+ return cls.from_host(host, port, password=password)
26
+
27
+ @classmethod
28
+ def from_host(cls, host: str, port: int = 6379, password: str | None = None) -> "RedisQueue":
29
+ from redis.asyncio import StrictRedis
30
+
31
+ client = StrictRedis(
32
+ host=host,
33
+ port=port,
34
+ decode_responses=True,
35
+ db=0,
36
+ password=password,
37
+ )
38
+ pubsub = client.pubsub(ignore_subscribe_messages=True)
39
+ return cls(client, pubsub)
40
+
41
+ @classmethod
42
+ def from_url(cls, url: URL | str) -> "RedisQueue":
43
+ url = URL(url)
44
+ return cls.from_host(url.host, url.port, password=url.password)
45
+
46
+ async def subscribe(self, topic: str) -> None:
47
+ """See `Queue.subscribe`."""
48
+ await self.pubsub.subscribe(topic)
49
+
50
+ async def unsubscribe(self, topic: str) -> None:
51
+ """See `Queue.unsubscribe`."""
52
+ await self.pubsub.unsubscribe(topic)
53
+
54
+ async def receive(self, timeout=0) -> str:
55
+ """See `Queue.receive`."""
56
+ stop_time = time() + timeout
57
+ while True:
58
+ remaining_timeout = max(0.0, stop_time - time())
59
+ message = await self.pubsub.get_message(ignore_subscribe_messages=True, timeout=remaining_timeout)
60
+ if message:
61
+ return message["data"]
62
+ if time() >= stop_time:
63
+ raise QueueEmpty("Queue is empty")
64
+
65
+ async def publish(self, topic: str, message: str) -> None:
66
+ """See `Queue.publish`."""
67
+ await self.client.publish(topic, message)
@@ -1,16 +1,16 @@
1
1
  """Compose server module."""
2
2
 
3
3
  from contextlib import contextmanager
4
+ from dataclasses import dataclass
4
5
  from datetime import datetime
5
6
 
6
- from attrs import define
7
7
  from more_itertools import only
8
8
  from pytest_xdocker.docker import DockerContainer
9
9
  from pytest_xdocker.process import ProcessData, ProcessServer
10
10
  from pytest_xdocker.xdocker import xdocker
11
11
 
12
12
 
13
- @define(frozen=True)
13
+ @dataclass(frozen=True)
14
14
  class ComposeService:
15
15
  """Compose service.
16
16
 
@@ -3,7 +3,7 @@
3
3
  import pytest
4
4
  from yarl import URL
5
5
 
6
- from taraqueue.queue import Queue
6
+ from taraqueue import Queue
7
7
 
8
8
 
9
9
  @pytest.fixture
@@ -2,7 +2,7 @@
2
2
 
3
3
  import pytest
4
4
 
5
- from taraqueue.queue import QueueEmpty
5
+ from taraqueue import QueueEmpty
6
6
 
7
7
 
8
8
  async def test_queue_send_receive(queue, unique):
@@ -859,7 +859,6 @@ wheels = [
859
859
  name = "taraqueue"
860
860
  source = { editable = "." }
861
861
  dependencies = [
862
- { name = "attrs" },
863
862
  { name = "yarl" },
864
863
  ]
865
864
 
@@ -887,7 +886,6 @@ test = [
887
886
 
888
887
  [package.metadata]
889
888
  requires-dist = [
890
- { name = "attrs", specifier = ">=26.1.0" },
891
889
  { name = "coverage", marker = "extra == 'test'", specifier = ">=7.2.3,<8.0.0" },
892
890
  { name = "more-itertools", marker = "extra == 'test'", specifier = ">=11.0.1,<11.1.0" },
893
891
  { name = "pytest", marker = "extra == 'test'", specifier = ">=9.0.0,<10.0.0" },
@@ -1,6 +0,0 @@
1
- Version 0.1.0
2
- -------------
3
-
4
- Released 2026-05-01
5
-
6
- - Initial release.
@@ -1,149 +0,0 @@
1
- """Queue abstraction and implementation."""
2
-
3
- import os
4
- from abc import ABC, abstractmethod
5
- from collections import defaultdict
6
- from contextlib import asynccontextmanager, suppress
7
- from time import time
8
-
9
- from attrs import define, field
10
- from yarl import URL
11
-
12
- from taraqueue.registry import registry_load
13
-
14
-
15
- class QueueEmpty(Exception):
16
- """Raised when the queue is empty."""
17
-
18
-
19
- @define
20
- class Queue(ABC):
21
- """Base queue class."""
22
-
23
- @classmethod
24
- def from_url(cls, url: URL | str, registry=None) -> "Queue":
25
- if registry is None:
26
- registry = registry_load("taraqueue")
27
- scheme = URL(url).scheme
28
- queue_cls = registry["taraqueue"][scheme]
29
- return queue_cls.from_url(url)
30
-
31
- @abstractmethod
32
- async def subscribe(self, topic: str) -> None:
33
- """Subscribe to a topic before receiving messages."""
34
-
35
- @abstractmethod
36
- async def unsubscribe(self, topic: str) -> None:
37
- """Unsubscribe from a topic after receiving messages."""
38
-
39
- @abstractmethod
40
- async def receive(self, timeout=None) -> str:
41
- """Listen for messages on the subscribed topics."""
42
-
43
- @abstractmethod
44
- async def publish(self, topic: str, message: str) -> None:
45
- """Publish a message to a topic."""
46
-
47
- @asynccontextmanager
48
- async def connect(self, topic: str):
49
- """Context manager that subscribes on entry and unsubscribes on exit."""
50
- await self.subscribe(topic)
51
- try:
52
- yield self
53
- finally:
54
- await self.unsubscribe(topic)
55
-
56
-
57
- _global_memory_queues = defaultdict(list)
58
-
59
-
60
- @define
61
- class MemoryQueue(Queue):
62
-
63
- topics = field(factory=list)
64
- queues = field(default=_global_memory_queues)
65
-
66
- @classmethod
67
- def from_url(cls, url: URL) -> "MemoryQueue":
68
- return cls()
69
-
70
- async def subscribe(self, topic: str) -> None:
71
- """See `Queue.subscribe`."""
72
- self.topics.append(topic)
73
-
74
- async def unsubscribe(self, topic: str) -> None:
75
- """See `Queue.unsubscribe`."""
76
- with suppress(ValueError):
77
- self.topics.remove(topic)
78
-
79
- async def receive(self, timeout=None) -> str:
80
- """See `Queue.receive`."""
81
- for topic in self.topics[:]:
82
- # Cycle through topics.
83
- self.topics.append(self.topics.pop(0))
84
- queue = self.queues[topic]
85
- with suppress(IndexError):
86
- return queue.pop(0)
87
-
88
- raise QueueEmpty("Queue is empty")
89
-
90
- async def publish(self, topic: str, message: str) -> None:
91
- """See `Queue.publish`."""
92
- queue = self.queues[topic]
93
- queue.append(message)
94
-
95
-
96
- @define
97
- class RedisQueue(Queue):
98
-
99
- client = field()
100
- pubsub = field()
101
-
102
- @classmethod
103
- def from_env(cls, env=os.environ) -> "RedisQueue":
104
- host = env.get("REDIS_SLAVEOF_IP", "") or env.get("IPV4_NETWORK", "172.22.1") + ".249"
105
- port = int(env.get("REDIS_SLAVEOF_PORT", "") or "6379")
106
- password = env.get("REDISPASS")
107
- return cls.from_host(host, port, password=password)
108
-
109
- @classmethod
110
- def from_host(cls, host: str, port: int = 6379, password: str | None = None) -> "RedisQueue":
111
- from redis.asyncio import StrictRedis
112
-
113
- client = StrictRedis(
114
- host=host,
115
- port=port,
116
- decode_responses=True,
117
- db=0,
118
- password=password,
119
- )
120
- pubsub = client.pubsub(ignore_subscribe_messages=True)
121
- return cls(client, pubsub)
122
-
123
- @classmethod
124
- def from_url(cls, url: URL | str) -> "RedisQueue":
125
- url = URL(url)
126
- return cls.from_host(url.host, url.port, password=url.password)
127
-
128
- async def subscribe(self, topic: str) -> None:
129
- """See `Queue.subscribe`."""
130
- await self.pubsub.subscribe(topic)
131
-
132
- async def unsubscribe(self, topic: str) -> None:
133
- """See `Queue.unsubscribe`."""
134
- await self.pubsub.unsubscribe(topic)
135
-
136
- async def receive(self, timeout=0) -> str:
137
- """See `Queue.receive`."""
138
- stop_time = time() + timeout
139
- while True:
140
- remaining_timeout = max(0.0, stop_time - time())
141
- message = await self.pubsub.get_message(ignore_subscribe_messages=True, timeout=remaining_timeout)
142
- if message:
143
- return message["data"]
144
- if time() >= stop_time:
145
- raise QueueEmpty("Queue is empty")
146
-
147
- async def publish(self, topic: str, message: str) -> None:
148
- """See `Queue.publish`."""
149
- await self.client.publish(topic, message)
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