haiway 0.6.2__py3-none-any.whl → 0.6.4__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.
- haiway/__init__.py +3 -1
- haiway/state/__init__.py +4 -0
- haiway/state/attributes.py +15 -14
- haiway/state/path.py +524 -0
- haiway/state/requirement.py +229 -0
- haiway/state/structure.py +15 -0
- haiway/state/validation.py +9 -2
- {haiway-0.6.2.dist-info → haiway-0.6.4.dist-info}/METADATA +1 -1
- {haiway-0.6.2.dist-info → haiway-0.6.4.dist-info}/RECORD +12 -10
- {haiway-0.6.2.dist-info → haiway-0.6.4.dist-info}/LICENSE +0 -0
- {haiway-0.6.2.dist-info → haiway-0.6.4.dist-info}/WHEEL +0 -0
- {haiway-0.6.2.dist-info → haiway-0.6.4.dist-info}/top_level.txt +0 -0
haiway/__init__.py
CHANGED
@@ -17,7 +17,7 @@ from haiway.helpers import (
|
|
17
17
|
traced,
|
18
18
|
wrap_async,
|
19
19
|
)
|
20
|
-
from haiway.state import State
|
20
|
+
from haiway.state import AttributePath, AttributeRequirement, State
|
21
21
|
from haiway.types import (
|
22
22
|
MISSING,
|
23
23
|
Missing,
|
@@ -46,6 +46,8 @@ __all__ = [
|
|
46
46
|
"MISSING",
|
47
47
|
"ArgumentsTrace",
|
48
48
|
"AsyncQueue",
|
49
|
+
"AttributePath",
|
50
|
+
"AttributeRequirement",
|
49
51
|
"Disposable",
|
50
52
|
"Disposables",
|
51
53
|
"Missing",
|
haiway/state/__init__.py
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
from haiway.state.attributes import AttributeAnnotation, attribute_annotations
|
2
|
+
from haiway.state.path import AttributePath
|
3
|
+
from haiway.state.requirement import AttributeRequirement
|
2
4
|
from haiway.state.structure import State
|
3
5
|
|
4
6
|
__all__ = [
|
5
7
|
"AttributeAnnotation",
|
8
|
+
"AttributePath",
|
9
|
+
"AttributeRequirement",
|
6
10
|
"State",
|
7
11
|
"attribute_annotations",
|
8
12
|
]
|
haiway/state/attributes.py
CHANGED
@@ -19,6 +19,7 @@ from typing import (
|
|
19
19
|
__all__ = [
|
20
20
|
"AttributeAnnotation",
|
21
21
|
"attribute_annotations",
|
22
|
+
"resolve_attribute_annotation",
|
22
23
|
]
|
23
24
|
|
24
25
|
|
@@ -68,7 +69,7 @@ def attribute_annotations(
|
|
68
69
|
if ((get_origin(annotation) or annotation) is ClassVar) or key.startswith("_"):
|
69
70
|
continue
|
70
71
|
|
71
|
-
attributes[key] =
|
72
|
+
attributes[key] = resolve_attribute_annotation(
|
72
73
|
annotation,
|
73
74
|
self_annotation=self_annotation,
|
74
75
|
type_parameters=type_parameters,
|
@@ -80,7 +81,7 @@ def attribute_annotations(
|
|
80
81
|
return attributes
|
81
82
|
|
82
83
|
|
83
|
-
def
|
84
|
+
def resolve_attribute_annotation( # noqa: C901, PLR0911, PLR0912, PLR0913
|
84
85
|
annotation: Any,
|
85
86
|
/,
|
86
87
|
self_annotation: AttributeAnnotation | None,
|
@@ -100,7 +101,7 @@ def _resolve_attribute_annotation( # noqa: C901, PLR0911, PLR0912, PLR0913
|
|
100
101
|
|
101
102
|
# forward reference through string
|
102
103
|
case str() as forward_ref:
|
103
|
-
return
|
104
|
+
return resolve_attribute_annotation(
|
104
105
|
ForwardRef(forward_ref, module=module)._evaluate(
|
105
106
|
globalns=None,
|
106
107
|
localns=localns,
|
@@ -115,7 +116,7 @@ def _resolve_attribute_annotation( # noqa: C901, PLR0911, PLR0912, PLR0913
|
|
115
116
|
|
116
117
|
# forward reference directly
|
117
118
|
case typing.ForwardRef() as reference:
|
118
|
-
return
|
119
|
+
return resolve_attribute_annotation(
|
119
120
|
reference._evaluate(
|
120
121
|
globalns=None,
|
121
122
|
localns=localns,
|
@@ -137,7 +138,7 @@ def _resolve_attribute_annotation( # noqa: C901, PLR0911, PLR0912, PLR0913
|
|
137
138
|
origin=TypeAliasType,
|
138
139
|
arguments=[],
|
139
140
|
)
|
140
|
-
resolved: AttributeAnnotation =
|
141
|
+
resolved: AttributeAnnotation = resolve_attribute_annotation(
|
141
142
|
alias.__value__,
|
142
143
|
self_annotation=None,
|
143
144
|
type_parameters=type_parameters,
|
@@ -169,7 +170,7 @@ def _resolve_attribute_annotation( # noqa: C901, PLR0911, PLR0912, PLR0913
|
|
169
170
|
return AttributeAnnotation(
|
170
171
|
origin=parametrized,
|
171
172
|
arguments=[
|
172
|
-
|
173
|
+
resolve_attribute_annotation(
|
173
174
|
argument,
|
174
175
|
self_annotation=self_annotation,
|
175
176
|
type_parameters=type_parameters,
|
@@ -193,7 +194,7 @@ def _resolve_attribute_annotation( # noqa: C901, PLR0911, PLR0912, PLR0913
|
|
193
194
|
return AttributeAnnotation(
|
194
195
|
origin=origin,
|
195
196
|
arguments=[
|
196
|
-
|
197
|
+
resolve_attribute_annotation(
|
197
198
|
argument,
|
198
199
|
self_annotation=self_annotation,
|
199
200
|
type_parameters=type_parameters,
|
@@ -211,7 +212,7 @@ def _resolve_attribute_annotation( # noqa: C901, PLR0911, PLR0912, PLR0913
|
|
211
212
|
origin=TypeAliasType,
|
212
213
|
arguments=[],
|
213
214
|
)
|
214
|
-
resolved: AttributeAnnotation =
|
215
|
+
resolved: AttributeAnnotation = resolve_attribute_annotation(
|
215
216
|
alias.__value__,
|
216
217
|
self_annotation=None,
|
217
218
|
type_parameters=type_parameters,
|
@@ -225,7 +226,7 @@ def _resolve_attribute_annotation( # noqa: C901, PLR0911, PLR0912, PLR0913
|
|
225
226
|
|
226
227
|
# type parameter
|
227
228
|
case typing.TypeVar():
|
228
|
-
return
|
229
|
+
return resolve_attribute_annotation(
|
229
230
|
# try to resolve it from current parameters if able
|
230
231
|
type_parameters.get(
|
231
232
|
annotation.__name__,
|
@@ -270,7 +271,7 @@ def _resolve_attribute_annotation( # noqa: C901, PLR0911, PLR0912, PLR0913
|
|
270
271
|
arguments=[
|
271
272
|
recursion_guard.get(
|
272
273
|
argument,
|
273
|
-
|
274
|
+
resolve_attribute_annotation(
|
274
275
|
argument,
|
275
276
|
self_annotation=self_annotation,
|
276
277
|
type_parameters=type_parameters,
|
@@ -287,7 +288,7 @@ def _resolve_attribute_annotation( # noqa: C901, PLR0911, PLR0912, PLR0913
|
|
287
288
|
return AttributeAnnotation(
|
288
289
|
origin=typing.Callable,
|
289
290
|
arguments=[
|
290
|
-
|
291
|
+
resolve_attribute_annotation(
|
291
292
|
argument,
|
292
293
|
self_annotation=self_annotation,
|
293
294
|
type_parameters=type_parameters,
|
@@ -314,7 +315,7 @@ def _resolve_attribute_annotation( # noqa: C901, PLR0911, PLR0912, PLR0913
|
|
314
315
|
|
315
316
|
# unwrap from irrelevant type wrappers
|
316
317
|
case typing.Annotated | typing.Final | typing.Required | typing.NotRequired:
|
317
|
-
return
|
318
|
+
return resolve_attribute_annotation(
|
318
319
|
get_args(annotation)[0],
|
319
320
|
self_annotation=self_annotation,
|
320
321
|
type_parameters=type_parameters,
|
@@ -327,7 +328,7 @@ def _resolve_attribute_annotation( # noqa: C901, PLR0911, PLR0912, PLR0913
|
|
327
328
|
return AttributeAnnotation(
|
328
329
|
origin=UnionType, # pyright: ignore[reportArgumentType]
|
329
330
|
arguments=[
|
330
|
-
|
331
|
+
resolve_attribute_annotation(
|
331
332
|
get_args(annotation)[0],
|
332
333
|
self_annotation=self_annotation,
|
333
334
|
type_parameters=type_parameters,
|
@@ -352,7 +353,7 @@ def _resolve_attribute_annotation( # noqa: C901, PLR0911, PLR0912, PLR0913
|
|
352
353
|
return AttributeAnnotation(
|
353
354
|
origin=other,
|
354
355
|
arguments=[
|
355
|
-
|
356
|
+
resolve_attribute_annotation(
|
356
357
|
argument,
|
357
358
|
self_annotation=self_annotation,
|
358
359
|
type_parameters=type_parameters,
|
haiway/state/path.py
ADDED
@@ -0,0 +1,524 @@
|
|
1
|
+
import builtins
|
2
|
+
import types
|
3
|
+
import typing
|
4
|
+
from abc import ABC, abstractmethod
|
5
|
+
from collections import abc as collections_abc
|
6
|
+
from collections import deque
|
7
|
+
from collections.abc import Callable, Mapping, Sequence
|
8
|
+
from copy import copy
|
9
|
+
from typing import Any, final, get_args, get_origin, overload
|
10
|
+
|
11
|
+
from haiway.types import MISSING, Missing, not_missing
|
12
|
+
|
13
|
+
__all__ = [
|
14
|
+
"AttributePath",
|
15
|
+
]
|
16
|
+
|
17
|
+
|
18
|
+
class AttributePathComponent(ABC):
|
19
|
+
@abstractmethod
|
20
|
+
def path_str(
|
21
|
+
self,
|
22
|
+
current: str | None = None,
|
23
|
+
/,
|
24
|
+
) -> str: ...
|
25
|
+
|
26
|
+
@abstractmethod
|
27
|
+
def access(
|
28
|
+
self,
|
29
|
+
subject: Any,
|
30
|
+
/,
|
31
|
+
) -> Any: ...
|
32
|
+
|
33
|
+
@abstractmethod
|
34
|
+
def assigning(
|
35
|
+
self,
|
36
|
+
subject: Any,
|
37
|
+
/,
|
38
|
+
value: Any,
|
39
|
+
) -> Any: ...
|
40
|
+
|
41
|
+
|
42
|
+
@final
|
43
|
+
class PropertyAttributePathComponent(AttributePathComponent):
|
44
|
+
def __init__[Root, Attribute](
|
45
|
+
self,
|
46
|
+
root: type[Root],
|
47
|
+
*,
|
48
|
+
attribute: type[Attribute],
|
49
|
+
name: str,
|
50
|
+
) -> None:
|
51
|
+
root_origin: Any = get_origin(root) or root
|
52
|
+
attribute_origin: Any = get_origin(attribute) or attribute
|
53
|
+
|
54
|
+
def access(
|
55
|
+
subject: Root,
|
56
|
+
/,
|
57
|
+
) -> Attribute:
|
58
|
+
assert isinstance(subject, root_origin), ( # nosec: B101
|
59
|
+
f"AttributePath used on unexpected subject of"
|
60
|
+
f" '{type(subject).__name__}' instead of '{root.__name__}' for '{name}'"
|
61
|
+
)
|
62
|
+
|
63
|
+
assert hasattr(subject, name), ( # nosec: B101
|
64
|
+
f"AttributePath pointing to attribute '{name}'"
|
65
|
+
f" which is not available in subject '{type(subject).__name__}'"
|
66
|
+
)
|
67
|
+
|
68
|
+
resolved: Any = getattr(subject, name)
|
69
|
+
|
70
|
+
assert isinstance(resolved, attribute_origin), ( # nosec: B101
|
71
|
+
f"AttributePath pointing to unexpected value of"
|
72
|
+
f" '{type(resolved).__name__}' instead of '{attribute.__name__}' for '{name}'"
|
73
|
+
)
|
74
|
+
return resolved
|
75
|
+
|
76
|
+
def assigning(
|
77
|
+
subject: Root,
|
78
|
+
/,
|
79
|
+
value: Attribute,
|
80
|
+
) -> Root:
|
81
|
+
assert isinstance(subject, root_origin), ( # nosec: B101
|
82
|
+
f"AttributePath used on unexpected subject of"
|
83
|
+
f" '{type(subject).__name__}' instead of '{root.__name__}' for '{name}'"
|
84
|
+
)
|
85
|
+
|
86
|
+
assert hasattr(subject, name), ( # nosec: B101
|
87
|
+
f"AttributePath pointing to attribute '{name}'"
|
88
|
+
f" which is not available in subject '{type(subject).__name__}'"
|
89
|
+
)
|
90
|
+
|
91
|
+
assert isinstance(value, attribute_origin), ( # nosec: B101
|
92
|
+
f"AttributePath assigning unexpected value of "
|
93
|
+
f"'{type(value).__name__}' instead of '{attribute.__name__}' for '{name}'"
|
94
|
+
)
|
95
|
+
|
96
|
+
updated: Root
|
97
|
+
# python 3.13 introduces __replace__, we are already implementing it for our types
|
98
|
+
if hasattr(subject, "__replace__"): # can't check full type here
|
99
|
+
updated = subject.__replace__(**{name: value}) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType, reportAttributeAccessIssue]
|
100
|
+
|
101
|
+
else:
|
102
|
+
updated = copy(subject)
|
103
|
+
setattr(updated, name, value)
|
104
|
+
|
105
|
+
return updated # pyright: ignore[reportUnknownVariableType]
|
106
|
+
|
107
|
+
self._access: Callable[[Any], Any] = access
|
108
|
+
self._assigning: Callable[[Any, Any], Any] = assigning
|
109
|
+
self._name: str = name
|
110
|
+
|
111
|
+
def path_str(
|
112
|
+
self,
|
113
|
+
current: str | None = None,
|
114
|
+
/,
|
115
|
+
) -> str:
|
116
|
+
if current:
|
117
|
+
return f"{current}.{self._name}"
|
118
|
+
|
119
|
+
else:
|
120
|
+
return self._name
|
121
|
+
|
122
|
+
def access(
|
123
|
+
self,
|
124
|
+
subject: Any,
|
125
|
+
/,
|
126
|
+
) -> Any:
|
127
|
+
return self._access(subject)
|
128
|
+
|
129
|
+
def assigning(
|
130
|
+
self,
|
131
|
+
subject: Any,
|
132
|
+
/,
|
133
|
+
value: Any,
|
134
|
+
) -> Any:
|
135
|
+
return self._assigning(subject, value)
|
136
|
+
|
137
|
+
|
138
|
+
@final
|
139
|
+
class SequenceItemAttributePathComponent(AttributePathComponent):
|
140
|
+
def __init__[Root: Sequence[Any], Attribute](
|
141
|
+
self,
|
142
|
+
root: type[Root],
|
143
|
+
*,
|
144
|
+
attribute: type[Attribute],
|
145
|
+
index: int,
|
146
|
+
) -> None:
|
147
|
+
root_origin: Any = get_origin(root) or root
|
148
|
+
attribute_origin: Any = get_origin(attribute) or attribute
|
149
|
+
|
150
|
+
def access(
|
151
|
+
subject: Root,
|
152
|
+
/,
|
153
|
+
) -> Attribute:
|
154
|
+
assert isinstance(subject, root_origin), ( # nosec: B101
|
155
|
+
f"AttributePathComponent used on unexpected root of "
|
156
|
+
f"'{type(root).__name__}' instead of '{root.__name__}' for '{index}'"
|
157
|
+
)
|
158
|
+
|
159
|
+
resolved: Any = subject.__getitem__(index) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
160
|
+
|
161
|
+
assert isinstance(resolved, attribute_origin), ( # nosec: B101
|
162
|
+
f"AttributePath pointing to unexpected value of "
|
163
|
+
f"'{type(resolved).__name__}' instead of '{attribute.__name__}' for '{index}'"
|
164
|
+
)
|
165
|
+
return resolved
|
166
|
+
|
167
|
+
def assigning(
|
168
|
+
subject: Root,
|
169
|
+
/,
|
170
|
+
value: Attribute,
|
171
|
+
) -> Root:
|
172
|
+
assert isinstance(subject, root_origin), ( # nosec: B101
|
173
|
+
f"AttributePath used on unexpected root of "
|
174
|
+
f"'{type(subject).__name__}' instead of '{root.__name__}' for '{index}'"
|
175
|
+
)
|
176
|
+
assert isinstance(value, attribute_origin), ( # nosec: B101
|
177
|
+
f"AttributePath assigning to unexpected value of "
|
178
|
+
f"'{type(value).__name__}' instead of '{attribute.__name__}' for '{index}'"
|
179
|
+
)
|
180
|
+
|
181
|
+
temp_list: list[Any] = list(subject) # pyright: ignore[reportUnknownArgumentType]
|
182
|
+
temp_list[index] = value
|
183
|
+
return subject.__class__(temp_list) # pyright: ignore[reportCallIssue, reportUnknownVariableType, reportUnknownMemberType]
|
184
|
+
|
185
|
+
self._access: Callable[[Any], Any] = access
|
186
|
+
self._assigning: Callable[[Any, Any], Any] = assigning
|
187
|
+
self._index: Any = index
|
188
|
+
|
189
|
+
def path_str(
|
190
|
+
self,
|
191
|
+
current: str | None = None,
|
192
|
+
/,
|
193
|
+
) -> str:
|
194
|
+
return f"{current or ''}[{self._index}]"
|
195
|
+
|
196
|
+
def access(
|
197
|
+
self,
|
198
|
+
subject: Any,
|
199
|
+
/,
|
200
|
+
) -> Any:
|
201
|
+
return self._access(subject)
|
202
|
+
|
203
|
+
def assigning(
|
204
|
+
self,
|
205
|
+
subject: Any,
|
206
|
+
/,
|
207
|
+
value: Any,
|
208
|
+
) -> Any:
|
209
|
+
return self._assigning(subject, value)
|
210
|
+
|
211
|
+
|
212
|
+
@final
|
213
|
+
class MappingItemAttributePathComponent(AttributePathComponent):
|
214
|
+
def __init__[Root: Mapping[Any, Any], Attribute](
|
215
|
+
self,
|
216
|
+
root: type[Root],
|
217
|
+
*,
|
218
|
+
attribute: type[Attribute],
|
219
|
+
key: str | int,
|
220
|
+
) -> None:
|
221
|
+
root_origin: Any = get_origin(root) or root
|
222
|
+
attribute_origin: Any = get_origin(attribute) or attribute
|
223
|
+
|
224
|
+
def access(
|
225
|
+
subject: Root,
|
226
|
+
/,
|
227
|
+
) -> Attribute:
|
228
|
+
assert isinstance(subject, root_origin), ( # nosec: B101
|
229
|
+
f"AttributePathComponent used on unexpected root of "
|
230
|
+
f"'{type(root).__name__}' instead of '{root.__name__}' for '{key}'"
|
231
|
+
)
|
232
|
+
|
233
|
+
resolved: Any = subject.__getitem__(key) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
234
|
+
|
235
|
+
assert isinstance(resolved, attribute_origin), ( # nosec: B101
|
236
|
+
f"AttributePath pointing to unexpected value of "
|
237
|
+
f"'{type(resolved).__name__}' instead of '{attribute.__name__}' for '{key}'"
|
238
|
+
)
|
239
|
+
return resolved
|
240
|
+
|
241
|
+
def assigning(
|
242
|
+
subject: Root,
|
243
|
+
/,
|
244
|
+
value: Attribute,
|
245
|
+
) -> Root:
|
246
|
+
assert isinstance(subject, root_origin), ( # nosec: B101
|
247
|
+
f"AttributePath used on unexpected root of "
|
248
|
+
f"'{type(subject).__name__}' instead of '{root.__name__}' for '{key}'"
|
249
|
+
)
|
250
|
+
assert isinstance(value, attribute_origin), ( # nosec: B101
|
251
|
+
f"AttributePath assigning to unexpected value of "
|
252
|
+
f"'{type(value).__name__}' instead of '{attribute.__name__}' for '{key}'"
|
253
|
+
)
|
254
|
+
|
255
|
+
temp_dict: dict[Any, Any] = dict(subject) # pyright: ignore[reportUnknownArgumentType]
|
256
|
+
temp_dict[key] = value
|
257
|
+
return subject.__class__(temp_dict) # pyright: ignore[reportCallIssue, reportUnknownVariableType, reportUnknownMemberType]
|
258
|
+
|
259
|
+
self._access: Callable[[Any], Any] = access
|
260
|
+
self._assigning: Callable[[Any, Any], Any] = assigning
|
261
|
+
self._key: Any = key
|
262
|
+
|
263
|
+
def path_str(
|
264
|
+
self,
|
265
|
+
current: str | None = None,
|
266
|
+
/,
|
267
|
+
) -> str:
|
268
|
+
return f"{current or ''}[{self._key}]"
|
269
|
+
|
270
|
+
def access(
|
271
|
+
self,
|
272
|
+
subject: Any,
|
273
|
+
/,
|
274
|
+
) -> Any:
|
275
|
+
return self._access(subject)
|
276
|
+
|
277
|
+
def assigning(
|
278
|
+
self,
|
279
|
+
subject: Any,
|
280
|
+
/,
|
281
|
+
value: Any,
|
282
|
+
) -> Any:
|
283
|
+
return self._assigning(subject, value)
|
284
|
+
|
285
|
+
|
286
|
+
@final
|
287
|
+
class AttributePath[Root, Attribute]:
|
288
|
+
@overload
|
289
|
+
def __init__(
|
290
|
+
self,
|
291
|
+
root: type[Root],
|
292
|
+
/,
|
293
|
+
*,
|
294
|
+
attribute: type[Root],
|
295
|
+
) -> None: ...
|
296
|
+
|
297
|
+
@overload
|
298
|
+
def __init__(
|
299
|
+
self,
|
300
|
+
root: type[Root],
|
301
|
+
/,
|
302
|
+
*components: AttributePathComponent,
|
303
|
+
attribute: type[Attribute],
|
304
|
+
) -> None: ...
|
305
|
+
|
306
|
+
def __init__(
|
307
|
+
self,
|
308
|
+
root: type[Root],
|
309
|
+
/,
|
310
|
+
*components: AttributePathComponent,
|
311
|
+
attribute: type[Attribute],
|
312
|
+
) -> None:
|
313
|
+
assert components or root == attribute # nosec: B101
|
314
|
+
self.__root__: type[Root] = root
|
315
|
+
self.__attribute__: type[Attribute] = attribute
|
316
|
+
self.__components__: tuple[AttributePathComponent, ...] = components
|
317
|
+
|
318
|
+
@property
|
319
|
+
def components(self) -> Sequence[str]:
|
320
|
+
return tuple(component.path_str() for component in self.__components__)
|
321
|
+
|
322
|
+
def __str__(self) -> str:
|
323
|
+
path: str = ""
|
324
|
+
for component in self.__components__:
|
325
|
+
path = component.path_str(path)
|
326
|
+
|
327
|
+
return path
|
328
|
+
|
329
|
+
def __repr__(self) -> str:
|
330
|
+
path: str = self.__root__.__name__
|
331
|
+
for component in self.__components__:
|
332
|
+
path = component.path_str(path)
|
333
|
+
|
334
|
+
return path
|
335
|
+
|
336
|
+
def __getattr__(
|
337
|
+
self,
|
338
|
+
name: str,
|
339
|
+
) -> Any:
|
340
|
+
try:
|
341
|
+
return object.__getattribute__(self, name)
|
342
|
+
|
343
|
+
except (AttributeError, KeyError):
|
344
|
+
pass # continue
|
345
|
+
|
346
|
+
assert not name.startswith( # nosec: B101
|
347
|
+
"_"
|
348
|
+
), f"Accessing private/special attribute paths ({name}) is forbidden"
|
349
|
+
|
350
|
+
try:
|
351
|
+
annotation: Any = self.__attribute__.__annotations__[name]
|
352
|
+
|
353
|
+
except Exception as exc:
|
354
|
+
raise AttributeError(
|
355
|
+
f"Failed to prepare AttributePath caused by inaccessible"
|
356
|
+
f" type annotation for '{name}' within '{self.__attribute__.__name__}'"
|
357
|
+
) from exc
|
358
|
+
|
359
|
+
return AttributePath[Root, Any](
|
360
|
+
self.__root__,
|
361
|
+
*(
|
362
|
+
*self.__components__,
|
363
|
+
PropertyAttributePathComponent(
|
364
|
+
root=self.__attribute__,
|
365
|
+
attribute=annotation,
|
366
|
+
name=name,
|
367
|
+
),
|
368
|
+
),
|
369
|
+
attribute=annotation,
|
370
|
+
)
|
371
|
+
|
372
|
+
def __getitem__(
|
373
|
+
self,
|
374
|
+
key: str | int,
|
375
|
+
) -> Any:
|
376
|
+
match get_origin(self.__attribute__) or self.__attribute__:
|
377
|
+
case collections_abc.Mapping | typing.Mapping | builtins.dict:
|
378
|
+
match get_args(self.__attribute__):
|
379
|
+
case (builtins.str | builtins.int, element_annotation):
|
380
|
+
return AttributePath[Root, Any](
|
381
|
+
self.__root__,
|
382
|
+
*(
|
383
|
+
*self.__components__,
|
384
|
+
MappingItemAttributePathComponent(
|
385
|
+
root=self.__attribute__, # pyright: ignore[reportArgumentType]
|
386
|
+
attribute=element_annotation,
|
387
|
+
key=key,
|
388
|
+
),
|
389
|
+
),
|
390
|
+
attribute=element_annotation,
|
391
|
+
)
|
392
|
+
|
393
|
+
case other:
|
394
|
+
raise TypeError(
|
395
|
+
"Unsupported Mapping type annotation",
|
396
|
+
self.__attribute__.__name__,
|
397
|
+
)
|
398
|
+
|
399
|
+
case builtins.tuple:
|
400
|
+
if not isinstance(key, int):
|
401
|
+
raise TypeError(
|
402
|
+
"Unsupported tuple type annotation",
|
403
|
+
self.__attribute__.__name__,
|
404
|
+
)
|
405
|
+
|
406
|
+
match get_args(self.__attribute__):
|
407
|
+
case (element_annotation, builtins.Ellipsis | types.EllipsisType):
|
408
|
+
return AttributePath[Root, Any](
|
409
|
+
self.__root__,
|
410
|
+
*(
|
411
|
+
*self.__components__,
|
412
|
+
SequenceItemAttributePathComponent(
|
413
|
+
root=self.__attribute__, # pyright: ignore[reportArgumentType]
|
414
|
+
attribute=element_annotation,
|
415
|
+
index=key,
|
416
|
+
),
|
417
|
+
),
|
418
|
+
attribute=element_annotation,
|
419
|
+
)
|
420
|
+
|
421
|
+
case other:
|
422
|
+
return AttributePath[Root, Any](
|
423
|
+
self.__root__,
|
424
|
+
*(
|
425
|
+
*self.__components__,
|
426
|
+
SequenceItemAttributePathComponent(
|
427
|
+
root=self.__attribute__, # pyright: ignore[reportArgumentType]
|
428
|
+
attribute=other[key],
|
429
|
+
index=key,
|
430
|
+
),
|
431
|
+
),
|
432
|
+
attribute=other[key],
|
433
|
+
)
|
434
|
+
|
435
|
+
case collections_abc.Sequence | typing.Sequence | builtins.list:
|
436
|
+
if not isinstance(key, int):
|
437
|
+
raise TypeError(
|
438
|
+
"Unsupported Sequence type annotation",
|
439
|
+
self.__attribute__.__name__,
|
440
|
+
)
|
441
|
+
|
442
|
+
match get_args(self.__attribute__):
|
443
|
+
case (element_annotation,):
|
444
|
+
return AttributePath[Root, Any](
|
445
|
+
self.__root__,
|
446
|
+
*(
|
447
|
+
*self.__components__,
|
448
|
+
SequenceItemAttributePathComponent(
|
449
|
+
root=self.__attribute__, # pyright: ignore[reportArgumentType]
|
450
|
+
attribute=element_annotation,
|
451
|
+
index=key,
|
452
|
+
),
|
453
|
+
),
|
454
|
+
attribute=element_annotation,
|
455
|
+
)
|
456
|
+
|
457
|
+
case other:
|
458
|
+
raise TypeError(
|
459
|
+
"Unsupported Seqence type annotation",
|
460
|
+
self.__attribute__.__name__,
|
461
|
+
)
|
462
|
+
|
463
|
+
case other:
|
464
|
+
raise TypeError("Unsupported type annotation", other)
|
465
|
+
|
466
|
+
@overload
|
467
|
+
def __call__(
|
468
|
+
self,
|
469
|
+
root: Root,
|
470
|
+
/,
|
471
|
+
) -> Attribute: ...
|
472
|
+
|
473
|
+
@overload
|
474
|
+
def __call__(
|
475
|
+
self,
|
476
|
+
root: Root,
|
477
|
+
/,
|
478
|
+
updated: Attribute,
|
479
|
+
) -> Root: ...
|
480
|
+
|
481
|
+
def __call__(
|
482
|
+
self,
|
483
|
+
root: Root,
|
484
|
+
/,
|
485
|
+
updated: Attribute | Missing = MISSING,
|
486
|
+
) -> Root | Attribute:
|
487
|
+
assert isinstance(root, get_origin(self.__root__) or self.__root__), ( # nosec: B101
|
488
|
+
f"AttributePath '{self.__repr__()}' used on unexpected root of "
|
489
|
+
f"'{type(root).__name__}' instead of '{self.__root__.__name__}'"
|
490
|
+
)
|
491
|
+
|
492
|
+
if not_missing(updated):
|
493
|
+
assert isinstance(updated, get_origin(self.__attribute__) or self.__attribute__), ( # nosec: B101
|
494
|
+
f"AttributePath '{self.__repr__()}' assigning to unexpected value of "
|
495
|
+
f"'{type(updated).__name__}' instead of '{self.__attribute__.__name__}'"
|
496
|
+
)
|
497
|
+
|
498
|
+
resolved: Any = root
|
499
|
+
updates_stack: deque[tuple[Any, AttributePathComponent]] = deque()
|
500
|
+
for component in self.__components__:
|
501
|
+
updates_stack.append((resolved, component))
|
502
|
+
resolved = component.access(resolved)
|
503
|
+
|
504
|
+
updated_value: Any = updated
|
505
|
+
while updates_stack:
|
506
|
+
subject, component = updates_stack.pop()
|
507
|
+
updated_value = component.assigning(
|
508
|
+
subject,
|
509
|
+
value=updated_value,
|
510
|
+
)
|
511
|
+
|
512
|
+
return updated_value
|
513
|
+
|
514
|
+
else:
|
515
|
+
resolved: Any = root
|
516
|
+
for component in self.__components__:
|
517
|
+
resolved = component.access(resolved)
|
518
|
+
|
519
|
+
assert isinstance(resolved, get_origin(self.__attribute__) or self.__attribute__), ( # nosec: B101
|
520
|
+
f"AttributePath '{self.__repr__()}' pointing to unexpected value of "
|
521
|
+
f"'{type(resolved).__name__}' instead of '{self.__attribute__.__name__}'"
|
522
|
+
)
|
523
|
+
|
524
|
+
return resolved
|
@@ -0,0 +1,229 @@
|
|
1
|
+
from collections.abc import Callable, Collection, Iterable
|
2
|
+
from typing import Any, Literal, Self, cast, final
|
3
|
+
|
4
|
+
from haiway.state.path import AttributePath
|
5
|
+
from haiway.utils import freeze
|
6
|
+
|
7
|
+
__all__ = [
|
8
|
+
"AttributeRequirement",
|
9
|
+
]
|
10
|
+
|
11
|
+
|
12
|
+
@final
|
13
|
+
class AttributeRequirement[Root]:
|
14
|
+
@classmethod
|
15
|
+
def equal[Parameter](
|
16
|
+
cls,
|
17
|
+
value: Parameter,
|
18
|
+
/,
|
19
|
+
path: AttributePath[Root, Parameter] | Parameter,
|
20
|
+
) -> Self:
|
21
|
+
assert isinstance( # nosec: B101
|
22
|
+
path, AttributePath
|
23
|
+
), "Prepare attribute path by using Self._.path.to.property or explicitly"
|
24
|
+
|
25
|
+
def check_equal(root: Root) -> None:
|
26
|
+
checked: Any = cast(AttributePath[Root, Parameter], path)(root)
|
27
|
+
if checked != value:
|
28
|
+
raise ValueError(f"{checked} is not equal {value} for '{path.__repr__()}'")
|
29
|
+
|
30
|
+
return cls(
|
31
|
+
path,
|
32
|
+
"equal",
|
33
|
+
value,
|
34
|
+
check=check_equal,
|
35
|
+
)
|
36
|
+
|
37
|
+
@classmethod
|
38
|
+
def not_equal[Parameter](
|
39
|
+
cls,
|
40
|
+
value: Parameter,
|
41
|
+
/,
|
42
|
+
path: AttributePath[Root, Parameter] | Parameter,
|
43
|
+
) -> Self:
|
44
|
+
assert isinstance( # nosec: B101
|
45
|
+
path, AttributePath
|
46
|
+
), "Prepare attribute path by using Self._.path.to.property or explicitly"
|
47
|
+
|
48
|
+
def check_not_equal(root: Root) -> None:
|
49
|
+
checked: Any = cast(AttributePath[Root, Parameter], path)(root)
|
50
|
+
if checked == value:
|
51
|
+
raise ValueError(f"{checked} is equal {value} for '{path.__repr__()}'")
|
52
|
+
|
53
|
+
return cls(
|
54
|
+
path,
|
55
|
+
"not_equal",
|
56
|
+
value,
|
57
|
+
check=check_not_equal,
|
58
|
+
)
|
59
|
+
|
60
|
+
@classmethod
|
61
|
+
def contains[Parameter](
|
62
|
+
cls,
|
63
|
+
value: Parameter,
|
64
|
+
/,
|
65
|
+
path: AttributePath[
|
66
|
+
Root,
|
67
|
+
Collection[Parameter] | tuple[Parameter, ...] | list[Parameter] | set[Parameter],
|
68
|
+
]
|
69
|
+
| Collection[Parameter]
|
70
|
+
| tuple[Parameter, ...]
|
71
|
+
| list[Parameter]
|
72
|
+
| set[Parameter],
|
73
|
+
) -> Self:
|
74
|
+
assert isinstance( # nosec: B101
|
75
|
+
path, AttributePath
|
76
|
+
), "Prepare attribute path by using Self._.path.to.property or explicitly"
|
77
|
+
|
78
|
+
def check_contains(root: Root) -> None:
|
79
|
+
checked: Any = cast(AttributePath[Root, Parameter], path)(root)
|
80
|
+
if value not in checked:
|
81
|
+
raise ValueError(f"{checked} does not contain {value} for '{path.__repr__()}'")
|
82
|
+
|
83
|
+
return cls(
|
84
|
+
path,
|
85
|
+
"contains",
|
86
|
+
value,
|
87
|
+
check=check_contains,
|
88
|
+
)
|
89
|
+
|
90
|
+
@classmethod
|
91
|
+
def contains_any[Parameter](
|
92
|
+
cls,
|
93
|
+
value: Collection[Parameter],
|
94
|
+
/,
|
95
|
+
path: AttributePath[
|
96
|
+
Root,
|
97
|
+
Collection[Parameter] | tuple[Parameter, ...] | list[Parameter] | set[Parameter],
|
98
|
+
]
|
99
|
+
| Collection[Parameter]
|
100
|
+
| tuple[Parameter, ...]
|
101
|
+
| list[Parameter]
|
102
|
+
| set[Parameter],
|
103
|
+
) -> Self:
|
104
|
+
assert isinstance( # nosec: B101
|
105
|
+
path, AttributePath
|
106
|
+
), "Prepare attribute path by using Self._.path.to.property or explicitly"
|
107
|
+
|
108
|
+
def check_contains_any(root: Root) -> None:
|
109
|
+
checked: Any = cast(AttributePath[Root, Parameter], path)(root)
|
110
|
+
if any(element in checked for element in value):
|
111
|
+
raise ValueError(
|
112
|
+
f"{checked} does not contain any of {value} for '{path.__repr__()}'"
|
113
|
+
)
|
114
|
+
|
115
|
+
return cls(
|
116
|
+
path,
|
117
|
+
"contains_any",
|
118
|
+
value,
|
119
|
+
check=check_contains_any,
|
120
|
+
)
|
121
|
+
|
122
|
+
@classmethod
|
123
|
+
def contained_in[Parameter](
|
124
|
+
cls,
|
125
|
+
value: Collection[Parameter],
|
126
|
+
/,
|
127
|
+
path: AttributePath[Root, Parameter] | Parameter,
|
128
|
+
) -> Self:
|
129
|
+
assert isinstance( # nosec: B101
|
130
|
+
path, AttributePath
|
131
|
+
), "Prepare attribute path by using Self._.path.to.property or explicitly"
|
132
|
+
|
133
|
+
def check_contained_in(root: Root) -> None:
|
134
|
+
checked: Any = cast(AttributePath[Root, Parameter], path)(root)
|
135
|
+
if checked not in value:
|
136
|
+
raise ValueError(f"{value} does not contain {checked} for '{path.__repr__()}'")
|
137
|
+
|
138
|
+
return cls(
|
139
|
+
value,
|
140
|
+
"contained_in",
|
141
|
+
path,
|
142
|
+
check=check_contained_in,
|
143
|
+
)
|
144
|
+
|
145
|
+
def __init__(
|
146
|
+
self,
|
147
|
+
lhs: Any,
|
148
|
+
operator: Literal[
|
149
|
+
"equal",
|
150
|
+
"not_equal",
|
151
|
+
"contains",
|
152
|
+
"contains_any",
|
153
|
+
"contained_in",
|
154
|
+
"and",
|
155
|
+
"or",
|
156
|
+
],
|
157
|
+
rhs: Any,
|
158
|
+
check: Callable[[Root], None],
|
159
|
+
) -> None:
|
160
|
+
self.lhs: Any = lhs
|
161
|
+
self.operator: Literal[
|
162
|
+
"equal",
|
163
|
+
"not_equal",
|
164
|
+
"contains",
|
165
|
+
"contains_any",
|
166
|
+
"contained_in",
|
167
|
+
"and",
|
168
|
+
"or",
|
169
|
+
] = operator
|
170
|
+
self.rhs: Any = rhs
|
171
|
+
self._check: Callable[[Root], None] = check
|
172
|
+
|
173
|
+
freeze(self)
|
174
|
+
|
175
|
+
def __and__(
|
176
|
+
self,
|
177
|
+
other: Self,
|
178
|
+
) -> Self:
|
179
|
+
def check_and(root: Root) -> None:
|
180
|
+
self.check(root)
|
181
|
+
other.check(root)
|
182
|
+
|
183
|
+
return self.__class__(
|
184
|
+
self,
|
185
|
+
"and",
|
186
|
+
other,
|
187
|
+
check=check_and,
|
188
|
+
)
|
189
|
+
|
190
|
+
def __or__(
|
191
|
+
self,
|
192
|
+
other: Self,
|
193
|
+
) -> Self:
|
194
|
+
def check_or(root: Root) -> None:
|
195
|
+
try:
|
196
|
+
self.check(root)
|
197
|
+
except ValueError:
|
198
|
+
other.check(root)
|
199
|
+
|
200
|
+
return self.__class__(
|
201
|
+
self,
|
202
|
+
"or",
|
203
|
+
other,
|
204
|
+
check=check_or,
|
205
|
+
)
|
206
|
+
|
207
|
+
def check(
|
208
|
+
self,
|
209
|
+
root: Root,
|
210
|
+
/,
|
211
|
+
*,
|
212
|
+
raise_exception: bool = True,
|
213
|
+
) -> bool:
|
214
|
+
try:
|
215
|
+
self._check(root)
|
216
|
+
return True
|
217
|
+
|
218
|
+
except Exception as exc:
|
219
|
+
if raise_exception:
|
220
|
+
raise exc
|
221
|
+
|
222
|
+
else:
|
223
|
+
return False
|
224
|
+
|
225
|
+
def filter(
|
226
|
+
self,
|
227
|
+
values: Iterable[Root],
|
228
|
+
) -> list[Root]:
|
229
|
+
return [value for value in values if self.check(value, raise_exception=False)]
|
haiway/state/structure.py
CHANGED
@@ -15,6 +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.path import AttributePath
|
18
19
|
from haiway.state.validation import attribute_validator
|
19
20
|
from haiway.types import MISSING, Missing, not_missing
|
20
21
|
|
@@ -86,6 +87,7 @@ class StateMeta(type):
|
|
86
87
|
state_type.__ATTRIBUTES__ = attributes # pyright: ignore[reportAttributeAccessIssue]
|
87
88
|
state_type.__slots__ = frozenset(attributes.keys()) # pyright: ignore[reportAttributeAccessIssue]
|
88
89
|
state_type.__match_args__ = state_type.__slots__ # pyright: ignore[reportAttributeAccessIssue]
|
90
|
+
state_type._ = AttributePath(state_type, attribute=state_type) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
89
91
|
|
90
92
|
return state_type
|
91
93
|
|
@@ -104,6 +106,7 @@ class State(metaclass=StateMeta):
|
|
104
106
|
Base class for immutable data structures.
|
105
107
|
"""
|
106
108
|
|
109
|
+
_: ClassVar[Self]
|
107
110
|
__IMMUTABLE__: ClassVar[EllipsisType] = ...
|
108
111
|
__ATTRIBUTES__: ClassVar[dict[str, StateAttribute[Any]]]
|
109
112
|
|
@@ -178,6 +181,18 @@ class State(metaclass=StateMeta):
|
|
178
181
|
),
|
179
182
|
)
|
180
183
|
|
184
|
+
def updating[Value](
|
185
|
+
self,
|
186
|
+
path: AttributePath[Self, Value] | Value,
|
187
|
+
/,
|
188
|
+
value: Value,
|
189
|
+
) -> Self:
|
190
|
+
assert isinstance( # nosec: B101
|
191
|
+
path, AttributePath
|
192
|
+
), "Prepare parameter path by using Self._.path.to.property or explicitly"
|
193
|
+
|
194
|
+
return cast(AttributePath[Self, Value], path)(self, updated=value)
|
195
|
+
|
181
196
|
def updated(
|
182
197
|
self,
|
183
198
|
**kwargs: Any,
|
haiway/state/validation.py
CHANGED
@@ -5,6 +5,9 @@ from pathlib import Path
|
|
5
5
|
from re import Pattern
|
6
6
|
from types import MappingProxyType, NoneType, UnionType
|
7
7
|
from typing import Any, Literal, Protocol, Union
|
8
|
+
from typing import Mapping as MappingType # noqa: UP035
|
9
|
+
from typing import Sequence as SequenceType # noqa: UP035
|
10
|
+
from typing import Sequence as SetType # noqa: UP035
|
8
11
|
from uuid import UUID
|
9
12
|
|
10
13
|
from haiway.state.attributes import AttributeAnnotation
|
@@ -128,7 +131,7 @@ def _prepare_validator_of_set(
|
|
128
131
|
def validator(
|
129
132
|
value: Any,
|
130
133
|
) -> Any:
|
131
|
-
if isinstance(value,
|
134
|
+
if isinstance(value, set):
|
132
135
|
return frozenset(element_validator(element) for element in value) # pyright: ignore[reportUnknownVariableType]
|
133
136
|
|
134
137
|
else:
|
@@ -171,7 +174,7 @@ def _prepare_validator_of_mapping(
|
|
171
174
|
match value:
|
172
175
|
case {**elements}:
|
173
176
|
return MappingProxyType(
|
174
|
-
{key_validator(key): value_validator(value) for key, value in elements}
|
177
|
+
{key_validator(key): value_validator(value) for key, value in elements.items()}
|
175
178
|
)
|
176
179
|
|
177
180
|
case _:
|
@@ -291,9 +294,13 @@ VALIDATORS: Mapping[Any, Callable[[AttributeAnnotation], Callable[[Any], Any]]]
|
|
291
294
|
tuple: _prepare_validator_of_tuple,
|
292
295
|
frozenset: _prepare_validator_of_set,
|
293
296
|
Literal: _prepare_validator_of_literal,
|
297
|
+
set: _prepare_validator_of_set,
|
294
298
|
Set: _prepare_validator_of_set,
|
299
|
+
SetType: _prepare_validator_of_set,
|
295
300
|
Sequence: _prepare_validator_of_sequence,
|
301
|
+
SequenceType: _prepare_validator_of_sequence,
|
296
302
|
Mapping: _prepare_validator_of_mapping,
|
303
|
+
MappingType: _prepare_validator_of_mapping,
|
297
304
|
range: _prepare_validator_of_type,
|
298
305
|
UUID: _prepare_validator_of_type,
|
299
306
|
date: _prepare_validator_of_type,
|
@@ -1,4 +1,4 @@
|
|
1
|
-
haiway/__init__.py,sha256=
|
1
|
+
haiway/__init__.py,sha256=GslXcxpiKSKmcWlsvj25X7uDZzuUDUUL8pxAKFqbvIs,1387
|
2
2
|
haiway/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
haiway/context/__init__.py,sha256=ZgQoQFUqfPDqeIbhS898C3dP02QzOCRmClVQpHaPTBA,336
|
4
4
|
haiway/context/access.py,sha256=CB-F9Yd6EAoJIqzMQGid9szww7aFiti5z6x0T79LV7k,14098
|
@@ -14,10 +14,12 @@ haiway/helpers/retries.py,sha256=gIkyUlqJLDYaxIZd3qzeqGFY9y5Gp8dgZLlZ6hs8hoc,753
|
|
14
14
|
haiway/helpers/throttling.py,sha256=zo0OwFq64si5KUwhd58cFHLmGAmYwRbFRJMbv9suhPs,3844
|
15
15
|
haiway/helpers/timeouted.py,sha256=1xU09hQnFdj6p48BwZl5xUvtIr3zC0ZUXehkdrduCjs,3074
|
16
16
|
haiway/helpers/tracing.py,sha256=eQpkIoGSB51jRF8RcLaihvHX3VzJIRdyRxTx3I14Pzg,3346
|
17
|
-
haiway/state/__init__.py,sha256=
|
18
|
-
haiway/state/attributes.py,sha256=
|
19
|
-
haiway/state/
|
20
|
-
haiway/state/
|
17
|
+
haiway/state/__init__.py,sha256=emTuwGFn7HyjyTJ_ass69J5jQIA7_WHO4teZz_dR05Y,355
|
18
|
+
haiway/state/attributes.py,sha256=OVUHp0_OwDwqJa-4Rk_diQhIpBYg0PW9APU3p_UTjd0,13635
|
19
|
+
haiway/state/path.py,sha256=4vh-fYQv8_xRWjS0ErMQslKDWRI6-KVECAr8JhYk0UY,17503
|
20
|
+
haiway/state/requirement.py,sha256=3iQqzp5Q7w6y5uClamJGH7S5Hib9pciuTAV27PP5lS8,6161
|
21
|
+
haiway/state/structure.py,sha256=_1K_RSqA20ufcMay6i2CthMkgJeF1gZi7Hr4rUiCJs0,7869
|
22
|
+
haiway/state/validation.py,sha256=l3NHHYVEr1M9F3HrV4hXA1gCibp7Uj5_nZWXMuy2tsU,9089
|
21
23
|
haiway/types/__init__.py,sha256=00Ulp2BxcIWm9vWXKQPodpFEwE8hpqj6OYgrNxelp5s,252
|
22
24
|
haiway/types/frozen.py,sha256=CZhFCXnWAKEhuWSfILxA8smfdpMd5Ku694ycfLh98R8,76
|
23
25
|
haiway/types/missing.py,sha256=JiXo5xdi7H-PbIJr0fuK5wpOuQZhjrDYUkMlfIFcsaE,1705
|
@@ -29,8 +31,8 @@ haiway/utils/logs.py,sha256=oDsc1ZdqKDjlTlctLbDcp9iX98Acr-1tdw-Pyg3DElo,1577
|
|
29
31
|
haiway/utils/mimic.py,sha256=BkVjTVP2TxxC8GChPGyDV6UXVwJmiRiSWeOYZNZFHxs,1828
|
30
32
|
haiway/utils/noop.py,sha256=qgbZlOKWY6_23Zs43OLukK2HagIQKRyR04zrFVm5rWI,344
|
31
33
|
haiway/utils/queue.py,sha256=oQ3GXCJ-PGNtMEr6EPdgqAvYZoj8lAa7Z2drBKBEoBM,2345
|
32
|
-
haiway-0.6.
|
33
|
-
haiway-0.6.
|
34
|
-
haiway-0.6.
|
35
|
-
haiway-0.6.
|
36
|
-
haiway-0.6.
|
34
|
+
haiway-0.6.4.dist-info/LICENSE,sha256=GehQEW_I1pkmxkkj3NEa7rCTQKYBn7vTPabpDYJlRuo,1063
|
35
|
+
haiway-0.6.4.dist-info/METADATA,sha256=Vx_AwXB2HZvP8BIN3EdA9l4gk7YdeILRy72qZ3yPgf8,3898
|
36
|
+
haiway-0.6.4.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
37
|
+
haiway-0.6.4.dist-info/top_level.txt,sha256=_LdXVLzUzgkvAGQnQJj5kQfoFhpPW6EF4Kj9NapniLg,7
|
38
|
+
haiway-0.6.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|