omlish 0.0.0.dev29__py3-none-any.whl → 0.0.0.dev31__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.
Files changed (36) hide show
  1. omlish/__about__.py +6 -6
  2. omlish/asyncs/anyio.py +1 -1
  3. omlish/asyncs/bridge.py +17 -9
  4. omlish/asyncs/trio.py +1 -1
  5. omlish/bootstrap/main.py +5 -0
  6. omlish/concurrent/threadlets.py +2 -2
  7. omlish/diag/replserver/server.py +1 -1
  8. omlish/docker.py +9 -0
  9. omlish/fnpipes.py +82 -0
  10. omlish/http/sessions.py +2 -2
  11. omlish/lang/__init__.py +4 -0
  12. omlish/lang/classes/abstract.py +1 -1
  13. omlish/lang/contextmanagers.py +35 -0
  14. omlish/lang/resources.py +1 -0
  15. omlish/lang/strings.py +49 -1
  16. omlish/lite/logs.py +20 -5
  17. omlish/logs/noisy.py +1 -0
  18. omlish/marshal/naming.py +5 -0
  19. omlish/marshal/polymorphism.py +20 -10
  20. omlish/matchfns.py +2 -2
  21. omlish/os.py +17 -0
  22. omlish/specs/__init__.py +9 -0
  23. omlish/sql/__init__.py +0 -14
  24. omlish/sql/alchemy/__init__.py +13 -0
  25. omlish/sql/{asyncs.py → alchemy/asyncs.py} +1 -1
  26. omlish/sql/{duckdb.py → alchemy/duckdb.py} +1 -1
  27. omlish/sql/{secrets.py → alchemy/secrets.py} +1 -1
  28. omlish/testing/pytest/plugins/asyncs.py +5 -0
  29. omlish/testing/pytest/plugins/switches.py +29 -9
  30. {omlish-0.0.0.dev29.dist-info → omlish-0.0.0.dev31.dist-info}/METADATA +17 -17
  31. {omlish-0.0.0.dev29.dist-info → omlish-0.0.0.dev31.dist-info}/RECORD +36 -34
  32. /omlish/sql/{exprs.py → alchemy/exprs.py} +0 -0
  33. /omlish/sql/{sqlean.py → alchemy/sqlean.py} +0 -0
  34. {omlish-0.0.0.dev29.dist-info → omlish-0.0.0.dev31.dist-info}/LICENSE +0 -0
  35. {omlish-0.0.0.dev29.dist-info → omlish-0.0.0.dev31.dist-info}/WHEEL +0 -0
  36. {omlish-0.0.0.dev29.dist-info → omlish-0.0.0.dev31.dist-info}/top_level.txt +0 -0
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev29'
2
- __revision__ = 'cde08614ca06e103fcae31adbb677c772002aef1'
1
+ __version__ = '0.0.0.dev31'
2
+ __revision__ = 'bd655a8d18c1cc31ce2c9d79fedaec771c5e1593'
3
3
 
4
4
 
5
5
  #
@@ -30,7 +30,7 @@ class Project(ProjectBase):
30
30
 
31
31
  optional_dependencies = {
32
32
  'async': [
33
- 'anyio ~= 4.5',
33
+ 'anyio ~= 4.6',
34
34
  'sniffio ~= 1.3',
35
35
 
36
36
  'greenlet ~= 3.1',
@@ -39,7 +39,7 @@ class Project(ProjectBase):
39
39
  'trio-asyncio ~= 0.15',
40
40
  ],
41
41
 
42
- 'compression': [
42
+ 'compress': [
43
43
  'lz4 ~= 4.0',
44
44
 
45
45
  'python-snappy ~= 0.7; python_version < "3.13"',
@@ -77,11 +77,11 @@ class Project(ProjectBase):
77
77
  'cryptography ~= 43.0',
78
78
  ],
79
79
 
80
- 'sql': [
80
+ 'sqlalchemy': [
81
81
  'sqlalchemy[asyncio] ~= 2.0',
82
82
  ],
83
83
 
84
- 'sql-drivers': [
84
+ 'sqlalchemy-drivers': [
85
85
  'pg8000 ~= 1.31',
86
86
  # 'psycopg2 ~= 2.9',
87
87
  # 'psycopg ~= 3.2',
omlish/asyncs/anyio.py CHANGED
@@ -4,7 +4,7 @@ TODO:
4
4
  - owned lock
5
5
  - async once
6
6
 
7
- lookit:
7
+ See:
8
8
  - https://github.com/davidbrochart/sqlite-anyio/blob/a3ba4c6ef0535b14a5a60071fcd6ed565a514963/sqlite_anyio/sqlite.py
9
9
  - https://github.com/rafalkrupinski/ratelimit-anyio/blob/2910a8a3d6fa54ed17ee6ba457686c9f7a4c4beb/src/ratelimit_anyio/__init__.py
10
10
  - https://github.com/nekitdev/async-extensions/tree/main/async_extensions
omlish/asyncs/bridge.py CHANGED
@@ -12,8 +12,9 @@ TODO:
12
12
  See:
13
13
  - https://greenback.readthedocs.io/en/latest/
14
14
  """
15
+ import functools # noqa
15
16
  import itertools
16
- import sys
17
+ import sys # noqa
17
18
  import types
18
19
  import typing as ta
19
20
  import weakref
@@ -90,7 +91,7 @@ class UnexpectedBridgeNestingError(Exception):
90
91
 
91
92
 
92
93
  _DEBUG_PRINT: ta.Callable[..., None] | None = None
93
- # _DEBUG_PRINT = print # noqa
94
+ # _DEBUG_PRINT = functools.partial(print, file=sys.stderr) # noqa
94
95
 
95
96
  _TRACK_TRANSITION_OBJS = False
96
97
 
@@ -253,8 +254,8 @@ def s_to_a(fn, *, require_await=False):
253
254
  switch_occurred = True
254
255
  try:
255
256
  value = yield result
256
- except BaseException: # noqa
257
- result = g.throw(*sys.exc_info())
257
+ except BaseException as e: # noqa
258
+ result = g.throw(e)
258
259
  else:
259
260
  result = g.switch(value)
260
261
 
@@ -302,15 +303,22 @@ def a_to_s(fn):
302
303
 
303
304
  cr = gate()
304
305
  sv = None
306
+ he = False
305
307
  try:
306
308
  while True:
307
- try:
308
- sv = cr.send(sv)
309
- except StopIteration:
310
- break
309
+ if not he:
310
+ try:
311
+ sv = cr.send(sv)
312
+ except StopIteration:
313
+ break
314
+ he = False
311
315
 
312
316
  if ret is missing or cr.cr_await is not None or cr.cr_running:
313
- sv = s_to_a_await(sv)
317
+ try:
318
+ sv = s_to_a_await(sv) # type: ignore
319
+ except BaseException as e: # noqa
320
+ sv = cr.throw(e)
321
+ he = True
314
322
 
315
323
  finally:
316
324
  cr.close()
omlish/asyncs/trio.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """
2
- lookit:
2
+ See:
3
3
  - https://github.com/oremanj/trio-monitor
4
4
  - https://github.com/python-trio/trio-monitor/tree/master
5
5
  - https://github.com/python-trio/triopg
omlish/bootstrap/main.py CHANGED
@@ -1,3 +1,8 @@
1
+ """
2
+ TODO:
3
+ - -x / --exec - os.exec entrypoint
4
+ - refuse to install non-exec-relevant Bootstraps when chosen
5
+ """
1
6
  # ruff: noqa: UP006 UP007
2
7
  import argparse
3
8
  import dataclasses as dc
@@ -48,7 +48,7 @@ class Threadlet(abc.ABC):
48
48
  raise NotImplementedError
49
49
 
50
50
  @abc.abstractmethod
51
- def throw(self, ex: Exception) -> ta.Any:
51
+ def throw(self, ex: BaseException) -> ta.Any:
52
52
  raise NotImplementedError
53
53
 
54
54
 
@@ -84,7 +84,7 @@ class GreenletThreadlet(Threadlet):
84
84
  def switch(self, *args: ta.Any, **kwargs: ta.Any) -> ta.Any:
85
85
  return self.g.switch(*args, **kwargs)
86
86
 
87
- def throw(self, ex: Exception) -> ta.Any:
87
+ def throw(self, ex: BaseException) -> ta.Any:
88
88
  return self.g.throw(ex)
89
89
 
90
90
 
@@ -9,7 +9,7 @@ TODO:
9
9
  - optional ipython embed
10
10
  - https://github.com/python/cpython/tree/56470004e58911b146c016fc9fec4461b8f69454/Lib/_pyrepl
11
11
 
12
- lookit:
12
+ See:
13
13
  - https://github.com/vxgmichel/aioconsole/blob/e55f4b0601da3b3a40a88c965526d35ab38b5841/aioconsole/server.py
14
14
  - https://github.com/nhoad/aiomanhole
15
15
  - https://github.com/twisted/twisted/blob/00aa56f5257060304d41f09651c6ab58ee6104d6/src/twisted/conch/manhole.py
omlish/docker.py CHANGED
@@ -107,6 +107,15 @@ def cli_inspect(ids: list[str]) -> list[Inspect]:
107
107
  return msh.unmarshal(json.loads(o.decode()), list[Inspect])
108
108
 
109
109
 
110
+ def has_cli() -> bool:
111
+ try:
112
+ proc = subprocess.run(['docker', '--version']) # noqa
113
+ except (FileNotFoundError, subprocess.CalledProcessError):
114
+ return False
115
+ else:
116
+ return not proc.returncode
117
+
118
+
110
119
  ##
111
120
 
112
121
 
omlish/fnpipes.py ADDED
@@ -0,0 +1,82 @@
1
+ import abc
2
+ import typing as ta
3
+
4
+
5
+ T = ta.TypeVar('T')
6
+ U = ta.TypeVar('U')
7
+
8
+
9
+ class Fn(abc.ABC, ta.Generic[T]):
10
+ @abc.abstractmethod
11
+ def __call__(self, *args: ta.Any, **kwargs: ta.Any) -> T:
12
+ raise NotImplementedError
13
+
14
+ def pipe(self, fn: ta.Callable[..., U], *args: ta.Any, **kwargs: ta.Any) -> 'Fn[U]':
15
+ return Pipe([self], Bind(fn, *args, **kwargs))
16
+
17
+ def __or__(self, fn: ta.Callable[..., U]) -> 'Fn[U]':
18
+ return self.pipe(fn)
19
+
20
+ def apply(self, fn: ta.Callable[[T], ta.Any], *args: ta.Any, **kwargs: ta.Any) -> 'Fn[T]':
21
+ return Pipe([self], Apply(Bind(fn, *args, **kwargs)))
22
+
23
+ def __and__(self, fn: ta.Callable[[T], ta.Any]) -> 'Fn[T]':
24
+ return self.apply(fn)
25
+
26
+
27
+ class Bind(Fn[T]):
28
+ def __init__(self, fn: ta.Callable[..., T], *args: ta.Any, **kwargs: ta.Any) -> None:
29
+ super().__init__()
30
+ if Ellipsis not in args and Ellipsis not in kwargs:
31
+ args += (Ellipsis,)
32
+ self._fn = fn
33
+ self._args = args
34
+ self._kwargs = kwargs
35
+
36
+ def __call__(self, *args: ta.Any, **kwargs: ta.Any) -> T:
37
+ fa: list = []
38
+ for a in self._args:
39
+ if a is Ellipsis:
40
+ fa.extend(args)
41
+ else:
42
+ fa.append(a)
43
+
44
+ fkw = {}
45
+ for k, v in self._kwargs.items():
46
+ if v is Ellipsis:
47
+ if len(args) != 1:
48
+ raise ValueError(args)
49
+ fkw[k] = args[0]
50
+ else:
51
+ fkw[k] = v
52
+ fkw.update(kwargs)
53
+
54
+ return self._fn(*fa, **fkw)
55
+
56
+
57
+ class Pipe(Fn[T]):
58
+ def __init__(self, lfns: ta.Sequence[ta.Callable], rfn: ta.Callable[..., T]) -> None:
59
+ super().__init__()
60
+ self._lfn, *self._rfns = [*lfns, rfn]
61
+
62
+ def __call__(self, *args: ta.Any, **kwargs: ta.Any) -> T:
63
+ o = self._lfn(*args, **kwargs)
64
+ for fn in self._rfns:
65
+ o = fn(o)
66
+ return o
67
+
68
+
69
+ class Apply(Fn[T]):
70
+ def __init__(self, *fns: ta.Callable[[T], ta.Any]) -> None:
71
+ super().__init__()
72
+ self._fns = fns
73
+
74
+ def __call__(self, o: T) -> T: # noqa
75
+ for fn in self._fns:
76
+ fn(o)
77
+ return o
78
+
79
+
80
+ bind = Bind
81
+ pipe = Pipe
82
+ apply = Apply
omlish/http/sessions.py CHANGED
@@ -8,7 +8,7 @@ import time
8
8
  import typing as ta
9
9
  import zlib
10
10
 
11
- from .. import fnpairs as fps
11
+ from .. import fnpairs as fpa
12
12
  from .. import lang
13
13
  from .. import secrets as sec
14
14
  from .cookies import dump_cookie
@@ -92,7 +92,7 @@ class SessionMarshal:
92
92
  def __init__(
93
93
  self,
94
94
  signer: Signer,
95
- serializer: fps.ObjectStr = fps.of(JSON_TAGGER.dumps, JSON_TAGGER.loads),
95
+ serializer: fpa.ObjectStr = fpa.of(JSON_TAGGER.dumps, JSON_TAGGER.loads),
96
96
  ) -> None:
97
97
  super().__init__()
98
98
 
omlish/lang/__init__.py CHANGED
@@ -60,6 +60,7 @@ from .contextmanagers import ( # noqa
60
60
  NOP_CONTEXT_MANAGER,
61
61
  NopContextManaged,
62
62
  NopContextManager,
63
+ Timer,
63
64
  a_defer,
64
65
  attr_setting,
65
66
  breakpoint_on_exception,
@@ -188,8 +189,11 @@ from .strings import ( # noqa
188
189
  is_ident_cont,
189
190
  is_ident_start,
190
191
  is_sunder,
192
+ prefix_delimited,
191
193
  prefix_lines,
192
194
  snake_case,
195
+ strip_prefix,
196
+ strip_suffix,
193
197
  )
194
198
 
195
199
  from .sys import ( # noqa
@@ -47,7 +47,7 @@ class Abstract(abc.ABC): # noqa
47
47
  seen.update(dir(b))
48
48
  if ams:
49
49
  raise TypeError(
50
- f'Cannot subclass abstract class {cls.__name__} with abstract methods'
50
+ f'Cannot subclass abstract class {cls.__name__} with abstract methods: '
51
51
  f'{", ".join(map(str, sorted(ams)))}',
52
52
  )
53
53
 
@@ -7,6 +7,7 @@ import contextlib
7
7
  import contextvars
8
8
  import functools
9
9
  import threading
10
+ import time
10
11
  import types
11
12
  import typing as ta
12
13
 
@@ -367,3 +368,37 @@ def default_lock(value: DefaultLockable, default: DefaultLockable = None) -> Loc
367
368
 
368
369
  else:
369
370
  raise TypeError(value)
371
+
372
+
373
+ ##
374
+
375
+
376
+ class Timer:
377
+ def __init__(
378
+ self,
379
+ clock: ta.Callable[[], float] | None = None,
380
+ ) -> None:
381
+ super().__init__()
382
+ self._clock = clock if clock is not None else time.monotonic
383
+
384
+ _start: float
385
+ _end: float
386
+
387
+ @property
388
+ def start(self) -> float:
389
+ return self._start
390
+
391
+ @property
392
+ def end(self) -> float:
393
+ return self._end
394
+
395
+ @property
396
+ def elapsed(self) -> float:
397
+ return self._end - self._start
398
+
399
+ def __enter__(self) -> ta.Self:
400
+ self._start = self._clock()
401
+ return self
402
+
403
+ def __exit__(self, exc_type, exc_val, exc_tb):
404
+ self._end = self._clock()
omlish/lang/resources.py CHANGED
@@ -27,6 +27,7 @@ def get_relative_resources(
27
27
 
28
28
  if os.sep in path:
29
29
  raise ValueError(path) # noqa
30
+
30
31
  if not path.startswith('.'):
31
32
  path = '.' + path
32
33
  if set(path) - {'.'}:
omlish/lang/strings.py CHANGED
@@ -4,9 +4,22 @@ import unicodedata
4
4
 
5
5
  ##
6
6
 
7
+ @ta.overload
8
+ def prefix_delimited(s: str, p: str, d: str) -> str:
9
+ ...
10
+
11
+
12
+ @ta.overload
13
+ def prefix_delimited(s: bytes, p: bytes, d: bytes) -> bytes:
14
+ ...
15
+
16
+
17
+ def prefix_delimited(s, p, d):
18
+ return d.join([p + l for l in s.split(d)])
19
+
7
20
 
8
21
  def prefix_lines(s: str, p: str) -> str:
9
- return '\n'.join([p + l for l in s.split('\n')])
22
+ return prefix_delimited(s, p, '\n')
10
23
 
11
24
 
12
25
  def indent_lines(s: str, num: int) -> str:
@@ -16,6 +29,41 @@ def indent_lines(s: str, num: int) -> str:
16
29
  ##
17
30
 
18
31
 
32
+ @ta.overload
33
+ def strip_prefix(s: str, pfx: str) -> str:
34
+ ...
35
+
36
+
37
+ @ta.overload
38
+ def strip_prefix(s: bytes, pfx: bytes) -> bytes:
39
+ ...
40
+
41
+
42
+ def strip_prefix(s, pfx):
43
+ if not s.startswith(pfx):
44
+ raise ValueError(f'{s!r} does not start with {pfx!r}')
45
+ return s[len(pfx):]
46
+
47
+
48
+ @ta.overload
49
+ def strip_suffix(s: str, sfx: str) -> str:
50
+ ...
51
+
52
+
53
+ @ta.overload
54
+ def strip_suffix(s: bytes, sfx: bytes) -> bytes:
55
+ ...
56
+
57
+
58
+ def strip_suffix(s, sfx):
59
+ if not s.endswith(sfx):
60
+ raise ValueError(f'{s!r} does not end with {sfx!r}')
61
+ return s[:-len(sfx)]
62
+
63
+
64
+ ##
65
+
66
+
19
67
  def camel_case(name: str) -> str:
20
68
  return ''.join(map(str.capitalize, name.split('_'))) # noqa
21
69
 
omlish/lite/logs.py CHANGED
@@ -4,6 +4,7 @@ TODO:
4
4
  - debug
5
5
  """
6
6
  # ruff: noqa: UP006 UP007 N802
7
+ import contextlib
7
8
  import datetime
8
9
  import logging
9
10
  import threading
@@ -203,6 +204,24 @@ class StandardLogHandler(ProxyLogHandler):
203
204
  ##
204
205
 
205
206
 
207
+ @contextlib.contextmanager
208
+ def _locking_logging_module_lock() -> ta.Iterator[None]:
209
+ if hasattr(logging, '_acquireLock'):
210
+ logging._acquireLock() # noqa
211
+ try:
212
+ yield
213
+ finally:
214
+ logging._releaseLock() # type: ignore # noqa
215
+
216
+ elif hasattr(logging, '_lock'):
217
+ # https://github.com/python/cpython/commit/74723e11109a320e628898817ab449b3dad9ee96
218
+ with logging._lock: # noqa
219
+ yield
220
+
221
+ else:
222
+ raise Exception("Can't find lock in logging module")
223
+
224
+
206
225
  def configure_standard_logging(
207
226
  level: ta.Union[int, str] = logging.INFO,
208
227
  *,
@@ -210,8 +229,7 @@ def configure_standard_logging(
210
229
  target: ta.Optional[logging.Logger] = None,
211
230
  force: bool = False,
212
231
  ) -> ta.Optional[StandardLogHandler]:
213
- logging._acquireLock() # type: ignore # noqa
214
- try:
232
+ with _locking_logging_module_lock():
215
233
  if target is None:
216
234
  target = logging.root
217
235
 
@@ -250,6 +268,3 @@ def configure_standard_logging(
250
268
  #
251
269
 
252
270
  return StandardLogHandler(handler)
253
-
254
- finally:
255
- logging._releaseLock() # type: ignore # noqa
omlish/logs/noisy.py CHANGED
@@ -5,6 +5,7 @@ NOISY_LOGGERS: set[str] = {
5
5
  'boto3.resources.action',
6
6
  'datadog.dogstatsd',
7
7
  'elasticsearch',
8
+ 'httpx',
8
9
  'kazoo.client',
9
10
  'requests.packages.urllib3.connectionpool',
10
11
  }
omlish/marshal/naming.py CHANGED
@@ -1,3 +1,8 @@
1
+ """
2
+ TODO:
3
+ - Namer: ta.TypeAlias = ta.Callable[[str], str] ?
4
+ - this interface is ~intentionally~ limited, but custom overrides would be useful
5
+ """
1
6
  import enum
2
7
 
3
8
  from .. import lang
@@ -98,24 +98,34 @@ def polymorphism_from_subclasses(
98
98
  ty: type,
99
99
  *,
100
100
  naming: Naming | None = None,
101
+ strip_suffix: bool = False,
101
102
  ) -> Polymorphism:
102
103
  dct: dict[str, Impl] = {}
104
+
103
105
  seen: set[type] = set()
104
106
  todo: list[type] = [ty]
105
107
  while todo:
106
108
  cur = todo.pop()
107
109
  seen.add(cur)
108
- if not lang.is_abstract_class(cur):
109
- nam = cur.__name__
110
- if naming is not None:
111
- nam = translate_name(nam, naming)
112
- if nam in dct:
113
- raise KeyError(f'Duplicate name: {nam}')
114
- dct[nam] = Impl(
115
- cur,
116
- nam,
117
- )
110
+
118
111
  todo.extend(nxt for nxt in cur.__subclasses__() if nxt not in seen)
112
+
113
+ if lang.is_abstract_class(cur):
114
+ continue
115
+
116
+ name = cur.__name__
117
+ if strip_suffix:
118
+ name = lang.strip_suffix(name, ty.__name__)
119
+ if naming is not None:
120
+ name = translate_name(name, naming)
121
+ if name in dct:
122
+ raise KeyError(f'Duplicate name: {name}')
123
+
124
+ dct[name] = Impl(
125
+ cur,
126
+ name,
127
+ )
128
+
119
129
  return Polymorphism(ty, dct.values())
120
130
 
121
131
 
omlish/matchfns.py CHANGED
@@ -130,7 +130,7 @@ def multi(*children: MatchFn[P, T], strict: bool = False) -> MultiMatchFn: # Mu
130
130
  ##
131
131
 
132
132
 
133
- class CachedMultiFn(MatchFn[P, T]):
133
+ class CachedMatchFn(MatchFn[P, T]):
134
134
  @staticmethod
135
135
  def _default_key(*args, **kwargs):
136
136
  return (args, tuple(sorted(kwargs.items(), key=lambda t: t[0])))
@@ -187,7 +187,7 @@ class CachedMultiFn(MatchFn[P, T]):
187
187
  return self.__class__(self._f.__get__(instance, owner), key=self._key) # noqa
188
188
 
189
189
 
190
- cached = CachedMultiFn
190
+ cached = CachedMatchFn
191
191
 
192
192
 
193
193
  ##
omlish/os.py CHANGED
@@ -103,3 +103,20 @@ class Pidfile:
103
103
  def kill(self, sig: int = signal.SIGTERM) -> None:
104
104
  pid = self.read()
105
105
  os.kill(pid, sig) # Still racy
106
+
107
+
108
+ def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None:
109
+ if exist_ok:
110
+ # First try to bump modification time
111
+ # Implementation note: GNU touch uses the UTIME_NOW option of the utimensat() / futimens() functions.
112
+ try:
113
+ os.utime(self, None)
114
+ except OSError:
115
+ pass
116
+ else:
117
+ return
118
+ flags = os.O_CREAT | os.O_WRONLY
119
+ if not exist_ok:
120
+ flags |= os.O_EXCL
121
+ fd = os.open(self, flags, mode)
122
+ os.close(fd)
omlish/specs/__init__.py CHANGED
@@ -0,0 +1,9 @@
1
+ """
2
+ TODO:
3
+ - avro
4
+ - jmespath
5
+ - jsonpatch
6
+ - jsonpointer.py
7
+ - openapi
8
+ - protobuf
9
+ """
omlish/sql/__init__.py CHANGED
@@ -1,13 +1,3 @@
1
- from .asyncs import ( # noqa
2
- AsyncConnection,
3
- AsyncConnectionLike,
4
- AsyncEngine,
5
- AsyncEngineLike,
6
- AsyncTransaction,
7
- AsyncTransactionLike,
8
- async_adapt,
9
- )
10
-
11
1
  from .dbs import ( # noqa
12
2
  DbLoc,
13
3
  DbSpec,
@@ -17,10 +7,6 @@ from .dbs import ( # noqa
17
7
  UrlDbLoc,
18
8
  )
19
9
 
20
- from .exprs import ( # noqa
21
- paren,
22
- )
23
-
24
10
  from .qualifiedname import ( # noqa
25
11
  QualifiedName,
26
12
  qn,
@@ -0,0 +1,13 @@
1
+ from .asyncs import ( # noqa
2
+ AsyncConnection,
3
+ AsyncConnectionLike,
4
+ AsyncEngine,
5
+ AsyncEngineLike,
6
+ AsyncTransaction,
7
+ AsyncTransactionLike,
8
+ async_adapt,
9
+ )
10
+
11
+ from .exprs import ( # noqa
12
+ paren,
13
+ )
@@ -9,7 +9,7 @@ import typing as ta
9
9
  import sqlalchemy as sa
10
10
  import sqlalchemy.ext.asyncio as saa
11
11
 
12
- from .. import asyncs as au
12
+ from ... import asyncs as au
13
13
 
14
14
 
15
15
  T = ta.TypeVar('T')
@@ -8,7 +8,7 @@ import typing as ta
8
8
  import sqlalchemy as sa
9
9
  from sqlalchemy.dialects import postgresql as sap
10
10
 
11
- from .. import lang
11
+ from ... import lang
12
12
 
13
13
 
14
14
  if ta.TYPE_CHECKING:
@@ -2,7 +2,7 @@
2
2
  TODO:
3
3
  - sync/async...
4
4
  """
5
- from .. import secrets as sec
5
+ from ... import secrets as sec
6
6
 
7
7
 
8
8
  class SqlFunctionSecrets(sec.Secrets):
@@ -111,6 +111,11 @@ class AsyncsPlugin:
111
111
  else:
112
112
  return
113
113
 
114
+ if 'trio_asyncio' in bes:
115
+ # NOTE: Importing it here is apparently necessary to get its patching working - otherwise fails later with
116
+ # `no running event loop` in anyio._backends._asyncio and such.
117
+ import trio_asyncio # noqa
118
+
114
119
  if pdu.is_present():
115
120
  pdu.patch_for_trio_asyncio()
116
121
 
@@ -10,19 +10,24 @@ import pytest
10
10
 
11
11
  from .... import check
12
12
  from .... import collections as col
13
+ from .... import docker
14
+ from .... import lang
13
15
  from ._registry import register
14
16
 
15
17
 
16
18
  Configable = pytest.FixtureRequest | pytest.Config
17
19
 
18
20
 
19
- SWITCHES = {
20
- 'docker': True,
21
+ SWITCHES: ta.Mapping[str, bool | ta.Callable[[], bool]] = {
22
+ 'docker': docker.has_cli,
23
+ 'docker-guest': docker.is_likely_in_docker,
21
24
  'online': True,
22
25
  'integration': True,
23
26
  'slow': False,
24
27
  }
25
28
 
29
+ SWITCH_ATTRS = {k.replace('-', '_'): k for k in SWITCHES}
30
+
26
31
 
27
32
  SwitchState: ta.TypeAlias = bool | ta.Literal['only']
28
33
 
@@ -53,9 +58,9 @@ def skip_if_disabled(obj: Configable | None, name: str) -> None:
53
58
  pytest.skip(f'{name} disabled')
54
59
 
55
60
 
56
- def get_switches(obj: Configable) -> ta.Mapping[str, SwitchState]:
61
+ def get_specified_switches(obj: Configable) -> ta.Mapping[str, SwitchState]:
57
62
  ret: dict[str, SwitchState] = {}
58
- for sw, d in SWITCHES.items():
63
+ for sw in SWITCHES:
59
64
  sts = {
60
65
  st
61
66
  for st, pfx in SWITCH_STATE_OPT_PREFIXES.items()
@@ -65,8 +70,6 @@ def get_switches(obj: Configable) -> ta.Mapping[str, SwitchState]:
65
70
  if len(sts) > 1:
66
71
  raise Exception(f'Multiple switches specified for {sw}')
67
72
  ret[sw] = check.single(sts)
68
- else:
69
- ret[sw] = d
70
73
  return ret
71
74
 
72
75
 
@@ -74,8 +77,9 @@ def get_switches(obj: Configable) -> ta.Mapping[str, SwitchState]:
74
77
  class SwitchesPlugin:
75
78
 
76
79
  def pytest_configure(self, config):
77
- for sw in SWITCHES:
80
+ for sw in SWITCH_ATTRS:
78
81
  config.addinivalue_line('markers', f'{sw}: mark test as {sw}')
82
+ config.addinivalue_line('markers', f'not_{sw}: mark test as not {sw}')
79
83
 
80
84
  def pytest_addoption(self, parser):
81
85
  for sw in SWITCHES:
@@ -83,13 +87,25 @@ class SwitchesPlugin:
83
87
  parser.addoption(f'--{sw}', action='store_true', default=False, help=f'enables {sw} tests')
84
88
  parser.addoption(f'--only-{sw}', action='store_true', default=False, help=f'enables only {sw} tests')
85
89
 
90
+ @lang.cached_function
91
+ def get_switches(self) -> ta.Mapping[str, SwitchState]:
92
+ return {
93
+ k: v() if callable(v) else v
94
+ for k, v in SWITCHES.items()
95
+ }
96
+
86
97
  def pytest_collection_modifyitems(self, config, items):
87
- sts = get_switches(config)
98
+ sts = {
99
+ **self.get_switches(),
100
+ **get_specified_switches(config),
101
+ }
102
+
88
103
  stx = col.multi_map(map(reversed, sts.items())) # type: ignore
89
104
  ts, fs, onlys = (stx.get(k, ()) for k in (True, False, 'only'))
90
105
 
91
106
  def process(item):
92
- sws = {sw for sw in SWITCHES if sw in item.keywords}
107
+ sws = {sw for swa, sw in SWITCH_ATTRS.items() if swa in item.keywords}
108
+ nsws = {sw for swa, sw in SWITCH_ATTRS.items() if ('not_' + swa) in item.keywords}
93
109
 
94
110
  if onlys:
95
111
  if not any(sw in onlys for sw in sws):
@@ -101,5 +117,9 @@ class SwitchesPlugin:
101
117
  if sw in fs:
102
118
  item.add_marker(pytest.mark.skip(reason=f'skipping switches {sw}'))
103
119
 
120
+ for nsw in nsws:
121
+ if nsw in ts:
122
+ item.add_marker(pytest.mark.skip(reason=f'skipping switches {nsw}'))
123
+
104
124
  for item in items:
105
125
  process(item)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev29
3
+ Version: 0.0.0.dev31
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -13,7 +13,7 @@ Classifier: Operating System :: POSIX
13
13
  Requires-Python: ~=3.12
14
14
  License-File: LICENSE
15
15
  Provides-Extra: all
16
- Requires-Dist: anyio ~=4.5 ; extra == 'all'
16
+ Requires-Dist: anyio ~=4.6 ; extra == 'all'
17
17
  Requires-Dist: sniffio ~=1.3 ; extra == 'all'
18
18
  Requires-Dist: greenlet ~=3.1 ; extra == 'all'
19
19
  Requires-Dist: trio ~=0.26 ; extra == 'all'
@@ -42,15 +42,15 @@ Requires-Dist: python-snappy ~=0.7 ; (python_version < "3.13") and extra == 'all
42
42
  Requires-Dist: asyncpg ~=0.29 ; (python_version < "3.13") and extra == 'all'
43
43
  Requires-Dist: sqlean.py ~=3.45 ; (python_version < "3.13") and extra == 'all'
44
44
  Provides-Extra: async
45
- Requires-Dist: anyio ~=4.5 ; extra == 'async'
45
+ Requires-Dist: anyio ~=4.6 ; extra == 'async'
46
46
  Requires-Dist: sniffio ~=1.3 ; extra == 'async'
47
47
  Requires-Dist: greenlet ~=3.1 ; extra == 'async'
48
48
  Requires-Dist: trio ~=0.26 ; extra == 'async'
49
49
  Requires-Dist: trio-asyncio ~=0.15 ; extra == 'async'
50
- Provides-Extra: compression
51
- Requires-Dist: lz4 ~=4.0 ; extra == 'compression'
52
- Requires-Dist: zstd ~=1.5 ; extra == 'compression'
53
- Requires-Dist: python-snappy ~=0.7 ; (python_version < "3.13") and extra == 'compression'
50
+ Provides-Extra: compress
51
+ Requires-Dist: lz4 ~=4.0 ; extra == 'compress'
52
+ Requires-Dist: zstd ~=1.5 ; extra == 'compress'
53
+ Requires-Dist: python-snappy ~=0.7 ; (python_version < "3.13") and extra == 'compress'
54
54
  Provides-Extra: diag
55
55
  Requires-Dist: asttokens ~=2.4 ; extra == 'diag'
56
56
  Requires-Dist: executing ~=2.1 ; extra == 'diag'
@@ -67,16 +67,16 @@ Provides-Extra: misc
67
67
  Requires-Dist: wrapt ~=1.14 ; extra == 'misc'
68
68
  Provides-Extra: secrets
69
69
  Requires-Dist: cryptography ~=43.0 ; extra == 'secrets'
70
- Provides-Extra: sql
71
- Requires-Dist: sqlalchemy[asyncio] ~=2.0 ; extra == 'sql'
72
- Provides-Extra: sql-drivers
73
- Requires-Dist: pg8000 ~=1.31 ; extra == 'sql-drivers'
74
- Requires-Dist: pymysql ~=1.1 ; extra == 'sql-drivers'
75
- Requires-Dist: aiomysql ~=0.2 ; extra == 'sql-drivers'
76
- Requires-Dist: aiosqlite ~=0.20 ; extra == 'sql-drivers'
77
- Requires-Dist: duckdb ~=1.1 ; extra == 'sql-drivers'
78
- Requires-Dist: asyncpg ~=0.29 ; (python_version < "3.13") and extra == 'sql-drivers'
79
- Requires-Dist: sqlean.py ~=3.45 ; (python_version < "3.13") and extra == 'sql-drivers'
70
+ Provides-Extra: sqlalchemy
71
+ Requires-Dist: sqlalchemy[asyncio] ~=2.0 ; extra == 'sqlalchemy'
72
+ Provides-Extra: sqlalchemy-drivers
73
+ Requires-Dist: pg8000 ~=1.31 ; extra == 'sqlalchemy-drivers'
74
+ Requires-Dist: pymysql ~=1.1 ; extra == 'sqlalchemy-drivers'
75
+ Requires-Dist: aiomysql ~=0.2 ; extra == 'sqlalchemy-drivers'
76
+ Requires-Dist: aiosqlite ~=0.20 ; extra == 'sqlalchemy-drivers'
77
+ Requires-Dist: duckdb ~=1.1 ; extra == 'sqlalchemy-drivers'
78
+ Requires-Dist: asyncpg ~=0.29 ; (python_version < "3.13") and extra == 'sqlalchemy-drivers'
79
+ Requires-Dist: sqlean.py ~=3.45 ; (python_version < "3.13") and extra == 'sqlalchemy-drivers'
80
80
  Provides-Extra: testing
81
81
  Requires-Dist: pytest ~=8.0 ; extra == 'testing'
82
82
 
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=N1F-Xz3GaBn2H1p7uKzhkhKCQV8QVR0t76XD6wmFtXA,3
2
- omlish/__about__.py,sha256=KzvTeQDmeZtJfbz_X82IPqzh5xJ44YhJRVMI4N3c2Ig,2698
2
+ omlish/__about__.py,sha256=G5PhHDsjnmH0v-N3l7GO0RtaA0iLAw7M9ihGEGPM0x4,2709
3
3
  omlish/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  omlish/argparse.py,sha256=QRQmX9G0-L_nATkFtGHvpd4qrpYzKATdjuFLbBqzJPM,6224
5
5
  omlish/c3.py,sha256=W5EwYx9Por3rWYLkKUitJ6OoRMLLgVTfLTyroOz41Y0,8047
@@ -7,32 +7,33 @@ omlish/cached.py,sha256=UAizxlH4eMWHPzQtmItmyE6FEpFEUFzIkxaO2BHWZ5s,196
7
7
  omlish/check.py,sha256=3qp1_W8uRp23I26nWvG_c7YFxdTwJAZEFxmY8Bfw50Y,10078
8
8
  omlish/datetimes.py,sha256=HajeM1kBvwlTa-uR1TTZHmZ3zTPnnUr1uGGQhiO1XQ0,2152
9
9
  omlish/defs.py,sha256=T3bq_7h_tO3nDB5RAFBn7DkdeQgqheXzkFColbOHZko,4890
10
- omlish/docker.py,sha256=wipM7Xsx7rUv7-PoBTTT2v0uk3JVan0USljgm2QesXg,6227
10
+ omlish/docker.py,sha256=uRVJUJwGtTFYhm4XeztrVEYk93f0NI5VpjEKQaN_5uY,6453
11
11
  omlish/dynamic.py,sha256=35C_cCX_Vq2HrHzGk5T-zbrMvmUdiIiwDzDNixczoDo,6541
12
12
  omlish/fnpairs.py,sha256=hVuLqQFdRNNze3FYH2cAQO3GC7nK5yQP_GWPUSbL7nE,10601
13
+ omlish/fnpipes.py,sha256=AJkgz9nvRRm7oqw7ZgYyz21klu276LWi54oYCLg-vOg,2196
13
14
  omlish/genmachine.py,sha256=LCMiqvK32dAWtrlB6lKw9tXdQFiXC8rRdk4TMQYIroU,1603
14
15
  omlish/iterators.py,sha256=GGLC7RIT86uXMjhIIIqnff_Iu5SI_b9rXYywYGFyzmo,7292
15
16
  omlish/libc.py,sha256=u0481imCiTFqP_e-v9g0pD-0WD249j5vYzhtn-fnNkY,15308
16
- omlish/matchfns.py,sha256=ygqbqthRxgF9I1PJaw9Xl7FoZnAVMmnKBrYgimKQ0ag,6152
17
+ omlish/matchfns.py,sha256=I1IlQGfEyk_AcFSy6ulVS3utC-uwyZM2YfUXYHc9Bw0,6152
17
18
  omlish/multiprocessing.py,sha256=QZT4C7I-uThCAjaEY3xgUYb-5GagUlnE4etN01LDyU4,5186
18
- omlish/os.py,sha256=cz4nL2ujaxH_-XRq3JUD8af8mSe1JXGPIoXP9XAEd0M,2607
19
+ omlish/os.py,sha256=vO1sZCzAhVxo46tDFLrD52q2KuMFWQwu5MPJgSsnliI,3107
19
20
  omlish/runmodule.py,sha256=PWvuAaJ9wQQn6bx9ftEL3_d04DyotNn8dR_twm2pgw0,700
20
21
  omlish/sync.py,sha256=AqwIfIuCMVHLwlJUa7dmaSjfA4sM5AYPCD5-nsz3XVQ,1516
21
22
  omlish/term.py,sha256=NEmxqAhicyInGtmFamZAizI2xdu819MzFYPEp0Fx97M,6111
22
23
  omlish/asyncs/__init__.py,sha256=uUz9ziKh4_QrgmdhKFMgq6j7mFbiZd3LiogguDCQsGI,587
23
- omlish/asyncs/anyio.py,sha256=Hqdi1iCopKoaAWGx-AYTRLEwnavLWx1esfJISK1IVF0,8024
24
+ omlish/asyncs/anyio.py,sha256=gfpx-D8QGmUfhnQxHEaHXcAP8zSMQjcGw4COFTGNnHI,8021
24
25
  omlish/asyncs/asyncio.py,sha256=JfM59QgB3asgEbrps0zoVbNjWD4kL2XdsEkRMEIoFos,971
25
26
  omlish/asyncs/asyncs.py,sha256=Tf7ZodTGepkM7HAuFcDNh9lLzzrMw6rELWvopGmFkh4,2035
26
- omlish/asyncs/bridge.py,sha256=fkMQWG2TNmPB9BfIxtXrE50W5FJw0ma44GleOXbcSKw,8628
27
+ omlish/asyncs/bridge.py,sha256=bcTEhsBJOWAMixu_fCxkDedLP3NEitAfwaHYTu26sQE,8980
27
28
  omlish/asyncs/flavors.py,sha256=1mNxGNRVmjUHzA13K5ht8vdJv4CLEmzYTQ6BZXr1520,4866
28
- omlish/asyncs/trio.py,sha256=GKG3wgelFr7gIKKHZhcflvMyCvxXHNZe862KB0Xw2uA,370
29
+ omlish/asyncs/trio.py,sha256=fmZ5b_lKdVV8NQ3euCUutWgnkqTFzSnOjvJSA_jvmrE,367
29
30
  omlish/asyncs/trio_asyncio.py,sha256=oqdOHy0slj9PjVxaDf3gJkq9AAgg7wYZbB469jOftVw,1327
30
31
  omlish/bootstrap/__init__.py,sha256=-Rtsg7uPQNhh1dIT9nqrz96XlqizwoLnWf-FwOEstJI,730
31
32
  omlish/bootstrap/__main__.py,sha256=d23loR_cKfTYZwYiqpt_CmKI7dd5WcYFgIYzqMep75E,68
32
33
  omlish/bootstrap/base.py,sha256=koELbK6UmsZaRj-6Bng5_zPVmEBqDpFCduEdR5BddOs,1077
33
34
  omlish/bootstrap/diag.py,sha256=x_BKS_uhfW8QFk1NeH_VIocHif-A6FVTZ37262qCgZ0,5052
34
35
  omlish/bootstrap/harness.py,sha256=pIeSXKfMsF7-3ZkU0gGpde-PtLAKKcVrWaxcin7Xzy0,2041
35
- omlish/bootstrap/main.py,sha256=9ZbgXaRNZwt_hXdg8W4YjpP0v0Si_qqlfu86jfgRz04,5215
36
+ omlish/bootstrap/main.py,sha256=u-TfxO4GkAK3LbRweQERogtO4kT0z3gqXRjnXvtJzC8,5328
36
37
  omlish/bootstrap/marshal.py,sha256=qKewGVs-3i2p5W9nywkXSo1pcbVxmOAlTvfLjyo0xpo,554
37
38
  omlish/bootstrap/sys.py,sha256=U0MFxO9tLFV3cdN5Y-Zrink6_45sFvzPUYQXyBk7-ns,8741
38
39
  omlish/collections/__init__.py,sha256=tGUzvS_ZjiqALsLRy7JX3h4KZRQX2CmtdAfTRD7UwMk,1677
@@ -59,7 +60,7 @@ omlish/collections/cache/types.py,sha256=yNjwd6CGyTJQdxN2CQxFqqBAlcs1Z7vvNV-aU1K
59
60
  omlish/concurrent/__init__.py,sha256=9p-s8MvBEYDqHIoYU3OYoe-Nni22QdkW7nhZGEukJTM,197
60
61
  omlish/concurrent/executors.py,sha256=FYKCDYYuj-OgMa8quLsA47SfFNX3KDJvRENVk8NDsrA,1292
61
62
  omlish/concurrent/futures.py,sha256=J2s9wYURUskqRJiBbAR0PNEAp1pXbIMYldOVBTQduQY,4239
62
- omlish/concurrent/threadlets.py,sha256=eQLJBJwT6iOt4jOm67BberSKLPhaEYJTStHK0LCo2v4,2374
63
+ omlish/concurrent/threadlets.py,sha256=0fl0_Q_joTLtFYhR2kpKu45BW-win-le0rCuXPBtjko,2382
63
64
  omlish/configs/__init__.py,sha256=3uh09ezodTwkMI0nRmAMP0eEuJ_0VdF-LYyNmPjHiCE,77
64
65
  omlish/configs/classes.py,sha256=GLbB8xKjHjjoUQRCUQm3nEjM8z1qNTx9gPV7ODSt5dg,1317
65
66
  omlish/configs/flattening.py,sha256=AOlRpBHm449MxwMp3CiIRGunStOC1DUNs1f3CLou0wc,4731
@@ -101,7 +102,7 @@ omlish/diag/threads.py,sha256=1-x02VCDZ407gfbtXm1pWK-ubqhqfePm9PMqkHCVoqk,3642
101
102
  omlish/diag/replserver/__init__.py,sha256=uLo6V2aQ29v9z3IMELlPDSlG3_2iOT4-_X8VniF-EgE,235
102
103
  omlish/diag/replserver/__main__.py,sha256=LmU41lQ58bm1h4Mx7S8zhE_uEBSC6kPcp9mn5JRpulA,32
103
104
  omlish/diag/replserver/console.py,sha256=XzBDVhYlr8FY6ym4OwoaIHuFOHnGK3dTYlMDIOMUUlA,7410
104
- omlish/diag/replserver/server.py,sha256=To2rLS-FHc0Ny8iFJaeEsU8ZZdkcWpk9bi1FYS7YNPA,5348
105
+ omlish/diag/replserver/server.py,sha256=5pRjBn-Vz8gtu45756ycdbJe58C_xrB9rElPytr6h5g,5345
105
106
  omlish/dispatch/__init__.py,sha256=GsiGJ91NKiQptSROtnCSkrZExBkvfDwYvdoTu5dBqF0,117
106
107
  omlish/dispatch/_dispatch2.py,sha256=v3tCNyxGpOwY8qTwdp54TlM8mG6OVwtQoUZfYJ_griU,1756
107
108
  omlish/dispatch/_dispatch3.py,sha256=Vnu5DfoPWFJLodudBqoZBXGTi2wYk-Az56MXJgdQvwc,2608
@@ -130,7 +131,7 @@ omlish/http/cookies.py,sha256=uuOYlHR6e2SC3GM41V0aozK10nef9tYg83Scqpn5-HM,6351
130
131
  omlish/http/dates.py,sha256=Otgp8wRxPgNGyzx8LFowu1vC4EKJYARCiAwLFncpfHM,2875
131
132
  omlish/http/encodings.py,sha256=w2WoKajpaZnQH8j-IBvk5ZFL2O2pAU_iBvZnkocaTlw,164
132
133
  omlish/http/json.py,sha256=9XwAsl4966Mxrv-1ytyCqhcE6lbBJw-0_tFZzGszgHE,7440
133
- omlish/http/sessions.py,sha256=gj_FgDoETAnLh_pISlwrqS78LFc1c3RNAKHSLYBEr5s,4793
134
+ omlish/http/sessions.py,sha256=VZ_WS5uiQG5y7i3u8oKuQMqf8dPKUOjFm_qk_0OvI8c,4793
134
135
  omlish/http/wsgi.py,sha256=czZsVUX-l2YTlMrUjKN49wRoP4rVpS0qpeBn4O5BoMY,948
135
136
  omlish/inject/__init__.py,sha256=JQ7x8l9MjU-kJ5ap7cPVq7SY7zbbCIrjyJAF0UeE5-s,1886
136
137
  omlish/inject/binder.py,sha256=H8AQ4ecmBOtDL8fMgrU1yUJl1gBADLNcdysRbvO8Wso,4167
@@ -162,11 +163,11 @@ omlish/inject/impl/privates.py,sha256=alpCYyk5VJ9lJknbRH2nLVNFYVvFhkj-VC1Vco3zCF
162
163
  omlish/inject/impl/providers.py,sha256=QnwhsujJFIHC0JTgd2Wlo1kP53i3CWTrj1nKU2DNxwg,2375
163
164
  omlish/inject/impl/proxy.py,sha256=1ko0VaKqzu9UG8bIldp9xtUrAVUOFTKWKTjOCqIGr4s,1636
164
165
  omlish/inject/impl/scopes.py,sha256=ASfULXgP_ETlsAqFJfrZmyEaZt64Zr8tNn5ScA-EoXk,5900
165
- omlish/lang/__init__.py,sha256=7GgNUH1ez1N68rkTN1-zKDaVOoDHW-BZ9V_H2LIFfqM,3532
166
+ omlish/lang/__init__.py,sha256=0OCNjiSSyFL3RSNtakj0ILhC1K2P2WJkDTAgkC7xMjA,3601
166
167
  omlish/lang/cached.py,sha256=LwsgWQjQ5op618rBvI8vbASOEGWDTt_SKq6Tc1vlgZM,7680
167
168
  omlish/lang/clsdct.py,sha256=AjtIWLlx2E6D5rC97zQ3Lwq2SOMkbg08pdO_AxpzEHI,1744
168
169
  omlish/lang/cmp.py,sha256=5vbzWWbqdzDmNKAGL19z6ZfUKe5Ci49e-Oegf9f4BsE,1346
169
- omlish/lang/contextmanagers.py,sha256=rzMSwJU7ObFXl46r6pGDbD45Zi_qZ9NHxDPnLNuux9o,9732
170
+ omlish/lang/contextmanagers.py,sha256=NEwaTLQMfhKawD5x_0HgI2RpeLXbMa5r9NqWqfDnUXI,10408
170
171
  omlish/lang/datetimes.py,sha256=ehI_DhQRM-bDxAavnp470XcekbbXc4Gdw9y1KpHDJT0,223
171
172
  omlish/lang/descriptors.py,sha256=OLM1qi14kY7PLGIJnvkd6CBEOzHgD9q8Cs2cB6Kzflk,6602
172
173
  omlish/lang/exceptions.py,sha256=qJBo3NU1mOWWm-NhQUHCY5feYXR3arZVyEHinLsmRH4,47
@@ -176,13 +177,13 @@ omlish/lang/iterables.py,sha256=_q6rHbdFfW3VBqez0IV3rUABoNxsA_oBv_sykm5zsbQ,2243
176
177
  omlish/lang/maybes.py,sha256=NYHZDjqDtwPMheDrj2VtUVujxRPf8Qpgk4ZlZCTvBZc,3492
177
178
  omlish/lang/objects.py,sha256=1dY8dX5voIZf5FBYUiN0BRsWg2JCdsgRbDl9fLG7OtY,4310
178
179
  omlish/lang/resolving.py,sha256=OuN2mDTPNyBUbcrswtvFKtj4xgH4H4WglgqSKv3MTy0,1606
179
- omlish/lang/resources.py,sha256=BD3EaLvTUPpUHfccfiSnnjuvlRhFwv_bcVOzwPPh1rw,2134
180
- omlish/lang/strings.py,sha256=ykeoou4JK7CEZXzrUJfqVOalEDvE--j0uhHt_SrsrUs,2834
180
+ omlish/lang/resources.py,sha256=-NmVTrSMKFZ6smVfOMz46ekZYVGgYh8cPooxQlFpG6s,2135
181
+ omlish/lang/strings.py,sha256=LqxR49cF5owHKpbUX8nUIBPsZ-4w7xqHPhlB-HIWqUg,3628
181
182
  omlish/lang/sys.py,sha256=UoZz_PJYVKLQAKqYxxn-LHz1okK_38I__maZgnXMcxU,406
182
183
  omlish/lang/timeouts.py,sha256=vECdWYhc_IZgcal1Ng1Y42wf2FV3KAx-i8As-MgGHIQ,1186
183
184
  omlish/lang/typing.py,sha256=lJ2NGe4Pmb61I0Tx4A_rOqXNFTws1XHOzafg2knRUio,4155
184
185
  omlish/lang/classes/__init__.py,sha256=h9QXrvAKD17_pIog0uF-7BCqZbSpJZYxL7kzVzvljp0,583
185
- omlish/lang/classes/abstract.py,sha256=goIV14oY24EOs88eVe6E6NyrSPOOLMOcWTXTMuYKiqc,2304
186
+ omlish/lang/classes/abstract.py,sha256=IRnjuLLNwpxvEJsp8fwoQdCIpw0MDAd0TiQfoDMgsn4,2306
186
187
  omlish/lang/classes/restrict.py,sha256=pSK7ZT_kpwqS6lWRrxwuEe-tt07F0-uZVazgGh-HDco,3921
187
188
  omlish/lang/classes/simple.py,sha256=JkWYrRWnSKil6kVMgXgRMJeCxkFHXQIKIzYtG1jK2Pk,3067
188
189
  omlish/lang/classes/virtual.py,sha256=W-QJuKsDehOcrydwg6eMN0bFPTYbk3Tz84TSH3blb44,3367
@@ -199,7 +200,7 @@ omlish/lite/cached.py,sha256=dUm647FbIsoxWT23XUFM51F7i-C2Buxr5b5zzgbCtQI,546
199
200
  omlish/lite/check.py,sha256=DYxkYkxpnQpjGpDwW_8M8YBXbswvnBUCr0UBPU7aNKo,642
200
201
  omlish/lite/contextmanagers.py,sha256=HnQJiyrOmSvTL22XRJrFl5CLpCyHD9fsntEUAr9G-60,427
201
202
  omlish/lite/json.py,sha256=7-02Ny4fq-6YAu5ynvqoijhuYXWpLmfCI19GUeZnb1c,740
202
- omlish/lite/logs.py,sha256=PeJZUJWd1K-UFErVs0wtD0zRMFW5-olUNFVVZAj_ScY,5549
203
+ omlish/lite/logs.py,sha256=vkFkSX0Izb2P-NNMqqNLSec0BzeLOtHoQWgdXwQuDPU,6007
203
204
  omlish/lite/marshal.py,sha256=u6jYUN_AndvI6__HJBvSw5ElHWC0CfHqgiDS28Vpqjg,8593
204
205
  omlish/lite/reflect.py,sha256=9QYJwdINraq1JNMEgvoqeSlVvRRgOXpxAkpgX8EgRXc,1307
205
206
  omlish/lite/runtime.py,sha256=VUhmNQvwf8QzkWSKj4Q0ReieJA_PzHaJNRBivfTseow,452
@@ -211,7 +212,7 @@ omlish/logs/_abc.py,sha256=UgrCUQVUi_PvT3p1CEkb3P74CFrFcZq2AFby3GEUv9M,5974
211
212
  omlish/logs/configs.py,sha256=EE0jlNaXJbGnM7V-y4xS5VwyTBSTzFzc0BYaVjg0JmA,1283
212
213
  omlish/logs/formatters.py,sha256=q79nMnR2mRIStPyGrydQHpYTXgC5HHptt8lH3W2Wwbs,671
213
214
  omlish/logs/handlers.py,sha256=nyuFgmO05By_Xwq7es58ClzS51-F53lJL7gD0x5IqAg,228
214
- omlish/logs/noisy.py,sha256=8JORjI1dH38yU2MddM54OB6qt32Xozfocdb88vY4wro,335
215
+ omlish/logs/noisy.py,sha256=Ubc-eTH6ZbGYsLfUUi69JAotwuUwzb-SJBeGo_0dIZI,348
215
216
  omlish/logs/utils.py,sha256=MgGovbP0zUrZ3FGD3qYNQWn-l0jy0Y0bStcQvv5BOmQ,391
216
217
  omlish/marshal/__init__.py,sha256=RkZGfdp7x9KU8nEpb8hYBGANK-P_9AXMyjBXCrdYMmc,1757
217
218
  omlish/marshal/any.py,sha256=e82OyYK3Emm1P1ClnsnxP7fIWC2iNVyW0H5nK4mLmWM,779
@@ -228,11 +229,11 @@ omlish/marshal/helpers.py,sha256=YA0pNo-Fkc-_qKeoRNXUpP36js8oelU7uENkvoGD4hY,120
228
229
  omlish/marshal/iterables.py,sha256=6I_ZdJemLSQtJ4J5NrB9wi-eyxiJZS61HzHXp1yeiX8,2592
229
230
  omlish/marshal/mappings.py,sha256=zhLtyot7tzQtBNj7C4RBxjMELxA5r2q2Mth8Br7xkFs,2803
230
231
  omlish/marshal/maybes.py,sha256=tKkVsJATERgbVcEfBnsHBK_2_LCQIVyBzca-cA-9KH0,2112
231
- omlish/marshal/naming.py,sha256=UCviMAXTTUpW1lyAGymybGP2rFUAW44P1X0zrIVbvi4,464
232
+ omlish/marshal/naming.py,sha256=lIklR_Od4x1ghltAgOzqcKhHs-leeSv2YmFhCHO7GIs,613
232
233
  omlish/marshal/numbers.py,sha256=oY_yMNJEnJhjfLh89gpPXvKqeUyhQcaTcQB6ecyHiG8,1704
233
234
  omlish/marshal/objects.py,sha256=R-NPCT1-UZhONTnrsrAvZvAtM2qyQsKZ8CPLfqkSg5g,4494
234
235
  omlish/marshal/optionals.py,sha256=r0XB5rqfasvgZJNrKYd6Unq2U4nHt3JURi26j0dYHlw,1499
235
- omlish/marshal/polymorphism.py,sha256=gyvNYUAkmQVhWrcXBLzXINxqx6RHyulf9n16Iv38PFI,5597
236
+ omlish/marshal/polymorphism.py,sha256=doA8aLUhna6aco5b2Ok3jsem1V4NsF3rM5RTfJt0a7U,5708
236
237
  omlish/marshal/primitives.py,sha256=wcvcs5GH_TWVmzAszh3dvyKibJgBxnXke-AlAXiwrrI,1107
237
238
  omlish/marshal/registries.py,sha256=GI2KogcxawMkk02Ky7-TsnijChoe1I7YTOPIbUNwSAI,1665
238
239
  omlish/marshal/standard.py,sha256=uQZIGiCwihmhB1tmhpKnZWZly0DDkdGjCnN0d41WHho,2985
@@ -256,7 +257,7 @@ omlish/secrets/openssl.py,sha256=wxA_wIlxtuOUy71ABxAJgavh-UI_taOfm-A0dVlmSwM,621
256
257
  omlish/secrets/passwords.py,sha256=3r-vEK6Gp6aq4L5Csnd06QnrjO9xfzHJP-g_7I9W_ao,4101
257
258
  omlish/secrets/secrets.py,sha256=ClD7t_mkmWkseVk4ahLzYLuLXeTxiwwPiidYm42vLh4,6871
258
259
  omlish/secrets/subprocesses.py,sha256=EcnKlHHtnUMHGrBWXDfu8tv28wlgZx4P4GOiuPW9Vo8,1105
259
- omlish/specs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
260
+ omlish/specs/__init__.py,sha256=_CSPXx25uGhXkmqLs9CSdcWAkZaQnc3cJk3lb2dadgE,88
260
261
  omlish/specs/jsonschema/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
261
262
  omlish/specs/jsonschema/types.py,sha256=qoxExgKfrI-UZXdk3qcVZIEyp1WckFbb85_eGInEoAY,467
262
263
  omlish/specs/jsonschema/keywords/__init__.py,sha256=Zt2g1BXd654uU2AQ5P7_-x2Wrtf6cNbP9mxI1wGN4wo,596
@@ -279,15 +280,16 @@ omlish/specs/jsonschema/schemas/draft202012/vocabularies/format.json,sha256=UOu_
279
280
  omlish/specs/jsonschema/schemas/draft202012/vocabularies/meta-data.json,sha256=j3bW4U9Bubku-TO3CM3FFEyLUmhlGtEZGEhfsXVPHHY,892
280
281
  omlish/specs/jsonschema/schemas/draft202012/vocabularies/unevaluated.json,sha256=Lb-8tzmUtnCwl2SSre4f_7RsIWgnhNL1pMpWH54tDLQ,506
281
282
  omlish/specs/jsonschema/schemas/draft202012/vocabularies/validation.json,sha256=cBCjHlQfMtK-ch4t40jfdcmzaHaj7TBId_wKvaHTelg,2834
282
- omlish/sql/__init__.py,sha256=QHKkiM0rvSDWKUYrvTSynOni5fB46QQ2gOw_TcMBQ2o,398
283
+ omlish/sql/__init__.py,sha256=TpZLsEJKJzvJ0eMzuV8hwOJJbkxBCV1RZPUMLAVB6io,173
283
284
  omlish/sql/_abc.py,sha256=HLhnnLZ7l0r_N99I-RCqJe6VHth-9iluU7cR-7-5jfs,1519
284
- omlish/sql/asyncs.py,sha256=Wye3dwh7oZEGYz2Y4DZQSHtW4xjI2AH5qjW-BSS2IfU,3688
285
285
  omlish/sql/dbs.py,sha256=lpdFmm2vTwLoBiVYGj9yPsVcTEYYNCxlYZZpjfChzkY,1870
286
- omlish/sql/duckdb.py,sha256=Z3wiZEn_21Lu1ElFRX0ATzoBMCw0KJxINjTRuTexYGM,3748
287
- omlish/sql/exprs.py,sha256=gO4Fj4xEY-PuDgV-N8hBMy55glZz7O-4H7v1LWabfZY,323
288
286
  omlish/sql/qualifiedname.py,sha256=rlW3gVmyucJbqwcxj_7BfK4X2HoXrMroZT2H45zPgJQ,2264
289
- omlish/sql/secrets.py,sha256=mDUunIACxHBsPD_ONbHQJVndeMMzJR4vMC2WWX7tGfY,177
290
- omlish/sql/sqlean.py,sha256=RbkuOuFIfM4fowwKk8-sQ6Dxk-tTUwxS94nY5Kxt52s,403
287
+ omlish/sql/alchemy/__init__.py,sha256=1ruDMiviH5fjevn2xVki-QspcE9O3VPy4hxOqpHjI2s,224
288
+ omlish/sql/alchemy/asyncs.py,sha256=C1bIzz9m2Qbgl4qYGQt_FRQg4BKRcxUqMsVg9RtnTPg,3689
289
+ omlish/sql/alchemy/duckdb.py,sha256=kr7pIhiBLNAuZrcigHDtFg9zHkVcrRW3LfryO9VJ4mk,3749
290
+ omlish/sql/alchemy/exprs.py,sha256=gO4Fj4xEY-PuDgV-N8hBMy55glZz7O-4H7v1LWabfZY,323
291
+ omlish/sql/alchemy/secrets.py,sha256=EMfy4EfTbEvrlv_41oOhn8qsoF-eTkY7HciPenIE6rI,178
292
+ omlish/sql/alchemy/sqlean.py,sha256=RbkuOuFIfM4fowwKk8-sQ6Dxk-tTUwxS94nY5Kxt52s,403
291
293
  omlish/testing/__init__.py,sha256=kfiF10ykrjWXniedIl3g8j3pNAFyuSbp1D3ZXug3-Eo,168
292
294
  omlish/testing/testing.py,sha256=pJUbZ0ymdrQoNG9r9UlGXypeU1x9ntEp9xcYBnyOHUs,3088
293
295
  omlish/testing/pytest/__init__.py,sha256=b6ObMEHTZnvGEI_de6nN1x5FyitV6B2mNYkurA4Q7fo,336
@@ -297,7 +299,7 @@ omlish/testing/pytest/inject/__init__.py,sha256=pdRKv1HcDmJ_yArKJbYITPXXZthRSGgB
297
299
  omlish/testing/pytest/inject/harness.py,sha256=sMKjP2EWHq-eeTB1YVXcANli2Czxt56_9ERg4HtkVPg,5810
298
300
  omlish/testing/pytest/plugins/__init__.py,sha256=ys1zXrYrNm7Uo6YOIVJ6Bd3dQo6kv387k7MbTYlqZSI,467
299
301
  omlish/testing/pytest/plugins/_registry.py,sha256=IK04KlBgiOJxKAyCCgjpX2R-9tE-btalYJkgjLc8Te8,77
300
- omlish/testing/pytest/plugins/asyncs.py,sha256=1eiKJMzSxhMMGHXbYyCTDElTyRrA31sK5Q80JR9YlBE,5054
302
+ omlish/testing/pytest/plugins/asyncs.py,sha256=SV6oKCy50CGkzLGYX-CT4MfWNqsrH8ONEbIWC3tFcHA,5324
301
303
  omlish/testing/pytest/plugins/depskip.py,sha256=xithY-OMtjwhv8mcRNkv-WI_PSQtHldQ8H1s60MIXkk,2673
302
304
  omlish/testing/pytest/plugins/logging.py,sha256=1zs6Xe54wiaSjabCviaFXwKkoN97CKm3mA5mEoUeJGs,380
303
305
  omlish/testing/pytest/plugins/managermarks.py,sha256=pDEcCNdDAcTS4jjZHSnAfmzqMJDBcJcSsM3QNhbJ6Gs,1485
@@ -305,7 +307,7 @@ omlish/testing/pytest/plugins/pydevd.py,sha256=u1fxfCgFw4wGKBkMV_H_l9WI8JoUwlRff
305
307
  omlish/testing/pytest/plugins/repeat.py,sha256=flSQzE9GFOWksVKz-mUGnpxJpv3yRqn1G4K8pW7JHs0,498
306
308
  omlish/testing/pytest/plugins/skips.py,sha256=EoZDg1uWccgbAegmzqI85c7RliycD1e2J4Y7vfDRhwM,1041
307
309
  omlish/testing/pytest/plugins/spacing.py,sha256=JQQhi9q3c523Ro1a_K_9RGAb7HotiO74N8bYX2VESFE,707
308
- omlish/testing/pytest/plugins/switches.py,sha256=9FtN5qtPBoS-teEp54OHPF6jlZJakRJdq4pnLJpPj_A,3001
310
+ omlish/testing/pytest/plugins/switches.py,sha256=hqFYM6tCcr-6fAPg8DBI-TJn762Kq4EUFt6gjXgiAQQ,3802
309
311
  omlish/testing/pytest/plugins/utils.py,sha256=L5C622UXcA_AUKDcvyh5IMiRfqSGGz0McdhwZWvfMlU,261
310
312
  omlish/text/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
311
313
  omlish/text/asdl.py,sha256=3v5UocAfxan_d9drkGNdH3AMfx_FFBpQu3ULGL4M6VM,16865
@@ -313,8 +315,8 @@ omlish/text/delimit.py,sha256=ubPXcXQmtbOVrUsNh5gH1mDq5H-n1y2R4cPL5_DQf68,4928
313
315
  omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,3296
314
316
  omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
315
317
  omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
316
- omlish-0.0.0.dev29.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
317
- omlish-0.0.0.dev29.dist-info/METADATA,sha256=J9BcC0SJmQjneAgNGyWD756p2EyX8iSvs92bU2JAQH4,3636
318
- omlish-0.0.0.dev29.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
319
- omlish-0.0.0.dev29.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
320
- omlish-0.0.0.dev29.dist-info/RECORD,,
318
+ omlish-0.0.0.dev31.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
319
+ omlish-0.0.0.dev31.dist-info/METADATA,sha256=ONRqZOyFknW_WO5Hntonm5hY10yIlv645eKTmoXhhyY,3694
320
+ omlish-0.0.0.dev31.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
321
+ omlish-0.0.0.dev31.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
322
+ omlish-0.0.0.dev31.dist-info/RECORD,,
File without changes
File without changes