DefStream 0.1.2__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.
- DefStream/Broker/Configuration.py +18 -0
- DefStream/Broker/Connection.py +97 -0
- DefStream/Broker/Consumer.py +59 -0
- DefStream/Broker/Errors.py +14 -0
- DefStream/Broker/Event.py +35 -0
- DefStream/Broker/Implements/__init__.py +1 -0
- DefStream/Broker/Policies.py +25 -0
- DefStream/Broker/Protocol.py +45 -0
- DefStream/Broker/__init__.py +47 -0
- DefStream/Descriptor/DataObject.py +384 -0
- DefStream/Descriptor/Types.py +229 -0
- DefStream/Descriptor/Value.py +132 -0
- DefStream/Descriptor/__init__.py +172 -0
- DefStream/Inspector/Extension.py +69 -0
- DefStream/Inspector/Extensions/Console.html +373 -0
- DefStream/Inspector/Extensions/Console.py +396 -0
- DefStream/Inspector/Extensions/Dashboard.html +206 -0
- DefStream/Inspector/Extensions/Dashboard.py +114 -0
- DefStream/Inspector/Extensions/HealthCheck.py +92 -0
- DefStream/Inspector/Extensions/Metrics.py +281 -0
- DefStream/Inspector/Extensions/Specifications.py +160 -0
- DefStream/Inspector/Extensions/__init__.py +13 -0
- DefStream/Inspector/Inspector.py +216 -0
- DefStream/Inspector/Response.py +14 -0
- DefStream/Inspector/__init__.py +9 -0
- DefStream/Validator/Pattern.py +36 -0
- DefStream/Validator/Patterns/Array.py +30 -0
- DefStream/Validator/Patterns/Common.py +93 -0
- DefStream/Validator/Patterns/Compare.py +180 -0
- DefStream/Validator/Patterns/Condition.py +60 -0
- DefStream/Validator/Patterns/DataObject.py +85 -0
- DefStream/Validator/Patterns/DateTime.py +111 -0
- DefStream/Validator/Patterns/Number.py +43 -0
- DefStream/Validator/Patterns/Object.py +150 -0
- DefStream/Validator/Patterns/Size.py +110 -0
- DefStream/Validator/Patterns/String.py +85 -0
- DefStream/Validator/Patterns/Type.py +350 -0
- DefStream/Validator/Patterns/__init__.py +137 -0
- DefStream/Validator/Validate.py +29 -0
- DefStream/Validator/Validator.py +30 -0
- DefStream/Validator/__init__.py +11 -0
- DefStream/Worker/Executor.py +298 -0
- DefStream/Worker/Message.py +103 -0
- DefStream/Worker/Pool.py +203 -0
- DefStream/Worker/Run.py +187 -0
- DefStream/Worker/Specification.py +91 -0
- DefStream/Worker/Worker.py +321 -0
- DefStream/Worker/__init__.py +49 -0
- DefStream/__init__.py +0 -0
- defstream-0.1.2.dist-info/METADATA +1197 -0
- defstream-0.1.2.dist-info/RECORD +54 -0
- defstream-0.1.2.dist-info/WHEEL +5 -0
- defstream-0.1.2.dist-info/licenses/LICENSE +674 -0
- defstream-0.1.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
from concurrent.futures import (
|
|
2
|
+
Executor as PyExecutor,
|
|
3
|
+
Future,
|
|
4
|
+
ProcessPoolExecutor,
|
|
5
|
+
ThreadPoolExecutor,
|
|
6
|
+
)
|
|
7
|
+
from multiprocessing import get_context
|
|
8
|
+
from multiprocessing.managers import SyncManager
|
|
9
|
+
from pickle import (
|
|
10
|
+
dumps,
|
|
11
|
+
loads,
|
|
12
|
+
)
|
|
13
|
+
from queue import (
|
|
14
|
+
Empty,
|
|
15
|
+
Queue,
|
|
16
|
+
)
|
|
17
|
+
from signal import (
|
|
18
|
+
SIGINT,
|
|
19
|
+
SIG_IGN,
|
|
20
|
+
signal,
|
|
21
|
+
)
|
|
22
|
+
from threading import (
|
|
23
|
+
Lock,
|
|
24
|
+
Thread,
|
|
25
|
+
)
|
|
26
|
+
from traceback import format_exc
|
|
27
|
+
from typing import (
|
|
28
|
+
Any,
|
|
29
|
+
Callable,
|
|
30
|
+
Dict,
|
|
31
|
+
List,
|
|
32
|
+
Optional,
|
|
33
|
+
Sequence,
|
|
34
|
+
TypeVar,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = (
|
|
38
|
+
'Executor',
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
Result = TypeVar('Result')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def initialize_process() -> None:
|
|
45
|
+
# The parent coordinates draining and cleanup after Ctrl+C.
|
|
46
|
+
signal(
|
|
47
|
+
SIGINT,
|
|
48
|
+
SIG_IGN,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def execute_loop(
|
|
53
|
+
incoming: Any,
|
|
54
|
+
outgoing: Any,
|
|
55
|
+
setups: Sequence[Callable[[], None]],
|
|
56
|
+
cleanups: Sequence[Callable[[], None]],
|
|
57
|
+
serialized: bool,
|
|
58
|
+
) -> None:
|
|
59
|
+
ready = False
|
|
60
|
+
try:
|
|
61
|
+
for setup in setups:
|
|
62
|
+
setup()
|
|
63
|
+
ready = True
|
|
64
|
+
while True:
|
|
65
|
+
item = incoming.get()
|
|
66
|
+
if item is None:
|
|
67
|
+
return
|
|
68
|
+
key, payload = item
|
|
69
|
+
try:
|
|
70
|
+
function, args, kwargs = loads(payload) if serialized else payload
|
|
71
|
+
result = function(
|
|
72
|
+
*args,
|
|
73
|
+
**kwargs,
|
|
74
|
+
)
|
|
75
|
+
except BaseException as error:
|
|
76
|
+
outcome = (
|
|
77
|
+
False,
|
|
78
|
+
error,
|
|
79
|
+
format_exc() if serialized else None,
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
outcome = (
|
|
83
|
+
True,
|
|
84
|
+
result,
|
|
85
|
+
None,
|
|
86
|
+
)
|
|
87
|
+
if serialized:
|
|
88
|
+
try:
|
|
89
|
+
outcome = dumps(outcome)
|
|
90
|
+
except Exception as error:
|
|
91
|
+
outcome = dumps((
|
|
92
|
+
False,
|
|
93
|
+
RuntimeError('Cannot serialize task outcome: ' + repr(error)),
|
|
94
|
+
format_exc(),
|
|
95
|
+
))
|
|
96
|
+
outgoing.put((
|
|
97
|
+
key,
|
|
98
|
+
outcome,
|
|
99
|
+
))
|
|
100
|
+
finally:
|
|
101
|
+
if ready:
|
|
102
|
+
run_cleanups(cleanups)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def run_cleanups(
|
|
106
|
+
cleanups: Sequence[Callable[..., None]],
|
|
107
|
+
*args: Any,
|
|
108
|
+
) -> None:
|
|
109
|
+
failure: Optional[BaseException] = None
|
|
110
|
+
for cleanup in cleanups:
|
|
111
|
+
try:
|
|
112
|
+
cleanup(*args)
|
|
113
|
+
except BaseException as error:
|
|
114
|
+
if failure is not None:
|
|
115
|
+
error.__context__ = failure
|
|
116
|
+
failure = error
|
|
117
|
+
if failure is not None:
|
|
118
|
+
raise failure
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class Executor(PyExecutor):
|
|
122
|
+
def __init__(
|
|
123
|
+
self,
|
|
124
|
+
size: int,
|
|
125
|
+
setups: Sequence[Callable[[], None]],
|
|
126
|
+
cleanups: Sequence[Callable[[], None]],
|
|
127
|
+
process: bool = False,
|
|
128
|
+
):
|
|
129
|
+
self.size = size
|
|
130
|
+
self.setups = setups
|
|
131
|
+
self.cleanups = cleanups
|
|
132
|
+
self.process = process
|
|
133
|
+
self.lock = Lock()
|
|
134
|
+
self.pending: Dict[int, Future[Any]] = {}
|
|
135
|
+
self.workers: List[Future] = []
|
|
136
|
+
self.closed = False
|
|
137
|
+
self.failure: Optional[BaseException] = None
|
|
138
|
+
self.manager: Optional[SyncManager] = None
|
|
139
|
+
self.executor: Optional[PyExecutor] = None
|
|
140
|
+
self.monitor: Optional[Thread] = None
|
|
141
|
+
self.incoming: Any = None
|
|
142
|
+
self.outgoing: Any = None
|
|
143
|
+
self.sequence = 0
|
|
144
|
+
|
|
145
|
+
def start(self) -> None:
|
|
146
|
+
try:
|
|
147
|
+
if self.process:
|
|
148
|
+
context = get_context('spawn')
|
|
149
|
+
self.manager = context.Manager()
|
|
150
|
+
self.incoming = self.manager.Queue()
|
|
151
|
+
self.outgoing = self.manager.Queue()
|
|
152
|
+
executor: PyExecutor = ProcessPoolExecutor(
|
|
153
|
+
self.size,
|
|
154
|
+
mp_context=context,
|
|
155
|
+
initializer=initialize_process,
|
|
156
|
+
)
|
|
157
|
+
else:
|
|
158
|
+
self.incoming = Queue()
|
|
159
|
+
self.outgoing = Queue()
|
|
160
|
+
executor = ThreadPoolExecutor(self.size)
|
|
161
|
+
self.executor = executor
|
|
162
|
+
for index in range(self.size):
|
|
163
|
+
self.workers.append(executor.submit(
|
|
164
|
+
execute_loop,
|
|
165
|
+
self.incoming,
|
|
166
|
+
self.outgoing,
|
|
167
|
+
self.setups,
|
|
168
|
+
self.cleanups,
|
|
169
|
+
self.process,
|
|
170
|
+
))
|
|
171
|
+
monitor = Thread(target=self.collect)
|
|
172
|
+
monitor.start()
|
|
173
|
+
self.monitor = monitor
|
|
174
|
+
except BaseException:
|
|
175
|
+
self.closed = True
|
|
176
|
+
try:
|
|
177
|
+
for worker in self.workers:
|
|
178
|
+
self.incoming.put(None)
|
|
179
|
+
finally:
|
|
180
|
+
try:
|
|
181
|
+
if self.executor is not None:
|
|
182
|
+
self.executor.shutdown(wait=True)
|
|
183
|
+
finally:
|
|
184
|
+
if self.manager is not None:
|
|
185
|
+
self.manager.shutdown()
|
|
186
|
+
raise
|
|
187
|
+
|
|
188
|
+
def submit(
|
|
189
|
+
self,
|
|
190
|
+
fn: Callable[..., Result],
|
|
191
|
+
/,
|
|
192
|
+
*args: Any,
|
|
193
|
+
**kwargs: Any,
|
|
194
|
+
) -> Future[Result]:
|
|
195
|
+
payload = (
|
|
196
|
+
fn,
|
|
197
|
+
args,
|
|
198
|
+
kwargs,
|
|
199
|
+
)
|
|
200
|
+
if self.process:
|
|
201
|
+
payload = dumps(payload)
|
|
202
|
+
with self.lock:
|
|
203
|
+
if self.closed:
|
|
204
|
+
raise RuntimeError('Pool is closed')
|
|
205
|
+
if self.failure is not None:
|
|
206
|
+
raise RuntimeError('Pool execution environment failed') from self.failure
|
|
207
|
+
if self.executor is None:
|
|
208
|
+
self.start()
|
|
209
|
+
key = self.sequence
|
|
210
|
+
self.sequence += 1
|
|
211
|
+
future: Future[Result] = Future()
|
|
212
|
+
future.set_running_or_notify_cancel()
|
|
213
|
+
self.pending[key] = future
|
|
214
|
+
try:
|
|
215
|
+
self.incoming.put((
|
|
216
|
+
key,
|
|
217
|
+
payload,
|
|
218
|
+
))
|
|
219
|
+
except BaseException:
|
|
220
|
+
del self.pending[key]
|
|
221
|
+
raise
|
|
222
|
+
return future
|
|
223
|
+
|
|
224
|
+
def collect(self) -> None:
|
|
225
|
+
try:
|
|
226
|
+
self.collect_results()
|
|
227
|
+
except BaseException as error:
|
|
228
|
+
self.fail(error)
|
|
229
|
+
|
|
230
|
+
def fail(
|
|
231
|
+
self,
|
|
232
|
+
error: BaseException,
|
|
233
|
+
) -> None:
|
|
234
|
+
with self.lock:
|
|
235
|
+
self.failure = error
|
|
236
|
+
pending = tuple(self.pending.values())
|
|
237
|
+
self.pending.clear()
|
|
238
|
+
for future in pending:
|
|
239
|
+
future.set_exception(error)
|
|
240
|
+
|
|
241
|
+
def collect_results(self) -> None:
|
|
242
|
+
while True:
|
|
243
|
+
try:
|
|
244
|
+
key, outcome = self.outgoing.get(timeout=0.05)
|
|
245
|
+
except Empty:
|
|
246
|
+
pass
|
|
247
|
+
else:
|
|
248
|
+
success, value, traceback = loads(outcome) if self.process else outcome
|
|
249
|
+
with self.lock:
|
|
250
|
+
future = self.pending.pop(
|
|
251
|
+
key,
|
|
252
|
+
None,
|
|
253
|
+
)
|
|
254
|
+
if future is not None:
|
|
255
|
+
if success:
|
|
256
|
+
future.set_result(value)
|
|
257
|
+
else:
|
|
258
|
+
if traceback is not None:
|
|
259
|
+
value.__cause__ = RuntimeError('Remote execution traceback:\n' + traceback)
|
|
260
|
+
future.set_exception(value)
|
|
261
|
+
failure = next(
|
|
262
|
+
(
|
|
263
|
+
worker.exception()
|
|
264
|
+
for worker in self.workers
|
|
265
|
+
if worker.done() and worker.exception() is not None
|
|
266
|
+
),
|
|
267
|
+
None,
|
|
268
|
+
)
|
|
269
|
+
if failure is not None:
|
|
270
|
+
self.fail(failure)
|
|
271
|
+
with self.lock:
|
|
272
|
+
finished = not self.pending and all(worker.done() for worker in self.workers)
|
|
273
|
+
if finished:
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
def shutdown(
|
|
277
|
+
self,
|
|
278
|
+
wait: bool = True,
|
|
279
|
+
*,
|
|
280
|
+
cancel_futures: bool = False,
|
|
281
|
+
) -> None:
|
|
282
|
+
with self.lock:
|
|
283
|
+
if not self.closed:
|
|
284
|
+
self.closed = True
|
|
285
|
+
if self.executor is not None:
|
|
286
|
+
for worker in self.workers:
|
|
287
|
+
self.incoming.put(None)
|
|
288
|
+
executor = self.executor
|
|
289
|
+
if executor is None:
|
|
290
|
+
return
|
|
291
|
+
executor.shutdown(wait=wait)
|
|
292
|
+
if wait:
|
|
293
|
+
if self.monitor is not None:
|
|
294
|
+
self.monitor.join()
|
|
295
|
+
if self.manager is not None:
|
|
296
|
+
self.manager.shutdown()
|
|
297
|
+
if self.failure is not None:
|
|
298
|
+
raise RuntimeError('Pool execution environment failed') from self.failure
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from inspect import isabstract
|
|
3
|
+
from typing import (
|
|
4
|
+
Any,
|
|
5
|
+
Type,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
from ..Broker.Connection import (
|
|
9
|
+
Declarable,
|
|
10
|
+
TaskSender,
|
|
11
|
+
Requestable,
|
|
12
|
+
)
|
|
13
|
+
from .Run import Run
|
|
14
|
+
|
|
15
|
+
__all__ = (
|
|
16
|
+
'declare',
|
|
17
|
+
'send',
|
|
18
|
+
'request',
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def task_parameters(
|
|
23
|
+
task: Type[Run],
|
|
24
|
+
) -> Any:
|
|
25
|
+
if not isinstance(
|
|
26
|
+
task,
|
|
27
|
+
type,
|
|
28
|
+
) or not issubclass(
|
|
29
|
+
task,
|
|
30
|
+
Run,
|
|
31
|
+
) or isabstract(task):
|
|
32
|
+
raise TypeError('A concrete Run class is required')
|
|
33
|
+
if task.parameters is None:
|
|
34
|
+
raise ValueError('Run subscription parameters are required')
|
|
35
|
+
return task.parameters
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def message_body(
|
|
39
|
+
body: Mapping,
|
|
40
|
+
) -> dict:
|
|
41
|
+
if not isinstance(
|
|
42
|
+
body,
|
|
43
|
+
Mapping,
|
|
44
|
+
) or not all(isinstance(
|
|
45
|
+
key,
|
|
46
|
+
str,
|
|
47
|
+
) for key in body):
|
|
48
|
+
raise TypeError('Task body must be a mapping with string keys')
|
|
49
|
+
return dict(body)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def declare(
|
|
53
|
+
*,
|
|
54
|
+
task: Type[Run],
|
|
55
|
+
broker: Declarable,
|
|
56
|
+
) -> None:
|
|
57
|
+
parameters = task_parameters(task)
|
|
58
|
+
if not isinstance(
|
|
59
|
+
broker,
|
|
60
|
+
Declarable,
|
|
61
|
+
):
|
|
62
|
+
raise NotImplementedError('This connection does not support task declarations')
|
|
63
|
+
broker.declare(parameters)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def send(
|
|
67
|
+
body: Mapping,
|
|
68
|
+
*,
|
|
69
|
+
task: Type[Run],
|
|
70
|
+
broker: TaskSender,
|
|
71
|
+
) -> Any:
|
|
72
|
+
parameters = task_parameters(task)
|
|
73
|
+
payload = message_body(body)
|
|
74
|
+
if not isinstance(
|
|
75
|
+
broker,
|
|
76
|
+
TaskSender,
|
|
77
|
+
):
|
|
78
|
+
raise NotImplementedError('This connection does not support task sending')
|
|
79
|
+
return broker.send(
|
|
80
|
+
payload,
|
|
81
|
+
parameters,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def request(
|
|
86
|
+
body: Mapping,
|
|
87
|
+
*,
|
|
88
|
+
task: Type[Run],
|
|
89
|
+
broker: TaskSender,
|
|
90
|
+
timeout: float = 5,
|
|
91
|
+
) -> Any:
|
|
92
|
+
parameters = task_parameters(task)
|
|
93
|
+
payload = message_body(body)
|
|
94
|
+
if not isinstance(
|
|
95
|
+
broker,
|
|
96
|
+
Requestable,
|
|
97
|
+
):
|
|
98
|
+
raise NotImplementedError('This connection does not support task RPC requests')
|
|
99
|
+
return broker.request(
|
|
100
|
+
payload,
|
|
101
|
+
parameters,
|
|
102
|
+
timeout=timeout,
|
|
103
|
+
)
|
DefStream/Worker/Pool.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
from abc import (
|
|
2
|
+
ABC,
|
|
3
|
+
abstractmethod,
|
|
4
|
+
)
|
|
5
|
+
from concurrent.futures import (
|
|
6
|
+
Executor as PyExecutor,
|
|
7
|
+
Future,
|
|
8
|
+
wait,
|
|
9
|
+
)
|
|
10
|
+
from os import cpu_count
|
|
11
|
+
from threading import (
|
|
12
|
+
BoundedSemaphore,
|
|
13
|
+
Lock,
|
|
14
|
+
)
|
|
15
|
+
from typing import (
|
|
16
|
+
Any,
|
|
17
|
+
Callable,
|
|
18
|
+
Dict,
|
|
19
|
+
Optional,
|
|
20
|
+
Sequence,
|
|
21
|
+
Set,
|
|
22
|
+
Tuple,
|
|
23
|
+
Type,
|
|
24
|
+
Union,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from .Run import Run
|
|
28
|
+
from .Executor import Executor
|
|
29
|
+
|
|
30
|
+
__all__ = (
|
|
31
|
+
'Pool',
|
|
32
|
+
'PoolFull',
|
|
33
|
+
'PoolSetup',
|
|
34
|
+
'PoolCleanup',
|
|
35
|
+
'ThreadPool',
|
|
36
|
+
'ProcessPool',
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PoolFull(RuntimeError):
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Pool:
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
executor: PyExecutor,
|
|
48
|
+
max: int,
|
|
49
|
+
):
|
|
50
|
+
self.pool = executor
|
|
51
|
+
self.max = max
|
|
52
|
+
self.slots = BoundedSemaphore(max)
|
|
53
|
+
self.lock = Lock()
|
|
54
|
+
self.futures: Set[Future] = set()
|
|
55
|
+
self.closed = False
|
|
56
|
+
|
|
57
|
+
def submit(
|
|
58
|
+
self,
|
|
59
|
+
run: Type[Run],
|
|
60
|
+
body: Dict[str, Any],
|
|
61
|
+
callback: Optional[Callable[[Any], None]] = None,
|
|
62
|
+
error_callback: Optional[Callable[[BaseException], None]] = None,
|
|
63
|
+
) -> Future:
|
|
64
|
+
with self.lock:
|
|
65
|
+
if self.closed:
|
|
66
|
+
raise RuntimeError('Pool is closed')
|
|
67
|
+
if not self.slots.acquire(blocking=False):
|
|
68
|
+
raise PoolFull('Pool capacity is exhausted')
|
|
69
|
+
try:
|
|
70
|
+
future = self.pool.submit(
|
|
71
|
+
run(),
|
|
72
|
+
**body,
|
|
73
|
+
)
|
|
74
|
+
except BaseException:
|
|
75
|
+
self.slots.release()
|
|
76
|
+
raise
|
|
77
|
+
self.futures.add(future)
|
|
78
|
+
future.add_done_callback(lambda completed: self.complete(
|
|
79
|
+
completed,
|
|
80
|
+
callback,
|
|
81
|
+
error_callback,
|
|
82
|
+
))
|
|
83
|
+
return future
|
|
84
|
+
|
|
85
|
+
def complete(
|
|
86
|
+
self,
|
|
87
|
+
future: Future,
|
|
88
|
+
callback: Optional[Callable[[Any], None]] = None,
|
|
89
|
+
error_callback: Optional[Callable[[BaseException], None]] = None,
|
|
90
|
+
) -> None:
|
|
91
|
+
with self.lock:
|
|
92
|
+
self.futures.remove(future)
|
|
93
|
+
self.slots.release()
|
|
94
|
+
try:
|
|
95
|
+
result = future.result()
|
|
96
|
+
except BaseException as error:
|
|
97
|
+
if error_callback is not None:
|
|
98
|
+
error_callback(error)
|
|
99
|
+
else:
|
|
100
|
+
if callback is not None:
|
|
101
|
+
callback(result)
|
|
102
|
+
|
|
103
|
+
def waits(
|
|
104
|
+
self,
|
|
105
|
+
timeout: Optional[float] = None,
|
|
106
|
+
) -> None:
|
|
107
|
+
with self.lock:
|
|
108
|
+
tasks = tuple(self.futures)
|
|
109
|
+
if tasks and wait(
|
|
110
|
+
tasks,
|
|
111
|
+
timeout=timeout,
|
|
112
|
+
).not_done:
|
|
113
|
+
raise TimeoutError('Pool tasks did not complete in time')
|
|
114
|
+
|
|
115
|
+
def stop(self) -> None:
|
|
116
|
+
with self.lock:
|
|
117
|
+
self.closed = True
|
|
118
|
+
self.pool.shutdown(wait=True)
|
|
119
|
+
|
|
120
|
+
def shutdown(self) -> None:
|
|
121
|
+
self.stop()
|
|
122
|
+
|
|
123
|
+
def size(self) -> int:
|
|
124
|
+
return self.max
|
|
125
|
+
|
|
126
|
+
def count(self) -> int:
|
|
127
|
+
with self.lock:
|
|
128
|
+
return len(self.futures)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class PoolSetup(ABC):
|
|
132
|
+
@abstractmethod
|
|
133
|
+
def __call__(self) -> None:
|
|
134
|
+
raise NotImplementedError
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class PoolCleanup(ABC):
|
|
138
|
+
@abstractmethod
|
|
139
|
+
def __call__(self) -> None:
|
|
140
|
+
raise NotImplementedError
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def pool_hooks(
|
|
144
|
+
hooks: Optional[Union[Callable[[], None], Sequence[Callable[[], None]]]],
|
|
145
|
+
) -> Tuple[Callable[[], None], ...]:
|
|
146
|
+
result = () if hooks is None else tuple(hooks) if isinstance(
|
|
147
|
+
hooks,
|
|
148
|
+
Sequence,
|
|
149
|
+
) else (hooks,)
|
|
150
|
+
if not all(callable(hook) for hook in result):
|
|
151
|
+
raise TypeError('Pool hooks must be callable')
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def pool_size(max: Optional[int]) -> int:
|
|
156
|
+
if max is None:
|
|
157
|
+
return cpu_count() or 1
|
|
158
|
+
if isinstance(
|
|
159
|
+
max,
|
|
160
|
+
bool,
|
|
161
|
+
) or not isinstance(
|
|
162
|
+
max,
|
|
163
|
+
int,
|
|
164
|
+
) or max < 1:
|
|
165
|
+
raise ValueError('Pool size must be a positive integer')
|
|
166
|
+
return max
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class ThreadPool(Pool):
|
|
170
|
+
def __init__(
|
|
171
|
+
self,
|
|
172
|
+
max: Optional[int] = None,
|
|
173
|
+
setups: Optional[Union[PoolSetup, Sequence[PoolSetup]]] = None,
|
|
174
|
+
cleanups: Optional[Union[PoolCleanup, Sequence[PoolCleanup]]] = None,
|
|
175
|
+
):
|
|
176
|
+
size = pool_size(max)
|
|
177
|
+
super().__init__(
|
|
178
|
+
Executor(
|
|
179
|
+
size,
|
|
180
|
+
pool_hooks(setups),
|
|
181
|
+
pool_hooks(cleanups),
|
|
182
|
+
),
|
|
183
|
+
size,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class ProcessPool(Pool):
|
|
188
|
+
def __init__(
|
|
189
|
+
self,
|
|
190
|
+
max: Optional[int] = None,
|
|
191
|
+
setups: Optional[Union[PoolSetup, Sequence[PoolSetup]]] = None,
|
|
192
|
+
cleanups: Optional[Union[PoolCleanup, Sequence[PoolCleanup]]] = None,
|
|
193
|
+
):
|
|
194
|
+
size = pool_size(max)
|
|
195
|
+
super().__init__(
|
|
196
|
+
Executor(
|
|
197
|
+
size,
|
|
198
|
+
pool_hooks(setups),
|
|
199
|
+
pool_hooks(cleanups),
|
|
200
|
+
process=True,
|
|
201
|
+
),
|
|
202
|
+
size,
|
|
203
|
+
)
|