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/term.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
import re
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
from . import lang
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
ESC = '\x1b'
|
|
9
|
+
BEL = '\x07'
|
|
10
|
+
|
|
11
|
+
DCS = ESC + 'P'
|
|
12
|
+
CSI = ESC + '['
|
|
13
|
+
ST = ESC + '\\'
|
|
14
|
+
OSC = ESC + ']'
|
|
15
|
+
RIS = ESC + 'c'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def set_title(title: str) -> str:
|
|
19
|
+
return OSC + '0;' + title + BEL
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def strip_ansi_codes(s: str) -> str:
|
|
23
|
+
return re.sub(r'\033\\[([0-9]+)(;[0-9]+)*m', '', s)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ControlSequence:
|
|
27
|
+
|
|
28
|
+
def __init__(self, fn: ta.Callable[..., str], desc: str) -> None:
|
|
29
|
+
super().__init__()
|
|
30
|
+
self._fn = fn
|
|
31
|
+
self._desc = desc
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def description(self) -> str:
|
|
35
|
+
return self._desc
|
|
36
|
+
|
|
37
|
+
def __call__(self, *args, **kwargs) -> str:
|
|
38
|
+
return self._fn(*args, **kwargs)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
CUU = ControlSequence(lambda n: CSI + str(n) + 'A', 'Cursor Up')
|
|
42
|
+
CUD = ControlSequence(lambda n: CSI + str(n) + 'B', 'Cursor Down')
|
|
43
|
+
CUF = ControlSequence(lambda n: CSI + str(n) + 'C', 'Cursor Forward')
|
|
44
|
+
CUB = ControlSequence(lambda n: CSI + str(n) + 'D', 'Cursor Back')
|
|
45
|
+
|
|
46
|
+
CNL = ControlSequence(lambda n: CSI + str(n) + 'E', 'Cursor Next Line')
|
|
47
|
+
CPL = ControlSequence(lambda n: CSI + str(n) + 'F', 'Cursor Previous Line')
|
|
48
|
+
CHA = ControlSequence(lambda n: CSI + str(n) + 'G', 'Cursor Horizontal Line Absolute')
|
|
49
|
+
|
|
50
|
+
CUP = ControlSequence(lambda n, m: CSI + str(n) + ';' + str(m) + 'H', 'Cursor Position')
|
|
51
|
+
HVP = ControlSequence(lambda n, m: CSI + str(n) + ';' + str(m) + 'f', 'Horizontal Vertical Position')
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _str_val(val: ta.Any) -> str:
|
|
55
|
+
if isinstance(val, enum.Enum):
|
|
56
|
+
return str(val.value)
|
|
57
|
+
else:
|
|
58
|
+
return str(val)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class EDs(enum.Enum):
|
|
62
|
+
FROM_CURSOR_TO_END = 0
|
|
63
|
+
FROM_CURSOR_TO_BEGINNING = 1
|
|
64
|
+
ALL = 2
|
|
65
|
+
ALL_AND_SCROLLBACK = 3
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
ED = ControlSequence(lambda n: CSI + _str_val(n) + 'J', 'Erase in Display')
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ELs(enum.Enum):
|
|
72
|
+
FROM_CURSOR_TO_END = 0
|
|
73
|
+
FROM_CURSOR_TO_BEGINNING = 1
|
|
74
|
+
ALL = 2
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
EL = ControlSequence(lambda n: CSI + _str_val(n) + 'K', 'Erase in Line')
|
|
78
|
+
|
|
79
|
+
SU = ControlSequence(lambda n: CSI + str(n) + 'S', 'Scroll Up')
|
|
80
|
+
SD = ControlSequence(lambda n: CSI + str(n) + 'T', 'Scroll Down')
|
|
81
|
+
|
|
82
|
+
DSR = ControlSequence(lambda: CSI + '6n', 'Device Status Report')
|
|
83
|
+
|
|
84
|
+
SCP = ControlSequence(lambda: CSI + 's', 'Save Cursor Position')
|
|
85
|
+
RCP = ControlSequence(lambda: CSI + 'u', 'Restore Cursor Position')
|
|
86
|
+
|
|
87
|
+
SHOW_CURSOR = ControlSequence(lambda: CSI + '?25h', 'Show Cursor')
|
|
88
|
+
HIDE_CURSOR = ControlSequence(lambda: CSI + '?25l', 'Hide Cursor')
|
|
89
|
+
|
|
90
|
+
SGR = ControlSequence(lambda n: CSI + _str_val(n) + 'm', 'Select Graphic Rendition')
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class SGRs(lang.Namespace):
|
|
94
|
+
RESET = 0
|
|
95
|
+
NORMAL_COLOR_AND_INTENSITY = 22
|
|
96
|
+
|
|
97
|
+
class FONT(enum.Enum):
|
|
98
|
+
BOLD = 1
|
|
99
|
+
FAINT = 2
|
|
100
|
+
ITALIC = 3
|
|
101
|
+
UNTERLINE = 4
|
|
102
|
+
|
|
103
|
+
SLOW_BLINK = 5
|
|
104
|
+
RAPID_BLINK = 6
|
|
105
|
+
|
|
106
|
+
REVERSE_VIDEO = 7
|
|
107
|
+
|
|
108
|
+
PRIMARY_FONT = 10
|
|
109
|
+
ITALIC_OFF = 23
|
|
110
|
+
UNDERLINE_OFF = 24
|
|
111
|
+
BLINK_OFF = 25
|
|
112
|
+
INVERSE_OFF = 27
|
|
113
|
+
|
|
114
|
+
FRAMED = 51
|
|
115
|
+
ENCIRCLED = 52
|
|
116
|
+
OVERLINED = 53
|
|
117
|
+
NOT_FRAMED_OR_ENCIRCLED = 54
|
|
118
|
+
NOT_OVERLINED = 55
|
|
119
|
+
|
|
120
|
+
class FG(enum.Enum):
|
|
121
|
+
BLACK = 30
|
|
122
|
+
RED = 31
|
|
123
|
+
GREEN = 32
|
|
124
|
+
YELLOW = 33
|
|
125
|
+
BLUE = 34
|
|
126
|
+
MAGENTA = 35
|
|
127
|
+
CYAN = 36
|
|
128
|
+
WHITE = 37
|
|
129
|
+
|
|
130
|
+
BRIGHT_BLACK = 90
|
|
131
|
+
BRIGHT_RED = 91
|
|
132
|
+
BRIGHT_GREEN = 92
|
|
133
|
+
BRIGHT_YELLOW = 93
|
|
134
|
+
BRIGHT_BLUE = 94
|
|
135
|
+
BRIGHT_MAGENTA = 95
|
|
136
|
+
BRIGHT_CYAN = 96
|
|
137
|
+
BRIGHT_WHITE = 97
|
|
138
|
+
|
|
139
|
+
class BG(enum.Enum):
|
|
140
|
+
BLACK = 40
|
|
141
|
+
RED = 41
|
|
142
|
+
GREEN = 42
|
|
143
|
+
YELLOW = 43
|
|
144
|
+
BLUE = 44
|
|
145
|
+
MAGENTA = 45
|
|
146
|
+
CYAN = 46
|
|
147
|
+
WHITE = 47
|
|
148
|
+
|
|
149
|
+
BRIGHT_BLACK = 100
|
|
150
|
+
BRIGHT_RED = 101
|
|
151
|
+
BRIGHT_GREEN = 102
|
|
152
|
+
BRIGHT_YELLOW = 103
|
|
153
|
+
BRIGHT_BLUE = 104
|
|
154
|
+
BRIGHT_MAGENTA = 105
|
|
155
|
+
BRIGHT_CYAN = 106
|
|
156
|
+
BRIGHT_WHITE = 107
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _clamp_ofs(v: int, hi: int, ofs: int) -> str:
|
|
160
|
+
if v < 0 or v > hi:
|
|
161
|
+
raise ValueError(v)
|
|
162
|
+
return str(v + ofs)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
FG8 = ControlSequence(
|
|
166
|
+
lambda n: CSI + '38;5;' + str(n) + 'm',
|
|
167
|
+
'8-Bit Foreground Color')
|
|
168
|
+
FG8_STANDARD = ControlSequence(
|
|
169
|
+
lambda n: CSI + '38;5;' + _clamp_ofs(n, 8, 0) + 'm',
|
|
170
|
+
'8-Bit Foreground Color (Standard)')
|
|
171
|
+
FG8_HIGH_INTENSITY = ControlSequence(
|
|
172
|
+
lambda n: CSI + '38;5;' + _clamp_ofs(n, 8, 8) + 'm',
|
|
173
|
+
'8-Bit Foreground Color (High Intensity)')
|
|
174
|
+
FG8_216 = ControlSequence(
|
|
175
|
+
lambda n: CSI + '38;5;' + _clamp_ofs(n, 216, 16) + 'm',
|
|
176
|
+
'8-Bit Foreground Color (High Intensity)')
|
|
177
|
+
FG8_GRAYSCALE = ControlSequence(
|
|
178
|
+
lambda n: CSI + '38;5;' + _clamp_ofs(n, 24, 232) + 'm',
|
|
179
|
+
'8-Bit Foreground Color (Grayscale)')
|
|
180
|
+
FG8_RGB = ControlSequence(
|
|
181
|
+
lambda r, g, b: CSI + '38;5;' + str(36 * r + 6 * g + b) + 'm',
|
|
182
|
+
'8-Bit Foreground Color (RGB)')
|
|
183
|
+
|
|
184
|
+
BG8 = ControlSequence(
|
|
185
|
+
lambda n: CSI + '48;5;' + str(n) + 'm',
|
|
186
|
+
'8-Bit Background Color')
|
|
187
|
+
BG8_STANDARD = ControlSequence(
|
|
188
|
+
lambda n: CSI + '48;5;' + _clamp_ofs(n, 8, 0) + 'm',
|
|
189
|
+
'8-Bit Background Color (Standard)')
|
|
190
|
+
BG8_HIGH_INTENSITY = ControlSequence(
|
|
191
|
+
lambda n: CSI + '48;5;' + _clamp_ofs(n, 8, 8) + 'm',
|
|
192
|
+
'8-Bit Background Color (High Intensity)')
|
|
193
|
+
BG8_216 = ControlSequence(
|
|
194
|
+
lambda n: CSI + '48;5;' + _clamp_ofs(n, 216, 16) + 'm',
|
|
195
|
+
'8-Bit Background Color (High Intensity)')
|
|
196
|
+
BG8_GRAYSCALE = ControlSequence(
|
|
197
|
+
lambda n: CSI + '48;5;' + _clamp_ofs(n, 24, 232) + 'm',
|
|
198
|
+
'8-Bit Background Color (Grayscale)')
|
|
199
|
+
BG8_RGB = ControlSequence(
|
|
200
|
+
lambda r, g, b: CSI + '48;5;' + str(36 * r + 6 * g + b) + 'm',
|
|
201
|
+
'8-Bit Background Color (RGB)')
|
|
202
|
+
|
|
203
|
+
FG24_RGB = ControlSequence(
|
|
204
|
+
lambda r, g, b: CSI + '38;2;' + str(r) + ';' + str(g) + ';' + str(b) + 'm',
|
|
205
|
+
'24-Bit Foreground Color (RGB)')
|
|
206
|
+
BG24_RGB = ControlSequence(
|
|
207
|
+
lambda r, g, b: CSI + '48;2;' + str(r) + ';' + str(g) + ';' + str(b) + 'm',
|
|
208
|
+
'24-Bit Background Color (RGB)')
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def main():
|
|
212
|
+
import sys
|
|
213
|
+
|
|
214
|
+
sys.stdout.write(SGR(SGRs.RESET))
|
|
215
|
+
sys.stdout.write(BG8(15) + ' ')
|
|
216
|
+
for i in [196, 160, 124, 88, 52, 16]:
|
|
217
|
+
sys.stdout.write(BG8(i) + ' ')
|
|
218
|
+
sys.stdout.write(SGR(SGRs.RESET) + '\n')
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
if __name__ == '__main__':
|
|
222
|
+
main()
|
omlish/testing/pydevd.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A small but likely growing collection of (completely optional) tools to make pydevd (PyCharm's, among other python
|
|
3
|
+
IDE's, debugger) do hard things. Originally explored and added to get spark jvm python subprocesses to connect back to
|
|
4
|
+
an already-debugging PyCharm instance to debug PySpark jobs.
|
|
5
|
+
|
|
6
|
+
TODO:
|
|
7
|
+
- https://www.jetbrains.com/help/pycharm/remote-debugging-with-product.html#
|
|
8
|
+
- move to dev?
|
|
9
|
+
- cython help? or in cython.py
|
|
10
|
+
"""
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import tempfile
|
|
15
|
+
import textwrap
|
|
16
|
+
import types
|
|
17
|
+
import typing as ta
|
|
18
|
+
|
|
19
|
+
from .. import check
|
|
20
|
+
from .. import lang
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
ALLOW_DEBUGGER_CALLS = False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
DEBUGGER_CALL_PACKAGES = {
|
|
27
|
+
'_pydevd_bundle',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def is_debugger_call(hoist: int = 0, walk: int = 2) -> bool:
|
|
32
|
+
frame: ta.Optional[types.FrameType] = sys._getframe(2 + hoist) # noqa
|
|
33
|
+
for _ in range(walk):
|
|
34
|
+
if frame is None:
|
|
35
|
+
break
|
|
36
|
+
package = frame.f_globals.get('__package__')
|
|
37
|
+
if package in DEBUGGER_CALL_PACKAGES:
|
|
38
|
+
return True
|
|
39
|
+
frame = frame.f_back
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DebuggerCallForbiddenException(Exception):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def forbid_debugger_call(hoist: int = 0) -> None:
|
|
48
|
+
# FIXME: only reentrant?
|
|
49
|
+
if not ALLOW_DEBUGGER_CALLS and is_debugger_call(hoist + 1):
|
|
50
|
+
raise DebuggerCallForbiddenException
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@lang.cached_function
|
|
57
|
+
def silence_subprocess_check() -> None:
|
|
58
|
+
try:
|
|
59
|
+
# /Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_bundle/pydev_monkey.py
|
|
60
|
+
from _pydev_bundle import pydev_monkey # noqa
|
|
61
|
+
except ImportError:
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
new_tb = lang.proxy_import('traceback')
|
|
65
|
+
new_tb.print_exc = lambda *a, **k: None # type: ignore
|
|
66
|
+
pydev_monkey.traceback = new_tb
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@lang.cached_function
|
|
70
|
+
def patch_for_trio_asyncio() -> None:
|
|
71
|
+
try:
|
|
72
|
+
import pydevd_nest_asyncio # noqa
|
|
73
|
+
except ImportError:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
import trio_asyncio._base # noqa
|
|
77
|
+
|
|
78
|
+
def new_call_soon(self, callback, *args, **context):
|
|
79
|
+
_, callback = pydevd_nest_asyncio._PydevdAsyncioUtils.try_to_get_internal_callback(callback) # noqa
|
|
80
|
+
return orig_call_soon(self, callback, *args, **context)
|
|
81
|
+
|
|
82
|
+
orig_call_soon = trio_asyncio._base.BaseTrioEventLoop.call_soon # noqa
|
|
83
|
+
trio_asyncio._base.BaseTrioEventLoop.call_soon = new_call_soon # noqa
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
##
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@lang.cached_function
|
|
90
|
+
def _pydevd() -> ta.Optional[types.ModuleType]:
|
|
91
|
+
try:
|
|
92
|
+
return __import__('pydevd')
|
|
93
|
+
except ImportError:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def is_present() -> bool:
|
|
98
|
+
return _pydevd() is not None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def get_setup() -> ta.Optional[dict]:
|
|
102
|
+
if is_present():
|
|
103
|
+
return _pydevd().SetupHolder.setup
|
|
104
|
+
else:
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def is_running() -> bool:
|
|
109
|
+
return get_setup() is not None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
##
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
ARGS_ENV_VAR = 'PYDEVD_ARGS'
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_args() -> list[str]:
|
|
119
|
+
check.state(is_present())
|
|
120
|
+
setup: ta.Mapping[ta.Any, ta.Any] = check.isinstance(get_setup(), dict)
|
|
121
|
+
args = [_pydevd().__file__]
|
|
122
|
+
|
|
123
|
+
for k in [
|
|
124
|
+
'port',
|
|
125
|
+
'vm_type',
|
|
126
|
+
'client',
|
|
127
|
+
]:
|
|
128
|
+
if v := setup[k]:
|
|
129
|
+
args.extend(['--' + k, str(v)])
|
|
130
|
+
|
|
131
|
+
for k in [
|
|
132
|
+
'server',
|
|
133
|
+
'multiproc',
|
|
134
|
+
'multiprocess',
|
|
135
|
+
'save-signatures',
|
|
136
|
+
'save-threading',
|
|
137
|
+
'save-asyncio',
|
|
138
|
+
'print-in-debugger-startup',
|
|
139
|
+
'cmd-line',
|
|
140
|
+
]:
|
|
141
|
+
if setup[k]:
|
|
142
|
+
args.append('--' + k)
|
|
143
|
+
|
|
144
|
+
if setup['qt-support']:
|
|
145
|
+
args.append('--qt-support=' + setup['qt-support'])
|
|
146
|
+
|
|
147
|
+
return args
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def save_args() -> None:
|
|
151
|
+
if is_present():
|
|
152
|
+
os.environ[ARGS_ENV_VAR] = json.dumps(get_args())
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def maybe_reexec(
|
|
156
|
+
*,
|
|
157
|
+
file: ta.Optional[str] = None,
|
|
158
|
+
module: ta.Optional[str] = None,
|
|
159
|
+
silence: bool = False,
|
|
160
|
+
) -> None:
|
|
161
|
+
if ARGS_ENV_VAR not in os.environ:
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
import pydevd # noqa
|
|
166
|
+
except ImportError:
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
if pydevd.SetupHolder.setup is not None: # noqa
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
if module is not None:
|
|
173
|
+
if file is not None:
|
|
174
|
+
raise ValueError
|
|
175
|
+
|
|
176
|
+
tmpdir = tempfile.mkdtemp()
|
|
177
|
+
bootstrap_path = os.path.join(tmpdir, 'bootstrap.py')
|
|
178
|
+
with open(bootstrap_path, 'w') as f:
|
|
179
|
+
f.write(textwrap.dedent(f"""
|
|
180
|
+
import sys
|
|
181
|
+
old_paths = set(sys.path)
|
|
182
|
+
for new_path in {sys.path!r}:
|
|
183
|
+
if new_path not in old_paths:
|
|
184
|
+
sys.path.insert(0, new_path)
|
|
185
|
+
|
|
186
|
+
import runpy
|
|
187
|
+
runpy.run_module({module!r}, run_name='__main__')
|
|
188
|
+
"""))
|
|
189
|
+
file = bootstrap_path
|
|
190
|
+
|
|
191
|
+
else:
|
|
192
|
+
if file is None:
|
|
193
|
+
raise ValueError
|
|
194
|
+
|
|
195
|
+
args = [sys.executable]
|
|
196
|
+
args.extend(json.loads(os.environ[ARGS_ENV_VAR]))
|
|
197
|
+
args.extend(['--file', file])
|
|
198
|
+
args.extend(sys.argv[1:])
|
|
199
|
+
|
|
200
|
+
if silence:
|
|
201
|
+
tmpdir = tempfile.mkdtemp()
|
|
202
|
+
bootstrap_path = os.path.join(tmpdir, 'bootstrap.py')
|
|
203
|
+
with open(bootstrap_path, 'w') as f:
|
|
204
|
+
f.write(textwrap.dedent(f"""
|
|
205
|
+
import sys
|
|
206
|
+
old_paths = set(sys.path)
|
|
207
|
+
for new_path in {sys.path!r}:
|
|
208
|
+
if new_path not in old_paths:
|
|
209
|
+
sys.path.insert(0, new_path)
|
|
210
|
+
|
|
211
|
+
_stderr_write = sys.stderr.write
|
|
212
|
+
def stderr_write(*args, **kwargs):
|
|
213
|
+
code = sys._getframe(1).f_code
|
|
214
|
+
if code is not None and code.co_filename and code.co_filename.endswith('/pydev_log.py'):
|
|
215
|
+
return
|
|
216
|
+
_stderr_write(*args, **kwargs)
|
|
217
|
+
sys.stderr.write = stderr_write
|
|
218
|
+
|
|
219
|
+
sys.argv = {args[1:]!r}
|
|
220
|
+
import runpy
|
|
221
|
+
runpy.run_path({args[1]!r}, run_name='__main__')
|
|
222
|
+
"""))
|
|
223
|
+
args = [args[0], bootstrap_path]
|
|
224
|
+
|
|
225
|
+
os.execvp(sys.executable, args)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import shutil
|
|
3
|
+
import sys
|
|
4
|
+
import typing as ta
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from ..testing import can_import
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def skip_if_cant_import(module: str, *args, **kwargs):
|
|
12
|
+
return pytest.mark.skipif(not can_import(module, *args, **kwargs), reason=f'requires import {module}')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def skip_if_not_on_path(exe: str):
|
|
16
|
+
return pytest.mark.skipif(shutil.which(exe) is None, reason=f'requires exe on path {exe}')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def skip_if_python_version_less_than(num: ta.Sequence[int]):
|
|
20
|
+
return pytest.mark.skipif(sys.version_info < tuple(num), reason=f'python version {tuple(sys.version_info)} < {tuple(num)}') # noqa
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def skip_if_not_single():
|
|
24
|
+
# [resolve_collection_argument(a) for a in session.config.args]
|
|
25
|
+
raise NotImplementedError
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@contextlib.contextmanager
|
|
29
|
+
def assert_raises_star(et):
|
|
30
|
+
num_caught = 0
|
|
31
|
+
try:
|
|
32
|
+
yield
|
|
33
|
+
except* et as eg: # noqa
|
|
34
|
+
num_caught += 1
|
|
35
|
+
assert num_caught > 0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import harness # noqa
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import enum
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from .. import plugins
|
|
8
|
+
from .... import check
|
|
9
|
+
from .... import inject as inj
|
|
10
|
+
from .... import lang
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
T = ta.TypeVar('T')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PytestScope(enum.Enum):
|
|
20
|
+
SESSION = enum.auto()
|
|
21
|
+
PACKAGE = enum.auto()
|
|
22
|
+
MODULE = enum.auto()
|
|
23
|
+
CLASS = enum.auto()
|
|
24
|
+
FUNCTION = enum.auto()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Scopes(lang.Namespace):
|
|
28
|
+
Session = inj.SeededScope(PytestScope.SESSION)
|
|
29
|
+
Package = inj.SeededScope(PytestScope.PACKAGE)
|
|
30
|
+
Module = inj.SeededScope(PytestScope.MODULE)
|
|
31
|
+
Class = inj.SeededScope(PytestScope.CLASS)
|
|
32
|
+
Function = inj.SeededScope(PytestScope.FUNCTION)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
_SCOPES_BY_PYTEST_SCOPE: ta.Mapping[PytestScope, inj.SeededScope] = {
|
|
36
|
+
check.isinstance(a.tag, PytestScope): a
|
|
37
|
+
for n, a in Scopes.__dict__.items()
|
|
38
|
+
if isinstance(a, inj.SeededScope)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_ACTIVE_HARNESSES: set['Harness'] = set()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Harness:
|
|
49
|
+
def __init__(self, es: inj.Elements) -> None:
|
|
50
|
+
super().__init__()
|
|
51
|
+
self._orig_es = es
|
|
52
|
+
self._es = inj.as_elements(
|
|
53
|
+
inj.as_binding(self),
|
|
54
|
+
*[
|
|
55
|
+
inj.as_elements(
|
|
56
|
+
inj.bind_scope(ss),
|
|
57
|
+
inj.bind_scope_seed(ss, inj.Key(pytest.FixtureRequest, tag=pts)),
|
|
58
|
+
)
|
|
59
|
+
for pts, ss in _SCOPES_BY_PYTEST_SCOPE.items()
|
|
60
|
+
],
|
|
61
|
+
es,
|
|
62
|
+
)
|
|
63
|
+
self._inj: inj.Injector | None = None
|
|
64
|
+
|
|
65
|
+
##
|
|
66
|
+
|
|
67
|
+
@contextlib.contextmanager
|
|
68
|
+
def activate(self) -> ta.Generator[ta.Self, None, None]:
|
|
69
|
+
check.none(self._inj)
|
|
70
|
+
check.not_in(self, _ACTIVE_HARNESSES)
|
|
71
|
+
_ACTIVE_HARNESSES.add(self)
|
|
72
|
+
try:
|
|
73
|
+
with inj.create_managed_injector(self._es) as i:
|
|
74
|
+
self._inj = i
|
|
75
|
+
yield self
|
|
76
|
+
finally:
|
|
77
|
+
self._inj = None
|
|
78
|
+
_ACTIVE_HARNESSES.remove(self)
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
|
|
82
|
+
def __getitem__(
|
|
83
|
+
self,
|
|
84
|
+
target: ta.Union[inj.Key[T], type[T]],
|
|
85
|
+
) -> T:
|
|
86
|
+
return check.not_none(self._inj)[target]
|
|
87
|
+
|
|
88
|
+
def session(self) -> pytest.FixtureRequest:
|
|
89
|
+
return self[inj.Key(pytest.FixtureRequest, tag=PytestScope.SESSION)]
|
|
90
|
+
|
|
91
|
+
def package(self) -> pytest.FixtureRequest:
|
|
92
|
+
return self[inj.Key(pytest.FixtureRequest, tag=PytestScope.PACKAGE)]
|
|
93
|
+
|
|
94
|
+
def module(self) -> pytest.FixtureRequest:
|
|
95
|
+
return self[inj.Key(pytest.FixtureRequest, tag=PytestScope.MODULE)]
|
|
96
|
+
|
|
97
|
+
def class_(self) -> pytest.FixtureRequest:
|
|
98
|
+
return self[inj.Key(pytest.FixtureRequest, tag=PytestScope.CLASS)]
|
|
99
|
+
|
|
100
|
+
def function(self) -> pytest.FixtureRequest:
|
|
101
|
+
return self[inj.Key(pytest.FixtureRequest, tag=PytestScope.FUNCTION)]
|
|
102
|
+
|
|
103
|
+
##
|
|
104
|
+
|
|
105
|
+
@contextlib.contextmanager
|
|
106
|
+
def _pytest_scope_manager(
|
|
107
|
+
self,
|
|
108
|
+
pytest_scope: PytestScope,
|
|
109
|
+
request: pytest.FixtureRequest,
|
|
110
|
+
) -> ta.Generator[None, None, None]:
|
|
111
|
+
ss = _SCOPES_BY_PYTEST_SCOPE[pytest_scope]
|
|
112
|
+
with inj.enter_seeded_scope(check.not_none(self._inj), ss, {
|
|
113
|
+
inj.Key(pytest.FixtureRequest, tag=pytest_scope): request,
|
|
114
|
+
}):
|
|
115
|
+
yield
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
##
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@plugins.register
|
|
122
|
+
class HarnessPlugin:
|
|
123
|
+
|
|
124
|
+
@pytest.fixture(scope='session', autouse=True)
|
|
125
|
+
def _harness_scope_listener_session(self, harness, request):
|
|
126
|
+
with harness._pytest_scope_manager(PytestScope.SESSION, request): # noqa
|
|
127
|
+
yield
|
|
128
|
+
|
|
129
|
+
@pytest.fixture(scope='package', autouse=True)
|
|
130
|
+
def _harness_scope_listener_package(self, harness, request):
|
|
131
|
+
with harness._pytest_scope_manager(PytestScope.PACKAGE, request): # noqa
|
|
132
|
+
yield
|
|
133
|
+
|
|
134
|
+
@pytest.fixture(scope='module', autouse=True)
|
|
135
|
+
def _harness_scope_listener_module(self, harness, request):
|
|
136
|
+
with harness._pytest_scope_manager(PytestScope.MODULE, request): # noqa
|
|
137
|
+
yield
|
|
138
|
+
|
|
139
|
+
@pytest.fixture(scope='class', autouse=True)
|
|
140
|
+
def _harness_scope_listener_class(self, harness, request):
|
|
141
|
+
with harness._pytest_scope_manager(PytestScope.CLASS, request): # noqa
|
|
142
|
+
yield
|
|
143
|
+
|
|
144
|
+
@pytest.fixture(scope='function', autouse=True)
|
|
145
|
+
def _harness_scope_listener_function(self, harness, request):
|
|
146
|
+
with harness._pytest_scope_manager(PytestScope.FUNCTION, request): # noqa
|
|
147
|
+
yield
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
##
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
_HARNESS_ELEMENTS_LIST: list[inj.Elements] = []
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@pytest.fixture(scope='session', autouse=True)
|
|
157
|
+
def harness() -> ta.Generator[Harness, None, None]:
|
|
158
|
+
with Harness(inj.as_elements(*_HARNESS_ELEMENTS_LIST)).activate() as h:
|
|
159
|
+
yield h
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from . import ( # noqa
|
|
2
|
+
logging,
|
|
3
|
+
pycharm,
|
|
4
|
+
repeat,
|
|
5
|
+
skips,
|
|
6
|
+
spacing,
|
|
7
|
+
switches,
|
|
8
|
+
)
|
|
9
|
+
from ._registry import ( # noqa
|
|
10
|
+
ALL,
|
|
11
|
+
register,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def addhooks(pluginmanager):
|
|
16
|
+
present_types = {type(p) for p in pluginmanager.get_plugins()}
|
|
17
|
+
|
|
18
|
+
for plugin in ALL:
|
|
19
|
+
if plugin not in present_types:
|
|
20
|
+
pluginmanager.register(plugin()) # noqa
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .... import logs
|
|
2
|
+
from ._registry import register
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@register
|
|
6
|
+
class LoggingPlugin:
|
|
7
|
+
|
|
8
|
+
def pytest_addoption(self, parser):
|
|
9
|
+
parser.addoption('--log', action='store', help='Configures logging with given log level')
|
|
10
|
+
|
|
11
|
+
def pytest_configure(self, config):
|
|
12
|
+
if config.option.log is not None:
|
|
13
|
+
logs.configure_standard_logging(config.option.log.upper())
|