DefStream 0.1.8__22-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.
Files changed (56) hide show
  1. DefStream/Broker/Configuration.py +18 -0
  2. DefStream/Broker/Connection.py +141 -0
  3. DefStream/Broker/Consumer.py +59 -0
  4. DefStream/Broker/Errors.py +14 -0
  5. DefStream/Broker/Event.py +35 -0
  6. DefStream/Broker/Implements/__init__.py +1 -0
  7. DefStream/Broker/Policies.py +25 -0
  8. DefStream/Broker/Priority.py +26 -0
  9. DefStream/Broker/Protocol.py +45 -0
  10. DefStream/Broker/__init__.py +59 -0
  11. DefStream/Descriptor/DataObject.py +409 -0
  12. DefStream/Descriptor/Types.py +248 -0
  13. DefStream/Descriptor/Value.py +188 -0
  14. DefStream/Descriptor/__init__.py +172 -0
  15. DefStream/Inspector/Extension.py +69 -0
  16. DefStream/Inspector/Extensions/Console.html +373 -0
  17. DefStream/Inspector/Extensions/Console.py +385 -0
  18. DefStream/Inspector/Extensions/Dashboard.html +206 -0
  19. DefStream/Inspector/Extensions/Dashboard.py +114 -0
  20. DefStream/Inspector/Extensions/HealthCheck.py +92 -0
  21. DefStream/Inspector/Extensions/Metrics.py +281 -0
  22. DefStream/Inspector/Extensions/Specifications.py +160 -0
  23. DefStream/Inspector/Extensions/__init__.py +13 -0
  24. DefStream/Inspector/Inspector.py +216 -0
  25. DefStream/Inspector/Response.py +14 -0
  26. DefStream/Inspector/__init__.py +9 -0
  27. DefStream/Validator/Pattern.py +36 -0
  28. DefStream/Validator/Patterns/Array.py +30 -0
  29. DefStream/Validator/Patterns/Common.py +93 -0
  30. DefStream/Validator/Patterns/Compare.py +180 -0
  31. DefStream/Validator/Patterns/Condition.py +60 -0
  32. DefStream/Validator/Patterns/DataObject.py +85 -0
  33. DefStream/Validator/Patterns/DateTime.py +111 -0
  34. DefStream/Validator/Patterns/Number.py +43 -0
  35. DefStream/Validator/Patterns/Object.py +150 -0
  36. DefStream/Validator/Patterns/Size.py +110 -0
  37. DefStream/Validator/Patterns/String.py +85 -0
  38. DefStream/Validator/Patterns/Type.py +350 -0
  39. DefStream/Validator/Patterns/__init__.py +137 -0
  40. DefStream/Validator/Validate.py +29 -0
  41. DefStream/Validator/Validator.py +30 -0
  42. DefStream/Validator/__init__.py +11 -0
  43. DefStream/Worker/Executor.py +298 -0
  44. DefStream/Worker/Message.py +180 -0
  45. DefStream/Worker/Pool.py +203 -0
  46. DefStream/Worker/Run.py +238 -0
  47. DefStream/Worker/Specification.py +91 -0
  48. DefStream/Worker/Startup.py +135 -0
  49. DefStream/Worker/Worker.py +315 -0
  50. DefStream/Worker/__init__.py +47 -0
  51. DefStream/__init__.py +0 -0
  52. defstream-0.1.8.dist-info/METADATA +623 -0
  53. defstream-0.1.8.dist-info/RECORD +56 -0
  54. defstream-0.1.8.dist-info/WHEEL +6 -0
  55. defstream-0.1.8.dist-info/licenses/LICENSE +674 -0
  56. defstream-0.1.8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,238 @@
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 ..Broker.Priority import validate_priority
16
+ from .Specification import Specification
17
+
18
+ __all__ = (
19
+ 'Context',
20
+ 'RunSetup',
21
+ 'RunComplete',
22
+ 'RunError',
23
+ 'Run',
24
+ )
25
+
26
+
27
+ def priority_setting(
28
+ parameters: Any,
29
+ name: str,
30
+ ) -> Optional[int]:
31
+ if isinstance(
32
+ parameters,
33
+ Mapping,
34
+ ):
35
+ return parameters.get(name)
36
+ return getattr(
37
+ parameters,
38
+ name,
39
+ None,
40
+ )
41
+
42
+
43
+ class Context:
44
+ def __init__(
45
+ self,
46
+ *args,
47
+ **kwargs,
48
+ ):
49
+ self.args = args
50
+ self.kwargs = kwargs
51
+
52
+
53
+ class RunSetup(ABC):
54
+ @abstractmethod
55
+ def __call__(
56
+ self,
57
+ context: Context,
58
+ ) -> None:
59
+ raise NotImplementedError
60
+
61
+
62
+ class RunComplete(ABC):
63
+ @abstractmethod
64
+ def __call__(
65
+ self,
66
+ context: Context,
67
+ completion: Any,
68
+ ) -> None:
69
+ raise NotImplementedError
70
+
71
+
72
+ class RunError(ABC):
73
+ @abstractmethod
74
+ def __call__(
75
+ self,
76
+ context: Context,
77
+ error: Exception,
78
+ ) -> None:
79
+ raise NotImplementedError
80
+
81
+
82
+ class Run(ABC):
83
+ parameters: Any = None
84
+ on_error: Optional[EventError] = None
85
+ setups: Tuple[RunSetup, ...] = ()
86
+ completes: Tuple[RunComplete, ...] = ()
87
+ errors: Tuple[RunError, ...] = ()
88
+ request: Optional[Specification] = None
89
+ response: Optional[Specification] = None
90
+
91
+ def __init_subclass__(
92
+ cls,
93
+ *,
94
+ parameters: Any = None,
95
+ on_error: Optional[EventError] = None,
96
+ setups: Optional[Union[RunSetup, Sequence[RunSetup]]] = None,
97
+ completes: Optional[Union[RunComplete, Sequence[RunComplete]]] = None,
98
+ errors: Optional[Union[RunError, Sequence[RunError]]] = None,
99
+ request: Optional[Specification] = None,
100
+ response: Optional[Specification] = None,
101
+ **kwargs,
102
+ ):
103
+ super().__init_subclass__(**kwargs)
104
+ parents = tuple(base for base in cls.__mro__[1:] if issubclass(
105
+ base,
106
+ Run,
107
+ ))
108
+ if parameters is not None:
109
+ cls.parameters = parameters
110
+ for name in (
111
+ 'priority',
112
+ 'max_priority',
113
+ ):
114
+ if any(priority_setting(
115
+ base.parameters,
116
+ name,
117
+ ) is not None for base in parents) and priority_setting(
118
+ cls.parameters,
119
+ name,
120
+ ) is None:
121
+ raise ValueError('Inherited task ' + name + ' cannot be cleared')
122
+ maximum = priority_setting(
123
+ cls.parameters,
124
+ 'max_priority',
125
+ )
126
+ validate_priority(
127
+ maximum,
128
+ None,
129
+ minimum=1,
130
+ name='max_priority',
131
+ )
132
+ validate_priority(
133
+ priority_setting(
134
+ cls.parameters,
135
+ 'priority',
136
+ ),
137
+ maximum,
138
+ )
139
+ for name, specification in (
140
+ (
141
+ 'request',
142
+ request,
143
+ ),
144
+ (
145
+ 'response',
146
+ response,
147
+ ),
148
+ ):
149
+ if specification is not None:
150
+ if not isinstance(
151
+ specification,
152
+ Specification,
153
+ ):
154
+ raise TypeError(name + ' must be a Specification')
155
+ setattr(
156
+ cls,
157
+ name,
158
+ specification,
159
+ )
160
+ if on_error is not None:
161
+ if not callable(on_error):
162
+ raise TypeError('on_error must be a callable event error policy')
163
+ cls.on_error = on_error
164
+ if setups is not None:
165
+ cls.setups = tuple(setups) if isinstance(
166
+ setups,
167
+ Sequence,
168
+ ) else (setups,)
169
+ if completes is not None:
170
+ cls.completes = tuple(completes) if isinstance(
171
+ completes,
172
+ Sequence,
173
+ ) else (completes,)
174
+ if errors is not None:
175
+ cls.errors = tuple(errors) if isinstance(
176
+ errors,
177
+ Sequence,
178
+ ) else (errors,)
179
+ for hooks in (
180
+ cls.setups,
181
+ cls.completes,
182
+ cls.errors,
183
+ ):
184
+ if not all(callable(hook) for hook in hooks):
185
+ raise TypeError('Run hooks must be callable')
186
+ return
187
+
188
+ def __call__(
189
+ self,
190
+ *args,
191
+ **kwargs,
192
+ ) -> Any:
193
+ context = Context(
194
+ *args,
195
+ **kwargs,
196
+ )
197
+ for setup in self.setups:
198
+ setup(context)
199
+ try:
200
+ if self.request is not None:
201
+ if context.args:
202
+ raise TypeError('A specified task request requires keyword arguments')
203
+ body = self.request(context.kwargs)
204
+ if not isinstance(
205
+ body,
206
+ Mapping,
207
+ ) or not all(isinstance(
208
+ key,
209
+ str,
210
+ ) for key in body):
211
+ raise TypeError('A task request must remain a mapping with string keys')
212
+ context.kwargs = dict(body)
213
+ result = self.run(
214
+ *context.args,
215
+ **context.kwargs,
216
+ )
217
+ if self.response is not None:
218
+ result = self.response(result)
219
+ except Exception as error:
220
+ for handler in self.errors:
221
+ handler(
222
+ context,
223
+ error,
224
+ )
225
+ raise
226
+ for complete in self.completes:
227
+ complete(
228
+ context,
229
+ result,
230
+ )
231
+ return result
232
+
233
+ @abstractmethod
234
+ def run(
235
+ self,
236
+ **kwargs,
237
+ ) -> Any:
238
+ 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,135 @@
1
+ from collections.abc import Mapping
2
+ from os import getpid
3
+ from typing import (
4
+ Any,
5
+ TYPE_CHECKING,
6
+ )
7
+
8
+ if TYPE_CHECKING:
9
+ from .Worker import Worker
10
+
11
+ __all__ = (
12
+ 'startup',
13
+ )
14
+
15
+
16
+ LOGO = r"""
17
+ ▄▄▄▄▄▄ ▄▄ ▄▄▄▄▄▄▄
18
+ ███▀▀██▄ ██ █████▀▀▀ ██
19
+ ███ ███ ▄█▀█▄ ▀██▀ ▀████▄ ▀██▀▀ ████▄ ▄█▀█▄ ▀▀█▄ ███▄███▄
20
+ ███ ███ ██▄█▀ ██ ▀████ ██ ██ ▀▀ ██▄█▀ ▄█▀██ ██ ██ ██
21
+ ██████▀ ▀█▄▄▄ ██ ███████▀ ██ ██ ▀█▄▄▄ ▀█▄██ ██ ██ ██
22
+ """
23
+
24
+
25
+ def subscription_value(
26
+ parameters: Any,
27
+ name: str,
28
+ ) -> Any:
29
+ if isinstance(
30
+ parameters,
31
+ str,
32
+ ):
33
+ return parameters if name == 'queue' else None
34
+ if isinstance(
35
+ parameters,
36
+ Mapping,
37
+ ):
38
+ return parameters.get(name)
39
+ return getattr(
40
+ parameters,
41
+ name,
42
+ None,
43
+ )
44
+
45
+
46
+ def startup(worker: 'Worker') -> None:
47
+ broker = type(worker.broker)
48
+ broker_name = broker.__qualname__
49
+ for base in broker.__mro__:
50
+ if '.Broker.Implements.' in base.__module__:
51
+ broker_name = base.__module__.split('.Broker.Implements.')[1].split('.')[0]
52
+ break
53
+ pool = type(worker.pool)
54
+ conf = getattr(
55
+ worker.broker,
56
+ 'conf',
57
+ None,
58
+ )
59
+ host = getattr(
60
+ conf,
61
+ 'host',
62
+ None,
63
+ )
64
+ port = getattr(
65
+ conf,
66
+ 'port',
67
+ None,
68
+ )
69
+ if isinstance(
70
+ host,
71
+ str,
72
+ ):
73
+ address = f'[{host}]' if ':' in host and not host.startswith('[') else host
74
+ broker_name += f'://{address}'
75
+ if isinstance(
76
+ port,
77
+ (
78
+ str,
79
+ int,
80
+ ),
81
+ ):
82
+ broker_name += f':{port}'
83
+ vhost = getattr(
84
+ conf,
85
+ 'vhost',
86
+ None,
87
+ )
88
+ if isinstance(
89
+ vhost,
90
+ str,
91
+ ) and vhost:
92
+ broker_name += '/' + vhost.lstrip('/')
93
+ lines = [
94
+ LOGO.rstrip(),
95
+ '',
96
+ 'Starting Worker',
97
+ f' PID: {getpid()}',
98
+ f' Broker: {broker_name}',
99
+ ]
100
+ lines.extend((
101
+ f' Pool: {pool.__name__} (concurrency={worker.pool.size()})',
102
+ f' Tasks: {len(worker.tasks)} tasks',
103
+ ))
104
+ queues = []
105
+ for task in worker.tasks:
106
+ name = task.__qualname__
107
+ queue = subscription_value(
108
+ task.parameters,
109
+ 'queue',
110
+ )
111
+ lines.append(f' - {name}')
112
+ lines.append(f' queue={queue!r}' if queue is not None else ' queue=<adapter-defined>')
113
+ if queue is not None and queue not in queues:
114
+ queues.append(queue)
115
+ for option in (
116
+ 'group',
117
+ 'qos',
118
+ 'offset',
119
+ 'error',
120
+ ):
121
+ value = subscription_value(
122
+ task.parameters,
123
+ option,
124
+ )
125
+ if value is not None:
126
+ lines.append(f' {option}={value!r}')
127
+ policy = type(task.on_error).__name__ if task.on_error is not None else 'broker default'
128
+ lines.append(f' on_error={policy}')
129
+ lines.append(f' Queues: {len(queues)} queues')
130
+ lines.extend(f' - {queue!r}' for queue in queues)
131
+ lines.append('\n')
132
+ print(
133
+ '\n'.join(lines),
134
+ flush=True,
135
+ )