haiway 0.13.0__py3-none-any.whl → 0.14.0__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 +2 -0
- haiway/context/access.py +32 -50
- haiway/context/state.py +1 -1
- haiway/context/tasks.py +1 -1
- haiway/helpers/__init__.py +4 -1
- haiway/helpers/asynchrony.py +18 -16
- haiway/helpers/caching.py +12 -9
- haiway/helpers/metrics.py +46 -24
- haiway/helpers/retries.py +3 -3
- haiway/helpers/throttling.py +16 -16
- haiway/helpers/timeouted.py +7 -6
- haiway/utils/__init__.py +2 -0
- haiway/utils/always.py +2 -2
- haiway/utils/stream.py +97 -0
- {haiway-0.13.0.dist-info → haiway-0.14.0.dist-info}/METADATA +1 -1
- {haiway-0.13.0.dist-info → haiway-0.14.0.dist-info}/RECORD +18 -17
- {haiway-0.13.0.dist-info → haiway-0.14.0.dist-info}/WHEEL +0 -0
- {haiway-0.13.0.dist-info → haiway-0.14.0.dist-info}/licenses/LICENSE +0 -0
haiway/__init__.py
CHANGED
@@ -40,6 +40,7 @@ from haiway.types import (
|
|
40
40
|
)
|
41
41
|
from haiway.utils import (
|
42
42
|
AsyncQueue,
|
43
|
+
AsyncStream,
|
43
44
|
always,
|
44
45
|
as_dict,
|
45
46
|
as_list,
|
@@ -63,6 +64,7 @@ __all__ = [
|
|
63
64
|
"MISSING",
|
64
65
|
"ArgumentsTrace",
|
65
66
|
"AsyncQueue",
|
67
|
+
"AsyncStream",
|
66
68
|
"AttributePath",
|
67
69
|
"AttributeRequirement",
|
68
70
|
"Default",
|
haiway/context/access.py
CHANGED
@@ -11,7 +11,6 @@ from collections.abc import (
|
|
11
11
|
Coroutine,
|
12
12
|
Iterable,
|
13
13
|
)
|
14
|
-
from contextvars import Context, copy_context
|
15
14
|
from logging import Logger
|
16
15
|
from types import TracebackType
|
17
16
|
from typing import Any, final, overload
|
@@ -24,6 +23,7 @@ from haiway.context.state import StateContext
|
|
24
23
|
from haiway.context.tasks import TaskGroupContext
|
25
24
|
from haiway.state import State
|
26
25
|
from haiway.utils import mimic_function
|
26
|
+
from haiway.utils.stream import AsyncStream
|
27
27
|
|
28
28
|
__all__ = [
|
29
29
|
"ctx",
|
@@ -37,7 +37,6 @@ class ScopeContext:
|
|
37
37
|
"_identifier",
|
38
38
|
"_logger_context",
|
39
39
|
"_metrics_context",
|
40
|
-
"_state",
|
41
40
|
"_state_context",
|
42
41
|
"_task_group_context",
|
43
42
|
)
|
@@ -67,13 +66,12 @@ class ScopeContext:
|
|
67
66
|
)
|
68
67
|
# postponing task group creation to include only when needed
|
69
68
|
self._task_group_context: TaskGroupContext
|
70
|
-
#
|
69
|
+
# prepare state context to capture current state
|
71
70
|
self._state_context: StateContext
|
72
|
-
self._state: tuple[State, ...]
|
73
71
|
object.__setattr__(
|
74
72
|
self,
|
75
|
-
"
|
76
|
-
state,
|
73
|
+
"_state_context",
|
74
|
+
StateContext.updated(state),
|
77
75
|
)
|
78
76
|
self._disposables: Disposables | None
|
79
77
|
object.__setattr__(
|
@@ -115,12 +113,6 @@ class ScopeContext:
|
|
115
113
|
assert self._disposables is None, "Can't enter synchronous context with disposables" # nosec: B101
|
116
114
|
self._identifier.__enter__()
|
117
115
|
self._logger_context.__enter__()
|
118
|
-
# lazily initialize state
|
119
|
-
object.__setattr__(
|
120
|
-
self,
|
121
|
-
"_state_context",
|
122
|
-
StateContext.updated(self._state),
|
123
|
-
)
|
124
116
|
self._state_context.__enter__()
|
125
117
|
self._metrics_context.__enter__()
|
126
118
|
|
@@ -169,24 +161,17 @@ class ScopeContext:
|
|
169
161
|
|
170
162
|
# lazily initialize state to include disposables results
|
171
163
|
if self._disposables is not None:
|
164
|
+
assert self._state_context._token is None # nosec: B101
|
172
165
|
object.__setattr__(
|
173
166
|
self,
|
174
167
|
"_state_context",
|
175
|
-
StateContext
|
176
|
-
(
|
177
|
-
|
178
|
-
|
179
|
-
)
|
168
|
+
StateContext(
|
169
|
+
state=self._state_context._state.updated(
|
170
|
+
await self._disposables.__aenter__(),
|
171
|
+
),
|
180
172
|
),
|
181
173
|
)
|
182
174
|
|
183
|
-
else:
|
184
|
-
object.__setattr__(
|
185
|
-
self,
|
186
|
-
"_state_context",
|
187
|
-
StateContext.updated(self._state),
|
188
|
-
)
|
189
|
-
|
190
175
|
self._state_context.__enter__()
|
191
176
|
self._metrics_context.__enter__()
|
192
177
|
|
@@ -238,8 +223,8 @@ class ScopeContext:
|
|
238
223
|
@overload
|
239
224
|
def __call__[Result, **Arguments](
|
240
225
|
self,
|
241
|
-
function: Callable[Arguments, Coroutine[
|
242
|
-
) -> Callable[Arguments, Coroutine[
|
226
|
+
function: Callable[Arguments, Coroutine[Any, Any, Result]],
|
227
|
+
) -> Callable[Arguments, Coroutine[Any, Any, Result]]: ...
|
243
228
|
|
244
229
|
@overload
|
245
230
|
def __call__[Result, **Arguments](
|
@@ -249,8 +234,8 @@ class ScopeContext:
|
|
249
234
|
|
250
235
|
def __call__[Result, **Arguments](
|
251
236
|
self,
|
252
|
-
function: Callable[Arguments, Coroutine[
|
253
|
-
) -> Callable[Arguments, Coroutine[
|
237
|
+
function: Callable[Arguments, Coroutine[Any, Any, Result]] | Callable[Arguments, Result],
|
238
|
+
) -> Callable[Arguments, Coroutine[Any, Any, Result]] | Callable[Arguments, Result]:
|
254
239
|
if iscoroutinefunction(function):
|
255
240
|
|
256
241
|
async def async_context(
|
@@ -372,7 +357,7 @@ class ctx:
|
|
372
357
|
|
373
358
|
@staticmethod
|
374
359
|
def spawn[Result, **Arguments](
|
375
|
-
function: Callable[Arguments, Coroutine[
|
360
|
+
function: Callable[Arguments, Coroutine[Any, Any, Result]],
|
376
361
|
/,
|
377
362
|
*args: Arguments.args,
|
378
363
|
**kwargs: Arguments.kwargs,
|
@@ -383,7 +368,7 @@ class ctx:
|
|
383
368
|
|
384
369
|
Parameters
|
385
370
|
----------
|
386
|
-
function: Callable[Arguments, Coroutine[
|
371
|
+
function: Callable[Arguments, Coroutine[Any, Any, Result]]
|
387
372
|
function to be called within the task group
|
388
373
|
|
389
374
|
*args: Arguments.args
|
@@ -401,12 +386,12 @@ class ctx:
|
|
401
386
|
return TaskGroupContext.run(function, *args, **kwargs)
|
402
387
|
|
403
388
|
@staticmethod
|
404
|
-
def stream[
|
405
|
-
source: Callable[Arguments, AsyncGenerator[
|
389
|
+
def stream[Element, **Arguments](
|
390
|
+
source: Callable[Arguments, AsyncGenerator[Element, None]],
|
406
391
|
/,
|
407
392
|
*args: Arguments.args,
|
408
393
|
**kwargs: Arguments.kwargs,
|
409
|
-
) -> AsyncIterator[
|
394
|
+
) -> AsyncIterator[Element]:
|
410
395
|
"""
|
411
396
|
Stream results produced by a generator within the proper context state.
|
412
397
|
|
@@ -427,25 +412,22 @@ class ctx:
|
|
427
412
|
iterator for accessing generated results
|
428
413
|
"""
|
429
414
|
|
430
|
-
|
431
|
-
context_snapshot: Context = copy_context()
|
432
|
-
|
433
|
-
# prepare nested context
|
434
|
-
streaming_context: ScopeContext = ctx.scope(
|
435
|
-
getattr(
|
436
|
-
source,
|
437
|
-
"__name__",
|
438
|
-
"streaming",
|
439
|
-
)
|
440
|
-
)
|
415
|
+
output_stream = AsyncStream[Element]()
|
441
416
|
|
442
|
-
|
443
|
-
|
417
|
+
@ctx.scope("stream")
|
418
|
+
async def stream() -> None:
|
419
|
+
try:
|
444
420
|
async for result in source(*args, **kwargs):
|
445
|
-
|
421
|
+
await output_stream.send(result)
|
422
|
+
|
423
|
+
except BaseException as exc:
|
424
|
+
output_stream.finish(exception=exc)
|
425
|
+
|
426
|
+
else:
|
427
|
+
output_stream.finish()
|
446
428
|
|
447
|
-
|
448
|
-
return
|
429
|
+
TaskGroupContext.run(stream)
|
430
|
+
return output_stream
|
449
431
|
|
450
432
|
@staticmethod
|
451
433
|
def check_cancellation() -> None:
|
@@ -488,7 +470,7 @@ class ctx:
|
|
488
470
|
StateType
|
489
471
|
resolved state instance
|
490
472
|
"""
|
491
|
-
return StateContext.
|
473
|
+
return StateContext.state(
|
492
474
|
state,
|
493
475
|
default=default,
|
494
476
|
)
|
haiway/context/state.py
CHANGED
haiway/context/tasks.py
CHANGED
@@ -16,7 +16,7 @@ class TaskGroupContext:
|
|
16
16
|
@classmethod
|
17
17
|
def run[Result, **Arguments](
|
18
18
|
cls,
|
19
|
-
function: Callable[Arguments, Coroutine[
|
19
|
+
function: Callable[Arguments, Coroutine[Any, Any, Result]],
|
20
20
|
/,
|
21
21
|
*args: Arguments.args,
|
22
22
|
**kwargs: Arguments.kwargs,
|
haiway/helpers/__init__.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from haiway.helpers.asynchrony import asynchronous, wrap_async
|
2
|
-
from haiway.helpers.caching import cache
|
2
|
+
from haiway.helpers.caching import CacheMakeKey, CacheRead, CacheWrite, cache
|
3
3
|
from haiway.helpers.metrics import MetricsHolder, MetricsLogger
|
4
4
|
from haiway.helpers.retries import retry
|
5
5
|
from haiway.helpers.throttling import throttle
|
@@ -8,6 +8,9 @@ from haiway.helpers.tracing import ArgumentsTrace, ResultTrace, traced
|
|
8
8
|
|
9
9
|
__all__ = [
|
10
10
|
"ArgumentsTrace",
|
11
|
+
"CacheMakeKey",
|
12
|
+
"CacheRead",
|
13
|
+
"CacheWrite",
|
11
14
|
"MetricsHolder",
|
12
15
|
"MetricsLogger",
|
13
16
|
"ResultTrace",
|
haiway/helpers/asynchrony.py
CHANGED
@@ -14,9 +14,9 @@ __all__ = [
|
|
14
14
|
|
15
15
|
|
16
16
|
def wrap_async[**Args, Result](
|
17
|
-
function: Callable[Args, Coroutine[
|
17
|
+
function: Callable[Args, Coroutine[Any, Any, Result]] | Callable[Args, Result],
|
18
18
|
/,
|
19
|
-
) -> Callable[Args, Coroutine[
|
19
|
+
) -> Callable[Args, Coroutine[Any, Any, Result]]:
|
20
20
|
if iscoroutinefunction(function):
|
21
21
|
return function
|
22
22
|
|
@@ -30,10 +30,12 @@ def wrap_async[**Args, Result](
|
|
30
30
|
|
31
31
|
|
32
32
|
@overload
|
33
|
-
def asynchronous[**Args, Result]() ->
|
34
|
-
|
35
|
-
|
36
|
-
]
|
33
|
+
def asynchronous[**Args, Result]() -> (
|
34
|
+
Callable[
|
35
|
+
[Callable[Args, Result]],
|
36
|
+
Callable[Args, Coroutine[Any, Any, Result]],
|
37
|
+
]
|
38
|
+
): ...
|
37
39
|
|
38
40
|
|
39
41
|
@overload
|
@@ -43,7 +45,7 @@ def asynchronous[**Args, Result](
|
|
43
45
|
executor: Executor,
|
44
46
|
) -> Callable[
|
45
47
|
[Callable[Args, Result]],
|
46
|
-
Callable[Args, Coroutine[
|
48
|
+
Callable[Args, Coroutine[Any, Any, Result]],
|
47
49
|
]: ...
|
48
50
|
|
49
51
|
|
@@ -51,7 +53,7 @@ def asynchronous[**Args, Result](
|
|
51
53
|
def asynchronous[**Args, Result](
|
52
54
|
function: Callable[Args, Result],
|
53
55
|
/,
|
54
|
-
) -> Callable[Args, Coroutine[
|
56
|
+
) -> Callable[Args, Coroutine[Any, Any, Result]]: ...
|
55
57
|
|
56
58
|
|
57
59
|
def asynchronous[**Args, Result](
|
@@ -62,9 +64,9 @@ def asynchronous[**Args, Result](
|
|
62
64
|
) -> (
|
63
65
|
Callable[
|
64
66
|
[Callable[Args, Result]],
|
65
|
-
Callable[Args, Coroutine[
|
67
|
+
Callable[Args, Coroutine[Any, Any, Result]],
|
66
68
|
]
|
67
|
-
| Callable[Args, Coroutine[
|
69
|
+
| Callable[Args, Coroutine[Any, Any, Result]]
|
68
70
|
):
|
69
71
|
"""\
|
70
72
|
Wrapper for a sync function to convert it to an async function. \
|
@@ -90,7 +92,7 @@ def asynchronous[**Args, Result](
|
|
90
92
|
|
91
93
|
def wrap(
|
92
94
|
wrapped: Callable[Args, Result],
|
93
|
-
) -> Callable[Args, Coroutine[
|
95
|
+
) -> Callable[Args, Coroutine[Any, Any, Result]]:
|
94
96
|
assert not iscoroutinefunction(wrapped), "Cannot wrap async function in executor" # nosec: B101
|
95
97
|
|
96
98
|
return _ExecutorWrapper(
|
@@ -152,7 +154,7 @@ class _ExecutorWrapper[**Args, Result]:
|
|
152
154
|
instance: object,
|
153
155
|
owner: type | None = None,
|
154
156
|
/,
|
155
|
-
) -> Callable[Args, Coroutine[
|
157
|
+
) -> Callable[Args, Coroutine[Any, Any, Result]]:
|
156
158
|
if owner is None:
|
157
159
|
return self
|
158
160
|
|
@@ -180,8 +182,8 @@ class _ExecutorWrapper[**Args, Result]:
|
|
180
182
|
def _mimic_async[**Args, Result](
|
181
183
|
function: Callable[Args, Result],
|
182
184
|
/,
|
183
|
-
within: Callable[..., Coroutine[
|
184
|
-
) -> Callable[Args, Coroutine[
|
185
|
+
within: Callable[..., Coroutine[Any, Any, Result]],
|
186
|
+
) -> Callable[Args, Coroutine[Any, Any, Result]]:
|
185
187
|
try:
|
186
188
|
annotations: Any = getattr( # noqa: B009
|
187
189
|
function,
|
@@ -192,7 +194,7 @@ def _mimic_async[**Args, Result](
|
|
192
194
|
"__annotations__",
|
193
195
|
{
|
194
196
|
**annotations,
|
195
|
-
"return": Coroutine[
|
197
|
+
"return": Coroutine[Any, Any, annotations.get("return", Any)],
|
196
198
|
},
|
197
199
|
)
|
198
200
|
|
@@ -234,6 +236,6 @@ def _mimic_async[**Args, Result](
|
|
234
236
|
)
|
235
237
|
|
236
238
|
return cast(
|
237
|
-
Callable[Args, Coroutine[
|
239
|
+
Callable[Args, Coroutine[Any, Any, Result]],
|
238
240
|
within,
|
239
241
|
)
|
haiway/helpers/caching.py
CHANGED
@@ -3,12 +3,15 @@ from collections import OrderedDict
|
|
3
3
|
from collections.abc import Callable, Coroutine, Hashable
|
4
4
|
from functools import _make_key # pyright: ignore[reportPrivateUsage]
|
5
5
|
from time import monotonic
|
6
|
-
from typing import NamedTuple, Protocol, cast, overload
|
6
|
+
from typing import Any, NamedTuple, Protocol, cast, overload
|
7
7
|
|
8
8
|
from haiway.context.access import ctx
|
9
9
|
from haiway.utils.mimic import mimic_function
|
10
10
|
|
11
11
|
__all__ = [
|
12
|
+
"CacheMakeKey",
|
13
|
+
"CacheRead",
|
14
|
+
"CacheWrite",
|
12
15
|
"cache",
|
13
16
|
]
|
14
17
|
|
@@ -59,7 +62,7 @@ def cache[**Args, Result, Key](
|
|
59
62
|
read: CacheRead[Key, Result],
|
60
63
|
write: CacheWrite[Key, Result],
|
61
64
|
) -> Callable[
|
62
|
-
[Callable[Args, Coroutine[
|
65
|
+
[Callable[Args, Coroutine[Any, Any, Result]]], Callable[Args, Coroutine[Any, Any, Result]]
|
63
66
|
]: ...
|
64
67
|
|
65
68
|
|
@@ -73,8 +76,8 @@ def cache[**Args, Result, Key]( # noqa: PLR0913
|
|
73
76
|
write: CacheWrite[Key, Result] | None = None,
|
74
77
|
) -> (
|
75
78
|
Callable[
|
76
|
-
[Callable[Args, Coroutine[
|
77
|
-
Callable[Args, Coroutine[
|
79
|
+
[Callable[Args, Coroutine[Any, Any, Result]]],
|
80
|
+
Callable[Args, Coroutine[Any, Any, Result]],
|
78
81
|
]
|
79
82
|
| Callable[[Callable[Args, Result]], Callable[Args, Result]]
|
80
83
|
| Callable[Args, Result]
|
@@ -317,13 +320,13 @@ class _AsyncCache[**Args, Result]:
|
|
317
320
|
|
318
321
|
def __init__(
|
319
322
|
self,
|
320
|
-
function: Callable[Args, Coroutine[
|
323
|
+
function: Callable[Args, Coroutine[Any, Any, Result]],
|
321
324
|
/,
|
322
325
|
limit: int,
|
323
326
|
expiration: float | None,
|
324
327
|
make_key: CacheMakeKey[Args, Hashable],
|
325
328
|
) -> None:
|
326
|
-
self._function: Callable[Args, Coroutine[
|
329
|
+
self._function: Callable[Args, Coroutine[Any, Any, Result]] = function
|
327
330
|
self._cached: OrderedDict[Hashable, _CacheEntry[Result]] = OrderedDict()
|
328
331
|
self._limit: int = limit
|
329
332
|
self._make_key: CacheMakeKey[Args, Hashable] = make_key
|
@@ -348,7 +351,7 @@ class _AsyncCache[**Args, Result]:
|
|
348
351
|
instance: object | None,
|
349
352
|
owner: type | None = None,
|
350
353
|
/,
|
351
|
-
) -> Callable[Args, Coroutine[
|
354
|
+
) -> Callable[Args, Coroutine[Any, Any, Result]]:
|
352
355
|
assert owner is None and instance is None, "cache does not work for classes" # nosec: B101
|
353
356
|
return self
|
354
357
|
|
@@ -405,13 +408,13 @@ class _CustomCache[**Args, Result, Key]:
|
|
405
408
|
|
406
409
|
def __init__(
|
407
410
|
self,
|
408
|
-
function: Callable[Args, Coroutine[
|
411
|
+
function: Callable[Args, Coroutine[Any, Any, Result]],
|
409
412
|
/,
|
410
413
|
make_key: CacheMakeKey[Args, Key],
|
411
414
|
read: CacheRead[Key, Result],
|
412
415
|
write: CacheWrite[Key, Result],
|
413
416
|
) -> None:
|
414
|
-
self._function: Callable[Args, Coroutine[
|
417
|
+
self._function: Callable[Args, Coroutine[Any, Any, Result]] = function
|
415
418
|
self._make_key: CacheMakeKey[Args, Key] = make_key
|
416
419
|
self._read: CacheRead[Key, Result] = read
|
417
420
|
self._write: CacheWrite[Key, Result] = write
|
haiway/helpers/metrics.py
CHANGED
@@ -15,6 +15,7 @@ __all_ = [
|
|
15
15
|
|
16
16
|
class MetricsScopeStore:
|
17
17
|
__slots__ = (
|
18
|
+
"allow_exit",
|
18
19
|
"entered",
|
19
20
|
"exited",
|
20
21
|
"identifier",
|
@@ -31,6 +32,7 @@ class MetricsScopeStore:
|
|
31
32
|
self.entered: float = monotonic()
|
32
33
|
self.metrics: dict[type[State], State] = {}
|
33
34
|
self.exited: float | None = None
|
35
|
+
self.allow_exit: bool = False
|
34
36
|
self.nested: list[MetricsScopeStore] = []
|
35
37
|
|
36
38
|
@property
|
@@ -115,7 +117,7 @@ class MetricsHolder:
|
|
115
117
|
|
116
118
|
def __init__(self) -> None:
|
117
119
|
self.root_scope: ScopeIdentifier | None = None
|
118
|
-
self.scopes: dict[
|
120
|
+
self.scopes: dict[str, MetricsScopeStore] = {}
|
119
121
|
|
120
122
|
def record(
|
121
123
|
self,
|
@@ -124,10 +126,10 @@ class MetricsHolder:
|
|
124
126
|
metric: State,
|
125
127
|
) -> None:
|
126
128
|
assert self.root_scope is not None # nosec: B101
|
127
|
-
assert scope in self.scopes # nosec: B101
|
129
|
+
assert scope.scope_id in self.scopes # nosec: B101
|
128
130
|
|
129
131
|
metric_type: type[State] = type(metric)
|
130
|
-
metrics: dict[type[State], State] = self.scopes[scope].metrics
|
132
|
+
metrics: dict[type[State], State] = self.scopes[scope.scope_id].metrics
|
131
133
|
if (current := metrics.get(metric_type)) and hasattr(current, "__add__"):
|
132
134
|
metrics[type(metric)] = current.__add__(metric) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
133
135
|
|
@@ -142,29 +144,29 @@ class MetricsHolder:
|
|
142
144
|
merged: bool,
|
143
145
|
) -> Metric | None:
|
144
146
|
assert self.root_scope is not None # nosec: B101
|
145
|
-
assert scope in self.scopes # nosec: B101
|
147
|
+
assert scope.scope_id in self.scopes # nosec: B101
|
146
148
|
|
147
149
|
if merged:
|
148
|
-
return self.scopes[scope].merged(metric)
|
150
|
+
return self.scopes[scope.scope_id].merged(metric)
|
149
151
|
|
150
152
|
else:
|
151
|
-
return cast(Metric | None, self.scopes[scope].metrics.get(metric))
|
153
|
+
return cast(Metric | None, self.scopes[scope.scope_id].metrics.get(metric))
|
152
154
|
|
153
155
|
def enter_scope[Metric: State](
|
154
156
|
self,
|
155
157
|
scope: ScopeIdentifier,
|
156
158
|
/,
|
157
159
|
) -> None:
|
158
|
-
assert scope not in self.scopes # nosec: B101
|
160
|
+
assert scope.scope_id not in self.scopes # nosec: B101
|
159
161
|
scope_metrics = MetricsScopeStore(scope)
|
160
|
-
self.scopes[scope] = scope_metrics
|
162
|
+
self.scopes[scope.scope_id] = scope_metrics
|
161
163
|
|
162
164
|
if self.root_scope is None:
|
163
165
|
self.root_scope = scope
|
164
166
|
|
165
167
|
else:
|
166
168
|
for key in self.scopes.keys():
|
167
|
-
if key
|
169
|
+
if key == scope.parent_id:
|
168
170
|
self.scopes[key].nested.append(scope_metrics)
|
169
171
|
return
|
170
172
|
|
@@ -177,8 +179,18 @@ class MetricsHolder:
|
|
177
179
|
scope: ScopeIdentifier,
|
178
180
|
/,
|
179
181
|
) -> None:
|
180
|
-
assert
|
181
|
-
self.scopes
|
182
|
+
assert self.root_scope is not None # nosec: B101
|
183
|
+
assert scope.scope_id in self.scopes # nosec: B101
|
184
|
+
|
185
|
+
self.scopes[scope.scope_id].allow_exit = True
|
186
|
+
|
187
|
+
if not all(nested.exited for nested in self.scopes[scope.scope_id].nested):
|
188
|
+
return # not completed yet
|
189
|
+
|
190
|
+
self.scopes[scope.scope_id].exited = monotonic()
|
191
|
+
|
192
|
+
if scope != self.root_scope and self.scopes[scope.parent_id].allow_exit:
|
193
|
+
self.exit_scope(self.scopes[scope.parent_id].identifier)
|
182
194
|
|
183
195
|
|
184
196
|
@final
|
@@ -213,7 +225,7 @@ class MetricsLogger:
|
|
213
225
|
redact_content: bool,
|
214
226
|
) -> None:
|
215
227
|
self.root_scope: ScopeIdentifier | None = None
|
216
|
-
self.scopes: dict[
|
228
|
+
self.scopes: dict[str, MetricsScopeStore] = {}
|
217
229
|
self.items_limit: int | None = items_limit
|
218
230
|
self.redact_content: bool = redact_content
|
219
231
|
|
@@ -224,10 +236,10 @@ class MetricsLogger:
|
|
224
236
|
metric: State,
|
225
237
|
) -> None:
|
226
238
|
assert self.root_scope is not None # nosec: B101
|
227
|
-
assert scope in self.scopes # nosec: B101
|
239
|
+
assert scope.scope_id in self.scopes # nosec: B101
|
228
240
|
|
229
241
|
metric_type: type[State] = type(metric)
|
230
|
-
metrics: dict[type[State], State] = self.scopes[scope].metrics
|
242
|
+
metrics: dict[type[State], State] = self.scopes[scope.scope_id].metrics
|
231
243
|
if (current := metrics.get(metric_type)) and hasattr(current, "__add__"):
|
232
244
|
metrics[type(metric)] = current.__add__(metric) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
233
245
|
|
@@ -248,29 +260,29 @@ class MetricsLogger:
|
|
248
260
|
merged: bool,
|
249
261
|
) -> Metric | None:
|
250
262
|
assert self.root_scope is not None # nosec: B101
|
251
|
-
assert scope in self.scopes # nosec: B101
|
263
|
+
assert scope.scope_id in self.scopes # nosec: B101
|
252
264
|
|
253
265
|
if merged:
|
254
|
-
return self.scopes[scope].merged(metric)
|
266
|
+
return self.scopes[scope.scope_id].merged(metric)
|
255
267
|
|
256
268
|
else:
|
257
|
-
return cast(Metric | None, self.scopes[scope].metrics.get(metric))
|
269
|
+
return cast(Metric | None, self.scopes[scope.scope_id].metrics.get(metric))
|
258
270
|
|
259
271
|
def enter_scope[Metric: State](
|
260
272
|
self,
|
261
273
|
scope: ScopeIdentifier,
|
262
274
|
/,
|
263
275
|
) -> None:
|
264
|
-
assert scope not in self.scopes # nosec: B101
|
276
|
+
assert scope.scope_id not in self.scopes # nosec: B101
|
265
277
|
scope_metrics = MetricsScopeStore(scope)
|
266
|
-
self.scopes[scope] = scope_metrics
|
278
|
+
self.scopes[scope.scope_id] = scope_metrics
|
267
279
|
|
268
280
|
if self.root_scope is None:
|
269
281
|
self.root_scope = scope
|
270
282
|
|
271
283
|
else:
|
272
284
|
for key in self.scopes.keys():
|
273
|
-
if key
|
285
|
+
if key == scope.parent_id:
|
274
286
|
self.scopes[key].nested.append(scope_metrics)
|
275
287
|
return
|
276
288
|
|
@@ -283,12 +295,22 @@ class MetricsLogger:
|
|
283
295
|
scope: ScopeIdentifier,
|
284
296
|
/,
|
285
297
|
) -> None:
|
286
|
-
assert
|
287
|
-
self.scopes
|
298
|
+
assert self.root_scope is not None # nosec: B101
|
299
|
+
assert scope.scope_id in self.scopes # nosec: B101
|
300
|
+
|
301
|
+
self.scopes[scope.scope_id].allow_exit = True
|
302
|
+
|
303
|
+
if not all(nested.exited for nested in self.scopes[scope.scope_id].nested):
|
304
|
+
return # not completed yet
|
305
|
+
|
306
|
+
self.scopes[scope.scope_id].exited = monotonic()
|
307
|
+
|
308
|
+
if scope != self.root_scope and self.scopes[scope.parent_id].allow_exit:
|
309
|
+
self.exit_scope(self.scopes[scope.parent_id].identifier)
|
288
310
|
|
289
|
-
|
311
|
+
elif scope == self.root_scope and self.scopes[self.root_scope.scope_id].finished:
|
290
312
|
if log := _tree_log(
|
291
|
-
self.scopes[scope],
|
313
|
+
self.scopes[scope.scope_id],
|
292
314
|
list_items_limit=self.items_limit,
|
293
315
|
redact_content=self.redact_content,
|
294
316
|
):
|
haiway/helpers/retries.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from asyncio import CancelledError, iscoroutinefunction, sleep
|
2
2
|
from collections.abc import Callable, Coroutine
|
3
3
|
from time import sleep as sleep_sync
|
4
|
-
from typing import cast, overload
|
4
|
+
from typing import Any, cast, overload
|
5
5
|
|
6
6
|
from haiway.context import ctx
|
7
7
|
from haiway.utils import mimic_function
|
@@ -178,12 +178,12 @@ def _wrap_sync[**Args, Result](
|
|
178
178
|
|
179
179
|
|
180
180
|
def _wrap_async[**Args, Result](
|
181
|
-
function: Callable[Args, Coroutine[
|
181
|
+
function: Callable[Args, Coroutine[Any, Any, Result]],
|
182
182
|
*,
|
183
183
|
limit: int,
|
184
184
|
delay: Callable[[int, Exception], float] | float | None,
|
185
185
|
catching: set[type[Exception]] | tuple[type[Exception], ...],
|
186
|
-
) -> Callable[Args, Coroutine[
|
186
|
+
) -> Callable[Args, Coroutine[Any, Any, Result]]:
|
187
187
|
assert limit > 0, "Limit has to be greater than zero" # nosec: B101
|
188
188
|
|
189
189
|
@mimic_function(function)
|
haiway/helpers/throttling.py
CHANGED
@@ -7,7 +7,7 @@ from collections import deque
|
|
7
7
|
from collections.abc import Callable, Coroutine
|
8
8
|
from datetime import timedelta
|
9
9
|
from time import monotonic
|
10
|
-
from typing import cast, overload
|
10
|
+
from typing import Any, cast, overload
|
11
11
|
|
12
12
|
from haiway.utils.mimic import mimic_function
|
13
13
|
|
@@ -18,9 +18,9 @@ __all__ = [
|
|
18
18
|
|
19
19
|
@overload
|
20
20
|
def throttle[**Args, Result](
|
21
|
-
function: Callable[Args, Coroutine[
|
21
|
+
function: Callable[Args, Coroutine[Any, Any, Result]],
|
22
22
|
/,
|
23
|
-
) -> Callable[Args, Coroutine[
|
23
|
+
) -> Callable[Args, Coroutine[Any, Any, Result]]: ...
|
24
24
|
|
25
25
|
|
26
26
|
@overload
|
@@ -29,21 +29,21 @@ def throttle[**Args, Result](
|
|
29
29
|
limit: int = 1,
|
30
30
|
period: timedelta | float = 1,
|
31
31
|
) -> Callable[
|
32
|
-
[Callable[Args, Coroutine[
|
32
|
+
[Callable[Args, Coroutine[Any, Any, Result]]], Callable[Args, Coroutine[Any, Any, Result]]
|
33
33
|
]: ...
|
34
34
|
|
35
35
|
|
36
36
|
def throttle[**Args, Result](
|
37
|
-
function: Callable[Args, Coroutine[
|
37
|
+
function: Callable[Args, Coroutine[Any, Any, Result]] | None = None,
|
38
38
|
*,
|
39
39
|
limit: int = 1,
|
40
40
|
period: timedelta | float = 1,
|
41
41
|
) -> (
|
42
42
|
Callable[
|
43
|
-
[Callable[Args, Coroutine[
|
44
|
-
Callable[Args, Coroutine[
|
43
|
+
[Callable[Args, Coroutine[Any, Any, Result]]],
|
44
|
+
Callable[Args, Coroutine[Any, Any, Result]],
|
45
45
|
]
|
46
|
-
| Callable[Args, Coroutine[
|
46
|
+
| Callable[Args, Coroutine[Any, Any, Result]]
|
47
47
|
):
|
48
48
|
"""\
|
49
49
|
Throttle for function calls with custom limit and period time. \
|
@@ -53,7 +53,7 @@ def throttle[**Args, Result](
|
|
53
53
|
|
54
54
|
Parameters
|
55
55
|
----------
|
56
|
-
function: Callable[Args, Coroutine[
|
56
|
+
function: Callable[Args, Coroutine[Any, Any, Result]]
|
57
57
|
function to wrap in throttle
|
58
58
|
limit: int
|
59
59
|
limit of executions in given period, if no period was specified
|
@@ -63,17 +63,17 @@ def throttle[**Args, Result](
|
|
63
63
|
|
64
64
|
Returns
|
65
65
|
-------
|
66
|
-
Callable[[Callable[Args, Coroutine[
|
67
|
-
| Callable[Args, Coroutine[
|
66
|
+
Callable[[Callable[Args, Coroutine[Any, Any, Result]]], Callable[Args, Coroutine[Any, Any, Result]]] \
|
67
|
+
| Callable[Args, Coroutine[Any, Any, Result]]
|
68
68
|
provided function wrapped in throttle
|
69
69
|
""" # noqa: E501
|
70
70
|
|
71
71
|
def _wrap(
|
72
|
-
function: Callable[Args, Coroutine[
|
73
|
-
) -> Callable[Args, Coroutine[
|
72
|
+
function: Callable[Args, Coroutine[Any, Any, Result]],
|
73
|
+
) -> Callable[Args, Coroutine[Any, Any, Result]]:
|
74
74
|
assert iscoroutinefunction(function) # nosec: B101
|
75
75
|
return cast(
|
76
|
-
Callable[Args, Coroutine[
|
76
|
+
Callable[Args, Coroutine[Any, Any, Result]],
|
77
77
|
_AsyncThrottle(
|
78
78
|
function,
|
79
79
|
limit=limit,
|
@@ -107,12 +107,12 @@ class _AsyncThrottle[**Args, Result]:
|
|
107
107
|
|
108
108
|
def __init__(
|
109
109
|
self,
|
110
|
-
function: Callable[Args, Coroutine[
|
110
|
+
function: Callable[Args, Coroutine[Any, Any, Result]],
|
111
111
|
/,
|
112
112
|
limit: int,
|
113
113
|
period: timedelta | float,
|
114
114
|
) -> None:
|
115
|
-
self._function: Callable[Args, Coroutine[
|
115
|
+
self._function: Callable[Args, Coroutine[Any, Any, Result]] = function
|
116
116
|
self._entries: deque[float] = deque()
|
117
117
|
self._lock: Lock = Lock()
|
118
118
|
self._limit: int = limit
|
haiway/helpers/timeouted.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from asyncio import AbstractEventLoop, Future, Task, TimerHandle, get_running_loop
|
2
2
|
from collections.abc import Callable, Coroutine
|
3
|
+
from typing import Any
|
3
4
|
|
4
5
|
from haiway.utils.mimic import mimic_function
|
5
6
|
|
@@ -12,8 +13,8 @@ def timeout[**Args, Result](
|
|
12
13
|
timeout: float,
|
13
14
|
/,
|
14
15
|
) -> Callable[
|
15
|
-
[Callable[Args, Coroutine[
|
16
|
-
Callable[Args, Coroutine[
|
16
|
+
[Callable[Args, Coroutine[Any, Any, Result]]],
|
17
|
+
Callable[Args, Coroutine[Any, Any, Result]],
|
17
18
|
]:
|
18
19
|
"""\
|
19
20
|
Timeout wrapper for a function call. \
|
@@ -34,8 +35,8 @@ def timeout[**Args, Result](
|
|
34
35
|
"""
|
35
36
|
|
36
37
|
def _wrap(
|
37
|
-
function: Callable[Args, Coroutine[
|
38
|
-
) -> Callable[Args, Coroutine[
|
38
|
+
function: Callable[Args, Coroutine[Any, Any, Result]],
|
39
|
+
) -> Callable[Args, Coroutine[Any, Any, Result]]:
|
39
40
|
return _AsyncTimeout(
|
40
41
|
function,
|
41
42
|
timeout=timeout,
|
@@ -60,11 +61,11 @@ class _AsyncTimeout[**Args, Result]:
|
|
60
61
|
|
61
62
|
def __init__(
|
62
63
|
self,
|
63
|
-
function: Callable[Args, Coroutine[
|
64
|
+
function: Callable[Args, Coroutine[Any, Any, Result]],
|
64
65
|
/,
|
65
66
|
timeout: float,
|
66
67
|
) -> None:
|
67
|
-
self._function: Callable[Args, Coroutine[
|
68
|
+
self._function: Callable[Args, Coroutine[Any, Any, Result]] = function
|
68
69
|
self._timeout: float = timeout
|
69
70
|
|
70
71
|
# mimic function attributes if able
|
haiway/utils/__init__.py
CHANGED
@@ -13,9 +13,11 @@ from haiway.utils.logs import setup_logging
|
|
13
13
|
from haiway.utils.mimic import mimic_function
|
14
14
|
from haiway.utils.noop import async_noop, noop
|
15
15
|
from haiway.utils.queue import AsyncQueue
|
16
|
+
from haiway.utils.stream import AsyncStream
|
16
17
|
|
17
18
|
__all__ = [
|
18
19
|
"AsyncQueue",
|
20
|
+
"AsyncStream",
|
19
21
|
"always",
|
20
22
|
"as_dict",
|
21
23
|
"as_list",
|
haiway/utils/always.py
CHANGED
@@ -37,7 +37,7 @@ def always[Value](
|
|
37
37
|
def async_always[Value](
|
38
38
|
value: Value,
|
39
39
|
/,
|
40
|
-
) -> Callable[..., Coroutine[
|
40
|
+
) -> Callable[..., Coroutine[Any, Any, Value]]:
|
41
41
|
"""
|
42
42
|
Factory method creating async functions returning always the same value.
|
43
43
|
|
@@ -48,7 +48,7 @@ def async_always[Value](
|
|
48
48
|
|
49
49
|
Returns
|
50
50
|
-------
|
51
|
-
Callable[..., Coroutine[
|
51
|
+
Callable[..., Coroutine[Any, Any, Value]]
|
52
52
|
async function ignoring arguments and always returning the provided value.
|
53
53
|
"""
|
54
54
|
|
haiway/utils/stream.py
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
from asyncio import (
|
2
|
+
AbstractEventLoop,
|
3
|
+
CancelledError,
|
4
|
+
Future,
|
5
|
+
get_running_loop,
|
6
|
+
)
|
7
|
+
from collections.abc import AsyncIterator
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"AsyncStream",
|
11
|
+
]
|
12
|
+
|
13
|
+
|
14
|
+
class AsyncStream[Element](AsyncIterator[Element]):
|
15
|
+
def __init__(
|
16
|
+
self,
|
17
|
+
loop: AbstractEventLoop | None = None,
|
18
|
+
) -> None:
|
19
|
+
self._loop: AbstractEventLoop = loop or get_running_loop()
|
20
|
+
self._ready: Future[None] = self._loop.create_future()
|
21
|
+
self._waiting: Future[Element] | None = None
|
22
|
+
self._finish_reason: BaseException | None = None
|
23
|
+
|
24
|
+
@property
|
25
|
+
def finished(self) -> bool:
|
26
|
+
return self._finish_reason is not None
|
27
|
+
|
28
|
+
async def send(
|
29
|
+
self,
|
30
|
+
element: Element,
|
31
|
+
/,
|
32
|
+
) -> None:
|
33
|
+
if self._finish_reason is not None:
|
34
|
+
return # already finished
|
35
|
+
|
36
|
+
# wait for readiness
|
37
|
+
await self._ready
|
38
|
+
# we could finish while waiting
|
39
|
+
if self._finish_reason is not None:
|
40
|
+
return # already finished
|
41
|
+
|
42
|
+
assert self._waiting is not None and not self._waiting.done() # nosec: B101
|
43
|
+
# send the element
|
44
|
+
self._waiting.set_result(element)
|
45
|
+
# and create new readiness future afterwards
|
46
|
+
self._ready = self._loop.create_future()
|
47
|
+
|
48
|
+
def finish(
|
49
|
+
self,
|
50
|
+
exception: BaseException | None = None,
|
51
|
+
) -> None:
|
52
|
+
if self.finished:
|
53
|
+
return # already finished, ignore
|
54
|
+
|
55
|
+
self._finish_reason = exception or StopAsyncIteration()
|
56
|
+
|
57
|
+
if not self._ready.done():
|
58
|
+
if get_running_loop() is not self._loop:
|
59
|
+
self._loop.call_soon_threadsafe(
|
60
|
+
self._ready.set_result,
|
61
|
+
None,
|
62
|
+
)
|
63
|
+
|
64
|
+
else:
|
65
|
+
self._ready.set_result(None)
|
66
|
+
|
67
|
+
if self._waiting is not None and not self._waiting.done():
|
68
|
+
if get_running_loop() is not self._loop:
|
69
|
+
self._loop.call_soon_threadsafe(
|
70
|
+
self._waiting.set_exception,
|
71
|
+
self._finish_reason,
|
72
|
+
)
|
73
|
+
|
74
|
+
else:
|
75
|
+
self._waiting.set_exception(self._finish_reason)
|
76
|
+
|
77
|
+
def cancel(self) -> None:
|
78
|
+
self.finish(exception=CancelledError())
|
79
|
+
|
80
|
+
async def __anext__(self) -> Element:
|
81
|
+
assert self._waiting is None, "AsyncStream can't be reused" # nosec: B101
|
82
|
+
|
83
|
+
if self._finish_reason:
|
84
|
+
raise self._finish_reason
|
85
|
+
|
86
|
+
try:
|
87
|
+
assert not self._ready.done() # nosec: B101
|
88
|
+
# create new waiting future
|
89
|
+
self._waiting = self._loop.create_future()
|
90
|
+
# and notify readiness
|
91
|
+
self._ready.set_result(None)
|
92
|
+
# and wait for the result
|
93
|
+
return await self._waiting
|
94
|
+
|
95
|
+
finally:
|
96
|
+
# cleanup waiting future
|
97
|
+
self._waiting = None
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: haiway
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.14.0
|
4
4
|
Summary: Framework for dependency injection and state management within structured concurrency model.
|
5
5
|
Project-URL: Homepage, https://miquido.com
|
6
6
|
Project-URL: Repository, https://github.com/miquido/haiway.git
|
@@ -1,21 +1,21 @@
|
|
1
|
-
haiway/__init__.py,sha256=
|
1
|
+
haiway/__init__.py,sha256=RhW9HOIAVQ3srQ-v23tPghJ20dWcn_uAyt8U8Hhn868,2081
|
2
2
|
haiway/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
haiway/context/__init__.py,sha256=feqd0eJnGQwh4B8BZXpS0fQRE-DqoFCFOHipF1jOY8A,762
|
4
|
-
haiway/context/access.py,sha256=
|
4
|
+
haiway/context/access.py,sha256=1Oq70pcp54bC9NNp-zMocDewVJpcZgepi99_xeqz98o,18502
|
5
5
|
haiway/context/disposables.py,sha256=vcsh8jRaJ8Q1ob7oh5LsrSPw9f5AMTcaD_p_Gb7tXAI,2588
|
6
6
|
haiway/context/identifier.py,sha256=lz-FuspOtsaEsfb7QPrEVWYfbcMJgd3A6BGG3kLbaV0,3914
|
7
7
|
haiway/context/logging.py,sha256=F3dr6MLjodg3MX5WTInxn3r3JuihG-giBzumI0GGUQw,5590
|
8
8
|
haiway/context/metrics.py,sha256=N20XQtC8au_e_3iWrsZdej78YBEIWF44fdtWcZBWono,5223
|
9
|
-
haiway/context/state.py,sha256=
|
10
|
-
haiway/context/tasks.py,sha256=
|
9
|
+
haiway/context/state.py,sha256=7pXb5gvyPOWiRbxX-sSfO-hjaHcTUIp_uTKhjaSLeRo,4552
|
10
|
+
haiway/context/tasks.py,sha256=MKfsa-921cIpQ_BKskwokjR27suCHkHZa3O9kOE8UOg,2826
|
11
11
|
haiway/context/types.py,sha256=VvJA7wAPZ3ISpgyThVguioYUXqhHf0XkPfRd0M1ERiQ,142
|
12
|
-
haiway/helpers/__init__.py,sha256=
|
13
|
-
haiway/helpers/asynchrony.py,sha256
|
14
|
-
haiway/helpers/caching.py,sha256=
|
15
|
-
haiway/helpers/metrics.py,sha256=
|
16
|
-
haiway/helpers/retries.py,sha256=
|
17
|
-
haiway/helpers/throttling.py,sha256=
|
18
|
-
haiway/helpers/timeouted.py,sha256=
|
12
|
+
haiway/helpers/__init__.py,sha256=ZKDlL3twDqXyI1a9FDgRy3m1-Dfycvke6BJ4C3CndEk,671
|
13
|
+
haiway/helpers/asynchrony.py,sha256=YHLK5Hjc-5UWlQRypC11yHeEQyeAtHqrMoBTBfqQBvQ,6286
|
14
|
+
haiway/helpers/caching.py,sha256=EU5usTHGDzf0SO3bMW4hHB9oZlLlE7BxO_2ckbjYBw8,13274
|
15
|
+
haiway/helpers/metrics.py,sha256=VNxgPgV8pgt-51f2CANy1IVx8VMYIAxT3F849t3IeQs,14604
|
16
|
+
haiway/helpers/retries.py,sha256=3m1SsJW_YY_HPufX9LEzcd_MEyRRFNXvSExLeEti8W8,7539
|
17
|
+
haiway/helpers/throttling.py,sha256=r9HnUuo4nX36Pf-oMFHUJk-ZCDeXQ__JTDHlkSltRhA,4121
|
18
|
+
haiway/helpers/timeouted.py,sha256=DthIm4ytKhmiIKf-pcO_vrO1X-ImZh-sLNCWcLY9gfw,3337
|
19
19
|
haiway/helpers/tracing.py,sha256=8Gpcc_DguuHAdaxM4rGP0mB-S-8E7DKt7ZGym9f6x6Q,4018
|
20
20
|
haiway/state/__init__.py,sha256=emTuwGFn7HyjyTJ_ass69J5jQIA7_WHO4teZz_dR05Y,355
|
21
21
|
haiway/state/attributes.py,sha256=3chvq3ENoIX688RSYiqZnOCpxbzt-kQ2Wl8Fc3vVyMo,23311
|
@@ -27,8 +27,8 @@ haiway/types/__init__.py,sha256=-j4uDN6ix3GBXLBqXC-k_QOJSDlO6zvNCxDej8vVzek,342
|
|
27
27
|
haiway/types/default.py,sha256=h38-zFkbn_UPEiw1SdDF5rkObVmD9UJpmyhOgS1gQ9U,2208
|
28
28
|
haiway/types/frozen.py,sha256=CZhFCXnWAKEhuWSfILxA8smfdpMd5Ku694ycfLh98R8,76
|
29
29
|
haiway/types/missing.py,sha256=rDnyA2wxPkTbJl0L-zbo0owp7IJ04xkCIp6xD6wh8NI,1712
|
30
|
-
haiway/utils/__init__.py,sha256=
|
31
|
-
haiway/utils/always.py,sha256=
|
30
|
+
haiway/utils/__init__.py,sha256=JYo5EVquL2BCBsHtvySPTio_x5hSVDJCfu_naWzbqKE,867
|
31
|
+
haiway/utils/always.py,sha256=u1tssiErzm0Q3ASc3CV1rLhcMQ54MjpMlC_bRJMQhK4,1230
|
32
32
|
haiway/utils/collections.py,sha256=IzD-XSEyngKyzLTNG9sr7QjXIneoAzi3oRsDmbRHtzU,3276
|
33
33
|
haiway/utils/env.py,sha256=-hI4CgLkzdyueuECVjm-TfR3lQjE2bDsc72w7vNC4nQ,5339
|
34
34
|
haiway/utils/freezing.py,sha256=K34ZIMzbkpgkHKH-KF73plEbXExsajNRkRTYp9nJEf4,620
|
@@ -36,7 +36,8 @@ haiway/utils/logs.py,sha256=oDsc1ZdqKDjlTlctLbDcp9iX98Acr-1tdw-Pyg3DElo,1577
|
|
36
36
|
haiway/utils/mimic.py,sha256=BkVjTVP2TxxC8GChPGyDV6UXVwJmiRiSWeOYZNZFHxs,1828
|
37
37
|
haiway/utils/noop.py,sha256=qgbZlOKWY6_23Zs43OLukK2HagIQKRyR04zrFVm5rWI,344
|
38
38
|
haiway/utils/queue.py,sha256=Tk1bXvuNbEgapeC3-h_PYBASqVjhEoL8mUvtJnM29xI,4000
|
39
|
-
haiway
|
40
|
-
haiway-0.
|
41
|
-
haiway-0.
|
42
|
-
haiway-0.
|
39
|
+
haiway/utils/stream.py,sha256=Vqyi0EwcupkVyKQ7eple6z9DkcbSHkE-6yMw85mak9Q,2832
|
40
|
+
haiway-0.14.0.dist-info/METADATA,sha256=e01xN8K8-d8RiPRaV2ibtsxEHfITllUZmxZA_VzEpBs,4299
|
41
|
+
haiway-0.14.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
42
|
+
haiway-0.14.0.dist-info/licenses/LICENSE,sha256=3phcpHVNBP8jsi77gOO0E7rgKeDeu99Pi7DSnK9YHoQ,1069
|
43
|
+
haiway-0.14.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|