omlish 0.0.0.dev4__py3-none-any.whl → 0.0.0.dev6__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 +1 -1
- omlish/__init__.py +1 -1
- omlish/asyncs/__init__.py +10 -4
- omlish/asyncs/anyio.py +142 -12
- omlish/asyncs/asyncio.py +23 -0
- omlish/asyncs/asyncs.py +9 -6
- omlish/asyncs/bridge.py +316 -0
- omlish/asyncs/flavors.py +27 -1
- omlish/asyncs/trio_asyncio.py +28 -18
- omlish/c3.py +1 -1
- omlish/cached.py +1 -2
- omlish/collections/__init__.py +5 -1
- omlish/collections/cache/impl.py +1 -1
- omlish/collections/identity.py +7 -0
- omlish/collections/indexed.py +1 -1
- omlish/collections/utils.py +38 -6
- omlish/configs/__init__.py +5 -0
- omlish/configs/classes.py +53 -0
- omlish/configs/strings.py +94 -0
- omlish/dataclasses/__init__.py +9 -0
- omlish/dataclasses/impl/api.py +1 -1
- omlish/dataclasses/impl/as_.py +1 -1
- omlish/dataclasses/impl/copy.py +30 -0
- omlish/dataclasses/impl/exceptions.py +6 -0
- omlish/dataclasses/impl/fields.py +25 -25
- omlish/dataclasses/impl/init.py +5 -3
- omlish/dataclasses/impl/main.py +3 -0
- omlish/dataclasses/impl/metaclass.py +6 -1
- omlish/dataclasses/impl/order.py +1 -1
- omlish/dataclasses/impl/reflect.py +15 -2
- omlish/dataclasses/utils.py +44 -0
- omlish/defs.py +1 -1
- omlish/diag/__init__.py +4 -0
- omlish/diag/procfs.py +31 -3
- omlish/diag/procstats.py +32 -0
- omlish/{testing → diag}/pydevd.py +35 -0
- omlish/diag/replserver/console.py +3 -3
- omlish/diag/replserver/server.py +6 -5
- omlish/diag/threads.py +86 -0
- omlish/dispatch/_dispatch2.py +65 -0
- omlish/dispatch/_dispatch3.py +104 -0
- omlish/docker.py +20 -1
- omlish/fnpairs.py +37 -18
- omlish/graphs/dags.py +113 -0
- omlish/graphs/domination.py +268 -0
- omlish/graphs/trees.py +2 -2
- omlish/http/__init__.py +25 -0
- omlish/http/asgi.py +132 -0
- omlish/http/collections.py +15 -0
- omlish/http/consts.py +47 -5
- omlish/http/cookies.py +194 -0
- omlish/http/dates.py +70 -0
- omlish/http/encodings.py +6 -0
- omlish/http/json.py +273 -0
- omlish/http/sessions.py +204 -0
- omlish/inject/__init__.py +51 -17
- omlish/inject/binder.py +185 -5
- omlish/inject/bindings.py +3 -36
- omlish/inject/eagers.py +2 -8
- omlish/inject/elements.py +30 -9
- omlish/inject/exceptions.py +3 -3
- omlish/inject/impl/elements.py +65 -31
- omlish/inject/impl/injector.py +20 -2
- omlish/inject/impl/inspect.py +33 -5
- omlish/inject/impl/multis.py +74 -0
- omlish/inject/impl/origins.py +75 -0
- omlish/inject/impl/{private.py → privates.py} +2 -2
- omlish/inject/impl/providers.py +19 -39
- omlish/inject/{proxy.py → impl/proxy.py} +2 -2
- omlish/inject/impl/scopes.py +7 -2
- omlish/inject/injector.py +9 -4
- omlish/inject/inspect.py +18 -0
- omlish/inject/keys.py +11 -23
- omlish/inject/listeners.py +26 -0
- omlish/inject/managed.py +76 -10
- omlish/inject/multis.py +120 -0
- omlish/inject/origins.py +27 -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 +20 -9
- omlish/inject/types.py +2 -8
- omlish/iterators.py +13 -0
- omlish/lang/__init__.py +12 -2
- omlish/lang/cached.py +2 -2
- omlish/lang/classes/restrict.py +3 -2
- omlish/lang/classes/simple.py +18 -8
- omlish/lang/classes/virtual.py +2 -2
- omlish/lang/contextmanagers.py +75 -2
- omlish/lang/datetimes.py +6 -5
- omlish/lang/descriptors.py +131 -0
- omlish/lang/functions.py +18 -28
- omlish/lang/imports.py +11 -2
- omlish/lang/iterables.py +20 -1
- omlish/lang/typing.py +6 -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/logs/formatters.py +1 -1
- omlish/logs/utils.py +1 -1
- omlish/marshal/__init__.py +4 -0
- omlish/marshal/datetimes.py +1 -1
- omlish/marshal/naming.py +4 -0
- omlish/marshal/objects.py +1 -0
- omlish/marshal/polymorphism.py +4 -4
- omlish/reflect.py +139 -18
- omlish/secrets/__init__.py +7 -0
- omlish/secrets/marshal.py +41 -0
- omlish/secrets/passwords.py +120 -0
- omlish/secrets/secrets.py +47 -0
- omlish/serde/__init__.py +0 -0
- omlish/serde/dotenv.py +574 -0
- omlish/{json.py → serde/json.py} +4 -2
- omlish/serde/props.py +604 -0
- omlish/serde/yaml.py +223 -0
- omlish/sql/dbs.py +1 -1
- omlish/sql/duckdb.py +136 -0
- omlish/sql/sqlean.py +17 -0
- omlish/sync.py +70 -0
- omlish/term.py +7 -2
- omlish/testing/pytest/__init__.py +8 -2
- omlish/testing/pytest/helpers.py +0 -24
- omlish/testing/pytest/inject/harness.py +4 -4
- omlish/testing/pytest/marks.py +45 -0
- omlish/testing/pytest/plugins/__init__.py +3 -0
- omlish/testing/pytest/plugins/asyncs.py +136 -0
- omlish/testing/pytest/plugins/managermarks.py +60 -0
- omlish/testing/pytest/plugins/pydevd.py +1 -1
- omlish/testing/testing.py +10 -0
- omlish/text/delimit.py +4 -0
- omlish/text/glyphsplit.py +92 -0
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/METADATA +1 -1
- omlish-0.0.0.dev6.dist-info/RECORD +240 -0
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/WHEEL +1 -1
- omlish/configs/props.py +0 -64
- omlish-0.0.0.dev4.dist-info/RECORD +0 -195
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/top_level.txt +0 -0
omlish/lang/classes/restrict.py
CHANGED
|
@@ -78,7 +78,8 @@ class PackageSealed:
|
|
|
78
78
|
for base in cls.__bases__:
|
|
79
79
|
if base is not Abstract:
|
|
80
80
|
if PackageSealed in base.__bases__:
|
|
81
|
-
|
|
81
|
+
pfx = base.__module__.split('.')[:-1]
|
|
82
|
+
if cls.__module__.split('.')[:len(pfx)] != pfx:
|
|
82
83
|
raise SealedError(base)
|
|
83
84
|
super().__init_subclass__(**kwargs)
|
|
84
85
|
|
|
@@ -89,7 +90,7 @@ class PackageSealed:
|
|
|
89
90
|
class NotInstantiable(Abstract):
|
|
90
91
|
__slots__ = ()
|
|
91
92
|
|
|
92
|
-
def __new__(cls, *args, **kwargs):
|
|
93
|
+
def __new__(cls, *args, **kwargs): # noqa
|
|
93
94
|
raise TypeError
|
|
94
95
|
|
|
95
96
|
|
omlish/lang/classes/simple.py
CHANGED
|
@@ -14,13 +14,25 @@ V = ta.TypeVar('V')
|
|
|
14
14
|
##
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
class
|
|
17
|
+
class _NamespaceMeta(abc.ABCMeta):
|
|
18
|
+
def __new__(mcls, name, bases, namespace):
|
|
19
|
+
if bases:
|
|
20
|
+
for nc in (NotInstantiable,):
|
|
21
|
+
if nc not in bases:
|
|
22
|
+
bases += (nc,)
|
|
23
|
+
return super().__new__(mcls, name, bases, namespace)
|
|
18
24
|
|
|
19
|
-
def
|
|
20
|
-
|
|
25
|
+
def __iter__(cls) -> ta.Iterator[tuple[str, ta.Any]]:
|
|
26
|
+
for a in dir(cls):
|
|
27
|
+
if not a.startswith('_'):
|
|
28
|
+
yield (a, getattr(cls, a))
|
|
21
29
|
|
|
30
|
+
def __getitem__(cls, n: str) -> ta.Any:
|
|
31
|
+
return getattr(cls, n)
|
|
22
32
|
|
|
23
|
-
|
|
33
|
+
|
|
34
|
+
class Namespace(metaclass=_NamespaceMeta):
|
|
35
|
+
pass
|
|
24
36
|
|
|
25
37
|
|
|
26
38
|
##
|
|
@@ -54,8 +66,6 @@ class _MarkerMeta(abc.ABCMeta):
|
|
|
54
66
|
class Marker(NotInstantiable, metaclass=_MarkerMeta):
|
|
55
67
|
"""A marker."""
|
|
56
68
|
|
|
57
|
-
__slots__ = ()
|
|
58
|
-
|
|
59
69
|
|
|
60
70
|
##
|
|
61
71
|
|
|
@@ -77,7 +87,7 @@ _SINGLETON_LOCK = threading.RLock()
|
|
|
77
87
|
|
|
78
88
|
|
|
79
89
|
def _set_singleton_instance(inst):
|
|
80
|
-
cls =
|
|
90
|
+
cls = inst.__class__
|
|
81
91
|
if _SINGLETON_INSTANCE_ATTR in cls.__dict__:
|
|
82
92
|
raise TypeError(cls)
|
|
83
93
|
|
|
@@ -86,7 +96,7 @@ def _set_singleton_instance(inst):
|
|
|
86
96
|
|
|
87
97
|
@functools.wraps(old_init)
|
|
88
98
|
def new_init(self):
|
|
89
|
-
if
|
|
99
|
+
if self.__class__ is not cls:
|
|
90
100
|
old_init(self) # noqa
|
|
91
101
|
|
|
92
102
|
setattr(cls, '__init__', new_init)
|
omlish/lang/classes/virtual.py
CHANGED
|
@@ -3,8 +3,8 @@ import types
|
|
|
3
3
|
import typing as ta
|
|
4
4
|
|
|
5
5
|
from .abstract import make_abstract
|
|
6
|
-
from .restrict import NotInstantiable
|
|
7
6
|
from .restrict import Final
|
|
7
|
+
from .restrict import NotInstantiable
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
T = ta.TypeVar('T')
|
|
@@ -122,7 +122,7 @@ class Callable(NotInstantiable, Final, ta.Generic[T]):
|
|
|
122
122
|
|
|
123
123
|
@classmethod
|
|
124
124
|
def __subclasscheck__(cls, subclass: type) -> bool:
|
|
125
|
-
if not hasattr(subclass, '__call__'):
|
|
125
|
+
if not hasattr(subclass, '__call__'): # noqa
|
|
126
126
|
return False
|
|
127
127
|
call = subclass.__call__
|
|
128
128
|
if isinstance(call, types.MethodWrapperType) and call.__self__ is subclass:
|
omlish/lang/contextmanagers.py
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- AsyncExitStacked
|
|
4
|
+
"""
|
|
5
|
+
import abc
|
|
1
6
|
import contextlib
|
|
2
7
|
import contextvars
|
|
3
8
|
import functools
|
|
@@ -14,7 +19,7 @@ T = ta.TypeVar('T')
|
|
|
14
19
|
|
|
15
20
|
class ContextManaged:
|
|
16
21
|
|
|
17
|
-
def __enter__(self
|
|
22
|
+
def __enter__(self) -> ta.Self:
|
|
18
23
|
return self
|
|
19
24
|
|
|
20
25
|
def __exit__(
|
|
@@ -50,6 +55,69 @@ NOP_CONTEXT_MANAGER = NopContextManager()
|
|
|
50
55
|
##
|
|
51
56
|
|
|
52
57
|
|
|
58
|
+
class ContextManager(abc.ABC, ta.Generic[T]):
|
|
59
|
+
|
|
60
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
|
61
|
+
super().__init_subclass__(**kwargs)
|
|
62
|
+
|
|
63
|
+
if not hasattr(cls.__contextmanager__, '_is_contextmanager'):
|
|
64
|
+
cls.__contextmanager__ = contextlib.contextmanager(cls.__contextmanager__) # type: ignore # noqa
|
|
65
|
+
cls.__contextmanager__._is_contextmanager = True # type: ignore # noqa
|
|
66
|
+
|
|
67
|
+
@abc.abstractmethod
|
|
68
|
+
def __contextmanager__(self) -> ta.Iterable[T]:
|
|
69
|
+
raise NotImplementedError
|
|
70
|
+
|
|
71
|
+
__contextmanager__._is_contextmanager = True # type: ignore # noqa
|
|
72
|
+
|
|
73
|
+
_contextmanager: ta.Any
|
|
74
|
+
|
|
75
|
+
def __enter__(self) -> T:
|
|
76
|
+
self._contextmanager = self.__contextmanager__()
|
|
77
|
+
return self._contextmanager.__enter__() # type: ignore
|
|
78
|
+
|
|
79
|
+
def __exit__(
|
|
80
|
+
self,
|
|
81
|
+
exc_type: type[BaseException] | None,
|
|
82
|
+
exc_val: BaseException | None,
|
|
83
|
+
exc_tb: types.TracebackType | None,
|
|
84
|
+
) -> bool | None:
|
|
85
|
+
return self._contextmanager.__exit__(exc_type, exc_val, exc_tb)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class AsyncContextManager(abc.ABC, ta.Generic[T]):
|
|
89
|
+
|
|
90
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
|
91
|
+
super().__init_subclass__(**kwargs)
|
|
92
|
+
|
|
93
|
+
if not hasattr(cls.__asynccontextmanager__, '_is_asynccontextmanager'):
|
|
94
|
+
cls.__asynccontextmanager__ = contextlib.asynccontextmanager(cls.__asynccontextmanager__) # type: ignore # noqa
|
|
95
|
+
cls.__asynccontextmanager__._is_asynccontextmanager = True # type: ignore # noqa
|
|
96
|
+
|
|
97
|
+
@abc.abstractmethod
|
|
98
|
+
def __asynccontextmanager__(self) -> ta.AsyncIterator[T]:
|
|
99
|
+
raise NotImplementedError
|
|
100
|
+
|
|
101
|
+
__asynccontextmanager__._is_asynccontextmanager = True # type: ignore # noqa
|
|
102
|
+
|
|
103
|
+
_asynccontextmanager: ta.Any
|
|
104
|
+
|
|
105
|
+
async def __aenter__(self) -> T:
|
|
106
|
+
self._asynccontextmanager = self.__asynccontextmanager__()
|
|
107
|
+
return await self._asynccontextmanager.__aenter__() # type: ignore
|
|
108
|
+
|
|
109
|
+
async def __aexit__(
|
|
110
|
+
self,
|
|
111
|
+
exc_type: type[BaseException] | None,
|
|
112
|
+
exc_val: BaseException | None,
|
|
113
|
+
exc_tb: types.TracebackType | None,
|
|
114
|
+
) -> None:
|
|
115
|
+
return await self._asynccontextmanager.__aexit__(exc_type, exc_val, exc_tb)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
##
|
|
119
|
+
|
|
120
|
+
|
|
53
121
|
@contextlib.contextmanager
|
|
54
122
|
def defer(fn: ta.Callable) -> ta.Generator[ta.Callable, None, None]:
|
|
55
123
|
try:
|
|
@@ -134,7 +202,7 @@ class ExitStacked:
|
|
|
134
202
|
def _enter_context(self, context_manager: ta.ContextManager[T]) -> T:
|
|
135
203
|
return self._exit_stack.enter_context(ta.cast(ta.ContextManager, context_manager))
|
|
136
204
|
|
|
137
|
-
def __enter__(self
|
|
205
|
+
def __enter__(self) -> ta.Self:
|
|
138
206
|
try:
|
|
139
207
|
superfn = super().__enter__ # type: ignore
|
|
140
208
|
except AttributeError:
|
|
@@ -242,14 +310,19 @@ DefaultLockable = bool | Lockable | ta.ContextManager | None
|
|
|
242
310
|
def default_lock(value: DefaultLockable, default: DefaultLockable) -> Lockable:
|
|
243
311
|
if value is None:
|
|
244
312
|
value = default
|
|
313
|
+
|
|
245
314
|
if value is True:
|
|
246
315
|
lock = threading.RLock()
|
|
247
316
|
return lambda: lock
|
|
317
|
+
|
|
248
318
|
elif value is False or value is None:
|
|
249
319
|
return NOP_CONTEXT_MANAGER
|
|
320
|
+
|
|
250
321
|
elif callable(value):
|
|
251
322
|
return value
|
|
323
|
+
|
|
252
324
|
elif isinstance(value, ta.ContextManager):
|
|
253
325
|
return lambda: value
|
|
326
|
+
|
|
254
327
|
else:
|
|
255
328
|
raise TypeError(value)
|
omlish/lang/datetimes.py
CHANGED
|
@@ -18,17 +18,18 @@ def months_ago(date: datetime.date, num: int) -> datetime.date:
|
|
|
18
18
|
return datetime.date(ago_year, ago_month, 1)
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def parse_date(s: str) -> datetime.date:
|
|
21
|
+
def parse_date(s: str, tz: datetime.timezone | None = None) -> datetime.date:
|
|
22
|
+
|
|
22
23
|
if s.lower() in ['today', 'now']:
|
|
23
|
-
return datetime.
|
|
24
|
+
return datetime.datetime.now(tz=tz)
|
|
24
25
|
elif s.lower() == 'yesterday':
|
|
25
|
-
return datetime.
|
|
26
|
+
return datetime.datetime.now(tz=tz) - datetime.timedelta(days=1)
|
|
26
27
|
elif s.lower().endswith(' days ago'):
|
|
27
28
|
num = int(s.split(' ', 1)[0])
|
|
28
|
-
return datetime.
|
|
29
|
+
return datetime.datetime.now(tz=tz) - datetime.timedelta(days=num)
|
|
29
30
|
elif s.lower().endswith(' months ago'):
|
|
30
31
|
months = int(s.split(' ', 1)[0])
|
|
31
|
-
return months_ago(datetime.
|
|
32
|
+
return months_ago(datetime.datetime.now(tz=tz), months)
|
|
32
33
|
else:
|
|
33
34
|
return datetime.date(*map(int, s.split('-', 3)))
|
|
34
35
|
|
omlish/lang/descriptors.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
import operator
|
|
3
|
+
import types
|
|
3
4
|
import typing as ta
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
T = ta.TypeVar('T')
|
|
8
|
+
P = ta.ParamSpec('P')
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
##
|
|
@@ -31,6 +33,19 @@ def is_method_descriptor(obj: ta.Any) -> bool:
|
|
|
31
33
|
return isinstance(obj, (*BUILTIN_METHOD_DESCRIPTORS, _MethodDescriptor))
|
|
32
34
|
|
|
33
35
|
|
|
36
|
+
def _has_method_descriptor(obj: ta.Any) -> bool:
|
|
37
|
+
while True:
|
|
38
|
+
if is_method_descriptor(obj):
|
|
39
|
+
return True
|
|
40
|
+
elif isinstance(obj, functools.partial):
|
|
41
|
+
obj = obj.func
|
|
42
|
+
else:
|
|
43
|
+
try:
|
|
44
|
+
obj = getattr(obj, '__wrapped__')
|
|
45
|
+
except AttributeError:
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
34
49
|
def unwrap_method_descriptors(fn: ta.Callable) -> ta.Callable:
|
|
35
50
|
while is_method_descriptor(fn):
|
|
36
51
|
fn = fn.__func__ # type: ignore # noqa
|
|
@@ -40,6 +55,122 @@ def unwrap_method_descriptors(fn: ta.Callable) -> ta.Callable:
|
|
|
40
55
|
##
|
|
41
56
|
|
|
42
57
|
|
|
58
|
+
def unwrap_func(fn: ta.Callable) -> ta.Callable:
|
|
59
|
+
fn, _ = unwrap_func_with_partials(fn)
|
|
60
|
+
return fn
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def unwrap_func_with_partials(fn: ta.Callable) -> tuple[ta.Callable, list[functools.partial]]:
|
|
64
|
+
ps = []
|
|
65
|
+
while True:
|
|
66
|
+
if is_method_descriptor(fn) or isinstance(fn, types.MethodType):
|
|
67
|
+
fn = fn.__func__ # type: ignore
|
|
68
|
+
elif hasattr(fn, '__wrapped__'):
|
|
69
|
+
nxt = fn.__wrapped__
|
|
70
|
+
if not callable(nxt):
|
|
71
|
+
raise TypeError(nxt)
|
|
72
|
+
if nxt is fn:
|
|
73
|
+
raise TypeError(fn)
|
|
74
|
+
fn = nxt
|
|
75
|
+
# NOTE: __wrapped__ takes precedence - a partial might point to a bound Method when the important information is
|
|
76
|
+
# still the unbound func. see _decorator_descriptor for an example of this.
|
|
77
|
+
elif isinstance(fn, functools.partial):
|
|
78
|
+
ps.append(fn)
|
|
79
|
+
fn = fn.func
|
|
80
|
+
else:
|
|
81
|
+
break
|
|
82
|
+
return fn, ps
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
##
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
WRAPPER_UPDATES_EXCEPT_DICT = tuple(a for a in functools.WRAPPER_UPDATES if a != '__dict__')
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def update_wrapper_except_dict(
|
|
92
|
+
wrapper,
|
|
93
|
+
wrapped,
|
|
94
|
+
assigned=functools.WRAPPER_ASSIGNMENTS,
|
|
95
|
+
updated=WRAPPER_UPDATES_EXCEPT_DICT,
|
|
96
|
+
):
|
|
97
|
+
return functools.update_wrapper(
|
|
98
|
+
wrapper,
|
|
99
|
+
wrapped,
|
|
100
|
+
assigned=assigned,
|
|
101
|
+
updated=updated,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
##
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
_DECORATOR_HANDLES_UNBOUND_METHODS = True
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class _decorator_descriptor: # noqa
|
|
112
|
+
if not _DECORATOR_HANDLES_UNBOUND_METHODS:
|
|
113
|
+
def __init__(self, wrapper, fn):
|
|
114
|
+
self._wrapper, self._fn = wrapper, fn
|
|
115
|
+
update_wrapper_except_dict(self, fn)
|
|
116
|
+
|
|
117
|
+
def __get__(self, instance, owner):
|
|
118
|
+
return functools.update_wrapper(functools.partial(self._wrapper, fn := self._fn.__get__(instance, owner)), fn) # noqa
|
|
119
|
+
|
|
120
|
+
else:
|
|
121
|
+
def __init__(self, wrapper, fn):
|
|
122
|
+
self._wrapper, self._fn = wrapper, fn
|
|
123
|
+
self._md = _has_method_descriptor(fn)
|
|
124
|
+
update_wrapper_except_dict(self, fn)
|
|
125
|
+
|
|
126
|
+
def __get__(self, instance, owner):
|
|
127
|
+
fn = self._fn.__get__(instance, owner)
|
|
128
|
+
if self._md or instance is not None:
|
|
129
|
+
@functools.wraps(fn)
|
|
130
|
+
def inner(*args, **kwargs):
|
|
131
|
+
return self._wrapper(fn, *args, **kwargs)
|
|
132
|
+
return inner
|
|
133
|
+
else:
|
|
134
|
+
@functools.wraps(fn)
|
|
135
|
+
def outer(this, *args, **kwargs):
|
|
136
|
+
@functools.wraps(self._fn)
|
|
137
|
+
def inner(*args2, **kwargs2):
|
|
138
|
+
return fn(this, *args2, **kwargs2)
|
|
139
|
+
return self._wrapper(inner, *args, **kwargs)
|
|
140
|
+
return outer
|
|
141
|
+
|
|
142
|
+
def __repr__(self):
|
|
143
|
+
return f'{self.__class__.__name__}<{self._wrapper}, {self._fn}>'
|
|
144
|
+
|
|
145
|
+
def __call__(self, *args, **kwargs):
|
|
146
|
+
return self._wrapper(self._fn, *args, **kwargs)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class _decorator: # noqa
|
|
150
|
+
def __init__(self, wrapper):
|
|
151
|
+
self._wrapper = wrapper
|
|
152
|
+
update_wrapper_except_dict(self, wrapper)
|
|
153
|
+
|
|
154
|
+
def __repr__(self):
|
|
155
|
+
return f'{self.__class__.__name__}<{self._wrapper}>'
|
|
156
|
+
|
|
157
|
+
def __call__(self, fn):
|
|
158
|
+
return _decorator_descriptor(self._wrapper, fn) # noqa
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# FIXME:
|
|
162
|
+
# def decorator(
|
|
163
|
+
# wrapper: ta.Callable[ta.Concatenate[ta.Any, P], T], # FIXME: https://youtrack.jetbrains.com/issue/PY-72164 # noqa
|
|
164
|
+
# ) -> ta.Callable[[ta.Callable[P, T]], ta.Callable[P, T]]:
|
|
165
|
+
# return _decorator(wrapper)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
decorator = _decorator
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
##
|
|
172
|
+
|
|
173
|
+
|
|
43
174
|
class AccessForbiddenError(Exception):
|
|
44
175
|
|
|
45
176
|
def __init__(self, name: str | None = None, *args: ta.Any, **kwargs: ta.Any) -> None:
|
omlish/lang/functions.py
CHANGED
|
@@ -3,8 +3,6 @@ import functools
|
|
|
3
3
|
import time
|
|
4
4
|
import typing as ta
|
|
5
5
|
|
|
6
|
-
from .descriptors import is_method_descriptor
|
|
7
|
-
|
|
8
6
|
|
|
9
7
|
T = ta.TypeVar('T')
|
|
10
8
|
P = ta.ParamSpec('P')
|
|
@@ -41,32 +39,6 @@ def recurse(fn: ta.Callable[..., T], *args, **kwargs) -> T:
|
|
|
41
39
|
##
|
|
42
40
|
|
|
43
41
|
|
|
44
|
-
def unwrap_func(fn: ta.Callable) -> ta.Callable:
|
|
45
|
-
fn, _ = unwrap_func_with_partials(fn)
|
|
46
|
-
return fn
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def unwrap_func_with_partials(fn: ta.Callable) -> tuple[ta.Callable, list[functools.partial]]:
|
|
50
|
-
ps = []
|
|
51
|
-
while True:
|
|
52
|
-
if is_method_descriptor(fn):
|
|
53
|
-
fn = fn.__func__ # type: ignore
|
|
54
|
-
elif isinstance(fn, functools.partial):
|
|
55
|
-
ps.append(fn)
|
|
56
|
-
fn = fn.func
|
|
57
|
-
else:
|
|
58
|
-
nxt = getattr(fn, '__wrapped__', None)
|
|
59
|
-
if not callable(nxt):
|
|
60
|
-
break
|
|
61
|
-
if nxt is fn:
|
|
62
|
-
raise TypeError(fn)
|
|
63
|
-
fn = nxt
|
|
64
|
-
return fn, ps
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
##
|
|
68
|
-
|
|
69
|
-
|
|
70
42
|
def raise_(o: BaseException) -> ta.NoReturn:
|
|
71
43
|
raise o
|
|
72
44
|
|
|
@@ -128,6 +100,14 @@ def is_not_none(o: ta.Any) -> bool:
|
|
|
128
100
|
return o is not None
|
|
129
101
|
|
|
130
102
|
|
|
103
|
+
def isinstance_of(class_or_tuple: ta.Any) -> ta.Callable[[ta.Any], bool]:
|
|
104
|
+
return lambda o: isinstance(o, class_or_tuple)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def issubclass_of(class_or_tuple: ta.Any) -> ta.Callable[[ta.Any], bool]:
|
|
108
|
+
return lambda o: issubclass(o, class_or_tuple)
|
|
109
|
+
|
|
110
|
+
|
|
131
111
|
class VoidError(Exception):
|
|
132
112
|
pass
|
|
133
113
|
|
|
@@ -148,6 +128,16 @@ def void(*args: ta.Any, **kwargs: ta.Any) -> ta.NoReturn:
|
|
|
148
128
|
##
|
|
149
129
|
|
|
150
130
|
|
|
131
|
+
def as_async(fn: ta.Callable[P, T]) -> ta.Callable[P, ta.Awaitable[T]]:
|
|
132
|
+
@functools.wraps(fn)
|
|
133
|
+
async def inner(*args, **kwargs):
|
|
134
|
+
return fn(*args, **kwargs)
|
|
135
|
+
return inner
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
##
|
|
139
|
+
|
|
140
|
+
|
|
151
141
|
_MISSING = object()
|
|
152
142
|
|
|
153
143
|
|
omlish/lang/imports.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
import functools
|
|
3
|
-
import importlib.
|
|
3
|
+
import importlib.util
|
|
4
4
|
import sys
|
|
5
5
|
import types
|
|
6
6
|
import typing as ta
|
|
@@ -11,6 +11,13 @@ from .cached import cached_function
|
|
|
11
11
|
##
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
def can_import(name: str, package: str | None = None) -> bool:
|
|
15
|
+
return importlib.util.find_spec(name, package) is not None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
|
|
20
|
+
|
|
14
21
|
def lazy_import(name: str, package: str | None = None) -> ta.Callable[[], ta.Any]:
|
|
15
22
|
return cached_function(functools.partial(importlib.import_module, name, package=package))
|
|
16
23
|
|
|
@@ -66,6 +73,8 @@ def yield_importable(
|
|
|
66
73
|
filter: ta.Callable[[str], bool] | None = None, # noqa
|
|
67
74
|
include_special: bool = False,
|
|
68
75
|
) -> ta.Iterator[str]:
|
|
76
|
+
from importlib import resources
|
|
77
|
+
|
|
69
78
|
def rec(cur):
|
|
70
79
|
if cur.split('.')[-1] == '__pycache__':
|
|
71
80
|
return
|
|
@@ -83,7 +92,7 @@ def yield_importable(
|
|
|
83
92
|
if getattr(module, '__file__', None) is None:
|
|
84
93
|
return
|
|
85
94
|
|
|
86
|
-
for file in
|
|
95
|
+
for file in resources.files(cur).iterdir():
|
|
87
96
|
if file.is_file() and file.name.endswith('.py'):
|
|
88
97
|
if not (include_special or file.name not in SPECIAL_IMPORTABLE):
|
|
89
98
|
continue
|
omlish/lang/iterables.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import dataclasses as dc
|
|
1
2
|
import itertools
|
|
2
3
|
import typing as ta
|
|
3
4
|
|
|
@@ -34,7 +35,13 @@ def peek(vs: ta.Iterable[T]) -> tuple[T, ta.Iterator[T]]:
|
|
|
34
35
|
return v, itertools.chain(iter((v,)), it)
|
|
35
36
|
|
|
36
37
|
|
|
37
|
-
Rangeable: ta.TypeAlias =
|
|
38
|
+
Rangeable: ta.TypeAlias = ta.Union[ # noqa
|
|
39
|
+
int,
|
|
40
|
+
tuple[int],
|
|
41
|
+
tuple[int, int],
|
|
42
|
+
tuple[int, int, int],
|
|
43
|
+
ta.Iterable[int],
|
|
44
|
+
]
|
|
38
45
|
|
|
39
46
|
|
|
40
47
|
def asrange(i: Rangeable) -> ta.Iterable[int]:
|
|
@@ -52,3 +59,15 @@ def prodrange(*dims: Rangeable) -> ta.Iterable[ta.Sequence[int]]:
|
|
|
52
59
|
if not dims:
|
|
53
60
|
return []
|
|
54
61
|
return itertools.product(*map(asrange, dims))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dc.dataclass(frozen=True)
|
|
65
|
+
class itergen(ta.Generic[T]): # noqa
|
|
66
|
+
fn: ta.Callable[[], ta.Iterable[T]]
|
|
67
|
+
|
|
68
|
+
def __iter__(self):
|
|
69
|
+
return iter(self.fn())
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def renumerate(it: ta.Iterable[T]) -> ta.Iterable[tuple[T, int]]:
|
|
73
|
+
return ((e, i) for i, e in enumerate(it))
|
omlish/lang/typing.py
CHANGED
|
@@ -3,6 +3,7 @@ TODO:
|
|
|
3
3
|
- typed_lambda is really kinda just 'annotate'
|
|
4
4
|
- TypedLambda / TypedPartial classes, picklable, reprs
|
|
5
5
|
- probably need to gen types per inst
|
|
6
|
+
- typed_factory
|
|
6
7
|
"""
|
|
7
8
|
import functools
|
|
8
9
|
import inspect
|
|
@@ -11,6 +12,11 @@ import typing as ta
|
|
|
11
12
|
|
|
12
13
|
Ty = ta.TypeVar('Ty', bound=type)
|
|
13
14
|
|
|
15
|
+
T = ta.TypeVar('T')
|
|
16
|
+
A0 = ta.TypeVar('A0')
|
|
17
|
+
A1 = ta.TypeVar('A1')
|
|
18
|
+
A2 = ta.TypeVar('A2')
|
|
19
|
+
|
|
14
20
|
|
|
15
21
|
BytesLike: ta.TypeAlias = bytes | bytearray
|
|
16
22
|
|
|
@@ -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)
|