queutils 0.9.4__py3-none-any.whl → 0.10.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.
- queutils/__init__.py +5 -0
- queutils/asyncqueue.py +0 -8
- queutils/awrap.py +45 -0
- queutils/eventcounterqueue.py +0 -7
- queutils/filequeue.py +7 -11
- queutils/iterablequeue.py +62 -62
- {queutils-0.9.4.dist-info → queutils-0.10.0.dist-info}/METADATA +1 -3
- queutils-0.10.0.dist-info/RECORD +12 -0
- queutils-0.9.4.dist-info/RECORD +0 -11
- {queutils-0.9.4.dist-info → queutils-0.10.0.dist-info}/WHEEL +0 -0
- {queutils-0.9.4.dist-info → queutils-0.10.0.dist-info}/licenses/LICENSE +0 -0
queutils/__init__.py
CHANGED
@@ -6,6 +6,10 @@ from .eventcounterqueue import (
|
|
6
6
|
QCounter as QCounter,
|
7
7
|
EventCounterQueue as EventCounterQueue,
|
8
8
|
)
|
9
|
+
from .awrap import (
|
10
|
+
awrap as awrap,
|
11
|
+
abatch as abatch,
|
12
|
+
)
|
9
13
|
|
10
14
|
__all__ = [
|
11
15
|
"asyncqueue",
|
@@ -13,4 +17,5 @@ __all__ = [
|
|
13
17
|
"eventcounterqueue",
|
14
18
|
"filequeue",
|
15
19
|
"iterablequeue",
|
20
|
+
"awrap",
|
16
21
|
]
|
queutils/asyncqueue.py
CHANGED
@@ -19,18 +19,10 @@ from queue import Full, Empty, Queue
|
|
19
19
|
from asyncio.queues import QueueEmpty, QueueFull
|
20
20
|
import asyncio
|
21
21
|
from typing import Generic, TypeVar
|
22
|
-
import logging
|
23
22
|
from asyncio import sleep
|
24
23
|
|
25
24
|
T = TypeVar("T")
|
26
25
|
|
27
|
-
logger = logging.getLogger(__name__)
|
28
|
-
|
29
|
-
debug = logger.debug
|
30
|
-
message = logger.warning
|
31
|
-
verbose = logger.info
|
32
|
-
error = logger.error
|
33
|
-
|
34
26
|
|
35
27
|
class AsyncQueue(asyncio.Queue, Generic[T]):
|
36
28
|
"""
|
queutils/awrap.py
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
"""awrap() is a async wrapper for Iterables
|
2
|
+
|
3
|
+
It converts an Iterable[T] to AsyncGenerator[T].
|
4
|
+
AsyncGenerator[T] is also AsyncIterable[T] allowing it to be used in async for
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Iterable, Sequence, TypeVar, AsyncGenerator, AsyncIterable
|
8
|
+
|
9
|
+
T = TypeVar("T")
|
10
|
+
|
11
|
+
|
12
|
+
async def awrap(iterable: Iterable[T]) -> AsyncGenerator[T, None]:
|
13
|
+
"""
|
14
|
+
Async wrapper for Iterable[T] so it can be used in async for
|
15
|
+
Can be used in async for loop
|
16
|
+
"""
|
17
|
+
for item in iter(iterable):
|
18
|
+
yield item
|
19
|
+
|
20
|
+
|
21
|
+
async def abatch(
|
22
|
+
iterable: Iterable[T] | AsyncIterable[T], size: int
|
23
|
+
) -> AsyncGenerator[Sequence[T], None]:
|
24
|
+
"""
|
25
|
+
Async wrapper reads batches from a AsyncIterable[T] or Iterable[T]
|
26
|
+
Can be used in async for loop
|
27
|
+
"""
|
28
|
+
batch: list[T] = []
|
29
|
+
if isinstance(iterable, AsyncIterable):
|
30
|
+
async for item in iterable:
|
31
|
+
batch.append(item)
|
32
|
+
if len(batch) == size:
|
33
|
+
yield batch
|
34
|
+
batch = []
|
35
|
+
elif isinstance(iterable, Iterable):
|
36
|
+
for item in iterable:
|
37
|
+
batch.append(item)
|
38
|
+
if len(batch) == size:
|
39
|
+
yield batch
|
40
|
+
batch = []
|
41
|
+
else:
|
42
|
+
raise TypeError(f"Expected Iterable or AsyncIterable, got {type(iterable)}")
|
43
|
+
|
44
|
+
if batch:
|
45
|
+
yield batch
|
queutils/eventcounterqueue.py
CHANGED
@@ -4,13 +4,6 @@ from deprecated import deprecated
|
|
4
4
|
from .countable import Countable
|
5
5
|
from .iterablequeue import IterableQueue, QueueDone
|
6
6
|
from collections import defaultdict
|
7
|
-
import logging
|
8
|
-
|
9
|
-
logger = logging.getLogger()
|
10
|
-
error = logger.error
|
11
|
-
message = logger.warning
|
12
|
-
verbose = logger.info
|
13
|
-
debug = logger.debug
|
14
7
|
|
15
8
|
###########################################
|
16
9
|
#
|
queutils/filequeue.py
CHANGED
@@ -29,8 +29,6 @@ from .iterablequeue import IterableQueue
|
|
29
29
|
|
30
30
|
logger = logging.getLogger(__name__)
|
31
31
|
error = logger.error
|
32
|
-
message = logger.warning
|
33
|
-
verbose = logger.info
|
34
32
|
debug = logger.debug
|
35
33
|
|
36
34
|
|
@@ -47,8 +45,8 @@ def str2path(filename: str | Path, suffix: str | None = None) -> Path:
|
|
47
45
|
|
48
46
|
class FileQueue(IterableQueue[Path]):
|
49
47
|
"""
|
50
|
-
Class to create a IterableQueue(asyncio.Queue) of filenames based on
|
51
|
-
given directories and files as arguments.
|
48
|
+
Class to create a IterableQueue(asyncio.Queue) of filenames based on
|
49
|
+
given directories and files as arguments.
|
52
50
|
Supports include/exclude filters based on filenames.
|
53
51
|
"""
|
54
52
|
|
@@ -58,21 +56,20 @@ class FileQueue(IterableQueue[Path]):
|
|
58
56
|
filter: str = "*",
|
59
57
|
exclude: bool = False,
|
60
58
|
case_sensitive: bool = True,
|
61
|
-
follow_symlinks: bool = False,
|
59
|
+
follow_symlinks: bool = False,
|
62
60
|
**kwargs,
|
63
61
|
):
|
64
62
|
assert base is None or isinstance(base, Path), "base has to be Path or None"
|
65
63
|
assert isinstance(filter, str), "filter has to be string"
|
66
64
|
assert isinstance(case_sensitive, bool), "case_sensitive has to be bool"
|
67
65
|
assert isinstance(follow_symlinks, bool), "follow_symlinks has to be bool"
|
68
|
-
|
66
|
+
|
69
67
|
# debug(f"maxsize={str(maxsize)}, filter='{filter}'")
|
70
|
-
super().__init__(
|
68
|
+
super().__init__(**kwargs)
|
71
69
|
self._base: Optional[Path] = base
|
72
|
-
# self._done: bool = False
|
73
70
|
self._case_sensitive: bool = False
|
74
71
|
self._exclude: bool = False
|
75
|
-
self._follow_symlinks
|
72
|
+
self._follow_symlinks: bool = follow_symlinks
|
76
73
|
self.set_filter(filter=filter, exclude=exclude, case_sensitive=case_sensitive)
|
77
74
|
|
78
75
|
def set_filter(
|
@@ -119,8 +116,7 @@ class FileQueue(IterableQueue[Path]):
|
|
119
116
|
await self.put(path)
|
120
117
|
except Exception as err:
|
121
118
|
error(f"{err}")
|
122
|
-
return await self.
|
123
|
-
|
119
|
+
return await self.finish_producer()
|
124
120
|
|
125
121
|
async def put(self, path: Path) -> None:
|
126
122
|
"""Recursive function to build process queue. Sanitize filename"""
|
queutils/iterablequeue.py
CHANGED
@@ -17,16 +17,14 @@ __email__ = "Jylpah@gmail.com"
|
|
17
17
|
__status__ = "Production"
|
18
18
|
|
19
19
|
|
20
|
-
from asyncio import Queue, QueueFull,
|
20
|
+
from asyncio import Queue, QueueFull, Event, Lock
|
21
21
|
from typing import AsyncIterable, TypeVar, Optional
|
22
|
+
from deprecated import deprecated
|
22
23
|
from .countable import Countable
|
23
24
|
import logging
|
24
25
|
|
25
26
|
# Setup logging
|
26
27
|
logger = logging.getLogger(__name__)
|
27
|
-
error = logger.error
|
28
|
-
message = logger.warning
|
29
|
-
verbose = logger.info
|
30
28
|
debug = logger.debug
|
31
29
|
|
32
30
|
T = TypeVar("T")
|
@@ -45,34 +43,44 @@ class IterableQueue(Queue[T], AsyncIterable[T], Countable):
|
|
45
43
|
IterableQueue is asyncio.Queue subclass that can be iterated asynchronusly.
|
46
44
|
|
47
45
|
IterableQueue terminates automatically when the queue has been
|
48
|
-
filled and emptied.
|
46
|
+
filled and emptied.
|
47
|
+
|
48
|
+
Supports:
|
49
49
|
- asyncio.Queue() interface, _nowait() methods are experimental
|
50
50
|
- AsyncIterable(): async for item in queue:
|
51
51
|
- Automatic termination of the consumers when the queue has been emptied with QueueDone exception
|
52
52
|
- Producers must be registered with add_producer() and they must notify the queue
|
53
53
|
with finish() once they have finished adding items
|
54
54
|
- Countable interface to count number of items task_done() through 'count' property
|
55
|
-
|
56
|
-
|
55
|
+
|
56
|
+
IterableQueue stages:
|
57
|
+
|
58
|
+
1) Initialized: Queue has been created and it is empty
|
59
|
+
2) is_illed: All producers have finished adding items to the queue
|
60
|
+
3) empty/has_wip: Queue has been emptied
|
61
|
+
4) is_done: All items have been marked with task_done()
|
57
62
|
"""
|
58
63
|
|
59
|
-
def __init__(self,
|
64
|
+
def __init__(self, **kwargs) -> None:
|
60
65
|
# _Q is required instead of inheriting from Queue()
|
61
66
|
# using super() since Queue is Optional[T], not [T]
|
62
67
|
self._Q: Queue[Optional[T]] = Queue(**kwargs)
|
68
|
+
self._maxsize: int = self._Q.maxsize # Asyncio.Queue has _maxsize
|
63
69
|
self._producers: int = 0
|
64
|
-
self._count_items: bool = count_items
|
65
70
|
self._count: int = 0
|
66
71
|
self._wip: int = 0
|
67
72
|
|
68
73
|
self._modify: Lock = Lock()
|
69
74
|
self._put_lock: Lock = Lock()
|
70
75
|
|
76
|
+
# the last producer has finished
|
71
77
|
self._filled: Event = Event()
|
78
|
+
# the last producer has finished and the queue is empty
|
72
79
|
self._empty: Event = Event()
|
80
|
+
# the queue is done, all items have been marked with task_done()
|
73
81
|
self._done: Event = Event()
|
74
82
|
|
75
|
-
self._empty.
|
83
|
+
self._empty.clear() # this will be tested only after queue is filled
|
76
84
|
|
77
85
|
@property
|
78
86
|
def is_filled(self) -> bool:
|
@@ -99,12 +107,6 @@ class IterableQueue(Queue[T], AsyncIterable[T], Countable):
|
|
99
107
|
"""
|
100
108
|
return self._Q.full()
|
101
109
|
|
102
|
-
def check_done(self) -> bool:
|
103
|
-
if self.is_filled and self.empty() and not self.has_wip:
|
104
|
-
self._done.set()
|
105
|
-
return True
|
106
|
-
return False
|
107
|
-
|
108
110
|
def empty(self) -> bool:
|
109
111
|
"""
|
110
112
|
Queue has no items except None as a sentinel
|
@@ -137,101 +139,95 @@ class IterableQueue(Queue[T], AsyncIterable[T], Countable):
|
|
137
139
|
|
138
140
|
@property
|
139
141
|
def count(self) -> int:
|
140
|
-
|
141
|
-
return self._count
|
142
|
-
else:
|
143
|
-
return 0
|
142
|
+
return self._count
|
144
143
|
|
145
144
|
async def add_producer(self, N: int = 1) -> int:
|
146
145
|
"""
|
147
146
|
Add producer(s) to the queue
|
148
147
|
"""
|
149
|
-
|
148
|
+
if N <= 0:
|
149
|
+
raise ValueError("N has to be positive")
|
150
150
|
async with self._modify:
|
151
151
|
if self.is_filled:
|
152
152
|
raise QueueDone
|
153
153
|
self._producers += N
|
154
154
|
return self._producers
|
155
155
|
|
156
|
-
|
156
|
+
@deprecated(version="0.10.0", reason="Use finish_producer() instead for clarity")
|
157
|
+
async def finish(self, all: bool = False) -> bool:
|
158
|
+
"""
|
159
|
+
Finish producer
|
160
|
+
|
161
|
+
Depreciated function, use finish_producer() instead
|
162
|
+
"""
|
163
|
+
return await self.finish_producer(all=all)
|
164
|
+
|
165
|
+
async def finish_producer(self, all: bool = False) -> bool:
|
157
166
|
"""
|
158
167
|
Producer has finished adding items to the queue.
|
159
168
|
Once the last producers has finished, the queue is_filled.
|
160
169
|
- all: finish() queue for all producers at once
|
170
|
+
|
171
|
+
Return True if the last producer is 'finished'
|
161
172
|
"""
|
162
173
|
async with self._modify:
|
163
|
-
if self.
|
164
|
-
|
165
|
-
|
166
|
-
return False
|
174
|
+
if self.is_filled:
|
175
|
+
return True
|
176
|
+
|
167
177
|
self._producers -= 1
|
168
178
|
|
169
|
-
if
|
179
|
+
if self._producers < 0:
|
180
|
+
raise ValueError("Too many finish() calls")
|
181
|
+
elif all or self._producers == 0:
|
170
182
|
self._filled.set()
|
171
183
|
self._producers = 0
|
172
184
|
|
173
185
|
if self._producers == 0:
|
174
|
-
if empty:
|
175
|
-
try:
|
176
|
-
while True:
|
177
|
-
_ = self.get_nowait()
|
178
|
-
self.task_done()
|
179
|
-
except (QueueDone, QueueEmpty):
|
180
|
-
pass
|
181
|
-
|
182
186
|
async with self._put_lock:
|
183
|
-
if
|
184
|
-
|
185
|
-
|
186
|
-
self.
|
187
|
-
except (QueueDone, QueueEmpty):
|
188
|
-
pass
|
189
|
-
self.check_done()
|
187
|
+
if self._Q.qsize() == 0:
|
188
|
+
self._empty.set()
|
189
|
+
if not self.has_wip:
|
190
|
+
self._done.set()
|
190
191
|
await self._Q.put(None)
|
191
192
|
return True
|
192
193
|
return False
|
193
194
|
|
194
195
|
async def put(self, item: T) -> None:
|
196
|
+
if item is None:
|
197
|
+
raise ValueError("Cannot add None to IterableQueue")
|
195
198
|
async with self._put_lock:
|
196
199
|
if self.is_filled: # should this be inside put_lock?
|
197
200
|
raise QueueDone
|
198
201
|
if self._producers <= 0:
|
199
202
|
raise ValueError("No registered producers")
|
200
|
-
elif item is None:
|
201
|
-
raise ValueError("Cannot add None to IterableQueue")
|
202
203
|
await self._Q.put(item=item)
|
203
|
-
self._empty.clear()
|
204
204
|
return None
|
205
205
|
|
206
206
|
def put_nowait(self, item: T) -> None:
|
207
207
|
"""
|
208
208
|
Experimental asyncio.Queue.put_nowait() implementation
|
209
209
|
"""
|
210
|
-
# raise NotImplementedError
|
211
210
|
if self.is_filled:
|
212
211
|
raise QueueDone
|
213
212
|
if self._producers <= 0:
|
214
213
|
raise ValueError("No registered producers")
|
215
|
-
|
214
|
+
if item is None:
|
216
215
|
raise ValueError("Cannot add None to IterableQueue")
|
217
216
|
self._Q.put_nowait(item=item)
|
218
|
-
self._empty.clear()
|
219
217
|
return None
|
220
218
|
|
221
219
|
async def get(self) -> T:
|
222
220
|
item = await self._Q.get()
|
223
221
|
if item is None:
|
224
222
|
self._empty.set()
|
223
|
+
if not self.has_wip:
|
224
|
+
self._done.set()
|
225
225
|
self._Q.task_done()
|
226
|
-
self.check_done()
|
227
226
|
async with self._put_lock:
|
228
227
|
await self._Q.put(None)
|
229
228
|
raise QueueDone
|
230
229
|
else:
|
231
|
-
|
232
|
-
self._empty.set()
|
233
|
-
async with self._modify:
|
234
|
-
self._wip += 1
|
230
|
+
self._wip += 1
|
235
231
|
return item
|
236
232
|
|
237
233
|
def get_nowait(self) -> T:
|
@@ -241,16 +237,15 @@ class IterableQueue(Queue[T], AsyncIterable[T], Countable):
|
|
241
237
|
item: T | None = self._Q.get_nowait()
|
242
238
|
if item is None:
|
243
239
|
self._empty.set()
|
240
|
+
if not self.has_wip:
|
241
|
+
self._done.set()
|
244
242
|
self._Q.task_done()
|
245
|
-
self.check_done()
|
246
243
|
try:
|
247
244
|
self._Q.put_nowait(None)
|
248
245
|
except QueueFull:
|
249
246
|
pass
|
250
247
|
raise QueueDone
|
251
248
|
else:
|
252
|
-
if self._Q.qsize() == 0:
|
253
|
-
self._empty.set()
|
254
249
|
self._wip += 1
|
255
250
|
return item
|
256
251
|
|
@@ -260,7 +255,8 @@ class IterableQueue(Queue[T], AsyncIterable[T], Countable):
|
|
260
255
|
self._wip -= 1
|
261
256
|
if self._wip < 0:
|
262
257
|
raise ValueError("task_done() called more than tasks open")
|
263
|
-
self.
|
258
|
+
if self.is_filled and self._empty.is_set() and not self.has_wip:
|
259
|
+
self._done.set()
|
264
260
|
|
265
261
|
async def join(self) -> None:
|
266
262
|
debug("Waiting queue to be filled")
|
@@ -271,14 +267,18 @@ class IterableQueue(Queue[T], AsyncIterable[T], Countable):
|
|
271
267
|
return None
|
272
268
|
|
273
269
|
def __aiter__(self):
|
270
|
+
"""
|
271
|
+
Return ASyncIterator to be able iterate the queue using async for
|
272
|
+
"""
|
274
273
|
return self
|
275
274
|
|
276
275
|
async def __anext__(self) -> T:
|
277
|
-
|
278
|
-
|
279
|
-
|
276
|
+
"""
|
277
|
+
Async iterator for IterableQueue
|
278
|
+
"""
|
279
|
+
if self._wip > 0: # do not mark task_done() at first call
|
280
|
+
self.task_done()
|
280
281
|
try:
|
281
|
-
|
282
|
-
return item
|
282
|
+
return await self.get()
|
283
283
|
except QueueDone:
|
284
284
|
raise StopAsyncIteration
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: queutils
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.10.0
|
4
4
|
Summary: Handy Python Queue utilies
|
5
5
|
Project-URL: Homepage, https://github.com/Jylpah/queutils
|
6
6
|
Project-URL: Bug Tracker, https://github.com/Jylpah/queutils/issues
|
@@ -72,8 +72,6 @@ producers are "finished", the queue enters into "filled" state and no new items
|
|
72
72
|
- Producers must be registered with `add_producer()` and they must notify the queue
|
73
73
|
with `finish()` once they have finished adding items
|
74
74
|
- Countable interface to count number of items task_done() through `count` property
|
75
|
-
- Countable property can be disabled with count_items=False. This is useful when you
|
76
|
-
want to sum the count of multiple IterableQueues
|
77
75
|
|
78
76
|
# EventCounterQueue
|
79
77
|
|
@@ -0,0 +1,12 @@
|
|
1
|
+
queutils/__init__.py,sha256=EGFMKYqmOdB22HjQH62w5eWmncr9IxC9_QAlT-4R9cY,519
|
2
|
+
queutils/asyncqueue.py,sha256=qYtrF03u954B7NdCOIIW9s1_Lpw38BmGgB-Pin8y4n0,2646
|
3
|
+
queutils/awrap.py,sha256=QydWBLmyNIHpYrRwVFn1RWjbQKRnJr61u-DYl7d3F7w,1293
|
4
|
+
queutils/countable.py,sha256=YSi7ILf9CuB5Tm3T4UUMEFlveqzqcmomfqJAlLGHEz8,172
|
5
|
+
queutils/eventcounterqueue.py,sha256=9CvgfnWmvXrMr3h-RiQyIVIBXqOnGvohql_ADvdlmxo,2913
|
6
|
+
queutils/filequeue.py,sha256=J_UK3VHKaM4ELFzedtj6lDmlXas8O732aC0nO8zNzNo,5256
|
7
|
+
queutils/iterablequeue.py,sha256=_aAtitQROpUHglcmGCAJkOErqDjibbiALp2ewnzf2U8,8699
|
8
|
+
queutils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
queutils-0.10.0.dist-info/METADATA,sha256=_6RN7Pde53fUpvEOgVn7Y3LV-EkVfeGtX4OfB9N3SLc,4630
|
10
|
+
queutils-0.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
11
|
+
queutils-0.10.0.dist-info/licenses/LICENSE,sha256=J1zeIKU2JVQmhwO2hHQDK8WR6zjVZ-wX8r7ZlL45AbI,1063
|
12
|
+
queutils-0.10.0.dist-info/RECORD,,
|
queutils-0.9.4.dist-info/RECORD
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
queutils/__init__.py,sha256=rbDSh9513Dday-MfWzPOUfN7Aj2x4IqrvCmKbBj7has,441
|
2
|
-
queutils/asyncqueue.py,sha256=GZRmlWTBQoKzTf7xr4MI-qhNqvIiaNWswAxFokP91Lg,2789
|
3
|
-
queutils/countable.py,sha256=YSi7ILf9CuB5Tm3T4UUMEFlveqzqcmomfqJAlLGHEz8,172
|
4
|
-
queutils/eventcounterqueue.py,sha256=NjT8FqEskZhjmus7L333DZU9kXoZCNsn7ydCI5HSe1U,3047
|
5
|
-
queutils/filequeue.py,sha256=q2ly9H-lSCq6xuOqT1IlWgyCVyLoZiKbc0NuzmkF4aw,5360
|
6
|
-
queutils/iterablequeue.py,sha256=ZXwa9040yTawL1DMMNBXgQKiYr32nmzuSbgi-raAtZc,8664
|
7
|
-
queutils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
-
queutils-0.9.4.dist-info/METADATA,sha256=v3MC3cKun7jnGfnR9WFs7PYQHiCEDu398YBBVEWVcqE,4768
|
9
|
-
queutils-0.9.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
10
|
-
queutils-0.9.4.dist-info/licenses/LICENSE,sha256=J1zeIKU2JVQmhwO2hHQDK8WR6zjVZ-wX8r7ZlL45AbI,1063
|
11
|
-
queutils-0.9.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|