omlish 0.0.0.dev5__py3-none-any.whl → 0.0.0.dev7__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.
Potentially problematic release.
This version of omlish might be problematic. Click here for more details.
- omlish/__about__.py +109 -5
- omlish/__init__.py +0 -8
- omlish/asyncs/__init__.py +9 -9
- omlish/asyncs/anyio.py +123 -19
- omlish/asyncs/asyncio.py +23 -0
- omlish/asyncs/asyncs.py +9 -6
- omlish/asyncs/bridge.py +316 -0
- omlish/asyncs/trio_asyncio.py +7 -3
- omlish/bootstrap.py +737 -0
- omlish/check.py +1 -1
- omlish/collections/__init__.py +5 -0
- omlish/collections/exceptions.py +2 -0
- omlish/collections/identity.py +7 -0
- omlish/collections/utils.py +38 -9
- omlish/configs/strings.py +96 -0
- omlish/dataclasses/__init__.py +16 -0
- omlish/dataclasses/impl/copy.py +30 -0
- omlish/dataclasses/impl/descriptors.py +95 -0
- omlish/dataclasses/impl/exceptions.py +6 -0
- omlish/dataclasses/impl/fields.py +24 -25
- omlish/dataclasses/impl/init.py +4 -2
- omlish/dataclasses/impl/main.py +2 -0
- omlish/dataclasses/impl/reflect.py +1 -1
- omlish/dataclasses/utils.py +67 -0
- omlish/{lang/datetimes.py → datetimes.py} +8 -4
- omlish/diag/__init__.py +4 -0
- omlish/diag/procfs.py +2 -2
- omlish/{testing → diag}/pydevd.py +35 -0
- omlish/diag/threads.py +131 -48
- omlish/dispatch/_dispatch2.py +65 -0
- omlish/dispatch/_dispatch3.py +104 -0
- omlish/docker.py +16 -1
- omlish/fnpairs.py +11 -4
- omlish/formats/__init__.py +0 -0
- omlish/{configs → formats}/dotenv.py +15 -24
- omlish/{json.py → formats/json.py} +2 -1
- omlish/formats/yaml.py +223 -0
- omlish/graphs/trees.py +1 -1
- omlish/http/asgi.py +2 -1
- omlish/http/collections.py +15 -0
- omlish/http/consts.py +22 -1
- omlish/http/sessions.py +10 -3
- omlish/inject/__init__.py +49 -17
- omlish/inject/binder.py +185 -5
- omlish/inject/bindings.py +3 -36
- omlish/inject/eagers.py +2 -8
- omlish/inject/elements.py +31 -10
- omlish/inject/exceptions.py +1 -1
- omlish/inject/impl/elements.py +37 -12
- omlish/inject/impl/injector.py +72 -25
- omlish/inject/impl/inspect.py +33 -5
- omlish/inject/impl/origins.py +77 -0
- omlish/inject/impl/{private.py → privates.py} +2 -2
- omlish/inject/impl/scopes.py +6 -2
- omlish/inject/injector.py +8 -4
- omlish/inject/inspect.py +18 -0
- omlish/inject/keys.py +8 -14
- omlish/inject/listeners.py +26 -0
- omlish/inject/managed.py +76 -10
- omlish/inject/multis.py +68 -18
- omlish/inject/origins.py +30 -0
- omlish/inject/overrides.py +5 -4
- omlish/inject/{private.py → privates.py} +6 -10
- omlish/inject/providers.py +12 -85
- omlish/inject/scopes.py +13 -6
- omlish/inject/types.py +3 -1
- omlish/inject/utils.py +18 -0
- omlish/iterators.py +69 -2
- omlish/lang/__init__.py +24 -9
- omlish/lang/cached.py +2 -2
- omlish/lang/classes/restrict.py +12 -1
- omlish/lang/classes/simple.py +18 -8
- omlish/lang/contextmanagers.py +13 -4
- omlish/lang/descriptors.py +132 -1
- omlish/lang/functions.py +8 -28
- omlish/lang/imports.py +67 -0
- omlish/lang/iterables.py +60 -1
- omlish/lang/maybes.py +3 -0
- omlish/lang/objects.py +38 -0
- omlish/lang/strings.py +25 -0
- omlish/lang/sys.py +9 -0
- omlish/lang/typing.py +42 -0
- omlish/lifecycles/__init__.py +34 -0
- omlish/lifecycles/abstract.py +43 -0
- omlish/lifecycles/base.py +51 -0
- omlish/lifecycles/contextmanagers.py +74 -0
- omlish/lifecycles/controller.py +116 -0
- omlish/lifecycles/manager.py +161 -0
- omlish/lifecycles/states.py +43 -0
- omlish/lifecycles/transitions.py +64 -0
- omlish/lite/__init__.py +1 -0
- omlish/lite/cached.py +18 -0
- omlish/lite/check.py +29 -0
- omlish/lite/contextmanagers.py +18 -0
- omlish/lite/json.py +30 -0
- omlish/lite/logs.py +52 -0
- omlish/lite/marshal.py +316 -0
- omlish/lite/reflect.py +49 -0
- omlish/lite/runtime.py +18 -0
- omlish/lite/secrets.py +19 -0
- omlish/lite/strings.py +25 -0
- omlish/lite/subprocesses.py +112 -0
- omlish/logs/configs.py +15 -2
- omlish/logs/formatters.py +7 -2
- omlish/marshal/__init__.py +32 -0
- omlish/marshal/any.py +5 -5
- omlish/marshal/base.py +27 -11
- omlish/marshal/base64.py +24 -9
- omlish/marshal/dataclasses.py +34 -28
- omlish/marshal/datetimes.py +74 -18
- omlish/marshal/enums.py +14 -8
- omlish/marshal/exceptions.py +11 -1
- omlish/marshal/factories.py +59 -74
- omlish/marshal/forbidden.py +35 -0
- omlish/marshal/global_.py +11 -4
- omlish/marshal/iterables.py +21 -24
- omlish/marshal/mappings.py +23 -26
- omlish/marshal/naming.py +4 -0
- omlish/marshal/numbers.py +51 -0
- omlish/marshal/objects.py +1 -0
- omlish/marshal/optionals.py +11 -12
- omlish/marshal/polymorphism.py +86 -21
- omlish/marshal/primitives.py +4 -5
- omlish/marshal/standard.py +13 -8
- omlish/marshal/uuids.py +4 -5
- omlish/matchfns.py +218 -0
- omlish/os.py +64 -0
- omlish/reflect/__init__.py +39 -0
- omlish/reflect/isinstance.py +38 -0
- omlish/reflect/ops.py +84 -0
- omlish/reflect/subst.py +110 -0
- omlish/reflect/types.py +275 -0
- omlish/secrets/__init__.py +23 -0
- omlish/secrets/crypto.py +132 -0
- omlish/secrets/marshal.py +70 -0
- omlish/secrets/openssl.py +207 -0
- omlish/secrets/passwords.py +120 -0
- omlish/secrets/secrets.py +299 -0
- omlish/secrets/subprocesses.py +42 -0
- omlish/sql/dbs.py +7 -6
- omlish/sql/duckdb.py +136 -0
- omlish/sql/exprs.py +12 -0
- omlish/sql/secrets.py +10 -0
- omlish/sql/sqlean.py +17 -0
- omlish/term.py +2 -2
- omlish/testing/pytest/__init__.py +3 -2
- omlish/testing/pytest/inject/harness.py +3 -3
- omlish/testing/pytest/marks.py +4 -7
- omlish/testing/pytest/plugins/__init__.py +1 -0
- omlish/testing/pytest/plugins/asyncs.py +136 -0
- omlish/testing/pytest/plugins/pydevd.py +1 -1
- omlish/testing/pytest/plugins/switches.py +54 -19
- omlish/text/glyphsplit.py +97 -0
- omlish-0.0.0.dev7.dist-info/METADATA +50 -0
- omlish-0.0.0.dev7.dist-info/RECORD +268 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/WHEEL +1 -1
- omlish/reflect.py +0 -355
- omlish-0.0.0.dev5.dist-info/METADATA +0 -34
- omlish-0.0.0.dev5.dist-info/RECORD +0 -212
- /omlish/{asyncs/futures.py → concurrent.py} +0 -0
- /omlish/{configs → formats}/props.py +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/top_level.txt +0 -0
omlish/lang/iterables.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import dataclasses as dc
|
|
1
2
|
import itertools
|
|
2
3
|
import typing as ta
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
T = ta.TypeVar('T')
|
|
7
|
+
S = ta.TypeVar('S')
|
|
8
|
+
R = ta.TypeVar('R')
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
BUILTIN_SCALAR_ITERABLE_TYPES: tuple[type, ...] = (
|
|
@@ -34,7 +37,13 @@ def peek(vs: ta.Iterable[T]) -> tuple[T, ta.Iterator[T]]:
|
|
|
34
37
|
return v, itertools.chain(iter((v,)), it)
|
|
35
38
|
|
|
36
39
|
|
|
37
|
-
Rangeable: ta.TypeAlias =
|
|
40
|
+
Rangeable: ta.TypeAlias = ta.Union[ # noqa
|
|
41
|
+
int,
|
|
42
|
+
tuple[int],
|
|
43
|
+
tuple[int, int],
|
|
44
|
+
tuple[int, int, int],
|
|
45
|
+
ta.Iterable[int],
|
|
46
|
+
]
|
|
38
47
|
|
|
39
48
|
|
|
40
49
|
def asrange(i: Rangeable) -> ta.Iterable[int]:
|
|
@@ -52,3 +61,53 @@ def prodrange(*dims: Rangeable) -> ta.Iterable[ta.Sequence[int]]:
|
|
|
52
61
|
if not dims:
|
|
53
62
|
return []
|
|
54
63
|
return itertools.product(*map(asrange, dims))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dc.dataclass(frozen=True)
|
|
67
|
+
class itergen(ta.Generic[T]): # noqa
|
|
68
|
+
fn: ta.Callable[[], ta.Iterable[T]]
|
|
69
|
+
|
|
70
|
+
def __iter__(self):
|
|
71
|
+
return iter(self.fn())
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def renumerate(it: ta.Iterable[T]) -> ta.Iterable[tuple[T, int]]:
|
|
75
|
+
return ((e, i) for i, e in enumerate(it))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
flatten = itertools.chain.from_iterable
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def flatmap(fn: ta.Callable[[T], ta.Iterable[R]], it: ta.Iterable[T]) -> ta.Iterable[R]:
|
|
82
|
+
return flatten(map(fn, it))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Generator(ta.Generator[T, S, R]):
|
|
86
|
+
def __init__(self, gen: ta.Generator[T, S, R]) -> None:
|
|
87
|
+
super().__init__()
|
|
88
|
+
self.gen = gen
|
|
89
|
+
|
|
90
|
+
value: R
|
|
91
|
+
|
|
92
|
+
def __iter__(self):
|
|
93
|
+
return self
|
|
94
|
+
|
|
95
|
+
def __next__(self):
|
|
96
|
+
return self.send(None)
|
|
97
|
+
|
|
98
|
+
def send(self, v):
|
|
99
|
+
try:
|
|
100
|
+
return self.gen.send(v)
|
|
101
|
+
except StopIteration as e:
|
|
102
|
+
self.value = e.value
|
|
103
|
+
raise
|
|
104
|
+
|
|
105
|
+
def throw(self, *args):
|
|
106
|
+
try:
|
|
107
|
+
return self.gen.throw(*args)
|
|
108
|
+
except StopIteration as e:
|
|
109
|
+
self.value = e.value
|
|
110
|
+
raise
|
|
111
|
+
|
|
112
|
+
def close(self):
|
|
113
|
+
self.gen.close()
|
omlish/lang/maybes.py
CHANGED
omlish/lang/objects.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import types
|
|
2
2
|
import typing as ta
|
|
3
|
+
import weakref
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
T = ta.TypeVar('T')
|
|
@@ -28,6 +29,28 @@ def opt_repr(obj: ta.Any) -> str | None:
|
|
|
28
29
|
##
|
|
29
30
|
|
|
30
31
|
|
|
32
|
+
_CAN_WEAKREF_TYPE_MAP: ta.MutableMapping[type, bool] = weakref.WeakKeyDictionary()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def can_weakref(obj: ta.Any) -> bool:
|
|
36
|
+
_type = type(obj)
|
|
37
|
+
try:
|
|
38
|
+
return _CAN_WEAKREF_TYPE_MAP[_type]
|
|
39
|
+
except KeyError:
|
|
40
|
+
pass
|
|
41
|
+
try:
|
|
42
|
+
weakref.ref(obj)
|
|
43
|
+
except TypeError:
|
|
44
|
+
ret = False
|
|
45
|
+
else:
|
|
46
|
+
ret = True
|
|
47
|
+
_CAN_WEAKREF_TYPE_MAP[_type] = ret
|
|
48
|
+
return ret
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
##
|
|
52
|
+
|
|
53
|
+
|
|
31
54
|
def new_type(
|
|
32
55
|
name: str,
|
|
33
56
|
bases: ta.Sequence[ta.Any],
|
|
@@ -62,6 +85,21 @@ def super_meta(
|
|
|
62
85
|
##
|
|
63
86
|
|
|
64
87
|
|
|
88
|
+
def deep_subclasses(cls: type) -> ta.Iterator[type]:
|
|
89
|
+
seen = set()
|
|
90
|
+
todo = list(reversed(cls.__subclasses__()))
|
|
91
|
+
while todo:
|
|
92
|
+
cur = todo.pop()
|
|
93
|
+
if cur in seen:
|
|
94
|
+
continue
|
|
95
|
+
seen.add(cur)
|
|
96
|
+
yield cur
|
|
97
|
+
todo.extend(reversed(cur.__subclasses__()))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
|
|
102
|
+
|
|
65
103
|
class SimpleProxy(ta.Generic[T]):
|
|
66
104
|
|
|
67
105
|
class Descriptor:
|
omlish/lang/strings.py
CHANGED
|
@@ -126,3 +126,28 @@ def is_ident_cont(c: str) -> bool:
|
|
|
126
126
|
|
|
127
127
|
def is_ident(name: str) -> bool:
|
|
128
128
|
return is_ident_start(name[0]) and all(is_ident_cont(c) for c in name[1:])
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
##
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
BOOL_STRINGS: ta.Sequence[tuple[str, str]] = [
|
|
135
|
+
('n', 'y'),
|
|
136
|
+
('no', 'yes'),
|
|
137
|
+
('f', 't'),
|
|
138
|
+
('false', 'true'),
|
|
139
|
+
('off', 'on'),
|
|
140
|
+
('0', '1'),
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
BOOL_FALSE_STRINGS = frozenset(tup[0] for tup in BOOL_STRINGS)
|
|
144
|
+
BOOL_TRUE_STRINGS = frozenset(tup[1] for tup in BOOL_STRINGS)
|
|
145
|
+
|
|
146
|
+
STRING_BOOL_VALUES: ta.Mapping[str, bool] = {
|
|
147
|
+
k: v
|
|
148
|
+
for ks, v in [
|
|
149
|
+
(BOOL_FALSE_STRINGS, False),
|
|
150
|
+
(BOOL_TRUE_STRINGS, True),
|
|
151
|
+
]
|
|
152
|
+
for k in ks
|
|
153
|
+
}
|
omlish/lang/sys.py
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
REQUIRED_PYTHON_VERSION = (3, 12)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def check_runtime_version() -> None:
|
|
8
|
+
if sys.version_info < REQUIRED_PYTHON_VERSION:
|
|
9
|
+
raise OSError(
|
|
10
|
+
f'Requires python {REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
|
|
11
|
+
|
|
12
|
+
|
|
4
13
|
def is_gil_enabled() -> bool:
|
|
5
14
|
if (fn := getattr(sys, '_is_gil_enabled', None)) is not None:
|
|
6
15
|
return fn()
|
omlish/lang/typing.py
CHANGED
|
@@ -5,6 +5,7 @@ TODO:
|
|
|
5
5
|
- probably need to gen types per inst
|
|
6
6
|
- typed_factory
|
|
7
7
|
"""
|
|
8
|
+
import dataclasses as dc
|
|
8
9
|
import functools
|
|
9
10
|
import inspect
|
|
10
11
|
import typing as ta
|
|
@@ -12,6 +13,11 @@ import typing as ta
|
|
|
12
13
|
|
|
13
14
|
Ty = ta.TypeVar('Ty', bound=type)
|
|
14
15
|
|
|
16
|
+
T = ta.TypeVar('T')
|
|
17
|
+
A0 = ta.TypeVar('A0')
|
|
18
|
+
A1 = ta.TypeVar('A1')
|
|
19
|
+
A2 = ta.TypeVar('A2')
|
|
20
|
+
|
|
15
21
|
|
|
16
22
|
BytesLike: ta.TypeAlias = bytes | bytearray
|
|
17
23
|
|
|
@@ -91,3 +97,39 @@ def typed_partial(obj, **kw): # noqa
|
|
|
91
97
|
},
|
|
92
98
|
)(inner)
|
|
93
99
|
return _update_wrapper_no_anns(lam, obj)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
##
|
|
103
|
+
# A workaround for typing deficiencies (like `Argument 2 to NewType(...) must be subclassable`).
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dc.dataclass(frozen=True)
|
|
107
|
+
class Func0(ta.Generic[T]):
|
|
108
|
+
fn: ta.Callable[[], T]
|
|
109
|
+
|
|
110
|
+
def __call__(self) -> T:
|
|
111
|
+
return self.fn()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dc.dataclass(frozen=True)
|
|
115
|
+
class Func1(ta.Generic[A0, T]):
|
|
116
|
+
fn: ta.Callable[[A0], T]
|
|
117
|
+
|
|
118
|
+
def __call__(self, a0: A0) -> T:
|
|
119
|
+
return self.fn(a0)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dc.dataclass(frozen=True)
|
|
123
|
+
class Func2(ta.Generic[A0, A1, T]):
|
|
124
|
+
fn: ta.Callable[[A0, A1], T]
|
|
125
|
+
|
|
126
|
+
def __call__(self, a0: A0, a1: A1) -> T:
|
|
127
|
+
return self.fn(a0, a1)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dc.dataclass(frozen=True)
|
|
131
|
+
class Func3(ta.Generic[A0, A1, A2, T]):
|
|
132
|
+
fn: ta.Callable[[A0, A1, A2], T]
|
|
133
|
+
|
|
134
|
+
def __call__(self, a0: A0, a1: A1, a2: A2) -> T:
|
|
135
|
+
return self.fn(a0, a1, a2)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from .abstract import ( # noqa
|
|
2
|
+
AbstractLifecycle,
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
from .base import ( # noqa
|
|
6
|
+
CallbackLifecycle,
|
|
7
|
+
Lifecycle,
|
|
8
|
+
LifecycleCallback,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from .contextmanagers import ( # noqa
|
|
12
|
+
ContextManagerLifecycle,
|
|
13
|
+
LifecycleContextManager,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from .controller import ( # noqa
|
|
17
|
+
LifecycleController,
|
|
18
|
+
LifecycleListener,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from .manager import ( # noqa
|
|
22
|
+
LifecycleManager,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from .states import ( # noqa
|
|
26
|
+
LifecycleState,
|
|
27
|
+
LifecycleStateError,
|
|
28
|
+
LifecycleStates,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
from .transitions import ( # noqa
|
|
32
|
+
LifecycleTransition,
|
|
33
|
+
LifecycleTransitions,
|
|
34
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
|
|
3
|
+
from .. import cached
|
|
4
|
+
from .. import dataclasses as dc
|
|
5
|
+
from .. import lang
|
|
6
|
+
from .base import Lifecycle
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
AbstractLifecycleT = ta.TypeVar('AbstractLifecycleT', bound='AbstractLifecycle')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AbstractLifecycle(lang.Abstract):
|
|
13
|
+
@dc.dataclass(frozen=True)
|
|
14
|
+
class _Lifecycle(Lifecycle, lang.Final, ta.Generic[AbstractLifecycleT]):
|
|
15
|
+
obj: AbstractLifecycleT
|
|
16
|
+
|
|
17
|
+
def lifecycle_construct(self) -> None:
|
|
18
|
+
self.obj._lifecycle_construct() # noqa
|
|
19
|
+
|
|
20
|
+
def lifecycle_start(self) -> None:
|
|
21
|
+
self.obj._lifecycle_start() # noqa
|
|
22
|
+
|
|
23
|
+
def lifecycle_stop(self) -> None:
|
|
24
|
+
self.obj._lifecycle_stop() # noqa
|
|
25
|
+
|
|
26
|
+
def lifecycle_destroy(self) -> None:
|
|
27
|
+
self.obj._lifecycle_destroy() # noqa
|
|
28
|
+
|
|
29
|
+
@cached.property
|
|
30
|
+
def _lifecycle(self) -> _Lifecycle[ta.Self]:
|
|
31
|
+
return AbstractLifecycle._Lifecycle(self)
|
|
32
|
+
|
|
33
|
+
def _lifecycle_construct(self) -> None:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def _lifecycle_start(self) -> None:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
def _lifecycle_stop(self) -> None:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
def _lifecycle_destroy(self) -> None:
|
|
43
|
+
pass
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
|
|
3
|
+
from .. import dataclasses as dc
|
|
4
|
+
from .. import lang
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
LifecycleT = ta.TypeVar('LifecycleT', bound='Lifecycle')
|
|
8
|
+
LifecycleCallback: ta.TypeAlias = ta.Callable[[LifecycleT], None]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Lifecycle:
|
|
12
|
+
|
|
13
|
+
def lifecycle_construct(self) -> None:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
def lifecycle_start(self) -> None:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
def lifecycle_stop(self) -> None:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def lifecycle_destroy(self) -> None:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
|
27
|
+
class CallbackLifecycle(Lifecycle, lang.Final, ta.Generic[LifecycleT]):
|
|
28
|
+
on_construct: LifecycleCallback['CallbackLifecycle[LifecycleT]'] | None = None
|
|
29
|
+
on_start: LifecycleCallback['CallbackLifecycle[LifecycleT]'] | None = None
|
|
30
|
+
on_stop: LifecycleCallback['CallbackLifecycle[LifecycleT]'] | None = None
|
|
31
|
+
on_destroy: LifecycleCallback['CallbackLifecycle[LifecycleT]'] | None = None
|
|
32
|
+
|
|
33
|
+
@ta.override
|
|
34
|
+
def lifecycle_construct(self) -> None:
|
|
35
|
+
if self.on_construct is not None:
|
|
36
|
+
self.on_construct(self)
|
|
37
|
+
|
|
38
|
+
@ta.override
|
|
39
|
+
def lifecycle_start(self) -> None:
|
|
40
|
+
if self.on_start is not None:
|
|
41
|
+
self.on_start(self)
|
|
42
|
+
|
|
43
|
+
@ta.override
|
|
44
|
+
def lifecycle_stop(self) -> None:
|
|
45
|
+
if self.on_stop is not None:
|
|
46
|
+
self.on_stop(self)
|
|
47
|
+
|
|
48
|
+
@ta.override
|
|
49
|
+
def lifecycle_destroy(self) -> None:
|
|
50
|
+
if self.on_destroy is not None:
|
|
51
|
+
self.on_destroy(self)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import types
|
|
2
|
+
import typing as ta
|
|
3
|
+
|
|
4
|
+
from .. import dataclasses as dc
|
|
5
|
+
from .. import defs
|
|
6
|
+
from .. import lang
|
|
7
|
+
from .base import Lifecycle
|
|
8
|
+
from .controller import LifecycleController
|
|
9
|
+
from .states import LifecycleState
|
|
10
|
+
from .states import LifecycleStates
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
LifecycleT = ta.TypeVar('LifecycleT', bound='Lifecycle')
|
|
14
|
+
ContextManagerT = ta.TypeVar('ContextManagerT', bound=ta.ContextManager)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dc.dataclass(frozen=True)
|
|
18
|
+
class ContextManagerLifecycle(Lifecycle, lang.Final, ta.Generic[ContextManagerT]):
|
|
19
|
+
cm: ContextManagerT
|
|
20
|
+
|
|
21
|
+
@ta.override
|
|
22
|
+
def lifecycle_start(self) -> None:
|
|
23
|
+
self.cm.__enter__()
|
|
24
|
+
|
|
25
|
+
@ta.override
|
|
26
|
+
def lifecycle_stop(self) -> None:
|
|
27
|
+
self.cm.__exit__(None, None, None)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class LifecycleContextManager(ta.Generic[LifecycleT]):
|
|
31
|
+
|
|
32
|
+
def __init__(self, lifecycle: LifecycleT) -> None:
|
|
33
|
+
super().__init__()
|
|
34
|
+
self._lifecycle = lifecycle
|
|
35
|
+
self._controller = lifecycle if isinstance(lifecycle, LifecycleController) else LifecycleController(lifecycle)
|
|
36
|
+
|
|
37
|
+
defs.repr('lifecycle', 'state')
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def lifecycle(self) -> LifecycleT:
|
|
41
|
+
return self._lifecycle
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def controller(self) -> LifecycleController:
|
|
45
|
+
return self._controller
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def state(self) -> LifecycleState:
|
|
49
|
+
return self._controller.state
|
|
50
|
+
|
|
51
|
+
def __enter__(self) -> ta.Self:
|
|
52
|
+
try:
|
|
53
|
+
self._controller.lifecycle_construct()
|
|
54
|
+
self._controller.lifecycle_start()
|
|
55
|
+
except Exception:
|
|
56
|
+
self._controller.lifecycle_destroy()
|
|
57
|
+
raise
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
def __exit__(
|
|
61
|
+
self,
|
|
62
|
+
exc_type: type[BaseException] | None,
|
|
63
|
+
exc_val: BaseException | None,
|
|
64
|
+
exc_tb: types.TracebackType | None,
|
|
65
|
+
) -> bool | None:
|
|
66
|
+
try:
|
|
67
|
+
if self._controller.state is LifecycleStates.STARTED:
|
|
68
|
+
self._controller.lifecycle_stop()
|
|
69
|
+
except Exception:
|
|
70
|
+
self._controller.lifecycle_destroy()
|
|
71
|
+
raise
|
|
72
|
+
else:
|
|
73
|
+
self._controller.lifecycle_destroy()
|
|
74
|
+
return None
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
|
|
3
|
+
from .. import check
|
|
4
|
+
from .. import defs
|
|
5
|
+
from .. import lang
|
|
6
|
+
from .base import Lifecycle
|
|
7
|
+
from .states import LifecycleState
|
|
8
|
+
from .states import LifecycleStates
|
|
9
|
+
from .transitions import LifecycleTransition
|
|
10
|
+
from .transitions import LifecycleTransitions
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
LifecycleT = ta.TypeVar('LifecycleT', bound='Lifecycle')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LifecycleListener(ta.Generic[LifecycleT]):
|
|
17
|
+
|
|
18
|
+
def on_starting(self, obj: LifecycleT) -> None:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def on_started(self, obj: LifecycleT) -> None:
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
def on_stopping(self, obj: LifecycleT) -> None:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
def on_stopped(self, obj: LifecycleT) -> None:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LifecycleController(Lifecycle, ta.Generic[LifecycleT]):
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
lifecycle: LifecycleT,
|
|
36
|
+
*,
|
|
37
|
+
lock: lang.DefaultLockable = None,
|
|
38
|
+
) -> None:
|
|
39
|
+
super().__init__()
|
|
40
|
+
|
|
41
|
+
self._lifecycle: LifecycleT = check.isinstance(lifecycle, Lifecycle) # type: ignore
|
|
42
|
+
self._lock = lang.default_lock(lock, False)
|
|
43
|
+
|
|
44
|
+
self._state = LifecycleStates.NEW
|
|
45
|
+
self._listeners: list[LifecycleListener[LifecycleT]] = []
|
|
46
|
+
|
|
47
|
+
defs.repr('lifecycle', 'state')
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def lifecycle(self) -> LifecycleT:
|
|
51
|
+
return self._lifecycle
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def state(self) -> LifecycleState:
|
|
55
|
+
return self._state
|
|
56
|
+
|
|
57
|
+
def add_listener(self, listener: LifecycleListener[LifecycleT]) -> 'LifecycleController':
|
|
58
|
+
self._listeners.append(check.isinstance(listener, LifecycleListener)) # type: ignore
|
|
59
|
+
return self
|
|
60
|
+
|
|
61
|
+
def _advance(
|
|
62
|
+
self,
|
|
63
|
+
transition: LifecycleTransition,
|
|
64
|
+
lifecycle_fn: ta.Callable[[], None],
|
|
65
|
+
pre_listener_fn: ta.Callable[[LifecycleListener[LifecycleT]], ta.Callable[[LifecycleT], None]] | None = None, # noqa
|
|
66
|
+
post_listener_fn: ta.Callable[[LifecycleListener[LifecycleT]], ta.Callable[[LifecycleT], None]] | None = None, # noqa
|
|
67
|
+
) -> None:
|
|
68
|
+
with self._lock():
|
|
69
|
+
if pre_listener_fn is not None:
|
|
70
|
+
for listener in self._listeners:
|
|
71
|
+
pre_listener_fn(listener)(self._lifecycle)
|
|
72
|
+
check.state(self._state in transition.old)
|
|
73
|
+
self._state = transition.new_intermediate
|
|
74
|
+
try:
|
|
75
|
+
lifecycle_fn()
|
|
76
|
+
except Exception:
|
|
77
|
+
self._state = transition.new_failed
|
|
78
|
+
raise
|
|
79
|
+
self._state = transition.new_succeeded
|
|
80
|
+
if post_listener_fn is not None:
|
|
81
|
+
for listener in self._listeners:
|
|
82
|
+
post_listener_fn(listener)(self._lifecycle)
|
|
83
|
+
|
|
84
|
+
##
|
|
85
|
+
|
|
86
|
+
@ta.override
|
|
87
|
+
def lifecycle_construct(self) -> None:
|
|
88
|
+
self._advance(
|
|
89
|
+
LifecycleTransitions.CONSTRUCT,
|
|
90
|
+
self._lifecycle.lifecycle_construct,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
@ta.override
|
|
94
|
+
def lifecycle_start(self) -> None:
|
|
95
|
+
self._advance(
|
|
96
|
+
LifecycleTransitions.START,
|
|
97
|
+
self._lifecycle.lifecycle_start,
|
|
98
|
+
lambda l: l.on_starting,
|
|
99
|
+
lambda l: l.on_started,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@ta.override
|
|
103
|
+
def lifecycle_stop(self) -> None:
|
|
104
|
+
self._advance(
|
|
105
|
+
LifecycleTransitions.STOP,
|
|
106
|
+
self._lifecycle.lifecycle_stop,
|
|
107
|
+
lambda l: l.on_stopping,
|
|
108
|
+
lambda l: l.on_stopped,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
@ta.override
|
|
112
|
+
def lifecycle_destroy(self) -> None:
|
|
113
|
+
self._advance(
|
|
114
|
+
LifecycleTransitions.DESTROY,
|
|
115
|
+
self._lifecycle.lifecycle_destroy,
|
|
116
|
+
)
|