haiway 0.10.15__py3-none-any.whl → 0.10.17__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 +111 -0
- haiway/context/__init__.py +27 -0
- haiway/context/access.py +615 -0
- haiway/context/disposables.py +78 -0
- haiway/context/identifier.py +92 -0
- haiway/context/logging.py +176 -0
- haiway/context/metrics.py +165 -0
- haiway/context/state.py +113 -0
- haiway/context/tasks.py +64 -0
- haiway/context/types.py +12 -0
- haiway/helpers/__init__.py +21 -0
- haiway/helpers/asynchrony.py +225 -0
- haiway/helpers/caching.py +326 -0
- haiway/helpers/metrics.py +459 -0
- haiway/helpers/retries.py +223 -0
- haiway/helpers/throttling.py +133 -0
- haiway/helpers/timeouted.py +112 -0
- haiway/helpers/tracing.py +137 -0
- haiway/py.typed +0 -0
- haiway/state/__init__.py +12 -0
- haiway/state/attributes.py +747 -0
- haiway/state/path.py +542 -0
- haiway/state/requirement.py +229 -0
- haiway/state/structure.py +414 -0
- haiway/state/validation.py +468 -0
- haiway/types/__init__.py +14 -0
- haiway/types/default.py +108 -0
- haiway/types/frozen.py +5 -0
- haiway/types/missing.py +95 -0
- haiway/utils/__init__.py +28 -0
- haiway/utils/always.py +61 -0
- haiway/utils/collections.py +185 -0
- haiway/utils/env.py +230 -0
- haiway/utils/freezing.py +28 -0
- haiway/utils/logs.py +57 -0
- haiway/utils/mimic.py +77 -0
- haiway/utils/noop.py +24 -0
- haiway/utils/queue.py +82 -0
- {haiway-0.10.15.dist-info → haiway-0.10.17.dist-info}/METADATA +1 -1
- haiway-0.10.17.dist-info/RECORD +42 -0
- haiway-0.10.15.dist-info/RECORD +0 -4
- {haiway-0.10.15.dist-info → haiway-0.10.17.dist-info}/WHEEL +0 -0
- {haiway-0.10.15.dist-info → haiway-0.10.17.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,468 @@
|
|
1
|
+
from collections.abc import Callable, Mapping, MutableMapping, Sequence, Set
|
2
|
+
from datetime import date, datetime, time, timedelta, timezone
|
3
|
+
from enum import Enum
|
4
|
+
from pathlib import Path
|
5
|
+
from re import Pattern
|
6
|
+
from types import EllipsisType, NoneType, UnionType
|
7
|
+
from typing import Any, Literal, Protocol, Self, Union, is_typeddict
|
8
|
+
from uuid import UUID
|
9
|
+
|
10
|
+
from haiway.state.attributes import AttributeAnnotation
|
11
|
+
from haiway.types import MISSING, Missing
|
12
|
+
|
13
|
+
__all__ = [
|
14
|
+
"AttributeValidation",
|
15
|
+
"AttributeValidationError",
|
16
|
+
"AttributeValidator",
|
17
|
+
]
|
18
|
+
|
19
|
+
|
20
|
+
class AttributeValidation[Type](Protocol):
|
21
|
+
def __call__(
|
22
|
+
self,
|
23
|
+
value: Any,
|
24
|
+
/,
|
25
|
+
) -> Type: ...
|
26
|
+
|
27
|
+
|
28
|
+
class AttributeValidationError(Exception):
|
29
|
+
pass
|
30
|
+
|
31
|
+
|
32
|
+
class AttributeValidator[Type]:
|
33
|
+
@classmethod
|
34
|
+
def of(
|
35
|
+
cls,
|
36
|
+
annotation: AttributeAnnotation,
|
37
|
+
/,
|
38
|
+
*,
|
39
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
40
|
+
) -> AttributeValidation[Any]:
|
41
|
+
if isinstance(annotation.origin, NotImplementedError | RuntimeError):
|
42
|
+
raise annotation.origin # raise an error if origin was not properly resolved
|
43
|
+
|
44
|
+
if recursive := recursion_guard.get(str(annotation)):
|
45
|
+
return recursive
|
46
|
+
|
47
|
+
validator: Self = cls(
|
48
|
+
annotation,
|
49
|
+
validation=MISSING,
|
50
|
+
)
|
51
|
+
recursion_guard[str(annotation)] = validator
|
52
|
+
|
53
|
+
if common := VALIDATORS.get(annotation.origin):
|
54
|
+
validator.validation = common(annotation, recursion_guard)
|
55
|
+
|
56
|
+
elif hasattr(annotation.origin, "__IMMUTABLE__"):
|
57
|
+
validator.validation = _prepare_validator_of_type(annotation, recursion_guard)
|
58
|
+
|
59
|
+
elif is_typeddict(annotation.origin):
|
60
|
+
validator.validation = _prepare_validator_of_typed_dict(annotation, recursion_guard)
|
61
|
+
|
62
|
+
elif issubclass(annotation.origin, Protocol):
|
63
|
+
validator.validation = _prepare_validator_of_type(annotation, recursion_guard)
|
64
|
+
|
65
|
+
elif issubclass(annotation.origin, Enum):
|
66
|
+
validator.validation = _prepare_validator_of_type(annotation, recursion_guard)
|
67
|
+
|
68
|
+
else:
|
69
|
+
raise TypeError(f"Unsupported type annotation: {annotation}")
|
70
|
+
|
71
|
+
return validator
|
72
|
+
|
73
|
+
def __init__(
|
74
|
+
self,
|
75
|
+
annotation: AttributeAnnotation,
|
76
|
+
validation: AttributeValidation[Type] | Missing,
|
77
|
+
) -> None:
|
78
|
+
self.annotation: AttributeAnnotation = annotation
|
79
|
+
self.validation: AttributeValidation[Type] | Missing = validation
|
80
|
+
|
81
|
+
def __call__(
|
82
|
+
self,
|
83
|
+
value: Any,
|
84
|
+
/,
|
85
|
+
) -> Any:
|
86
|
+
assert self.validation is not MISSING # nosec: B101
|
87
|
+
return self.validation(value) # pyright: ignore[reportCallIssue, reportUnknownVariableType]
|
88
|
+
|
89
|
+
def __str__(self) -> str:
|
90
|
+
return f"Validator[{self.annotation}]"
|
91
|
+
|
92
|
+
def __repr__(self) -> str:
|
93
|
+
return f"Validator[{self.annotation}]"
|
94
|
+
|
95
|
+
|
96
|
+
def _prepare_validator_of_any(
|
97
|
+
annotation: AttributeAnnotation,
|
98
|
+
/,
|
99
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
100
|
+
) -> AttributeValidation[Any]:
|
101
|
+
def validator(
|
102
|
+
value: Any,
|
103
|
+
/,
|
104
|
+
) -> Any:
|
105
|
+
return value # any is always valid
|
106
|
+
|
107
|
+
return validator
|
108
|
+
|
109
|
+
|
110
|
+
def _prepare_validator_of_none(
|
111
|
+
annotation: AttributeAnnotation,
|
112
|
+
/,
|
113
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
114
|
+
) -> AttributeValidation[Any]:
|
115
|
+
def validator(
|
116
|
+
value: Any,
|
117
|
+
/,
|
118
|
+
) -> Any:
|
119
|
+
if value is None:
|
120
|
+
return value
|
121
|
+
|
122
|
+
else:
|
123
|
+
raise TypeError(f"'{value}' is not matching expected type of 'None'")
|
124
|
+
|
125
|
+
return validator
|
126
|
+
|
127
|
+
|
128
|
+
def _prepare_validator_of_missing(
|
129
|
+
annotation: AttributeAnnotation,
|
130
|
+
/,
|
131
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
132
|
+
) -> AttributeValidation[Any]:
|
133
|
+
def validator(
|
134
|
+
value: Any,
|
135
|
+
/,
|
136
|
+
) -> Any:
|
137
|
+
if value is MISSING:
|
138
|
+
return value
|
139
|
+
|
140
|
+
else:
|
141
|
+
raise TypeError(f"'{value}' is not matching expected type of 'Missing'")
|
142
|
+
|
143
|
+
return validator
|
144
|
+
|
145
|
+
|
146
|
+
def _prepare_validator_of_literal(
|
147
|
+
annotation: AttributeAnnotation,
|
148
|
+
/,
|
149
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
150
|
+
) -> AttributeValidation[Any]:
|
151
|
+
elements: Sequence[Any] = annotation.arguments
|
152
|
+
formatted_type: str = str(annotation)
|
153
|
+
|
154
|
+
def validator(
|
155
|
+
value: Any,
|
156
|
+
/,
|
157
|
+
) -> Any:
|
158
|
+
if value in elements:
|
159
|
+
return value
|
160
|
+
|
161
|
+
else:
|
162
|
+
raise ValueError(f"'{value}' is not matching expected values of '{formatted_type}'")
|
163
|
+
|
164
|
+
return validator
|
165
|
+
|
166
|
+
|
167
|
+
def _prepare_validator_of_type(
|
168
|
+
annotation: AttributeAnnotation,
|
169
|
+
/,
|
170
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
171
|
+
) -> AttributeValidation[Any]:
|
172
|
+
validated_type: type[Any] = annotation.origin
|
173
|
+
formatted_type: str = str(annotation)
|
174
|
+
|
175
|
+
def validator(
|
176
|
+
value: Any,
|
177
|
+
/,
|
178
|
+
) -> Any:
|
179
|
+
match value:
|
180
|
+
case value if isinstance(value, validated_type):
|
181
|
+
return value
|
182
|
+
|
183
|
+
case _:
|
184
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
185
|
+
|
186
|
+
return validator
|
187
|
+
|
188
|
+
|
189
|
+
def _prepare_validator_of_set(
|
190
|
+
annotation: AttributeAnnotation,
|
191
|
+
/,
|
192
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
193
|
+
) -> AttributeValidation[Any]:
|
194
|
+
element_validator: AttributeValidation[Any] = AttributeValidator.of(
|
195
|
+
annotation.arguments[0],
|
196
|
+
recursion_guard=recursion_guard,
|
197
|
+
)
|
198
|
+
formatted_type: str = str(annotation)
|
199
|
+
|
200
|
+
def validator(
|
201
|
+
value: Any,
|
202
|
+
/,
|
203
|
+
) -> Any:
|
204
|
+
if isinstance(value, set):
|
205
|
+
return frozenset(element_validator(element) for element in value) # pyright: ignore[reportUnknownVariableType]
|
206
|
+
|
207
|
+
else:
|
208
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
209
|
+
|
210
|
+
return validator
|
211
|
+
|
212
|
+
|
213
|
+
def _prepare_validator_of_sequence(
|
214
|
+
annotation: AttributeAnnotation,
|
215
|
+
/,
|
216
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
217
|
+
) -> AttributeValidation[Any]:
|
218
|
+
element_validator: AttributeValidation[Any] = AttributeValidator.of(
|
219
|
+
annotation.arguments[0],
|
220
|
+
recursion_guard=recursion_guard,
|
221
|
+
)
|
222
|
+
formatted_type: str = str(annotation)
|
223
|
+
|
224
|
+
def validator(
|
225
|
+
value: Any,
|
226
|
+
/,
|
227
|
+
) -> Any:
|
228
|
+
match value:
|
229
|
+
case [*elements]:
|
230
|
+
return tuple(element_validator(element) for element in elements)
|
231
|
+
|
232
|
+
case _:
|
233
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
234
|
+
|
235
|
+
return validator
|
236
|
+
|
237
|
+
|
238
|
+
def _prepare_validator_of_mapping(
|
239
|
+
annotation: AttributeAnnotation,
|
240
|
+
/,
|
241
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
242
|
+
) -> AttributeValidation[Any]:
|
243
|
+
key_validator: AttributeValidation[Any] = AttributeValidator.of(
|
244
|
+
annotation.arguments[0],
|
245
|
+
recursion_guard=recursion_guard,
|
246
|
+
)
|
247
|
+
value_validator: AttributeValidation[Any] = AttributeValidator.of(
|
248
|
+
annotation.arguments[1],
|
249
|
+
recursion_guard=recursion_guard,
|
250
|
+
)
|
251
|
+
formatted_type: str = str(annotation)
|
252
|
+
|
253
|
+
def validator(
|
254
|
+
value: Any,
|
255
|
+
/,
|
256
|
+
) -> Any:
|
257
|
+
match value:
|
258
|
+
case {**elements}:
|
259
|
+
# TODO: make sure dict is not mutable with MappingProxyType?
|
260
|
+
return {
|
261
|
+
key_validator(key): value_validator(element)
|
262
|
+
for key, element in elements.items()
|
263
|
+
}
|
264
|
+
|
265
|
+
case _:
|
266
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
267
|
+
|
268
|
+
return validator
|
269
|
+
|
270
|
+
|
271
|
+
def _prepare_validator_of_tuple(
|
272
|
+
annotation: AttributeAnnotation,
|
273
|
+
/,
|
274
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
275
|
+
) -> AttributeValidation[Any]:
|
276
|
+
if (
|
277
|
+
annotation.arguments[-1].origin == Ellipsis
|
278
|
+
or annotation.arguments[-1].origin == EllipsisType
|
279
|
+
):
|
280
|
+
element_validator: AttributeValidation[Any] = AttributeValidator.of(
|
281
|
+
annotation.arguments[0],
|
282
|
+
recursion_guard=recursion_guard,
|
283
|
+
)
|
284
|
+
formatted_type: str = str(annotation)
|
285
|
+
|
286
|
+
def validator(
|
287
|
+
value: Any,
|
288
|
+
/,
|
289
|
+
) -> Any:
|
290
|
+
match value:
|
291
|
+
case [*elements]:
|
292
|
+
return tuple(element_validator(element) for element in elements)
|
293
|
+
|
294
|
+
case _:
|
295
|
+
raise TypeError(
|
296
|
+
f"'{value}' is not matching expected type of '{formatted_type}'"
|
297
|
+
)
|
298
|
+
|
299
|
+
return validator
|
300
|
+
|
301
|
+
else:
|
302
|
+
element_validators: list[AttributeValidation[Any]] = [
|
303
|
+
AttributeValidator.of(alternative, recursion_guard=recursion_guard)
|
304
|
+
for alternative in annotation.arguments
|
305
|
+
]
|
306
|
+
elements_count: int = len(element_validators)
|
307
|
+
formatted_type: str = str(annotation)
|
308
|
+
|
309
|
+
def validator(
|
310
|
+
value: Any,
|
311
|
+
/,
|
312
|
+
) -> Any:
|
313
|
+
match value:
|
314
|
+
case [*elements]:
|
315
|
+
if len(elements) != elements_count:
|
316
|
+
raise ValueError(
|
317
|
+
f"'{value}' is not matching expected type of '{formatted_type}'"
|
318
|
+
)
|
319
|
+
|
320
|
+
return tuple(
|
321
|
+
element_validators[idx](element) for idx, element in enumerate(elements)
|
322
|
+
)
|
323
|
+
|
324
|
+
case _:
|
325
|
+
raise TypeError(
|
326
|
+
f"'{value}' is not matching expected type of '{formatted_type}'"
|
327
|
+
)
|
328
|
+
|
329
|
+
return validator
|
330
|
+
|
331
|
+
|
332
|
+
def _prepare_validator_of_union(
|
333
|
+
annotation: AttributeAnnotation,
|
334
|
+
/,
|
335
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
336
|
+
) -> AttributeValidation[Any]:
|
337
|
+
validators: list[AttributeValidation[Any]] = [
|
338
|
+
AttributeValidator.of(alternative, recursion_guard=recursion_guard)
|
339
|
+
for alternative in annotation.arguments
|
340
|
+
]
|
341
|
+
formatted_type: str = str(annotation)
|
342
|
+
|
343
|
+
def validator(
|
344
|
+
value: Any,
|
345
|
+
/,
|
346
|
+
) -> Any:
|
347
|
+
errors: list[Exception] = []
|
348
|
+
for validator in validators:
|
349
|
+
try:
|
350
|
+
return validator(value)
|
351
|
+
|
352
|
+
except Exception as exc:
|
353
|
+
errors.append(exc)
|
354
|
+
|
355
|
+
raise ExceptionGroup(
|
356
|
+
f"'{value}' is not matching expected type of '{formatted_type}'",
|
357
|
+
errors,
|
358
|
+
)
|
359
|
+
|
360
|
+
return validator
|
361
|
+
|
362
|
+
|
363
|
+
def _prepare_validator_of_callable(
|
364
|
+
annotation: AttributeAnnotation,
|
365
|
+
/,
|
366
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
367
|
+
) -> AttributeValidation[Any]:
|
368
|
+
formatted_type: str = str(annotation)
|
369
|
+
|
370
|
+
def validator(
|
371
|
+
value: Any,
|
372
|
+
/,
|
373
|
+
) -> Any:
|
374
|
+
if callable(value):
|
375
|
+
# TODO: we could verify callable signature here
|
376
|
+
return value
|
377
|
+
|
378
|
+
else:
|
379
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
380
|
+
|
381
|
+
return validator
|
382
|
+
|
383
|
+
|
384
|
+
def _prepare_validator_of_typed_dict(
|
385
|
+
annotation: AttributeAnnotation,
|
386
|
+
/,
|
387
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
388
|
+
) -> AttributeValidation[Any]:
|
389
|
+
def key_validator(
|
390
|
+
value: Any,
|
391
|
+
/,
|
392
|
+
) -> Any:
|
393
|
+
match value:
|
394
|
+
case value if isinstance(value, str):
|
395
|
+
return value
|
396
|
+
|
397
|
+
case _:
|
398
|
+
raise TypeError(f"'{value}' is not matching expected type of 'str'")
|
399
|
+
|
400
|
+
formatted_type: str = str(annotation)
|
401
|
+
values_validators: dict[str, AttributeValidation[Any]] = {
|
402
|
+
key: AttributeValidator.of(element, recursion_guard=recursion_guard)
|
403
|
+
for key, element in annotation.extra["attributes"].items()
|
404
|
+
}
|
405
|
+
required_values: Set[str] = annotation.extra["required"]
|
406
|
+
|
407
|
+
def validator(
|
408
|
+
value: Any,
|
409
|
+
/,
|
410
|
+
) -> Any:
|
411
|
+
match value:
|
412
|
+
case {**elements}:
|
413
|
+
validated: MutableMapping[str, Any] = {}
|
414
|
+
for key, validator in values_validators.items():
|
415
|
+
element: Any = elements.get(key, MISSING)
|
416
|
+
if element is MISSING and key not in required_values:
|
417
|
+
continue # skip missing and not required
|
418
|
+
|
419
|
+
validated[key_validator(key)] = validator(element)
|
420
|
+
|
421
|
+
# TODO: make sure dict is not mutable with MappingProxyType?
|
422
|
+
return validated
|
423
|
+
|
424
|
+
case _:
|
425
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
426
|
+
|
427
|
+
return validator
|
428
|
+
|
429
|
+
|
430
|
+
VALIDATORS: Mapping[
|
431
|
+
Any,
|
432
|
+
Callable[
|
433
|
+
[
|
434
|
+
AttributeAnnotation,
|
435
|
+
MutableMapping[str, AttributeValidation[Any]],
|
436
|
+
],
|
437
|
+
AttributeValidation[Any],
|
438
|
+
],
|
439
|
+
] = {
|
440
|
+
Any: _prepare_validator_of_any,
|
441
|
+
NoneType: _prepare_validator_of_none,
|
442
|
+
Missing: _prepare_validator_of_missing,
|
443
|
+
type: _prepare_validator_of_type,
|
444
|
+
bool: _prepare_validator_of_type,
|
445
|
+
int: _prepare_validator_of_type,
|
446
|
+
float: _prepare_validator_of_type,
|
447
|
+
complex: _prepare_validator_of_type,
|
448
|
+
bytes: _prepare_validator_of_type,
|
449
|
+
str: _prepare_validator_of_type,
|
450
|
+
tuple: _prepare_validator_of_tuple,
|
451
|
+
frozenset: _prepare_validator_of_set,
|
452
|
+
Set: _prepare_validator_of_set,
|
453
|
+
Sequence: _prepare_validator_of_sequence,
|
454
|
+
Mapping: _prepare_validator_of_mapping,
|
455
|
+
Literal: _prepare_validator_of_literal,
|
456
|
+
range: _prepare_validator_of_type,
|
457
|
+
UUID: _prepare_validator_of_type,
|
458
|
+
date: _prepare_validator_of_type,
|
459
|
+
datetime: _prepare_validator_of_type,
|
460
|
+
time: _prepare_validator_of_type,
|
461
|
+
timedelta: _prepare_validator_of_type,
|
462
|
+
timezone: _prepare_validator_of_type,
|
463
|
+
Path: _prepare_validator_of_type,
|
464
|
+
Pattern: _prepare_validator_of_type,
|
465
|
+
Union: _prepare_validator_of_union,
|
466
|
+
UnionType: _prepare_validator_of_union,
|
467
|
+
Callable: _prepare_validator_of_callable,
|
468
|
+
}
|
haiway/types/__init__.py
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
from haiway.types.default import Default, DefaultValue
|
2
|
+
from haiway.types.frozen import frozenlist
|
3
|
+
from haiway.types.missing import MISSING, Missing, is_missing, not_missing, when_missing
|
4
|
+
|
5
|
+
__all__ = [
|
6
|
+
"MISSING",
|
7
|
+
"Default",
|
8
|
+
"DefaultValue",
|
9
|
+
"Missing",
|
10
|
+
"frozenlist",
|
11
|
+
"is_missing",
|
12
|
+
"not_missing",
|
13
|
+
"when_missing",
|
14
|
+
]
|
haiway/types/default.py
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
from collections.abc import Callable
|
2
|
+
from typing import Any, cast, final, overload
|
3
|
+
|
4
|
+
from haiway.types.missing import MISSING, Missing, not_missing
|
5
|
+
from haiway.utils.always import always
|
6
|
+
|
7
|
+
__all__ = [
|
8
|
+
"Default",
|
9
|
+
"DefaultValue",
|
10
|
+
]
|
11
|
+
|
12
|
+
|
13
|
+
@final
|
14
|
+
class DefaultValue[Value]:
|
15
|
+
@overload
|
16
|
+
def __init__(
|
17
|
+
self,
|
18
|
+
value: Value,
|
19
|
+
/,
|
20
|
+
) -> None: ...
|
21
|
+
|
22
|
+
@overload
|
23
|
+
def __init__(
|
24
|
+
self,
|
25
|
+
/,
|
26
|
+
*,
|
27
|
+
factory: Callable[[], Value],
|
28
|
+
) -> None: ...
|
29
|
+
|
30
|
+
@overload
|
31
|
+
def __init__(
|
32
|
+
self,
|
33
|
+
value: Value | Missing,
|
34
|
+
/,
|
35
|
+
*,
|
36
|
+
factory: Callable[[], Value] | Missing,
|
37
|
+
) -> None: ...
|
38
|
+
|
39
|
+
def __init__(
|
40
|
+
self,
|
41
|
+
value: Value | Missing = MISSING,
|
42
|
+
/,
|
43
|
+
*,
|
44
|
+
factory: Callable[[], Value] | Missing = MISSING,
|
45
|
+
) -> None:
|
46
|
+
assert ( # nosec: B101
|
47
|
+
value is MISSING or factory is MISSING
|
48
|
+
), "Can't specify both default value and factory"
|
49
|
+
|
50
|
+
self._value: Callable[[], Value | Missing]
|
51
|
+
if not_missing(factory):
|
52
|
+
object.__setattr__(
|
53
|
+
self,
|
54
|
+
"_value",
|
55
|
+
factory,
|
56
|
+
)
|
57
|
+
|
58
|
+
else:
|
59
|
+
object.__setattr__(
|
60
|
+
self,
|
61
|
+
"_value",
|
62
|
+
always(value),
|
63
|
+
)
|
64
|
+
|
65
|
+
def __call__(self) -> Value | Missing:
|
66
|
+
return self._value()
|
67
|
+
|
68
|
+
def __setattr__(
|
69
|
+
self,
|
70
|
+
__name: str,
|
71
|
+
__value: Any,
|
72
|
+
) -> None:
|
73
|
+
raise AttributeError("Missing can't be modified")
|
74
|
+
|
75
|
+
def __delattr__(
|
76
|
+
self,
|
77
|
+
__name: str,
|
78
|
+
) -> None:
|
79
|
+
raise AttributeError("Missing can't be modified")
|
80
|
+
|
81
|
+
|
82
|
+
@overload
|
83
|
+
def Default[Value](
|
84
|
+
value: Value,
|
85
|
+
/,
|
86
|
+
) -> Value: ...
|
87
|
+
|
88
|
+
|
89
|
+
@overload
|
90
|
+
def Default[Value](
|
91
|
+
*,
|
92
|
+
factory: Callable[[], Value],
|
93
|
+
) -> Value: ...
|
94
|
+
|
95
|
+
|
96
|
+
def Default[Value](
|
97
|
+
value: Value | Missing = MISSING,
|
98
|
+
/,
|
99
|
+
*,
|
100
|
+
factory: Callable[[], Value] | Missing = MISSING,
|
101
|
+
) -> Value: # it is actually a DefaultValue, but type checker has to be fooled most some cases
|
102
|
+
return cast(
|
103
|
+
Value,
|
104
|
+
DefaultValue(
|
105
|
+
value,
|
106
|
+
factory=factory,
|
107
|
+
),
|
108
|
+
)
|
haiway/types/frozen.py
ADDED
haiway/types/missing.py
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
from typing import Any, Final, TypeGuard, cast, final
|
2
|
+
|
3
|
+
__all__ = [
|
4
|
+
"MISSING",
|
5
|
+
"Missing",
|
6
|
+
"is_missing",
|
7
|
+
"not_missing",
|
8
|
+
"when_missing",
|
9
|
+
]
|
10
|
+
|
11
|
+
|
12
|
+
class MissingType(type):
|
13
|
+
_instance: Any = None
|
14
|
+
|
15
|
+
def __call__(cls) -> Any:
|
16
|
+
if cls._instance is None:
|
17
|
+
cls._instance = super().__call__()
|
18
|
+
return cls._instance
|
19
|
+
|
20
|
+
else:
|
21
|
+
return cls._instance
|
22
|
+
|
23
|
+
|
24
|
+
@final
|
25
|
+
class Missing(metaclass=MissingType):
|
26
|
+
"""
|
27
|
+
Type representing absence of a value. Use MISSING constant for its value.
|
28
|
+
"""
|
29
|
+
|
30
|
+
__slots__ = ()
|
31
|
+
__match_args__ = ()
|
32
|
+
|
33
|
+
def __bool__(self) -> bool:
|
34
|
+
return False
|
35
|
+
|
36
|
+
def __eq__(
|
37
|
+
self,
|
38
|
+
value: object,
|
39
|
+
) -> bool:
|
40
|
+
return value is MISSING
|
41
|
+
|
42
|
+
def __str__(self) -> str:
|
43
|
+
return "MISSING"
|
44
|
+
|
45
|
+
def __repr__(self) -> str:
|
46
|
+
return "MISSING"
|
47
|
+
|
48
|
+
def __getattr__(
|
49
|
+
self,
|
50
|
+
name: str,
|
51
|
+
) -> Any:
|
52
|
+
raise AttributeError("Missing has no attributes")
|
53
|
+
|
54
|
+
def __setattr__(
|
55
|
+
self,
|
56
|
+
__name: str,
|
57
|
+
__value: Any,
|
58
|
+
) -> None:
|
59
|
+
raise AttributeError("Missing can't be modified")
|
60
|
+
|
61
|
+
def __delattr__(
|
62
|
+
self,
|
63
|
+
__name: str,
|
64
|
+
) -> None:
|
65
|
+
raise AttributeError("Missing can't be modified")
|
66
|
+
|
67
|
+
|
68
|
+
MISSING: Final[Missing] = Missing()
|
69
|
+
|
70
|
+
|
71
|
+
def is_missing(
|
72
|
+
check: Any | Missing,
|
73
|
+
/,
|
74
|
+
) -> TypeGuard[Missing]:
|
75
|
+
return check is MISSING
|
76
|
+
|
77
|
+
|
78
|
+
def not_missing[Value](
|
79
|
+
check: Value | Missing,
|
80
|
+
/,
|
81
|
+
) -> TypeGuard[Value]:
|
82
|
+
return check is not MISSING
|
83
|
+
|
84
|
+
|
85
|
+
def when_missing[Value](
|
86
|
+
check: Value | Missing,
|
87
|
+
/,
|
88
|
+
*,
|
89
|
+
value: Value,
|
90
|
+
) -> Value:
|
91
|
+
if check is MISSING:
|
92
|
+
return value
|
93
|
+
|
94
|
+
else:
|
95
|
+
return cast(Value, check)
|
haiway/utils/__init__.py
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
from haiway.utils.always import always, async_always
|
2
|
+
from haiway.utils.collections import as_dict, as_list, as_set, as_tuple
|
3
|
+
from haiway.utils.env import getenv_bool, getenv_float, getenv_int, getenv_str, load_env
|
4
|
+
from haiway.utils.freezing import freeze
|
5
|
+
from haiway.utils.logs import setup_logging
|
6
|
+
from haiway.utils.mimic import mimic_function
|
7
|
+
from haiway.utils.noop import async_noop, noop
|
8
|
+
from haiway.utils.queue import AsyncQueue
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
"AsyncQueue",
|
12
|
+
"always",
|
13
|
+
"as_dict",
|
14
|
+
"as_list",
|
15
|
+
"as_set",
|
16
|
+
"as_tuple",
|
17
|
+
"async_always",
|
18
|
+
"async_noop",
|
19
|
+
"freeze",
|
20
|
+
"getenv_bool",
|
21
|
+
"getenv_float",
|
22
|
+
"getenv_int",
|
23
|
+
"getenv_str",
|
24
|
+
"load_env",
|
25
|
+
"mimic_function",
|
26
|
+
"noop",
|
27
|
+
"setup_logging",
|
28
|
+
]
|