queutils 0.8.1__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 +11 -0
- queutils/asyncqueue.py +112 -0
- queutils/countable.py +11 -0
- queutils/filequeue.py +156 -0
- queutils/iterablequeue.py +283 -0
- queutils-0.8.1.dist-info/METADATA +78 -0
- queutils-0.8.1.dist-info/RECORD +9 -0
- queutils-0.8.1.dist-info/WHEEL +4 -0
- queutils-0.8.1.dist-info/licenses/LICENSE +21 -0
queutils/__init__.py
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
from .countable import Countable as Countable
|
2
|
+
from .asyncqueue import AsyncQueue as AsyncQueue
|
3
|
+
from .iterablequeue import IterableQueue as IterableQueue, QueueDone as QueueDone
|
4
|
+
from .filequeue import FileQueue as FileQueue
|
5
|
+
|
6
|
+
__all__ = [
|
7
|
+
"asyncqueue",
|
8
|
+
"countable",
|
9
|
+
"filequeue",
|
10
|
+
"iterablequeue",
|
11
|
+
]
|
queutils/asyncqueue.py
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# -----------------------------------------------------------
|
2
|
+
# Class AsyncQueue(asyncio.Queue, Generic[T])
|
3
|
+
#
|
4
|
+
# asyncio wrapper for non-async queue.Queue. Can be used to create
|
5
|
+
# an asyncio.Queue out of a (non-async) multiprocessing.Queue.
|
6
|
+
#
|
7
|
+
# -----------------------------------------------------------
|
8
|
+
|
9
|
+
__author__ = "Jylpah"
|
10
|
+
__copyright__ = "Copyright 2024, Jylpah <Jylpah@gmail.com>"
|
11
|
+
__credits__ = ["Jylpah"]
|
12
|
+
__license__ = "MIT"
|
13
|
+
# __version__ = "1.0"
|
14
|
+
__maintainer__ = "Jylpah"
|
15
|
+
__email__ = "Jylpah@gmail.com"
|
16
|
+
__status__ = "Production"
|
17
|
+
|
18
|
+
|
19
|
+
from queue import Full, Empty, Queue
|
20
|
+
from asyncio.queues import QueueEmpty, QueueFull
|
21
|
+
import asyncio
|
22
|
+
from typing import Generic, TypeVar
|
23
|
+
import logging
|
24
|
+
from asyncio import sleep
|
25
|
+
|
26
|
+
T = TypeVar("T")
|
27
|
+
|
28
|
+
logger = logging.getLogger(__name__)
|
29
|
+
|
30
|
+
debug = logger.debug
|
31
|
+
message = logger.warning
|
32
|
+
verbose = logger.info
|
33
|
+
error = logger.error
|
34
|
+
|
35
|
+
|
36
|
+
class AsyncQueue(asyncio.Queue, Generic[T]):
|
37
|
+
"""
|
38
|
+
Async wrapper for non-async queue.Queue. Can be used to create
|
39
|
+
an asyncio.Queue out of a (non-async) multiprocessing.Queue.
|
40
|
+
"""
|
41
|
+
|
42
|
+
def __init__(self, queue: Queue[T], asleep: float = 0.01):
|
43
|
+
self._Q: Queue[T] = queue
|
44
|
+
# self._maxsize: int = queue.maxsize
|
45
|
+
self._done: int = 0
|
46
|
+
self._items: int = 0
|
47
|
+
self._sleep: float = asleep
|
48
|
+
|
49
|
+
|
50
|
+
@property
|
51
|
+
def maxsize(self) -> int:
|
52
|
+
"""not supported by queue.Queue"""
|
53
|
+
return self._Q.maxsize
|
54
|
+
|
55
|
+
async def get(self) -> T:
|
56
|
+
while True:
|
57
|
+
try:
|
58
|
+
return self.get_nowait()
|
59
|
+
except QueueEmpty:
|
60
|
+
await sleep(self._sleep)
|
61
|
+
|
62
|
+
def get_nowait(self) -> T:
|
63
|
+
try:
|
64
|
+
return self._Q.get_nowait()
|
65
|
+
except Empty:
|
66
|
+
raise QueueEmpty
|
67
|
+
|
68
|
+
async def put(self, item: T) -> None:
|
69
|
+
while True:
|
70
|
+
try:
|
71
|
+
return self.put_nowait(item)
|
72
|
+
except QueueFull:
|
73
|
+
await sleep(self._sleep)
|
74
|
+
|
75
|
+
def put_nowait(self, item: T) -> None:
|
76
|
+
try:
|
77
|
+
self._Q.put_nowait(item)
|
78
|
+
self._items += 1
|
79
|
+
return None
|
80
|
+
except Full:
|
81
|
+
raise QueueFull
|
82
|
+
|
83
|
+
async def join(self) -> None:
|
84
|
+
while True:
|
85
|
+
if self._Q.empty():
|
86
|
+
return None
|
87
|
+
else:
|
88
|
+
await sleep(self._sleep)
|
89
|
+
|
90
|
+
def task_done(self) -> None:
|
91
|
+
self._Q.task_done()
|
92
|
+
self._done += 1
|
93
|
+
return None
|
94
|
+
|
95
|
+
def qsize(self) -> int:
|
96
|
+
return self._Q.qsize()
|
97
|
+
|
98
|
+
@property
|
99
|
+
def done(self) -> int:
|
100
|
+
"""Number of done items"""
|
101
|
+
return self._done
|
102
|
+
|
103
|
+
@property
|
104
|
+
def items(self) -> int:
|
105
|
+
"""Number of items ever put to the queue"""
|
106
|
+
return self._items
|
107
|
+
|
108
|
+
def empty(self) -> bool:
|
109
|
+
return self._Q.empty()
|
110
|
+
|
111
|
+
def full(self) -> bool:
|
112
|
+
return self._Q.full()
|
queutils/countable.py
ADDED
queutils/filequeue.py
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
# -----------------------------------------------------------
|
2
|
+
# Class FileQueue(asyncio.Queue)
|
3
|
+
#
|
4
|
+
# Class to build asyncio.Queue of filename (pathlib.Path) based on arguments given.
|
5
|
+
# - Inherits from queutils.IterableQueue
|
6
|
+
# - Supports include/exclude filters
|
7
|
+
#
|
8
|
+
# -----------------------------------------------------------
|
9
|
+
|
10
|
+
__author__ = "Jylpah"
|
11
|
+
__copyright__ = "Copyright 2024, Jylpah <Jylpah@gmail.com>"
|
12
|
+
__credits__ = ["Jylpah"]
|
13
|
+
__license__ = "MIT"
|
14
|
+
# __version__ = "1.0"
|
15
|
+
__maintainer__ = "Jylpah"
|
16
|
+
__email__ = "Jylpah@gmail.com"
|
17
|
+
__status__ = "Production"
|
18
|
+
|
19
|
+
|
20
|
+
import logging
|
21
|
+
|
22
|
+
# from asyncio import Queue
|
23
|
+
import aioconsole # type: ignore
|
24
|
+
from fnmatch import fnmatch, fnmatchcase
|
25
|
+
from pathlib import Path
|
26
|
+
from typing import Optional, Sequence
|
27
|
+
|
28
|
+
from .iterablequeue import IterableQueue
|
29
|
+
|
30
|
+
logger = logging.getLogger(__name__)
|
31
|
+
error = logger.error
|
32
|
+
message = logger.warning
|
33
|
+
verbose = logger.info
|
34
|
+
debug = logger.debug
|
35
|
+
|
36
|
+
|
37
|
+
def str2path(filename: str | Path, suffix: str | None = None) -> Path:
|
38
|
+
"""
|
39
|
+
Convert filename (str) to pathlib.Path
|
40
|
+
"""
|
41
|
+
if isinstance(filename, str):
|
42
|
+
filename = Path(filename)
|
43
|
+
if suffix is not None and not filename.name.lower().endswith(suffix):
|
44
|
+
filename = filename.with_suffix(suffix)
|
45
|
+
return filename
|
46
|
+
|
47
|
+
|
48
|
+
class FileQueue(IterableQueue[Path]):
|
49
|
+
"""
|
50
|
+
Class to create a IterableQueue(asyncio.Queue) of filenames based on
|
51
|
+
given directories and files as arguments.
|
52
|
+
Supports include/exclude filters based on filenames.
|
53
|
+
"""
|
54
|
+
|
55
|
+
def __init__(
|
56
|
+
self,
|
57
|
+
base: Optional[Path] = None,
|
58
|
+
filter: str = "*",
|
59
|
+
exclude: bool = False,
|
60
|
+
case_sensitive: bool = True,
|
61
|
+
follow_symlinks: bool = False,
|
62
|
+
**kwargs,
|
63
|
+
):
|
64
|
+
assert base is None or isinstance(base, Path), "base has to be Path or None"
|
65
|
+
assert isinstance(filter, str), "filter has to be string"
|
66
|
+
assert isinstance(case_sensitive, bool), "case_sensitive has to be bool"
|
67
|
+
assert isinstance(follow_symlinks, bool), "follow_symlinks has to be bool"
|
68
|
+
|
69
|
+
# debug(f"maxsize={str(maxsize)}, filter='{filter}'")
|
70
|
+
super().__init__(count_items=True, **kwargs)
|
71
|
+
self._base: Optional[Path] = base
|
72
|
+
# self._done: bool = False
|
73
|
+
self._case_sensitive: bool = False
|
74
|
+
self._exclude: bool = False
|
75
|
+
self._follow_symlinks : bool = follow_symlinks
|
76
|
+
self.set_filter(filter=filter, exclude=exclude, case_sensitive=case_sensitive)
|
77
|
+
|
78
|
+
def set_filter(
|
79
|
+
self,
|
80
|
+
filter: str = "*",
|
81
|
+
exclude: bool = False,
|
82
|
+
case_sensitive: bool = False,
|
83
|
+
):
|
84
|
+
"""set filtering logic. Only set (!= None) params are changed"""
|
85
|
+
assert isinstance(case_sensitive, bool), "case_sensitive must be type of bool"
|
86
|
+
assert isinstance(exclude, bool), "exclude must be type of bool"
|
87
|
+
|
88
|
+
self._case_sensitive = case_sensitive
|
89
|
+
self._exclude = exclude
|
90
|
+
self._filter = filter
|
91
|
+
debug(
|
92
|
+
"filter=%s exclude=%s, case_sensitive=%s",
|
93
|
+
str(self._filter),
|
94
|
+
self._exclude,
|
95
|
+
self._case_sensitive,
|
96
|
+
)
|
97
|
+
|
98
|
+
async def mk_queue(self, files: Sequence[str | Path]) -> bool:
|
99
|
+
"""Create file queue from arguments given
|
100
|
+
'-' denotes for STDIN
|
101
|
+
"""
|
102
|
+
assert files is not None and len(files) > 0, "No files given to process"
|
103
|
+
await self.add_producer()
|
104
|
+
path: Path
|
105
|
+
file: str | Path
|
106
|
+
try:
|
107
|
+
if isinstance(files[0], str) and files[0] == "-":
|
108
|
+
stdin, _ = await aioconsole.get_standard_streams()
|
109
|
+
while (line := await stdin.readline()) is not None:
|
110
|
+
path = Path(line.decode("utf-8").removesuffix("\n"))
|
111
|
+
if self._base is not None:
|
112
|
+
path = self._base / path
|
113
|
+
await self.put(path)
|
114
|
+
else:
|
115
|
+
for file in files:
|
116
|
+
path = str2path(file)
|
117
|
+
if self._base is not None:
|
118
|
+
path = self._base / path
|
119
|
+
await self.put(path)
|
120
|
+
except Exception as err:
|
121
|
+
error(f"{err}")
|
122
|
+
return await self.finish()
|
123
|
+
|
124
|
+
|
125
|
+
async def put(self, path: Path) -> None:
|
126
|
+
"""Recursive function to build process queue. Sanitize filename"""
|
127
|
+
assert isinstance(path, Path), "path has to be type Path()"
|
128
|
+
try:
|
129
|
+
if path.is_symlink() and not self._follow_symlinks:
|
130
|
+
return None
|
131
|
+
if path.is_dir():
|
132
|
+
for child in path.iterdir():
|
133
|
+
await self.put(child)
|
134
|
+
elif path.is_file() and self.match(path):
|
135
|
+
debug("Adding file to queue: %s", str(path))
|
136
|
+
await super().put(path)
|
137
|
+
except Exception as err:
|
138
|
+
error(f"{err}")
|
139
|
+
return None
|
140
|
+
|
141
|
+
def match(self, path: Path) -> bool:
|
142
|
+
""" "Match file name with filter
|
143
|
+
|
144
|
+
https://docs.python.org/3/library/fnmatch.html
|
145
|
+
"""
|
146
|
+
assert isinstance(path, Path), "path has to be type Path()"
|
147
|
+
try:
|
148
|
+
m: bool
|
149
|
+
if self._case_sensitive:
|
150
|
+
m = fnmatch(path.name, self._filter)
|
151
|
+
else:
|
152
|
+
m = fnmatchcase(path.name, self._filter)
|
153
|
+
return m != self._exclude
|
154
|
+
except Exception as err:
|
155
|
+
error(f"{err}")
|
156
|
+
return False
|
@@ -0,0 +1,283 @@
|
|
1
|
+
# -----------------------------------------------------------
|
2
|
+
# Class IterableQueue(asyncio.Queue[T], AsyncIterable[T], Countable)
|
3
|
+
#
|
4
|
+
# IterableQueue is asyncio.Queue subclass that can be iterated asynchronusly.
|
5
|
+
# IterableQueue terminates automatically when the queue has been
|
6
|
+
# filled and emptied.
|
7
|
+
#
|
8
|
+
# -----------------------------------------------------------
|
9
|
+
|
10
|
+
__author__ = "Jylpah"
|
11
|
+
__copyright__ = "Copyright 2024, Jylpah <Jylpah@gmail.com>"
|
12
|
+
__credits__ = ["Jylpah"]
|
13
|
+
__license__ = "MIT"
|
14
|
+
# __version__ = "1.0"
|
15
|
+
__maintainer__ = "Jylpah"
|
16
|
+
__email__ = "Jylpah@gmail.com"
|
17
|
+
__status__ = "Production"
|
18
|
+
|
19
|
+
|
20
|
+
from asyncio import Queue, QueueFull, QueueEmpty, Event, Lock
|
21
|
+
from typing import AsyncIterable, TypeVar, Optional
|
22
|
+
from .countable import Countable
|
23
|
+
import logging
|
24
|
+
|
25
|
+
# Setup logging
|
26
|
+
logger = logging.getLogger()
|
27
|
+
error = logger.error
|
28
|
+
message = logger.warning
|
29
|
+
verbose = logger.info
|
30
|
+
debug = logger.debug
|
31
|
+
|
32
|
+
T = TypeVar("T")
|
33
|
+
|
34
|
+
class QueueDone(Exception):
|
35
|
+
"""
|
36
|
+
Exception to mark an IterableQueue as filled and emptied i.e. done
|
37
|
+
"""
|
38
|
+
pass
|
39
|
+
|
40
|
+
|
41
|
+
class IterableQueue(Queue[T], AsyncIterable[T], Countable):
|
42
|
+
"""
|
43
|
+
IterableQueue is asyncio.Queue subclass that can be iterated asynchronusly.
|
44
|
+
|
45
|
+
IterableQueue terminates automatically when the queue has been
|
46
|
+
filled and emptied. Supports:
|
47
|
+
- asyncio.Queue() interface, _nowait() methods are experimental
|
48
|
+
- AsyncIterable(): async for item in queue:
|
49
|
+
- Automatic termination of the consumers when the queue has been emptied with QueueDone exception
|
50
|
+
- Producers must be registered with add_producer() and they must notify the queue
|
51
|
+
with finish() once they have finished adding items
|
52
|
+
- Countable interface to count number of items task_done() through 'count' property
|
53
|
+
- Countable property can be disabled with count_items=False. This is useful when you
|
54
|
+
want to sum the count of multiple IterableQueues
|
55
|
+
"""
|
56
|
+
|
57
|
+
def __init__(self, count_items: bool = True, **kwargs):
|
58
|
+
# _Q is required instead of inheriting from Queue()
|
59
|
+
# using super() since Queue is Optional[T], not [T]
|
60
|
+
self._Q: Queue[Optional[T]] = Queue(**kwargs)
|
61
|
+
self._producers: int = 0
|
62
|
+
self._count_items: bool = count_items
|
63
|
+
self._count: int = 0
|
64
|
+
self._wip: int = 0
|
65
|
+
|
66
|
+
self._modify: Lock = Lock()
|
67
|
+
self._put_lock: Lock = Lock()
|
68
|
+
|
69
|
+
self._filled: Event = Event()
|
70
|
+
self._empty: Event = Event()
|
71
|
+
self._done: Event = Event()
|
72
|
+
|
73
|
+
self._empty.set()
|
74
|
+
|
75
|
+
@property
|
76
|
+
def is_filled(self) -> bool:
|
77
|
+
""""
|
78
|
+
True if all the producers finished filling the queue.
|
79
|
+
New items cannot be added to a filled queue nor can new producers can be added.
|
80
|
+
"""
|
81
|
+
return self._filled.is_set()
|
82
|
+
|
83
|
+
@property
|
84
|
+
def is_done(self) -> bool:
|
85
|
+
"""
|
86
|
+
Has the queue been filled, emptied and all the items have been marked with task_done()
|
87
|
+
"""
|
88
|
+
return self.is_filled and self.empty() and not self.has_wip
|
89
|
+
|
90
|
+
@property
|
91
|
+
def maxsize(self) -> int:
|
92
|
+
return self._Q.maxsize
|
93
|
+
|
94
|
+
|
95
|
+
def full(self) -> bool:
|
96
|
+
"""
|
97
|
+
True if the queue is full
|
98
|
+
"""
|
99
|
+
return self._Q.full()
|
100
|
+
|
101
|
+
def check_done(self) -> bool:
|
102
|
+
if self.is_filled and self.empty() and not self.has_wip:
|
103
|
+
self._done.set()
|
104
|
+
return True
|
105
|
+
return False
|
106
|
+
|
107
|
+
def empty(self) -> bool:
|
108
|
+
"""
|
109
|
+
Queue has no items except None as a sentinel
|
110
|
+
"""
|
111
|
+
return self._empty.is_set() or self.qsize() == 0
|
112
|
+
|
113
|
+
def qsize(self) -> int:
|
114
|
+
"""
|
115
|
+
asyncio.Queue.qsize()
|
116
|
+
"""
|
117
|
+
if self.is_filled:
|
118
|
+
return self._Q.qsize() - 1
|
119
|
+
else:
|
120
|
+
return self._Q.qsize()
|
121
|
+
|
122
|
+
@property
|
123
|
+
def wip(self) -> int:
|
124
|
+
"""
|
125
|
+
Number of items in progress i.e. items that have been
|
126
|
+
read from the queue, but not marked with task_done()
|
127
|
+
"""
|
128
|
+
return self._wip
|
129
|
+
|
130
|
+
@property
|
131
|
+
def has_wip(self) -> bool:
|
132
|
+
"""
|
133
|
+
True if queue has items in progress
|
134
|
+
"""
|
135
|
+
return self._wip > 0
|
136
|
+
|
137
|
+
@property
|
138
|
+
def count(self) -> int:
|
139
|
+
if self._count_items:
|
140
|
+
return self._count
|
141
|
+
else:
|
142
|
+
return 0
|
143
|
+
|
144
|
+
async def add_producer(self, N: int = 1) -> int:
|
145
|
+
"""
|
146
|
+
Add producer(s) to the queue
|
147
|
+
"""
|
148
|
+
assert N > 0, "N has to be positive"
|
149
|
+
async with self._modify:
|
150
|
+
if self.is_filled:
|
151
|
+
raise QueueDone
|
152
|
+
self._producers += N
|
153
|
+
return self._producers
|
154
|
+
|
155
|
+
async def finish(self, all: bool = False, empty: bool = False) -> bool:
|
156
|
+
"""
|
157
|
+
Producer has finished adding items to the queue.
|
158
|
+
Once the last producers has finished, the queue is_filled.
|
159
|
+
- all: finish() queue for all producers at once
|
160
|
+
"""
|
161
|
+
async with self._modify:
|
162
|
+
if self._producers <= 0 or self.is_filled:
|
163
|
+
# raise ValueError("finish() called more than the is producers")
|
164
|
+
self._producers = 0
|
165
|
+
return False
|
166
|
+
self._producers -= 1
|
167
|
+
|
168
|
+
if all or self._producers <= 0:
|
169
|
+
self._filled.set()
|
170
|
+
self._producers = 0
|
171
|
+
|
172
|
+
if self._producers == 0:
|
173
|
+
if empty:
|
174
|
+
try:
|
175
|
+
while True:
|
176
|
+
_ = self.get_nowait()
|
177
|
+
self.task_done()
|
178
|
+
except (QueueDone, QueueEmpty):
|
179
|
+
pass
|
180
|
+
|
181
|
+
async with self._put_lock:
|
182
|
+
if empty:
|
183
|
+
try:
|
184
|
+
_ = self.get_nowait()
|
185
|
+
self.task_done()
|
186
|
+
except (QueueDone, QueueEmpty):
|
187
|
+
pass
|
188
|
+
self.check_done()
|
189
|
+
await self._Q.put(None)
|
190
|
+
return True
|
191
|
+
return False
|
192
|
+
|
193
|
+
async def put(self, item: T) -> None:
|
194
|
+
async with self._put_lock:
|
195
|
+
if self.is_filled: # should this be inside put_lock?
|
196
|
+
raise QueueDone
|
197
|
+
if self._producers <= 0:
|
198
|
+
raise ValueError("No registered producers")
|
199
|
+
elif item is None:
|
200
|
+
raise ValueError("Cannot add None to IterableQueue")
|
201
|
+
await self._Q.put(item=item)
|
202
|
+
self._empty.clear()
|
203
|
+
return None
|
204
|
+
|
205
|
+
def put_nowait(self, item: T) -> None:
|
206
|
+
"""
|
207
|
+
Experimental asyncio.Queue.put_nowait() implementation
|
208
|
+
"""
|
209
|
+
# raise NotImplementedError
|
210
|
+
if self.is_filled:
|
211
|
+
raise QueueDone
|
212
|
+
if self._producers <= 0:
|
213
|
+
raise ValueError("No registered producers")
|
214
|
+
elif item is None:
|
215
|
+
raise ValueError("Cannot add None to IterableQueue")
|
216
|
+
self._Q.put_nowait(item=item)
|
217
|
+
self._empty.clear()
|
218
|
+
return None
|
219
|
+
|
220
|
+
async def get(self) -> T:
|
221
|
+
item = await self._Q.get()
|
222
|
+
if item is None:
|
223
|
+
self._empty.set()
|
224
|
+
self._Q.task_done()
|
225
|
+
self.check_done()
|
226
|
+
async with self._put_lock:
|
227
|
+
await self._Q.put(None)
|
228
|
+
raise QueueDone
|
229
|
+
else:
|
230
|
+
if self._Q.qsize() == 0:
|
231
|
+
self._empty.set()
|
232
|
+
async with self._modify:
|
233
|
+
self._wip += 1
|
234
|
+
return item
|
235
|
+
|
236
|
+
def get_nowait(self) -> T:
|
237
|
+
"""
|
238
|
+
Experimental asyncio.Queue.get_nowait() implementation
|
239
|
+
"""
|
240
|
+
item: T | None = self._Q.get_nowait()
|
241
|
+
if item is None:
|
242
|
+
self._empty.set()
|
243
|
+
self._Q.task_done()
|
244
|
+
self.check_done()
|
245
|
+
try:
|
246
|
+
self._Q.put_nowait(None)
|
247
|
+
except QueueFull:
|
248
|
+
pass
|
249
|
+
raise QueueDone
|
250
|
+
else:
|
251
|
+
if self._Q.qsize() == 0:
|
252
|
+
self._empty.set()
|
253
|
+
self._wip += 1
|
254
|
+
return item
|
255
|
+
|
256
|
+
def task_done(self) -> None:
|
257
|
+
self._Q.task_done()
|
258
|
+
self._count += 1
|
259
|
+
self._wip -= 1
|
260
|
+
if self._wip < 0:
|
261
|
+
raise ValueError("task_done() called more than tasks open")
|
262
|
+
self.check_done()
|
263
|
+
|
264
|
+
async def join(self) -> None:
|
265
|
+
debug("Waiting queue to be filled")
|
266
|
+
await self._filled.wait()
|
267
|
+
debug("Queue filled, waiting when queue is done")
|
268
|
+
await self._done.wait()
|
269
|
+
debug("queue is done")
|
270
|
+
return None
|
271
|
+
|
272
|
+
def __aiter__(self):
|
273
|
+
return self
|
274
|
+
|
275
|
+
async def __anext__(self) -> T:
|
276
|
+
async with self._modify:
|
277
|
+
if self._wip > 0: # do not mark task_done() at first call
|
278
|
+
self.task_done()
|
279
|
+
try:
|
280
|
+
item = await self.get()
|
281
|
+
return item
|
282
|
+
except QueueDone:
|
283
|
+
raise StopAsyncIteration
|
@@ -0,0 +1,78 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: queutils
|
3
|
+
Version: 0.8.1
|
4
|
+
Summary: Handy Python Queue utilies
|
5
|
+
Project-URL: Homepage, https://github.com/Jylpah/queutils
|
6
|
+
Project-URL: Bug Tracker, https://github.com/Jylpah/queutils/issues
|
7
|
+
Author-email: Jylpah <jylpah@gmail.com>
|
8
|
+
License-File: LICENSE
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
10
|
+
Classifier: Framework :: AsyncIO
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
12
|
+
Classifier: Operating System :: OS Independent
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
15
|
+
Requires-Python: >=3.11
|
16
|
+
Requires-Dist: aioconsole>=0.6
|
17
|
+
Provides-Extra: dev
|
18
|
+
Requires-Dist: build>=0.10; extra == 'dev'
|
19
|
+
Requires-Dist: hatchling>=1.22.4; extra == 'dev'
|
20
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
21
|
+
Requires-Dist: pip-chill>=1.0; extra == 'dev'
|
22
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
23
|
+
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
|
24
|
+
Requires-Dist: pytest-datafiles>=3.0; extra == 'dev'
|
25
|
+
Requires-Dist: pytest-timeout>=2.2; extra == 'dev'
|
26
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
27
|
+
Requires-Dist: ruff>=0.1.9; extra == 'dev'
|
28
|
+
Description-Content-Type: text/markdown
|
29
|
+
|
30
|
+
[](https://github.com/Jylpah/queutils/actions/workflows/python-package.yml) [](https://codecov.io/gh/Jylpah/queutils)
|
31
|
+
|
32
|
+
# Queutils
|
33
|
+
|
34
|
+
Queutils *[Queue Utils]* is a package if handy Python queue classes:
|
35
|
+
- **[AsyncQueue](docs/asyncqueue.md)** - `async` wrapper for `queue.Queue`
|
36
|
+
- **[IterableQueue](docs/iterablequeue.md)** - `AsyncIterable` queue
|
37
|
+
- **[FileQueue](docs/filequeue.md)** - builds a queue of filenames from input
|
38
|
+
|
39
|
+
|
40
|
+
# AsyncQueue
|
41
|
+
|
42
|
+
[`AsyncQueue`](docs/asyncqueue.md) is a async wrapper for non-async `queue.Queue`. It can be used to create
|
43
|
+
an `asyncio.Queue` compatible out of a (non-async) `multiprocessing.Queue`. This is handy to have `async` code running in `multiprocessing` processes and yet be able to communicate with the parent via (non-async) managed `multiprocessing.Queue` queue.
|
44
|
+
|
45
|
+
## Features
|
46
|
+
|
47
|
+
- `asyncio.Queue` compatible
|
48
|
+
- `queue.Queue` support
|
49
|
+
- `multiprocessing.Queue` support
|
50
|
+
|
51
|
+
|
52
|
+
# IterableQueue
|
53
|
+
|
54
|
+
[`IterableQueue`](docs/iterablequeue.md) is an `asyncio.Queue` subclass that is `AsyncIterable[T]` i.e. it can be
|
55
|
+
iterated in `async for` loop. `IterableQueue` terminates automatically when the queue has been filled and emptied.
|
56
|
+
|
57
|
+
## Features
|
58
|
+
|
59
|
+
- `asyncio.Queue` interface, `_nowait()` methods are experimental
|
60
|
+
- `AsyncIterable` support: `async for item in queue:`
|
61
|
+
- Automatic termination of the consumers with `QueueDone` exception when the queue has been emptied
|
62
|
+
- Producers must be registered with `add_producer()` and they must notify the queue
|
63
|
+
with `finish()` once they have finished adding items
|
64
|
+
- Countable interface to count number of items task_done() through `count` property
|
65
|
+
- Countable property can be disabled with count_items=False. This is useful when you
|
66
|
+
want to sum the count of multiple IterableQueues
|
67
|
+
|
68
|
+
# FileQueue
|
69
|
+
|
70
|
+
[`FileQueue`](docs/filequeue.md) builds a queue (`IterableQueue[pathlib.Path]`) of the matching files found based on search parameters given. It can search both list of files or directories or mixed. Async method `FileQueue.mk_queue()` searches subdirectories of given directories.
|
71
|
+
|
72
|
+
## Features
|
73
|
+
|
74
|
+
- Input can be given both as `str` and `pathlib.Path`
|
75
|
+
- `exclude: bool` exclusive or inclusive filtering. Default is `False`.
|
76
|
+
- `case_sensitive: bool` case sensitive filtering (use of `fnmatch` or `fnmatchcase`). Default is `True`.
|
77
|
+
- `follow_symlinks: bool` whether to follow symlinks. Default is `False`.
|
78
|
+
|
@@ -0,0 +1,9 @@
|
|
1
|
+
queutils/__init__.py,sha256=MboAom80iQTQURz0liGv_f2ae2Np8YA5ORX1LwsNa50,311
|
2
|
+
queutils/asyncqueue.py,sha256=cZPrXJgRCK0iOtE6k-VTzA9kcDDg818h9v0cwYzm2g0,2813
|
3
|
+
queutils/countable.py,sha256=YSi7ILf9CuB5Tm3T4UUMEFlveqzqcmomfqJAlLGHEz8,172
|
4
|
+
queutils/filequeue.py,sha256=q2ly9H-lSCq6xuOqT1IlWgyCVyLoZiKbc0NuzmkF4aw,5360
|
5
|
+
queutils/iterablequeue.py,sha256=hygOtgRRuqaPebcK27Ixm_KOOW0MnlKmBNcYreQoWOA,8677
|
6
|
+
queutils-0.8.1.dist-info/METADATA,sha256=IcC2JgXacCtVK-NWXMP5T1ehHqJVWzDMuaBr93tvS-0,3694
|
7
|
+
queutils-0.8.1.dist-info/WHEEL,sha256=uNdcs2TADwSd5pVaP0Z_kcjcvvTUklh2S7bxZMF8Uj0,87
|
8
|
+
queutils-0.8.1.dist-info/licenses/LICENSE,sha256=J1zeIKU2JVQmhwO2hHQDK8WR6zjVZ-wX8r7ZlL45AbI,1063
|
9
|
+
queutils-0.8.1.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 Jylpah
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|