omlish 0.0.0.dev1__py3-none-any.whl → 0.0.0.dev3__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 (147) hide show
  1. omlish/__about__.py +2 -3
  2. omlish/argparse.py +8 -8
  3. omlish/asyncs/__init__.py +2 -2
  4. omlish/asyncs/anyio.py +64 -1
  5. omlish/asyncs/asyncs.py +1 -3
  6. omlish/asyncs/futures.py +16 -15
  7. omlish/c3.py +5 -5
  8. omlish/check.py +8 -8
  9. omlish/collections/__init__.py +98 -63
  10. omlish/collections/_abc.py +2 -0
  11. omlish/collections/_io_abc.py +4 -2
  12. omlish/collections/cache/__init__.py +1 -1
  13. omlish/collections/cache/descriptor.py +12 -12
  14. omlish/collections/cache/impl.py +27 -20
  15. omlish/collections/cache/types.py +1 -1
  16. omlish/collections/coerce.py +44 -44
  17. omlish/collections/frozen.py +9 -9
  18. omlish/collections/identity.py +4 -5
  19. omlish/collections/mappings.py +5 -5
  20. omlish/collections/ordered.py +8 -8
  21. omlish/collections/skiplist.py +7 -7
  22. omlish/collections/sorted.py +4 -4
  23. omlish/collections/treap.py +42 -17
  24. omlish/collections/treapmap.py +59 -7
  25. omlish/collections/unmodifiable.py +25 -24
  26. omlish/collections/utils.py +1 -1
  27. omlish/configs/flattening.py +8 -7
  28. omlish/configs/props.py +3 -3
  29. omlish/dataclasses/__init__.py +1 -1
  30. omlish/dataclasses/impl/__init__.py +18 -0
  31. omlish/dataclasses/impl/api.py +15 -24
  32. omlish/dataclasses/impl/as_.py +4 -4
  33. omlish/dataclasses/impl/exceptions.py +1 -1
  34. omlish/dataclasses/impl/fields.py +8 -8
  35. omlish/dataclasses/impl/frozen.py +2 -2
  36. omlish/dataclasses/impl/init.py +6 -6
  37. omlish/dataclasses/impl/internals.py +16 -1
  38. omlish/dataclasses/impl/main.py +4 -4
  39. omlish/dataclasses/impl/metaclass.py +2 -2
  40. omlish/dataclasses/impl/metadata.py +1 -1
  41. omlish/dataclasses/impl/order.py +2 -2
  42. omlish/dataclasses/impl/params.py +4 -38
  43. omlish/dataclasses/impl/reflect.py +1 -7
  44. omlish/dataclasses/impl/replace.py +1 -1
  45. omlish/dataclasses/impl/repr.py +24 -6
  46. omlish/dataclasses/impl/simple.py +2 -2
  47. omlish/dataclasses/impl/slots.py +2 -2
  48. omlish/dataclasses/impl/utils.py +7 -7
  49. omlish/defs.py +13 -17
  50. omlish/diag/procfs.py +334 -0
  51. omlish/diag/ps.py +47 -0
  52. omlish/{replserver → diag/replserver}/console.py +26 -28
  53. omlish/{replserver → diag/replserver}/server.py +12 -12
  54. omlish/dispatch/dispatch.py +14 -16
  55. omlish/dispatch/functions.py +1 -1
  56. omlish/dispatch/methods.py +6 -7
  57. omlish/docker.py +8 -6
  58. omlish/dynamic.py +13 -13
  59. omlish/fnpairs.py +311 -0
  60. omlish/graphs/dot/items.py +1 -1
  61. omlish/graphs/trees.py +25 -31
  62. omlish/inject/__init__.py +7 -7
  63. omlish/inject/elements.py +2 -2
  64. omlish/inject/exceptions.py +8 -8
  65. omlish/inject/impl/elements.py +4 -4
  66. omlish/inject/impl/injector.py +6 -6
  67. omlish/inject/impl/inspect.py +3 -3
  68. omlish/inject/impl/scopes.py +9 -9
  69. omlish/inject/injector.py +1 -1
  70. omlish/inject/providers.py +2 -2
  71. omlish/inject/proxy.py +5 -5
  72. omlish/iterators.py +62 -26
  73. omlish/json.py +7 -6
  74. omlish/lang/__init__.py +172 -112
  75. omlish/lang/cached.py +15 -10
  76. omlish/lang/classes/__init__.py +35 -24
  77. omlish/lang/classes/abstract.py +3 -3
  78. omlish/lang/classes/restrict.py +14 -14
  79. omlish/lang/classes/simple.py +2 -2
  80. omlish/lang/classes/virtual.py +5 -5
  81. omlish/lang/clsdct.py +2 -2
  82. omlish/lang/cmp.py +2 -2
  83. omlish/lang/contextmanagers.py +31 -25
  84. omlish/lang/datetimes.py +1 -1
  85. omlish/lang/descriptors.py +51 -6
  86. omlish/lang/exceptions.py +2 -0
  87. omlish/lang/functions.py +101 -35
  88. omlish/lang/imports.py +25 -30
  89. omlish/lang/iterables.py +2 -2
  90. omlish/lang/maybes.py +2 -1
  91. omlish/lang/objects.py +17 -11
  92. omlish/lang/resolving.py +1 -1
  93. omlish/lang/strings.py +1 -1
  94. omlish/lang/timeouts.py +53 -0
  95. omlish/lang/typing.py +5 -5
  96. omlish/libc.py +15 -11
  97. omlish/logs/_abc.py +5 -1
  98. omlish/logs/filters.py +2 -0
  99. omlish/logs/formatters.py +6 -2
  100. omlish/logs/utils.py +1 -1
  101. omlish/marshal/base.py +9 -9
  102. omlish/marshal/dataclasses.py +2 -2
  103. omlish/marshal/enums.py +2 -2
  104. omlish/marshal/exceptions.py +1 -1
  105. omlish/marshal/factories.py +10 -10
  106. omlish/marshal/global_.py +10 -4
  107. omlish/marshal/iterables.py +2 -2
  108. omlish/marshal/mappings.py +2 -2
  109. omlish/marshal/objects.py +1 -2
  110. omlish/marshal/optionals.py +4 -4
  111. omlish/marshal/polymorphism.py +4 -4
  112. omlish/marshal/registries.py +3 -3
  113. omlish/marshal/standard.py +6 -6
  114. omlish/marshal/utils.py +3 -3
  115. omlish/marshal/values.py +1 -1
  116. omlish/math.py +9 -9
  117. omlish/os.py +13 -4
  118. omlish/reflect.py +5 -15
  119. omlish/sql/__init__.py +0 -0
  120. omlish/sql/_abc.py +65 -0
  121. omlish/sql/dbs.py +90 -0
  122. omlish/stats.py +7 -8
  123. omlish/term.py +1 -1
  124. omlish/testing/pydevd.py +30 -12
  125. omlish/testing/pytest/inject/__init__.py +7 -0
  126. omlish/testing/pytest/inject/harness.py +24 -2
  127. omlish/testing/pytest/plugins/__init__.py +1 -1
  128. omlish/testing/pytest/plugins/pydevd.py +12 -0
  129. omlish/testing/pytest/plugins/switches.py +3 -3
  130. omlish/testing/testing.py +5 -5
  131. omlish/text/delimit.py +3 -6
  132. omlish/text/parts.py +3 -3
  133. omlish-0.0.0.dev3.dist-info/METADATA +31 -0
  134. omlish-0.0.0.dev3.dist-info/RECORD +191 -0
  135. {omlish-0.0.0.dev1.dist-info → omlish-0.0.0.dev3.dist-info}/WHEEL +1 -1
  136. omlish/lang/classes/test/test_abstract.py +0 -89
  137. omlish/lang/classes/test/test_restrict.py +0 -71
  138. omlish/lang/classes/test/test_simple.py +0 -58
  139. omlish/lang/classes/test/test_virtual.py +0 -72
  140. omlish/testing/pytest/plugins/pycharm.py +0 -54
  141. omlish-0.0.0.dev1.dist-info/METADATA +0 -17
  142. omlish-0.0.0.dev1.dist-info/RECORD +0 -187
  143. /omlish/{lang/classes/test → diag}/__init__.py +0 -0
  144. /omlish/{replserver → diag/replserver}/__init__.py +0 -0
  145. /omlish/{replserver → diag/replserver}/__main__.py +0 -0
  146. {omlish-0.0.0.dev1.dist-info → omlish-0.0.0.dev3.dist-info}/LICENSE +0 -0
  147. {omlish-0.0.0.dev1.dist-info → omlish-0.0.0.dev3.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,13 @@
1
- import abc
2
1
  import dataclasses as dc
3
2
  import threading
4
3
  import typing as ta
5
4
 
6
5
  from .. import check
6
+ from .. import lang
7
7
  from .. import reflect as rfl
8
8
 
9
9
 
10
- class RegistryItem(abc.ABC):
10
+ class RegistryItem(lang.Abstract):
11
11
  pass
12
12
 
13
13
 
@@ -31,7 +31,7 @@ class Registry:
31
31
  super().__init__()
32
32
  self._mtx = threading.Lock()
33
33
  self._dct: dict[rfl.Type, _TypeRegistry] = {}
34
- self._ps: ta.Sequence['Registry'] = []
34
+ self._ps: ta.Sequence[Registry] = []
35
35
 
36
36
  def register(self, rty: rfl.Type, *items: RegistryItem) -> 'Registry':
37
37
  check.isinstance(rty, rfl.TYPES)
@@ -47,9 +47,9 @@ def new_standard_marshaler_factory() -> MarshalerFactory:
47
47
  return TypeCacheFactory( # noqa
48
48
  RecursiveMarshalerFactory(
49
49
  CompositeFactory(
50
- *STANDARD_MARSHALER_FACTORIES
51
- )
52
- )
50
+ *STANDARD_MARSHALER_FACTORIES,
51
+ ),
52
+ ),
53
53
  )
54
54
 
55
55
 
@@ -74,7 +74,7 @@ def new_standard_unmarshaler_factory() -> UnmarshalerFactory:
74
74
  return TypeCacheFactory( # noqa
75
75
  RecursiveUnmarshalerFactory(
76
76
  CompositeFactory(
77
- *STANDARD_UNMARSHALER_FACTORIES
78
- )
79
- )
77
+ *STANDARD_UNMARSHALER_FACTORIES,
78
+ ),
79
+ ),
80
80
  )
omlish/marshal/utils.py CHANGED
@@ -5,7 +5,7 @@ T = ta.TypeVar('T')
5
5
 
6
6
 
7
7
  class _Proxy(ta.Generic[T]):
8
- __obj: ta.Optional[T] = None
8
+ __obj: T | None = None
9
9
 
10
10
  @property
11
11
  def _obj(self) -> T:
@@ -19,5 +19,5 @@ class _Proxy(ta.Generic[T]):
19
19
  self.__obj = obj
20
20
 
21
21
  @classmethod
22
- def _new(cls):
23
- return (p := cls()), p._set_obj
22
+ def _new(cls) -> tuple[ta.Any, ta.Callable[[ta.Any], None]]:
23
+ return (p := cls()), p._set_obj # noqa
omlish/marshal/values.py CHANGED
@@ -13,7 +13,7 @@ Any
13
13
  import typing as ta
14
14
 
15
15
 
16
- Value = ta.Union[
16
+ Value = ta.Union[ # noqa
17
17
  None,
18
18
 
19
19
  bool,
omlish/math.py CHANGED
@@ -66,7 +66,7 @@ class FixedWidthInt(int):
66
66
 
67
67
  MASK: ta.ClassVar[int]
68
68
 
69
- def __init_subclass__(cls, **kwargs):
69
+ def __init_subclass__(cls) -> None:
70
70
  super().__init_subclass__()
71
71
 
72
72
  if not isinstance(cls.BITS, int):
@@ -82,13 +82,13 @@ class FixedWidthInt(int):
82
82
  cls.MASK = (1 << cls.BITS) - 1
83
83
 
84
84
  @classmethod
85
- def clamp(cls, value):
85
+ def clamp(cls, value: int) -> int:
86
86
  return ((value - cls.MIN) & cls.MASK) + cls.MIN
87
87
 
88
- def __new__(cls, value, *args, **kwargs):
89
- return super().__new__(cls, cls.clamp(value))
88
+ def __new__(cls, value: int) -> 'FixedWidthInt':
89
+ return super().__new__(cls, cls.clamp(value)) # noqa
90
90
 
91
- SCALAR_PROXY_METHODS = {
91
+ SCALAR_PROXY_METHODS = frozenset([
92
92
  '__abs__',
93
93
  '__add__',
94
94
  '__and__',
@@ -117,12 +117,12 @@ class FixedWidthInt(int):
117
117
  '__sub__',
118
118
  '__truediv__',
119
119
  '__xor__',
120
- }
120
+ ])
121
121
 
122
- TUPLE_PROXY_METHODS = {
122
+ TUPLE_PROXY_METHODS = frozenset([
123
123
  '__divmod__',
124
124
  '__rdivmod__',
125
- }
125
+ ])
126
126
 
127
127
  for _proxy_name in SCALAR_PROXY_METHODS:
128
128
  locals()[_proxy_name] = _gen_scalar_proxy_method(_proxy_name)
@@ -130,7 +130,7 @@ class FixedWidthInt(int):
130
130
  locals()[_proxy_name] = _gen_tuple_proxy_method(_proxy_name)
131
131
  del _proxy_name
132
132
 
133
- def __repr__(self):
133
+ def __repr__(self) -> str:
134
134
  return f'{self.__class__.__name__}({int(self)})'
135
135
 
136
136
 
omlish/os.py CHANGED
@@ -1,14 +1,23 @@
1
1
  import contextlib
2
+ import resource
2
3
  import shutil
3
4
  import tempfile
4
5
  import typing as ta
5
6
 
6
7
 
8
+ PAGE_SIZE = resource.getpagesize()
9
+
10
+
11
+ def round_to_page_size(sz: int) -> int:
12
+ sz += PAGE_SIZE - 1
13
+ return sz - (sz % PAGE_SIZE)
14
+
15
+
7
16
  @contextlib.contextmanager
8
17
  def tmp_dir(
9
- root_dir: ta.Optional[str] = None,
18
+ root_dir: str | None = None,
10
19
  cleanup: bool = True,
11
- **kwargs: ta.Any
20
+ **kwargs: ta.Any,
12
21
  ) -> ta.Iterator[str]:
13
22
  path = tempfile.mkdtemp(dir=root_dir, **kwargs)
14
23
  try:
@@ -20,9 +29,9 @@ def tmp_dir(
20
29
 
21
30
  @contextlib.contextmanager
22
31
  def tmp_file(
23
- root_dir: ta.Optional[str] = None,
32
+ root_dir: str | None = None,
24
33
  cleanup: bool = True,
25
- **kwargs: ta.Any
34
+ **kwargs: ta.Any,
26
35
  ) -> ta.Iterator[tempfile._TemporaryFileWrapper]: # noqa
27
36
  with tempfile.NamedTemporaryFile(dir=root_dir, delete=False, **kwargs) as f:
28
37
  try:
omlish/reflect.py CHANGED
@@ -21,7 +21,7 @@ else:
21
21
 
22
22
  _NoneType = types.NoneType # type: ignore
23
23
 
24
- _NONE_TYPE_FROZENSET: ta.FrozenSet['Type'] = frozenset([_NoneType])
24
+ _NONE_TYPE_FROZENSET: frozenset['Type'] = frozenset([_NoneType])
25
25
 
26
26
 
27
27
  _GenericAlias = ta._GenericAlias # type: ignore # noqa
@@ -63,16 +63,6 @@ _KNOWN_SPECIAL_TYPE_VARS = tuple(
63
63
  ##
64
64
 
65
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
66
  def get_params(obj: ta.Any) -> tuple[ta.TypeVar, ...]:
77
67
  if isinstance(obj, type):
78
68
  if issubclass(obj, ta.Generic): # type: ignore
@@ -109,7 +99,7 @@ Type = ta.Union[
109
99
 
110
100
 
111
101
  class Union(ta.NamedTuple):
112
- args: ta.FrozenSet[Type]
102
+ args: frozenset[Type]
113
103
 
114
104
  @property
115
105
  def is_optional(self) -> bool:
@@ -131,7 +121,7 @@ class Generic(ta.NamedTuple):
131
121
  # params2: tuple[ta.TypeVar, ...] # map[int, V] = (V,) | map[T, T] = (T,)
132
122
  obj: ta.Any
133
123
 
134
- def __repr__(self):
124
+ def __repr__(self) -> str:
135
125
  return (
136
126
  f'{self.__class__.__name__}('
137
127
  f'cls={self.cls.__name__}, '
@@ -218,7 +208,7 @@ def get_underlying(nt: NewType) -> Type:
218
208
  return type_(nt.obj.__supertype__) # noqa
219
209
 
220
210
 
221
- def get_concrete_type(ty: Type) -> ta.Optional[type]:
211
+ def get_concrete_type(ty: Type) -> type | None:
222
212
  if isinstance(ty, type):
223
213
  return ty
224
214
  if isinstance(ty, Generic):
@@ -299,7 +289,7 @@ class GenericSubstitution:
299
289
  if (cty := get_concrete_type(ty)) is not None:
300
290
  rpl = get_type_var_replacements(ty)
301
291
  ret: list[Type] = []
302
- for b in get_original_bases(cty):
292
+ for b in types.get_original_bases(cty):
303
293
  bty = type_(b)
304
294
  if isinstance(bty, Generic) and isinstance(b, type):
305
295
  # FIXME: throws away relative types, but can't use original vars as they're class-contextual
omlish/sql/__init__.py ADDED
File without changes
omlish/sql/_abc.py ADDED
@@ -0,0 +1,65 @@
1
+ import typing as ta
2
+
3
+
4
+ DBAPITypeCode: ta.TypeAlias = ta.Any | None
5
+
6
+ DBAPIColumnDescription: ta.TypeAlias = tuple[
7
+ str,
8
+ DBAPITypeCode,
9
+ int | None,
10
+ int | None,
11
+ int | None,
12
+ int | None,
13
+ bool | None,
14
+ ]
15
+
16
+
17
+ class DBAPIConnection(ta.Protocol):
18
+ def close(self) -> object: ...
19
+
20
+ def commit(self) -> object: ...
21
+
22
+ # optional:
23
+ # def rollback(self) -> ta.Any: ...
24
+
25
+ def cursor(self) -> 'DBAPICursor': ...
26
+
27
+
28
+ class DBAPICursor(ta.Protocol):
29
+ @property
30
+ def description(self) -> ta.Sequence[DBAPIColumnDescription] | None: ...
31
+
32
+ @property
33
+ def rowcount(self) -> int: ...
34
+
35
+ # optional:
36
+ # def callproc(self, procname: str, parameters: Sequence[ta.Any] = ...) -> Sequence[ta.Any]: ...
37
+
38
+ def close(self) -> object: ...
39
+
40
+ def execute(
41
+ self,
42
+ operation: str,
43
+ parameters: ta.Sequence[ta.Any] | ta.Mapping[str, ta.Any] = ...,
44
+ ) -> object: ...
45
+
46
+ def executemany(
47
+ self,
48
+ operation: str,
49
+ seq_of_parameters: ta.Sequence[ta.Sequence[ta.Any]],
50
+ ) -> object: ...
51
+
52
+ def fetchone(self) -> ta.Sequence[ta.Any] | None: ...
53
+
54
+ def fetchmany(self, size: int = ...) -> ta.Sequence[ta.Sequence[ta.Any]]: ...
55
+
56
+ def fetchall(self) -> ta.Sequence[ta.Sequence[ta.Any]]: ...
57
+
58
+ # optional:
59
+ # def nextset(self) -> None | Literal[True]: ...
60
+
61
+ arraysize: int
62
+
63
+ def setinputsizes(self, sizes: ta.Sequence[DBAPITypeCode | int | None]) -> object: ...
64
+
65
+ def setoutputsize(self, size: int, column: int = ...) -> object: ...
omlish/sql/dbs.py ADDED
@@ -0,0 +1,90 @@
1
+ import typing as ta
2
+ import urllib.parse
3
+
4
+ from .. import dataclasses as dc
5
+ from .. import lang
6
+
7
+
8
+ ##
9
+
10
+
11
+ @dc.dataclass(frozen=True, kw_only=True)
12
+ class DbType:
13
+ name: str
14
+ dialect_name: str
15
+
16
+ default_port: int | None = None
17
+
18
+
19
+ class DbTypes(lang.Namespace):
20
+ MYSQL = DbType(
21
+ name='mysql',
22
+ dialect_name='mysql',
23
+ default_port=3306,
24
+ )
25
+
26
+ POSTGRES = DbType(
27
+ name='postgres',
28
+ dialect_name='postgresql',
29
+ default_port=5432,
30
+ )
31
+
32
+ SQLITE = DbType(
33
+ name='sqlite',
34
+ dialect_name='sqlite',
35
+ )
36
+
37
+
38
+ ##
39
+
40
+
41
+ class DbLoc(lang.Abstract):
42
+ pass
43
+
44
+
45
+ @dc.dataclass(frozen=True)
46
+ class UrlDbLoc(DbLoc, lang.Final):
47
+ url: str
48
+
49
+
50
+ @dc.dataclass(frozen=True)
51
+ class HostDbLoc(DbLoc, lang.Final):
52
+ host: str
53
+ port: int | None = None
54
+
55
+ username: str | None = None
56
+ password: str | None = dc.xfield(default=None, repr_fn=lambda pw: '...' if pw is not None else None)
57
+
58
+
59
+ ##
60
+
61
+
62
+ @dc.dataclass(frozen=True)
63
+ class DbSpec:
64
+ name: str
65
+ type: DbType
66
+ loc: DbLoc
67
+
68
+
69
+ ##
70
+
71
+
72
+ def rebuild_url(url: str, fn: ta.Callable[[urllib.parse.ParseResult], urllib.parse.ParseResult]) -> str:
73
+ if '://' in url:
74
+ engine, _, url = url.partition('://')
75
+ url = 'sql://' + url
76
+ else:
77
+ engine = None
78
+ parsed = urllib.parse.urlparse(url)
79
+ parsed = fn(parsed)
80
+ if engine is not None and parsed.scheme == 'sql':
81
+ parsed = parsed._replace(scheme=engine)
82
+ return urllib.parse.urlunparse(parsed) # noqa
83
+
84
+
85
+ def set_url_engine(url: str, engine: str) -> str:
86
+ return rebuild_url(url, lambda parsed: parsed._replace(scheme=engine)) # noqa
87
+
88
+
89
+ def set_url_database(url: str, database: str) -> str:
90
+ return rebuild_url(url, lambda parsed: parsed._replace(path='/' + database)) # noqa
omlish/stats.py CHANGED
@@ -5,6 +5,7 @@ TODO:
5
5
  """
6
6
  import bisect
7
7
  import collections
8
+ import contextlib
8
9
  import dataclasses as dc
9
10
  import math
10
11
  import operator
@@ -215,7 +216,7 @@ class Stats(ta.Sequence[float]):
215
216
  else:
216
217
  bins = [float(x) for x in bins]
217
218
  if self.min < bins[0]:
218
- bins = [self.min] + bins
219
+ bins = [self.min, *bins]
219
220
 
220
221
  round_factor = 10.0 ** bin_digits
221
222
  bins = [math.floor(b * round_factor) / round_factor for b in bins]
@@ -251,7 +252,7 @@ class SamplingHistogram:
251
252
  sample_percentiles: list['SamplingHistogram.Percentile']
252
253
 
253
254
  DEFAULT_SIZE = 1000
254
- DEFAULT_PERCENTILES = [0.5, 0.75, 0.9, 0.95, 0.99]
255
+ DEFAULT_PERCENTILES = (0.5, 0.75, 0.9, 0.95, 0.99)
255
256
 
256
257
  def __init__(
257
258
  self,
@@ -272,10 +273,10 @@ class SamplingHistogram:
272
273
 
273
274
  self._percentile_pos_list = [self._calc_percentile_pos(p, self._size) for p in self._percentiles]
274
275
 
275
- self._ring: list[ta.Optional[SamplingHistogram.Entry]] = [None] * size
276
+ self._ring: list[SamplingHistogram.Entry | None] = [None] * size
276
277
  self._ring_pos = 0
277
278
 
278
- self._sample: list[ta.Optional[SamplingHistogram.Entry]] = [None] * size
279
+ self._sample: list[SamplingHistogram.Entry | None] = [None] * size
279
280
  self._sample_pos_queue = list(reversed(range(size)))
280
281
 
281
282
  def add(self, value: float) -> None:
@@ -291,10 +292,8 @@ class SamplingHistogram:
291
292
 
292
293
  sample_pos = None
293
294
  if self._sample_pos_queue:
294
- try:
295
+ with contextlib.suppress(IndexError):
295
296
  sample_pos = self._sample_pos_queue.pop()
296
- except IndexError:
297
- pass
298
297
  if sample_pos is None:
299
298
  sample_pos = random.randrange(0, self._size)
300
299
  self._sample[sample_pos] = entry
@@ -303,7 +302,7 @@ class SamplingHistogram:
303
302
  def _calc_percentile_pos(p: float, sz: int) -> int:
304
303
  return int(round((p * sz) - 1))
305
304
 
306
- def _calc_percentiles(self, entries: list[ta.Optional[Entry]]) -> list[Percentile]:
305
+ def _calc_percentiles(self, entries: list[Entry | None]) -> list[Percentile]:
307
306
  entries = list(filter(None, entries))
308
307
  sz = len(entries)
309
308
  if not sz:
omlish/term.py CHANGED
@@ -208,7 +208,7 @@ BG24_RGB = ControlSequence(
208
208
  '24-Bit Background Color (RGB)')
209
209
 
210
210
 
211
- def main():
211
+ def main() -> None:
212
212
  import sys
213
213
 
214
214
  sys.stdout.write(SGR(SGRs.RESET))
omlish/testing/pydevd.py CHANGED
@@ -5,8 +5,27 @@ an already-debugging PyCharm instance to debug PySpark jobs.
5
5
 
6
6
  TODO:
7
7
  - https://www.jetbrains.com/help/pycharm/remote-debugging-with-product.html#
8
- - move to dev?
9
- - cython help? or in cython.py
8
+ - PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py -> USE_LOW_IMPACT_MONITORING
9
+
10
+ ==
11
+
12
+ https://www.jetbrains.com/help/pycharm/remote-debugging-with-product.html#remote-debug-config ->
13
+
14
+ pycharm_port = 43251
15
+ pycharm_version = '241.18034.82'
16
+ buf = textwrap.dedent(f'''
17
+ import subprocess
18
+ import sys
19
+ subprocess.check_call([sys.executable, '-mpip', 'install', f'pydevd-pycharm~={pycharm_version}'])
20
+
21
+ import pydevd_pycharm # noqa
22
+ pydevd_pycharm.settrace(
23
+ 'docker.for.mac.localhost',
24
+ port={pycharm_port},
25
+ stdoutToServer=True,
26
+ stderrToServer=True,
27
+ )
28
+ ''') + '\n' * 2 + buf
10
29
  """
11
30
  import json
12
31
  import os
@@ -40,14 +59,14 @@ def is_debugger_call(hoist: int = 0, walk: int = 2) -> bool:
40
59
  return False
41
60
 
42
61
 
43
- class DebuggerCallForbiddenException(Exception):
62
+ class DebuggerCallForbiddenError(Exception):
44
63
  pass
45
64
 
46
65
 
47
66
  def forbid_debugger_call(hoist: int = 0) -> None:
48
67
  # FIXME: only reentrant?
49
68
  if not ALLOW_DEBUGGER_CALLS and is_debugger_call(hoist + 1):
50
- raise DebuggerCallForbiddenException
69
+ raise DebuggerCallForbiddenError
51
70
 
52
71
 
53
72
  ##
@@ -62,7 +81,7 @@ def silence_subprocess_check() -> None:
62
81
  return
63
82
 
64
83
  new_tb = lang.proxy_import('traceback')
65
- new_tb.print_exc = lambda *a, **k: None # type: ignore
84
+ new_tb.print_exc = lambda *a, **k: None # type: ignore # noqa
66
85
  pydev_monkey.traceback = new_tb
67
86
 
68
87
 
@@ -87,7 +106,7 @@ def patch_for_trio_asyncio() -> None:
87
106
 
88
107
 
89
108
  @lang.cached_function
90
- def _pydevd() -> ta.Optional[types.ModuleType]:
109
+ def _pydevd() -> types.ModuleType | None:
91
110
  try:
92
111
  return __import__('pydevd')
93
112
  except ImportError:
@@ -98,7 +117,7 @@ def is_present() -> bool:
98
117
  return _pydevd() is not None
99
118
 
100
119
 
101
- def get_setup() -> ta.Optional[dict]:
120
+ def get_setup() -> dict | None:
102
121
  if is_present():
103
122
  return _pydevd().SetupHolder.setup
104
123
  else:
@@ -154,8 +173,8 @@ def save_args() -> None:
154
173
 
155
174
  def maybe_reexec(
156
175
  *,
157
- file: ta.Optional[str] = None,
158
- module: ta.Optional[str] = None,
176
+ file: str | None = None,
177
+ module: str | None = None,
159
178
  silence: bool = False,
160
179
  ) -> None:
161
180
  if ARGS_ENV_VAR not in os.environ:
@@ -188,9 +207,8 @@ def maybe_reexec(
188
207
  """))
189
208
  file = bootstrap_path
190
209
 
191
- else:
192
- if file is None:
193
- raise ValueError
210
+ elif file is None:
211
+ raise ValueError
194
212
 
195
213
  args = [sys.executable]
196
214
  args.extend(json.loads(os.environ[ARGS_ENV_VAR]))
@@ -1 +1,8 @@
1
1
  from . import harness # noqa
2
+
3
+ from .harness import ( # noqa
4
+ PytestScope,
5
+ Scopes,
6
+ bind,
7
+ register,
8
+ )
@@ -81,7 +81,7 @@ class Harness:
81
81
 
82
82
  def __getitem__(
83
83
  self,
84
- target: ta.Union[inj.Key[T], type[T]],
84
+ target: inj.Key[T] | type[T],
85
85
  ) -> T:
86
86
  return check.not_none(self._inj)[target]
87
87
 
@@ -141,7 +141,7 @@ class HarnessPlugin:
141
141
  with harness._pytest_scope_manager(PytestScope.CLASS, request): # noqa
142
142
  yield
143
143
 
144
- @pytest.fixture(scope='function', autouse=True)
144
+ @pytest.fixture(scope='function', autouse=True) # noqa
145
145
  def _harness_scope_listener_function(self, harness, request):
146
146
  with harness._pytest_scope_manager(PytestScope.FUNCTION, request): # noqa
147
147
  yield
@@ -153,6 +153,28 @@ class HarnessPlugin:
153
153
  _HARNESS_ELEMENTS_LIST: list[inj.Elements] = []
154
154
 
155
155
 
156
+ def register(*args: inj.Element | inj.Elements) -> None:
157
+ _HARNESS_ELEMENTS_LIST.append(inj.as_elements(*args))
158
+
159
+
160
+ TypeT = ta.TypeVar('TypeT', bound=type)
161
+
162
+
163
+ def bind(
164
+ scope: PytestScope | str = PytestScope.SESSION,
165
+ *,
166
+ eager: bool = False,
167
+ ) -> ta.Callable[[TypeT], TypeT]:
168
+ def inner(cls):
169
+ pts = scope if isinstance(scope, PytestScope) else PytestScope[check.isinstance(scope, str).upper()]
170
+ check.isinstance(cls, type)
171
+ register(inj.as_elements(
172
+ inj.in_(cls, _SCOPES_BY_PYTEST_SCOPE[pts]),
173
+ ))
174
+ return cls
175
+ return inner
176
+
177
+
156
178
  @pytest.fixture(scope='session', autouse=True)
157
179
  def harness() -> ta.Generator[Harness, None, None]:
158
180
  with Harness(inj.as_elements(*_HARNESS_ELEMENTS_LIST)).activate() as h:
@@ -1,6 +1,6 @@
1
1
  from . import ( # noqa
2
2
  logging,
3
- pycharm,
3
+ pydevd,
4
4
  repeat,
5
5
  skips,
6
6
  spacing,
@@ -0,0 +1,12 @@
1
+ from ... import pydevd as opd
2
+ from ._registry import register
3
+
4
+
5
+ @register
6
+ class PydevdPlugin:
7
+
8
+ def pytest_collection(self, session):
9
+ setup = opd.get_setup()
10
+ if setup is not None:
11
+ if hasattr(session.config, '_env_timeout'):
12
+ session.config._env_timeout = None # noqa
@@ -13,7 +13,7 @@ from .... import collections as col
13
13
  from ._registry import register
14
14
 
15
15
 
16
- Configable = ta.Union[pytest.FixtureRequest, pytest.Config]
16
+ Configable = pytest.FixtureRequest | pytest.Config
17
17
 
18
18
 
19
19
  SWITCHES = col.OrderedSet([
@@ -31,13 +31,13 @@ def _get_obj_config(obj: Configable) -> pytest.Config:
31
31
  raise TypeError(obj)
32
32
 
33
33
 
34
- def is_disabled(obj: ta.Optional[Configable], name: str) -> bool:
34
+ def is_disabled(obj: Configable | None, name: str) -> bool:
35
35
  check.isinstance(name, str)
36
36
  check.in_(name, SWITCHES)
37
37
  return obj is not None and _get_obj_config(obj).getoption(f'--no-{name}')
38
38
 
39
39
 
40
- def skip_if_disabled(obj: ta.Optional[Configable], name: str) -> None:
40
+ def skip_if_disabled(obj: Configable | None, name: str) -> None:
41
41
  if is_disabled(obj, name):
42
42
  pytest.skip(f'{name} disabled')
43
43