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
DefStream/Worker/Run.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
from abc import (
|
|
2
|
+
ABC,
|
|
3
|
+
abstractmethod,
|
|
4
|
+
)
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Optional,
|
|
9
|
+
Sequence,
|
|
10
|
+
Tuple,
|
|
11
|
+
Union,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from ..Broker.Event import EventError
|
|
15
|
+
from .Specification import Specification
|
|
16
|
+
|
|
17
|
+
__all__ = (
|
|
18
|
+
'Context',
|
|
19
|
+
'RunSetup',
|
|
20
|
+
'RunComplete',
|
|
21
|
+
'RunError',
|
|
22
|
+
'Run',
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Context:
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
*args,
|
|
30
|
+
**kwargs,
|
|
31
|
+
):
|
|
32
|
+
self.args = args
|
|
33
|
+
self.kwargs = kwargs
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class RunSetup(ABC):
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def __call__(
|
|
39
|
+
self,
|
|
40
|
+
context: Context,
|
|
41
|
+
) -> None:
|
|
42
|
+
raise NotImplementedError
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class RunComplete(ABC):
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def __call__(
|
|
48
|
+
self,
|
|
49
|
+
context: Context,
|
|
50
|
+
completion: Any,
|
|
51
|
+
) -> None:
|
|
52
|
+
raise NotImplementedError
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class RunError(ABC):
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def __call__(
|
|
58
|
+
self,
|
|
59
|
+
context: Context,
|
|
60
|
+
error: Exception,
|
|
61
|
+
) -> None:
|
|
62
|
+
raise NotImplementedError
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Run(ABC):
|
|
66
|
+
parameters: Any = None
|
|
67
|
+
on_error: Optional[EventError] = None
|
|
68
|
+
setups: Tuple[RunSetup, ...] = ()
|
|
69
|
+
completes: Tuple[RunComplete, ...] = ()
|
|
70
|
+
errors: Tuple[RunError, ...] = ()
|
|
71
|
+
request: Optional[Specification] = None
|
|
72
|
+
response: Optional[Specification] = None
|
|
73
|
+
|
|
74
|
+
def __init_subclass__(
|
|
75
|
+
cls,
|
|
76
|
+
*,
|
|
77
|
+
parameters: Any = None,
|
|
78
|
+
on_error: Optional[EventError] = None,
|
|
79
|
+
setups: Optional[Union[RunSetup, Sequence[RunSetup]]] = None,
|
|
80
|
+
completes: Optional[Union[RunComplete, Sequence[RunComplete]]] = None,
|
|
81
|
+
errors: Optional[Union[RunError, Sequence[RunError]]] = None,
|
|
82
|
+
request: Optional[Specification] = None,
|
|
83
|
+
response: Optional[Specification] = None,
|
|
84
|
+
**kwargs,
|
|
85
|
+
):
|
|
86
|
+
super().__init_subclass__(**kwargs)
|
|
87
|
+
if parameters is not None:
|
|
88
|
+
cls.parameters = parameters
|
|
89
|
+
for name, specification in (
|
|
90
|
+
(
|
|
91
|
+
'request',
|
|
92
|
+
request,
|
|
93
|
+
),
|
|
94
|
+
(
|
|
95
|
+
'response',
|
|
96
|
+
response,
|
|
97
|
+
),
|
|
98
|
+
):
|
|
99
|
+
if specification is not None:
|
|
100
|
+
if not isinstance(
|
|
101
|
+
specification,
|
|
102
|
+
Specification,
|
|
103
|
+
):
|
|
104
|
+
raise TypeError(name + ' must be a Specification')
|
|
105
|
+
setattr(
|
|
106
|
+
cls,
|
|
107
|
+
name,
|
|
108
|
+
specification,
|
|
109
|
+
)
|
|
110
|
+
if on_error is not None:
|
|
111
|
+
if not callable(on_error):
|
|
112
|
+
raise TypeError('on_error must be a callable event error policy')
|
|
113
|
+
cls.on_error = on_error
|
|
114
|
+
if setups is not None:
|
|
115
|
+
cls.setups = tuple(setups) if isinstance(
|
|
116
|
+
setups,
|
|
117
|
+
Sequence,
|
|
118
|
+
) else (setups,)
|
|
119
|
+
if completes is not None:
|
|
120
|
+
cls.completes = tuple(completes) if isinstance(
|
|
121
|
+
completes,
|
|
122
|
+
Sequence,
|
|
123
|
+
) else (completes,)
|
|
124
|
+
if errors is not None:
|
|
125
|
+
cls.errors = tuple(errors) if isinstance(
|
|
126
|
+
errors,
|
|
127
|
+
Sequence,
|
|
128
|
+
) else (errors,)
|
|
129
|
+
for hooks in (
|
|
130
|
+
cls.setups,
|
|
131
|
+
cls.completes,
|
|
132
|
+
cls.errors,
|
|
133
|
+
):
|
|
134
|
+
if not all(callable(hook) for hook in hooks):
|
|
135
|
+
raise TypeError('Run hooks must be callable')
|
|
136
|
+
|
|
137
|
+
def __call__(
|
|
138
|
+
self,
|
|
139
|
+
*args,
|
|
140
|
+
**kwargs,
|
|
141
|
+
) -> Any:
|
|
142
|
+
context = Context(
|
|
143
|
+
*args,
|
|
144
|
+
**kwargs,
|
|
145
|
+
)
|
|
146
|
+
for setup in self.setups:
|
|
147
|
+
setup(context)
|
|
148
|
+
try:
|
|
149
|
+
if self.request is not None:
|
|
150
|
+
if context.args:
|
|
151
|
+
raise TypeError('A specified task request requires keyword arguments')
|
|
152
|
+
body = self.request(context.kwargs)
|
|
153
|
+
if not isinstance(
|
|
154
|
+
body,
|
|
155
|
+
Mapping,
|
|
156
|
+
) or not all(isinstance(
|
|
157
|
+
key,
|
|
158
|
+
str,
|
|
159
|
+
) for key in body):
|
|
160
|
+
raise TypeError('A task request must remain a mapping with string keys')
|
|
161
|
+
context.kwargs = dict(body)
|
|
162
|
+
result = self.run(
|
|
163
|
+
*context.args,
|
|
164
|
+
**context.kwargs,
|
|
165
|
+
)
|
|
166
|
+
if self.response is not None:
|
|
167
|
+
result = self.response(result)
|
|
168
|
+
except Exception as error:
|
|
169
|
+
for handler in self.errors:
|
|
170
|
+
handler(
|
|
171
|
+
context,
|
|
172
|
+
error,
|
|
173
|
+
)
|
|
174
|
+
raise
|
|
175
|
+
for complete in self.completes:
|
|
176
|
+
complete(
|
|
177
|
+
context,
|
|
178
|
+
result,
|
|
179
|
+
)
|
|
180
|
+
return result
|
|
181
|
+
|
|
182
|
+
@abstractmethod
|
|
183
|
+
def run(
|
|
184
|
+
self,
|
|
185
|
+
**kwargs,
|
|
186
|
+
) -> Any:
|
|
187
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
from dataclasses import is_dataclass
|
|
4
|
+
from re import fullmatch
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
Callable,
|
|
8
|
+
Optional,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from ..Descriptor import (
|
|
12
|
+
DumpSchema,
|
|
13
|
+
Schema,
|
|
14
|
+
ToSchema,
|
|
15
|
+
)
|
|
16
|
+
from ..Descriptor.Value import (
|
|
17
|
+
Condition,
|
|
18
|
+
Descriptor,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = (
|
|
22
|
+
'Specification',
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Specification:
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
schema: Any = None,
|
|
30
|
+
validator: Optional[Callable[[Any], Any]] = None,
|
|
31
|
+
example: Any = ...,
|
|
32
|
+
description: Optional[str] = None,
|
|
33
|
+
format: str = 'application/json',
|
|
34
|
+
):
|
|
35
|
+
if not isinstance(
|
|
36
|
+
format,
|
|
37
|
+
str,
|
|
38
|
+
) or fullmatch(
|
|
39
|
+
r"[!#$%&'+.^_`|~0-9A-Za-z-]+/[!#$%&'+.^_`|~0-9A-Za-z-]+(?:[ \t]*;[^\r\n]+)?",
|
|
40
|
+
format,
|
|
41
|
+
) is None:
|
|
42
|
+
raise ValueError('Specification format must be a concrete media type')
|
|
43
|
+
if validator is not None and not callable(validator):
|
|
44
|
+
raise TypeError('A Specification validator must be callable')
|
|
45
|
+
if isinstance(
|
|
46
|
+
schema,
|
|
47
|
+
type,
|
|
48
|
+
) and is_dataclass(schema):
|
|
49
|
+
schema = ToSchema(schema)
|
|
50
|
+
if schema is not None and not isinstance(
|
|
51
|
+
schema,
|
|
52
|
+
(
|
|
53
|
+
Schema,
|
|
54
|
+
Descriptor,
|
|
55
|
+
Condition,
|
|
56
|
+
Mapping,
|
|
57
|
+
),
|
|
58
|
+
):
|
|
59
|
+
raise TypeError('A Descriptor schema, schema mapping or dataclass type is required')
|
|
60
|
+
self.schema = schema
|
|
61
|
+
self.validator = validator
|
|
62
|
+
self.example = example
|
|
63
|
+
self.description = description
|
|
64
|
+
self.format = format
|
|
65
|
+
self.document()
|
|
66
|
+
|
|
67
|
+
def __call__(
|
|
68
|
+
self,
|
|
69
|
+
value: Any,
|
|
70
|
+
) -> Any:
|
|
71
|
+
return self.validator(value) if self.validator is not None else value
|
|
72
|
+
|
|
73
|
+
def document(self) -> dict:
|
|
74
|
+
payload = {} if self.schema is None else DumpSchema(
|
|
75
|
+
self.schema if isinstance(
|
|
76
|
+
self.schema,
|
|
77
|
+
Schema,
|
|
78
|
+
) else Schema(
|
|
79
|
+
'Payload',
|
|
80
|
+
self.schema,
|
|
81
|
+
),
|
|
82
|
+
)
|
|
83
|
+
message = {
|
|
84
|
+
'contentType': self.format,
|
|
85
|
+
'payload': payload,
|
|
86
|
+
}
|
|
87
|
+
if self.description is not None:
|
|
88
|
+
message['description'] = self.description
|
|
89
|
+
if self.example is not ...:
|
|
90
|
+
message['examples'] = [{'payload': deepcopy(self.example)}]
|
|
91
|
+
return message
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
from abc import (
|
|
2
|
+
ABC,
|
|
3
|
+
abstractmethod,
|
|
4
|
+
)
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from logging import getLogger
|
|
7
|
+
from threading import Event as PyEvent
|
|
8
|
+
from typing import (
|
|
9
|
+
Any,
|
|
10
|
+
List,
|
|
11
|
+
Callable,
|
|
12
|
+
Optional,
|
|
13
|
+
Sequence,
|
|
14
|
+
Type,
|
|
15
|
+
Tuple,
|
|
16
|
+
Union,
|
|
17
|
+
Protocol,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from ..Broker import (
|
|
21
|
+
Connection,
|
|
22
|
+
GetConsumer,
|
|
23
|
+
Consumer,
|
|
24
|
+
EventHandler,
|
|
25
|
+
)
|
|
26
|
+
from ..Broker.Event import Event
|
|
27
|
+
from .Message import task_parameters
|
|
28
|
+
from ..Inspector import Inspector
|
|
29
|
+
from .Pool import (
|
|
30
|
+
Pool,
|
|
31
|
+
PoolFull,
|
|
32
|
+
)
|
|
33
|
+
from .Run import Run
|
|
34
|
+
from .Executor import run_cleanups
|
|
35
|
+
|
|
36
|
+
__all__ = (
|
|
37
|
+
'Worker',
|
|
38
|
+
'WorkerConnection',
|
|
39
|
+
'WorkerSetup',
|
|
40
|
+
'WorkerCleanup',
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
logger = getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class WorkerConnection(
|
|
47
|
+
Connection,
|
|
48
|
+
GetConsumer,
|
|
49
|
+
Protocol,
|
|
50
|
+
):
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class WorkerSetup(ABC):
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def __call__(
|
|
57
|
+
self,
|
|
58
|
+
worker: 'Worker',
|
|
59
|
+
) -> None:
|
|
60
|
+
raise NotImplementedError
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class WorkerCleanup(ABC):
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def __call__(
|
|
66
|
+
self,
|
|
67
|
+
worker: 'Worker',
|
|
68
|
+
) -> None:
|
|
69
|
+
raise NotImplementedError
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def worker_hooks(
|
|
73
|
+
hooks: Optional[Union[Callable[['Worker'], None], Sequence[Callable[['Worker'], None]]]],
|
|
74
|
+
) -> Tuple[Callable[['Worker'], None], ...]:
|
|
75
|
+
result = () if hooks is None else tuple(hooks) if isinstance(
|
|
76
|
+
hooks,
|
|
77
|
+
Sequence,
|
|
78
|
+
) else (hooks,)
|
|
79
|
+
if not all(callable(hook) for hook in result):
|
|
80
|
+
raise TypeError('Worker hooks must be callable')
|
|
81
|
+
return result
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Worker:
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
broker: WorkerConnection,
|
|
88
|
+
pool: Pool,
|
|
89
|
+
setups: Optional[Union[WorkerSetup, Sequence[WorkerSetup]]] = None,
|
|
90
|
+
cleanups: Optional[Union[WorkerCleanup, Sequence[WorkerCleanup]]] = None,
|
|
91
|
+
inspector: Optional[Inspector] = None,
|
|
92
|
+
):
|
|
93
|
+
self.broker = broker
|
|
94
|
+
self.pool = pool
|
|
95
|
+
self.tasks: List[Type[Run]] = []
|
|
96
|
+
self.consumer: Optional[Consumer] = None
|
|
97
|
+
self.started = False
|
|
98
|
+
self.running = PyEvent()
|
|
99
|
+
self.stopping = PyEvent()
|
|
100
|
+
self.finished = PyEvent()
|
|
101
|
+
self.pending = 0
|
|
102
|
+
self.setups = worker_hooks(setups)
|
|
103
|
+
self.cleanups = worker_hooks(cleanups)
|
|
104
|
+
self.inspector = inspector
|
|
105
|
+
|
|
106
|
+
def add(
|
|
107
|
+
self,
|
|
108
|
+
run: Type[Run],
|
|
109
|
+
) -> None:
|
|
110
|
+
if self.started:
|
|
111
|
+
raise RuntimeError('Cannot register runs after starting the worker')
|
|
112
|
+
task_parameters(run)
|
|
113
|
+
if run in self.tasks:
|
|
114
|
+
raise ValueError('Run is already registered')
|
|
115
|
+
self.tasks.append(run)
|
|
116
|
+
|
|
117
|
+
def run(self) -> None:
|
|
118
|
+
if self.started:
|
|
119
|
+
raise RuntimeError('Worker can only be started once')
|
|
120
|
+
if not self.tasks:
|
|
121
|
+
raise RuntimeError('Register at least one Run before starting')
|
|
122
|
+
self.started = True
|
|
123
|
+
try:
|
|
124
|
+
if self.inspector is not None:
|
|
125
|
+
self.inspector.start(self)
|
|
126
|
+
self.broker.connect()
|
|
127
|
+
for setup in self.setups:
|
|
128
|
+
setup(self)
|
|
129
|
+
self.consumer = self.broker.consumer()
|
|
130
|
+
for run in self.tasks:
|
|
131
|
+
self.consumer.subs(
|
|
132
|
+
run.parameters,
|
|
133
|
+
Handler(
|
|
134
|
+
self,
|
|
135
|
+
run,
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
if not self.stopping.is_set():
|
|
139
|
+
self.running.set()
|
|
140
|
+
self.consumer.run()
|
|
141
|
+
finally:
|
|
142
|
+
self.running.clear()
|
|
143
|
+
self.finished.set()
|
|
144
|
+
if self.pending:
|
|
145
|
+
logger.error(
|
|
146
|
+
'Consumer exited with %s unconfirmed deliveries',
|
|
147
|
+
self.pending,
|
|
148
|
+
)
|
|
149
|
+
try:
|
|
150
|
+
self.pool.stop()
|
|
151
|
+
finally:
|
|
152
|
+
try:
|
|
153
|
+
self.cleanup()
|
|
154
|
+
finally:
|
|
155
|
+
try:
|
|
156
|
+
if self.consumer is not None:
|
|
157
|
+
self.consumer.close()
|
|
158
|
+
finally:
|
|
159
|
+
try:
|
|
160
|
+
self.broker.close()
|
|
161
|
+
finally:
|
|
162
|
+
if self.inspector is not None and self.inspector.worker is self:
|
|
163
|
+
self.inspector.stop()
|
|
164
|
+
|
|
165
|
+
def cleanup(self) -> None:
|
|
166
|
+
run_cleanups(
|
|
167
|
+
self.cleanups,
|
|
168
|
+
self,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def stop(self) -> None:
|
|
172
|
+
self.stopping.set()
|
|
173
|
+
if self.consumer is not None and not self.finished.is_set():
|
|
174
|
+
self.consumer.call(self.try_stop)
|
|
175
|
+
|
|
176
|
+
def try_stop(self) -> None:
|
|
177
|
+
if self.stopping.is_set() and self.pending == 0 and self.consumer is not None:
|
|
178
|
+
self.consumer.stop()
|
|
179
|
+
|
|
180
|
+
def receive(
|
|
181
|
+
self,
|
|
182
|
+
run: Type[Run],
|
|
183
|
+
event: Event,
|
|
184
|
+
) -> None:
|
|
185
|
+
if self.stopping.is_set():
|
|
186
|
+
event.nack()
|
|
187
|
+
return
|
|
188
|
+
if not isinstance(
|
|
189
|
+
event.body,
|
|
190
|
+
Mapping,
|
|
191
|
+
):
|
|
192
|
+
self.failed(
|
|
193
|
+
run,
|
|
194
|
+
event,
|
|
195
|
+
TypeError('Run message body must be a mapping'),
|
|
196
|
+
)
|
|
197
|
+
return
|
|
198
|
+
self.pending += 1
|
|
199
|
+
consumer = self.consumer
|
|
200
|
+
if consumer is None:
|
|
201
|
+
self.pending -= 1
|
|
202
|
+
raise RuntimeError('Worker consumer is not initialized')
|
|
203
|
+
inspector = self.inspector
|
|
204
|
+
task = run.__module__ + '.' + run.__qualname__
|
|
205
|
+
contexts = inspector.begin(task) if inspector is not None else ()
|
|
206
|
+
|
|
207
|
+
def schedule(
|
|
208
|
+
error: Optional[BaseException] = None,
|
|
209
|
+
result: Any = None,
|
|
210
|
+
) -> None:
|
|
211
|
+
if inspector is not None:
|
|
212
|
+
inspector.end(
|
|
213
|
+
task,
|
|
214
|
+
contexts,
|
|
215
|
+
error is None,
|
|
216
|
+
)
|
|
217
|
+
if self.finished.is_set():
|
|
218
|
+
logger.error(
|
|
219
|
+
'Consumer exited before Run %s delivery could be confirmed',
|
|
220
|
+
run.__name__,
|
|
221
|
+
)
|
|
222
|
+
return
|
|
223
|
+
consumer.call(lambda: self.complete(
|
|
224
|
+
run,
|
|
225
|
+
event,
|
|
226
|
+
error,
|
|
227
|
+
result,
|
|
228
|
+
))
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
self.pool.submit(
|
|
232
|
+
run,
|
|
233
|
+
dict(event.body),
|
|
234
|
+
callback=lambda result: schedule(result=result),
|
|
235
|
+
error_callback=schedule,
|
|
236
|
+
)
|
|
237
|
+
except PoolFull:
|
|
238
|
+
if inspector is not None:
|
|
239
|
+
inspector.end(
|
|
240
|
+
task,
|
|
241
|
+
contexts,
|
|
242
|
+
None,
|
|
243
|
+
)
|
|
244
|
+
self.pending -= 1
|
|
245
|
+
event.nack()
|
|
246
|
+
return
|
|
247
|
+
except Exception:
|
|
248
|
+
if inspector is not None:
|
|
249
|
+
inspector.end(
|
|
250
|
+
task,
|
|
251
|
+
contexts,
|
|
252
|
+
None,
|
|
253
|
+
)
|
|
254
|
+
self.pending -= 1
|
|
255
|
+
raise
|
|
256
|
+
|
|
257
|
+
def complete(
|
|
258
|
+
self,
|
|
259
|
+
run: Type[Run],
|
|
260
|
+
event: Event,
|
|
261
|
+
error: Optional[BaseException] = None,
|
|
262
|
+
result: Any = None,
|
|
263
|
+
) -> None:
|
|
264
|
+
try:
|
|
265
|
+
if error is not None:
|
|
266
|
+
self.failed(
|
|
267
|
+
run,
|
|
268
|
+
event,
|
|
269
|
+
error,
|
|
270
|
+
)
|
|
271
|
+
else:
|
|
272
|
+
if self.consumer is None:
|
|
273
|
+
raise RuntimeError('Worker consumer is not initialized')
|
|
274
|
+
self.consumer.complete(
|
|
275
|
+
event,
|
|
276
|
+
result,
|
|
277
|
+
)
|
|
278
|
+
finally:
|
|
279
|
+
self.pending -= 1
|
|
280
|
+
self.try_stop()
|
|
281
|
+
|
|
282
|
+
def failed(
|
|
283
|
+
self,
|
|
284
|
+
run: Type[Run],
|
|
285
|
+
event: Event,
|
|
286
|
+
error: BaseException,
|
|
287
|
+
) -> None:
|
|
288
|
+
logger.error(
|
|
289
|
+
'Run %s failed',
|
|
290
|
+
run.__name__,
|
|
291
|
+
exc_info=(
|
|
292
|
+
type(error),
|
|
293
|
+
error,
|
|
294
|
+
error.__traceback__,
|
|
295
|
+
),
|
|
296
|
+
)
|
|
297
|
+
if self.consumer is None:
|
|
298
|
+
raise RuntimeError('Worker consumer is not initialized')
|
|
299
|
+
self.consumer.fail(
|
|
300
|
+
event,
|
|
301
|
+
run.on_error,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class Handler(EventHandler):
|
|
306
|
+
def __init__(
|
|
307
|
+
self,
|
|
308
|
+
worker: Worker,
|
|
309
|
+
run: Type[Run],
|
|
310
|
+
):
|
|
311
|
+
self.worker = worker
|
|
312
|
+
self.run = run
|
|
313
|
+
|
|
314
|
+
def __call__(
|
|
315
|
+
self,
|
|
316
|
+
event: Event,
|
|
317
|
+
) -> None:
|
|
318
|
+
self.worker.receive(
|
|
319
|
+
self.run,
|
|
320
|
+
event,
|
|
321
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from .Worker import (
|
|
2
|
+
Worker,
|
|
3
|
+
WorkerConnection,
|
|
4
|
+
WorkerSetup,
|
|
5
|
+
WorkerCleanup,
|
|
6
|
+
)
|
|
7
|
+
from .Message import (
|
|
8
|
+
declare,
|
|
9
|
+
send,
|
|
10
|
+
request,
|
|
11
|
+
)
|
|
12
|
+
from .Run import (
|
|
13
|
+
Run,
|
|
14
|
+
Context,
|
|
15
|
+
RunSetup,
|
|
16
|
+
RunComplete,
|
|
17
|
+
RunError,
|
|
18
|
+
)
|
|
19
|
+
from .Specification import Specification
|
|
20
|
+
from .Pool import (
|
|
21
|
+
Pool,
|
|
22
|
+
PoolFull,
|
|
23
|
+
PoolSetup,
|
|
24
|
+
PoolCleanup,
|
|
25
|
+
ThreadPool,
|
|
26
|
+
ProcessPool,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = (
|
|
30
|
+
'Worker',
|
|
31
|
+
'declare',
|
|
32
|
+
'send',
|
|
33
|
+
'request',
|
|
34
|
+
'WorkerConnection',
|
|
35
|
+
'WorkerSetup',
|
|
36
|
+
'WorkerCleanup',
|
|
37
|
+
'Run',
|
|
38
|
+
'Specification',
|
|
39
|
+
'Pool',
|
|
40
|
+
'PoolFull',
|
|
41
|
+
'PoolSetup',
|
|
42
|
+
'PoolCleanup',
|
|
43
|
+
'ThreadPool',
|
|
44
|
+
'ProcessPool',
|
|
45
|
+
'Context',
|
|
46
|
+
'RunSetup',
|
|
47
|
+
'RunComplete',
|
|
48
|
+
'RunError',
|
|
49
|
+
)
|
DefStream/__init__.py
ADDED
|
File without changes
|