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/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()
@@ -0,0 +1,7 @@
1
+ from .testing import ( # noqa
2
+ call_many_with_timeout,
3
+ can_import,
4
+ run_with_timeout,
5
+ waitpid_with_timeout,
6
+ xfail,
7
+ )
@@ -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,8 @@
1
+ from . import plugins # noqa
2
+
3
+ from .helpers import ( # noqa
4
+ assert_raises_star,
5
+ skip_if_cant_import,
6
+ skip_if_not_on_path,
7
+ skip_if_python_version_less_than,
8
+ )
@@ -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,6 @@
1
+ ALL: list[type] = []
2
+
3
+
4
+ def register(obj):
5
+ ALL.append(obj)
6
+ return obj
@@ -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())