omlish 0.0.0.dev303__py3-none-any.whl → 0.0.0.dev305__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 +2 -2
  2. omlish/dataclasses/__init__.py +5 -0
  3. omlish/dataclasses/api/__init__.py +1 -0
  4. omlish/dataclasses/api/fields/metadata.py +8 -0
  5. omlish/dataclasses/tools/only_.py +45 -0
  6. omlish/diag/_pycharm/runhack.py +4 -1
  7. omlish/graphs/trees.py +6 -0
  8. omlish/lang/__init__.py +7 -0
  9. omlish/lang/cached/function.py +7 -2
  10. omlish/lang/casing.py +2 -2
  11. omlish/lang/objects.py +28 -0
  12. omlish/lang/recursion.py +109 -0
  13. omlish/marshal/__init__.py +1 -1
  14. omlish/marshal/objects/helpers.py +1 -1
  15. omlish/specs/jsonschema/__init__.py +5 -0
  16. omlish/specs/jsonschema/keywords/base.py +21 -1
  17. omlish/specs/jsonschema/keywords/parse.py +22 -3
  18. omlish/specs/jsonschema/keywords/render.py +22 -3
  19. omlish/specs/jsonschema/keywords/validation.py +30 -3
  20. omlish/specs/jsonschema/schemas/LICENSE +21 -0
  21. omlish/sql/api/__init__.py +8 -1
  22. omlish/sql/api/base.py +1 -1
  23. omlish/sql/api/funcs.py +70 -11
  24. omlish/sql/qualifiedname.py +20 -12
  25. omlish/sql/queries/__init__.py +3 -0
  26. omlish/sql/queries/base.py +16 -0
  27. omlish/sql/queries/idents.py +12 -1
  28. omlish/sql/queries/names.py +8 -1
  29. omlish/sql/queries/relations.py +0 -12
  30. omlish/sql/queries/rendering.py +0 -3
  31. {omlish-0.0.0.dev303.dist-info → omlish-0.0.0.dev305.dist-info}/METADATA +1 -1
  32. {omlish-0.0.0.dev303.dist-info → omlish-0.0.0.dev305.dist-info}/RECORD +36 -33
  33. {omlish-0.0.0.dev303.dist-info → omlish-0.0.0.dev305.dist-info}/WHEEL +1 -1
  34. {omlish-0.0.0.dev303.dist-info → omlish-0.0.0.dev305.dist-info}/entry_points.txt +0 -0
  35. {omlish-0.0.0.dev303.dist-info → omlish-0.0.0.dev305.dist-info}/licenses/LICENSE +0 -0
  36. {omlish-0.0.0.dev303.dist-info → omlish-0.0.0.dev305.dist-info}/top_level.txt +0 -0
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev303'
2
- __revision__ = '8c675f0f9a5c459eddf54867184cd1da05cff6ac'
1
+ __version__ = '0.0.0.dev305'
2
+ __revision__ = '13f1bfc223e6af63310421aa4ac6133b3f1e0110'
3
3
 
4
4
 
5
5
  #
@@ -60,6 +60,7 @@ from .api import ( # noqa
60
60
  extra_field_params,
61
61
  set_field_metadata,
62
62
  update_extra_field_params,
63
+ with_extra_field_params,
63
64
  )
64
65
 
65
66
  from .errors import ( # noqa
@@ -108,6 +109,10 @@ from .tools.modifiers import ( # noqa
108
109
  update_fields,
109
110
  )
110
111
 
112
+ from .tools.only_ import ( # noqa
113
+ only,
114
+ )
115
+
111
116
  from .tools.replace import ( # noqa
112
117
  deep_replace,
113
118
  )
@@ -18,6 +18,7 @@ from .fields.metadata import ( # noqa
18
18
  extra_field_params,
19
19
  set_field_metadata,
20
20
  update_extra_field_params,
21
+ with_extra_field_params,
21
22
  )
22
23
 
23
24
  from .fields.constructor import ( # noqa
@@ -5,6 +5,7 @@ from .... import check
5
5
  from .... import lang
6
6
  from ...debug import DEBUG
7
7
  from ...specs import FieldSpec
8
+ from ...tools.modifiers import field_modifier
8
9
  from ...utils import chain_mapping_proxy
9
10
 
10
11
 
@@ -92,3 +93,10 @@ def update_extra_field_params(
92
93
  **(fe if unless_non_default else {}),
93
94
  },
94
95
  })
96
+
97
+
98
+ def with_extra_field_params(**kwargs: ta.Any) -> field_modifier:
99
+ @field_modifier
100
+ def inner(f: dc.Field) -> dc.Field:
101
+ return update_extra_field_params(f, **kwargs)
102
+ return inner
@@ -0,0 +1,45 @@
1
+ import collections.abc
2
+ import typing as ta
3
+
4
+ from .iter import fields_dict
5
+
6
+
7
+ ##
8
+
9
+
10
+ def _only_test(v: ta.Any) -> bool:
11
+ if v is None:
12
+ return False
13
+ elif isinstance(v, collections.abc.Iterable):
14
+ return bool(v)
15
+ else:
16
+ return True
17
+
18
+
19
+ def only(
20
+ obj: ta.Any,
21
+ *flds: str,
22
+ all: bool = False, # noqa
23
+ test: ta.Callable[[ta.Any], bool] = _only_test,
24
+ ) -> bool:
25
+ fdct = fields_dict(obj)
26
+ for fn in flds:
27
+ if fn not in fdct:
28
+ raise KeyError(fn)
29
+
30
+ rem = set(flds)
31
+ for fn, f in fdct.items():
32
+ if not f.compare and fn not in rem:
33
+ continue
34
+
35
+ v = getattr(obj, fn)
36
+ if test(v):
37
+ if fn in rem:
38
+ rem.remove(fn)
39
+ else:
40
+ return False
41
+
42
+ if rem and all:
43
+ return False
44
+
45
+ return True
@@ -24,7 +24,10 @@ DEBUG_ENV_VAR = 'OMLISH_PYCHARM_RUNHACK_DEBUG'
24
24
 
25
25
  #
26
26
 
27
- _DEFAULT_PTH_FILE_NAME = 'omlish-pycharm-runhack.pth'
27
+ # This hack must run first, before things like '__editable__.foo.pth' files installed by setuptools. pth file processing
28
+ # is sorted by name, so prepend with underscores:
29
+ # https://github.com/python/cpython/blob/aeb3a6f61af53ed3fbf31f0b3704f49b71ac553c/Lib/site.py#L246
30
+ _DEFAULT_PTH_FILE_NAME = '___omlish-pycharm-runhack.pth'
28
31
 
29
32
  _DEFAULT_DEBUG = False
30
33
  _DEFAULT_ENABLED = True
omlish/graphs/trees.py CHANGED
@@ -19,6 +19,9 @@ NodeWalker = ta.Callable[[NodeT], ta.Iterable[NodeT]]
19
19
  NodeGenerator = ta.Generator[NodeT, None, None]
20
20
 
21
21
 
22
+ ##
23
+
24
+
22
25
  class NodeError(ta.Generic[NodeT], Exception):
23
26
  def __init__(self, node: NodeT, msg: str, *args, **kwargs) -> None:
24
27
  super().__init__(msg, *args, **kwargs) # noqa
@@ -39,6 +42,9 @@ class UnknownNodeError(NodeError[NodeT]):
39
42
  super().__init__(node, f'Unknown node: {node!r}', *args, **kwargs)
40
43
 
41
44
 
45
+ #
46
+
47
+
42
48
  class BasicTreeAnalysis(ta.Generic[NodeT]):
43
49
  def __init__(
44
50
  self,
omlish/lang/__init__.py CHANGED
@@ -231,6 +231,7 @@ from .maybes import ( # noqa
231
231
  )
232
232
 
233
233
  from .objects import ( # noqa
234
+ Identity,
234
235
  SimpleProxy,
235
236
  anon_object,
236
237
  arg_repr,
@@ -269,6 +270,12 @@ from .params import ( # noqa
269
270
  param_render,
270
271
  )
271
272
 
273
+ from .recursion import ( # noqa
274
+ LimitedRecursionError,
275
+ recursion_limiting,
276
+ recursion_limiting_context,
277
+ )
278
+
272
279
  from .resolving import ( # noqa
273
280
  Resolvable,
274
281
  ResolvableClassNameError,
@@ -15,6 +15,8 @@ TODO:
15
15
  - and must be transient?
16
16
  - use __transient_dict__ to support common state nuking
17
17
  - use __set_name__ ?
18
+ - on_compute
19
+ - max_recursion?
18
20
  """
19
21
  import dataclasses as dc
20
22
  import functools
@@ -201,6 +203,9 @@ class _CachedFunction(ta.Generic[T], Abstract):
201
203
  except KeyError:
202
204
  pass
203
205
 
206
+ def call_value_fn():
207
+ return self._value_fn(*args, **kwargs)
208
+
204
209
  if self._lock is not None:
205
210
  with self._lock:
206
211
  try:
@@ -208,10 +213,10 @@ class _CachedFunction(ta.Generic[T], Abstract):
208
213
  except KeyError:
209
214
  pass
210
215
 
211
- value = self._value_fn(*args, **kwargs)
216
+ value = call_value_fn()
212
217
 
213
218
  else:
214
- value = self._value_fn(*args, **kwargs)
219
+ value = call_value_fn()
215
220
 
216
221
  self._values[k] = value
217
222
  return value
omlish/lang/casing.py CHANGED
@@ -78,7 +78,7 @@ class LowCamelCase(StringCasing):
78
78
  """fooBarBaz"""
79
79
 
80
80
  _MATCH_PAT: ta.ClassVar[re.Pattern] = re.compile(r'[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*')
81
- _SPLIT_PAT: ta.ClassVar[re.Pattern] = re.compile(r'^[a-z0-9]+')
81
+ _FIRST_PAT: ta.ClassVar[re.Pattern] = re.compile(r'^[a-z0-9]+')
82
82
  _UPPER_PAT: ta.ClassVar[re.Pattern] = re.compile(r'[A-Z][a-z0-9]*')
83
83
 
84
84
  def match(self, s: str) -> bool:
@@ -88,7 +88,7 @@ class LowCamelCase(StringCasing):
88
88
  if not self.match(s):
89
89
  raise ImproperStringCasingError(f'Not valid lowCamelCase: {s!r}')
90
90
  parts: list[str] = []
91
- m0 = self._SPLIT_PAT.match(s)
91
+ m0 = self._FIRST_PAT.match(s)
92
92
  if m0:
93
93
  parts.append(m0.group(0))
94
94
  start = m0.end()
omlish/lang/objects.py CHANGED
@@ -222,3 +222,31 @@ class _AnonObject:
222
222
 
223
223
  def anon_object(**attrs: ta.Any) -> ta.Any:
224
224
  return _AnonObject(**attrs)
225
+
226
+
227
+ ##
228
+
229
+
230
+ class Identity(ta.Generic[T]):
231
+ def __init__(self, obj: T) -> None:
232
+ super().__init__()
233
+
234
+ self._obj = obj
235
+
236
+ def __bool__(self):
237
+ raise TypeError
238
+
239
+ @property
240
+ def obj(self) -> T:
241
+ return self._obj
242
+
243
+ def __repr__(self) -> str:
244
+ return f'{self.__class__.__name__}({self._obj!r})'
245
+
246
+ def __hash__(self) -> int:
247
+ return id(self._obj)
248
+
249
+ def __eq__(self, other):
250
+ if type(other) is not type(self):
251
+ return NotImplemented
252
+ return self._obj is other._obj # noqa
@@ -0,0 +1,109 @@
1
+ import contextlib
2
+ import dataclasses as dc
3
+ import functools
4
+ import threading
5
+ import typing as ta
6
+
7
+
8
+ T = ta.TypeVar('T')
9
+ P = ta.ParamSpec('P')
10
+
11
+
12
+ ##
13
+
14
+
15
+ _LOCK = threading.RLock()
16
+
17
+ _LOCAL: threading.local
18
+
19
+
20
+ def _local() -> threading.local:
21
+ global _LOCAL
22
+
23
+ try:
24
+ return _LOCAL
25
+ except NameError:
26
+ pass
27
+
28
+ with _LOCK:
29
+ try:
30
+ return _LOCAL
31
+ except NameError:
32
+ pass
33
+
34
+ _LOCAL = threading.local()
35
+ return _LOCAL
36
+
37
+
38
+ def _depth_map() -> dict[ta.Any, int]:
39
+ lo = _local()
40
+ try:
41
+ return lo.depth_map
42
+ except AttributeError:
43
+ dm = lo.depth_map = {}
44
+ return dm
45
+
46
+
47
+ ##
48
+
49
+
50
+ @dc.dataclass()
51
+ class LimitedRecursionError(RecursionError):
52
+ key: ta.Any
53
+ depth: int
54
+
55
+
56
+ @contextlib.contextmanager
57
+ def recursion_limiting_context(key: ta.Any, limit: int | None) -> ta.Iterator[int | None]:
58
+ if limit is None:
59
+ yield None
60
+ return
61
+
62
+ dm = _depth_map()
63
+
64
+ try:
65
+ pd: int | None = dm[key]
66
+ except KeyError:
67
+ pd = None
68
+ else:
69
+ if not isinstance(pd, int) and pd > 0: # type: ignore[operator]
70
+ raise RuntimeError
71
+
72
+ if pd is not None and pd >= limit:
73
+ raise LimitedRecursionError(key, pd)
74
+
75
+ nd = (pd or 0) + 1
76
+ dm[key] = nd
77
+
78
+ try:
79
+ yield nd
80
+
81
+ finally:
82
+ if dm.get(key) != nd:
83
+ raise RuntimeError
84
+
85
+ if pd is not None:
86
+ dm[key] = pd
87
+ else:
88
+ del dm[key]
89
+
90
+
91
+ ##
92
+
93
+
94
+ def recursion_limiting(limit: int | None) -> ta.Callable[[ta.Callable[P, T]], ta.Callable[P, T]]:
95
+ def outer(fn):
96
+ if not callable(fn):
97
+ raise TypeError(fn)
98
+
99
+ if limit is None:
100
+ return fn
101
+
102
+ @functools.wraps(fn)
103
+ def inner(*args, **kwargs):
104
+ with recursion_limiting_context(fn, limit):
105
+ return fn(*args, **kwargs)
106
+
107
+ return inner
108
+
109
+ return outer
@@ -82,9 +82,9 @@ from .objects.dataclasses import ( # noqa
82
82
  )
83
83
 
84
84
  from .objects.helpers import ( # noqa
85
- update_field_metadata,
86
85
  update_fields_metadata,
87
86
  update_object_metadata,
87
+ with_field_metadata,
88
88
  )
89
89
 
90
90
  from .objects.marshal import ( # noqa
@@ -11,7 +11,7 @@ T = ta.TypeVar('T')
11
11
  ##
12
12
 
13
13
 
14
- def update_field_metadata(**kwargs: ta.Any) -> dc.field_modifier:
14
+ def with_field_metadata(**kwargs: ta.Any) -> dc.field_modifier:
15
15
  @dc.field_modifier
16
16
  def inner(f: dc.Field) -> dc.Field:
17
17
  return dc.set_field_metadata(f, {
@@ -44,6 +44,10 @@ from .keywords.unknown import ( # noqa
44
44
  )
45
45
 
46
46
  from .keywords.validation import ( # noqa
47
+ AdditionalProperties,
48
+ AnyOf,
49
+ Const,
50
+ Enum,
47
51
  ExclusiveMaximum,
48
52
  ExclusiveMinimum,
49
53
  Items,
@@ -51,6 +55,7 @@ from .keywords.validation import ( # noqa
51
55
  Maximum,
52
56
  MinItems,
53
57
  Minimum,
58
+ OneOf,
54
59
  Properties,
55
60
  Required,
56
61
  Type,
@@ -75,6 +75,16 @@ class Keywords(lang.Final):
75
75
  ##
76
76
 
77
77
 
78
+ @dc.dataclass(frozen=True)
79
+ class AnyKeyword(Keyword, lang.Abstract):
80
+ v: ta.Any
81
+
82
+
83
+ @dc.dataclass(frozen=True)
84
+ class AnyArrayKeyword(Keyword, lang.Abstract):
85
+ vs: ta.Sequence[ta.Any]
86
+
87
+
78
88
  @dc.dataclass(frozen=True)
79
89
  class BooleanKeyword(Keyword, lang.Abstract):
80
90
  b: bool
@@ -91,7 +101,7 @@ class StrKeyword(Keyword, lang.Abstract):
91
101
 
92
102
 
93
103
  @dc.dataclass(frozen=True)
94
- class StrOrStrsKeyword(Keyword, lang.Abstract):
104
+ class StrOrStrArrayKeyword(Keyword, lang.Abstract):
95
105
  ss: str | ta.Sequence[str]
96
106
 
97
107
 
@@ -100,6 +110,16 @@ class KeywordsKeyword(Keyword, lang.Abstract):
100
110
  kw: Keywords
101
111
 
102
112
 
113
+ @dc.dataclass(frozen=True)
114
+ class KeywordsArrayKeyword(Keyword, lang.Abstract):
115
+ kws: ta.Sequence[Keywords]
116
+
117
+
103
118
  @dc.dataclass(frozen=True)
104
119
  class StrToKeywordsKeyword(Keyword, lang.Abstract):
105
120
  m: ta.Mapping[str, Keywords]
121
+
122
+
123
+ @dc.dataclass(frozen=True)
124
+ class BooleanOrKeywordsKeyword(Keyword, lang.Abstract):
125
+ bk: bool | Keywords
@@ -3,14 +3,18 @@ import typing as ta
3
3
  from .... import check
4
4
  from .... import collections as col
5
5
  from .... import lang
6
+ from .base import AnyArrayKeyword
7
+ from .base import AnyKeyword
6
8
  from .base import BooleanKeyword
9
+ from .base import BooleanOrKeywordsKeyword
7
10
  from .base import Keyword
8
11
  from .base import Keywords
12
+ from .base import KeywordsArrayKeyword
9
13
  from .base import KeywordsKeyword
10
14
  from .base import KnownKeyword
11
15
  from .base import NumberKeyword
12
16
  from .base import StrKeyword
13
- from .base import StrOrStrsKeyword
17
+ from .base import StrOrStrArrayKeyword
14
18
  from .base import StrToKeywordsKeyword
15
19
  from .core import CoreKeyword
16
20
  from .format import FormatKeyword
@@ -69,16 +73,28 @@ class KeywordParser:
69
73
  self._allow_unknown = allow_unknown
70
74
 
71
75
  def parse_keyword(self, cls: type[KeywordT], v: ta.Any) -> KeywordT:
72
- if issubclass(cls, BooleanKeyword):
76
+ if issubclass(cls, AnyKeyword):
77
+ return cls(v) # type: ignore
78
+
79
+ elif issubclass(cls, AnyArrayKeyword):
80
+ return cls(tuple(check.isinstance(v, ta.Sequence))) # type: ignore
81
+
82
+ elif issubclass(cls, BooleanKeyword):
73
83
  return cls(check.isinstance(v, bool)) # type: ignore
74
84
 
85
+ elif issubclass(cls, BooleanOrKeywordsKeyword):
86
+ if isinstance(v, bool):
87
+ return cls(v) # type: ignore
88
+ else:
89
+ return cls(self.parse_keywords(v)) # type: ignore
90
+
75
91
  elif issubclass(cls, NumberKeyword):
76
92
  return cls(check.isinstance(v, (int, float))) # type: ignore
77
93
 
78
94
  elif issubclass(cls, StrKeyword):
79
95
  return cls(check.isinstance(v, str)) # type: ignore
80
96
 
81
- elif issubclass(cls, StrOrStrsKeyword):
97
+ elif issubclass(cls, StrOrStrArrayKeyword):
82
98
  ss: str | ta.Sequence[str]
83
99
  if isinstance(v, str):
84
100
  ss = v
@@ -91,6 +107,9 @@ class KeywordParser:
91
107
  elif issubclass(cls, KeywordsKeyword):
92
108
  return cls(self.parse_keywords(v)) # type: ignore
93
109
 
110
+ elif issubclass(cls, KeywordsArrayKeyword):
111
+ return cls(tuple(self.parse_keywords(e) for e in v)) # type: ignore
112
+
94
113
  elif issubclass(cls, StrToKeywordsKeyword):
95
114
  return cls({k: self.parse_keywords(mv) for k, mv in v.items()}) # type: ignore
96
115
 
@@ -1,12 +1,16 @@
1
1
  import typing as ta
2
2
 
3
+ from .base import AnyArrayKeyword
4
+ from .base import AnyKeyword
3
5
  from .base import BooleanKeyword
6
+ from .base import BooleanOrKeywordsKeyword
4
7
  from .base import Keyword
5
8
  from .base import Keywords
9
+ from .base import KeywordsArrayKeyword
6
10
  from .base import KeywordsKeyword
7
11
  from .base import NumberKeyword
8
12
  from .base import StrKeyword
9
- from .base import StrOrStrsKeyword
13
+ from .base import StrOrStrArrayKeyword
10
14
  from .base import StrToKeywordsKeyword
11
15
  from .unknown import UnknownKeyword
12
16
 
@@ -15,16 +19,28 @@ from .unknown import UnknownKeyword
15
19
 
16
20
 
17
21
  def render_keyword(kw: Keyword) -> dict[str, ta.Any]:
18
- if isinstance(kw, BooleanKeyword):
22
+ if isinstance(kw, AnyKeyword):
23
+ return {kw.tag: kw.v}
24
+
25
+ elif isinstance(kw, AnyArrayKeyword):
26
+ return {kw.tag: kw.vs}
27
+
28
+ elif isinstance(kw, BooleanKeyword):
19
29
  return {kw.tag: kw.b}
20
30
 
31
+ elif isinstance(kw, BooleanOrKeywordsKeyword):
32
+ if isinstance(kw.bk, bool):
33
+ return {kw.tag: kw.bk}
34
+ else:
35
+ return {kw.tag: render_keywords(kw.bk)}
36
+
21
37
  elif isinstance(kw, NumberKeyword):
22
38
  return {kw.tag: kw.n}
23
39
 
24
40
  elif isinstance(kw, StrKeyword):
25
41
  return {kw.tag: kw.s}
26
42
 
27
- elif isinstance(kw, StrOrStrsKeyword):
43
+ elif isinstance(kw, StrOrStrArrayKeyword):
28
44
  if isinstance(kw.ss, str):
29
45
  return {kw.tag: kw.ss}
30
46
  else:
@@ -33,6 +49,9 @@ def render_keyword(kw: Keyword) -> dict[str, ta.Any]:
33
49
  elif isinstance(kw, KeywordsKeyword):
34
50
  return {kw.tag: render_keywords(kw.kw)}
35
51
 
52
+ elif isinstance(kw, KeywordsArrayKeyword):
53
+ return {kw.tag: [render_keywords(c) for c in kw.kws]}
54
+
36
55
  elif isinstance(kw, StrToKeywordsKeyword):
37
56
  return {kw.tag: {k: render_keywords(v) for k, v in kw.m.items()}}
38
57
 
@@ -1,9 +1,13 @@
1
1
  from .... import lang
2
+ from .base import AnyArrayKeyword
3
+ from .base import AnyKeyword
2
4
  from .base import BooleanKeyword
5
+ from .base import BooleanOrKeywordsKeyword
6
+ from .base import KeywordsArrayKeyword
3
7
  from .base import KeywordsKeyword
4
8
  from .base import KnownKeyword
5
9
  from .base import NumberKeyword
6
- from .base import StrOrStrsKeyword
10
+ from .base import StrOrStrArrayKeyword
7
11
  from .base import StrToKeywordsKeyword
8
12
 
9
13
 
@@ -17,7 +21,15 @@ class ValidationKeyword(KnownKeyword, lang.Abstract, lang.Sealed):
17
21
  ##
18
22
 
19
23
 
20
- class Type(StrOrStrsKeyword, ValidationKeyword, lang.Final, tag='type'):
24
+ class Type(StrOrStrArrayKeyword, ValidationKeyword, lang.Final, tag='type'):
25
+ pass
26
+
27
+
28
+ class Const(AnyKeyword, ValidationKeyword, lang.Final, tag='const'):
29
+ pass
30
+
31
+
32
+ class Enum(AnyArrayKeyword, ValidationKeyword, lang.Final, tag='enum'):
21
33
  pass
22
34
 
23
35
 
@@ -25,7 +37,7 @@ class Items(KeywordsKeyword, ValidationKeyword, lang.Final, tag='items'):
25
37
  pass
26
38
 
27
39
 
28
- class Required(StrOrStrsKeyword, ValidationKeyword, lang.Final, tag='required'):
40
+ class Required(StrOrStrArrayKeyword, ValidationKeyword, lang.Final, tag='required'):
29
41
  pass
30
42
 
31
43
 
@@ -33,6 +45,10 @@ class Properties(StrToKeywordsKeyword, ValidationKeyword, lang.Final, tag='prope
33
45
  pass
34
46
 
35
47
 
48
+ class AdditionalProperties(BooleanOrKeywordsKeyword, ValidationKeyword, lang.Final, tag='additionalProperties'):
49
+ pass
50
+
51
+
36
52
  ##
37
53
 
38
54
 
@@ -65,3 +81,14 @@ class Minimum(NumberKeyword, ValidationKeyword, lang.Final, tag='minimum'):
65
81
 
66
82
  class ExclusiveMinimum(NumberKeyword, ValidationKeyword, lang.Final, tag='exclusiveMinimum'):
67
83
  pass
84
+
85
+
86
+ #
87
+
88
+
89
+ class AnyOf(KeywordsArrayKeyword, ValidationKeyword, lang.Final, tag='anyOf'):
90
+ pass
91
+
92
+
93
+ class OneOf(KeywordsArrayKeyword, ValidationKeyword, lang.Final, tag='oneOf'):
94
+ pass
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2022 JSON Schema Specification Authors
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4
+ following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7
+ disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
10
+ disclaimer in the documentation and/or other materials provided with the distribution.
11
+
12
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
13
+ derived from this software without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
21
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -35,9 +35,16 @@ from .errors import ( # noqa
35
35
  )
36
36
 
37
37
  from .funcs import ( # noqa
38
+ exec, # noqa
39
+
38
40
  query,
39
41
  query_all,
40
- exec, # noqa
42
+ query_first,
43
+ query_opt_first,
44
+ query_one,
45
+ query_opt_one,
46
+ query_scalar,
47
+ query_maybe_scalar,
41
48
  )
42
49
 
43
50
  from .queries import ( # noqa
omlish/sql/api/base.py CHANGED
@@ -30,7 +30,7 @@ class Querier(ContextCloser, lang.Abstract):
30
30
  ##
31
31
 
32
32
 
33
- class Rows(ContextCloser, lang.Abstract):
33
+ class Rows(ContextCloser, ta.Iterator[Row], lang.Abstract):
34
34
  @property
35
35
  @abc.abstractmethod
36
36
  def columns(self) -> Columns:
omlish/sql/api/funcs.py CHANGED
@@ -1,5 +1,7 @@
1
1
  import typing as ta
2
2
 
3
+ from ... import check
4
+ from ... import lang
3
5
  from .asquery import as_query
4
6
  from .base import Querier
5
7
  from .base import Rows
@@ -10,6 +12,25 @@ from .rows import Row
10
12
  ##
11
13
 
12
14
 
15
+ def exec( # noqa
16
+ querier: Querier,
17
+ obj: ta.Any,
18
+ *args: ta.Any,
19
+ ) -> None:
20
+ q = as_query(
21
+ obj,
22
+ *args,
23
+ mode=QueryMode.EXEC,
24
+ querier=querier,
25
+ )
26
+
27
+ with querier.query(q):
28
+ pass
29
+
30
+
31
+ ##
32
+
33
+
13
34
  def query(
14
35
  querier: Querier,
15
36
  obj: ta.Any,
@@ -37,20 +58,58 @@ def query_all(
37
58
  return list(rows)
38
59
 
39
60
 
40
- ##
61
+ def query_first(
62
+ querier: Querier,
63
+ obj: ta.Any,
64
+ *args: ta.Any,
65
+ ) -> Row:
66
+ with query(querier, obj, *args) as rows:
67
+ return next(rows)
41
68
 
42
69
 
43
- def exec( # noqa
70
+ def query_opt_first(
44
71
  querier: Querier,
45
72
  obj: ta.Any,
46
73
  *args: ta.Any,
47
- ) -> None:
48
- q = as_query(
49
- obj,
50
- *args,
51
- mode=QueryMode.EXEC,
52
- querier=querier,
53
- )
74
+ ) -> Row | None:
75
+ with query(querier, obj, *args) as rows:
76
+ return next(rows, None)
54
77
 
55
- with querier.query(q):
56
- pass
78
+
79
+ def query_one(
80
+ querier: Querier,
81
+ obj: ta.Any,
82
+ *args: ta.Any,
83
+ ) -> Row:
84
+ with query(querier, obj, *args) as rows:
85
+ return check.single(rows)
86
+
87
+
88
+ def query_opt_one(
89
+ querier: Querier,
90
+ obj: ta.Any,
91
+ *args: ta.Any,
92
+ ) -> Row | None:
93
+ with query(querier, obj, *args) as rows:
94
+ return check.opt_single(rows)
95
+
96
+
97
+ def query_scalar(
98
+ querier: Querier,
99
+ obj: ta.Any,
100
+ *args: ta.Any,
101
+ ) -> ta.Any:
102
+ row = query_one(querier, obj, *args)
103
+ return check.single(row.values)
104
+
105
+
106
+ def query_maybe_scalar(
107
+ querier: Querier,
108
+ obj: ta.Any,
109
+ *args: ta.Any,
110
+ ) -> lang.Maybe[ta.Any]:
111
+ row = query_opt_one(querier, obj, *args)
112
+ if row is not None:
113
+ return lang.just(check.single(row.values))
114
+ else:
115
+ return lang.empty()
@@ -1,23 +1,31 @@
1
1
  import collections.abc
2
- import dataclasses as dc
3
2
  import typing as ta
4
3
 
4
+ from .. import dataclasses as dc
5
+ from .. import lang
6
+
5
7
 
6
8
  ##
7
9
 
8
10
 
11
+ def coerce_parts(parts: ta.Sequence[str]) -> tuple[str, ...]:
12
+ if not parts:
13
+ raise ValueError
14
+ if isinstance(parts, str):
15
+ raise TypeError(parts)
16
+ if not isinstance(parts, tuple):
17
+ parts = tuple(parts)
18
+ if not all(parts) and all(isinstance(p, str) for p in parts):
19
+ raise ValueError(parts)
20
+ return parts
21
+
22
+
23
+ #
24
+
25
+
9
26
  @dc.dataclass(frozen=True)
10
- class QualifiedName(ta.Sequence[str]):
11
- parts: ta.Sequence[str]
12
-
13
- def __post_init__(self) -> None:
14
- if not (
15
- self.parts and
16
- not isinstance(self.parts, str) and
17
- all(self.parts) and
18
- all(isinstance(p, str) for p in self.parts)
19
- ):
20
- raise ValueError(self)
27
+ class QualifiedName(ta.Sequence[str], lang.Final):
28
+ parts: ta.Sequence[str] = dc.field() | dc.with_extra_field_params(coerce=coerce_parts)
21
29
 
22
30
  def __repr__(self) -> str:
23
31
  return f'{self.__class__.__name__}([{", ".join(map(repr, self.parts))}])'
@@ -1,5 +1,6 @@
1
1
  from .base import ( # noqa
2
2
  Builder,
3
+ HasQn,
3
4
  Node,
4
5
  NodeComparisonTypeError,
5
6
  Value,
@@ -56,6 +57,8 @@ from .params import ( # noqa
56
57
  from .relations import ( # noqa
57
58
  CanRelation,
58
59
  CanTable,
60
+ Join,
61
+ JoinKind,
59
62
  Relation,
60
63
  RelationBuilder,
61
64
  Table,
@@ -1,8 +1,10 @@
1
+ import abc
1
2
  import types
2
3
  import typing as ta
3
4
 
4
5
  from ... import dataclasses as dc
5
6
  from ... import lang
7
+ from ..qualifiedname import QualifiedName
6
8
 
7
9
 
8
10
  ##
@@ -45,6 +47,10 @@ class Node(
45
47
  def __ne__(self, other) -> ta.NoReturn:
46
48
  raise NodeComparisonTypeError(type(self))
47
49
 
50
+ @ta.final
51
+ def __bool__(self) -> ta.NoReturn:
52
+ raise TypeError
53
+
48
54
  #
49
55
 
50
56
  @dc.dataclass(frozen=True)
@@ -130,3 +136,13 @@ class Node(
130
136
 
131
137
  class Builder(lang.Abstract):
132
138
  pass
139
+
140
+
141
+ ##
142
+
143
+
144
+ class HasQn(lang.Abstract):
145
+ @property
146
+ @abc.abstractmethod
147
+ def qn(self) -> QualifiedName:
148
+ raise NotImplementedError
@@ -1,9 +1,16 @@
1
+ """
2
+ TODO:
3
+ - clamp down on as_ident / CanIdent - no strs allowed
4
+ """
1
5
  import abc
2
6
  import functools
3
7
  import typing as ta
4
8
 
9
+ from ... import cached
5
10
  from ... import lang
11
+ from ..qualifiedname import QualifiedName
6
12
  from .base import Builder
13
+ from .base import HasQn
7
14
  from .base import Node
8
15
 
9
16
 
@@ -17,9 +24,13 @@ class IdentLike(abc.ABC): # noqa
17
24
  ##
18
25
 
19
26
 
20
- class Ident(Node, IdentLike, lang.Final):
27
+ class Ident(Node, IdentLike, HasQn, lang.Final):
21
28
  s: str
22
29
 
30
+ @cached.property
31
+ def qn(self) -> QualifiedName:
32
+ return QualifiedName((self.s,))
33
+
23
34
 
24
35
  ##
25
36
 
@@ -2,9 +2,12 @@ import abc
2
2
  import functools
3
3
  import typing as ta
4
4
 
5
+ from ... import cached
5
6
  from ... import check
6
7
  from ... import dataclasses as dc
7
8
  from ... import lang
9
+ from ..qualifiedname import QualifiedName
10
+ from .base import HasQn
8
11
  from .base import Node
9
12
  from .idents import CanIdent
10
13
  from .idents import Ident
@@ -27,9 +30,13 @@ def _coerce_name_parts(o: ta.Iterable[Ident]) -> ta.Sequence[Ident]:
27
30
  return check.not_empty(tuple(check.isinstance(e, Ident) for e in o))
28
31
 
29
32
 
30
- class Name(Node, NameLike, lang.Final):
33
+ class Name(Node, NameLike, HasQn, lang.Final):
31
34
  ps: ta.Sequence[Ident] = dc.xfield(coerce=_coerce_name_parts)
32
35
 
36
+ @cached.property
37
+ def qn(self) -> QualifiedName:
38
+ return QualifiedName(tuple(p.s for p in self.ps))
39
+
33
40
 
34
41
  ##
35
42
 
@@ -39,11 +39,8 @@ class JoinKind(enum.Enum):
39
39
  DEFAULT = enum.auto()
40
40
  INNER = enum.auto()
41
41
  LEFT = enum.auto()
42
- LEFT_OUTER = enum.auto()
43
42
  RIGHT = enum.auto()
44
- RIGHT_OUTER = enum.auto()
45
43
  FULL = enum.auto()
46
- FULL_OUTER = enum.auto()
47
44
  CROSS = enum.auto()
48
45
  NATURAL = enum.auto()
49
46
 
@@ -95,21 +92,12 @@ class RelationBuilder(MultiBuilder):
95
92
  def left_join(self, l: CanRelation, r: CanRelation, *cs: CanExpr) -> Join:
96
93
  return self.join(JoinKind.LEFT, l, r, *cs)
97
94
 
98
- def left_outer_join(self, l: CanRelation, r: CanRelation, *cs: CanExpr) -> Join:
99
- return self.join(JoinKind.LEFT_OUTER, l, r, *cs)
100
-
101
95
  def right_join(self, l: CanRelation, r: CanRelation, *cs: CanExpr) -> Join:
102
96
  return self.join(JoinKind.RIGHT, l, r, *cs)
103
97
 
104
- def right_outer_join(self, l: CanRelation, r: CanRelation, *cs: CanExpr) -> Join:
105
- return self.join(JoinKind.RIGHT_OUTER, l, r, *cs)
106
-
107
98
  def full_join(self, l: CanRelation, r: CanRelation, *cs: CanExpr) -> Join:
108
99
  return self.join(JoinKind.FULL, l, r, *cs)
109
100
 
110
- def full_outer_join(self, l: CanRelation, r: CanRelation, *cs: CanExpr) -> Join:
111
- return self.join(JoinKind.FULL_OUTER, l, r, *cs)
112
-
113
101
  def cross_join(self, l: CanRelation, r: CanRelation, *cs: CanExpr) -> Join:
114
102
  return self.join(JoinKind.CROSS, l, r, *cs)
115
103
 
@@ -225,11 +225,8 @@ class StdRenderer(Renderer):
225
225
  JoinKind.DEFAULT: 'join',
226
226
  JoinKind.INNER: 'inner join',
227
227
  JoinKind.LEFT: 'left join',
228
- JoinKind.LEFT_OUTER: 'left outer join',
229
228
  JoinKind.RIGHT: 'right join',
230
- JoinKind.RIGHT_OUTER: 'right outer join',
231
229
  JoinKind.FULL: 'full join',
232
- JoinKind.FULL_OUTER: 'full outer join',
233
230
  JoinKind.CROSS: 'cross join',
234
231
  JoinKind.NATURAL: 'natural join',
235
232
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish
3
- Version: 0.0.0.dev303
3
+ Version: 0.0.0.dev305
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=pjGUyLHaoWpPqRP3jz2u1fC1qoRc2lvrEcpU_Ax2tdg,8253
2
- omlish/__about__.py,sha256=ZtA9R7gsXvmQ5vqquGdnyiA7g3hwkTA_jGG19gRDrkI,3478
2
+ omlish/__about__.py,sha256=LvSNEgc-zfbkykExJmNhbtQkXdEXRqPSJufulYnCjfw,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
@@ -202,7 +202,7 @@ omlish/daemons/services.py,sha256=YYp2SMkJ71WgzOcYSXjWHeAyKKxu3j1dfuJvWkl0Dgw,34
202
202
  omlish/daemons/spawning.py,sha256=psR73zOYjMKTqNpx1bMib8uU9wAZz62tw5TaWHrTdyY,5337
203
203
  omlish/daemons/targets.py,sha256=00KmtlknMhQ5PyyVAhWl3rpeTMPym0GxvHHq6mYPZ7c,3051
204
204
  omlish/daemons/waiting.py,sha256=RfgD1L33QQVbD2431dkKZGE4w6DUcGvYeRXXi8puAP4,1676
205
- omlish/dataclasses/__init__.py,sha256=xo5rJYrQ2JE5jcX4fHv5e9bC6RIXFd-K8yurUP20oO8,1834
205
+ omlish/dataclasses/__init__.py,sha256=NQc2jnXOaKia04px011joPMuB98YHOWk_SAUgaqL_T0,1911
206
206
  omlish/dataclasses/debug.py,sha256=giBiv6aXvX0IagwNCW64qBzNjfOFr3-VmgDy_KYlb-k,29
207
207
  omlish/dataclasses/errors.py,sha256=tyv3WR6az66uGGiq9FIuCHvy1Ef-G7zeMY7mMG6hy2Y,2527
208
208
  omlish/dataclasses/inspect.py,sha256=BlpPghVCU3w_YDnONEqqE99YHzJM2q3eoqe39YX25Ko,4596
@@ -210,7 +210,7 @@ omlish/dataclasses/internals.py,sha256=vIGCZnStgD3ef4drYRtVOrxhxmAPa0vJpo4pXcDcQ
210
210
  omlish/dataclasses/reflection.py,sha256=5N4acL27xwSnQJvoks6ts2JseGfwL_P9D2gM9vqtAFM,2243
211
211
  omlish/dataclasses/specs.py,sha256=r9hpIWy83ODiB1n7wTf7JL5fiCeeT8fWbMvxs7c4e3g,6148
212
212
  omlish/dataclasses/utils.py,sha256=gv6za6oJYBr1VaeGd7oXqq9lhBs_sxkpC27XYzJggno,1870
213
- omlish/dataclasses/api/__init__.py,sha256=GFodt19CKrfuBpdv1EUqVUCPAFIlHPnabE2G8QJElBY,426
213
+ omlish/dataclasses/api/__init__.py,sha256=k5iS9QOwf_f4iOfGffYhnqDOcmEIwEUUTp00u11kIPM,455
214
214
  omlish/dataclasses/api/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
215
215
  omlish/dataclasses/api/classes/conversion.py,sha256=x1Ayo5W3t8lVh10KGH_ZBJ_kK7HqcdI2LdJA6mR_WXQ,810
216
216
  omlish/dataclasses/api/classes/decorator.py,sha256=q-m05j6VhJ9_00TmrXBvwmv0_TFPTD8OURi1KbKo9-4,3933
@@ -221,7 +221,7 @@ omlish/dataclasses/api/fields/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
221
221
  omlish/dataclasses/api/fields/building.py,sha256=YVAJZ2c318vK0YWz9VU8_MdjA5qNjqsI3N85K-bD0TI,3314
222
222
  omlish/dataclasses/api/fields/constructor.py,sha256=T2GeczTz9uy2EOTDlU4VJoiXp-I6jvlxnYXChvQqHxk,1533
223
223
  omlish/dataclasses/api/fields/conversion.py,sha256=Pye4KOON78CBgDzwTqNb5fx8wE7uvlKcwVfQ2nhDzrM,5134
224
- omlish/dataclasses/api/fields/metadata.py,sha256=4hGRvG96qPNeipbnHlnbDNWqCANGCuE9BmjKUzTEzRQ,1689
224
+ omlish/dataclasses/api/fields/metadata.py,sha256=9y8-M12pbOuEBC2gciuTeENrTV1AZheoHH2sKfq_Fuo,1933
225
225
  omlish/dataclasses/concerns/__init__.py,sha256=4Goga5wAOAbK6KcoHWC8jDC8M9ywgH7PfmXXxgCYCC8,208
226
226
  omlish/dataclasses/concerns/abc.py,sha256=ok2qE9ltgZXdf34YtLElTr7A-IYErJvVfP1-G2t2Sbc,391
227
227
  omlish/dataclasses/concerns/copy.py,sha256=U8kSHEvVupvXArS3OPB7GK_M7aPO9R3_Uk1nqHqxmJ8,1761
@@ -266,6 +266,7 @@ omlish/dataclasses/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
266
266
  omlish/dataclasses/tools/as_.py,sha256=3mTFXf563Z4ZZnbCzJHv83WizOYBs2mpigJCamxTlpQ,2893
267
267
  omlish/dataclasses/tools/iter.py,sha256=JQUFG4Gn-xthhJ3OEqXLOWkq2KhRMuIqvEow0HcFPZg,540
268
268
  omlish/dataclasses/tools/modifiers.py,sha256=xo6D4yl58HhJrFwV1JeoNrLrgC4MUA6SAXCGoyV-gnM,1212
269
+ omlish/dataclasses/tools/only_.py,sha256=hPpqzr9YW09YmlX_QJNU2aePHYJEIrbGCPwmnvVS_to,849
269
270
  omlish/dataclasses/tools/replace.py,sha256=izM9lPT6AhEtjqn22auqaofa0j69KO7iootF-2Uj4cY,396
270
271
  omlish/dataclasses/tools/repr.py,sha256=KFvF6uv2YYIKq8O3ZNbEAS1tqRQALsJ-SUlBNPd5_GI,190
271
272
  omlish/dataclasses/tools/static.py,sha256=4TOwUmSthWUin0UMtZYw0O8APJYRAD39O3TW7WZShic,8665
@@ -281,7 +282,7 @@ omlish/diag/pycharm.py,sha256=Z2W-Viqw5xq08ZC4z36skpozfYw_qNNhWQx_GYr2D0k,4695
281
282
  omlish/diag/pydevd.py,sha256=UN55ZjkWLCVyHxE2CNRRYamuvSKfzWsn0D5oczRTXO4,7536
282
283
  omlish/diag/threads.py,sha256=1-x02VCDZ407gfbtXm1pWK-ubqhqfePm9PMqkHCVoqk,3642
283
284
  omlish/diag/_pycharm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
284
- omlish/diag/_pycharm/runhack.py,sha256=JFz4GVN4AXndJo38iwnK5X_vH2MS6wyO8YmVZss5YRE,35157
285
+ omlish/diag/_pycharm/runhack.py,sha256=O37sduvkx99uQaEg_XWLDBJlUxaqgd2LkjmdGaUT6e4,35432
285
286
  omlish/diag/replserver/__init__.py,sha256=uLo6V2aQ29v9z3IMELlPDSlG3_2iOT4-_X8VniF-EgE,235
286
287
  omlish/diag/replserver/__main__.py,sha256=LmU41lQ58bm1h4Mx7S8zhE_uEBSC6kPcp9mn5JRpulA,32
287
288
  omlish/diag/replserver/console.py,sha256=XzBDVhYlr8FY6ym4OwoaIHuFOHnGK3dTYlMDIOMUUlA,7410
@@ -366,7 +367,7 @@ omlish/funcs/pipes.py,sha256=E7Sz8Aj8ke_vCs5AMNwg1I36kRdHVGTnzxVQaDyn43U,2490
366
367
  omlish/graphs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
367
368
  omlish/graphs/dags.py,sha256=7ee4GfzHcAVUA5-LMJyT7odwxoooh4cTwz4H8qi8Anc,3045
368
369
  omlish/graphs/domination.py,sha256=N-rCzB3uAug964Tx4YGew1Nuh7GvzGjA598GcOj-Who,7571
369
- omlish/graphs/trees.py,sha256=IA2lMbPec-4UrkYFUjUqqxAKeww6YMV35xj8uQhILYk,8188
370
+ omlish/graphs/trees.py,sha256=dPlIBqWNqPn-kGHBeTkSgmZ_3haq5nEmsdBY1fYnvgE,8197
370
371
  omlish/graphs/dot/__init__.py,sha256=Y1MZRQBZkcYyG1Tn7K2FhL8aYbm4v4tk6f5g9AqEkUw,359
371
372
  omlish/graphs/dot/items.py,sha256=7c-lvB2pp6LKWDI2di-S6Zh4vlUZt97eJeNQzuGilGc,4082
372
373
  omlish/graphs/dot/make.py,sha256=RN30gHfJPiXx5Q51kbDdhVJYf59Fr84Lz9J-mXRt9sI,360
@@ -464,9 +465,9 @@ omlish/iterators/iterators.py,sha256=RxW35yQ5ed8vBQ22IqpDXFx-i5JiLQdp7-pkMZXhJJ8
464
465
  omlish/iterators/recipes.py,sha256=wOwOZg-zWG9Zc3wcAxJFSe2rtavVBYwZOfG09qYEx_4,472
465
466
  omlish/iterators/tools.py,sha256=c4hArZEVV8y9_dFfmRwakusv1cWJLT4MkTkGRjnGN5U,2556
466
467
  omlish/iterators/unique.py,sha256=Nw0pSaNEcHAkve0ugfLPvJcirDOn9ECyC5wIL8JlJKI,1395
467
- omlish/lang/__init__.py,sha256=1m2Kc4CBk_C_B7ydPcGWTdCY00JRMrGtX_RlFvyVTo8,5729
468
+ omlish/lang/__init__.py,sha256=ldzz3bIIkYhUd-Xmzn62NTOJ6av09vWfJSudkQJvz3M,5862
468
469
  omlish/lang/attrs.py,sha256=i7euRF81uNF8QDmUVXSK_BtqLGshaMi4VVdUnMjiMwg,5050
469
- omlish/lang/casing.py,sha256=hMnWMBv-yP4tr8Rc9_An9IgcAsbtVcEb2D_7n8TaFgw,4636
470
+ omlish/lang/casing.py,sha256=cFUlbDdXLhwnWwcYx4qnM5c4zGX7hIRUfcjiZbxUD28,4636
470
471
  omlish/lang/clsdct.py,sha256=HAGIvBSbCefzRjXriwYSBLO7QHKRv2UsE78jixOb-fA,1828
471
472
  omlish/lang/collections.py,sha256=LVm0Sory60IXyFzYhhO8BZAWy_z_pjiA-meXNlSJP7o,2465
472
473
  omlish/lang/comparison.py,sha256=MOwEG0Yny-jBPHO9kQto9FSRyeNpQW24UABsghkrHxY,1356
@@ -479,16 +480,17 @@ omlish/lang/generators.py,sha256=5tbjVAywiZH6oAdj1sJLRMtIkC9y3rAkecLT7Z3m7_g,525
479
480
  omlish/lang/imports.py,sha256=y9W9Y-d_cQ35QCLuSIPoa6vnEqSErFCz8b-34IH128U,10552
480
481
  omlish/lang/iterables.py,sha256=HOjcxOwyI5bBApDLsxRAGGhTTmw7fdZl2kEckxRVl-0,1994
481
482
  omlish/lang/maybes.py,sha256=pb1YrxmpXy-hWKmWR89GxXqZq1MoUD1uuTaTX30peh0,3697
482
- omlish/lang/objects.py,sha256=q1T26cxLkejU5XMl5iEVC9IIhjib0VBpe7JCo2bz2Ws,5411
483
+ omlish/lang/objects.py,sha256=nbxBHfQHVw0OG4qeSTP2GvIiFIcH2tbbitY8y-mYPPo,5959
483
484
  omlish/lang/outcomes.py,sha256=mpFy_VoM-b74L1aCFsjsZVUHx_icZ1AHMOKeVesjOp4,8628
484
485
  omlish/lang/params.py,sha256=QmNVBfJsfxjDG5ilDPgHV7sK4UwRztkSQdLTo0umb8I,6648
486
+ omlish/lang/recursion.py,sha256=1VfSqzKO-8Is3t9LKw0W4jwPfE0aBS70EUlbUxAx4eE,1900
485
487
  omlish/lang/resolving.py,sha256=ei9LDyJexsMMHB9z8diUkNmynWhd_da7h7TqrMYM6lA,1611
486
488
  omlish/lang/resources.py,sha256=WKkAddC3ctMK1bvGw-elGe8ZxAj2IaUTKVSu2nfgHTo,2839
487
489
  omlish/lang/strings.py,sha256=kJmRFd1D36xXcjd9MdB12BCwF_-MVhNr-TpWj7hMi_4,4252
488
490
  omlish/lang/sys.py,sha256=b4qOPiJZQru_mbb04FNfOjYWUxlV2becZOoc-yya_rQ,411
489
491
  omlish/lang/typing.py,sha256=Zdad9Zv0sa-hIaUXPrzPidT7sDVpRcussAI7D-j-I1c,3296
490
492
  omlish/lang/cached/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
491
- omlish/lang/cached/function.py,sha256=Nqr7XDVQdD21TawF-O9YG4jU4RwlBri-E1SusrDrmzo,9560
493
+ omlish/lang/cached/function.py,sha256=0uTtDR1uaxwB2uTB_JTFcCr2c9NxsiWVvCLKVWUUHUw,9641
492
494
  omlish/lang/cached/property.py,sha256=WHYyg4-6EA86TcNMfbXTjVhjEZPc0kngt9hfY3WN5w8,2768
493
495
  omlish/lang/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
494
496
  omlish/lang/classes/abstract.py,sha256=n4rDlDraUKxPF0GtOWEFZ6mEzEDmP7Z8LSI6Jww_thw,3715
@@ -546,7 +548,7 @@ omlish/manifests/base.py,sha256=D1WvJYcBR_njkc0gpALpFCWh1h3agb9qgqphnbbPlm4,935
546
548
  omlish/manifests/load.py,sha256=9mdsS3egmSX9pymO-m-y2Fhs4p6ruOdbsYaKT1-1Hwg,6655
547
549
  omlish/manifests/static.py,sha256=7YwOVh_Ek9_aTrWsWNO8kWS10_j4K7yv3TpXZSHsvDY,501
548
550
  omlish/manifests/types.py,sha256=IOt9dOe0r8okCHSL82ryi3sn4VZ6AT80g_QQR6oZtCE,306
549
- omlish/marshal/__init__.py,sha256=vZX1PPOJZ9kFafZySmvCoxH6YT3qJ0pUj94vVUD7QOc,3368
551
+ omlish/marshal/__init__.py,sha256=6nTREepo0aGQZcJdeOUkJRnSZ9Mx2Q1ok2XoE4dqijM,3366
550
552
  omlish/marshal/base.py,sha256=Q0ZRsz5z0NTI6PeWPc9mdMstJryDDbeIAdpKH9-SDps,11427
551
553
  omlish/marshal/errors.py,sha256=g5XJyTHd__8lfwQ4KwgK-E5WR6MoNTMrqKP2U_QRQQQ,307
552
554
  omlish/marshal/factories.py,sha256=Q926jSVjaQLEmStnHLhm_c_vqEysN1LnDCwAsFLIzXw,2970
@@ -566,7 +568,7 @@ omlish/marshal/composite/optionals.py,sha256=MnecrmrJYjQvYJipIHNCDq78oH09dOjnw5p
566
568
  omlish/marshal/composite/wrapped.py,sha256=jOsn3h1vLIqcoSTB-0KObnsdbV8jSVWJYbf7Kg9AUwg,750
567
569
  omlish/marshal/objects/__init__.py,sha256=F4wej8L_tedC8ETYxAnmKfdPR9TjsqIus9Z3nZofYuc,182
568
570
  omlish/marshal/objects/dataclasses.py,sha256=klXXY1zOKh_FYlw6L5ZNPjpXSk0ntUGxSOrm309WoiY,8953
569
- omlish/marshal/objects/helpers.py,sha256=H3K0J4fsJTaBVi7m6Be066YmlsRmOet48sop3LBr1wU,1104
571
+ omlish/marshal/objects/helpers.py,sha256=hj5I1pILt3QFSVkYJNrSO3wiCaalAopEYWPL17Ip4zs,1102
570
572
  omlish/marshal/objects/marshal.py,sha256=JarKGecMgaFYSUHUj-ZUYVP9HK6u2rjpBb3DWX9Uhh0,2648
571
573
  omlish/marshal/objects/metadata.py,sha256=2_rESnslP5fwtEW1xy9ECJg64uA4cQBuYcGkRfPElts,3342
572
574
  omlish/marshal/objects/namedtuples.py,sha256=8de8L7rwmvr_LLBHHfOl2wHObxc_1yZ8fC_J25yZi7Q,2866
@@ -685,17 +687,18 @@ omlish/specs/jsonrpc/__init__.py,sha256=QQwr-jkgvwr1ZMlNwl5W1TuHcxx8RuzQVFwWwNhp
685
687
  omlish/specs/jsonrpc/errors.py,sha256=-Zgmlo6bV6J8w5f8h9axQgLquIFBHDgIwcpufEH5NsE,707
686
688
  omlish/specs/jsonrpc/marshal.py,sha256=HM736piPGnBZrg8CMLDX-L5fZpegyF6l6JUjzLoSDtk,1852
687
689
  omlish/specs/jsonrpc/types.py,sha256=emEiTPWsjsYy0jCC3It1bUEcu9nHp5y7-j73U1D6vl4,2700
688
- omlish/specs/jsonschema/__init__.py,sha256=qmlpJJlB9TBwvE2qCjRHeecNhEYonpbncXfX0T2L-do,1060
690
+ omlish/specs/jsonschema/__init__.py,sha256=55P7Yg2MprqDyaifac2ExNzK6blTZuDP4ejrUXZWpt8,1129
689
691
  omlish/specs/jsonschema/types.py,sha256=_H7ma99hD3_Xu42BFGHOXRI5p79tY8WBX8QE36k7lbw,472
690
692
  omlish/specs/jsonschema/keywords/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
691
- omlish/specs/jsonschema/keywords/base.py,sha256=jbLzU5_MeLcVrsbPlF2HPt0X0MldyOQmofZxelNTwP4,2416
693
+ omlish/specs/jsonschema/keywords/base.py,sha256=KYxKDxZtJGykLcJPTPvDMPDbbmuUw6RLOGgpCNkFcww,2830
692
694
  omlish/specs/jsonschema/keywords/core.py,sha256=3Pbi8d-eocEAxEdemNa0ldp5lrLWNmH0Tye-5rglUoU,535
693
695
  omlish/specs/jsonschema/keywords/format.py,sha256=9trrxHe38FDx47I8UfvO4PD_IygRlkEyTUJ3XlxDM6Y,244
694
696
  omlish/specs/jsonschema/keywords/metadata.py,sha256=IVWQKWT9xi0R07pXc79ErT7RBu5b6Kzg4pWYIITIRbs,336
695
- omlish/specs/jsonschema/keywords/parse.py,sha256=zWSOhHHzXbj-Yn5owU_w0V5w8yhiW4qqc4c7lMvavk0,3690
696
- omlish/specs/jsonschema/keywords/render.py,sha256=kHlBwNjSvKtca0IXBP5DWDRW-H6MduO6PZjhkBFRImU,1353
697
+ omlish/specs/jsonschema/keywords/parse.py,sha256=8lx17ZoaMF0u0NyAIVM9EmZD6gYLANlw0-zr9DN2HTA,4414
698
+ omlish/specs/jsonschema/keywords/render.py,sha256=wZ9yvfscnW8fYlQTXzmJjMYTKhSTg9hjrtlAEwMRaMQ,1947
697
699
  omlish/specs/jsonschema/keywords/unknown.py,sha256=iYIQlBbLUqISvTvj_Kup4wVDRxcEwmnLTyAAWjlxsck,234
698
- omlish/specs/jsonschema/keywords/validation.py,sha256=RJdGAsl_FtUmw1Bin_mrJ5qhN06rBC3f_L-rCBYdIOc,1342
700
+ omlish/specs/jsonschema/keywords/validation.py,sha256=bKu1bDOms5zxg5Xme1ki_jb7TYDFEOv_JKOzeYbxuYQ,1970
701
+ omlish/specs/jsonschema/schemas/LICENSE,sha256=CU2AUO-rKNc9s9vngzyGxUMgaZ0Aevr8lgN2DFFFxKw,1491
699
702
  omlish/specs/jsonschema/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
700
703
  omlish/specs/jsonschema/schemas/draft202012/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
701
704
  omlish/specs/jsonschema/schemas/draft202012/metaschema.json,sha256=Qdp29a-3zgYtJI92JGOpL3ykfk4PkFsiS6av7vkd7Q8,2452
@@ -726,7 +729,7 @@ omlish/sql/abc.py,sha256=3hrCjB4jnPVMef_YXClCblzYUZ9l9yaxJJdd5_Nu9GM,4043
726
729
  omlish/sql/dbapi.py,sha256=DfMxCyltpMj6hUiLti1omUvJyMGbrkGxcz2ywolOpPA,2979
727
730
  omlish/sql/dbs.py,sha256=65e388987upJpsFX8bNL7uhiYv2sCsmk9Y04V0MXdsI,1873
728
731
  omlish/sql/params.py,sha256=Z4VPet6GhNqD1T_MXSWSHkdy3cpUEhST-OplC4B_fYI,4433
729
- omlish/sql/qualifiedname.py,sha256=c3GQzxh9sNyE_TP32PnlCun_F2bKXvX1ohtt6esTsjo,2269
732
+ omlish/sql/qualifiedname.py,sha256=KdWOozaxsCi5hhsW6KwFM0HD3lTtBtJHDJ5HOx4DsWc,2447
730
733
  omlish/sql/alchemy/__init__.py,sha256=mZiu0F4Y_5qQpl-xz4xnb0feVGZTDp3I4R6k7SVY_7M,760
731
734
  omlish/sql/alchemy/apiadapter.py,sha256=QrkvlzqMl_S2nxOPQ8-g1UaWRSTZNna1-8XouOn8xdY,2696
732
735
  omlish/sql/alchemy/asyncs.py,sha256=MwZwWIaZsUCQLcTA8mdHUPZmR-pXEVSAsvd15FCm3W4,3692
@@ -734,14 +737,14 @@ omlish/sql/alchemy/duckdb.py,sha256=kr7pIhiBLNAuZrcigHDtFg9zHkVcrRW3LfryO9VJ4mk,
734
737
  omlish/sql/alchemy/exprs.py,sha256=gO4Fj4xEY-PuDgV-N8hBMy55glZz7O-4H7v1LWabfZY,323
735
738
  omlish/sql/alchemy/secrets.py,sha256=WEeaec1ejQcE3Yaa7p5BSP9AMGEzy1lwr7QMSRL0VBw,180
736
739
  omlish/sql/alchemy/sqlean.py,sha256=RbkuOuFIfM4fowwKk8-sQ6Dxk-tTUwxS94nY5Kxt52s,403
737
- omlish/sql/api/__init__.py,sha256=Ee-DgQPo_fUqlYL3NBzk6kmj_cXlnZIua5jk85UHWHI,951
740
+ omlish/sql/api/__init__.py,sha256=VJo12MNFn0-GfSrbnve_l6Uo9bOYePKpZNQ9nApaPcg,1066
738
741
  omlish/sql/api/_queries.py,sha256=FqIrtU6JdzQD4S4zv9tdDuzCLHSgJjK8WSfT7Ag4umI,530
739
742
  omlish/sql/api/asquery.py,sha256=8eBoJyyu0kHW3k3yytEcT-B6UAI_slKB0QtSK_WQS7g,1203
740
- omlish/sql/api/base.py,sha256=E_t54OFr0BX604vOfQZGxh_n6EdOBF2Tvnrg25dMKXo,1314
743
+ omlish/sql/api/base.py,sha256=oYFywk1fraINEyCeuZ3TvGaEcI6CdSojYX8iuHaSSow,1332
741
744
  omlish/sql/api/columns.py,sha256=UBol4bfwZ1nhcjv2gE1JhUMzRFeqtiCDo2T9CUGYb64,1943
742
745
  omlish/sql/api/dbapi.py,sha256=bDVqmljmLMdurMhIy3vx-dJvK1KiZ7XBhZ2Zx6W9aBs,2627
743
746
  omlish/sql/api/errors.py,sha256=YtC2gz5DqRTT3uCJniUOufVH1GEnFIc5ElkYLK3BHwM,230
744
- omlish/sql/api/funcs.py,sha256=kOMMzODkJHyKVmGKFS-6GIgil5I6ha1qjqA3IecgCvI,819
747
+ omlish/sql/api/funcs.py,sha256=Raa8kL65l1JprMsJupFw-8EPO6MxxDhm0OPSwecMLOI,2045
745
748
  omlish/sql/api/queries.py,sha256=OVsVqNyXXJQVDPfV3GFE2gwnHyGEenS65rTQRTNGx1Y,735
746
749
  omlish/sql/api/resources.py,sha256=DTVQmKjhaLi27jHWYkF20VhwGvIazZWxyKzF0Wn9-dc,2228
747
750
  omlish/sql/api/rows.py,sha256=Jo3AA_6Wt7tlwLO6-rp0arzYFqZXSxPudGPkW2xCYgQ,1346
@@ -753,19 +756,19 @@ omlish/sql/parsing/_antlr/MinisqlListener.py,sha256=2py_bfDQeEtJaJh9rtpKghbSrQlE
753
756
  omlish/sql/parsing/_antlr/MinisqlParser.py,sha256=y9SFjXdQlWYJa2PbPm30d5SfcYM_8M8ts46IHhENbNc,132457
754
757
  omlish/sql/parsing/_antlr/MinisqlVisitor.py,sha256=NCPorucLLOZ-Q99BtNbDOAfHytQl8wyroR8pI1uVovg,10030
755
758
  omlish/sql/parsing/_antlr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
756
- omlish/sql/queries/__init__.py,sha256=LpBnJOGoT5Dw2C043yo1XXIg0C_eHda-u6DR2ZAKT7c,1530
757
- omlish/sql/queries/base.py,sha256=pQ6p8dSsHwsWa-lmK2h8RY2UJ1mwJRhByOgrCIW3dBs,3229
759
+ omlish/sql/queries/__init__.py,sha256=m4sjtdWN7_9f70rc3vf7CuHgKJvf1QBcfGW_GDHb3hM,1565
760
+ omlish/sql/queries/base.py,sha256=nsavenCsZgzpITSI1zGEAi95K3cRDfAxRgNJKJmYul0,3502
758
761
  omlish/sql/queries/binary.py,sha256=dcEzeEn104AMPuQ7QrJU2O-YCN3SUdxB5S4jaWKOUqY,2253
759
762
  omlish/sql/queries/exprs.py,sha256=dG9L218QtJM1HtDYIMWqHimK03N6AL1WONk3FvVRcXY,1480
760
- omlish/sql/queries/idents.py,sha256=w2RxO6SR3K-u30S259OtnAZaPv7YA70PzY9R7RtuCQ8,891
763
+ omlish/sql/queries/idents.py,sha256=T9aFK8sia09ibkKdAWOUvCVeLqI9JLwXjUDPBAvDf6w,1153
761
764
  omlish/sql/queries/inserts.py,sha256=pSQXDSR1oDUmxjeIYuSbxpAZXvYQ-EJF6a62ON5Un-k,1299
762
765
  omlish/sql/queries/marshal.py,sha256=yVfN5VgYMYDaYTE6FZ8Sea7GrRobn40Hc8kogrfKmjE,3016
763
766
  omlish/sql/queries/multi.py,sha256=7x6x-4jnPzxA6ZasBjnjFuhHFpWt5rGCua3UvuTMIJ0,837
764
- omlish/sql/queries/names.py,sha256=4sDvgRobMEt_6mDeuYVbCqHzLCOwpXUdEyyB4-QjxKo,1996
767
+ omlish/sql/queries/names.py,sha256=ko5ADruhhma9xfm4T7-UEd2uXRgGchfUeiz-xgiZlFE,2207
765
768
  omlish/sql/queries/ops.py,sha256=pDZ_2Jo_Fa8DDbtYkc6-9eehkWsZPI-jh-KFlubcY6Y,134
766
769
  omlish/sql/queries/params.py,sha256=FXgZO6F_fmRQb9CVo4PkRoypm_fSB_AB_JoM3PxRkKM,1206
767
- omlish/sql/queries/relations.py,sha256=7YrEC9IjoVpRGLAFKRSRsHHnTmx-g7hBNXsOgP2HOuI,2998
768
- omlish/sql/queries/rendering.py,sha256=iEQGVP1nOhaZy9FYNwKkOUgqFH0pxfvsCsJpHX8E9Vw,7662
770
+ omlish/sql/queries/relations.py,sha256=_uOFcv_bfS0jfAexagxNMqMftmDWuWWWeRMzEQ3g_Oc,2479
771
+ omlish/sql/queries/rendering.py,sha256=Js0ad4gAUOP9ZmkXQmjYWItwg62nDols5lEz50F1h-c,7516
769
772
  omlish/sql/queries/selects.py,sha256=5CD4qmlv5hysQKmoZuF1bbikA-VcC9opN94Ea1QE3mU,1577
770
773
  omlish/sql/queries/std.py,sha256=7kF76kq_b6Qaki-lYiBlZiZmtpWYqRqAcLU8x-bBZn0,648
771
774
  omlish/sql/queries/stmts.py,sha256=pBqwD7dRlqMu6uh6vR3xaWOEgbZCcFWbOQ9ryYd17T4,441
@@ -844,9 +847,9 @@ omlish/typedvalues/holder.py,sha256=ZTnHiw-K38ciOBLEdwgrltr7Xp8jjEs_0Lp69DH-G-o,
844
847
  omlish/typedvalues/marshal.py,sha256=hWHRLcrGav7lvXJDtb9bNI0ickl4SKPQ6F4BbTpqw3A,4219
845
848
  omlish/typedvalues/reflect.py,sha256=Ih1YgU-srUjsvBn_P7C66f73_VCvcwqE3ffeBnZBgt4,674
846
849
  omlish/typedvalues/values.py,sha256=ym46I-q2QJ_6l4UlERqv3yj87R-kp8nCKMRph0xQ3UA,1307
847
- omlish-0.0.0.dev303.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
848
- omlish-0.0.0.dev303.dist-info/METADATA,sha256=Oe8ylkuWV_TTJtgQkuyG97boKvpRe_Z0O8At2xQ97SU,4416
849
- omlish-0.0.0.dev303.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
850
- omlish-0.0.0.dev303.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
851
- omlish-0.0.0.dev303.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
852
- omlish-0.0.0.dev303.dist-info/RECORD,,
850
+ omlish-0.0.0.dev305.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
851
+ omlish-0.0.0.dev305.dist-info/METADATA,sha256=xv6KDihpZe0TCjJOQAtfs6Yld4oOHnwCwhVuHNkBYR4,4416
852
+ omlish-0.0.0.dev305.dist-info/WHEEL,sha256=wXxTzcEDnjrTwFYjLPcsW_7_XihufBwmpiBeiXNBGEA,91
853
+ omlish-0.0.0.dev305.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
854
+ omlish-0.0.0.dev305.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
855
+ omlish-0.0.0.dev305.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.0.0)
2
+ Generator: setuptools (80.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5