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/reflect.py
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- uniform collection isinstance - items() for mappings, iter() for other
|
|
4
|
+
- also check instance type in isinstance not just items lol
|
|
5
|
+
- ta.Generic in mro causing trouble - omit? no longer 1:1
|
|
6
|
+
- cache this shit, esp generic_mro shit
|
|
7
|
+
- cache __hash__ in Generic/Union
|
|
8
|
+
"""
|
|
9
|
+
import collections.abc
|
|
10
|
+
import typing as ta
|
|
11
|
+
import types
|
|
12
|
+
|
|
13
|
+
from . import c3
|
|
14
|
+
from . import lang
|
|
15
|
+
|
|
16
|
+
if ta.TYPE_CHECKING:
|
|
17
|
+
from .collections import cache
|
|
18
|
+
else:
|
|
19
|
+
cache = lang.proxy_import('.collections.cache', __package__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_NoneType = types.NoneType # type: ignore
|
|
23
|
+
|
|
24
|
+
_NONE_TYPE_FROZENSET: ta.FrozenSet['Type'] = frozenset([_NoneType])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_GenericAlias = ta._GenericAlias # type: ignore # noqa
|
|
28
|
+
_SpecialGenericAlias = ta._SpecialGenericAlias # type: ignore # noqa
|
|
29
|
+
_UnionGenericAlias = ta._UnionGenericAlias # type: ignore # noqa
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class _Special(ta.NamedTuple):
|
|
36
|
+
name: str
|
|
37
|
+
alias: _SpecialGenericAlias # type: ignore
|
|
38
|
+
origin: type
|
|
39
|
+
nparams: int
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
_KNOWN_SPECIALS = [
|
|
43
|
+
_Special(
|
|
44
|
+
v._name, # noqa
|
|
45
|
+
v,
|
|
46
|
+
v.__origin__,
|
|
47
|
+
v._nparams, # noqa
|
|
48
|
+
)
|
|
49
|
+
for v in ta.__dict__.values()
|
|
50
|
+
if isinstance(v, _SpecialGenericAlias)
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
_KNOWN_SPECIALS_BY_NAME = {s.name: s for s in _KNOWN_SPECIALS}
|
|
54
|
+
_KNOWN_SPECIALS_BY_ALIAS = {s.alias: s for s in _KNOWN_SPECIALS}
|
|
55
|
+
_KNOWN_SPECIALS_BY_ORIGIN = {s.origin: s for s in _KNOWN_SPECIALS}
|
|
56
|
+
|
|
57
|
+
_KNOWN_SPECIAL_TYPE_VARS = tuple(
|
|
58
|
+
ta.TypeVar(f'_{i}') # noqa
|
|
59
|
+
for i in range(max(s.nparams for s in _KNOWN_SPECIALS) + 1)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
from types import get_original_bases # type: ignore
|
|
68
|
+
except ImportError:
|
|
69
|
+
def get_original_bases(cls, /):
|
|
70
|
+
try:
|
|
71
|
+
return cls.__dict__.get('__orig_bases__', cls.__bases__)
|
|
72
|
+
except AttributeError:
|
|
73
|
+
raise TypeError(f'Expected an instance of type, not {type(cls).__name__!r}') from None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_params(obj: ta.Any) -> tuple[ta.TypeVar, ...]:
|
|
77
|
+
if isinstance(obj, type):
|
|
78
|
+
if issubclass(obj, ta.Generic): # type: ignore
|
|
79
|
+
return obj.__dict__.get('__parameters__', ()) # noqa
|
|
80
|
+
|
|
81
|
+
if (ks := _KNOWN_SPECIALS_BY_ORIGIN.get(obj)) is not None:
|
|
82
|
+
return _KNOWN_SPECIAL_TYPE_VARS[:ks.nparams]
|
|
83
|
+
|
|
84
|
+
oty = type(obj)
|
|
85
|
+
|
|
86
|
+
if oty is _GenericAlias or oty is ta.GenericAlias: # type: ignore # noqa
|
|
87
|
+
return obj.__dict__.get('__parameters__', ()) # noqa
|
|
88
|
+
|
|
89
|
+
raise TypeError(obj)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def is_union_type(cls: ta.Any) -> bool:
|
|
93
|
+
if hasattr(ta, 'UnionType'):
|
|
94
|
+
return ta.get_origin(cls) in {ta.Union, getattr(ta, 'UnionType')}
|
|
95
|
+
else:
|
|
96
|
+
return ta.get_origin(cls) in {ta.Union}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
##
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
Type = ta.Union[
|
|
103
|
+
type,
|
|
104
|
+
ta.TypeVar,
|
|
105
|
+
'NewType',
|
|
106
|
+
'Union',
|
|
107
|
+
'Generic',
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class Union(ta.NamedTuple):
|
|
112
|
+
args: ta.FrozenSet[Type]
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def is_optional(self) -> bool:
|
|
116
|
+
return _NoneType in self.args
|
|
117
|
+
|
|
118
|
+
def without_none(self) -> Type:
|
|
119
|
+
if _NoneType not in self.args:
|
|
120
|
+
return self
|
|
121
|
+
rem = self.args - _NONE_TYPE_FROZENSET
|
|
122
|
+
if len(rem) == 1:
|
|
123
|
+
return next(iter(rem))
|
|
124
|
+
return Union(rem)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class Generic(ta.NamedTuple):
|
|
128
|
+
cls: type
|
|
129
|
+
args: tuple[Type, ...] # map[int, V] = (int, V) | map[T, T] = (T, T)
|
|
130
|
+
params: tuple[ta.TypeVar, ...] # map[int, V] = (_0, _1) | map[T, T] = (_0, _1)
|
|
131
|
+
# params2: tuple[ta.TypeVar, ...] # map[int, V] = (V,) | map[T, T] = (T,)
|
|
132
|
+
obj: ta.Any
|
|
133
|
+
|
|
134
|
+
def __repr__(self):
|
|
135
|
+
return (
|
|
136
|
+
f'{self.__class__.__name__}('
|
|
137
|
+
f'cls={self.cls.__name__}, '
|
|
138
|
+
f'args={self.args!r}, '
|
|
139
|
+
f'params={self.params!r})'
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class NewType(ta.NamedTuple):
|
|
144
|
+
obj: ta.Any
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
TYPES: tuple[type, ...] = (
|
|
148
|
+
type,
|
|
149
|
+
ta.TypeVar,
|
|
150
|
+
Union,
|
|
151
|
+
Generic,
|
|
152
|
+
NewType,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def type_(obj: ta.Any) -> Type:
|
|
157
|
+
if isinstance(obj, (Union, Generic, ta.TypeVar, NewType)): # noqa
|
|
158
|
+
return obj
|
|
159
|
+
|
|
160
|
+
oty = type(obj)
|
|
161
|
+
|
|
162
|
+
if oty is _UnionGenericAlias or oty is types.UnionType:
|
|
163
|
+
return Union(frozenset(type_(a) for a in ta.get_args(obj)))
|
|
164
|
+
|
|
165
|
+
if isinstance(obj, ta.NewType): # noqa
|
|
166
|
+
return NewType(oty)
|
|
167
|
+
|
|
168
|
+
if oty is _GenericAlias or oty is ta.GenericAlias: # type: ignore # noqa
|
|
169
|
+
origin = ta.get_origin(obj)
|
|
170
|
+
args = ta.get_args(obj)
|
|
171
|
+
if origin is ta.Generic:
|
|
172
|
+
params = args
|
|
173
|
+
else:
|
|
174
|
+
params = get_params(origin)
|
|
175
|
+
if len(args) != len(params):
|
|
176
|
+
raise TypeError(f'Mismatched {args=} and {params=} for {obj=}')
|
|
177
|
+
return Generic(
|
|
178
|
+
origin,
|
|
179
|
+
tuple(type_(a) for a in args),
|
|
180
|
+
params,
|
|
181
|
+
obj,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if isinstance(obj, type):
|
|
185
|
+
if issubclass(obj, ta.Generic): # type: ignore
|
|
186
|
+
params = get_params(obj)
|
|
187
|
+
return Generic(
|
|
188
|
+
obj,
|
|
189
|
+
params,
|
|
190
|
+
params,
|
|
191
|
+
obj,
|
|
192
|
+
)
|
|
193
|
+
return obj
|
|
194
|
+
|
|
195
|
+
if isinstance(obj, _SpecialGenericAlias):
|
|
196
|
+
if (ks := _KNOWN_SPECIALS_BY_ALIAS.get(obj)) is not None:
|
|
197
|
+
params = _KNOWN_SPECIAL_TYPE_VARS[:ks.nparams]
|
|
198
|
+
return Generic(
|
|
199
|
+
ks.origin,
|
|
200
|
+
params,
|
|
201
|
+
params,
|
|
202
|
+
obj,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
raise TypeError(obj)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
##
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def types_equivalent(l: Type, r: Type) -> bool:
|
|
212
|
+
if isinstance(l, Generic) and isinstance(r, Generic):
|
|
213
|
+
return l.cls == r.cls and l.args == r.args
|
|
214
|
+
return l == r
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def get_underlying(nt: NewType) -> Type:
|
|
218
|
+
return type_(nt.obj.__supertype__) # noqa
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def get_concrete_type(ty: Type) -> ta.Optional[type]:
|
|
222
|
+
if isinstance(ty, type):
|
|
223
|
+
return ty
|
|
224
|
+
if isinstance(ty, Generic):
|
|
225
|
+
return ty.cls
|
|
226
|
+
if isinstance(ty, NewType):
|
|
227
|
+
return get_concrete_type(get_underlying(ty))
|
|
228
|
+
if isinstance(ty, (Union, ta.TypeVar)):
|
|
229
|
+
return None
|
|
230
|
+
raise TypeError(ty)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def get_type_var_replacements(ty: Type) -> ta.Mapping[ta.TypeVar, Type]:
|
|
234
|
+
if isinstance(ty, Generic):
|
|
235
|
+
return dict(zip(ty.params, ty.args))
|
|
236
|
+
return {}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def to_annotation(ty: Type) -> ta.Any:
|
|
240
|
+
if isinstance(ty, Generic):
|
|
241
|
+
return ty.obj if ty.obj is not None else ty.cls
|
|
242
|
+
if isinstance(ty, Union):
|
|
243
|
+
return ta.Union[*tuple(to_annotation(e) for e in ty.args)]
|
|
244
|
+
if isinstance(ty, (type, ta.TypeVar, NewType)):
|
|
245
|
+
return ty
|
|
246
|
+
raise TypeError(ty)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def replace_type_vars(
|
|
250
|
+
ty: Type,
|
|
251
|
+
rpl: ta.Mapping[ta.TypeVar, Type],
|
|
252
|
+
*,
|
|
253
|
+
update_aliases: bool = False,
|
|
254
|
+
) -> Type:
|
|
255
|
+
def rec(cur):
|
|
256
|
+
if isinstance(cur, type):
|
|
257
|
+
return cur
|
|
258
|
+
if isinstance(cur, NewType):
|
|
259
|
+
return cur
|
|
260
|
+
if isinstance(cur, Generic):
|
|
261
|
+
args = tuple(rec(a) for a in cur.args)
|
|
262
|
+
if update_aliases:
|
|
263
|
+
obj = cur.obj
|
|
264
|
+
if (ops := get_params(obj)):
|
|
265
|
+
nargs = [to_annotation(rpl[p]) for p in ops]
|
|
266
|
+
if ta.get_origin(obj) is ta.Generic:
|
|
267
|
+
# FIXME: None? filter_typing_generic in get_generic_bases?
|
|
268
|
+
pass
|
|
269
|
+
else:
|
|
270
|
+
obj = cur.obj[*nargs]
|
|
271
|
+
else:
|
|
272
|
+
obj = None
|
|
273
|
+
return cur._replace(args=args, obj=obj)
|
|
274
|
+
if isinstance(cur, Union):
|
|
275
|
+
return Union(frozenset(rec(e) for e in cur.args))
|
|
276
|
+
if isinstance(cur, ta.TypeVar):
|
|
277
|
+
return rpl[cur]
|
|
278
|
+
raise TypeError(cur)
|
|
279
|
+
|
|
280
|
+
return rec(ty)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class GenericSubstitution:
|
|
284
|
+
def __init__(
|
|
285
|
+
self,
|
|
286
|
+
*,
|
|
287
|
+
update_aliases: bool = False,
|
|
288
|
+
cache_size: int = 0, # FIXME: ta.Generic isn't weakrefable..
|
|
289
|
+
) -> None:
|
|
290
|
+
super().__init__()
|
|
291
|
+
|
|
292
|
+
self._update_aliases = update_aliases
|
|
293
|
+
|
|
294
|
+
if cache_size > 0:
|
|
295
|
+
self.get_generic_bases = cache.cache(weak_keys=True, max_size=cache_size)(self.get_generic_bases) # type: ignore # noqa
|
|
296
|
+
self.generic_mro = cache.cache(weak_keys=True, max_size=cache_size)(self.generic_mro) # type: ignore
|
|
297
|
+
|
|
298
|
+
def get_generic_bases(self, ty: Type) -> tuple[Type, ...]:
|
|
299
|
+
if (cty := get_concrete_type(ty)) is not None:
|
|
300
|
+
rpl = get_type_var_replacements(ty)
|
|
301
|
+
ret: list[Type] = []
|
|
302
|
+
for b in get_original_bases(cty):
|
|
303
|
+
bty = type_(b)
|
|
304
|
+
if isinstance(bty, Generic) and isinstance(b, type):
|
|
305
|
+
# FIXME: throws away relative types, but can't use original vars as they're class-contextual
|
|
306
|
+
bty = type_(b[*((ta.Any,) * len(bty.params))]) # type: ignore
|
|
307
|
+
rty = replace_type_vars(bty, rpl, update_aliases=self._update_aliases)
|
|
308
|
+
ret.append(rty)
|
|
309
|
+
return tuple(ret)
|
|
310
|
+
return ()
|
|
311
|
+
|
|
312
|
+
def generic_mro(self, obj: ta.Any) -> list[Type]:
|
|
313
|
+
mro = c3.mro(
|
|
314
|
+
type_(obj),
|
|
315
|
+
get_bases=lambda t: self.get_generic_bases(t),
|
|
316
|
+
is_subclass=lambda l, r: issubclass(get_concrete_type(l), get_concrete_type(r)), # type: ignore
|
|
317
|
+
)
|
|
318
|
+
return [ty for ty in mro if get_concrete_type(ty) is not ta.Generic]
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
DEFAULT_GENERIC_SUBSTITUTION = GenericSubstitution()
|
|
322
|
+
|
|
323
|
+
get_generic_bases = DEFAULT_GENERIC_SUBSTITUTION.get_generic_bases
|
|
324
|
+
generic_mro = DEFAULT_GENERIC_SUBSTITUTION.generic_mro
|
|
325
|
+
|
|
326
|
+
ALIAS_UPDATING_GENERIC_SUBSTITUTION = GenericSubstitution(update_aliases=True)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
##
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
KNOWN_GENERICS: ta.AbstractSet[type] = frozenset([
|
|
333
|
+
collections.abc.Mapping,
|
|
334
|
+
collections.abc.Sequence,
|
|
335
|
+
collections.abc.Set,
|
|
336
|
+
])
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def isinstance_of(rfl: Type) -> ta.Callable[[ta.Any], bool]:
|
|
340
|
+
if isinstance(rfl, type):
|
|
341
|
+
return lambda o: isinstance(o, rfl)
|
|
342
|
+
|
|
343
|
+
if isinstance(rfl, NewType):
|
|
344
|
+
return isinstance_of(get_underlying(rfl))
|
|
345
|
+
|
|
346
|
+
if isinstance(rfl, Union):
|
|
347
|
+
fns = [isinstance_of(a) for a in rfl.args]
|
|
348
|
+
return lambda o: any(fn(o) for fn in fns)
|
|
349
|
+
|
|
350
|
+
if isinstance(rfl, Generic):
|
|
351
|
+
if rfl.cls in (collections.abc.Sequence, collections.abc.Set):
|
|
352
|
+
[efn] = map(isinstance_of, rfl.args)
|
|
353
|
+
return lambda o: isinstance(o, rfl.cls) and all(efn(e) for e in o) # type: ignore
|
|
354
|
+
|
|
355
|
+
if rfl.cls == collections.abc.Mapping:
|
|
356
|
+
kfn, vfn = map(isinstance_of, rfl.args)
|
|
357
|
+
return lambda o: isinstance(o, rfl.cls) and all(kfn(k) and vfn(v) for k, v in o.items()) # type: ignore
|
|
358
|
+
|
|
359
|
+
raise TypeError(rfl)
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""
|
|
2
|
+
import sys, threading, pdb, functools
|
|
3
|
+
def _attach(repl):
|
|
4
|
+
frame = sys._current_frames()[threading.enumerate()[0].ident]
|
|
5
|
+
debugger = pdb.Pdb(
|
|
6
|
+
stdin=repl.conn.makefile('r'),
|
|
7
|
+
stdout=repl.conn.makefile('w'),
|
|
8
|
+
)
|
|
9
|
+
debugger.reset()
|
|
10
|
+
while frame:
|
|
11
|
+
frame.f_trace = debugger.trace_dispatch
|
|
12
|
+
debugger.botframe = frame
|
|
13
|
+
frame = frame.f_back
|
|
14
|
+
debugger.set_step()
|
|
15
|
+
frame.f_trace = debugger.trace_dispatch
|
|
16
|
+
"""
|
|
17
|
+
import ast
|
|
18
|
+
import codeop
|
|
19
|
+
import errno
|
|
20
|
+
import logging
|
|
21
|
+
import socket as sock
|
|
22
|
+
import sys
|
|
23
|
+
import threading
|
|
24
|
+
import traceback
|
|
25
|
+
import types
|
|
26
|
+
import typing as ta
|
|
27
|
+
|
|
28
|
+
from .. import check
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
log = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DisconnectException(Exception):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class InteractiveSocketConsole:
|
|
39
|
+
"""code.InteractiveConsole but just different enough to not be worth subclassing."""
|
|
40
|
+
|
|
41
|
+
ENCODING = 'utf-8'
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
conn: sock.socket,
|
|
46
|
+
locals: ta.Optional[dict[str, ta.Any]] = None,
|
|
47
|
+
filename: str = '<console>'
|
|
48
|
+
) -> None:
|
|
49
|
+
super().__init__()
|
|
50
|
+
|
|
51
|
+
if locals is None:
|
|
52
|
+
locals = {
|
|
53
|
+
'__name__': '__console__',
|
|
54
|
+
'__doc__': None,
|
|
55
|
+
'__console__': self,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
self._conn = conn
|
|
59
|
+
self._locals = locals
|
|
60
|
+
self._filename = filename
|
|
61
|
+
|
|
62
|
+
self._compiler = codeop.CommandCompiler()
|
|
63
|
+
self._buffer: list[str] = []
|
|
64
|
+
self._count = 0
|
|
65
|
+
self._write_count = -1
|
|
66
|
+
|
|
67
|
+
def reset_buffer(self) -> None:
|
|
68
|
+
self._buffer = []
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def conn(self) -> sock.socket:
|
|
72
|
+
return self._conn
|
|
73
|
+
|
|
74
|
+
CPRT = 'Type "help", "copyright", "credits" or "license" for more information.'
|
|
75
|
+
|
|
76
|
+
def interact(self, banner: ta.Optional[str] = None, exitmsg: ta.Optional[str] = None) -> None:
|
|
77
|
+
log.info(f'Console {id(self)} on thread {threading.current_thread().ident} interacting')
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
ps1 = getattr(sys, 'ps1', '>>> ')
|
|
81
|
+
ps2 = getattr(sys, 'ps2', '... ')
|
|
82
|
+
|
|
83
|
+
if banner is None:
|
|
84
|
+
self.write(
|
|
85
|
+
'Python %s on %s\n%s\n(%s)\n' %
|
|
86
|
+
(sys.version, sys.platform, self.CPRT, self.__class__.__name__))
|
|
87
|
+
elif banner:
|
|
88
|
+
self.write('%s\n' % (str(banner),))
|
|
89
|
+
|
|
90
|
+
more = False
|
|
91
|
+
while True:
|
|
92
|
+
try:
|
|
93
|
+
try:
|
|
94
|
+
line = self.raw_input(ps2 if more else ps1)
|
|
95
|
+
except EOFError:
|
|
96
|
+
self.write('\n')
|
|
97
|
+
break
|
|
98
|
+
else:
|
|
99
|
+
more = self.push_line(line)
|
|
100
|
+
|
|
101
|
+
except KeyboardInterrupt:
|
|
102
|
+
self.write('\nKeyboardInterrupt\n')
|
|
103
|
+
self.reset_buffer()
|
|
104
|
+
more = False
|
|
105
|
+
|
|
106
|
+
if exitmsg is None:
|
|
107
|
+
self.write('now exiting %s...\n' % self.__class__.__name__)
|
|
108
|
+
|
|
109
|
+
elif exitmsg != '':
|
|
110
|
+
self.write('%s\n' % exitmsg)
|
|
111
|
+
|
|
112
|
+
except DisconnectException:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
except OSError as oe:
|
|
116
|
+
if oe.errno == errno.EBADF:
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
finally:
|
|
120
|
+
log.info(f'Console {id(self)} on thread {threading.current_thread().ident} finished')
|
|
121
|
+
|
|
122
|
+
def push_line(self, line: str) -> bool:
|
|
123
|
+
self._buffer.append(line)
|
|
124
|
+
source = '\n'.join(self._buffer)
|
|
125
|
+
more = self.run_source(source, self._filename)
|
|
126
|
+
if not more:
|
|
127
|
+
self.reset_buffer()
|
|
128
|
+
return more
|
|
129
|
+
|
|
130
|
+
def raw_input(self, prompt: str = '') -> str:
|
|
131
|
+
self.write(prompt)
|
|
132
|
+
buf = b''
|
|
133
|
+
while True:
|
|
134
|
+
b = self._conn.recv(1)
|
|
135
|
+
if not b:
|
|
136
|
+
raise DisconnectException
|
|
137
|
+
if b == b'\n':
|
|
138
|
+
break
|
|
139
|
+
buf += b
|
|
140
|
+
return buf.decode(self.ENCODING)
|
|
141
|
+
|
|
142
|
+
def write(self, data: str) -> None:
|
|
143
|
+
self._conn.send(data.encode(self.ENCODING))
|
|
144
|
+
|
|
145
|
+
def compile(
|
|
146
|
+
self,
|
|
147
|
+
source: ta.Union[str, ast.AST],
|
|
148
|
+
filename: str = '<input>',
|
|
149
|
+
symbol: str = 'single'
|
|
150
|
+
) -> ta.Optional[types.CodeType]:
|
|
151
|
+
if isinstance(source, ast.AST):
|
|
152
|
+
return self._compiler.compiler(source, filename, symbol) # type: ignore
|
|
153
|
+
else:
|
|
154
|
+
return self._compiler(source, filename, symbol)
|
|
155
|
+
|
|
156
|
+
def run_source(
|
|
157
|
+
self,
|
|
158
|
+
source: ta.Union[str, ast.AST],
|
|
159
|
+
filename: str = '<input>',
|
|
160
|
+
symbol: str = 'single',
|
|
161
|
+
) -> bool:
|
|
162
|
+
try:
|
|
163
|
+
code = self.compile(source, filename, symbol)
|
|
164
|
+
except (OverflowError, SyntaxError, ValueError):
|
|
165
|
+
# Case 1 (incorrect)
|
|
166
|
+
self.show_syntax_error(filename)
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
if code is None:
|
|
170
|
+
# Case 2 (incomplete)
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
# Case 3 (complete)
|
|
174
|
+
try:
|
|
175
|
+
node = ast.parse(source) # type: ignore
|
|
176
|
+
except (OverflowError, SyntaxError, ValueError):
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
if (
|
|
180
|
+
isinstance(node, ast.Module) and
|
|
181
|
+
node.body and
|
|
182
|
+
isinstance(node.body[-1], ast.Expr)
|
|
183
|
+
):
|
|
184
|
+
expr = node.body[-1]
|
|
185
|
+
source = ast.Interactive(
|
|
186
|
+
[
|
|
187
|
+
*node.body[:-1],
|
|
188
|
+
ast.Assign(
|
|
189
|
+
[ast.Name(
|
|
190
|
+
f'_{self._count}',
|
|
191
|
+
ast.Store(),
|
|
192
|
+
lineno=expr.lineno,
|
|
193
|
+
col_offset=expr.col_offset,
|
|
194
|
+
)],
|
|
195
|
+
expr.value,
|
|
196
|
+
lineno=expr.lineno,
|
|
197
|
+
col_offset=expr.col_offset,
|
|
198
|
+
)
|
|
199
|
+
],
|
|
200
|
+
)
|
|
201
|
+
ast.fix_missing_locations(source)
|
|
202
|
+
self._write_count = self._count
|
|
203
|
+
|
|
204
|
+
code = check.not_none(self.compile(source, filename, symbol))
|
|
205
|
+
self.run_code(code)
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
def run_code(self, code: types.CodeType) -> None:
|
|
209
|
+
try:
|
|
210
|
+
exec(code, self._locals)
|
|
211
|
+
except SystemExit:
|
|
212
|
+
raise
|
|
213
|
+
except Exception:
|
|
214
|
+
self.show_traceback()
|
|
215
|
+
else:
|
|
216
|
+
if self._count == self._write_count:
|
|
217
|
+
self.write(repr(self._locals[f'_{self._count}']))
|
|
218
|
+
self.write('\n')
|
|
219
|
+
self._count += 1
|
|
220
|
+
|
|
221
|
+
def show_traceback(self) -> None:
|
|
222
|
+
sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
|
|
223
|
+
sys.last_traceback = last_tb
|
|
224
|
+
try:
|
|
225
|
+
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next) # type: ignore
|
|
226
|
+
self.write(''.join(lines))
|
|
227
|
+
finally:
|
|
228
|
+
last_tb = ei = None # type: ignore # noqa
|
|
229
|
+
|
|
230
|
+
def show_syntax_error(self, filename: ta.Optional[str] = None) -> None:
|
|
231
|
+
type, value, tb = sys.exc_info()
|
|
232
|
+
sys.last_type = type
|
|
233
|
+
sys.last_value = value
|
|
234
|
+
sys.last_traceback = tb
|
|
235
|
+
if filename and type is SyntaxError:
|
|
236
|
+
# Work hard to stuff the correct filename in the exception
|
|
237
|
+
try:
|
|
238
|
+
msg, (dummy_filename, lineno, offset, line) = value.args # type: ignore
|
|
239
|
+
except ValueError:
|
|
240
|
+
# Not the format we expect; leave it alone
|
|
241
|
+
pass
|
|
242
|
+
else:
|
|
243
|
+
# Stuff in the right filename
|
|
244
|
+
value = SyntaxError(msg, (filename, lineno, offset, line))
|
|
245
|
+
sys.last_value = value
|
|
246
|
+
lines = traceback.format_exception_only(type, value)
|
|
247
|
+
self.write(''.join(lines))
|