queutils 0.9.3__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 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
@@ -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__(count_items=True, **kwargs)
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 : bool = 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.finish()
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, QueueEmpty, Event, Lock
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. Supports:
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
- - Countable property can be disabled with count_items=False. This is useful when you
56
- want to sum the count of multiple IterableQueues
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, count_items: bool = True, **kwargs):
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.set()
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
- if self._count_items:
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
- assert N > 0, "N has to be positive"
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
- async def finish(self, all: bool = False, empty: bool = False) -> bool:
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._producers <= 0 or self.is_filled:
164
- # raise ValueError("finish() called more than the is producers")
165
- self._producers = 0
166
- return False
174
+ if self.is_filled:
175
+ return True
176
+
167
177
  self._producers -= 1
168
178
 
169
- if all or self._producers <= 0:
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 empty:
184
- try:
185
- _ = self.get_nowait()
186
- self.task_done()
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
- elif item is None:
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
- if self._Q.qsize() == 0:
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.check_done()
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
- async with self._modify:
278
- if self._wip > 0: # do not mark task_done() at first call
279
- self.task_done()
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
- item = await self.get()
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.9.3
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
@@ -38,13 +38,13 @@ Queutils *[Queue Utils]* is a package of handy Python queue classes:
38
38
  - **AsyncQueue** - An `async` wrapper for non-async `queue.Queue`
39
39
  - **IterableQueue** - An `AsyncIterable` queue that terminates when finished
40
40
  - **EventCounterQueue** - An `IterableQueue` for counting events in `async` threads
41
- - **FileQueue** - Builds an iterable queue of filenames from files/dirs given as input
41
+ - **FileQueue** - Builds an `IterableQueue[pathlib.Path]` of filenames from files/dirs given as input
42
42
 
43
43
 
44
44
  # AsyncQueue
45
45
 
46
46
  `AsyncQueue` is a async wrapper for non-async `queue.Queue`. It can be used to create
47
- an `asyncio.Queue` compatible interface to a (non-async) managed `multiprocessing.Queue` and thus enable `async` code in parent/child processes to communicate over `multiprocessing.Queue` as it were an `asyncio.Queue`.
47
+ an `asyncio.Queue` compatible interface to a (non-async) managed `multiprocessing.Queue` and thus enable `async` code in parent/child processes to communicate over `multiprocessing.Queue` as it were an `asyncio.Queue`. Uses `sleep()` for `get()`/`put()` if the queue is empty/full.
48
48
 
49
49
  ## Features
50
50
 
@@ -58,7 +58,7 @@ an `asyncio.Queue` compatible interface to a (non-async) managed `multiprocessin
58
58
  `IterableQueue` is an `asyncio.Queue` subclass that is `AsyncIterable[T]` i.e. it can be
59
59
  iterated in `async for` loop. `IterableQueue` terminates automatically when the queue has been filled and emptied.
60
60
 
61
- The `IterableQueue` requires "producers" (functions adding items to the queue) to register themselves and it
61
+ The `IterableQueue` requires "producers" (functions adding items to the queue) to register themselves with `add_producer()` call and it
62
62
  keeps count of registered producers which are "finished" adding items to the queue. Once all the registered
63
63
  producers are "finished", the queue enters into "filled" state and no new items can be added. Once an
64
64
  "filled" queue is emptied, the queue becomes "done" and all new `get()` calls to the queue will
@@ -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,,
@@ -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.3.dist-info/METADATA,sha256=VGe0K6t0aepLD6DlXlwjFiifK8IUZfcmQlOUXVCKFbA,4664
9
- queutils-0.9.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
- queutils-0.9.3.dist-info/licenses/LICENSE,sha256=J1zeIKU2JVQmhwO2hHQDK8WR6zjVZ-wX8r7ZlL45AbI,1063
11
- queutils-0.9.3.dist-info/RECORD,,