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/iterators.py
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import collections
|
|
2
|
+
import dataclasses as dc
|
|
2
3
|
import functools
|
|
3
4
|
import heapq
|
|
4
5
|
import itertools
|
|
5
6
|
import typing as ta
|
|
6
7
|
|
|
8
|
+
# from . import check
|
|
9
|
+
from . import lang
|
|
10
|
+
|
|
7
11
|
|
|
8
12
|
T = ta.TypeVar('T')
|
|
9
13
|
U = ta.TypeVar('U')
|
|
@@ -13,10 +17,10 @@ _MISSING = object()
|
|
|
13
17
|
|
|
14
18
|
class PeekIterator(ta.Iterator[T]):
|
|
15
19
|
|
|
16
|
-
def __init__(self, it: ta.
|
|
20
|
+
def __init__(self, it: ta.Iterable[T]) -> None:
|
|
17
21
|
super().__init__()
|
|
18
22
|
|
|
19
|
-
self._it = it
|
|
23
|
+
self._it = iter(it)
|
|
20
24
|
self._pos = -1
|
|
21
25
|
self._next_item: ta.Any = _MISSING
|
|
22
26
|
|
|
@@ -231,3 +235,66 @@ def sliding_window(it: ta.Iterable[T], n: int) -> ta.Iterator[tuple[T, ...]]:
|
|
|
231
235
|
for x in iterator:
|
|
232
236
|
window.append(x)
|
|
233
237
|
yield tuple(window)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
##
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@dc.dataclass()
|
|
244
|
+
class UniqueStats:
|
|
245
|
+
key: ta.Any
|
|
246
|
+
num_seen: int
|
|
247
|
+
first_idx: int
|
|
248
|
+
last_idx: int
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@dc.dataclass(frozen=True)
|
|
252
|
+
class UniqueItem(ta.Generic[T]):
|
|
253
|
+
idx: int
|
|
254
|
+
item: T
|
|
255
|
+
stats: UniqueStats
|
|
256
|
+
out: lang.Maybe[T]
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class UniqueIterator(ta.Iterator[UniqueItem[T]]):
|
|
260
|
+
def __init__(
|
|
261
|
+
self,
|
|
262
|
+
it: ta.Iterable[T],
|
|
263
|
+
keyer: ta.Callable[[T], ta.Any] = lang.identity,
|
|
264
|
+
) -> None:
|
|
265
|
+
super().__init__()
|
|
266
|
+
self._it = enumerate(it)
|
|
267
|
+
self._keyer = keyer
|
|
268
|
+
|
|
269
|
+
self.stats: dict[ta.Any, UniqueStats] = {}
|
|
270
|
+
|
|
271
|
+
def __next__(self) -> UniqueItem[T]:
|
|
272
|
+
idx, item = next(self._it)
|
|
273
|
+
key = self._keyer(item)
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
stats = self.stats[key]
|
|
277
|
+
|
|
278
|
+
except KeyError:
|
|
279
|
+
stats = self.stats[key] = UniqueStats(
|
|
280
|
+
key,
|
|
281
|
+
num_seen=1,
|
|
282
|
+
first_idx=idx,
|
|
283
|
+
last_idx=idx,
|
|
284
|
+
)
|
|
285
|
+
return UniqueItem(
|
|
286
|
+
idx,
|
|
287
|
+
item,
|
|
288
|
+
stats,
|
|
289
|
+
lang.just(item),
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
else:
|
|
293
|
+
stats.num_seen += 1
|
|
294
|
+
stats.last_idx = idx
|
|
295
|
+
return UniqueItem(
|
|
296
|
+
idx,
|
|
297
|
+
item,
|
|
298
|
+
stats,
|
|
299
|
+
lang.empty(),
|
|
300
|
+
)
|
omlish/lang/__init__.py
CHANGED
|
@@ -68,21 +68,18 @@ from .contextmanagers import ( # noqa
|
|
|
68
68
|
maybe_managing,
|
|
69
69
|
)
|
|
70
70
|
|
|
71
|
-
from .datetimes import ( # noqa
|
|
72
|
-
months_ago,
|
|
73
|
-
parse_date,
|
|
74
|
-
parse_timedelta,
|
|
75
|
-
to_seconds,
|
|
76
|
-
)
|
|
77
|
-
|
|
78
71
|
from .descriptors import ( # noqa
|
|
79
72
|
AccessForbiddenError,
|
|
80
73
|
access_forbidden,
|
|
81
74
|
attr_property,
|
|
82
75
|
classonly,
|
|
76
|
+
decorator,
|
|
83
77
|
is_method_descriptor,
|
|
84
78
|
item_property,
|
|
79
|
+
unwrap_func,
|
|
80
|
+
unwrap_func_with_partials,
|
|
85
81
|
unwrap_method_descriptors,
|
|
82
|
+
update_wrapper_except_dict,
|
|
86
83
|
)
|
|
87
84
|
|
|
88
85
|
from .exceptions import ( # noqa
|
|
@@ -99,14 +96,14 @@ from .functions import ( # noqa
|
|
|
99
96
|
is_lambda,
|
|
100
97
|
is_none,
|
|
101
98
|
is_not_none,
|
|
99
|
+
isinstance_of,
|
|
100
|
+
issubclass_of,
|
|
102
101
|
maybe_call,
|
|
103
102
|
periodically,
|
|
104
103
|
raise_,
|
|
105
104
|
raising,
|
|
106
105
|
recurse,
|
|
107
106
|
try_,
|
|
108
|
-
unwrap_func,
|
|
109
|
-
unwrap_func_with_partials,
|
|
110
107
|
void,
|
|
111
108
|
)
|
|
112
109
|
|
|
@@ -117,6 +114,7 @@ from .imports import ( # noqa
|
|
|
117
114
|
import_module_attr,
|
|
118
115
|
lazy_import,
|
|
119
116
|
proxy_import,
|
|
117
|
+
resolve_import_name,
|
|
120
118
|
try_import,
|
|
121
119
|
yield_import_all,
|
|
122
120
|
yield_importable,
|
|
@@ -124,11 +122,16 @@ from .imports import ( # noqa
|
|
|
124
122
|
|
|
125
123
|
from .iterables import ( # noqa
|
|
126
124
|
BUILTIN_SCALAR_ITERABLE_TYPES,
|
|
125
|
+
Generator,
|
|
127
126
|
asrange,
|
|
128
127
|
exhaust,
|
|
128
|
+
flatmap,
|
|
129
|
+
flatten,
|
|
129
130
|
ilen,
|
|
131
|
+
itergen,
|
|
130
132
|
peek,
|
|
131
133
|
prodrange,
|
|
134
|
+
renumerate,
|
|
132
135
|
take,
|
|
133
136
|
)
|
|
134
137
|
|
|
@@ -143,12 +146,18 @@ from .objects import ( # noqa
|
|
|
143
146
|
SimpleProxy,
|
|
144
147
|
arg_repr,
|
|
145
148
|
attr_repr,
|
|
149
|
+
can_weakref,
|
|
150
|
+
deep_subclasses,
|
|
146
151
|
new_type,
|
|
147
152
|
opt_repr,
|
|
148
153
|
super_meta,
|
|
149
154
|
)
|
|
150
155
|
|
|
151
156
|
from .strings import ( # noqa
|
|
157
|
+
BOOL_FALSE_STRINGS,
|
|
158
|
+
BOOL_STRINGS,
|
|
159
|
+
BOOL_TRUE_STRINGS,
|
|
160
|
+
STRING_BOOL_VALUES,
|
|
152
161
|
camel_case,
|
|
153
162
|
indent_lines,
|
|
154
163
|
is_dunder,
|
|
@@ -161,6 +170,8 @@ from .strings import ( # noqa
|
|
|
161
170
|
)
|
|
162
171
|
|
|
163
172
|
from .sys import ( # noqa
|
|
173
|
+
REQUIRED_PYTHON_VERSION,
|
|
174
|
+
check_runtime_version,
|
|
164
175
|
is_gil_enabled,
|
|
165
176
|
)
|
|
166
177
|
|
|
@@ -174,6 +185,10 @@ from .timeouts import ( # noqa
|
|
|
174
185
|
|
|
175
186
|
from .typing import ( # noqa
|
|
176
187
|
BytesLike,
|
|
188
|
+
Func0,
|
|
189
|
+
Func1,
|
|
190
|
+
Func2,
|
|
191
|
+
Func3,
|
|
177
192
|
protocol_check,
|
|
178
193
|
typed_lambda,
|
|
179
194
|
typed_partial,
|
omlish/lang/cached.py
CHANGED
|
@@ -11,8 +11,8 @@ import typing as ta
|
|
|
11
11
|
|
|
12
12
|
from .contextmanagers import DefaultLockable
|
|
13
13
|
from .contextmanagers import default_lock
|
|
14
|
-
from .
|
|
15
|
-
from .
|
|
14
|
+
from .descriptors import unwrap_func
|
|
15
|
+
from .descriptors import unwrap_func_with_partials
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
P = ta.ParamSpec('P')
|
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
|
|
|
@@ -96,9 +97,19 @@ class NotInstantiable(Abstract):
|
|
|
96
97
|
class NotPicklable:
|
|
97
98
|
__slots__ = ()
|
|
98
99
|
|
|
100
|
+
@ta.final
|
|
101
|
+
def __reduce__(self) -> ta.NoReturn:
|
|
102
|
+
raise TypeError
|
|
103
|
+
|
|
104
|
+
@ta.final
|
|
105
|
+
def __reduce_ex__(self, protocol) -> ta.NoReturn:
|
|
106
|
+
raise TypeError
|
|
107
|
+
|
|
108
|
+
@ta.final
|
|
99
109
|
def __getstate__(self) -> ta.NoReturn:
|
|
100
110
|
raise TypeError
|
|
101
111
|
|
|
112
|
+
@ta.final
|
|
102
113
|
def __setstate__(self, state) -> ta.NoReturn:
|
|
103
114
|
raise TypeError
|
|
104
115
|
|
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/contextmanagers.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- AsyncExitStacked
|
|
4
|
+
"""
|
|
1
5
|
import abc
|
|
2
6
|
import contextlib
|
|
3
7
|
import contextvars
|
|
@@ -15,7 +19,7 @@ T = ta.TypeVar('T')
|
|
|
15
19
|
|
|
16
20
|
class ContextManaged:
|
|
17
21
|
|
|
18
|
-
def __enter__(self
|
|
22
|
+
def __enter__(self) -> ta.Self:
|
|
19
23
|
return self
|
|
20
24
|
|
|
21
25
|
def __exit__(
|
|
@@ -77,7 +81,7 @@ class ContextManager(abc.ABC, ta.Generic[T]):
|
|
|
77
81
|
exc_type: type[BaseException] | None,
|
|
78
82
|
exc_val: BaseException | None,
|
|
79
83
|
exc_tb: types.TracebackType | None,
|
|
80
|
-
) -> None:
|
|
84
|
+
) -> bool | None:
|
|
81
85
|
return self._contextmanager.__exit__(exc_type, exc_val, exc_tb)
|
|
82
86
|
|
|
83
87
|
|
|
@@ -198,7 +202,7 @@ class ExitStacked:
|
|
|
198
202
|
def _enter_context(self, context_manager: ta.ContextManager[T]) -> T:
|
|
199
203
|
return self._exit_stack.enter_context(ta.cast(ta.ContextManager, context_manager))
|
|
200
204
|
|
|
201
|
-
def __enter__(self
|
|
205
|
+
def __enter__(self) -> ta.Self:
|
|
202
206
|
try:
|
|
203
207
|
superfn = super().__enter__ # type: ignore
|
|
204
208
|
except AttributeError:
|
|
@@ -303,17 +307,22 @@ Lockable = ta.Callable[[], ta.ContextManager]
|
|
|
303
307
|
DefaultLockable = bool | Lockable | ta.ContextManager | None
|
|
304
308
|
|
|
305
309
|
|
|
306
|
-
def default_lock(value: DefaultLockable, default: DefaultLockable) -> Lockable:
|
|
310
|
+
def default_lock(value: DefaultLockable, default: DefaultLockable = None) -> Lockable:
|
|
307
311
|
if value is None:
|
|
308
312
|
value = default
|
|
313
|
+
|
|
309
314
|
if value is True:
|
|
310
315
|
lock = threading.RLock()
|
|
311
316
|
return lambda: lock
|
|
317
|
+
|
|
312
318
|
elif value is False or value is None:
|
|
313
319
|
return NOP_CONTEXT_MANAGER
|
|
320
|
+
|
|
314
321
|
elif callable(value):
|
|
315
322
|
return value
|
|
323
|
+
|
|
316
324
|
elif isinstance(value, ta.ContextManager):
|
|
317
325
|
return lambda: value
|
|
326
|
+
|
|
318
327
|
else:
|
|
319
328
|
raise TypeError(value)
|
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=None):
|
|
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=None):
|
|
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:
|
|
@@ -87,7 +218,7 @@ class _ClassOnly:
|
|
|
87
218
|
def _mth(self, x):
|
|
88
219
|
self.__mth = x
|
|
89
220
|
|
|
90
|
-
def __get__(self, instance, owner):
|
|
221
|
+
def __get__(self, instance, owner=None):
|
|
91
222
|
if instance is not None:
|
|
92
223
|
raise TypeError(f'method must not be used on instance: {self._mth}')
|
|
93
224
|
return self._mth[0].__get__(instance, owner)
|
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
|
|
omlish/lang/imports.py
CHANGED
|
@@ -155,3 +155,70 @@ def try_import(spec: str) -> types.ModuleType | None:
|
|
|
155
155
|
return __import__(s, globals(), level=l)
|
|
156
156
|
except ImportError:
|
|
157
157
|
return None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
##
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def resolve_import_name(name: str, package: str | None = None) -> str:
|
|
164
|
+
level = 0
|
|
165
|
+
|
|
166
|
+
if name.startswith('.'):
|
|
167
|
+
if not package:
|
|
168
|
+
raise TypeError("the 'package' argument is required to perform a relative import for {name!r}")
|
|
169
|
+
for character in name:
|
|
170
|
+
if character != '.':
|
|
171
|
+
break
|
|
172
|
+
level += 1
|
|
173
|
+
|
|
174
|
+
name = name[level:]
|
|
175
|
+
|
|
176
|
+
if not isinstance(name, str):
|
|
177
|
+
raise TypeError(f'module name must be str, not {type(name)}')
|
|
178
|
+
if level < 0:
|
|
179
|
+
raise ValueError('level must be >= 0')
|
|
180
|
+
if level > 0:
|
|
181
|
+
if not isinstance(package, str):
|
|
182
|
+
raise TypeError('__package__ not set to a string')
|
|
183
|
+
elif not package:
|
|
184
|
+
raise ImportError('attempted relative import with no known parent package')
|
|
185
|
+
if not name and level == 0:
|
|
186
|
+
raise ValueError('Empty module name')
|
|
187
|
+
|
|
188
|
+
if level > 0:
|
|
189
|
+
bits = package.rsplit('.', level - 1) # type: ignore
|
|
190
|
+
if len(bits) < level:
|
|
191
|
+
raise ImportError('attempted relative import beyond top-level package')
|
|
192
|
+
base = bits[0]
|
|
193
|
+
name = f'{base}.{name}' if name else base
|
|
194
|
+
|
|
195
|
+
return name
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
##
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
_REGISTERED_CONDITIONAL_IMPORTS: dict[str, list[str] | None] = {}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _register_conditional_import(when: str, then: str, package: str | None = None) -> None:
|
|
205
|
+
wn = resolve_import_name(when, package)
|
|
206
|
+
tn = resolve_import_name(then, package)
|
|
207
|
+
if tn in sys.modules:
|
|
208
|
+
return
|
|
209
|
+
if wn in sys.modules:
|
|
210
|
+
__import__(tn)
|
|
211
|
+
else:
|
|
212
|
+
tns = _REGISTERED_CONDITIONAL_IMPORTS.setdefault(wn, [])
|
|
213
|
+
if tns is None:
|
|
214
|
+
raise Exception(f'Conditional import trigger already cleared: {wn=} {tn=}')
|
|
215
|
+
tns.append(tn)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _trigger_conditional_imports(package: str) -> None:
|
|
219
|
+
tns = _REGISTERED_CONDITIONAL_IMPORTS.get(package, [])
|
|
220
|
+
if tns is None:
|
|
221
|
+
raise Exception(f'Conditional import trigger already cleared: {package=}')
|
|
222
|
+
_REGISTERED_CONDITIONAL_IMPORTS[package] = None
|
|
223
|
+
for tn in tns:
|
|
224
|
+
__import__(tn)
|