omlish 0.0.0.dev1__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 +7 -0
- omlish/__init__.py +0 -0
- omlish/argparse.py +223 -0
- omlish/asyncs/__init__.py +17 -0
- omlish/asyncs/anyio.py +23 -0
- omlish/asyncs/asyncio.py +19 -0
- omlish/asyncs/asyncs.py +76 -0
- omlish/asyncs/futures.py +179 -0
- omlish/asyncs/trio.py +11 -0
- omlish/c3.py +173 -0
- omlish/cached.py +9 -0
- omlish/check.py +231 -0
- omlish/collections/__init__.py +63 -0
- omlish/collections/_abc.py +156 -0
- omlish/collections/_io_abc.py +78 -0
- omlish/collections/cache/__init__.py +11 -0
- omlish/collections/cache/descriptor.py +188 -0
- omlish/collections/cache/impl.py +485 -0
- omlish/collections/cache/types.py +37 -0
- omlish/collections/coerce.py +337 -0
- omlish/collections/frozen.py +148 -0
- omlish/collections/identity.py +106 -0
- omlish/collections/indexed.py +75 -0
- omlish/collections/mappings.py +127 -0
- omlish/collections/ordered.py +81 -0
- omlish/collections/persistent.py +36 -0
- omlish/collections/skiplist.py +193 -0
- omlish/collections/sorted.py +126 -0
- omlish/collections/treap.py +228 -0
- omlish/collections/treapmap.py +144 -0
- omlish/collections/unmodifiable.py +174 -0
- omlish/collections/utils.py +110 -0
- omlish/configs/__init__.py +0 -0
- omlish/configs/flattening.py +147 -0
- omlish/configs/props.py +64 -0
- omlish/dataclasses/__init__.py +83 -0
- omlish/dataclasses/impl/__init__.py +6 -0
- omlish/dataclasses/impl/api.py +260 -0
- omlish/dataclasses/impl/as_.py +76 -0
- omlish/dataclasses/impl/exceptions.py +2 -0
- omlish/dataclasses/impl/fields.py +148 -0
- omlish/dataclasses/impl/frozen.py +55 -0
- omlish/dataclasses/impl/hashing.py +85 -0
- omlish/dataclasses/impl/init.py +173 -0
- omlish/dataclasses/impl/internals.py +118 -0
- omlish/dataclasses/impl/main.py +150 -0
- omlish/dataclasses/impl/metaclass.py +126 -0
- omlish/dataclasses/impl/metadata.py +74 -0
- omlish/dataclasses/impl/order.py +47 -0
- omlish/dataclasses/impl/params.py +150 -0
- omlish/dataclasses/impl/processing.py +16 -0
- omlish/dataclasses/impl/reflect.py +173 -0
- omlish/dataclasses/impl/replace.py +40 -0
- omlish/dataclasses/impl/repr.py +34 -0
- omlish/dataclasses/impl/simple.py +92 -0
- omlish/dataclasses/impl/slots.py +80 -0
- omlish/dataclasses/impl/utils.py +167 -0
- omlish/defs.py +193 -0
- omlish/dispatch/__init__.py +3 -0
- omlish/dispatch/dispatch.py +137 -0
- omlish/dispatch/functions.py +52 -0
- omlish/dispatch/methods.py +162 -0
- omlish/docker.py +149 -0
- omlish/dynamic.py +220 -0
- omlish/graphs/__init__.py +0 -0
- omlish/graphs/dot/__init__.py +19 -0
- omlish/graphs/dot/items.py +162 -0
- omlish/graphs/dot/rendering.py +147 -0
- omlish/graphs/dot/utils.py +30 -0
- omlish/graphs/trees.py +249 -0
- omlish/http/__init__.py +0 -0
- omlish/http/consts.py +20 -0
- omlish/http/wsgi.py +34 -0
- omlish/inject/__init__.py +85 -0
- omlish/inject/binder.py +12 -0
- omlish/inject/bindings.py +49 -0
- omlish/inject/eagers.py +21 -0
- omlish/inject/elements.py +43 -0
- omlish/inject/exceptions.py +49 -0
- omlish/inject/impl/__init__.py +0 -0
- omlish/inject/impl/bindings.py +19 -0
- omlish/inject/impl/elements.py +154 -0
- omlish/inject/impl/injector.py +182 -0
- omlish/inject/impl/inspect.py +98 -0
- omlish/inject/impl/private.py +109 -0
- omlish/inject/impl/providers.py +132 -0
- omlish/inject/impl/scopes.py +198 -0
- omlish/inject/injector.py +40 -0
- omlish/inject/inspect.py +14 -0
- omlish/inject/keys.py +43 -0
- omlish/inject/managed.py +24 -0
- omlish/inject/overrides.py +18 -0
- omlish/inject/private.py +29 -0
- omlish/inject/providers.py +111 -0
- omlish/inject/proxy.py +48 -0
- omlish/inject/scopes.py +84 -0
- omlish/inject/types.py +21 -0
- omlish/iterators.py +184 -0
- omlish/json.py +194 -0
- omlish/lang/__init__.py +112 -0
- omlish/lang/cached.py +267 -0
- omlish/lang/classes/__init__.py +24 -0
- omlish/lang/classes/abstract.py +74 -0
- omlish/lang/classes/restrict.py +137 -0
- omlish/lang/classes/simple.py +120 -0
- omlish/lang/classes/test/__init__.py +0 -0
- omlish/lang/classes/test/test_abstract.py +89 -0
- omlish/lang/classes/test/test_restrict.py +71 -0
- omlish/lang/classes/test/test_simple.py +58 -0
- omlish/lang/classes/test/test_virtual.py +72 -0
- omlish/lang/classes/virtual.py +130 -0
- omlish/lang/clsdct.py +67 -0
- omlish/lang/cmp.py +63 -0
- omlish/lang/contextmanagers.py +249 -0
- omlish/lang/datetimes.py +67 -0
- omlish/lang/descriptors.py +52 -0
- omlish/lang/functions.py +126 -0
- omlish/lang/imports.py +153 -0
- omlish/lang/iterables.py +54 -0
- omlish/lang/maybes.py +136 -0
- omlish/lang/objects.py +103 -0
- omlish/lang/resolving.py +50 -0
- omlish/lang/strings.py +128 -0
- omlish/lang/typing.py +92 -0
- omlish/libc.py +532 -0
- omlish/logs/__init__.py +9 -0
- omlish/logs/_abc.py +247 -0
- omlish/logs/configs.py +62 -0
- omlish/logs/filters.py +9 -0
- omlish/logs/formatters.py +67 -0
- omlish/logs/utils.py +20 -0
- omlish/marshal/__init__.py +52 -0
- omlish/marshal/any.py +25 -0
- omlish/marshal/base.py +201 -0
- omlish/marshal/base64.py +25 -0
- omlish/marshal/dataclasses.py +115 -0
- omlish/marshal/datetimes.py +90 -0
- omlish/marshal/enums.py +43 -0
- omlish/marshal/exceptions.py +7 -0
- omlish/marshal/factories.py +129 -0
- omlish/marshal/global_.py +33 -0
- omlish/marshal/iterables.py +57 -0
- omlish/marshal/mappings.py +66 -0
- omlish/marshal/naming.py +17 -0
- omlish/marshal/objects.py +106 -0
- omlish/marshal/optionals.py +49 -0
- omlish/marshal/polymorphism.py +147 -0
- omlish/marshal/primitives.py +43 -0
- omlish/marshal/registries.py +57 -0
- omlish/marshal/standard.py +80 -0
- omlish/marshal/utils.py +23 -0
- omlish/marshal/uuids.py +29 -0
- omlish/marshal/values.py +30 -0
- omlish/math.py +184 -0
- omlish/os.py +32 -0
- omlish/reflect.py +359 -0
- omlish/replserver/__init__.py +5 -0
- omlish/replserver/__main__.py +4 -0
- omlish/replserver/console.py +247 -0
- omlish/replserver/server.py +146 -0
- omlish/runmodule.py +28 -0
- omlish/stats.py +342 -0
- omlish/term.py +222 -0
- omlish/testing/__init__.py +7 -0
- omlish/testing/pydevd.py +225 -0
- omlish/testing/pytest/__init__.py +8 -0
- omlish/testing/pytest/helpers.py +35 -0
- omlish/testing/pytest/inject/__init__.py +1 -0
- omlish/testing/pytest/inject/harness.py +159 -0
- omlish/testing/pytest/plugins/__init__.py +20 -0
- omlish/testing/pytest/plugins/_registry.py +6 -0
- omlish/testing/pytest/plugins/logging.py +13 -0
- omlish/testing/pytest/plugins/pycharm.py +54 -0
- omlish/testing/pytest/plugins/repeat.py +19 -0
- omlish/testing/pytest/plugins/skips.py +32 -0
- omlish/testing/pytest/plugins/spacing.py +19 -0
- omlish/testing/pytest/plugins/switches.py +70 -0
- omlish/testing/testing.py +102 -0
- omlish/text/__init__.py +0 -0
- omlish/text/delimit.py +171 -0
- omlish/text/indent.py +50 -0
- omlish/text/parts.py +265 -0
- omlish-0.0.0.dev1.dist-info/LICENSE +21 -0
- omlish-0.0.0.dev1.dist-info/METADATA +17 -0
- omlish-0.0.0.dev1.dist-info/RECORD +187 -0
- omlish-0.0.0.dev1.dist-info/WHEEL +5 -0
- omlish-0.0.0.dev1.dist-info/top_level.txt +1 -0
omlish/lang/cached.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- integrate / expose with collections.cache
|
|
4
|
+
- weakrefs (selectable by arg)
|
|
5
|
+
- locks
|
|
6
|
+
"""
|
|
7
|
+
import dataclasses as dc
|
|
8
|
+
import functools
|
|
9
|
+
import inspect
|
|
10
|
+
import typing as ta
|
|
11
|
+
|
|
12
|
+
from .contextmanagers import DefaultLockable
|
|
13
|
+
from .contextmanagers import default_lock
|
|
14
|
+
from .functions import unwrap_func
|
|
15
|
+
from .functions import unwrap_func_with_partials
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
P = ta.ParamSpec('P')
|
|
19
|
+
T = ta.TypeVar('T')
|
|
20
|
+
|
|
21
|
+
_IGNORE = object()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _nullary_cache_keyer():
|
|
25
|
+
return ()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _simple_cache_keyer(*args, **kwargs):
|
|
29
|
+
return (args, tuple(sorted(kwargs.items())))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _make_cache_keyer(fn, *, simple=False, bound=False):
|
|
33
|
+
if simple:
|
|
34
|
+
return _simple_cache_keyer
|
|
35
|
+
|
|
36
|
+
fn, partials = unwrap_func_with_partials(fn)
|
|
37
|
+
|
|
38
|
+
if inspect.isgeneratorfunction(fn) or inspect.iscoroutinefunction(fn):
|
|
39
|
+
raise TypeError(fn)
|
|
40
|
+
|
|
41
|
+
sig = inspect.signature(fn)
|
|
42
|
+
sig_params = list(sig.parameters.values())[1 if bound else 0:]
|
|
43
|
+
if not sig_params:
|
|
44
|
+
return _nullary_cache_keyer
|
|
45
|
+
|
|
46
|
+
ns = {}
|
|
47
|
+
src_params = []
|
|
48
|
+
src_vals = []
|
|
49
|
+
kwargs_name = None
|
|
50
|
+
render_pos_only_separator = False
|
|
51
|
+
render_kw_only_separator = True
|
|
52
|
+
for p in sig_params:
|
|
53
|
+
formatted = p.name
|
|
54
|
+
if p.default is not inspect.Parameter.empty:
|
|
55
|
+
ns[p.name] = p.default
|
|
56
|
+
formatted = f'{formatted}={formatted}'
|
|
57
|
+
kind = p.kind
|
|
58
|
+
if kind == inspect.Parameter.VAR_POSITIONAL:
|
|
59
|
+
formatted = '*' + formatted
|
|
60
|
+
elif kind == inspect.Parameter.VAR_KEYWORD:
|
|
61
|
+
formatted = '**' + formatted
|
|
62
|
+
if kind == inspect.Parameter.POSITIONAL_ONLY:
|
|
63
|
+
render_pos_only_separator = True
|
|
64
|
+
elif render_pos_only_separator:
|
|
65
|
+
src_params.append('/')
|
|
66
|
+
render_pos_only_separator = False
|
|
67
|
+
if kind == inspect.Parameter.VAR_POSITIONAL:
|
|
68
|
+
render_kw_only_separator = False
|
|
69
|
+
elif kind == inspect.Parameter.KEYWORD_ONLY and render_kw_only_separator:
|
|
70
|
+
src_params.append('*')
|
|
71
|
+
render_kw_only_separator = False
|
|
72
|
+
src_params.append(formatted)
|
|
73
|
+
if kind == inspect.Parameter.VAR_KEYWORD:
|
|
74
|
+
kwargs_name = p.name
|
|
75
|
+
else:
|
|
76
|
+
src_vals.append(p.name)
|
|
77
|
+
if render_pos_only_separator:
|
|
78
|
+
src_params.append('/')
|
|
79
|
+
|
|
80
|
+
kwa = f', __builtins__.tuple(__builtins__.sorted({kwargs_name}.items()))' if kwargs_name else ''
|
|
81
|
+
rendered = (
|
|
82
|
+
f'def __func__({", ".join(src_params)}):\n'
|
|
83
|
+
f' return ({", ".join(src_vals)}{kwa})\n'
|
|
84
|
+
)
|
|
85
|
+
exec(rendered, ns)
|
|
86
|
+
|
|
87
|
+
kfn = ns['__func__']
|
|
88
|
+
for part in partials[::-1]:
|
|
89
|
+
kfn = functools.partial(kfn, *part.args, **part.keywords)
|
|
90
|
+
|
|
91
|
+
return kfn
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class _CachedFunction(ta.Generic[T]):
|
|
95
|
+
@dc.dataclass(frozen=True)
|
|
96
|
+
class Opts:
|
|
97
|
+
map_maker: ta.Callable[[], ta.MutableMapping] = dict
|
|
98
|
+
simple_key: bool = False
|
|
99
|
+
lock: DefaultLockable = None
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
fn: ta.Callable[P, T],
|
|
104
|
+
*,
|
|
105
|
+
opts: Opts = Opts(),
|
|
106
|
+
keyer: ta.Callable[..., tuple] | None = None,
|
|
107
|
+
values: ta.MutableMapping | None = None,
|
|
108
|
+
value_fn: ta.Optional[ta.Callable[P, T]] = None,
|
|
109
|
+
) -> None:
|
|
110
|
+
super().__init__()
|
|
111
|
+
|
|
112
|
+
self._fn = fn
|
|
113
|
+
self._opts = opts
|
|
114
|
+
self._keyer = keyer if keyer is not None else _make_cache_keyer(fn, simple=opts.simple_key)
|
|
115
|
+
|
|
116
|
+
self._lock = default_lock(opts.lock, False)() if opts.lock is not None else None
|
|
117
|
+
self._values = values if values is not None else opts.map_maker()
|
|
118
|
+
self._value_fn = value_fn if value_fn is not None else fn
|
|
119
|
+
functools.update_wrapper(self, fn)
|
|
120
|
+
|
|
121
|
+
def reset(self) -> None:
|
|
122
|
+
self._values = {}
|
|
123
|
+
|
|
124
|
+
def __bool__(self) -> bool:
|
|
125
|
+
raise TypeError
|
|
126
|
+
|
|
127
|
+
def __call__(self, *args, **kwargs) -> T:
|
|
128
|
+
k = self._keyer(*args, **kwargs)
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
return self._values[k]
|
|
132
|
+
except KeyError:
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
if self._lock is not None:
|
|
136
|
+
with self._lock:
|
|
137
|
+
try:
|
|
138
|
+
return self._values[k]
|
|
139
|
+
except KeyError:
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
value = self._value_fn(*args, **kwargs)
|
|
143
|
+
|
|
144
|
+
else:
|
|
145
|
+
value = self._value_fn(*args, **kwargs)
|
|
146
|
+
|
|
147
|
+
self._values[k] = value
|
|
148
|
+
return value
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class _CachedFunctionDescriptor(_CachedFunction[T]):
|
|
152
|
+
|
|
153
|
+
def __init__(
|
|
154
|
+
self,
|
|
155
|
+
fn: ta.Callable[P, T],
|
|
156
|
+
scope: ta.Any,
|
|
157
|
+
*,
|
|
158
|
+
instance: ta.Any = None,
|
|
159
|
+
owner: ta.Any = None,
|
|
160
|
+
name: ta.Optional[str] = None,
|
|
161
|
+
**kwargs
|
|
162
|
+
) -> None:
|
|
163
|
+
super().__init__(fn, **kwargs)
|
|
164
|
+
|
|
165
|
+
self._scope = scope
|
|
166
|
+
self._instance = instance
|
|
167
|
+
self._owner = owner
|
|
168
|
+
self._name = name if name is not None else unwrap_func(fn).__name__
|
|
169
|
+
self._bound_keyer = None
|
|
170
|
+
|
|
171
|
+
def __get__(self, instance, owner=None):
|
|
172
|
+
scope = self._scope
|
|
173
|
+
if owner is self._owner and (instance is self._instance or scope is classmethod):
|
|
174
|
+
return self
|
|
175
|
+
|
|
176
|
+
fn = self._fn
|
|
177
|
+
name = self._name
|
|
178
|
+
bound_fn = fn.__get__(instance, owner)
|
|
179
|
+
if self._bound_keyer is None:
|
|
180
|
+
self._bound_keyer = _make_cache_keyer(fn, simple=self._opts.simple_key, bound=True)
|
|
181
|
+
|
|
182
|
+
bound = self.__class__(
|
|
183
|
+
fn,
|
|
184
|
+
scope,
|
|
185
|
+
opts=self._opts,
|
|
186
|
+
instance=instance,
|
|
187
|
+
owner=owner,
|
|
188
|
+
name=name,
|
|
189
|
+
keyer=self._bound_keyer,
|
|
190
|
+
# values=None if scope is classmethod else self._values,
|
|
191
|
+
value_fn=bound_fn,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if scope is classmethod and owner is not None:
|
|
195
|
+
setattr(owner, name, bound)
|
|
196
|
+
elif instance is not None:
|
|
197
|
+
instance.__dict__[name] = bound
|
|
198
|
+
|
|
199
|
+
return bound
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def cached_function(fn=None, **kwargs):
|
|
203
|
+
if fn is None:
|
|
204
|
+
return functools.partial(cached_function, **kwargs)
|
|
205
|
+
opts = _CachedFunction.Opts(**kwargs)
|
|
206
|
+
if isinstance(fn, staticmethod):
|
|
207
|
+
return _CachedFunction(fn, opts=opts, value_fn=unwrap_func(fn))
|
|
208
|
+
scope = classmethod if isinstance(fn, classmethod) else None
|
|
209
|
+
return _CachedFunctionDescriptor(fn, scope, opts=opts)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
cached_function = cached_function
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
##
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class _CachedProperty:
|
|
219
|
+
def __init__(
|
|
220
|
+
self,
|
|
221
|
+
fn,
|
|
222
|
+
*,
|
|
223
|
+
name=None,
|
|
224
|
+
ignore_if=lambda _: False,
|
|
225
|
+
clear_on_init=False,
|
|
226
|
+
):
|
|
227
|
+
super().__init__()
|
|
228
|
+
if isinstance(fn, property):
|
|
229
|
+
fn = fn.fget
|
|
230
|
+
self._fn = fn
|
|
231
|
+
self._ignore_if = ignore_if
|
|
232
|
+
self._name = name
|
|
233
|
+
self._clear_on_init = clear_on_init
|
|
234
|
+
|
|
235
|
+
def __set_name__(self, owner, name):
|
|
236
|
+
if self._name is None:
|
|
237
|
+
self._name = name
|
|
238
|
+
|
|
239
|
+
def __get__(self, instance, owner=None):
|
|
240
|
+
if instance is None:
|
|
241
|
+
return self
|
|
242
|
+
if self._name is None:
|
|
243
|
+
raise TypeError(self)
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
return instance.__dict__[self._name]
|
|
247
|
+
except KeyError:
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
value = self._fn.__get__(instance, owner)()
|
|
251
|
+
if value is _IGNORE:
|
|
252
|
+
return None
|
|
253
|
+
instance.__dict__[self._name] = value
|
|
254
|
+
return value
|
|
255
|
+
|
|
256
|
+
def __set__(self, instance, value):
|
|
257
|
+
if self._ignore_if(value):
|
|
258
|
+
return
|
|
259
|
+
if instance.__dict__[self._name] == value:
|
|
260
|
+
return
|
|
261
|
+
raise TypeError(self._name)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def cached_property(fn=None, **kwargs):
|
|
265
|
+
if fn is None:
|
|
266
|
+
return functools.partial(cached_property, **kwargs)
|
|
267
|
+
return _CachedProperty(fn, **kwargs)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .abstract import Abstract # noqa
|
|
2
|
+
from .abstract import is_abstract # noqa
|
|
3
|
+
from .abstract import is_abstract_class # noqa
|
|
4
|
+
from .abstract import is_abstract_method # noqa
|
|
5
|
+
from .abstract import make_abstract # noqa
|
|
6
|
+
from .restrict import Final # noqa
|
|
7
|
+
from .restrict import FinalException # noqa
|
|
8
|
+
from .restrict import NoBool # noqa
|
|
9
|
+
from .restrict import NotInstantiable # noqa
|
|
10
|
+
from .restrict import NotPicklable # noqa
|
|
11
|
+
from .restrict import PackageSealed # noqa
|
|
12
|
+
from .restrict import Sealed # noqa
|
|
13
|
+
from .restrict import SealedException # noqa
|
|
14
|
+
from .restrict import no_bool # noqa
|
|
15
|
+
from .simple import LazySingleton # noqa
|
|
16
|
+
from .simple import Marker # noqa
|
|
17
|
+
from .simple import Namespace # noqa
|
|
18
|
+
from .simple import SimpleMetaDict # noqa
|
|
19
|
+
from .simple import Singleton # noqa
|
|
20
|
+
from .virtual import Callable # noqa
|
|
21
|
+
from .virtual import Descriptor # noqa
|
|
22
|
+
from .virtual import Picklable # noqa
|
|
23
|
+
from .virtual import Virtual # noqa
|
|
24
|
+
from .virtual import virtual_check # noqa
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing as ta
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
T = ta.TypeVar('T')
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
_DISABLE_CHECKS = False
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def make_abstract(obj: T) -> T:
|
|
12
|
+
if callable(obj):
|
|
13
|
+
return ta.cast(T, abc.abstractmethod(obj))
|
|
14
|
+
elif isinstance(obj, property):
|
|
15
|
+
return ta.cast(T, property(
|
|
16
|
+
abc.abstractmethod(obj.fget) if obj.fget is not None else None,
|
|
17
|
+
abc.abstractmethod(obj.fset) if obj.fset is not None else None,
|
|
18
|
+
abc.abstractmethod(obj.fdel) if obj.fdel is not None else None,
|
|
19
|
+
))
|
|
20
|
+
elif isinstance(obj, (classmethod, staticmethod)):
|
|
21
|
+
return ta.cast(T, type(obj)(abc.abstractmethod(obj.__func__)))
|
|
22
|
+
else:
|
|
23
|
+
return obj
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Abstract(abc.ABC):
|
|
27
|
+
__slots__ = ()
|
|
28
|
+
|
|
29
|
+
def __forceabstract__(self):
|
|
30
|
+
raise TypeError
|
|
31
|
+
|
|
32
|
+
setattr(__forceabstract__, '__isabstractmethod__', True)
|
|
33
|
+
|
|
34
|
+
def __init_subclass__(cls, **kwargs) -> None:
|
|
35
|
+
if Abstract in cls.__bases__:
|
|
36
|
+
cls.__forceabstract__ = Abstract.__forceabstract__ # type: ignore
|
|
37
|
+
else:
|
|
38
|
+
cls.__forceabstract__ = False # type: ignore
|
|
39
|
+
|
|
40
|
+
super().__init_subclass__(**kwargs)
|
|
41
|
+
|
|
42
|
+
if not _DISABLE_CHECKS and Abstract not in cls.__bases__:
|
|
43
|
+
ams = {a for a, o in cls.__dict__.items() if is_abstract_method(o)}
|
|
44
|
+
seen = set(cls.__dict__)
|
|
45
|
+
for b in cls.__bases__:
|
|
46
|
+
ams.update(set(getattr(b, '__abstractmethods__', [])) - seen)
|
|
47
|
+
seen.update(dir(b))
|
|
48
|
+
if ams:
|
|
49
|
+
raise TypeError(
|
|
50
|
+
f'Cannot subclass abstract class {cls.__name__} with abstract methods'
|
|
51
|
+
f'{", ".join(map(str, sorted(ams)))}'
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def is_abstract_method(obj: ta.Any) -> bool:
|
|
56
|
+
return bool(getattr(obj, '__isabstractmethod__', False))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_abstract_class(obj: ta.Any) -> bool:
|
|
60
|
+
if bool(getattr(obj, '__abstractmethods__', [])):
|
|
61
|
+
return True
|
|
62
|
+
if isinstance(obj, type):
|
|
63
|
+
if Abstract in obj.__bases__:
|
|
64
|
+
return True
|
|
65
|
+
if (
|
|
66
|
+
Abstract in obj.__mro__
|
|
67
|
+
and getattr(obj.__dict__.get('__forceabstract__', None), '__isabstractmethod__', False)
|
|
68
|
+
):
|
|
69
|
+
return True
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def is_abstract(obj: ta.Any) -> bool:
|
|
74
|
+
return is_abstract_method(obj) or is_abstract_class(obj)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import typing as ta
|
|
3
|
+
|
|
4
|
+
from .abstract import Abstract
|
|
5
|
+
from .abstract import is_abstract
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FinalException(TypeError):
|
|
12
|
+
|
|
13
|
+
def __init__(self, _type: ta.Type) -> None:
|
|
14
|
+
super().__init__()
|
|
15
|
+
|
|
16
|
+
self._type = _type
|
|
17
|
+
|
|
18
|
+
def __repr__(self) -> str:
|
|
19
|
+
return f'{type(self).__name__}({self._type})'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Final(Abstract):
|
|
23
|
+
__slots__ = ()
|
|
24
|
+
|
|
25
|
+
def __init_subclass__(cls, **kwargs) -> None:
|
|
26
|
+
super().__init_subclass__(**kwargs)
|
|
27
|
+
|
|
28
|
+
abstracts: set[ta.Any] = set()
|
|
29
|
+
for base in cls.__bases__:
|
|
30
|
+
if base is Abstract:
|
|
31
|
+
raise FinalException(base)
|
|
32
|
+
elif base is Final:
|
|
33
|
+
continue
|
|
34
|
+
elif Final in base.__mro__:
|
|
35
|
+
raise FinalException(base)
|
|
36
|
+
else:
|
|
37
|
+
abstracts.update(getattr(base, '__abstractmethods__', []))
|
|
38
|
+
|
|
39
|
+
for a in abstracts:
|
|
40
|
+
try:
|
|
41
|
+
v = cls.__dict__[a]
|
|
42
|
+
except KeyError:
|
|
43
|
+
raise FinalException(a)
|
|
44
|
+
if is_abstract(v):
|
|
45
|
+
raise FinalException(a)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SealedException(TypeError):
|
|
52
|
+
|
|
53
|
+
def __init__(self, _type) -> None:
|
|
54
|
+
super().__init__()
|
|
55
|
+
|
|
56
|
+
self._type = _type
|
|
57
|
+
|
|
58
|
+
def __repr__(self) -> str:
|
|
59
|
+
return f'{type(self).__name__}({self._type})'
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Sealed:
|
|
63
|
+
__slots__ = ()
|
|
64
|
+
|
|
65
|
+
def __init_subclass__(cls, **kwargs) -> None:
|
|
66
|
+
for base in cls.__bases__:
|
|
67
|
+
if base is not Abstract:
|
|
68
|
+
if Sealed in base.__bases__:
|
|
69
|
+
if cls.__module__ != base.__module__:
|
|
70
|
+
raise SealedException(base)
|
|
71
|
+
super().__init_subclass__(**kwargs)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class PackageSealed:
|
|
75
|
+
__slots__ = ()
|
|
76
|
+
|
|
77
|
+
def __init_subclass__(cls, **kwargs) -> None:
|
|
78
|
+
for base in cls.__bases__:
|
|
79
|
+
if base is not Abstract:
|
|
80
|
+
if PackageSealed in base.__bases__:
|
|
81
|
+
if cls.__module__.split('.')[:-1] != base.__module__.split('.')[:-1]:
|
|
82
|
+
raise SealedException(base)
|
|
83
|
+
super().__init_subclass__(**kwargs)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
##
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class NotInstantiable(Abstract):
|
|
90
|
+
__slots__ = ()
|
|
91
|
+
|
|
92
|
+
def __new__(cls, *args, **kwargs) -> ta.NoReturn:
|
|
93
|
+
raise TypeError
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class NotPicklable:
|
|
97
|
+
__slots__ = ()
|
|
98
|
+
|
|
99
|
+
def __getstate__(self) -> ta.NoReturn:
|
|
100
|
+
raise TypeError
|
|
101
|
+
|
|
102
|
+
def __setstate__(self, state) -> ta.NoReturn:
|
|
103
|
+
raise TypeError
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
##
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class NoBool:
|
|
110
|
+
|
|
111
|
+
def __bool__(self) -> bool:
|
|
112
|
+
raise TypeError
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class _NoBoolDescriptor:
|
|
116
|
+
|
|
117
|
+
def __init__(self, fn, instance=None, owner=None) -> None:
|
|
118
|
+
super().__init__()
|
|
119
|
+
self._fn = fn
|
|
120
|
+
self._instance = instance
|
|
121
|
+
self._owner = owner
|
|
122
|
+
functools.update_wrapper(self, fn)
|
|
123
|
+
|
|
124
|
+
def __bool__(self) -> bool:
|
|
125
|
+
raise TypeError
|
|
126
|
+
|
|
127
|
+
def __get__(self, instance, owner=None):
|
|
128
|
+
if instance is self._instance and owner is self._owner:
|
|
129
|
+
return self
|
|
130
|
+
return _NoBoolDescriptor(self._fn.__get__(instance, owner), instance, owner)
|
|
131
|
+
|
|
132
|
+
def __call__(self, *args, **kwargs):
|
|
133
|
+
return self._fn(*args, **kwargs)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def no_bool(fn):
|
|
137
|
+
return _NoBoolDescriptor(fn)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import functools
|
|
3
|
+
import threading
|
|
4
|
+
import typing as ta
|
|
5
|
+
|
|
6
|
+
from .restrict import Final
|
|
7
|
+
from .restrict import NotInstantiable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
K = ta.TypeVar('K')
|
|
11
|
+
V = ta.TypeVar('V')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _Namespace(Final):
|
|
18
|
+
|
|
19
|
+
def __mro_entries__(self, bases, **kwargs):
|
|
20
|
+
return (Final, NotInstantiable)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
Namespace: type = _Namespace() # type: ignore
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_MARKER_NAMESPACE_KEYS: ta.Optional[set[str]] = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _MarkerMeta(abc.ABCMeta):
|
|
33
|
+
|
|
34
|
+
def __new__(mcls, name, bases, namespace):
|
|
35
|
+
global _MARKER_NAMESPACE_KEYS
|
|
36
|
+
if _MARKER_NAMESPACE_KEYS is None:
|
|
37
|
+
if not (namespace.get('__module__') == __name__ and name == 'Marker'):
|
|
38
|
+
raise RuntimeError
|
|
39
|
+
_MARKER_NAMESPACE_KEYS = set(namespace)
|
|
40
|
+
else:
|
|
41
|
+
if set(namespace) - _MARKER_NAMESPACE_KEYS:
|
|
42
|
+
raise TypeError('Markers must not include contents. Did you mean to use Namespace?')
|
|
43
|
+
if Final not in bases:
|
|
44
|
+
bases += (Final,)
|
|
45
|
+
return super().__new__(mcls, name, bases, namespace)
|
|
46
|
+
|
|
47
|
+
def __instancecheck__(self, instance):
|
|
48
|
+
return instance is self
|
|
49
|
+
|
|
50
|
+
def __repr__(cls) -> str:
|
|
51
|
+
return f'<{cls.__name__}>'
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Marker(NotInstantiable, metaclass=_MarkerMeta):
|
|
55
|
+
"""A marker."""
|
|
56
|
+
|
|
57
|
+
__slots__ = ()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SimpleMetaDict(dict):
|
|
64
|
+
|
|
65
|
+
def update(self, m: ta.Mapping[K, V], **kwargs: V) -> None: # type: ignore
|
|
66
|
+
for k, v in m.items():
|
|
67
|
+
self[k] = v
|
|
68
|
+
for k, v in kwargs.items(): # type: ignore
|
|
69
|
+
self[k] = v
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
_SINGLETON_INSTANCE_ATTR = '__singleton_instance__'
|
|
76
|
+
_SINGLETON_LOCK = threading.RLock()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _set_singleton_instance(inst):
|
|
80
|
+
cls = type(inst)
|
|
81
|
+
if _SINGLETON_INSTANCE_ATTR in cls.__dict__:
|
|
82
|
+
raise TypeError(cls)
|
|
83
|
+
|
|
84
|
+
inst.__init__()
|
|
85
|
+
old_init = cls.__init__
|
|
86
|
+
|
|
87
|
+
@functools.wraps(old_init)
|
|
88
|
+
def new_init(self):
|
|
89
|
+
if type(self) is not cls:
|
|
90
|
+
old_init(self) # noqa
|
|
91
|
+
|
|
92
|
+
setattr(cls, '__init__', new_init)
|
|
93
|
+
setattr(cls, _SINGLETON_INSTANCE_ATTR, inst)
|
|
94
|
+
|
|
95
|
+
return inst
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Singleton:
|
|
99
|
+
|
|
100
|
+
def __new__(cls):
|
|
101
|
+
return cls.__dict__[_SINGLETON_INSTANCE_ATTR]
|
|
102
|
+
|
|
103
|
+
def __init_subclass__(cls, **kwargs):
|
|
104
|
+
super().__init_subclass__(**kwargs)
|
|
105
|
+
_set_singleton_instance(super().__new__(cls)) # noqa
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class LazySingleton:
|
|
109
|
+
|
|
110
|
+
def __new__(cls):
|
|
111
|
+
try:
|
|
112
|
+
return cls.__dict__[_SINGLETON_INSTANCE_ATTR]
|
|
113
|
+
except KeyError:
|
|
114
|
+
pass
|
|
115
|
+
with _SINGLETON_LOCK:
|
|
116
|
+
try:
|
|
117
|
+
return cls.__dict__[_SINGLETON_INSTANCE_ATTR]
|
|
118
|
+
except KeyError:
|
|
119
|
+
pass
|
|
120
|
+
return _set_singleton_instance(super().__new__(cls))
|
|
File without changes
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from ..abstract import Abstract
|
|
6
|
+
from ..abstract import is_abstract
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_abstract():
|
|
10
|
+
class C(Abstract):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
class D(C):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
class E(D, Abstract):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
class F(E):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
with pytest.raises(TypeError):
|
|
23
|
+
C()
|
|
24
|
+
D()
|
|
25
|
+
with pytest.raises(TypeError):
|
|
26
|
+
E()
|
|
27
|
+
F()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_abstract2():
|
|
31
|
+
class O(Abstract):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
assert is_abstract(O)
|
|
35
|
+
|
|
36
|
+
with pytest.raises(TypeError):
|
|
37
|
+
O()
|
|
38
|
+
|
|
39
|
+
class A(Abstract):
|
|
40
|
+
@abc.abstractmethod
|
|
41
|
+
def f(self):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
assert is_abstract(A)
|
|
45
|
+
|
|
46
|
+
with pytest.raises(TypeError):
|
|
47
|
+
A() # type: ignore
|
|
48
|
+
|
|
49
|
+
class B(A, Abstract):
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
with pytest.raises(TypeError):
|
|
53
|
+
B() # type: ignore
|
|
54
|
+
|
|
55
|
+
class C(B):
|
|
56
|
+
f = 0
|
|
57
|
+
|
|
58
|
+
assert C()
|
|
59
|
+
|
|
60
|
+
with pytest.raises(TypeError):
|
|
61
|
+
class D(A):
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_is_abstract():
|
|
66
|
+
class A(Abstract):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
assert is_abstract(A)
|
|
70
|
+
|
|
71
|
+
class B(A):
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
assert not is_abstract(B)
|
|
75
|
+
|
|
76
|
+
class C(Abstract):
|
|
77
|
+
|
|
78
|
+
def __init_subclass__(cls, **kwargs):
|
|
79
|
+
super().__init_subclass__(**kwargs)
|
|
80
|
+
if cls.__name__ == 'D':
|
|
81
|
+
assert is_abstract(cls)
|
|
82
|
+
else:
|
|
83
|
+
assert not is_abstract(cls)
|
|
84
|
+
|
|
85
|
+
class D(C, Abstract):
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
class E(D):
|
|
89
|
+
pass
|