haiway 0.5.3__tar.gz → 0.6.1__tar.gz
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.
- {haiway-0.5.3/src/haiway.egg-info → haiway-0.6.1}/PKG-INFO +3 -2
- {haiway-0.5.3 → haiway-0.6.1}/pyproject.toml +3 -2
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/context/access.py +1 -5
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/helpers/tracing.py +4 -4
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/state/attributes.py +6 -1
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/state/structure.py +4 -3
- haiway-0.6.1/src/haiway/state/validation.py +297 -0
- {haiway-0.5.3 → haiway-0.6.1/src/haiway.egg-info}/PKG-INFO +3 -2
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway.egg-info/requires.txt +2 -1
- {haiway-0.5.3 → haiway-0.6.1}/tests/test_async_queue.py +2 -1
- {haiway-0.5.3 → haiway-0.6.1}/tests/test_auto_retry.py +2 -1
- {haiway-0.5.3 → haiway-0.6.1}/tests/test_cache.py +2 -1
- {haiway-0.5.3 → haiway-0.6.1}/tests/test_context.py +2 -1
- {haiway-0.5.3 → haiway-0.6.1}/tests/test_state.py +17 -9
- {haiway-0.5.3 → haiway-0.6.1}/tests/test_streaming.py +2 -1
- {haiway-0.5.3 → haiway-0.6.1}/tests/test_timeout.py +2 -1
- haiway-0.5.3/src/haiway/state/validation.py +0 -149
- {haiway-0.5.3 → haiway-0.6.1}/LICENSE +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/README.md +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/setup.cfg +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/__init__.py +11 -11
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/context/__init__.py +1 -1
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/context/disposables.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/context/metrics.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/context/state.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/context/tasks.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/context/types.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/helpers/__init__.py +1 -1
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/helpers/asynchrony.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/helpers/caching.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/helpers/retries.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/helpers/throttling.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/helpers/timeouted.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/py.typed +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/state/__init__.py +1 -1
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/types/__init__.py +2 -2
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/types/frozen.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/types/missing.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/utils/__init__.py +1 -1
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/utils/always.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/utils/env.py +1 -1
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/utils/immutable.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/utils/logs.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/utils/mimic.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/utils/noop.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway/utils/queue.py +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway.egg-info/SOURCES.txt +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway.egg-info/dependency_links.txt +0 -0
- {haiway-0.5.3 → haiway-0.6.1}/src/haiway.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: haiway
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.1
|
4
4
|
Summary: Framework for dependency injection and state management within structured concurrency model.
|
5
5
|
Maintainer-email: Kacper Kaliński <kacper.kalinski@miquido.com>
|
6
6
|
License: MIT License
|
@@ -36,7 +36,8 @@ Requires-Python: >=3.12
|
|
36
36
|
Description-Content-Type: text/markdown
|
37
37
|
License-File: LICENSE
|
38
38
|
Provides-Extra: dev
|
39
|
-
Requires-Dist:
|
39
|
+
Requires-Dist: haiway; extra == "dev"
|
40
|
+
Requires-Dist: ruff~=0.8.0; extra == "dev"
|
40
41
|
Requires-Dist: pyright~=1.1; extra == "dev"
|
41
42
|
Requires-Dist: bandit~=1.7; extra == "dev"
|
42
43
|
Requires-Dist: pytest~=7.4; extra == "dev"
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
5
5
|
[project]
|
6
6
|
name = "haiway"
|
7
7
|
description = "Framework for dependency injection and state management within structured concurrency model."
|
8
|
-
version = "0.
|
8
|
+
version = "0.6.1"
|
9
9
|
readme = "README.md"
|
10
10
|
maintainers = [
|
11
11
|
{ name = "Kacper Kaliński", email = "kacper.kalinski@miquido.com" },
|
@@ -28,7 +28,8 @@ Repository = "https://github.com/miquido/haiway.git"
|
|
28
28
|
|
29
29
|
[project.optional-dependencies]
|
30
30
|
dev = [
|
31
|
-
"
|
31
|
+
"haiway",
|
32
|
+
"ruff~=0.8.0",
|
32
33
|
"pyright~=1.1",
|
33
34
|
"bandit~=1.7",
|
34
35
|
"pytest~=7.4",
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from asyncio import iscoroutinefunction
|
2
|
-
from collections.abc import Callable, Coroutine
|
2
|
+
from collections.abc import Callable, Coroutine, Mapping, Sequence
|
3
3
|
from typing import Any, Self, cast
|
4
4
|
|
5
5
|
from haiway.context import ctx
|
@@ -8,9 +8,9 @@ from haiway.types import MISSING, Missing
|
|
8
8
|
from haiway.utils import mimic_function
|
9
9
|
|
10
10
|
__all__ = [
|
11
|
-
"traced",
|
12
11
|
"ArgumentsTrace",
|
13
12
|
"ResultTrace",
|
13
|
+
"traced",
|
14
14
|
]
|
15
15
|
|
16
16
|
|
@@ -33,8 +33,8 @@ class ArgumentsTrace(State):
|
|
33
33
|
kwargs=MISSING,
|
34
34
|
)
|
35
35
|
|
36
|
-
args:
|
37
|
-
kwargs:
|
36
|
+
args: Sequence[Any] | Missing
|
37
|
+
kwargs: Mapping[str, Any] | Missing
|
38
38
|
|
39
39
|
|
40
40
|
class ResultTrace(State):
|
@@ -17,8 +17,8 @@ from typing import (
|
|
17
17
|
)
|
18
18
|
|
19
19
|
__all__ = [
|
20
|
-
"attribute_annotations",
|
21
20
|
"AttributeAnnotation",
|
21
|
+
"attribute_annotations",
|
22
22
|
]
|
23
23
|
|
24
24
|
|
@@ -42,6 +42,11 @@ class AttributeAnnotation:
|
|
42
42
|
and self.arguments == other.arguments
|
43
43
|
)
|
44
44
|
|
45
|
+
def __str__(self) -> str:
|
46
|
+
return f"{getattr(self.origin, "__name__", str(self.origin))}" + (
|
47
|
+
("[" + ", ".join(str(arg) for arg in self.arguments) + "]") if self.arguments else ""
|
48
|
+
)
|
49
|
+
|
45
50
|
|
46
51
|
def attribute_annotations(
|
47
52
|
cls: type[Any],
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from collections.abc import Callable
|
2
2
|
from copy import deepcopy
|
3
|
-
from types import GenericAlias
|
3
|
+
from types import EllipsisType, GenericAlias
|
4
4
|
from typing import (
|
5
5
|
Any,
|
6
6
|
ClassVar,
|
@@ -15,7 +15,7 @@ from typing import (
|
|
15
15
|
from weakref import WeakValueDictionary
|
16
16
|
|
17
17
|
from haiway.state.attributes import AttributeAnnotation, attribute_annotations
|
18
|
-
from haiway.state.validation import
|
18
|
+
from haiway.state.validation import attribute_validator
|
19
19
|
from haiway.types import MISSING, Missing, not_missing
|
20
20
|
|
21
21
|
__all__ = [
|
@@ -80,7 +80,7 @@ class StateMeta(type):
|
|
80
80
|
attributes[key] = StateAttribute(
|
81
81
|
annotation=annotation,
|
82
82
|
default=getattr(state_type, key, MISSING),
|
83
|
-
validator=
|
83
|
+
validator=attribute_validator(annotation),
|
84
84
|
)
|
85
85
|
|
86
86
|
state_type.__ATTRIBUTES__ = attributes # pyright: ignore[reportAttributeAccessIssue]
|
@@ -104,6 +104,7 @@ class State(metaclass=StateMeta):
|
|
104
104
|
Base class for immutable data structures.
|
105
105
|
"""
|
106
106
|
|
107
|
+
__IMMUTABLE__: ClassVar[EllipsisType] = ...
|
107
108
|
__ATTRIBUTES__: ClassVar[dict[str, StateAttribute[Any]]]
|
108
109
|
|
109
110
|
def __class_getitem__(
|
@@ -0,0 +1,297 @@
|
|
1
|
+
from collections.abc import Callable, Mapping, Sequence, Set
|
2
|
+
from enum import Enum
|
3
|
+
from types import MappingProxyType, NoneType, UnionType
|
4
|
+
from typing import Any, Literal, Protocol, Union
|
5
|
+
from uuid import UUID
|
6
|
+
|
7
|
+
from haiway.state.attributes import AttributeAnnotation
|
8
|
+
from haiway.types import MISSING, Missing
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
"attribute_validator",
|
12
|
+
]
|
13
|
+
|
14
|
+
|
15
|
+
def attribute_validator(
|
16
|
+
annotation: AttributeAnnotation,
|
17
|
+
/,
|
18
|
+
) -> Callable[[Any], Any]:
|
19
|
+
if validator := VALIDATORS.get(annotation.origin):
|
20
|
+
return validator(annotation)
|
21
|
+
|
22
|
+
elif hasattr(annotation.origin, "__IMMUTABLE__"):
|
23
|
+
return _prepare_validator_of_type(annotation)
|
24
|
+
|
25
|
+
elif issubclass(annotation.origin, Protocol):
|
26
|
+
return _prepare_validator_of_type(annotation)
|
27
|
+
|
28
|
+
elif issubclass(annotation.origin, Enum):
|
29
|
+
return _prepare_validator_of_type(annotation)
|
30
|
+
|
31
|
+
else:
|
32
|
+
raise TypeError(f"Unsupported type annotation: {annotation}")
|
33
|
+
|
34
|
+
|
35
|
+
def _prepare_validator_of_any(
|
36
|
+
annotation: AttributeAnnotation,
|
37
|
+
/,
|
38
|
+
) -> Callable[[Any], Any]:
|
39
|
+
def validator(
|
40
|
+
value: Any,
|
41
|
+
) -> Any:
|
42
|
+
return value # any is always valid
|
43
|
+
|
44
|
+
return validator
|
45
|
+
|
46
|
+
|
47
|
+
def _prepare_validator_of_none(
|
48
|
+
annotation: AttributeAnnotation,
|
49
|
+
/,
|
50
|
+
) -> Callable[[Any], Any]:
|
51
|
+
def validator(
|
52
|
+
value: Any,
|
53
|
+
) -> Any:
|
54
|
+
if value is None:
|
55
|
+
return value
|
56
|
+
|
57
|
+
else:
|
58
|
+
raise TypeError(f"'{value}' is not matching expected type of 'None'")
|
59
|
+
|
60
|
+
return validator
|
61
|
+
|
62
|
+
|
63
|
+
def _prepare_validator_of_missing(
|
64
|
+
annotation: AttributeAnnotation,
|
65
|
+
/,
|
66
|
+
) -> Callable[[Any], Any]:
|
67
|
+
def validator(
|
68
|
+
value: Any,
|
69
|
+
) -> Any:
|
70
|
+
if value is MISSING:
|
71
|
+
return value
|
72
|
+
|
73
|
+
else:
|
74
|
+
raise TypeError(f"'{value}' is not matching expected type of 'Missing'")
|
75
|
+
|
76
|
+
return validator
|
77
|
+
|
78
|
+
|
79
|
+
def _prepare_validator_of_literal(
|
80
|
+
annotation: AttributeAnnotation,
|
81
|
+
/,
|
82
|
+
) -> Callable[[Any], Any]:
|
83
|
+
elements: list[Any] = annotation.arguments
|
84
|
+
formatted_type: str = str(annotation)
|
85
|
+
|
86
|
+
def validator(
|
87
|
+
value: Any,
|
88
|
+
) -> Any:
|
89
|
+
if value in elements:
|
90
|
+
return value
|
91
|
+
|
92
|
+
else:
|
93
|
+
raise ValueError(f"'{value}' is not matching expected values of '{formatted_type}'")
|
94
|
+
|
95
|
+
return validator
|
96
|
+
|
97
|
+
|
98
|
+
def _prepare_validator_of_type(
|
99
|
+
annotation: AttributeAnnotation,
|
100
|
+
/,
|
101
|
+
) -> Callable[[Any], Any]:
|
102
|
+
validated_type: type[Any] = annotation.origin
|
103
|
+
formatted_type: str = str(annotation)
|
104
|
+
|
105
|
+
def type_validator(
|
106
|
+
value: Any,
|
107
|
+
) -> Any:
|
108
|
+
match value:
|
109
|
+
case value if isinstance(value, validated_type):
|
110
|
+
return value
|
111
|
+
|
112
|
+
case _:
|
113
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
114
|
+
|
115
|
+
return type_validator
|
116
|
+
|
117
|
+
|
118
|
+
def _prepare_validator_of_set(
|
119
|
+
annotation: AttributeAnnotation,
|
120
|
+
/,
|
121
|
+
) -> Callable[[Any], Any]:
|
122
|
+
element_validator: Callable[[Any], Any] = attribute_validator(annotation.arguments[0])
|
123
|
+
formatted_type: str = str(annotation)
|
124
|
+
|
125
|
+
def validator(
|
126
|
+
value: Any,
|
127
|
+
) -> Any:
|
128
|
+
if isinstance(value, Set):
|
129
|
+
return frozenset(element_validator(element) for element in value) # pyright: ignore[reportUnknownVariableType]
|
130
|
+
|
131
|
+
else:
|
132
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
133
|
+
|
134
|
+
return validator
|
135
|
+
|
136
|
+
|
137
|
+
def _prepare_validator_of_sequence(
|
138
|
+
annotation: AttributeAnnotation,
|
139
|
+
/,
|
140
|
+
) -> Callable[[Any], Any]:
|
141
|
+
element_validator: Callable[[Any], Any] = attribute_validator(annotation.arguments[0])
|
142
|
+
formatted_type: str = str(annotation)
|
143
|
+
|
144
|
+
def validator(
|
145
|
+
value: Any,
|
146
|
+
) -> Any:
|
147
|
+
match value:
|
148
|
+
case [*elements]:
|
149
|
+
return tuple(element_validator(element) for element in elements)
|
150
|
+
|
151
|
+
case _:
|
152
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
153
|
+
|
154
|
+
return validator
|
155
|
+
|
156
|
+
|
157
|
+
def _prepare_validator_of_mapping(
|
158
|
+
annotation: AttributeAnnotation,
|
159
|
+
/,
|
160
|
+
) -> Callable[[Any], Any]:
|
161
|
+
key_validator: Callable[[Any], Any] = attribute_validator(annotation.arguments[0])
|
162
|
+
value_validator: Callable[[Any], Any] = attribute_validator(annotation.arguments[1])
|
163
|
+
formatted_type: str = str(annotation)
|
164
|
+
|
165
|
+
def validator(
|
166
|
+
value: Any,
|
167
|
+
) -> Any:
|
168
|
+
match value:
|
169
|
+
case {**elements}:
|
170
|
+
return MappingProxyType(
|
171
|
+
{key_validator(key): value_validator(value) for key, value in elements}
|
172
|
+
)
|
173
|
+
|
174
|
+
case _:
|
175
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
176
|
+
|
177
|
+
return validator
|
178
|
+
|
179
|
+
|
180
|
+
def _prepare_validator_of_tuple(
|
181
|
+
annotation: AttributeAnnotation,
|
182
|
+
/,
|
183
|
+
) -> Callable[[Any], Any]:
|
184
|
+
if annotation.arguments[-1].origin == Ellipsis:
|
185
|
+
element_validator: Callable[[Any], Any] = attribute_validator(annotation.arguments[0])
|
186
|
+
formatted_type: str = str(annotation)
|
187
|
+
|
188
|
+
def validator(
|
189
|
+
value: Any,
|
190
|
+
) -> Any:
|
191
|
+
match value:
|
192
|
+
case [*elements]:
|
193
|
+
return tuple(element_validator(element) for element in elements)
|
194
|
+
|
195
|
+
case _:
|
196
|
+
raise TypeError(
|
197
|
+
f"'{value}' is not matching expected type of '{formatted_type}'"
|
198
|
+
)
|
199
|
+
|
200
|
+
return validator
|
201
|
+
|
202
|
+
else:
|
203
|
+
element_validators: list[Callable[[Any], Any]] = [
|
204
|
+
attribute_validator(alternative) for alternative in annotation.arguments
|
205
|
+
]
|
206
|
+
elements_count: int = len(element_validators)
|
207
|
+
formatted_type: str = str(annotation)
|
208
|
+
|
209
|
+
def validator(
|
210
|
+
value: Any,
|
211
|
+
) -> Any:
|
212
|
+
match value:
|
213
|
+
case [*elements]:
|
214
|
+
if len(elements) != elements_count:
|
215
|
+
raise ValueError(
|
216
|
+
f"'{value}' is not matching expected type of '{formatted_type}'"
|
217
|
+
)
|
218
|
+
|
219
|
+
return tuple(
|
220
|
+
element_validators[idx](value) for idx, value in enumerate(elements)
|
221
|
+
)
|
222
|
+
|
223
|
+
case _:
|
224
|
+
raise TypeError(
|
225
|
+
f"'{value}' is not matching expected type of '{formatted_type}'"
|
226
|
+
)
|
227
|
+
|
228
|
+
return validator
|
229
|
+
|
230
|
+
|
231
|
+
def _prepare_validator_of_union(
|
232
|
+
annotation: AttributeAnnotation,
|
233
|
+
/,
|
234
|
+
) -> Callable[[Any], Any]:
|
235
|
+
validators: list[Callable[[Any], Any]] = [
|
236
|
+
attribute_validator(alternative) for alternative in annotation.arguments
|
237
|
+
]
|
238
|
+
formatted_type: str = str(annotation)
|
239
|
+
|
240
|
+
def validator(
|
241
|
+
value: Any,
|
242
|
+
) -> Any:
|
243
|
+
errors: list[Exception] = []
|
244
|
+
for validator in validators:
|
245
|
+
try:
|
246
|
+
return validator(value)
|
247
|
+
|
248
|
+
except Exception as exc:
|
249
|
+
errors.append(exc)
|
250
|
+
|
251
|
+
raise ExceptionGroup(
|
252
|
+
f"'{value}' is not matching expected type of '{formatted_type}'",
|
253
|
+
errors,
|
254
|
+
)
|
255
|
+
|
256
|
+
return validator
|
257
|
+
|
258
|
+
|
259
|
+
def _prepare_validator_of_callable(
|
260
|
+
annotation: AttributeAnnotation,
|
261
|
+
/,
|
262
|
+
) -> Callable[[Any], Any]:
|
263
|
+
formatted_type: str = str(annotation)
|
264
|
+
|
265
|
+
def validator(
|
266
|
+
value: Any,
|
267
|
+
) -> Any:
|
268
|
+
if callable(value):
|
269
|
+
return value
|
270
|
+
|
271
|
+
else:
|
272
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
273
|
+
|
274
|
+
return validator
|
275
|
+
|
276
|
+
|
277
|
+
VALIDATORS: Mapping[Any, Callable[[AttributeAnnotation], Callable[[Any], Any]]] = {
|
278
|
+
Any: _prepare_validator_of_any,
|
279
|
+
NoneType: _prepare_validator_of_none,
|
280
|
+
Missing: _prepare_validator_of_missing,
|
281
|
+
type: _prepare_validator_of_type,
|
282
|
+
bool: _prepare_validator_of_type,
|
283
|
+
int: _prepare_validator_of_type,
|
284
|
+
float: _prepare_validator_of_type,
|
285
|
+
bytes: _prepare_validator_of_type,
|
286
|
+
str: _prepare_validator_of_type,
|
287
|
+
tuple: _prepare_validator_of_tuple,
|
288
|
+
frozenset: _prepare_validator_of_set,
|
289
|
+
Literal: _prepare_validator_of_literal,
|
290
|
+
Set: _prepare_validator_of_set,
|
291
|
+
Sequence: _prepare_validator_of_sequence,
|
292
|
+
Mapping: _prepare_validator_of_mapping,
|
293
|
+
UUID: _prepare_validator_of_type,
|
294
|
+
Union: _prepare_validator_of_union,
|
295
|
+
UnionType: _prepare_validator_of_union,
|
296
|
+
Callable: _prepare_validator_of_callable,
|
297
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: haiway
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.1
|
4
4
|
Summary: Framework for dependency injection and state management within structured concurrency model.
|
5
5
|
Maintainer-email: Kacper Kaliński <kacper.kalinski@miquido.com>
|
6
6
|
License: MIT License
|
@@ -36,7 +36,8 @@ Requires-Python: >=3.12
|
|
36
36
|
Description-Content-Type: text/markdown
|
37
37
|
License-File: LICENSE
|
38
38
|
Provides-Extra: dev
|
39
|
-
Requires-Dist:
|
39
|
+
Requires-Dist: haiway; extra == "dev"
|
40
|
+
Requires-Dist: ruff~=0.8.0; extra == "dev"
|
40
41
|
Requires-Dist: pyright~=1.1; extra == "dev"
|
41
42
|
Requires-Dist: bandit~=1.7; extra == "dev"
|
42
43
|
Requires-Dist: pytest~=7.4; extra == "dev"
|
@@ -2,9 +2,10 @@ from asyncio import CancelledError, Task, sleep
|
|
2
2
|
from collections.abc import Callable, Generator
|
3
3
|
from time import sleep as sync_sleep
|
4
4
|
|
5
|
-
from haiway import cache
|
6
5
|
from pytest import fixture, mark, raises
|
7
6
|
|
7
|
+
from haiway import cache
|
8
|
+
|
8
9
|
|
9
10
|
class FakeException(Exception):
|
10
11
|
pass
|
@@ -1,21 +1,26 @@
|
|
1
|
-
from collections.abc import Callable
|
2
|
-
from
|
1
|
+
from collections.abc import Callable, Sequence, Set
|
2
|
+
from enum import StrEnum
|
3
|
+
from typing import Literal, Protocol, Self, runtime_checkable
|
4
|
+
from uuid import UUID, uuid4
|
3
5
|
|
4
6
|
from haiway import MISSING, Missing, State, frozenlist
|
5
7
|
|
6
8
|
|
7
9
|
def test_basic_initializes_with_arguments() -> None:
|
8
|
-
class
|
9
|
-
|
10
|
+
class Selection(StrEnum):
|
11
|
+
A = "A"
|
12
|
+
B = "A"
|
10
13
|
|
11
14
|
@runtime_checkable
|
12
15
|
class Proto(Protocol):
|
13
16
|
def __call__(self) -> None: ...
|
14
17
|
|
15
18
|
class Basics(State):
|
19
|
+
uuid: UUID
|
16
20
|
string: str
|
17
21
|
literal: Literal["A", "B"]
|
18
|
-
sequence:
|
22
|
+
sequence: Sequence[str]
|
23
|
+
string_set: Set[str]
|
19
24
|
frozen: frozenlist[int]
|
20
25
|
integer: int
|
21
26
|
union: str | int
|
@@ -23,12 +28,14 @@ def test_basic_initializes_with_arguments() -> None:
|
|
23
28
|
none: None
|
24
29
|
function: Callable[[], None]
|
25
30
|
proto: Proto
|
26
|
-
|
31
|
+
selection: Selection
|
27
32
|
|
28
33
|
basic = Basics(
|
34
|
+
uuid=uuid4(),
|
29
35
|
string="string",
|
30
36
|
literal="A",
|
31
37
|
sequence=["a", "b", "c"],
|
38
|
+
string_set={"a", "b"},
|
32
39
|
frozen=(1, 2, 3),
|
33
40
|
integer=0,
|
34
41
|
union="union",
|
@@ -36,17 +43,18 @@ def test_basic_initializes_with_arguments() -> None:
|
|
36
43
|
none=None,
|
37
44
|
function=lambda: None,
|
38
45
|
proto=lambda: None,
|
39
|
-
|
46
|
+
selection=Selection.A,
|
40
47
|
)
|
41
48
|
assert basic.string == "string"
|
42
49
|
assert basic.literal == "A"
|
43
|
-
assert basic.sequence ==
|
50
|
+
assert basic.sequence == ("a", "b", "c")
|
51
|
+
assert basic.string_set == {"a", "b"}
|
44
52
|
assert basic.frozen == (1, 2, 3)
|
45
53
|
assert basic.integer == 0
|
46
54
|
assert basic.union == "union"
|
47
55
|
assert basic.optional == "optional"
|
48
56
|
assert basic.none is None
|
49
|
-
assert basic.
|
57
|
+
assert basic.selection == Selection.A
|
50
58
|
assert callable(basic.function)
|
51
59
|
assert isinstance(basic.proto, Proto)
|
52
60
|
|
@@ -1,10 +1,11 @@
|
|
1
1
|
from asyncio import CancelledError, get_running_loop, sleep
|
2
2
|
from collections.abc import AsyncGenerator, AsyncIterator
|
3
3
|
|
4
|
+
from pytest import mark, raises
|
5
|
+
|
4
6
|
from haiway import ctx
|
5
7
|
from haiway.context.metrics import ScopeMetrics
|
6
8
|
from haiway.state.structure import State
|
7
|
-
from pytest import mark, raises
|
8
9
|
|
9
10
|
|
10
11
|
class FakeException(Exception):
|
@@ -1,149 +0,0 @@
|
|
1
|
-
import types
|
2
|
-
import typing
|
3
|
-
from collections.abc import Callable, Sequence
|
4
|
-
from typing import Any
|
5
|
-
|
6
|
-
from haiway import types as _types
|
7
|
-
from haiway.state.attributes import AttributeAnnotation
|
8
|
-
|
9
|
-
__all__ = [
|
10
|
-
"attribute_type_validator",
|
11
|
-
]
|
12
|
-
|
13
|
-
|
14
|
-
def attribute_type_validator( # noqa: PLR0911
|
15
|
-
annotation: AttributeAnnotation,
|
16
|
-
/,
|
17
|
-
) -> Callable[[Any], Any]:
|
18
|
-
match annotation.origin:
|
19
|
-
case types.NoneType:
|
20
|
-
return _none_validator
|
21
|
-
|
22
|
-
case _types.Missing:
|
23
|
-
return _missing_validator
|
24
|
-
|
25
|
-
case types.UnionType:
|
26
|
-
return _prepare_union_validator(annotation.arguments)
|
27
|
-
|
28
|
-
case typing.Literal:
|
29
|
-
return _prepare_literal_validator(annotation.arguments)
|
30
|
-
|
31
|
-
case typing.Any:
|
32
|
-
return _any_validator
|
33
|
-
|
34
|
-
# typed dicts fail on type checks
|
35
|
-
case typed_dict if typing.is_typeddict(typed_dict):
|
36
|
-
return _prepare_typed_dict_validator(typed_dict)
|
37
|
-
|
38
|
-
case type() as other_type:
|
39
|
-
return _prepare_type_validator(other_type)
|
40
|
-
|
41
|
-
case other:
|
42
|
-
raise TypeError(f"Unsupported type annotation: {other}")
|
43
|
-
|
44
|
-
|
45
|
-
def _none_validator(
|
46
|
-
value: Any,
|
47
|
-
) -> Any:
|
48
|
-
match value:
|
49
|
-
case None:
|
50
|
-
return None
|
51
|
-
|
52
|
-
case _:
|
53
|
-
raise TypeError(f"Type '{type(value)}' is not matching expected type 'None'")
|
54
|
-
|
55
|
-
|
56
|
-
def _missing_validator(
|
57
|
-
value: Any,
|
58
|
-
) -> Any:
|
59
|
-
match value:
|
60
|
-
case _types.Missing():
|
61
|
-
return _types.MISSING
|
62
|
-
|
63
|
-
case _:
|
64
|
-
raise TypeError(f"Type '{type(value)}' is not matching expected type 'Missing'")
|
65
|
-
|
66
|
-
|
67
|
-
def _any_validator(
|
68
|
-
value: Any,
|
69
|
-
) -> Any:
|
70
|
-
return value # any is always valid
|
71
|
-
|
72
|
-
|
73
|
-
def _prepare_union_validator(
|
74
|
-
elements: Sequence[AttributeAnnotation],
|
75
|
-
/,
|
76
|
-
) -> Callable[[Any], Any]:
|
77
|
-
validators: list[Callable[[Any], Any]] = [
|
78
|
-
attribute_type_validator(alternative) for alternative in elements
|
79
|
-
]
|
80
|
-
|
81
|
-
def union_validator(
|
82
|
-
value: Any,
|
83
|
-
) -> Any:
|
84
|
-
errors: list[Exception] = []
|
85
|
-
for validator in validators:
|
86
|
-
try:
|
87
|
-
return validator(value)
|
88
|
-
|
89
|
-
except Exception as exc:
|
90
|
-
errors.append(exc)
|
91
|
-
|
92
|
-
raise ExceptionGroup("Multiple errors", errors)
|
93
|
-
|
94
|
-
return union_validator
|
95
|
-
|
96
|
-
|
97
|
-
def _prepare_literal_validator(
|
98
|
-
elements: Sequence[Any],
|
99
|
-
/,
|
100
|
-
) -> Callable[[Any], Any]:
|
101
|
-
def literal_validator(
|
102
|
-
value: Any,
|
103
|
-
) -> Any:
|
104
|
-
if value in elements:
|
105
|
-
return value
|
106
|
-
|
107
|
-
else:
|
108
|
-
raise ValueError(f"Value '{value}' is not matching expected '{elements}'")
|
109
|
-
|
110
|
-
return literal_validator
|
111
|
-
|
112
|
-
|
113
|
-
def _prepare_type_validator(
|
114
|
-
validated_type: type[Any],
|
115
|
-
/,
|
116
|
-
) -> Callable[[Any], Any]:
|
117
|
-
def type_validator(
|
118
|
-
value: Any,
|
119
|
-
) -> Any:
|
120
|
-
match value:
|
121
|
-
case value if isinstance(value, validated_type):
|
122
|
-
return value
|
123
|
-
|
124
|
-
case _:
|
125
|
-
raise TypeError(
|
126
|
-
f"Type '{type(value)}' is not matching expected type '{validated_type}'"
|
127
|
-
)
|
128
|
-
|
129
|
-
return type_validator
|
130
|
-
|
131
|
-
|
132
|
-
def _prepare_typed_dict_validator(
|
133
|
-
validated_type: type[Any],
|
134
|
-
/,
|
135
|
-
) -> Callable[[Any], Any]:
|
136
|
-
def typed_dict_validator(
|
137
|
-
value: Any,
|
138
|
-
) -> Any:
|
139
|
-
match value:
|
140
|
-
case value if isinstance(value, dict):
|
141
|
-
# for typed dicts check only if that is a dict
|
142
|
-
return value # pyright: ignore[reportUnknownVariableType]
|
143
|
-
|
144
|
-
case _:
|
145
|
-
raise TypeError(
|
146
|
-
f"Type '{type(value)}' is not matching expected type '{validated_type}'"
|
147
|
-
)
|
148
|
-
|
149
|
-
return typed_dict_validator
|
File without changes
|
File without changes
|
File without changes
|
@@ -43,16 +43,23 @@ from haiway.utils import (
|
|
43
43
|
)
|
44
44
|
|
45
45
|
__all__ = [
|
46
|
-
"
|
46
|
+
"MISSING",
|
47
47
|
"ArgumentsTrace",
|
48
|
+
"AsyncQueue",
|
49
|
+
"Disposable",
|
50
|
+
"Disposables",
|
51
|
+
"Missing",
|
52
|
+
"MissingContext",
|
53
|
+
"MissingState",
|
54
|
+
"ResultTrace",
|
55
|
+
"ScopeMetrics",
|
56
|
+
"State",
|
57
|
+
"always",
|
48
58
|
"async_always",
|
49
59
|
"async_noop",
|
50
60
|
"asynchronous",
|
51
|
-
"AsyncQueue",
|
52
61
|
"cache",
|
53
62
|
"ctx",
|
54
|
-
"Disposable",
|
55
|
-
"Disposables",
|
56
63
|
"freeze",
|
57
64
|
"frozenlist",
|
58
65
|
"getenv_bool",
|
@@ -62,17 +69,10 @@ __all__ = [
|
|
62
69
|
"is_missing",
|
63
70
|
"load_env",
|
64
71
|
"mimic_function",
|
65
|
-
"Missing",
|
66
|
-
"MISSING",
|
67
|
-
"MissingContext",
|
68
|
-
"MissingState",
|
69
72
|
"noop",
|
70
73
|
"not_missing",
|
71
|
-
"ResultTrace",
|
72
74
|
"retry",
|
73
|
-
"ScopeMetrics",
|
74
75
|
"setup_logging",
|
75
|
-
"State",
|
76
76
|
"throttle",
|
77
77
|
"timeout",
|
78
78
|
"traced",
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -2,10 +2,10 @@ from haiway.types.frozen import frozenlist
|
|
2
2
|
from haiway.types.missing import MISSING, Missing, is_missing, not_missing, when_missing
|
3
3
|
|
4
4
|
__all__ = [
|
5
|
+
"MISSING",
|
6
|
+
"Missing",
|
5
7
|
"frozenlist",
|
6
8
|
"is_missing",
|
7
|
-
"Missing",
|
8
|
-
"MISSING",
|
9
9
|
"not_missing",
|
10
10
|
"when_missing",
|
11
11
|
]
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|