omlish 0.0.0.dev419__py3-none-any.whl → 0.0.0.dev421__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.
- omlish/__about__.py +2 -2
- omlish/asyncs/bluelet/core.py +2 -2
- omlish/asyncs/bluelet/events.py +4 -3
- omlish/asyncs/bluelet/files.py +2 -2
- omlish/asyncs/bluelet/sockets.py +2 -2
- omlish/collections/frozen.py +1 -1
- omlish/collections/kv/base.py +1 -1
- omlish/collections/multimaps.py +1 -1
- omlish/collections/persistent/treapmap.py +2 -1
- omlish/concurrent/threadlets.py +2 -2
- omlish/configs/all.py +39 -32
- omlish/configs/formats.py +5 -4
- omlish/configs/processing/all.py +32 -26
- omlish/configs/processing/flattening.py +2 -1
- omlish/configs/processing/rewriting.py +2 -2
- omlish/configs/shadow.py +3 -2
- omlish/daemons/spawning.py +2 -2
- omlish/daemons/targets.py +1 -1
- omlish/daemons/waiting.py +2 -1
- omlish/dataclasses/impl/generation/base.py +3 -2
- omlish/dataclasses/impl/generation/compilation.py +2 -1
- omlish/dataclasses/impl/generation/ops.py +2 -2
- omlish/dataclasses/impl/generation/processor.py +1 -1
- omlish/dataclasses/metaclass/meta.py +1 -0
- omlish/dataclasses/tools/static.py +5 -1
- omlish/formats/dotenv.py +3 -1
- omlish/formats/json/backends/default.py +14 -7
- omlish/formats/logfmt.py +111 -0
- omlish/formats/yaml.py +1 -1
- omlish/funcs/builders.py +2 -1
- omlish/funcs/match.py +1 -1
- omlish/funcs/pairs.py +41 -23
- omlish/funcs/pipes.py +3 -1
- omlish/http/asgi.py +2 -1
- omlish/http/coro/client/io.py +3 -2
- omlish/http/coro/server/server.py +2 -2
- omlish/http/handlers.py +2 -1
- omlish/http/jwt.py +1 -1
- omlish/http/parsing.py +2 -2
- omlish/io/compress/base.py +3 -2
- omlish/io/coro/readers.py +4 -3
- omlish/io/fdio/handlers.py +3 -2
- omlish/io/fdio/pollers.py +3 -1
- omlish/lang/__init__.py +20 -8
- omlish/lang/attrs.py +3 -1
- omlish/lang/casing.py +3 -1
- omlish/lang/classes/abstract.py +35 -96
- omlish/lang/classes/virtual.py +2 -2
- omlish/lang/contextmanagers.py +6 -4
- omlish/lang/generators.py +2 -2
- omlish/lang/iterables.py +6 -6
- omlish/lang/objects.py +0 -58
- omlish/lang/outcomes.py +3 -1
- omlish/lang/typing.py +5 -24
- omlish/lite/abstract.py +119 -0
- omlish/lite/contextmanagers.py +4 -1
- omlish/lite/inject.py +9 -9
- omlish/lite/marshal.py +230 -114
- omlish/lite/maybes.py +3 -1
- omlish/lite/maysync.py +4 -2
- omlish/lite/objects.py +81 -0
- omlish/lite/reflect.py +0 -15
- omlish/lite/timeouts.py +3 -1
- omlish/lite/wrappers.py +23 -0
- omlish/logs/abc.py +21 -5
- omlish/logs/all.py +56 -38
- omlish/logs/configs.py +13 -10
- omlish/logs/levels.py +4 -0
- omlish/logs/protocol.py +77 -39
- omlish/marshal/__init__.py +1 -0
- omlish/os/atomics.py +3 -2
- omlish/os/deathpacts/base.py +4 -2
- omlish/os/forkhooks.py +2 -2
- omlish/os/pidfiles/pinning.py +2 -1
- omlish/reflect/types.py +4 -3
- omlish/secrets/crypto.py +1 -1
- omlish/sockets/bind.py +2 -1
- omlish/sockets/handlers.py +3 -2
- omlish/sockets/server/handlers.py +2 -1
- omlish/sockets/server/server.py +4 -3
- omlish/sql/api/base.py +2 -2
- omlish/subprocesses/asyncs.py +2 -1
- omlish/subprocesses/base.py +2 -2
- omlish/subprocesses/maysync.py +1 -2
- omlish/subprocesses/run.py +2 -1
- omlish/subprocesses/sync.py +2 -1
- omlish/term/coloring.py +3 -1
- omlish/testing/pytest/plugins/asyncs/backends/base.py +3 -1
- omlish/testing/unittest/loading.py +2 -2
- omlish/text/asdl.py +4 -3
- {omlish-0.0.0.dev419.dist-info → omlish-0.0.0.dev421.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev419.dist-info → omlish-0.0.0.dev421.dist-info}/RECORD +96 -91
- {omlish-0.0.0.dev419.dist-info → omlish-0.0.0.dev421.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev419.dist-info → omlish-0.0.0.dev421.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev419.dist-info → omlish-0.0.0.dev421.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev419.dist-info → omlish-0.0.0.dev421.dist-info}/top_level.txt +0 -0
omlish/lang/classes/abstract.py
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
import abc
|
2
2
|
import typing as ta
|
3
3
|
|
4
|
+
from ...lite.abstract import _ABSTRACT_METHODS_ATTR # noqa
|
5
|
+
from ...lite.abstract import _FORCE_ABSTRACT_ATTR # noqa
|
6
|
+
from ...lite.abstract import _IS_ABSTRACT_METHOD_ATTR # noqa
|
7
|
+
from ...lite.abstract import Abstract
|
8
|
+
from ...lite.abstract import is_abstract_method
|
9
|
+
|
4
10
|
|
5
11
|
T = ta.TypeVar('T')
|
6
12
|
|
@@ -8,82 +14,20 @@ T = ta.TypeVar('T')
|
|
8
14
|
##
|
9
15
|
|
10
16
|
|
11
|
-
_DISABLE_CHECKS = False
|
12
|
-
|
13
|
-
_ABSTRACT_METHODS_ATTR = '__abstractmethods__'
|
14
|
-
_IS_ABSTRACT_METHOD_ATTR = '__isabstractmethod__'
|
15
|
-
_FORCE_ABSTRACT_ATTR = '__forceabstract__'
|
16
|
-
|
17
|
-
_INTERNAL_ABSTRACT_ATTRS = frozenset([_FORCE_ABSTRACT_ATTR])
|
18
|
-
|
19
|
-
|
20
|
-
def make_abstract(obj: T) -> T:
|
21
|
-
if callable(obj):
|
22
|
-
return abc.abstractmethod(obj)
|
23
|
-
elif isinstance(obj, property):
|
24
|
-
return ta.cast(T, property(
|
25
|
-
abc.abstractmethod(obj.fget) if obj.fget is not None else None,
|
26
|
-
abc.abstractmethod(obj.fset) if obj.fset is not None else None,
|
27
|
-
abc.abstractmethod(obj.fdel) if obj.fdel is not None else None,
|
28
|
-
))
|
29
|
-
elif isinstance(obj, (classmethod, staticmethod)):
|
30
|
-
return ta.cast(T, type(obj)(abc.abstractmethod(obj.__func__)))
|
31
|
-
else:
|
32
|
-
return obj
|
33
|
-
|
34
|
-
|
35
|
-
class AbstractTypeError(TypeError):
|
36
|
-
pass
|
37
|
-
|
38
|
-
|
39
|
-
class Abstract(abc.ABC): # noqa
|
40
|
-
__slots__ = ()
|
41
|
-
|
42
|
-
def __forceabstract__(self):
|
43
|
-
raise TypeError
|
44
|
-
|
45
|
-
setattr(__forceabstract__, _IS_ABSTRACT_METHOD_ATTR, True)
|
46
|
-
|
47
|
-
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
48
|
-
if Abstract in cls.__bases__:
|
49
|
-
setattr(cls, _FORCE_ABSTRACT_ATTR, getattr(Abstract, _FORCE_ABSTRACT_ATTR))
|
50
|
-
else:
|
51
|
-
setattr(cls, _FORCE_ABSTRACT_ATTR, False)
|
52
|
-
|
53
|
-
super().__init_subclass__(**kwargs)
|
54
|
-
|
55
|
-
if (
|
56
|
-
not _DISABLE_CHECKS and
|
57
|
-
Abstract not in cls.__bases__ and
|
58
|
-
abc.ABC not in cls.__bases__
|
59
|
-
):
|
60
|
-
ams = {a for a, o in cls.__dict__.items() if is_abstract_method(o)}
|
61
|
-
seen = set(cls.__dict__)
|
62
|
-
for b in cls.__bases__:
|
63
|
-
ams.update(set(getattr(b, _ABSTRACT_METHODS_ATTR, [])) - seen)
|
64
|
-
seen.update(dir(b))
|
65
|
-
if ams:
|
66
|
-
raise AbstractTypeError(
|
67
|
-
f'Cannot subclass abstract class {cls.__name__} with abstract methods: '
|
68
|
-
f'{", ".join(map(str, sorted(ams)))}',
|
69
|
-
)
|
70
|
-
|
71
|
-
|
72
|
-
def is_abstract_method(obj: ta.Any) -> bool:
|
73
|
-
return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
|
74
|
-
|
75
|
-
|
76
17
|
def is_abstract_class(obj: ta.Any) -> bool:
|
77
18
|
if bool(getattr(obj, _ABSTRACT_METHODS_ATTR, [])):
|
78
19
|
return True
|
20
|
+
|
79
21
|
if isinstance(obj, type):
|
80
22
|
if Abstract in obj.__bases__:
|
81
23
|
return True
|
24
|
+
|
82
25
|
if (
|
83
26
|
Abstract in obj.__mro__
|
84
27
|
and getattr(obj.__dict__.get(_FORCE_ABSTRACT_ATTR, None), _IS_ABSTRACT_METHOD_ATTR, False)
|
85
28
|
):
|
86
29
|
return True
|
30
|
+
|
87
31
|
return False
|
88
32
|
|
89
33
|
|
@@ -91,40 +35,35 @@ def is_abstract(obj: ta.Any) -> bool:
|
|
91
35
|
return is_abstract_method(obj) or is_abstract_class(obj)
|
92
36
|
|
93
37
|
|
94
|
-
|
38
|
+
##
|
39
|
+
|
40
|
+
|
41
|
+
_INTERNAL_ABSTRACT_ATTRS = frozenset([_FORCE_ABSTRACT_ATTR])
|
42
|
+
|
43
|
+
|
44
|
+
def get_abstracts(cls: type, *, include_internal: bool = False) -> frozenset[str]:
|
95
45
|
ms = frozenset(getattr(cls, _ABSTRACT_METHODS_ATTR))
|
96
46
|
if not include_internal:
|
97
47
|
ms -= _INTERNAL_ABSTRACT_ATTRS
|
98
48
|
return ms
|
99
49
|
|
100
50
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
if
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
if name not in ams:
|
121
|
-
raise NameError(name)
|
122
|
-
if isinstance(impl, str):
|
123
|
-
impl = getattr(cls, impl)
|
124
|
-
setattr(cls, name, impl)
|
125
|
-
names.add(name)
|
126
|
-
|
127
|
-
setattr(cls, _ABSTRACT_METHODS_ATTR, ams - names)
|
128
|
-
return cls
|
129
|
-
|
130
|
-
return inner
|
51
|
+
##
|
52
|
+
|
53
|
+
|
54
|
+
def make_abstract(obj: T) -> T:
|
55
|
+
if callable(obj):
|
56
|
+
return abc.abstractmethod(obj)
|
57
|
+
|
58
|
+
elif isinstance(obj, property):
|
59
|
+
return ta.cast(T, property(
|
60
|
+
abc.abstractmethod(obj.fget) if obj.fget is not None else None,
|
61
|
+
abc.abstractmethod(obj.fset) if obj.fset is not None else None,
|
62
|
+
abc.abstractmethod(obj.fdel) if obj.fdel is not None else None,
|
63
|
+
))
|
64
|
+
|
65
|
+
elif isinstance(obj, (classmethod, staticmethod)):
|
66
|
+
return ta.cast(T, type(obj)(abc.abstractmethod(obj.__func__)))
|
67
|
+
|
68
|
+
else:
|
69
|
+
return obj
|
omlish/lang/classes/virtual.py
CHANGED
@@ -59,7 +59,7 @@ class _VirtualMeta(abc.ABCMeta):
|
|
59
59
|
if get_missing_reqs(subclass):
|
60
60
|
return False
|
61
61
|
if user_subclasshook is not None:
|
62
|
-
ret = user_subclasshook(cls, subclass)
|
62
|
+
ret = user_subclasshook(cls, subclass) # noqa
|
63
63
|
else:
|
64
64
|
ret = super(kls, cls).__subclasshook__(subclass) # type: ignore
|
65
65
|
return True if ret is NotImplemented else ret
|
@@ -113,7 +113,7 @@ class Picklable(Virtual):
|
|
113
113
|
##
|
114
114
|
|
115
115
|
|
116
|
-
class Callable(NotInstantiable, Final, ta.Generic[T]):
|
116
|
+
class Callable(NotInstantiable, Final, ta.Generic[T], metaclass=abc.ABCMeta):
|
117
117
|
def __call__(self, *args: ta.Any, **kwargs: ta.Any) -> T:
|
118
118
|
raise TypeError
|
119
119
|
|
omlish/lang/contextmanagers.py
CHANGED
@@ -13,6 +13,8 @@ import time
|
|
13
13
|
import types
|
14
14
|
import typing as ta
|
15
15
|
|
16
|
+
from ..lite.abstract import Abstract
|
17
|
+
|
16
18
|
|
17
19
|
T = ta.TypeVar('T')
|
18
20
|
K = ta.TypeVar('K')
|
@@ -22,7 +24,7 @@ V = ta.TypeVar('V')
|
|
22
24
|
##
|
23
25
|
|
24
26
|
|
25
|
-
class ContextManaged(
|
27
|
+
class ContextManaged(Abstract):
|
26
28
|
def __enter__(self):
|
27
29
|
return None
|
28
30
|
|
@@ -56,7 +58,7 @@ NOP_CONTEXT_MANAGER = ValueContextManager(None)
|
|
56
58
|
#
|
57
59
|
|
58
60
|
|
59
|
-
class AsyncContextManaged(
|
61
|
+
class AsyncContextManaged(Abstract):
|
60
62
|
async def __aenter__(self):
|
61
63
|
return None
|
62
64
|
|
@@ -90,7 +92,7 @@ NOP_ASYNC_CONTEXT_MANAGER = ValueAsyncContextManager(None)
|
|
90
92
|
##
|
91
93
|
|
92
94
|
|
93
|
-
class ContextManager(
|
95
|
+
class ContextManager(Abstract, ta.Generic[T]):
|
94
96
|
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
95
97
|
super().__init_subclass__(**kwargs)
|
96
98
|
|
@@ -122,7 +124,7 @@ class ContextManager(abc.ABC, ta.Generic[T]):
|
|
122
124
|
#
|
123
125
|
|
124
126
|
|
125
|
-
class AsyncContextManager(
|
127
|
+
class AsyncContextManager(Abstract, ta.Generic[T]):
|
126
128
|
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
127
129
|
super().__init_subclass__(**kwargs)
|
128
130
|
|
omlish/lang/generators.py
CHANGED
@@ -2,8 +2,8 @@ import abc
|
|
2
2
|
import functools
|
3
3
|
import typing as ta
|
4
4
|
|
5
|
+
from ..lite.abstract import Abstract
|
5
6
|
from ..lite.maybes import Maybe
|
6
|
-
from .classes.restrict import Abstract
|
7
7
|
|
8
8
|
|
9
9
|
T = ta.TypeVar('T')
|
@@ -45,7 +45,7 @@ class GeneratorLike(ta.Protocol[O_co, I_contra, R_co]):
|
|
45
45
|
...
|
46
46
|
|
47
47
|
|
48
|
-
class GeneratorLike_(
|
48
|
+
class GeneratorLike_(Abstract, ta.Generic[O, I, R]): # noqa
|
49
49
|
@abc.abstractmethod
|
50
50
|
def send(self, i: I) -> O: # Raises[StopIteration[R]]
|
51
51
|
raise NotImplementedError
|
omlish/lang/iterables.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import
|
1
|
+
import collections
|
2
2
|
import functools
|
3
3
|
import itertools
|
4
4
|
import typing as ta
|
@@ -22,9 +22,8 @@ def take(n: int, it: ta.Iterable[T]) -> list[T]:
|
|
22
22
|
return list(itertools.islice(it, n))
|
23
23
|
|
24
24
|
|
25
|
-
def
|
26
|
-
|
27
|
-
pass
|
25
|
+
def consume(it: ta.Iterable[ta.Any]) -> None:
|
26
|
+
collections.deque(it, maxlen=0)
|
28
27
|
|
29
28
|
|
30
29
|
def peek(vs: ta.Iterable[T]) -> tuple[T, ta.Iterator[T]]:
|
@@ -87,9 +86,10 @@ def readiter(f, sz):
|
|
87
86
|
##
|
88
87
|
|
89
88
|
|
90
|
-
@
|
89
|
+
@ta.final
|
91
90
|
class IterGen(ta.Generic[T]):
|
92
|
-
fn: ta.Callable[[], ta.Iterable[T]]
|
91
|
+
def __init__(self, fn: ta.Callable[[], ta.Iterable[T]]) -> None:
|
92
|
+
self.fn = fn
|
93
93
|
|
94
94
|
def __iter__(self):
|
95
95
|
return iter(self.fn())
|
omlish/lang/objects.py
CHANGED
@@ -101,64 +101,6 @@ def deep_subclasses(
|
|
101
101
|
todo.extend(reversed(cur.__subclasses__()))
|
102
102
|
|
103
103
|
|
104
|
-
def mro_owner_dict(
|
105
|
-
instance_cls: type,
|
106
|
-
owner_cls: type | None = None,
|
107
|
-
*,
|
108
|
-
bottom_up_key_order: bool = False,
|
109
|
-
sort_keys: bool = False,
|
110
|
-
) -> ta.Mapping[str, tuple[type, ta.Any]]:
|
111
|
-
if owner_cls is None:
|
112
|
-
owner_cls = instance_cls
|
113
|
-
|
114
|
-
mro = instance_cls.__mro__[-2::-1]
|
115
|
-
try:
|
116
|
-
pos = mro.index(owner_cls)
|
117
|
-
except ValueError:
|
118
|
-
raise TypeError(f'Owner class {owner_cls} not in mro of instance class {instance_cls}') from None
|
119
|
-
|
120
|
-
dct: dict[str, tuple[type, ta.Any]] = {}
|
121
|
-
if not bottom_up_key_order:
|
122
|
-
for cur_cls in mro[:pos + 1][::-1]:
|
123
|
-
for k, v in cur_cls.__dict__.items():
|
124
|
-
if k not in dct:
|
125
|
-
dct[k] = (cur_cls, v)
|
126
|
-
|
127
|
-
else:
|
128
|
-
for cur_cls in mro[:pos + 1]:
|
129
|
-
dct.update({k: (cur_cls, v) for k, v in cur_cls.__dict__.items()})
|
130
|
-
|
131
|
-
if sort_keys:
|
132
|
-
dct = dict(sorted(dct.items(), key=lambda t: t[0]))
|
133
|
-
|
134
|
-
return dct
|
135
|
-
|
136
|
-
|
137
|
-
def mro_dict(
|
138
|
-
instance_cls: type,
|
139
|
-
owner_cls: type | None = None,
|
140
|
-
*,
|
141
|
-
bottom_up_key_order: bool = False,
|
142
|
-
sort_keys: bool = False,
|
143
|
-
) -> ta.Mapping[str, ta.Any]:
|
144
|
-
return {
|
145
|
-
k: v
|
146
|
-
for k, (o, v) in mro_owner_dict(
|
147
|
-
instance_cls,
|
148
|
-
owner_cls,
|
149
|
-
bottom_up_key_order=bottom_up_key_order,
|
150
|
-
sort_keys=sort_keys,
|
151
|
-
).items()
|
152
|
-
}
|
153
|
-
|
154
|
-
|
155
|
-
def dir_dict(o: ta.Any) -> dict[str, ta.Any]:
|
156
|
-
return {
|
157
|
-
a: getattr(o, a)
|
158
|
-
for a in dir(o)
|
159
|
-
}
|
160
|
-
|
161
|
-
|
162
104
|
##
|
163
105
|
|
164
106
|
|
omlish/lang/outcomes.py
CHANGED
@@ -22,6 +22,8 @@ import abc
|
|
22
22
|
import dataclasses as dc
|
23
23
|
import typing as ta
|
24
24
|
|
25
|
+
from ..lite.abstract import Abstract
|
26
|
+
|
25
27
|
|
26
28
|
T = ta.TypeVar('T')
|
27
29
|
ValueT_co = ta.TypeVar('ValueT_co', covariant=True)
|
@@ -131,7 +133,7 @@ async def acapture(
|
|
131
133
|
|
132
134
|
|
133
135
|
@dc.dataclass(repr=False, init=False, slots=True, frozen=True, order=True)
|
134
|
-
class Outcome(
|
136
|
+
class Outcome(Abstract, ta.Generic[ValueT_co]):
|
135
137
|
"""
|
136
138
|
An abstract class representing the result of a Python computation.
|
137
139
|
|
omlish/lang/typing.py
CHANGED
@@ -5,10 +5,11 @@ TODO:
|
|
5
5
|
- probably need to gen types per inst
|
6
6
|
- typed_factory
|
7
7
|
"""
|
8
|
-
import functools
|
9
8
|
import inspect
|
10
9
|
import typing as ta
|
11
10
|
|
11
|
+
from ..lite.wrappers import update_wrapper_no_annotations
|
12
|
+
|
12
13
|
|
13
14
|
Ty = ta.TypeVar('Ty', bound=type)
|
14
15
|
|
@@ -59,26 +60,6 @@ def protocol_check(proto: type) -> ta.Callable[[Ty], Ty]:
|
|
59
60
|
##
|
60
61
|
|
61
62
|
|
62
|
-
_ANN_ATTRS: frozenset[str] = frozenset([
|
63
|
-
'__annotations__',
|
64
|
-
|
65
|
-
'__annotate__',
|
66
|
-
'__annotate_func__',
|
67
|
-
|
68
|
-
'__annotations_cache__',
|
69
|
-
])
|
70
|
-
|
71
|
-
_UPDATE_WRAPPER_ASSIGNED_NO_ANNS = list(frozenset(functools.WRAPPER_ASSIGNMENTS) - _ANN_ATTRS)
|
72
|
-
|
73
|
-
|
74
|
-
def _update_wrapper_no_anns(wrapper, wrapped):
|
75
|
-
functools.update_wrapper(wrapper, wrapped, assigned=_UPDATE_WRAPPER_ASSIGNED_NO_ANNS)
|
76
|
-
return wrapper
|
77
|
-
|
78
|
-
|
79
|
-
##
|
80
|
-
|
81
|
-
|
82
63
|
_MISSING = object()
|
83
64
|
|
84
65
|
|
@@ -113,7 +94,7 @@ def typed_lambda(ret=_MISSING, **kw): # noqa
|
|
113
94
|
src = f'{"".join(proto)} {"".join(call)}'
|
114
95
|
exec(src, ns)
|
115
96
|
|
116
|
-
lam =
|
97
|
+
lam = update_wrapper_no_annotations(ns['__lam'], fn)
|
117
98
|
lam.__signature__ = inspect.signature(lam, follow_wrapped=False)
|
118
99
|
return lam
|
119
100
|
|
@@ -131,7 +112,7 @@ def typed_partial(obj, **kw): # noqa
|
|
131
112
|
|
132
113
|
sig = inspect.signature(obj)
|
133
114
|
|
134
|
-
inner =
|
115
|
+
inner = update_wrapper_no_annotations(lambda **lkw: obj(**lkw, **kw), obj)
|
135
116
|
|
136
117
|
ret = (
|
137
118
|
obj if isinstance(obj, type) else
|
@@ -149,7 +130,7 @@ def typed_partial(obj, **kw): # noqa
|
|
149
130
|
},
|
150
131
|
)(inner)
|
151
132
|
|
152
|
-
return
|
133
|
+
return update_wrapper_no_annotations(lam, obj)
|
153
134
|
|
154
135
|
|
155
136
|
##
|
omlish/lite/abstract.py
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
# ruff: noqa: UP006
|
2
|
+
import abc
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
|
6
|
+
##
|
7
|
+
|
8
|
+
|
9
|
+
_ABSTRACT_METHODS_ATTR = '__abstractmethods__'
|
10
|
+
_IS_ABSTRACT_METHOD_ATTR = '__isabstractmethod__'
|
11
|
+
|
12
|
+
|
13
|
+
def is_abstract_method(obj: ta.Any) -> bool:
|
14
|
+
return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
|
15
|
+
|
16
|
+
|
17
|
+
def update_abstracts(cls, *, force=False):
|
18
|
+
if not force and not hasattr(cls, _ABSTRACT_METHODS_ATTR):
|
19
|
+
# Per stdlib: We check for __abstractmethods__ here because cls might by a C implementation or a python
|
20
|
+
# implementation (especially during testing), and we want to handle both cases.
|
21
|
+
return cls
|
22
|
+
|
23
|
+
abstracts: ta.Set[str] = set()
|
24
|
+
|
25
|
+
for scls in cls.__bases__:
|
26
|
+
for name in getattr(scls, _ABSTRACT_METHODS_ATTR, ()):
|
27
|
+
value = getattr(cls, name, None)
|
28
|
+
if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
|
29
|
+
abstracts.add(name)
|
30
|
+
|
31
|
+
for name, value in cls.__dict__.items():
|
32
|
+
if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
|
33
|
+
abstracts.add(name)
|
34
|
+
|
35
|
+
setattr(cls, _ABSTRACT_METHODS_ATTR, frozenset(abstracts))
|
36
|
+
return cls
|
37
|
+
|
38
|
+
|
39
|
+
#
|
40
|
+
|
41
|
+
|
42
|
+
class AbstractTypeError(TypeError):
|
43
|
+
pass
|
44
|
+
|
45
|
+
|
46
|
+
_FORCE_ABSTRACT_ATTR = '__forceabstract__'
|
47
|
+
|
48
|
+
|
49
|
+
class Abstract:
|
50
|
+
"""
|
51
|
+
Different from, but interoperable with, abc.ABC / abc.ABCMeta:
|
52
|
+
|
53
|
+
- This raises AbstractTypeError during class creation, not instance instantiation - unless Abstract or abc.ABC are
|
54
|
+
explicitly present in the class's direct bases.
|
55
|
+
- This will forbid instantiation of classes with Abstract in their direct bases even if there are no
|
56
|
+
abstractmethods left on the class.
|
57
|
+
- This is a mixin, not a metaclass.
|
58
|
+
- As it is not an ABCMeta, this does not support virtual base classes. As a result, operations like `isinstance`
|
59
|
+
and `issubclass` are ~7x faster.
|
60
|
+
- It additionally enforces a base class order of (Abstract, abc.ABC) to preemptively prevent common mro conflicts.
|
61
|
+
|
62
|
+
If not mixed-in with an ABCMeta, it will update __abstractmethods__ itself.
|
63
|
+
"""
|
64
|
+
|
65
|
+
__slots__ = ()
|
66
|
+
|
67
|
+
__abstractmethods__: ta.ClassVar[ta.FrozenSet[str]] = frozenset()
|
68
|
+
|
69
|
+
#
|
70
|
+
|
71
|
+
def __forceabstract__(self):
|
72
|
+
raise TypeError
|
73
|
+
|
74
|
+
# This is done manually, rather than through @abc.abstractmethod, to mask it from static analysis.
|
75
|
+
setattr(__forceabstract__, _IS_ABSTRACT_METHOD_ATTR, True)
|
76
|
+
|
77
|
+
#
|
78
|
+
|
79
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
80
|
+
setattr(
|
81
|
+
cls,
|
82
|
+
_FORCE_ABSTRACT_ATTR,
|
83
|
+
getattr(Abstract, _FORCE_ABSTRACT_ATTR) if Abstract in cls.__bases__ else False,
|
84
|
+
)
|
85
|
+
|
86
|
+
super().__init_subclass__(**kwargs)
|
87
|
+
|
88
|
+
if not (Abstract in cls.__bases__ or abc.ABC in cls.__bases__):
|
89
|
+
ams = {a: cls for a, o in cls.__dict__.items() if is_abstract_method(o)}
|
90
|
+
|
91
|
+
seen = set(cls.__dict__)
|
92
|
+
for b in cls.__bases__:
|
93
|
+
ams.update({a: b for a in set(getattr(b, _ABSTRACT_METHODS_ATTR, [])) - seen}) # noqa
|
94
|
+
seen.update(dir(b))
|
95
|
+
|
96
|
+
if ams:
|
97
|
+
raise AbstractTypeError(
|
98
|
+
f'Cannot subclass abstract class {cls.__name__} with abstract methods: ' +
|
99
|
+
', '.join(sorted([
|
100
|
+
'.'.join([
|
101
|
+
*([m] if (m := getattr(c, '__module__')) else []),
|
102
|
+
getattr(c, '__qualname__', getattr(c, '__name__')),
|
103
|
+
a,
|
104
|
+
])
|
105
|
+
for a, c in ams.items()
|
106
|
+
])),
|
107
|
+
)
|
108
|
+
|
109
|
+
xbi = (Abstract, abc.ABC) # , ta.Generic ?
|
110
|
+
bis = [(cls.__bases__.index(b), b) for b in xbi if b in cls.__bases__]
|
111
|
+
if bis != sorted(bis):
|
112
|
+
raise TypeError(
|
113
|
+
f'Abstract subclass {cls.__name__} must have proper base class order of '
|
114
|
+
f'({", ".join(getattr(b, "__name__") for b in xbi)}), got: '
|
115
|
+
f'({", ".join(getattr(b, "__name__") for _, b in sorted(bis))})',
|
116
|
+
)
|
117
|
+
|
118
|
+
if not isinstance(cls, abc.ABCMeta):
|
119
|
+
update_abstracts(cls, force=True)
|
omlish/lite/contextmanagers.py
CHANGED
@@ -190,7 +190,7 @@ def attr_setting(obj, attr, val, *, default=None): # noqa
|
|
190
190
|
##
|
191
191
|
|
192
192
|
|
193
|
-
class
|
193
|
+
class AsyncClosingManager(contextlib.AbstractAsyncContextManager):
|
194
194
|
def __init__(self, thing):
|
195
195
|
self.thing = thing
|
196
196
|
|
@@ -199,3 +199,6 @@ class aclosing(contextlib.AbstractAsyncContextManager): # noqa
|
|
199
199
|
|
200
200
|
async def __aexit__(self, *exc_info):
|
201
201
|
await self.thing.aclose()
|
202
|
+
|
203
|
+
|
204
|
+
aclosing = AsyncClosingManager
|
omlish/lite/inject.py
CHANGED
@@ -9,6 +9,7 @@ import types
|
|
9
9
|
import typing as ta
|
10
10
|
import weakref
|
11
11
|
|
12
|
+
from .abstract import Abstract
|
12
13
|
from .check import check
|
13
14
|
from .maybes import Maybe
|
14
15
|
from .reflect import get_optional_alias_arg
|
@@ -58,7 +59,7 @@ def check_valid_injector_key_cls(cls: T) -> T:
|
|
58
59
|
##
|
59
60
|
|
60
61
|
|
61
|
-
class InjectorProvider(
|
62
|
+
class InjectorProvider(Abstract):
|
62
63
|
@abc.abstractmethod
|
63
64
|
def provider_fn(self) -> InjectorProviderFn:
|
64
65
|
raise NotImplementedError
|
@@ -77,7 +78,7 @@ class InjectorBinding:
|
|
77
78
|
check.isinstance(self.provider, InjectorProvider)
|
78
79
|
|
79
80
|
|
80
|
-
class InjectorBindings(
|
81
|
+
class InjectorBindings(Abstract):
|
81
82
|
@abc.abstractmethod
|
82
83
|
def bindings(self) -> ta.Iterator[InjectorBinding]:
|
83
84
|
raise NotImplementedError
|
@@ -85,7 +86,7 @@ class InjectorBindings(abc.ABC):
|
|
85
86
|
##
|
86
87
|
|
87
88
|
|
88
|
-
class Injector(
|
89
|
+
class Injector(Abstract):
|
89
90
|
@abc.abstractmethod
|
90
91
|
def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
|
91
92
|
raise NotImplementedError
|
@@ -344,14 +345,12 @@ def injector_override(p: InjectorBindings, *args: InjectorBindingOrBindings) ->
|
|
344
345
|
# scopes
|
345
346
|
|
346
347
|
|
347
|
-
class InjectorScope(
|
348
|
+
class InjectorScope(Abstract):
|
348
349
|
def __init__(
|
349
350
|
self,
|
350
351
|
*,
|
351
352
|
_i: Injector,
|
352
353
|
) -> None:
|
353
|
-
check.not_in(abc.ABC, type(self).__bases__)
|
354
|
-
|
355
354
|
super().__init__()
|
356
355
|
|
357
356
|
self._i = _i
|
@@ -382,7 +381,7 @@ class InjectorScope(abc.ABC): # noqa
|
|
382
381
|
raise NotImplementedError
|
383
382
|
|
384
383
|
|
385
|
-
class ExclusiveInjectorScope(InjectorScope,
|
384
|
+
class ExclusiveInjectorScope(InjectorScope, Abstract):
|
386
385
|
_st: ta.Optional[InjectorScope.State] = None
|
387
386
|
|
388
387
|
def state(self) -> InjectorScope.State:
|
@@ -398,12 +397,13 @@ class ExclusiveInjectorScope(InjectorScope, abc.ABC):
|
|
398
397
|
self._st = None
|
399
398
|
|
400
399
|
|
401
|
-
class ContextvarInjectorScope(InjectorScope,
|
400
|
+
class ContextvarInjectorScope(InjectorScope, Abstract):
|
402
401
|
_cv: contextvars.ContextVar
|
403
402
|
|
404
403
|
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
405
404
|
super().__init_subclass__(**kwargs)
|
406
405
|
|
406
|
+
check.not_in(Abstract, cls.__bases__)
|
407
407
|
check.not_in(abc.ABC, cls.__bases__)
|
408
408
|
check.state(not hasattr(cls, '_cv'))
|
409
409
|
cls._cv = contextvars.ContextVar(f'{cls.__name__}_cv')
|
@@ -909,7 +909,7 @@ class InjectorBinder:
|
|
909
909
|
pws: ta.List[ta.Any] = []
|
910
910
|
if in_ is not None:
|
911
911
|
check.issubclass(in_, InjectorScope)
|
912
|
-
check.not_in(
|
912
|
+
check.not_in(Abstract, in_.__bases__)
|
913
913
|
pws.append(functools.partial(ScopedInjectorProvider, k=key, sc=in_))
|
914
914
|
if singleton:
|
915
915
|
pws.append(SingletonInjectorProvider)
|