haiway 0.4.0__tar.gz → 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {haiway-0.4.0/src/haiway.egg-info → haiway-0.5.1}/PKG-INFO +1 -1
- {haiway-0.4.0 → haiway-0.5.1}/pyproject.toml +1 -1
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/__init__.py +2 -0
- haiway-0.5.1/src/haiway/context/disposables.py +78 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/helpers/__init__.py +2 -1
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/helpers/asynchrony.py +17 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/state/structure.py +8 -2
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/utils/queue.py +0 -4
- {haiway-0.4.0 → haiway-0.5.1/src/haiway.egg-info}/PKG-INFO +1 -1
- {haiway-0.4.0 → haiway-0.5.1}/tests/test_state.py +32 -1
- haiway-0.4.0/src/haiway/context/disposables.py +0 -68
- {haiway-0.4.0 → haiway-0.5.1}/LICENSE +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/README.md +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/setup.cfg +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/context/__init__.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/context/access.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/context/metrics.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/context/state.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/context/tasks.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/context/types.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/helpers/caching.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/helpers/retries.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/helpers/throttling.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/helpers/timeouted.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/helpers/tracing.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/py.typed +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/state/__init__.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/state/attributes.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/state/validation.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/types/__init__.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/types/frozen.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/types/missing.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/utils/__init__.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/utils/always.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/utils/env.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/utils/immutable.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/utils/logs.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/utils/mimic.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway/utils/noop.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway.egg-info/SOURCES.txt +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway.egg-info/dependency_links.txt +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway.egg-info/requires.txt +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/src/haiway.egg-info/top_level.txt +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/tests/test_async_queue.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/tests/test_auto_retry.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/tests/test_cache.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/tests/test_context.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/tests/test_streaming.py +0 -0
- {haiway-0.4.0 → haiway-0.5.1}/tests/test_timeout.py +0 -0
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
5
5
|
[project]
|
6
6
|
name = "haiway"
|
7
7
|
description = "Framework for dependency injection and state management within structured concurrency model."
|
8
|
-
version = "0.
|
8
|
+
version = "0.5.1"
|
9
9
|
readme = "README.md"
|
10
10
|
maintainers = [
|
11
11
|
{ name = "Kacper Kaliński", email = "kacper.kalinski@miquido.com" },
|
@@ -0,0 +1,78 @@
|
|
1
|
+
from asyncio import gather
|
2
|
+
from collections.abc import Iterable
|
3
|
+
from contextlib import AbstractAsyncContextManager
|
4
|
+
from itertools import chain
|
5
|
+
from types import TracebackType
|
6
|
+
from typing import final
|
7
|
+
|
8
|
+
from haiway.state import State
|
9
|
+
from haiway.utils import freeze
|
10
|
+
|
11
|
+
__all__ = [
|
12
|
+
"Disposable",
|
13
|
+
"Disposables",
|
14
|
+
]
|
15
|
+
|
16
|
+
type Disposable = AbstractAsyncContextManager[Iterable[State] | State | None]
|
17
|
+
|
18
|
+
|
19
|
+
@final
|
20
|
+
class Disposables:
|
21
|
+
def __init__(
|
22
|
+
self,
|
23
|
+
*disposables: Disposable,
|
24
|
+
) -> None:
|
25
|
+
self._disposables: tuple[Disposable, ...] = disposables
|
26
|
+
|
27
|
+
freeze(self)
|
28
|
+
|
29
|
+
def __bool__(self) -> bool:
|
30
|
+
return len(self._disposables) > 0
|
31
|
+
|
32
|
+
async def _initialize(
|
33
|
+
self,
|
34
|
+
disposable: Disposable,
|
35
|
+
/,
|
36
|
+
) -> Iterable[State]:
|
37
|
+
match await disposable.__aenter__():
|
38
|
+
case None:
|
39
|
+
return ()
|
40
|
+
|
41
|
+
case State() as single:
|
42
|
+
return (single,)
|
43
|
+
|
44
|
+
case multiple:
|
45
|
+
return multiple
|
46
|
+
|
47
|
+
async def __aenter__(self) -> Iterable[State]:
|
48
|
+
return [
|
49
|
+
*chain.from_iterable(
|
50
|
+
state
|
51
|
+
for state in await gather(
|
52
|
+
*[self._initialize(disposable) for disposable in self._disposables],
|
53
|
+
)
|
54
|
+
)
|
55
|
+
]
|
56
|
+
|
57
|
+
async def __aexit__(
|
58
|
+
self,
|
59
|
+
exc_type: type[BaseException] | None,
|
60
|
+
exc_val: BaseException | None,
|
61
|
+
exc_tb: TracebackType | None,
|
62
|
+
) -> None:
|
63
|
+
results: list[bool | BaseException | None] = await gather(
|
64
|
+
*[
|
65
|
+
disposable.__aexit__(
|
66
|
+
exc_type,
|
67
|
+
exc_val,
|
68
|
+
exc_tb,
|
69
|
+
)
|
70
|
+
for disposable in self._disposables
|
71
|
+
],
|
72
|
+
return_exceptions=True,
|
73
|
+
)
|
74
|
+
|
75
|
+
exceptions: list[BaseException] = [exc for exc in results if isinstance(exc, BaseException)]
|
76
|
+
|
77
|
+
if len(exceptions) > 1:
|
78
|
+
raise BaseExceptionGroup("Disposing errors", exceptions)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from haiway.helpers.asynchrony import asynchronous
|
1
|
+
from haiway.helpers.asynchrony import asynchronous, wrap_async
|
2
2
|
from haiway.helpers.caching import cache
|
3
3
|
from haiway.helpers.retries import retry
|
4
4
|
from haiway.helpers.throttling import throttle
|
@@ -14,4 +14,5 @@ __all__ = [
|
|
14
14
|
"throttle",
|
15
15
|
"timeout",
|
16
16
|
"traced",
|
17
|
+
"wrap_async",
|
17
18
|
]
|
@@ -9,9 +9,26 @@ from haiway.types.missing import MISSING, Missing
|
|
9
9
|
|
10
10
|
__all__ = [
|
11
11
|
"asynchronous",
|
12
|
+
"wrap_async",
|
12
13
|
]
|
13
14
|
|
14
15
|
|
16
|
+
def wrap_async[**Args, Result](
|
17
|
+
function: Callable[Args, Coroutine[None, None, Result]] | Callable[Args, Result],
|
18
|
+
/,
|
19
|
+
) -> Callable[Args, Coroutine[None, None, Result]]:
|
20
|
+
if iscoroutinefunction(function):
|
21
|
+
return function
|
22
|
+
|
23
|
+
else:
|
24
|
+
|
25
|
+
async def async_function(*args: Args.args, **kwargs: Args.kwargs) -> Result:
|
26
|
+
return cast(Callable[Args, Result], function)(*args, **kwargs)
|
27
|
+
|
28
|
+
_mimic_async(function, within=async_function)
|
29
|
+
return async_function
|
30
|
+
|
31
|
+
|
15
32
|
@overload
|
16
33
|
def asynchronous[**Args, Result]() -> (
|
17
34
|
Callable[
|
@@ -16,7 +16,7 @@ from weakref import WeakValueDictionary
|
|
16
16
|
|
17
17
|
from haiway.state.attributes import AttributeAnnotation, attribute_annotations
|
18
18
|
from haiway.state.validation import attribute_type_validator
|
19
|
-
from haiway.types
|
19
|
+
from haiway.types import MISSING, Missing, not_missing
|
20
20
|
|
21
21
|
__all__ = [
|
22
22
|
"State",
|
@@ -184,7 +184,13 @@ class State(metaclass=StateMeta):
|
|
184
184
|
return self.__replace__(**kwargs)
|
185
185
|
|
186
186
|
def as_dict(self) -> dict[str, Any]:
|
187
|
-
|
187
|
+
dict_result: dict[str, Any] = {}
|
188
|
+
for key in self.__ATTRIBUTES__.keys():
|
189
|
+
value: Any | Missing = getattr(self, key, MISSING)
|
190
|
+
if not_missing(value):
|
191
|
+
dict_result[key] = value
|
192
|
+
|
193
|
+
return dict_result
|
188
194
|
|
189
195
|
def __str__(self) -> str:
|
190
196
|
attributes: str = ", ".join([f"{key}: {value}" for key, value in vars(self).items()])
|
@@ -1,7 +1,6 @@
|
|
1
1
|
from asyncio import AbstractEventLoop, CancelledError, Future, get_running_loop
|
2
2
|
from collections import deque
|
3
3
|
from collections.abc import AsyncIterator
|
4
|
-
from typing import Self
|
5
4
|
|
6
5
|
__all__ = [
|
7
6
|
"AsyncQueue",
|
@@ -63,9 +62,6 @@ class AsyncQueue[Element](AsyncIterator[Element]):
|
|
63
62
|
def cancel(self) -> None:
|
64
63
|
self.finish(exception=CancelledError())
|
65
64
|
|
66
|
-
def __aiter__(self) -> Self:
|
67
|
-
return self
|
68
|
-
|
69
65
|
async def __anext__(self) -> Element:
|
70
66
|
assert self._waiting is None, "Only a single queue consumer is supported!" # nosec: B101
|
71
67
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from collections.abc import Callable
|
2
2
|
from typing import Literal, Protocol, Self, TypedDict, runtime_checkable
|
3
3
|
|
4
|
-
from haiway import State, frozenlist
|
4
|
+
from haiway import MISSING, Missing, State, frozenlist
|
5
5
|
|
6
6
|
|
7
7
|
def test_basic_initializes_with_arguments() -> None:
|
@@ -63,6 +63,17 @@ def test_basic_initializes_with_defaults() -> None:
|
|
63
63
|
assert basic.optional is None
|
64
64
|
|
65
65
|
|
66
|
+
def test_basic_equals_checks_properties() -> None:
|
67
|
+
class Basics(State):
|
68
|
+
string: str
|
69
|
+
integer: int
|
70
|
+
|
71
|
+
assert Basics(string="a", integer=1) == Basics(string="a", integer=1)
|
72
|
+
assert Basics(string="a", integer=1) != Basics(string="b", integer=1)
|
73
|
+
assert Basics(string="a", integer=1) != Basics(string="a", integer=2)
|
74
|
+
assert Basics(string="a", integer=1) != Basics(string="b", integer=2)
|
75
|
+
|
76
|
+
|
66
77
|
def test_basic_initializes_with_arguments_and_defaults() -> None:
|
67
78
|
class Basics(State):
|
68
79
|
string: str
|
@@ -119,3 +130,23 @@ def test_nested_initializes_with_proper_arguments() -> None:
|
|
119
130
|
recursion=None,
|
120
131
|
self_recursion=None,
|
121
132
|
)
|
133
|
+
|
134
|
+
|
135
|
+
def test_dict_skips_missing_properties() -> None:
|
136
|
+
class Basics(State):
|
137
|
+
string: str
|
138
|
+
integer: int | Missing | None
|
139
|
+
|
140
|
+
assert Basics(string="a", integer=1).as_dict() == {"string": "a", "integer": 1}
|
141
|
+
assert Basics(string="a", integer=MISSING).as_dict() == {"string": "a"}
|
142
|
+
assert Basics(string="a", integer=None).as_dict() == {"string": "a", "integer": None}
|
143
|
+
|
144
|
+
|
145
|
+
def test_initialization_allows_missing_properties() -> None:
|
146
|
+
class Basics(State):
|
147
|
+
string: str
|
148
|
+
integer: int | Missing | None
|
149
|
+
|
150
|
+
assert Basics(**{"string": "a", "integer": 1}) == Basics(string="a", integer=1)
|
151
|
+
assert Basics(**{"string": "a", "integer": None}) == Basics(string="a", integer=None)
|
152
|
+
assert Basics(**{"string": "a"}) == Basics(string="a", integer=MISSING)
|
@@ -1,68 +0,0 @@
|
|
1
|
-
from asyncio import gather, shield
|
2
|
-
from collections.abc import Iterable
|
3
|
-
from types import TracebackType
|
4
|
-
from typing import Protocol, final, runtime_checkable
|
5
|
-
|
6
|
-
from haiway.state import State
|
7
|
-
|
8
|
-
__all__ = [
|
9
|
-
"Disposable",
|
10
|
-
"Disposables",
|
11
|
-
]
|
12
|
-
|
13
|
-
|
14
|
-
@runtime_checkable
|
15
|
-
class Disposable(Protocol):
|
16
|
-
async def initialize(self) -> State | None: ...
|
17
|
-
async def dispose(self) -> None: ...
|
18
|
-
|
19
|
-
|
20
|
-
@final
|
21
|
-
class Disposables:
|
22
|
-
def __init__(
|
23
|
-
self,
|
24
|
-
*disposables: Disposable,
|
25
|
-
) -> None:
|
26
|
-
self._disposables: tuple[Disposable, ...] = disposables
|
27
|
-
|
28
|
-
async def initialize(self) -> Iterable[State]:
|
29
|
-
return [
|
30
|
-
state
|
31
|
-
for state in await gather(
|
32
|
-
*[disposable.initialize() for disposable in self._disposables],
|
33
|
-
)
|
34
|
-
if state is not None
|
35
|
-
]
|
36
|
-
|
37
|
-
async def dispose(self) -> None:
|
38
|
-
results: list[BaseException | None] = await shield(
|
39
|
-
gather(
|
40
|
-
*[disposable.dispose() for disposable in self._disposables],
|
41
|
-
return_exceptions=True,
|
42
|
-
),
|
43
|
-
)
|
44
|
-
|
45
|
-
self._disposables = ()
|
46
|
-
exceptions: list[BaseException] = [
|
47
|
-
exception for exception in results if exception is not None
|
48
|
-
]
|
49
|
-
|
50
|
-
if len(exceptions) > 1:
|
51
|
-
raise BaseExceptionGroup("Disposing errors", exceptions)
|
52
|
-
|
53
|
-
elif exceptions:
|
54
|
-
raise exceptions[0]
|
55
|
-
|
56
|
-
def __bool__(self) -> bool:
|
57
|
-
return len(self._disposables) > 0
|
58
|
-
|
59
|
-
async def __aenter__(self) -> Iterable[State]:
|
60
|
-
return await self.initialize()
|
61
|
-
|
62
|
-
async def __aexit__(
|
63
|
-
self,
|
64
|
-
exc_type: type[BaseException] | None,
|
65
|
-
exc_val: BaseException | None,
|
66
|
-
exc_tb: TracebackType | None,
|
67
|
-
) -> None:
|
68
|
-
await self.dispose()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|