taraqueue 0.0.2.dev0__py3-none-any.whl → 0.2.0__py3-none-any.whl

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.
taraqueue/__init__.py CHANGED
@@ -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)
taraqueue/memory.py ADDED
@@ -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
+
taraqueue/redis.py ADDED
@@ -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
@@ -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'
@@ -0,0 +1,13 @@
1
+ taraqueue/__init__.py,sha256=kCe0aTFoyiVQdilCJjUDsO3mYh2EjBIqxkcQQBRnM0s,1434
2
+ taraqueue/memory.py,sha256=JpxH_Ru6VMLehtiVh7KbcvRsCVsuUKKtEHIp-BOKaP8,1323
3
+ taraqueue/redis.py,sha256=AiGN7PUW8bdpuj3FwfXSz9KWq69QAyaMQ8GSHiNKwHM,2151
4
+ taraqueue/registry.py,sha256=YcF9FE21HWfzoFbGGtC4GBozj-g5YbQuWdOor9XoPrU,2036
5
+ taraqueue/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ taraqueue/testing/compose.py,sha256=-AjhI5mOF1BzFfBweK1a1sZQo_V4uM7QM6-V1QLaACY,2908
7
+ taraqueue/testing/queue.py,sha256=mTJVCBHS5B2QF0rillW2uPx227JcoDWyFEIxxMhKpWs,671
8
+ taraqueue/testing/services.py,sha256=YmbsbAiX_kBVziwnNwkuvxn-82zhNE926oxF6V0SfzU,2213
9
+ taraqueue-0.2.0.dist-info/METADATA,sha256=Q46HXLE76L_bwqeVrQJQbQmiDTdx0qh_VeQoG1t5qOM,2102
10
+ taraqueue-0.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
11
+ taraqueue-0.2.0.dist-info/entry_points.txt,sha256=xqxmScNzMBHMqUEDuTaNXhDzytsrwwvRVujNbdnEOZc,187
12
+ taraqueue-0.2.0.dist-info/licenses/LICENSE.rst,sha256=aBUUonOC9jBjhQb0VIObBylPnlIVph0P0QnlnZ8z4BM,1068
13
+ taraqueue-0.2.0.dist-info/RECORD,,
@@ -3,5 +3,5 @@ taraqueue-queue = taraqueue.testing.queue
3
3
  taraqueue-services = taraqueue.testing.services
4
4
 
5
5
  [taraqueue]
6
- memory = taraqueue.queue:MemoryQueue
7
- redis = taraqueue.queue:RedisQueue
6
+ memory = taraqueue.memory:MemoryQueue
7
+ redis = taraqueue.redis:RedisQueue
taraqueue/queue.py DELETED
@@ -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)
@@ -1,12 +0,0 @@
1
- taraqueue/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- taraqueue/queue.py,sha256=-ya3DVSLTjNQ1nmL1ltpJgPmTkdmQe2W2UHvhUGKfzI,4490
3
- taraqueue/registry.py,sha256=YcF9FE21HWfzoFbGGtC4GBozj-g5YbQuWdOor9XoPrU,2036
4
- taraqueue/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- taraqueue/testing/compose.py,sha256=wyI7dkhdXgQra0RcrYqye7RnYSv4WRSOMUVJRloKfDk,2896
6
- taraqueue/testing/queue.py,sha256=ryIcwE7Txeybairo6HkW9bO9mH7A6Qj9RU3YDiUpG8M,677
7
- taraqueue/testing/services.py,sha256=YmbsbAiX_kBVziwnNwkuvxn-82zhNE926oxF6V0SfzU,2213
8
- taraqueue-0.0.2.dev0.dist-info/METADATA,sha256=DScT2V7LJ2g4fdxQ22WmwsR5IDljc3qbfcXydQ9gVJM,2136
9
- taraqueue-0.0.2.dev0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
10
- taraqueue-0.0.2.dev0.dist-info/entry_points.txt,sha256=IDp7ErMVJHagqZeDuawQyIxa6ZmLF9Kh3Wv3is1up6c,186
11
- taraqueue-0.0.2.dev0.dist-info/licenses/LICENSE.rst,sha256=aBUUonOC9jBjhQb0VIObBylPnlIVph0P0QnlnZ8z4BM,1068
12
- taraqueue-0.0.2.dev0.dist-info/RECORD,,