omlish 0.0.0.dev316__py3-none-any.whl → 0.0.0.dev318__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.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev316'
2
- __revision__ = 'c32f4122c29baf0486a328e8d6e54beb8c47b3fc'
1
+ __version__ = '0.0.0.dev318'
2
+ __revision__ = 'b80bceb394abe03974e4312775224ac1dbcb5b22'
3
3
 
4
4
 
5
5
  #
@@ -103,7 +103,7 @@ class Project(ProjectBase):
103
103
 
104
104
  'sqlean.py ~= 3.49',
105
105
 
106
- 'duckdb ~= 1.2',
106
+ 'duckdb ~= 1.3',
107
107
  ],
108
108
 
109
109
  'templates': [
@@ -58,13 +58,33 @@ def unique(
58
58
  ##
59
59
 
60
60
 
61
+ @ta.overload
62
+ def make_map(
63
+ kvs: ta.Iterable[tuple[K, V]],
64
+ *,
65
+ identity: ta.Literal[False] = False,
66
+ strict: bool = False,
67
+ ) -> dict[K, V]:
68
+ ...
69
+
70
+
71
+ @ta.overload
61
72
  def make_map(
62
73
  kvs: ta.Iterable[tuple[K, V]],
63
74
  *,
64
75
  identity: bool = False,
65
76
  strict: bool = False,
66
77
  ) -> ta.MutableMapping[K, V]:
67
- d: ta.MutableMapping[K, V] = IdentityKeyDict() if identity else {}
78
+ ...
79
+
80
+
81
+ def make_map(
82
+ kvs,
83
+ *,
84
+ identity=False,
85
+ strict=False,
86
+ ):
87
+ d: ta.MutableMapping = IdentityKeyDict() if identity else {}
68
88
  for k, v in kvs:
69
89
  if k in d:
70
90
  if strict:
@@ -74,6 +94,21 @@ def make_map(
74
94
  return d
75
95
 
76
96
 
97
+ #
98
+
99
+
100
+ @ta.overload
101
+ def make_map_by(
102
+ fn: ta.Callable[[V], K],
103
+ vs: ta.Iterable[V],
104
+ *,
105
+ identity: ta.Literal[False] = False,
106
+ strict: bool = False,
107
+ ) -> dict[K, V]:
108
+ ...
109
+
110
+
111
+ @ta.overload
77
112
  def make_map_by(
78
113
  fn: ta.Callable[[V], K],
79
114
  vs: ta.Iterable[V],
@@ -81,6 +116,16 @@ def make_map_by(
81
116
  identity: bool = False,
82
117
  strict: bool = False,
83
118
  ) -> ta.MutableMapping[K, V]:
119
+ ...
120
+
121
+
122
+ def make_map_by(
123
+ fn,
124
+ vs,
125
+ *,
126
+ identity=False,
127
+ strict=False,
128
+ ):
84
129
  return make_map(
85
130
  ((fn(v), v) for v in vs),
86
131
  identity=identity,
@@ -91,9 +136,30 @@ def make_map_by(
91
136
  ##
92
137
 
93
138
 
94
- def multi_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.MutableMapping[K, list[V]]:
95
- d: ta.MutableMapping[K, list[V]] = IdentityKeyDict() if identity else {}
96
- l: list[V]
139
+ @ta.overload
140
+ def multi_map(
141
+ kvs: ta.Iterable[tuple[K, V]],
142
+ *,
143
+ identity: ta.Literal[False] = False,
144
+ ) -> dict[K, list[V]]:
145
+ ...
146
+
147
+
148
+ @ta.overload
149
+ def multi_map(
150
+ kvs: ta.Iterable[tuple[K, V]],
151
+ *,
152
+ identity: bool = False,
153
+ ) -> ta.MutableMapping[K, list[V]]:
154
+ ...
155
+
156
+
157
+ def multi_map(
158
+ kvs,
159
+ *,
160
+ identity=False,
161
+ ):
162
+ d: ta.MutableMapping = IdentityKeyDict() if identity else {}
97
163
  for k, v in kvs:
98
164
  try:
99
165
  l = d[k]
@@ -103,7 +169,35 @@ def multi_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.Mu
103
169
  return d
104
170
 
105
171
 
106
- def multi_map_by(fn: ta.Callable[[V], K], vs: ta.Iterable[V], *, identity: bool = False) -> ta.MutableMapping[K, list[V]]: # noqa
172
+ #
173
+
174
+
175
+ @ta.overload
176
+ def multi_map_by(
177
+ fn: ta.Callable[[V], K],
178
+ vs: ta.Iterable[V],
179
+ *,
180
+ identity: ta.Literal[False] = False,
181
+ ) -> dict[K, list[V]]: # noqa
182
+ ...
183
+
184
+
185
+ @ta.overload
186
+ def multi_map_by(
187
+ fn: ta.Callable[[V], K],
188
+ vs: ta.Iterable[V],
189
+ *,
190
+ identity: bool = False,
191
+ ) -> ta.MutableMapping[K, list[V]]: # noqa
192
+ ...
193
+
194
+
195
+ def multi_map_by(
196
+ fn,
197
+ vs,
198
+ *,
199
+ identity=False,
200
+ ):
107
201
  return multi_map(((fn(v), v) for v in vs), identity=identity)
108
202
 
109
203
 
@@ -12,6 +12,8 @@ from ...specs import FieldSpec
12
12
  def std_params_to_class_spec(
13
13
  p: StdParams,
14
14
  fields: ta.Sequence[FieldSpec],
15
+ *,
16
+ metadata: ta.Sequence[ta.Any] | None = None,
15
17
  ) -> ClassSpec:
16
18
  return ClassSpec(
17
19
  fields=fields,
@@ -27,4 +29,6 @@ def std_params_to_class_spec(
27
29
  kw_only=check.isinstance(p.kw_only, bool),
28
30
  slots=check.isinstance(p.slots, bool),
29
31
  weakref_slot=check.isinstance(p.weakref_slot, bool),
32
+
33
+ metadata=metadata,
30
34
  )
@@ -3,6 +3,7 @@ import typing as ta
3
3
 
4
4
  from .. import lang
5
5
  from .api.classes.conversion import std_params_to_class_spec
6
+ from .api.classes.metadata import extract_cls_metadata
6
7
  from .api.classes.params import get_class_spec
7
8
  from .api.fields.conversion import std_field_to_field_spec
8
9
  from .concerns.fields import InitFields
@@ -49,9 +50,12 @@ class ClassReflection:
49
50
  for f in dc.fields(self._cls) # noqa
50
51
  ]
51
52
 
53
+ cmd = extract_cls_metadata(self._cls, deep=True)
54
+
52
55
  cs = std_params_to_class_spec(
53
56
  p,
54
57
  fsl,
58
+ metadata=cmd.user_metadata or None,
55
59
  )
56
60
 
57
61
  return cs
@@ -59,6 +59,9 @@ class _cached_nullary: # noqa
59
59
  #
60
60
 
61
61
 
62
+ _repr = repr
63
+
64
+
62
65
  def _attr_repr(obj, *atts):
63
66
  return f'{obj.__class__.__name__}({", ".join(f"{a}={getattr(obj, a)!r}" for a in atts)})'
64
67
 
@@ -127,6 +130,109 @@ class AsJson:
127
130
  ##
128
131
 
129
132
 
133
+ class SysModule(AttrsClass, AsJson):
134
+ def __init__(
135
+ self,
136
+ name, # type: str
137
+ *,
138
+ seq=None, # type: int | None
139
+ repr=None, # type: str | None # noqa
140
+
141
+ origin=None, # type: str | None
142
+ file=None, # type: str | None
143
+ ) -> None:
144
+ super().__init__()
145
+
146
+ self._name = name
147
+
148
+ self._seq = seq
149
+ self._repr = repr
150
+
151
+ self._origin = origin
152
+ self._file = file
153
+
154
+ __attrs__ = (
155
+ 'name',
156
+
157
+ 'seq',
158
+ 'repr',
159
+
160
+ 'origin',
161
+ 'file',
162
+ )
163
+
164
+ @property
165
+ def name(self): # type: () -> str
166
+ return self._name
167
+
168
+ @property
169
+ def seq(self): # type: () -> int | None
170
+ return self._seq
171
+
172
+ @property
173
+ def repr(self): # type: () -> str | None
174
+ return self._repr
175
+
176
+ @property
177
+ def origin(self): # type: () -> str | None
178
+ return self._origin
179
+
180
+ @property
181
+ def file(self): # type: () -> str | None
182
+ return self._file
183
+
184
+ def as_json(self): # type: () -> dict[str, object]
185
+ return self.attrs_dict()
186
+
187
+
188
+ def build_sys_module(
189
+ name, # type: str
190
+ mod,
191
+ *,
192
+ seq=None, # type: int | None
193
+ ) -> SysModule:
194
+ kwargs = dict() # type: dict
195
+ kwargs.update(
196
+ name=name,
197
+
198
+ seq=seq,
199
+ repr=_repr(mod),
200
+ )
201
+
202
+ try:
203
+ spec = mod.__spec__
204
+ except AttributeError:
205
+ pass
206
+ else:
207
+ if spec is not None:
208
+ kwargs.update(origin=spec.origin)
209
+
210
+ try:
211
+ file = mod.__file__
212
+ except AttributeError:
213
+ pass
214
+ else:
215
+ kwargs.update(file=file)
216
+
217
+ return SysModule(**kwargs)
218
+
219
+
220
+ def build_sys_modules(dct=None): # type: (dict[str, object] | None) -> list[SysModule]
221
+ if dct is None:
222
+ dct_ = sys.modules
223
+ else:
224
+ dct_ = dct # type: ignore
225
+
226
+ lst = [] # type: list[SysModule]
227
+ for i, (n, m) in enumerate(dct_.items()):
228
+ lst.append(build_sys_module(n, m, seq=i))
229
+
230
+ return lst
231
+
232
+
233
+ ##
234
+
235
+
130
236
  class RunEnv(AttrsClass, AsJson):
131
237
  def __init__(
132
238
  self,
@@ -143,6 +249,7 @@ class RunEnv(AttrsClass, AsJson):
143
249
  pycharm_hosted=None, # type: bool | None
144
250
 
145
251
  sys_path=None, # type: list[str] | None
252
+ sys_modules=None, # type: list[SysModule] | None
146
253
  ) -> None:
147
254
  super().__init__()
148
255
 
@@ -182,6 +289,10 @@ class RunEnv(AttrsClass, AsJson):
182
289
  sys_path = list(sys.path)
183
290
  self._sys_path = sys_path
184
291
 
292
+ if sys_modules is None:
293
+ sys_modules = build_sys_modules()
294
+ self._sys_modules = sys_modules
295
+
185
296
  __attrs__ = (
186
297
  'argv',
187
298
  'orig_argv',
@@ -195,6 +306,7 @@ class RunEnv(AttrsClass, AsJson):
195
306
  'pycharm_hosted',
196
307
 
197
308
  'sys_path',
309
+ 'sys_modules',
198
310
  )
199
311
 
200
312
  @property
@@ -233,8 +345,15 @@ class RunEnv(AttrsClass, AsJson):
233
345
  def sys_path(self): # type: () -> list[str]
234
346
  return self._sys_path
235
347
 
348
+ @property
349
+ def sys_modules(self): # type: () -> list[SysModule]
350
+ return self._sys_modules
351
+
236
352
  def as_json(self): # type: () -> dict[str, object]
237
- return self.attrs_dict()
353
+ return {
354
+ **self.attrs_dict(),
355
+ 'sys_modules': [m.as_json() for m in self.sys_modules],
356
+ }
238
357
 
239
358
 
240
359
  ##
@@ -29,7 +29,7 @@ import typing as ta
29
29
  _ESCAPE_PAT = re.compile(r'[\x00-\x1f\\"]')
30
30
  _ESCAPE_ASCII_PAT = re.compile(r'([\\"]|[^\ -~])')
31
31
 
32
- _ESCAPE_DCT = {
32
+ ESCAPE_MAP: ta.Mapping[str, str] = {
33
33
  **{
34
34
  chr(i): f'\\u{i:04x}'
35
35
  for i in range(0x20)
@@ -56,19 +56,35 @@ def _convert_to_string(s: str | bytes) -> str:
56
56
  return s
57
57
 
58
58
 
59
- def encode_string(s: str | bytes, q: str = '"') -> str:
60
- """Return a JSON representation of a Python string"""
59
+ def encode_string(
60
+ s: str | bytes,
61
+ q: str = '"',
62
+ *,
63
+ escape_map: ta.Mapping[str, str] | None = None,
64
+ ) -> str:
65
+ """Return a JSON representation of a Python string."""
66
+
67
+ if escape_map is None:
68
+ escape_map = ESCAPE_MAP
61
69
 
62
70
  s = _convert_to_string(s)
63
71
 
64
72
  def replace(m):
65
- return _ESCAPE_DCT[m.group(0)]
73
+ return escape_map[m.group(0)]
66
74
 
67
75
  return q + _ESCAPE_PAT.sub(replace, s) + q
68
76
 
69
77
 
70
- def encode_string_ascii(s: str | bytes, q: str = '"') -> str:
71
- """Return an ASCII-only JSON representation of a Python string"""
78
+ def encode_string_ascii(
79
+ s: str | bytes,
80
+ q: str = '"',
81
+ *,
82
+ escape_map: ta.Mapping[str, str] | None = None,
83
+ ) -> str:
84
+ """Return an ASCII-only JSON representation of a Python string."""
85
+
86
+ if escape_map is None:
87
+ escape_map = ESCAPE_MAP
72
88
 
73
89
  s = _convert_to_string(s)
74
90
 
@@ -76,7 +92,7 @@ def encode_string_ascii(s: str | bytes, q: str = '"') -> str:
76
92
  s = m.group(0)
77
93
 
78
94
  try:
79
- return _ESCAPE_DCT[s]
95
+ return escape_map[s]
80
96
 
81
97
  except KeyError:
82
98
  n = ord(s)
@@ -102,7 +118,7 @@ def _scan_four_digit_hex(
102
118
  s: str,
103
119
  end: int,
104
120
  ):
105
- """Scan a four digit hex number from s[end:end + 4]"""
121
+ """Scan a four digit hex number from s[end:end + 4]."""
106
122
 
107
123
  msg = 'Invalid \\uXXXX escape sequence'
108
124
  esc = s[end: end + 4]
@@ -117,7 +133,7 @@ def _scan_four_digit_hex(
117
133
 
118
134
  _STRING_CHUNK_PAT = re.compile(r'(.*?)(["\\\x00-\x1f])', re.VERBOSE | re.MULTILINE | re.DOTALL)
119
135
 
120
- _BACKSLASH_MAP = {
136
+ BACKSLASH_MAP: ta.Mapping[str, str] = {
121
137
  '"': '"',
122
138
  '\\': '\\',
123
139
  '/': '/',
@@ -180,7 +196,7 @@ def parse_string(
180
196
  # If not a unicode escape sequence, must be in the lookup table
181
197
  if esc != 'u':
182
198
  try:
183
- char = _BACKSLASH_MAP[esc]
199
+ char = BACKSLASH_MAP[esc]
184
200
  except KeyError:
185
201
  msg = 'Invalid \\X escape sequence %r'
186
202
  raise json.JSONDecodeError(msg, s, end) from None
@@ -26,6 +26,7 @@ class AbstractJsonRenderer(lang.Abstract, ta.Generic[I]):
26
26
 
27
27
  def __init__(
28
28
  self,
29
+ *,
29
30
  indent: int | str | None = None,
30
31
  separators: tuple[str, str] | None = None,
31
32
  sort_keys: bool = False,
@@ -0,0 +1,44 @@
1
+ import typing as ta
2
+
3
+ from ..json import Scalar
4
+ from ..json.literals import ESCAPE_MAP
5
+ from ..json.literals import encode_string
6
+ from ..json.literals import encode_string_ascii
7
+ from ..json.rendering import JsonRenderer
8
+ from ..json.rendering import JsonRendererOut
9
+
10
+
11
+ ##
12
+
13
+
14
+ MULTILINE_STRINGS_ESCAPE_MAP = {
15
+ **ESCAPE_MAP,
16
+ '\n': '\\n\\\n',
17
+ }
18
+
19
+
20
+ class Json5Renderer(JsonRenderer):
21
+ def __init__(
22
+ self,
23
+ out: JsonRendererOut,
24
+ *,
25
+ multiline_strings: bool = False,
26
+ **kwargs: ta.Any,
27
+ ) -> None:
28
+ super().__init__(out, **kwargs)
29
+
30
+ self._multiline_strings = multiline_strings
31
+
32
+ def _format_scalar(self, o: Scalar) -> str:
33
+ if (
34
+ self._multiline_strings and
35
+ isinstance(o, str) and
36
+ '\n' in o
37
+ ):
38
+ if self._ensure_ascii:
39
+ return encode_string_ascii(o, escape_map=MULTILINE_STRINGS_ESCAPE_MAP)
40
+ else:
41
+ return encode_string(o, escape_map=MULTILINE_STRINGS_ESCAPE_MAP)
42
+
43
+ else:
44
+ return super()._format_scalar(o)
@@ -22,6 +22,7 @@ TODO:
22
22
  import dataclasses as dc
23
23
  import functools
24
24
  import inspect
25
+ import types
25
26
  import typing as ta
26
27
 
27
28
  from ..classes.abstract import Abstract
@@ -153,6 +154,10 @@ def _make_cache_key_maker(
153
154
  ##
154
155
 
155
156
 
157
+ class _CachedException(ta.NamedTuple):
158
+ ex: BaseException
159
+
160
+
156
161
  class _CachedFunction(ta.Generic[T], Abstract):
157
162
  @dc.dataclass(frozen=True, kw_only=True)
158
163
  class Opts:
@@ -160,6 +165,19 @@ class _CachedFunction(ta.Generic[T], Abstract):
160
165
  lock: DefaultLockable = None
161
166
  transient: bool = False
162
167
  no_wrapper_update: bool = False
168
+ cache_exceptions: type[BaseException] | tuple[type[BaseException], ...] | None = None
169
+
170
+ def __post_init__(self) -> None:
171
+ if (ce := self.cache_exceptions) is not None:
172
+ if isinstance(ce, type):
173
+ if not issubclass(ce, BaseException):
174
+ raise TypeError(ce)
175
+ elif isinstance(ce, tuple):
176
+ for e in ce:
177
+ if not issubclass(e, BaseException):
178
+ raise TypeError(e)
179
+ else:
180
+ raise TypeError(ce)
163
181
 
164
182
  def __init__(
165
183
  self,
@@ -199,13 +217,33 @@ class _CachedFunction(ta.Generic[T], Abstract):
199
217
  def __call__(self, *args, **kwargs) -> T:
200
218
  k = self._key_maker(*args, **kwargs)
201
219
 
202
- try:
203
- return self._values[k]
204
- except KeyError:
205
- pass
220
+ if (ce := self._opts.cache_exceptions) is None:
221
+ try:
222
+ return self._values[k]
223
+ except KeyError:
224
+ pass
206
225
 
207
- def call_value_fn():
208
- return self._value_fn(*args, **kwargs)
226
+ else:
227
+ try:
228
+ hit = self._values[k]
229
+ except KeyError:
230
+ pass
231
+ else:
232
+ if isinstance(hit, _CachedException):
233
+ raise hit.ex
234
+ else:
235
+ return hit
236
+
237
+ if ce is None:
238
+ def call_value_fn():
239
+ return self._value_fn(*args, **kwargs)
240
+
241
+ else:
242
+ def call_value_fn():
243
+ try:
244
+ return self._value_fn(*args, **kwargs)
245
+ except ce as ex: # type: ignore[misc]
246
+ return _CachedException(ex)
209
247
 
210
248
  if self._lock is not None:
211
249
  with self._lock:
@@ -220,7 +258,11 @@ class _CachedFunction(ta.Generic[T], Abstract):
220
258
  value = call_value_fn()
221
259
 
222
260
  self._values[k] = value
223
- return value
261
+
262
+ if isinstance(value, _CachedException):
263
+ raise value.ex
264
+ else:
265
+ return value
224
266
 
225
267
 
226
268
  #
@@ -369,12 +411,27 @@ def cached_function(fn=None, **kwargs): # noqa
369
411
 
370
412
  opts = _CachedFunction.Opts(**kwargs)
371
413
 
414
+ if isinstance(fn, types.MethodType):
415
+ return _FreeCachedFunction(
416
+ fn,
417
+ opts=opts,
418
+ key_maker=_make_cache_key_maker(fn, bound=True),
419
+ )
420
+
372
421
  if isinstance(fn, staticmethod):
373
- return _FreeCachedFunction(fn, opts=opts, value_fn=unwrap_func(fn))
422
+ return _FreeCachedFunction(
423
+ fn,
424
+ opts=opts,
425
+ value_fn=unwrap_func(fn),
426
+ )
374
427
 
375
428
  scope = classmethod if isinstance(fn, classmethod) else None
376
429
 
377
- return _DescriptorCachedFunction(fn, scope, opts=opts)
430
+ return _DescriptorCachedFunction(
431
+ fn,
432
+ scope,
433
+ opts=opts,
434
+ )
378
435
 
379
436
 
380
437
  def static_init(fn: CallableT) -> CallableT:
omlish/lite/maybes.py CHANGED
@@ -166,9 +166,11 @@ class _JustMaybe(_Maybe[T]):
166
166
  def __repr__(self) -> str:
167
167
  return f'just({self._v!r})'
168
168
 
169
+ _hash: int
170
+
169
171
  def __hash__(self) -> int:
170
172
  try:
171
- return self._hash # type: ignore[has-type]
173
+ return self._hash
172
174
  except AttributeError:
173
175
  pass
174
176
  h = self._hash = hash((_JustMaybe, self._v))
omlish/sockets/ports.py CHANGED
@@ -16,21 +16,38 @@ DEFAULT_AVAILABLE_PORT_HOST: str = '127.0.0.1'
16
16
 
17
17
 
18
18
  @contextlib.contextmanager
19
- def get_available_port_context(host: ta.Optional[str] = None) -> ta.Iterator[int]:
19
+ def get_available_port_context(
20
+ host: ta.Optional[str] = None,
21
+ family: int = socket.AF_INET,
22
+ type: int = socket.SOCK_STREAM, # noqa
23
+ *,
24
+ listen: ta.Union[bool, int, None] = False,
25
+ ) -> ta.Iterator[int]:
20
26
  if host is None:
21
27
  host = DEFAULT_AVAILABLE_PORT_HOST
22
28
 
23
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
29
+ if listen is not None:
30
+ if listen is True:
31
+ listen = 1
32
+ elif listen is False:
33
+ listen = None
34
+ else:
35
+ listen = check.isinstance(listen, int)
36
+
37
+ with socket.socket(family, type) as sock:
24
38
  sock.bind((host, 0))
25
- sock.listen(1)
39
+
40
+ if listen is not None:
41
+ sock.listen(listen)
42
+
26
43
  port = sock.getsockname()[1]
44
+
27
45
  yield port
28
46
 
29
47
 
30
- def get_available_port(host: ta.Optional[str] = None) -> int:
31
- with get_available_port_context(host) as port:
32
- pass
33
- return port
48
+ def get_available_port(*args: ta.Any, **kwargs: ta.Any) -> int:
49
+ with get_available_port_context(*args, **kwargs) as port:
50
+ return port
34
51
 
35
52
 
36
53
  def get_available_ports(
@@ -4,29 +4,87 @@ TODO:
4
4
  - dynamic registration
5
5
  - dynamic switching (skip docker if not running, skip online if not online, ...)
6
6
  """
7
+ import dataclasses as dc
7
8
  import typing as ta
8
9
 
9
10
  import pytest
10
11
 
11
12
  from .... import check
12
13
  from .... import collections as col
13
- from .... import lang
14
14
  from ....docker import all as docker
15
15
  from ._registry import register
16
16
 
17
17
 
18
- Configable = pytest.FixtureRequest | pytest.Config
18
+ Configable: ta.TypeAlias = pytest.FixtureRequest | pytest.Config
19
19
 
20
20
 
21
- SWITCHES: ta.Mapping[str, bool | ta.Callable[[], bool]] = {
22
- 'docker': docker.has_cli,
23
- 'docker-guest': docker.is_likely_in_docker,
24
- 'online': True,
25
- 'integration': True,
26
- 'slow': False,
27
- }
21
+ ##
22
+
23
+
24
+ @dc.dataclass(frozen=True, eq=False)
25
+ class Switch:
26
+ name: str
27
+ _default_enabled: bool | ta.Callable[[], bool]
28
+
29
+ _: dc.KW_ONLY
30
+
31
+ add_marks: ta.Sequence[ta.Any] | None = None
32
+
33
+ def default_enabled(self) -> bool:
34
+ if isinstance(e := self._default_enabled, bool):
35
+ return e
36
+ elif callable(e):
37
+ return check.isinstance(e(), bool)
38
+ else:
39
+ raise TypeError(e)
40
+
41
+ @property
42
+ def attr(self) -> str:
43
+ return self.name.replace('-', '_')
44
+
45
+
46
+ SWITCHES: ta.Sequence[Switch] = [
47
+ Switch(
48
+ 'name',
49
+ docker.has_cli,
50
+ ),
51
+
52
+ Switch(
53
+ 'docker-guest',
54
+ docker.is_likely_in_docker,
55
+ ),
56
+
57
+ Switch(
58
+ 'online',
59
+ True,
60
+ ),
28
61
 
29
- SWITCH_ATTRS = {k.replace('-', '_'): k for k in SWITCHES}
62
+ Switch(
63
+ 'integration',
64
+ True,
65
+ ),
66
+
67
+ Switch(
68
+ 'high-mem',
69
+ True,
70
+ add_marks=[
71
+ # https://pytest-xdist.readthedocs.io/en/latest/distribution.html
72
+ pytest.mark.xdist_group('high-mem'),
73
+ ],
74
+ ),
75
+
76
+ Switch(
77
+ 'slow',
78
+ False,
79
+ ),
80
+ ]
81
+
82
+
83
+ SWITCHES_BY_NAME: ta.Mapping[str, Switch] = col.make_map_by(lambda sw: sw.name, SWITCHES, strict=True)
84
+ SWITCHES_BY_ATTR: ta.Mapping[str, Switch] = col.make_map_by(lambda sw: sw.attr, SWITCHES, strict=True)
85
+
86
+
87
+ ##
30
88
 
31
89
 
32
90
  SwitchState: ta.TypeAlias = bool | ta.Literal['only']
@@ -49,7 +107,7 @@ def _get_obj_config(obj: Configable) -> pytest.Config:
49
107
 
50
108
  def is_disabled(obj: Configable | None, name: str) -> bool:
51
109
  check.isinstance(name, str)
52
- check.in_(name, SWITCHES)
110
+ check.in_(name, SWITCHES_BY_NAME)
53
111
  return obj is not None and _get_obj_config(obj).getoption(f'--no-{name}')
54
112
 
55
113
 
@@ -58,17 +116,17 @@ def skip_if_disabled(obj: Configable | None, name: str) -> None:
58
116
  pytest.skip(f'{name} disabled')
59
117
 
60
118
 
61
- def get_specified_switches(obj: Configable) -> ta.Mapping[str, SwitchState]:
62
- ret: dict[str, SwitchState] = {}
119
+ def get_specified_switches(obj: Configable) -> dict[Switch, SwitchState]:
120
+ ret: dict[Switch, SwitchState] = {}
63
121
  for sw in SWITCHES:
64
122
  sts = {
65
123
  st
66
124
  for st, pfx in SWITCH_STATE_OPT_PREFIXES.items()
67
- if _get_obj_config(obj).getoption(pfx + sw)
125
+ if _get_obj_config(obj).getoption(pfx + sw.name)
68
126
  }
69
127
  if sts:
70
128
  if len(sts) > 1:
71
- raise Exception(f'Multiple switches specified for {sw}')
129
+ raise Exception(f'Multiple switches specified for {sw.name}')
72
130
  ret[sw] = check.single(sts)
73
131
  return ret
74
132
 
@@ -76,49 +134,50 @@ def get_specified_switches(obj: Configable) -> ta.Mapping[str, SwitchState]:
76
134
  @register
77
135
  class SwitchesPlugin:
78
136
  def pytest_configure(self, config):
79
- for sw in SWITCH_ATTRS:
80
- config.addinivalue_line('markers', f'{sw}: mark test as {sw}')
81
- config.addinivalue_line('markers', f'not_{sw}: mark test as not {sw}')
137
+ for sw in SWITCHES:
138
+ config.addinivalue_line('markers', f'{sw.attr}: mark test as {sw.attr}')
139
+ config.addinivalue_line('markers', f'not_{sw.attr}: mark test as not {sw.attr}')
82
140
 
83
141
  def pytest_addoption(self, parser):
84
142
  for sw in SWITCHES:
85
- parser.addoption(f'--no-{sw}', action='store_true', default=False, help=f'disable {sw} tests')
86
- parser.addoption(f'--{sw}', action='store_true', default=False, help=f'enables {sw} tests')
87
- parser.addoption(f'--only-{sw}', action='store_true', default=False, help=f'enables only {sw} tests')
88
-
89
- @lang.cached_function
90
- def get_switches(self) -> ta.Mapping[str, SwitchState]:
91
- return {
92
- k: v() if callable(v) else v
93
- for k, v in SWITCHES.items()
94
- }
143
+ parser.addoption(f'--no-{sw.name}', action='store_true', default=False, help=f'disable {sw.name} tests')
144
+ parser.addoption(f'--{sw.name}', action='store_true', default=False, help=f'enables {sw.name} tests')
145
+ parser.addoption(f'--only-{sw.name}', action='store_true', default=False, help=f'enables only {sw.name} tests') # noqa
95
146
 
96
147
  def pytest_collection_modifyitems(self, config, items):
97
- sts = {
98
- **self.get_switches(),
148
+ switch_states: dict[Switch, SwitchState] = {
149
+ **{
150
+ sw: sw.default_enabled()
151
+ for sw in SWITCHES
152
+ },
99
153
  **get_specified_switches(config),
100
154
  }
101
155
 
102
- stx = col.multi_map(map(reversed, sts.items())) # type: ignore
103
- ts, fs, onlys = (stx.get(k, ()) for k in (True, False, 'only'))
156
+ inv_switch_states: dict[SwitchState, list[Switch]] = col.multi_map((st, sw) for sw, st in switch_states.items())
157
+ true_switches = frozenset(inv_switch_states.get(True, ()))
158
+ false_switches = frozenset(inv_switch_states.get(False, ()))
159
+ only_switches = frozenset(inv_switch_states.get('only', ()))
104
160
 
105
161
  def process(item):
106
- sws = {sw for swa, sw in SWITCH_ATTRS.items() if swa in item.keywords}
107
- nsws = {sw for swa, sw in SWITCH_ATTRS.items() if ('not_' + swa) in item.keywords}
162
+ item_switches = {sw for sw in SWITCHES if sw.attr in item.keywords}
163
+ item_not_switches = {sw for sw in SWITCHES if ('not_' + sw.attr) in item.keywords}
164
+
165
+ for sw in item_switches:
166
+ for mk in sw.add_marks or []:
167
+ item.add_marker(mk)
108
168
 
109
- if onlys:
110
- if not any(sw in onlys for sw in sws):
111
- item.add_marker(pytest.mark.skip(reason=f'skipping switches {sws}'))
169
+ if only_switches:
170
+ if not any(sw in only_switches for sw in item_switches):
171
+ item.add_marker(pytest.mark.skip(reason=f'skipping switches {item_switches}'))
112
172
  return
113
173
 
114
- else:
115
- for sw in sws:
116
- if sw in fs:
117
- item.add_marker(pytest.mark.skip(reason=f'skipping switches {sw}'))
174
+ for sw in item_switches:
175
+ if sw in false_switches:
176
+ item.add_marker(pytest.mark.skip(reason=f'skipping switches {sw}'))
118
177
 
119
- for nsw in nsws:
120
- if nsw in ts:
121
- item.add_marker(pytest.mark.skip(reason=f'skipping switches {nsw}'))
178
+ for sw in item_not_switches:
179
+ if sw in true_switches:
180
+ item.add_marker(pytest.mark.skip(reason=f'skipping switches {sw}'))
122
181
 
123
182
  for item in items:
124
183
  process(item)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish
3
- Version: 0.0.0.dev316
3
+ Version: 0.0.0.dev318
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -41,7 +41,7 @@ Requires-Dist: aiosqlite~=0.21; extra == "all"
41
41
  Requires-Dist: asyncpg~=0.30; extra == "all"
42
42
  Requires-Dist: apsw~=3.49; extra == "all"
43
43
  Requires-Dist: sqlean.py~=3.49; extra == "all"
44
- Requires-Dist: duckdb~=1.2; extra == "all"
44
+ Requires-Dist: duckdb~=1.3; extra == "all"
45
45
  Requires-Dist: markupsafe~=3.0; extra == "all"
46
46
  Requires-Dist: jinja2~=3.1; extra == "all"
47
47
  Requires-Dist: pytest~=8.3; extra == "all"
@@ -89,7 +89,7 @@ Requires-Dist: aiosqlite~=0.21; extra == "sqldrivers"
89
89
  Requires-Dist: asyncpg~=0.30; extra == "sqldrivers"
90
90
  Requires-Dist: apsw~=3.49; extra == "sqldrivers"
91
91
  Requires-Dist: sqlean.py~=3.49; extra == "sqldrivers"
92
- Requires-Dist: duckdb~=1.2; extra == "sqldrivers"
92
+ Requires-Dist: duckdb~=1.3; extra == "sqldrivers"
93
93
  Provides-Extra: templates
94
94
  Requires-Dist: markupsafe~=3.0; extra == "templates"
95
95
  Requires-Dist: jinja2~=3.1; extra == "templates"
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=orgsRvtpHu8tdhaCvlP9v3P495OJopYYiHKjK68WtWg,8587
2
- omlish/__about__.py,sha256=Lteg-7hW9bBGPlLc6vR48ef4x6Ods9p5RyKlY5-teWw,3478
2
+ omlish/__about__.py,sha256=kisuBtrw4R5wrzm5nluakJkzLD7HYXcYuB7zYWAgrQc,3478
3
3
  omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
4
4
  omlish/c3.py,sha256=rer-TPOFDU6fYq_AWio_AmA-ckZ8JDY5shIzQ_yXfzA,8414
5
5
  omlish/cached.py,sha256=MLap_p0rdGoDIMVhXVHm1tsbcWobJF0OanoodV03Ju8,542
@@ -87,7 +87,7 @@ omlish/collections/mappings.py,sha256=iXb7oq1rCQak0KgzblgrzWCJLrkfJAYHFvl9lprOVU
87
87
  omlish/collections/ordered.py,sha256=7zTbrAt12rf6i33XHkQERKar258fJacaw_WbtGEBgWo,2338
88
88
  omlish/collections/ranked.py,sha256=McB8C2UQfUvrbmxGTpBz1-EZuyCLkBFtktzncMdt8_Y,2287
89
89
  omlish/collections/unmodifiable.py,sha256=X7kKhPFdZF4m28SfLDxZL-riWlhbZffdPv35aTP30YM,4753
90
- omlish/collections/utils.py,sha256=voqecDPFuqSLN8IwJDKyy-HnecEU4JYOXxd1HAaEuMY,2900
90
+ omlish/collections/utils.py,sha256=1LED_KLXsEb39Byhql7BD5ObHoSkFn2OyUdnHsKXKNo,4091
91
91
  omlish/collections/cache/__init__.py,sha256=D1gO71VcwxFTZP9gAc9isHfg_TEdalwhsJcgGLvS9hg,233
92
92
  omlish/collections/cache/descriptor.py,sha256=F9aRsF-xOUcNMzTwIWrUpKTjop1Z-oItQqJDwVaR0FU,5026
93
93
  omlish/collections/cache/impl.py,sha256=Y18OcAsNL6dIWuk89UlZBpqq0iBU-P4VDio6eis43Us,14760
@@ -139,12 +139,12 @@ omlish/dataclasses/debug.py,sha256=giBiv6aXvX0IagwNCW64qBzNjfOFr3-VmgDy_KYlb-k,2
139
139
  omlish/dataclasses/errors.py,sha256=tyv3WR6az66uGGiq9FIuCHvy1Ef-G7zeMY7mMG6hy2Y,2527
140
140
  omlish/dataclasses/inspect.py,sha256=BlpPghVCU3w_YDnONEqqE99YHzJM2q3eoqe39YX25Ko,4596
141
141
  omlish/dataclasses/internals.py,sha256=vIGCZnStgD3ef4drYRtVOrxhxmAPa0vJpo4pXcDcQvM,3073
142
- omlish/dataclasses/reflection.py,sha256=5N4acL27xwSnQJvoks6ts2JseGfwL_P9D2gM9vqtAFM,2243
142
+ omlish/dataclasses/reflection.py,sha256=FSSTiS7pGTmDrYNkS6fZzaMSe8qtSvDv5Pgz0T55qXg,2404
143
143
  omlish/dataclasses/specs.py,sha256=r9hpIWy83ODiB1n7wTf7JL5fiCeeT8fWbMvxs7c4e3g,6148
144
144
  omlish/dataclasses/utils.py,sha256=gv6za6oJYBr1VaeGd7oXqq9lhBs_sxkpC27XYzJggno,1870
145
145
  omlish/dataclasses/api/__init__.py,sha256=k5iS9QOwf_f4iOfGffYhnqDOcmEIwEUUTp00u11kIPM,455
146
146
  omlish/dataclasses/api/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
147
- omlish/dataclasses/api/classes/conversion.py,sha256=x1Ayo5W3t8lVh10KGH_ZBJ_kK7HqcdI2LdJA6mR_WXQ,810
147
+ omlish/dataclasses/api/classes/conversion.py,sha256=U3g5S-HefJrg4to1BF8XnlEWqMxFeGEqVt8o5OTQK-U,902
148
148
  omlish/dataclasses/api/classes/decorator.py,sha256=q-m05j6VhJ9_00TmrXBvwmv0_TFPTD8OURi1KbKo9-4,3933
149
149
  omlish/dataclasses/api/classes/make.py,sha256=Q_8ZVjhhFEUstIZA3Ht_vlGptYjTcJqX48qT57W7SZw,2820
150
150
  omlish/dataclasses/api/classes/metadata.py,sha256=ocUu75VWOdZA8-R7K95yPhQtS0sFZB1NtqCdzXyDNeQ,2769
@@ -214,7 +214,7 @@ omlish/diag/pycharm.py,sha256=Z2W-Viqw5xq08ZC4z36skpozfYw_qNNhWQx_GYr2D0k,4695
214
214
  omlish/diag/pydevd.py,sha256=UN55ZjkWLCVyHxE2CNRRYamuvSKfzWsn0D5oczRTXO4,7536
215
215
  omlish/diag/threads.py,sha256=1-x02VCDZ407gfbtXm1pWK-ubqhqfePm9PMqkHCVoqk,3642
216
216
  omlish/diag/_pycharm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
217
- omlish/diag/_pycharm/runhack.py,sha256=O37sduvkx99uQaEg_XWLDBJlUxaqgd2LkjmdGaUT6e4,35432
217
+ omlish/diag/_pycharm/runhack.py,sha256=f4R9SOH0nDfO2WqPh0xQ9-45qOLrtTVysS-hSwtbdOA,37864
218
218
  omlish/diag/replserver/__init__.py,sha256=uLo6V2aQ29v9z3IMELlPDSlG3_2iOT4-_X8VniF-EgE,235
219
219
  omlish/diag/replserver/__main__.py,sha256=LmU41lQ58bm1h4Mx7S8zhE_uEBSC6kPcp9mn5JRpulA,32
220
220
  omlish/diag/replserver/console.py,sha256=XzBDVhYlr8FY6ym4OwoaIHuFOHnGK3dTYlMDIOMUUlA,7410
@@ -264,8 +264,8 @@ omlish/formats/json/codecs.py,sha256=E5KErfqsgGZq763ixXLT3qysbk5MIsypT92xG5aSaIs
264
264
  omlish/formats/json/consts.py,sha256=A0cTAGGLyjo-gcYIQrL4JIaardI0yPMhQoNmh42BaRg,387
265
265
  omlish/formats/json/encoding.py,sha256=iwoYyJePibgvRDZ6e9b2OlXmOBJEczquRNoiffVf3hE,502
266
266
  omlish/formats/json/json.py,sha256=Mdqv2vdMi7gp96eV0BIYH5UdWpjWfsh-tSMZeywG-08,331
267
- omlish/formats/json/literals.py,sha256=NMMBhP43cX2mfpiFsMysQWhbSBc0Ux-zegiah62cSmE,7198
268
- omlish/formats/json/rendering.py,sha256=rWDIiGvLQ0GkCyOKsPywFSo3FdYJ-pCHyrX6MCqitRs,4556
267
+ omlish/formats/json/literals.py,sha256=MMou9UIoztL9EyX2Zhv3Kzfq8eaKMfGNCFhL16n-fTk,7532
268
+ omlish/formats/json/rendering.py,sha256=0S_ppr5ihkUW7PP0mipZXHr72otqwUCNfanwopcboag,4571
269
269
  omlish/formats/json/types.py,sha256=ueO9-uOU2eVWowJf0LH1fHFLjZ6fTIZyq9qybcLQaiQ,147
270
270
  omlish/formats/json/backends/__init__.py,sha256=gnaNDCxy_KmmPUPDnjxO5_WjuWxLGbI9FYWx8ZJuQUU,97
271
271
  omlish/formats/json/backends/base.py,sha256=WqtyoM82pyM0NyqpPwndrebr1bUVU1QlpmVQNrcAO8c,1114
@@ -287,6 +287,7 @@ omlish/formats/json5/codec.py,sha256=ldnxCRo0JP1fkGLt0mMxJlLvNxqIF_1KUCcSp1HtI-M
287
287
  omlish/formats/json5/errors.py,sha256=AHkR9ySjAoQdUrizpqgL8fg0M5oe5mHZkml3KZHEvC4,38
288
288
  omlish/formats/json5/literals.py,sha256=rj4-9KFXfgdq5oK_eUkp57cgoMQ8T0gRaG9ga430he4,2429
289
289
  omlish/formats/json5/parsing.py,sha256=CWJHfe_FXCQuyDk00a7PI5wOdROq7Tc3fbWrNuwaKCw,2346
290
+ omlish/formats/json5/rendering.py,sha256=Kf-yA4AB3BqyjellBOFTGrMxxpIUsmgHIFzVRSJkQnE,1112
290
291
  omlish/formats/json5/_antlr/Json5Lexer.py,sha256=LnURBD0tL35VHNpxqM4DOPrTkE_9oONHtbftRM26tak,23239
291
292
  omlish/formats/json5/_antlr/Json5Listener.py,sha256=37Jhzu7tQEw6gLIxtcZtV5Yjn08kS-GkxF7d0KjXyo8,2097
292
293
  omlish/formats/json5/_antlr/Json5Parser.py,sha256=YU9bP89nq97A8ZCrjIcbgoBg4z76-N-6IMFGQe_ws54,19679
@@ -426,7 +427,7 @@ omlish/lang/strings.py,sha256=kJmRFd1D36xXcjd9MdB12BCwF_-MVhNr-TpWj7hMi_4,4252
426
427
  omlish/lang/sys.py,sha256=b4qOPiJZQru_mbb04FNfOjYWUxlV2becZOoc-yya_rQ,411
427
428
  omlish/lang/typing.py,sha256=Zdad9Zv0sa-hIaUXPrzPidT7sDVpRcussAI7D-j-I1c,3296
428
429
  omlish/lang/cached/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
429
- omlish/lang/cached/function.py,sha256=gXrU4DYO952lCJpNDuSTVb83f0tVjVnaG3BY2Sqab0k,9712
430
+ omlish/lang/cached/function.py,sha256=tch6VlDljfUUFMP61RY0LYXYyNwj1Sy8CAQQQMHCypA,11344
430
431
  omlish/lang/cached/property.py,sha256=WHYyg4-6EA86TcNMfbXTjVhjEZPc0kngt9hfY3WN5w8,2768
431
432
  omlish/lang/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
432
433
  omlish/lang/classes/abstract.py,sha256=n4rDlDraUKxPF0GtOWEFZ6mEzEDmP7Z8LSI6Jww_thw,3715
@@ -452,7 +453,7 @@ omlish/lite/inject.py,sha256=-tTsOqqef-Ix5Tgl2DP_JAsNWJQDFUptERl3lk14Uzs,29007
452
453
  omlish/lite/json.py,sha256=7-02Ny4fq-6YAu5ynvqoijhuYXWpLmfCI19GUeZnb1c,740
453
454
  omlish/lite/logs.py,sha256=CWFG0NKGhqNeEgryF5atN2gkPYbUdTINEw_s1phbINM,51
454
455
  omlish/lite/marshal.py,sha256=4DCbLoimLwJakihpvMjJ_kpc7v9aZQY8P3-gkoqEGUE,18471
455
- omlish/lite/maybes.py,sha256=00UUjtI29-gQD3sjgogIqxl74iD395GqdDQB-k0nxm8,4363
456
+ omlish/lite/maybes.py,sha256=hAIzp2pmPccnULwKiA8vaxiTJj6av3dvrqhhDfMLte0,4353
456
457
  omlish/lite/pycharm.py,sha256=pUOJevrPClSqTCEOkQBO11LKX2003tfDcp18a03QFrc,1163
457
458
  omlish/lite/reflect.py,sha256=pzOY2PPuHH0omdtglkN6DheXDrGopdL3PtTJnejyLFU,2189
458
459
  omlish/lite/reprs.py,sha256=QI5VBtvq_TW1TojWL25c04QfOABLi8Smt5jc5J-bArc,2008
@@ -580,7 +581,7 @@ omlish/sockets/addresses.py,sha256=vbVeQBkzI513H4vRv-JS89QtRbr9U8v5zqkm3oODl_s,1
580
581
  omlish/sockets/bind.py,sha256=J1SfFFFnVf3H5nqESDX2NGEY8DmjyIMUXZciZM33zQY,8003
581
582
  omlish/sockets/handlers.py,sha256=Gj6xZoo4vommge8XvkehYw3B7O4aql2P4qzZIIa0p24,462
582
583
  omlish/sockets/io.py,sha256=lfhTkB7NnAIx9kuQhAkwgsEUXY78Mp1_WtYrIQNS_k8,1408
583
- omlish/sockets/ports.py,sha256=NQYVvd8IBbQkPl8TnBtupTiv-Htqhc6QBIXJJ6GprnQ,1389
584
+ omlish/sockets/ports.py,sha256=6sp1jVTYuhuAGQ9NB12sFklPkygLhhrNMO9Wa9PcFeU,1772
584
585
  omlish/sockets/wait.py,sha256=OtLbcoLYzksl4GU0TNbQpzzVWGxp2iRLgo2cGZZfFFM,1559
585
586
  omlish/sockets/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
586
587
  omlish/sockets/server/handlers.py,sha256=PPsb1X5oU9dN8jfztaMGsRiqWTyEANT-1aSLbS6bUVg,3867
@@ -748,7 +749,7 @@ omlish/testing/pytest/plugins/pydevd.py,sha256=5moE64LrNRV6gKRaeCuONDiwuh-4UFxJP
748
749
  omlish/testing/pytest/plugins/repeat.py,sha256=jiZCI-17042jBvUEbZCxNwqWtr7s3EhTBSeSHh_Uz4E,497
749
750
  omlish/testing/pytest/plugins/skips.py,sha256=eMir_n777Kk2BvOwjQzRROKL4iAMYKSHFwWCHJ-bdPI,1040
750
751
  omlish/testing/pytest/plugins/spacing.py,sha256=tzq-L-exegHe2BImumkYIsVcUwpUUhLJJOuSrsKDuCU,706
751
- omlish/testing/pytest/plugins/switches.py,sha256=U8tDP-WSI2yjxwp4hocgZNJEmEv8UO8_CFEv8mXWH3A,3814
752
+ omlish/testing/pytest/plugins/switches.py,sha256=azw-pqSQfZYs7YlKsPK1bzerg20pX55YXejZgIxJu9U,5166
752
753
  omlish/testing/pytest/plugins/utils.py,sha256=L5C622UXcA_AUKDcvyh5IMiRfqSGGz0McdhwZWvfMlU,261
753
754
  omlish/testing/pytest/plugins/asyncs/__init__.py,sha256=TTNhFmP_krug1973sq_bpWBTIvg68-1nbuVLSs92Z6k,41
754
755
  omlish/testing/pytest/plugins/asyncs/consts.py,sha256=0NOCkzV43dOu3u97BqYMQ4mPG8JuFncpWibkOZpCqX4,55
@@ -852,9 +853,9 @@ omlish/typedvalues/holder.py,sha256=ZTnHiw-K38ciOBLEdwgrltr7Xp8jjEs_0Lp69DH-G-o,
852
853
  omlish/typedvalues/marshal.py,sha256=hWHRLcrGav7lvXJDtb9bNI0ickl4SKPQ6F4BbTpqw3A,4219
853
854
  omlish/typedvalues/reflect.py,sha256=Ih1YgU-srUjsvBn_P7C66f73_VCvcwqE3ffeBnZBgt4,674
854
855
  omlish/typedvalues/values.py,sha256=ym46I-q2QJ_6l4UlERqv3yj87R-kp8nCKMRph0xQ3UA,1307
855
- omlish-0.0.0.dev316.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
856
- omlish-0.0.0.dev316.dist-info/METADATA,sha256=-NOxa8LncyL8kMJ9KG9rdMF4oCf60aY1fALrI0TCdMg,4416
857
- omlish-0.0.0.dev316.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
858
- omlish-0.0.0.dev316.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
859
- omlish-0.0.0.dev316.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
860
- omlish-0.0.0.dev316.dist-info/RECORD,,
856
+ omlish-0.0.0.dev318.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
857
+ omlish-0.0.0.dev318.dist-info/METADATA,sha256=-Nc1DXmiJNc5JokC60eHTE9VTgT6oMoXEf496J6i6UE,4416
858
+ omlish-0.0.0.dev318.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
859
+ omlish-0.0.0.dev318.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
860
+ omlish-0.0.0.dev318.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
861
+ omlish-0.0.0.dev318.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5