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.

Files changed (187) hide show
  1. omlish/__about__.py +7 -0
  2. omlish/__init__.py +0 -0
  3. omlish/argparse.py +223 -0
  4. omlish/asyncs/__init__.py +17 -0
  5. omlish/asyncs/anyio.py +23 -0
  6. omlish/asyncs/asyncio.py +19 -0
  7. omlish/asyncs/asyncs.py +76 -0
  8. omlish/asyncs/futures.py +179 -0
  9. omlish/asyncs/trio.py +11 -0
  10. omlish/c3.py +173 -0
  11. omlish/cached.py +9 -0
  12. omlish/check.py +231 -0
  13. omlish/collections/__init__.py +63 -0
  14. omlish/collections/_abc.py +156 -0
  15. omlish/collections/_io_abc.py +78 -0
  16. omlish/collections/cache/__init__.py +11 -0
  17. omlish/collections/cache/descriptor.py +188 -0
  18. omlish/collections/cache/impl.py +485 -0
  19. omlish/collections/cache/types.py +37 -0
  20. omlish/collections/coerce.py +337 -0
  21. omlish/collections/frozen.py +148 -0
  22. omlish/collections/identity.py +106 -0
  23. omlish/collections/indexed.py +75 -0
  24. omlish/collections/mappings.py +127 -0
  25. omlish/collections/ordered.py +81 -0
  26. omlish/collections/persistent.py +36 -0
  27. omlish/collections/skiplist.py +193 -0
  28. omlish/collections/sorted.py +126 -0
  29. omlish/collections/treap.py +228 -0
  30. omlish/collections/treapmap.py +144 -0
  31. omlish/collections/unmodifiable.py +174 -0
  32. omlish/collections/utils.py +110 -0
  33. omlish/configs/__init__.py +0 -0
  34. omlish/configs/flattening.py +147 -0
  35. omlish/configs/props.py +64 -0
  36. omlish/dataclasses/__init__.py +83 -0
  37. omlish/dataclasses/impl/__init__.py +6 -0
  38. omlish/dataclasses/impl/api.py +260 -0
  39. omlish/dataclasses/impl/as_.py +76 -0
  40. omlish/dataclasses/impl/exceptions.py +2 -0
  41. omlish/dataclasses/impl/fields.py +148 -0
  42. omlish/dataclasses/impl/frozen.py +55 -0
  43. omlish/dataclasses/impl/hashing.py +85 -0
  44. omlish/dataclasses/impl/init.py +173 -0
  45. omlish/dataclasses/impl/internals.py +118 -0
  46. omlish/dataclasses/impl/main.py +150 -0
  47. omlish/dataclasses/impl/metaclass.py +126 -0
  48. omlish/dataclasses/impl/metadata.py +74 -0
  49. omlish/dataclasses/impl/order.py +47 -0
  50. omlish/dataclasses/impl/params.py +150 -0
  51. omlish/dataclasses/impl/processing.py +16 -0
  52. omlish/dataclasses/impl/reflect.py +173 -0
  53. omlish/dataclasses/impl/replace.py +40 -0
  54. omlish/dataclasses/impl/repr.py +34 -0
  55. omlish/dataclasses/impl/simple.py +92 -0
  56. omlish/dataclasses/impl/slots.py +80 -0
  57. omlish/dataclasses/impl/utils.py +167 -0
  58. omlish/defs.py +193 -0
  59. omlish/dispatch/__init__.py +3 -0
  60. omlish/dispatch/dispatch.py +137 -0
  61. omlish/dispatch/functions.py +52 -0
  62. omlish/dispatch/methods.py +162 -0
  63. omlish/docker.py +149 -0
  64. omlish/dynamic.py +220 -0
  65. omlish/graphs/__init__.py +0 -0
  66. omlish/graphs/dot/__init__.py +19 -0
  67. omlish/graphs/dot/items.py +162 -0
  68. omlish/graphs/dot/rendering.py +147 -0
  69. omlish/graphs/dot/utils.py +30 -0
  70. omlish/graphs/trees.py +249 -0
  71. omlish/http/__init__.py +0 -0
  72. omlish/http/consts.py +20 -0
  73. omlish/http/wsgi.py +34 -0
  74. omlish/inject/__init__.py +85 -0
  75. omlish/inject/binder.py +12 -0
  76. omlish/inject/bindings.py +49 -0
  77. omlish/inject/eagers.py +21 -0
  78. omlish/inject/elements.py +43 -0
  79. omlish/inject/exceptions.py +49 -0
  80. omlish/inject/impl/__init__.py +0 -0
  81. omlish/inject/impl/bindings.py +19 -0
  82. omlish/inject/impl/elements.py +154 -0
  83. omlish/inject/impl/injector.py +182 -0
  84. omlish/inject/impl/inspect.py +98 -0
  85. omlish/inject/impl/private.py +109 -0
  86. omlish/inject/impl/providers.py +132 -0
  87. omlish/inject/impl/scopes.py +198 -0
  88. omlish/inject/injector.py +40 -0
  89. omlish/inject/inspect.py +14 -0
  90. omlish/inject/keys.py +43 -0
  91. omlish/inject/managed.py +24 -0
  92. omlish/inject/overrides.py +18 -0
  93. omlish/inject/private.py +29 -0
  94. omlish/inject/providers.py +111 -0
  95. omlish/inject/proxy.py +48 -0
  96. omlish/inject/scopes.py +84 -0
  97. omlish/inject/types.py +21 -0
  98. omlish/iterators.py +184 -0
  99. omlish/json.py +194 -0
  100. omlish/lang/__init__.py +112 -0
  101. omlish/lang/cached.py +267 -0
  102. omlish/lang/classes/__init__.py +24 -0
  103. omlish/lang/classes/abstract.py +74 -0
  104. omlish/lang/classes/restrict.py +137 -0
  105. omlish/lang/classes/simple.py +120 -0
  106. omlish/lang/classes/test/__init__.py +0 -0
  107. omlish/lang/classes/test/test_abstract.py +89 -0
  108. omlish/lang/classes/test/test_restrict.py +71 -0
  109. omlish/lang/classes/test/test_simple.py +58 -0
  110. omlish/lang/classes/test/test_virtual.py +72 -0
  111. omlish/lang/classes/virtual.py +130 -0
  112. omlish/lang/clsdct.py +67 -0
  113. omlish/lang/cmp.py +63 -0
  114. omlish/lang/contextmanagers.py +249 -0
  115. omlish/lang/datetimes.py +67 -0
  116. omlish/lang/descriptors.py +52 -0
  117. omlish/lang/functions.py +126 -0
  118. omlish/lang/imports.py +153 -0
  119. omlish/lang/iterables.py +54 -0
  120. omlish/lang/maybes.py +136 -0
  121. omlish/lang/objects.py +103 -0
  122. omlish/lang/resolving.py +50 -0
  123. omlish/lang/strings.py +128 -0
  124. omlish/lang/typing.py +92 -0
  125. omlish/libc.py +532 -0
  126. omlish/logs/__init__.py +9 -0
  127. omlish/logs/_abc.py +247 -0
  128. omlish/logs/configs.py +62 -0
  129. omlish/logs/filters.py +9 -0
  130. omlish/logs/formatters.py +67 -0
  131. omlish/logs/utils.py +20 -0
  132. omlish/marshal/__init__.py +52 -0
  133. omlish/marshal/any.py +25 -0
  134. omlish/marshal/base.py +201 -0
  135. omlish/marshal/base64.py +25 -0
  136. omlish/marshal/dataclasses.py +115 -0
  137. omlish/marshal/datetimes.py +90 -0
  138. omlish/marshal/enums.py +43 -0
  139. omlish/marshal/exceptions.py +7 -0
  140. omlish/marshal/factories.py +129 -0
  141. omlish/marshal/global_.py +33 -0
  142. omlish/marshal/iterables.py +57 -0
  143. omlish/marshal/mappings.py +66 -0
  144. omlish/marshal/naming.py +17 -0
  145. omlish/marshal/objects.py +106 -0
  146. omlish/marshal/optionals.py +49 -0
  147. omlish/marshal/polymorphism.py +147 -0
  148. omlish/marshal/primitives.py +43 -0
  149. omlish/marshal/registries.py +57 -0
  150. omlish/marshal/standard.py +80 -0
  151. omlish/marshal/utils.py +23 -0
  152. omlish/marshal/uuids.py +29 -0
  153. omlish/marshal/values.py +30 -0
  154. omlish/math.py +184 -0
  155. omlish/os.py +32 -0
  156. omlish/reflect.py +359 -0
  157. omlish/replserver/__init__.py +5 -0
  158. omlish/replserver/__main__.py +4 -0
  159. omlish/replserver/console.py +247 -0
  160. omlish/replserver/server.py +146 -0
  161. omlish/runmodule.py +28 -0
  162. omlish/stats.py +342 -0
  163. omlish/term.py +222 -0
  164. omlish/testing/__init__.py +7 -0
  165. omlish/testing/pydevd.py +225 -0
  166. omlish/testing/pytest/__init__.py +8 -0
  167. omlish/testing/pytest/helpers.py +35 -0
  168. omlish/testing/pytest/inject/__init__.py +1 -0
  169. omlish/testing/pytest/inject/harness.py +159 -0
  170. omlish/testing/pytest/plugins/__init__.py +20 -0
  171. omlish/testing/pytest/plugins/_registry.py +6 -0
  172. omlish/testing/pytest/plugins/logging.py +13 -0
  173. omlish/testing/pytest/plugins/pycharm.py +54 -0
  174. omlish/testing/pytest/plugins/repeat.py +19 -0
  175. omlish/testing/pytest/plugins/skips.py +32 -0
  176. omlish/testing/pytest/plugins/spacing.py +19 -0
  177. omlish/testing/pytest/plugins/switches.py +70 -0
  178. omlish/testing/testing.py +102 -0
  179. omlish/text/__init__.py +0 -0
  180. omlish/text/delimit.py +171 -0
  181. omlish/text/indent.py +50 -0
  182. omlish/text/parts.py +265 -0
  183. omlish-0.0.0.dev1.dist-info/LICENSE +21 -0
  184. omlish-0.0.0.dev1.dist-info/METADATA +17 -0
  185. omlish-0.0.0.dev1.dist-info/RECORD +187 -0
  186. omlish-0.0.0.dev1.dist-info/WHEEL +5 -0
  187. 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,5 @@
1
+ """
2
+ A background thread that opens a unix socket server accepting connections to an in-proc python repl (from which one can
3
+ inspect module globals, live thread stacks, and other such things).
4
+ """
5
+ from .server import ReplServer # noqa
@@ -0,0 +1,4 @@
1
+ from .server import run
2
+
3
+
4
+ run()
@@ -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))