omlish 0.0.0.dev6__py3-none-any.whl → 0.0.0.dev8__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 +109 -5
- omlish/__init__.py +0 -8
- omlish/asyncs/__init__.py +0 -9
- omlish/asyncs/anyio.py +40 -0
- omlish/bootstrap.py +737 -0
- omlish/check.py +1 -1
- omlish/collections/__init__.py +4 -0
- omlish/collections/exceptions.py +2 -0
- omlish/collections/utils.py +38 -9
- omlish/configs/strings.py +2 -0
- omlish/dataclasses/__init__.py +7 -0
- omlish/dataclasses/impl/descriptors.py +95 -0
- omlish/dataclasses/impl/reflect.py +1 -1
- omlish/dataclasses/utils.py +23 -0
- omlish/{lang/datetimes.py → datetimes.py} +8 -4
- omlish/diag/procfs.py +1 -1
- omlish/diag/threads.py +131 -48
- omlish/docker.py +16 -1
- omlish/fnpairs.py +0 -4
- omlish/{serde → formats}/dotenv.py +3 -0
- omlish/{serde → formats}/yaml.py +2 -2
- omlish/graphs/trees.py +1 -1
- omlish/http/consts.py +6 -0
- omlish/http/sessions.py +2 -2
- omlish/inject/__init__.py +4 -0
- omlish/inject/binder.py +3 -3
- omlish/inject/elements.py +1 -1
- omlish/inject/impl/injector.py +57 -27
- omlish/inject/impl/origins.py +2 -0
- omlish/inject/origins.py +3 -0
- omlish/inject/utils.py +18 -0
- omlish/iterators.py +69 -2
- omlish/lang/__init__.py +16 -7
- omlish/lang/classes/restrict.py +10 -0
- omlish/lang/contextmanagers.py +1 -1
- omlish/lang/descriptors.py +3 -3
- omlish/lang/imports.py +67 -0
- omlish/lang/iterables.py +40 -0
- 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 +37 -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 +121 -0
- omlish/lite/marshal.py +318 -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/__init__.py +13 -9
- omlish/logs/configs.py +17 -22
- omlish/logs/formatters.py +3 -48
- omlish/marshal/__init__.py +28 -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/numbers.py +51 -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 +18 -2
- omlish/secrets/crypto.py +132 -0
- omlish/secrets/marshal.py +36 -7
- omlish/secrets/openssl.py +207 -0
- omlish/secrets/secrets.py +260 -8
- omlish/secrets/subprocesses.py +42 -0
- omlish/sql/dbs.py +6 -5
- omlish/sql/exprs.py +12 -0
- omlish/sql/secrets.py +10 -0
- omlish/term.py +1 -1
- omlish/testing/pytest/plugins/switches.py +54 -19
- omlish/text/glyphsplit.py +5 -0
- omlish-0.0.0.dev8.dist-info/METADATA +50 -0
- {omlish-0.0.0.dev6.dist-info → omlish-0.0.0.dev8.dist-info}/RECORD +105 -78
- {omlish-0.0.0.dev6.dist-info → omlish-0.0.0.dev8.dist-info}/WHEEL +1 -1
- omlish/logs/filters.py +0 -11
- omlish/reflect.py +0 -470
- omlish-0.0.0.dev6.dist-info/METADATA +0 -34
- /omlish/{asyncs/futures.py → concurrent.py} +0 -0
- /omlish/{serde → formats}/__init__.py +0 -0
- /omlish/{serde → formats}/json.py +0 -0
- /omlish/{serde → formats}/props.py +0 -0
- {omlish-0.0.0.dev6.dist-info → omlish-0.0.0.dev8.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev6.dist-info → omlish-0.0.0.dev8.dist-info}/top_level.txt +0 -0
omlish/inject/elements.py
CHANGED
omlish/inject/impl/injector.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
TODO:
|
|
3
|
+
- ** can currently bind in a child/private scope shadowing an external parent binding **
|
|
4
|
+
- better source tracking
|
|
3
5
|
- cache/export ElementCollections lol
|
|
4
6
|
- scope bindings, auto in root
|
|
5
7
|
- injector-internal / blacklisted bindings (Injector itself, default scopes) without rebuilding ElementCollection
|
|
@@ -14,6 +16,7 @@ TODO:
|
|
|
14
16
|
import contextlib
|
|
15
17
|
import functools
|
|
16
18
|
import itertools
|
|
19
|
+
import logging
|
|
17
20
|
import typing as ta
|
|
18
21
|
import weakref
|
|
19
22
|
|
|
@@ -39,6 +42,9 @@ from .scopes import ScopeImpl
|
|
|
39
42
|
from .scopes import make_scope_impl
|
|
40
43
|
|
|
41
44
|
|
|
45
|
+
log = logging.getLogger(__name__)
|
|
46
|
+
|
|
47
|
+
|
|
42
48
|
DEFAULT_SCOPES: list[Scope] = [
|
|
43
49
|
Unscoped(),
|
|
44
50
|
Singleton(),
|
|
@@ -103,10 +109,11 @@ class InjectorImpl(Injector, lang.Final):
|
|
|
103
109
|
def __init__(self, injector: 'InjectorImpl') -> None:
|
|
104
110
|
super().__init__()
|
|
105
111
|
self._injector = injector
|
|
106
|
-
self._provisions: dict[Key,
|
|
112
|
+
self._provisions: dict[Key, lang.Maybe] = {}
|
|
107
113
|
self._seen_keys: set[Key] = set()
|
|
114
|
+
self._source_stack: list[ta.Any] = []
|
|
108
115
|
|
|
109
|
-
def handle_key(self, key: Key) -> lang.Maybe:
|
|
116
|
+
def handle_key(self, key: Key) -> lang.Maybe[lang.Maybe]:
|
|
110
117
|
try:
|
|
111
118
|
return lang.just(self._provisions[key])
|
|
112
119
|
except KeyError:
|
|
@@ -116,10 +123,21 @@ class InjectorImpl(Injector, lang.Final):
|
|
|
116
123
|
self._seen_keys.add(key)
|
|
117
124
|
return lang.empty()
|
|
118
125
|
|
|
119
|
-
def handle_provision(self, key: Key,
|
|
126
|
+
def handle_provision(self, key: Key, mv: lang.Maybe) -> lang.Maybe:
|
|
120
127
|
check.in_(key, self._seen_keys)
|
|
121
128
|
check.not_in(key, self._provisions)
|
|
122
|
-
self._provisions[key] =
|
|
129
|
+
self._provisions[key] = mv
|
|
130
|
+
return mv
|
|
131
|
+
|
|
132
|
+
@contextlib.contextmanager
|
|
133
|
+
def push_source(self, source: ta.Any) -> ta.Iterator[None]:
|
|
134
|
+
self._source_stack.append(source)
|
|
135
|
+
try:
|
|
136
|
+
yield
|
|
137
|
+
finally:
|
|
138
|
+
nsource = self._source_stack.pop()
|
|
139
|
+
if source is not nsource:
|
|
140
|
+
raise Exception(f'Stack error: {source=} is not {nsource=}')
|
|
123
141
|
|
|
124
142
|
def __enter__(self) -> ta.Self:
|
|
125
143
|
return self
|
|
@@ -140,38 +158,50 @@ class InjectorImpl(Injector, lang.Final):
|
|
|
140
158
|
finally:
|
|
141
159
|
self.__cur_req = None
|
|
142
160
|
|
|
143
|
-
def
|
|
161
|
+
def _try_provide(self, key: ta.Any, *, source: ta.Any = None) -> lang.Maybe[ta.Any]:
|
|
144
162
|
key = as_key(key)
|
|
145
163
|
|
|
164
|
+
cr: InjectorImpl._Request
|
|
146
165
|
with self._current_request() as cr:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
166
|
+
with cr.push_source(source):
|
|
167
|
+
if (rv := cr.handle_key(key)).present:
|
|
168
|
+
return rv.must()
|
|
150
169
|
|
|
151
|
-
|
|
152
|
-
|
|
170
|
+
ic = self._internal_consts.get(key)
|
|
171
|
+
if ic is not None:
|
|
172
|
+
return cr.handle_provision(key, lang.just(ic))
|
|
153
173
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
174
|
+
bi = self._bim.get(key)
|
|
175
|
+
if bi is not None:
|
|
176
|
+
sc = self._scopes[bi.scope]
|
|
157
177
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
178
|
+
fn = lambda: sc.provide(bi, self) # noqa
|
|
179
|
+
for pl in self._pls:
|
|
180
|
+
fn = functools.partial(pl, self, key, bi.binding, fn)
|
|
181
|
+
v = fn()
|
|
162
182
|
|
|
163
|
-
|
|
164
|
-
return lang.just(v)
|
|
183
|
+
return cr.handle_provision(key, lang.just(v))
|
|
165
184
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
185
|
+
if self._p is not None:
|
|
186
|
+
pv = self._p._try_provide(key, source=source) # noqa
|
|
187
|
+
if pv.present:
|
|
188
|
+
return cr.handle_provision(key, pv)
|
|
170
189
|
|
|
171
|
-
|
|
190
|
+
return cr.handle_provision(key, lang.empty())
|
|
191
|
+
|
|
192
|
+
def _provide(self, key: ta.Any, *, source: ta.Any = None) -> ta.Any:
|
|
193
|
+
v = self._try_provide(key, source=source)
|
|
194
|
+
if v.present:
|
|
195
|
+
return v.must()
|
|
196
|
+
raise UnboundKeyError(key)
|
|
197
|
+
|
|
198
|
+
#
|
|
199
|
+
|
|
200
|
+
def try_provide(self, key: ta.Any) -> lang.Maybe[ta.Any]:
|
|
201
|
+
return self.try_provide(key)
|
|
172
202
|
|
|
173
203
|
def provide(self, key: ta.Any) -> ta.Any:
|
|
174
|
-
v = self.
|
|
204
|
+
v = self._try_provide(key)
|
|
175
205
|
if v.present:
|
|
176
206
|
return v.must()
|
|
177
207
|
raise UnboundKeyError(key)
|
|
@@ -180,11 +210,11 @@ class InjectorImpl(Injector, lang.Final):
|
|
|
180
210
|
ret: dict[str, ta.Any] = {}
|
|
181
211
|
for kw in kt.kwargs:
|
|
182
212
|
if kw.has_default:
|
|
183
|
-
if not (mv := self.
|
|
213
|
+
if not (mv := self._try_provide(kw.key, source=kt)).present:
|
|
184
214
|
continue
|
|
185
215
|
v = mv.must()
|
|
186
216
|
else:
|
|
187
|
-
v = self.
|
|
217
|
+
v = self._provide(kw.key, source=kt)
|
|
188
218
|
ret[kw.name] = v
|
|
189
219
|
return ret
|
|
190
220
|
|
omlish/inject/impl/origins.py
CHANGED
omlish/inject/origins.py
CHANGED
omlish/inject/utils.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
|
|
3
|
+
from .. import dataclasses as dc
|
|
4
|
+
from .. import lang
|
|
5
|
+
from .impl.origins import HasOriginsImpl
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
T = ta.TypeVar('T')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dc.dataclass(frozen=True, eq=False)
|
|
12
|
+
class ConstFn(HasOriginsImpl, lang.Final, ta.Generic[T]):
|
|
13
|
+
"""An origin tracking provider function for a constant value. Equivalent to `lambda: v` but transparent."""
|
|
14
|
+
|
|
15
|
+
v: T
|
|
16
|
+
|
|
17
|
+
def __call__(self) -> T:
|
|
18
|
+
return self.v
|
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,13 +68,6 @@ 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,
|
|
@@ -121,6 +114,7 @@ from .imports import ( # noqa
|
|
|
121
114
|
import_module_attr,
|
|
122
115
|
lazy_import,
|
|
123
116
|
proxy_import,
|
|
117
|
+
resolve_import_name,
|
|
124
118
|
try_import,
|
|
125
119
|
yield_import_all,
|
|
126
120
|
yield_importable,
|
|
@@ -128,8 +122,11 @@ from .imports import ( # noqa
|
|
|
128
122
|
|
|
129
123
|
from .iterables import ( # noqa
|
|
130
124
|
BUILTIN_SCALAR_ITERABLE_TYPES,
|
|
125
|
+
Generator,
|
|
131
126
|
asrange,
|
|
132
127
|
exhaust,
|
|
128
|
+
flatmap,
|
|
129
|
+
flatten,
|
|
133
130
|
ilen,
|
|
134
131
|
itergen,
|
|
135
132
|
peek,
|
|
@@ -149,12 +146,18 @@ from .objects import ( # noqa
|
|
|
149
146
|
SimpleProxy,
|
|
150
147
|
arg_repr,
|
|
151
148
|
attr_repr,
|
|
149
|
+
can_weakref,
|
|
150
|
+
deep_subclasses,
|
|
152
151
|
new_type,
|
|
153
152
|
opt_repr,
|
|
154
153
|
super_meta,
|
|
155
154
|
)
|
|
156
155
|
|
|
157
156
|
from .strings import ( # noqa
|
|
157
|
+
BOOL_FALSE_STRINGS,
|
|
158
|
+
BOOL_STRINGS,
|
|
159
|
+
BOOL_TRUE_STRINGS,
|
|
160
|
+
STRING_BOOL_VALUES,
|
|
158
161
|
camel_case,
|
|
159
162
|
indent_lines,
|
|
160
163
|
is_dunder,
|
|
@@ -167,6 +170,8 @@ from .strings import ( # noqa
|
|
|
167
170
|
)
|
|
168
171
|
|
|
169
172
|
from .sys import ( # noqa
|
|
173
|
+
REQUIRED_PYTHON_VERSION,
|
|
174
|
+
check_runtime_version,
|
|
170
175
|
is_gil_enabled,
|
|
171
176
|
)
|
|
172
177
|
|
|
@@ -180,6 +185,10 @@ from .timeouts import ( # noqa
|
|
|
180
185
|
|
|
181
186
|
from .typing import ( # noqa
|
|
182
187
|
BytesLike,
|
|
188
|
+
Func0,
|
|
189
|
+
Func1,
|
|
190
|
+
Func2,
|
|
191
|
+
Func3,
|
|
183
192
|
protocol_check,
|
|
184
193
|
typed_lambda,
|
|
185
194
|
typed_partial,
|
omlish/lang/classes/restrict.py
CHANGED
|
@@ -97,9 +97,19 @@ class NotInstantiable(Abstract):
|
|
|
97
97
|
class NotPicklable:
|
|
98
98
|
__slots__ = ()
|
|
99
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
|
|
100
109
|
def __getstate__(self) -> ta.NoReturn:
|
|
101
110
|
raise TypeError
|
|
102
111
|
|
|
112
|
+
@ta.final
|
|
103
113
|
def __setstate__(self, state) -> ta.NoReturn:
|
|
104
114
|
raise TypeError
|
|
105
115
|
|
omlish/lang/contextmanagers.py
CHANGED
|
@@ -307,7 +307,7 @@ Lockable = ta.Callable[[], ta.ContextManager]
|
|
|
307
307
|
DefaultLockable = bool | Lockable | ta.ContextManager | None
|
|
308
308
|
|
|
309
309
|
|
|
310
|
-
def default_lock(value: DefaultLockable, default: DefaultLockable) -> Lockable:
|
|
310
|
+
def default_lock(value: DefaultLockable, default: DefaultLockable = None) -> Lockable:
|
|
311
311
|
if value is None:
|
|
312
312
|
value = default
|
|
313
313
|
|
omlish/lang/descriptors.py
CHANGED
|
@@ -114,7 +114,7 @@ class _decorator_descriptor: # noqa
|
|
|
114
114
|
self._wrapper, self._fn = wrapper, fn
|
|
115
115
|
update_wrapper_except_dict(self, fn)
|
|
116
116
|
|
|
117
|
-
def __get__(self, instance, owner):
|
|
117
|
+
def __get__(self, instance, owner=None):
|
|
118
118
|
return functools.update_wrapper(functools.partial(self._wrapper, fn := self._fn.__get__(instance, owner)), fn) # noqa
|
|
119
119
|
|
|
120
120
|
else:
|
|
@@ -123,7 +123,7 @@ class _decorator_descriptor: # noqa
|
|
|
123
123
|
self._md = _has_method_descriptor(fn)
|
|
124
124
|
update_wrapper_except_dict(self, fn)
|
|
125
125
|
|
|
126
|
-
def __get__(self, instance, owner):
|
|
126
|
+
def __get__(self, instance, owner=None):
|
|
127
127
|
fn = self._fn.__get__(instance, owner)
|
|
128
128
|
if self._md or instance is not None:
|
|
129
129
|
@functools.wraps(fn)
|
|
@@ -218,7 +218,7 @@ class _ClassOnly:
|
|
|
218
218
|
def _mth(self, x):
|
|
219
219
|
self.__mth = x
|
|
220
220
|
|
|
221
|
-
def __get__(self, instance, owner):
|
|
221
|
+
def __get__(self, instance, owner=None):
|
|
222
222
|
if instance is not None:
|
|
223
223
|
raise TypeError(f'method must not be used on instance: {self._mth}')
|
|
224
224
|
return self._mth[0].__get__(instance, owner)
|
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)
|
omlish/lang/iterables.py
CHANGED
|
@@ -4,6 +4,8 @@ import typing as ta
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
T = ta.TypeVar('T')
|
|
7
|
+
S = ta.TypeVar('S')
|
|
8
|
+
R = ta.TypeVar('R')
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
BUILTIN_SCALAR_ITERABLE_TYPES: tuple[type, ...] = (
|
|
@@ -71,3 +73,41 @@ class itergen(ta.Generic[T]): # noqa
|
|
|
71
73
|
|
|
72
74
|
def renumerate(it: ta.Iterable[T]) -> ta.Iterable[tuple[T, int]]:
|
|
73
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()
|