omlish 0.0.0.dev238__py3-none-any.whl → 0.0.0.dev240__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev238'
2
- __revision__ = '0ab60b1d75e5b0b298707ba55797958e528c69b5'
1
+ __version__ = '0.0.0.dev240'
2
+ __revision__ = '971f90a65c71e29b2ef80c9a63301bac68a526e8'
3
3
 
4
4
 
5
5
  #
@@ -1,3 +1,8 @@
1
+ """
2
+ TODO:
3
+ - metaclass, to forbid __subclasscheck__ / __instancecheck__? Clash with dc.Meta, don't want that lock-in, not
4
+ necessary for functionality, just a helpful misuse prevention.
5
+ """
1
6
  import abc
2
7
  import copy
3
8
  import dataclasses as dc
@@ -12,6 +17,14 @@ from .impl.api import dataclass
12
17
 
13
18
 
14
19
  class Static(lang.Abstract):
20
+ """
21
+ Dataclass mixin for dataclasses in which all fields have class-level defaults and are not intended for any
22
+ instance-level overrides - effectively making their subclasses equivalent to instances. For dataclasses which make
23
+ sense as singletons (such as project specs or manifests), for which dynamic instantiation is not the usecase, this
24
+ can enable more natural syntax. Inheritance is permitted - equivalent to a `functools.partial` or composing
25
+ `**kwargs`, as long as there is only ever one unambiguous underlying non-static dataclass to be instantiated.
26
+ """
27
+
15
28
  __static_dataclass_class__: ta.ClassVar[type]
16
29
  __static_dataclass_instance__: ta.ClassVar[ta.Any]
17
30
 
@@ -23,7 +36,7 @@ class Static(lang.Abstract):
23
36
  raise TypeError(f'Static base class {cls} must not implement {a}')
24
37
 
25
38
  if cls.__init__ is not Static.__init__:
26
- # This is necessary to make type-checking work (by allowing it to accept zero). This isn't strictly
39
+ # This is necessary to make type-checking work (by allowing it to accept zero args). This isn't strictly
27
40
  # necessary, but since it's useful to do sometimes it might as well be done everywhere to prevent clashing.
28
41
  raise TypeError(f'Static.__init__ should be first in mro of {cls}')
29
42
 
@@ -144,8 +157,8 @@ class Static(lang.Abstract):
144
157
  )
145
158
 
146
159
  if not is_abstract:
147
- # This is the only time the Statices are ever actually instantiated, and it's only to produce the
148
- # kwargs passed to the underlying dataclass.
160
+ # This is the only time the Statics are ever actually instantiated, and it's only to produce the kwargs
161
+ # passed to the underlying dataclass.
149
162
  tmp_inst = cls()
150
163
  inst_kw = dc.asdict(tmp_inst) # type: ignore[call-overload] # noqa
151
164
  inst = sdc_cls(**inst_kw)
omlish/lang/__init__.py CHANGED
@@ -137,9 +137,6 @@ from .imports import ( # noqa
137
137
  can_import,
138
138
  get_real_module_name,
139
139
  import_all,
140
- import_attr,
141
- import_module,
142
- import_module_attr,
143
140
  lazy_import,
144
141
  proxy_import,
145
142
  proxy_init,
@@ -235,6 +232,12 @@ from .typing import ( # noqa
235
232
 
236
233
  ##
237
234
 
235
+ from ..lite.imports import ( # noqa
236
+ import_attr,
237
+ import_module,
238
+ import_module_attr,
239
+ )
240
+
238
241
  from ..lite.timeouts import ( # noqa
239
242
  DeadlineTimeout,
240
243
  InfiniteTimeout,
omlish/lang/imports.py CHANGED
@@ -87,49 +87,6 @@ def proxy_import(
87
87
  ##
88
88
 
89
89
 
90
- def import_module(dotted_path: str) -> types.ModuleType:
91
- if not dotted_path:
92
- raise ImportError(dotted_path)
93
- mod = __import__(dotted_path, globals(), locals(), [])
94
- for name in dotted_path.split('.')[1:]:
95
- try:
96
- mod = getattr(mod, name)
97
- except AttributeError:
98
- raise AttributeError(f'Module {mod!r} has no attribute {name!r}') from None
99
- return mod
100
-
101
-
102
- def import_module_attr(dotted_path: str) -> ta.Any:
103
- module_name, _, class_name = dotted_path.rpartition('.')
104
- mod = import_module(module_name)
105
- try:
106
- return getattr(mod, class_name)
107
- except AttributeError:
108
- raise AttributeError(f'Module {module_name!r} has no attr {class_name!r}') from None
109
-
110
-
111
- def import_attr(dotted_path: str) -> ta.Any:
112
- parts = dotted_path.split('.')
113
- mod: ta.Any = None
114
- mod_pos = 0
115
- while mod_pos < len(parts):
116
- mod_name = '.'.join(parts[:mod_pos + 1])
117
- try:
118
- mod = importlib.import_module(mod_name)
119
- except ImportError:
120
- break
121
- mod_pos += 1
122
- if mod is None:
123
- raise ImportError(dotted_path)
124
- obj = mod
125
- for att_pos in range(mod_pos, len(parts)):
126
- obj = getattr(obj, parts[att_pos])
127
- return obj
128
-
129
-
130
- ##
131
-
132
-
133
90
  SPECIAL_IMPORTABLE: ta.AbstractSet[str] = frozenset([
134
91
  '__init__.py',
135
92
  '__main__.py',
omlish/lite/imports.py ADDED
@@ -0,0 +1,47 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import types
3
+ import typing as ta
4
+
5
+
6
+ ##
7
+
8
+
9
+ def import_module(dotted_path: str) -> types.ModuleType:
10
+ if not dotted_path:
11
+ raise ImportError(dotted_path)
12
+ mod = __import__(dotted_path, globals(), locals(), [])
13
+ for name in dotted_path.split('.')[1:]:
14
+ try:
15
+ mod = getattr(mod, name)
16
+ except AttributeError:
17
+ raise AttributeError(f'Module {mod!r} has no attribute {name!r}') from None
18
+ return mod
19
+
20
+
21
+ def import_module_attr(dotted_path: str) -> ta.Any:
22
+ module_name, _, class_name = dotted_path.rpartition('.')
23
+ mod = import_module(module_name)
24
+ try:
25
+ return getattr(mod, class_name)
26
+ except AttributeError:
27
+ raise AttributeError(f'Module {module_name!r} has no attr {class_name!r}') from None
28
+
29
+
30
+ def import_attr(dotted_path: str) -> ta.Any:
31
+ importlib = __import__('importlib')
32
+ parts = dotted_path.split('.')
33
+ mod: ta.Any = None
34
+ mod_pos = 0
35
+ while mod_pos < len(parts):
36
+ mod_name = '.'.join(parts[:mod_pos + 1])
37
+ try:
38
+ mod = importlib.import_module(mod_name)
39
+ except ImportError:
40
+ break
41
+ mod_pos += 1
42
+ if mod is None:
43
+ raise ImportError(dotted_path)
44
+ obj = mod
45
+ for att_pos in range(mod_pos, len(parts)):
46
+ obj = getattr(obj, parts[att_pos])
47
+ return obj
omlish/manifests/types.py CHANGED
@@ -7,7 +7,7 @@ import typing as ta
7
7
  @dc.dataclass(frozen=True)
8
8
  class ManifestOrigin:
9
9
  module: str
10
- attr: str
10
+ attr: ta.Optional[str] # None if inline
11
11
 
12
12
  file: str
13
13
  line: int
omlish/sql/abc.py CHANGED
@@ -26,6 +26,13 @@ class DbapiColumnDescription_(ta.NamedTuple): # noqa
26
26
  scale: int | None
27
27
  null_ok: bool | None
28
28
 
29
+ @classmethod
30
+ def of(cls, obj: ta.Any) -> 'DbapiColumnDescription_':
31
+ if isinstance(obj, cls):
32
+ return obj
33
+ else:
34
+ return cls(*obj[:len(cls._fields)])
35
+
29
36
 
30
37
  class DbapiConnection(ta.Protocol):
31
38
  def close(self) -> object: ...
File without changes
omlish/sql/api/base.py ADDED
@@ -0,0 +1,89 @@
1
+ """
2
+ TODO:
3
+ - sync vs async
4
+ """
5
+ import abc
6
+ import typing as ta
7
+
8
+ from ... import lang
9
+ from .columns import Column
10
+ from .columns import Columns
11
+ from .queries import Query
12
+ from .rows import Row
13
+
14
+
15
+ ##
16
+
17
+
18
+ class Closer(lang.Abstract):
19
+ def close(self) -> None:
20
+ pass
21
+
22
+
23
+ class SelfCloser(Closer):
24
+ def __enter__(self) -> ta.Self:
25
+ return self
26
+
27
+ def __exit__(self, exc_type, exc_val, exc_tb):
28
+ self.close()
29
+
30
+
31
+ ##
32
+
33
+
34
+ class Querier(SelfCloser, lang.Abstract):
35
+ @property
36
+ @abc.abstractmethod
37
+ def adapter(self) -> 'Adapter':
38
+ raise NotImplementedError
39
+
40
+ @abc.abstractmethod
41
+ def query(self, query: Query) -> 'Rows': # ta.Raises[QueryError]
42
+ raise NotImplementedError
43
+
44
+
45
+ ##
46
+
47
+
48
+ class Rows(SelfCloser, lang.Abstract):
49
+ @property
50
+ @abc.abstractmethod
51
+ def columns(self) -> Columns:
52
+ raise NotImplementedError
53
+
54
+ @ta.final
55
+ def __iter__(self) -> ta.Self:
56
+ return self
57
+
58
+ @abc.abstractmethod
59
+ def __next__(self) -> Row: # ta.Raises[StopIteration]
60
+ raise NotImplementedError
61
+
62
+
63
+ ##
64
+
65
+
66
+ class Conn(Querier, lang.Abstract): # noqa
67
+ pass
68
+
69
+
70
+ ##
71
+
72
+
73
+ class Db(Querier, lang.Abstract): # noqa
74
+ @abc.abstractmethod
75
+ def connect(self) -> Conn:
76
+ raise NotImplementedError
77
+
78
+ def query(self, query: Query) -> Rows:
79
+ with self.connect() as conn:
80
+ return conn.query(query)
81
+
82
+
83
+ ##
84
+
85
+
86
+ class Adapter(lang.Abstract):
87
+ @abc.abstractmethod
88
+ def scan_type(self, c: Column) -> type:
89
+ raise NotImplementedError
@@ -0,0 +1,90 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from ... import lang
5
+ from .errors import DuplicateColumnNameError
6
+
7
+
8
+ ##
9
+
10
+
11
+ @dc.dataclass(frozen=True)
12
+ class Column(lang.Final):
13
+ name: str
14
+
15
+ _: dc.KW_ONLY
16
+
17
+ db_type: str | None = None
18
+ type: type = object
19
+
20
+
21
+ ##
22
+
23
+
24
+ class Columns(lang.Final):
25
+ def __init__(self, *cs: Column) -> None:
26
+ super().__init__()
27
+
28
+ self._seq = cs
29
+
30
+ by_name: dict[str, Column] = {}
31
+ idxs_by_name: dict[str, int] = {}
32
+ for i, c in enumerate(cs):
33
+ if c.name in by_name:
34
+ raise DuplicateColumnNameError(c.name)
35
+ by_name[c.name] = c
36
+ idxs_by_name[c.name] = i
37
+
38
+ self._by_name = by_name
39
+ self._idxs_by_name = idxs_by_name
40
+
41
+ #
42
+
43
+ _EMPTY: ta.ClassVar['Columns']
44
+
45
+ @classmethod
46
+ def empty(cls) -> 'Columns':
47
+ return cls._EMPTY
48
+
49
+ #
50
+
51
+ def __repr__(self) -> str:
52
+ return f'{self.__class__.__name__}<{", ".join(repr(c.name) for c in self._seq)}>'
53
+
54
+ #
55
+
56
+ def __iter__(self) -> ta.Iterator[Column]:
57
+ return iter(self._seq)
58
+
59
+ def __len__(self) -> int:
60
+ return len(self._seq)
61
+
62
+ def __contains__(self, item: str | int) -> bool:
63
+ if isinstance(item, str):
64
+ return item in self._by_name
65
+ elif isinstance(item, int):
66
+ return 0 <= item < len(self._by_name)
67
+ else:
68
+ raise TypeError(item)
69
+
70
+ def __getitem__(self, item) -> Column:
71
+ if isinstance(item, str):
72
+ return self._by_name[item]
73
+ elif isinstance(item, int):
74
+ return self._seq[item]
75
+ else:
76
+ raise TypeError(item)
77
+
78
+ def get(self, name: str) -> Column | None:
79
+ return self._by_name.get(name)
80
+
81
+ #
82
+
83
+ def index(self, name: str) -> int:
84
+ return self._idxs_by_name[name]
85
+
86
+ def get_index(self, name: str) -> int | None:
87
+ return self._idxs_by_name.get(name)
88
+
89
+
90
+ Columns._EMPTY = Columns() # noqa
@@ -0,0 +1,105 @@
1
+ import typing as ta
2
+
3
+ from ... import check
4
+ from .. import abc as dbapi_abc
5
+ from .base import Adapter
6
+ from .base import Conn
7
+ from .base import Db
8
+ from .base import Rows
9
+ from .columns import Column
10
+ from .columns import Columns
11
+ from .queries import Query
12
+ from .rows import Row
13
+
14
+
15
+ ##
16
+
17
+
18
+ def build_dbapi_columns(desc: ta.Sequence[dbapi_abc.DbapiColumnDescription] | None) -> Columns:
19
+ if desc is None:
20
+ return Columns.empty()
21
+
22
+ cols: list[Column] = []
23
+ for desc_col in desc:
24
+ dbapi_col = dbapi_abc.DbapiColumnDescription_.of(desc_col)
25
+
26
+ cols.append(Column(
27
+ check.non_empty_str(dbapi_col.name),
28
+ ))
29
+
30
+ return Columns(*cols)
31
+
32
+
33
+ class DbapiRows(Rows):
34
+ def __init__(
35
+ self,
36
+ cursor: dbapi_abc.DbapiCursor,
37
+ columns: Columns,
38
+ ) -> None:
39
+ super().__init__()
40
+
41
+ self._cursor = cursor
42
+ self._columns = columns
43
+
44
+ @property
45
+ def columns(self) -> Columns:
46
+ return self._columns
47
+
48
+ def __next__(self) -> Row:
49
+ values = self._cursor.fetchone()
50
+ if values is None:
51
+ raise StopIteration
52
+ return Row(self._columns, values)
53
+
54
+
55
+ class DbapiConn(Conn):
56
+ def __init__(self, conn: dbapi_abc.DbapiConnection) -> None:
57
+ super().__init__()
58
+
59
+ self._conn = conn
60
+
61
+ @property
62
+ def adapter(self) -> Adapter:
63
+ raise NotImplementedError
64
+
65
+ def query(self, query: Query) -> Rows:
66
+ cursor = self._conn.cursor()
67
+ try:
68
+ cursor.execute(query.text)
69
+ columns = build_dbapi_columns(cursor.description)
70
+ return DbapiRows(cursor, columns)
71
+
72
+ except Exception: # noqa
73
+ cursor.close()
74
+ raise
75
+
76
+ def close(self) -> None:
77
+ self._conn.close()
78
+
79
+
80
+ class DbapiDb(Db):
81
+ def __init__(
82
+ self,
83
+ conn_fac: ta.Callable[[], dbapi_abc.DbapiConnection],
84
+ *,
85
+ adapter: ta.Optional['DbapiAdapter'] = None,
86
+ ) -> None:
87
+ super().__init__()
88
+
89
+ self._conn_fac = conn_fac
90
+ if adapter is None:
91
+ adapter = DbapiAdapter()
92
+ self._adapter = adapter
93
+
94
+ def connect(self) -> Conn:
95
+ dbapi_conn = self._conn_fac()
96
+ return DbapiConn(dbapi_conn)
97
+
98
+ @property
99
+ def adapter(self) -> Adapter:
100
+ return self._adapter
101
+
102
+
103
+ class DbapiAdapter(Adapter):
104
+ def scan_type(self, c: Column) -> type:
105
+ raise NotImplementedError
@@ -0,0 +1,24 @@
1
+ class Error(Exception):
2
+ pass
3
+
4
+
5
+ ##
6
+
7
+
8
+ class ColumnError(Error):
9
+ pass
10
+
11
+
12
+ class DuplicateColumnNameError(ColumnError):
13
+ pass
14
+
15
+
16
+ class MismatchedColumnCountError(ColumnError):
17
+ pass
18
+
19
+
20
+ ##
21
+
22
+
23
+ class QueryError(Error):
24
+ pass
@@ -0,0 +1,73 @@
1
+ import typing as ta
2
+
3
+ from .base import Querier
4
+ from .base import Rows
5
+ from .queries import Query
6
+ from .queries import QueryMode
7
+
8
+
9
+ ##
10
+
11
+
12
+ @ta.overload
13
+ def query(
14
+ querier: Querier,
15
+ query: Query, # noqa
16
+ ) -> Rows:
17
+ ...
18
+
19
+
20
+ @ta.overload
21
+ def query(
22
+ querier: Querier,
23
+ text: str,
24
+ *args: ta.Any,
25
+ ) -> Rows:
26
+ ...
27
+
28
+
29
+ def query(
30
+ querier,
31
+ obj,
32
+ *args,
33
+ ):
34
+ if isinstance(obj, Query):
35
+ q = obj
36
+ else:
37
+ q = Query.of(obj, *args, mode=QueryMode.QUERY)
38
+
39
+ return querier.query(q)
40
+
41
+
42
+ ##
43
+
44
+
45
+ @ta.overload
46
+ def exec( # noqa
47
+ querier: Querier,
48
+ query: Query, # noqa
49
+ ) -> None:
50
+ ...
51
+
52
+
53
+ @ta.overload
54
+ def exec( # noqa
55
+ querier: Querier,
56
+ text: str,
57
+ *args: ta.Any,
58
+ ) -> None:
59
+ ...
60
+
61
+
62
+ def exec( # noqa
63
+ querier,
64
+ obj,
65
+ *args,
66
+ ):
67
+ if isinstance(obj, Query):
68
+ q = obj
69
+ else:
70
+ q = Query.of(obj, *args, mode=QueryMode.EXEC)
71
+
72
+ with querier.query(q):
73
+ pass
@@ -0,0 +1,63 @@
1
+ import dataclasses as dc
2
+ import enum
3
+ import typing as ta
4
+
5
+ from ... import check
6
+ from ... import lang
7
+
8
+
9
+ ##
10
+
11
+
12
+ class QueryMode(enum.Enum):
13
+ QUERY = enum.auto()
14
+ EXEC = enum.auto()
15
+
16
+
17
+ @dc.dataclass(frozen=True)
18
+ class Query(lang.Final):
19
+ mode: QueryMode
20
+ text: str
21
+ args: ta.Sequence[ta.Any]
22
+
23
+ #
24
+
25
+ @classmethod
26
+ @ta.overload
27
+ def of(
28
+ cls,
29
+ query: 'Query',
30
+ ) -> 'Query':
31
+ ...
32
+
33
+ @classmethod
34
+ @ta.overload
35
+ def of(
36
+ cls,
37
+ text: str,
38
+ *args: ta.Any,
39
+ mode: str | QueryMode = QueryMode.QUERY,
40
+ ) -> 'Query':
41
+ ...
42
+
43
+ @classmethod # type: ignore[misc]
44
+ def of(cls, obj, *args, **kwargs):
45
+ if isinstance(obj, Query):
46
+ check.arg(not args)
47
+ check.arg(not kwargs)
48
+ return obj
49
+
50
+ elif isinstance(obj, str):
51
+ mode = kwargs.pop('mode', QueryMode.QUERY)
52
+ if isinstance(mode, str):
53
+ mode = QueryMode[mode.upper()]
54
+ check.arg(not kwargs)
55
+
56
+ return cls(
57
+ mode=mode,
58
+ text=obj,
59
+ args=args,
60
+ )
61
+
62
+ else:
63
+ raise TypeError(obj)
omlish/sql/api/rows.py ADDED
@@ -0,0 +1,48 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from ... import lang
5
+ from .columns import Column
6
+ from .columns import Columns
7
+ from .errors import MismatchedColumnCountError
8
+
9
+
10
+ T = ta.TypeVar('T')
11
+
12
+
13
+ ##
14
+
15
+
16
+ @dc.dataclass(frozen=True)
17
+ class Row(lang.Final, ta.Generic[T]):
18
+ columns: Columns
19
+ values: ta.Sequence[T]
20
+
21
+ def __post_init__(self) -> None:
22
+ if len(self.columns) != len(self.values):
23
+ raise MismatchedColumnCountError(self.columns, self.values)
24
+
25
+ #
26
+
27
+ def __iter__(self) -> ta.Iterator[tuple[Column, T]]:
28
+ return iter(zip(self.columns, self.values))
29
+
30
+ def __len__(self) -> int:
31
+ return len(self.values)
32
+
33
+ def __contains__(self, item: str | int) -> bool:
34
+ raise TypeError('Row.__contains__ is ambiguous - use .columns.__contains__ or .values.__contains__')
35
+
36
+ def __getitem__(self, item) -> T:
37
+ if isinstance(item, str):
38
+ return self.values[self.columns.index(item)]
39
+ elif isinstance(item, int):
40
+ return self.values[item]
41
+ else:
42
+ raise TypeError(item)
43
+
44
+ def get(self, name: str) -> T | None:
45
+ if (idx := self.columns.get_index(name)) is not None:
46
+ return self.values[idx]
47
+ else:
48
+ return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: omlish
3
- Version: 0.0.0.dev238
3
+ Version: 0.0.0.dev240
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=vQTAIvR8OblSq-uP2GUfnbei0RnmAnM5j0T1-OToh9E,8253
2
- omlish/__about__.py,sha256=-JHhiSxa1m9XW-39IA3EWqH7y7ZB6GG03v7p93wDeA0,3380
2
+ omlish/__about__.py,sha256=yWkALlaBMUZQGBFqzomouYR0oDIe1-AI6oxA2oWnPx4,3380
3
3
  omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
4
4
  omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
5
5
  omlish/cached.py,sha256=UI-XTFBwA6YXWJJJeBn-WkwBkfzDjLBBaZf4nIJA9y0,510
@@ -187,7 +187,7 @@ omlish/daemons/spawning.py,sha256=cx00xeqSrfhlFbjCtKqaBHvMuHwB9hdjuKNHzAAo_dw,40
187
187
  omlish/daemons/targets.py,sha256=00KmtlknMhQ5PyyVAhWl3rpeTMPym0GxvHHq6mYPZ7c,3051
188
188
  omlish/daemons/waiting.py,sha256=RfgD1L33QQVbD2431dkKZGE4w6DUcGvYeRXXi8puAP4,1676
189
189
  omlish/dataclasses/__init__.py,sha256=b7EZCIfHnEHCHWwgD3YXxkdsU-uYd9iD4hM36RgpI1g,1598
190
- omlish/dataclasses/static.py,sha256=35tPuneXUXZztFTzjNkW6DnQbZgKJUf68GmjzXV-WkQ,6903
190
+ omlish/dataclasses/static.py,sha256=6pZG2iTR9NN8pKm-5ukDABnaVlTKFOzMwkg-rbxURoo,7691
191
191
  omlish/dataclasses/utils.py,sha256=QWsHxVisS-MUCqh89JQsRdCLgdBVeC6EYK6jRwO9akU,3631
192
192
  omlish/dataclasses/impl/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936
193
193
  omlish/dataclasses/impl/__init__.py,sha256=zqGBC5gSbjJxaqG_zS1LL1PX-zAfhIua8UqOE4IwO2k,789
@@ -397,7 +397,7 @@ omlish/iterators/iterators.py,sha256=ghI4dO6WPyyFOLTIIMaHQ_IOy2xXaFpGPqveZ5YGIBU
397
397
  omlish/iterators/recipes.py,sha256=53mkexitMhkwXQZbL6DrhpT0WePQ_56uXd5Jaw3DfzI,467
398
398
  omlish/iterators/tools.py,sha256=Pi4ybXytUXVZ3xwK89xpPImQfYYId9p1vIFQvVqVLqA,2551
399
399
  omlish/iterators/unique.py,sha256=0jAX3kwzVfRNhe0Tmh7kVP_Q2WBIn8POo_O-rgFV0rQ,1390
400
- omlish/lang/__init__.py,sha256=rS9JPXiHchs2_9QG7JCx9pbsYpgpe3ANUumDBb8hSFA,4105
400
+ omlish/lang/__init__.py,sha256=2jxO7QWT0uOvdYdmBFgnqmVh-6I7DofsapgUl3wu1fY,4145
401
401
  omlish/lang/cached.py,sha256=tQaqMu1LID0q4NSTk5vPXsgxIBWSFAmjs5AhQoEHoCQ,7833
402
402
  omlish/lang/clsdct.py,sha256=sJYadm-fwzti-gsi98knR5qQUxriBmOqQE_qz3RopNk,1743
403
403
  omlish/lang/cmp.py,sha256=5vbzWWbqdzDmNKAGL19z6ZfUKe5Ci49e-Oegf9f4BsE,1346
@@ -407,7 +407,7 @@ omlish/lang/descriptors.py,sha256=mZ2h9zJ__MMpw8hByjRbAiONcwfVb6GD0btNnVi8C5w,65
407
407
  omlish/lang/exceptions.py,sha256=qJBo3NU1mOWWm-NhQUHCY5feYXR3arZVyEHinLsmRH4,47
408
408
  omlish/lang/functions.py,sha256=0ql9EXA_gEEhvUVzMJCjVhEnVtHecsLKmfmAXuQqeGY,4388
409
409
  omlish/lang/generators.py,sha256=5LX17j-Ej3QXhwBgZvRTm_dq3n9veC4IOUcVmvSu2vU,5243
410
- omlish/lang/imports.py,sha256=nKPTHVW5gUpT_Ok8QsPyj7b-ToE7nWN2ev9MsI4pRCA,10465
410
+ omlish/lang/imports.py,sha256=Gdl6xCF89xiMOE1yDmdvKWamLq8HX-XPianO58Jdpmw,9218
411
411
  omlish/lang/iterables.py,sha256=HOjcxOwyI5bBApDLsxRAGGhTTmw7fdZl2kEckxRVl-0,1994
412
412
  omlish/lang/maybes.py,sha256=dAgrUoAhCgyrHRqa73CkaGnpXwGc-o9n-NIThrNXnbU,3416
413
413
  omlish/lang/objects.py,sha256=65XsD7UtblRdNe2ID1-brn_QvRkJhBIk5nyZWcQNeqU,4574
@@ -435,6 +435,7 @@ omlish/lite/check.py,sha256=OLwtE2x6nlbGx4vS3Rda7zMHpgqzDSLJminTAX2lqLA,13529
435
435
  omlish/lite/configs.py,sha256=Ev_19sbII67pTWzInYjYqa9VyTiZBvyjhZqyG8TtufE,908
436
436
  omlish/lite/contextmanagers.py,sha256=ciaMl0D3QDHToM7M28-kwZ-Q48LtwgCxiud3nekgutA,2863
437
437
  omlish/lite/dataclasses.py,sha256=t1G5-xOuvE6o6w9RyqHzLT9wHD0HkqBh5P8HUZWxGzs,1912
438
+ omlish/lite/imports.py,sha256=o9WWrNrWg0hKeMvaj91giaovED_9VFanN2MyEHBGekY,1346
438
439
  omlish/lite/inject.py,sha256=qBUftFeXMiRgANYbNS2e7TePMYyFAcuLgsJiLyMTW5o,28769
439
440
  omlish/lite/json.py,sha256=7-02Ny4fq-6YAu5ynvqoijhuYXWpLmfCI19GUeZnb1c,740
440
441
  omlish/lite/logs.py,sha256=CWFG0NKGhqNeEgryF5atN2gkPYbUdTINEw_s1phbINM,51
@@ -469,7 +470,7 @@ omlish/manifests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
469
470
  omlish/manifests/base.py,sha256=D1WvJYcBR_njkc0gpALpFCWh1h3agb9qgqphnbbPlm4,935
470
471
  omlish/manifests/load.py,sha256=9mdsS3egmSX9pymO-m-y2Fhs4p6ruOdbsYaKT1-1Hwg,6655
471
472
  omlish/manifests/static.py,sha256=7YwOVh_Ek9_aTrWsWNO8kWS10_j4K7yv3TpXZSHsvDY,501
472
- omlish/manifests/types.py,sha256=mxL6N44mu06995YF8qzmg6V94EmfRiryzIUQcXVSCbI,275
473
+ omlish/manifests/types.py,sha256=IOt9dOe0r8okCHSL82ryi3sn4VZ6AT80g_QQR6oZtCE,306
473
474
  omlish/marshal/__init__.py,sha256=00D3S6qwUld1TUWd67hVHuNcrj3c_FAFSkCVXgGWT-s,2607
474
475
  omlish/marshal/base.py,sha256=tJ4iNuD7cW2GpGMznOhkAf2hugqp2pF2em0FaQcekrk,6740
475
476
  omlish/marshal/exceptions.py,sha256=jwQWn4LcPnadT2KRI_1JJCOSkwWh0yHnYK9BmSkNN4U,302
@@ -628,7 +629,7 @@ omlish/specs/openapi/__init__.py,sha256=zilQhafjvteRDF_TUIRgF293dBC6g-TJChmUb6T9
628
629
  omlish/specs/openapi/marshal.py,sha256=Z-E2Knm04C81N8AA8cibCVSl2ImhSpHZVc7yAhmPx88,2135
629
630
  omlish/specs/openapi/openapi.py,sha256=y4h04jeB7ORJSVrcy7apaBdpwLjIyscv1Ub5SderH2c,12682
630
631
  omlish/sql/__init__.py,sha256=TpZLsEJKJzvJ0eMzuV8hwOJJbkxBCV1RZPUMLAVB6io,173
631
- omlish/sql/abc.py,sha256=NfbcIlEvdEu6vn0TolhfYb2SO5uzUmY0kARCbg6uVAU,1879
632
+ omlish/sql/abc.py,sha256=K3AmEPVxzvQrrc1AXdlbM9-9LERGq6lko9slx88kB90,2074
632
633
  omlish/sql/dbapi.py,sha256=5ghJH-HexsmDlYdWlhf00nCGQX2IC98_gxIxMkucOas,3195
633
634
  omlish/sql/dbs.py,sha256=65e388987upJpsFX8bNL7uhiYv2sCsmk9Y04V0MXdsI,1873
634
635
  omlish/sql/params.py,sha256=Z4VPet6GhNqD1T_MXSWSHkdy3cpUEhST-OplC4B_fYI,4433
@@ -639,6 +640,14 @@ omlish/sql/alchemy/duckdb.py,sha256=kr7pIhiBLNAuZrcigHDtFg9zHkVcrRW3LfryO9VJ4mk,
639
640
  omlish/sql/alchemy/exprs.py,sha256=gO4Fj4xEY-PuDgV-N8hBMy55glZz7O-4H7v1LWabfZY,323
640
641
  omlish/sql/alchemy/secrets.py,sha256=WEeaec1ejQcE3Yaa7p5BSP9AMGEzy1lwr7QMSRL0VBw,180
641
642
  omlish/sql/alchemy/sqlean.py,sha256=RbkuOuFIfM4fowwKk8-sQ6Dxk-tTUwxS94nY5Kxt52s,403
643
+ omlish/sql/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
644
+ omlish/sql/api/base.py,sha256=iPvLwI_noMzazgPlYOZb9xzpXfyb_OfX2WKD9nzWpQc,1506
645
+ omlish/sql/api/columns.py,sha256=UBol4bfwZ1nhcjv2gE1JhUMzRFeqtiCDo2T9CUGYb64,1943
646
+ omlish/sql/api/dbapi.py,sha256=H3bFoWI-ox81APX3xBbjylNWyMUXh78ICMQjRrlDNxw,2426
647
+ omlish/sql/api/errors.py,sha256=YtC2gz5DqRTT3uCJniUOufVH1GEnFIc5ElkYLK3BHwM,230
648
+ omlish/sql/api/funcs.py,sha256=-H6V-o9JPSHFXsxdHtutB4mP2LwJfCzlLbRrPHunmB4,990
649
+ omlish/sql/api/queries.py,sha256=IgB8_sDe40-mKE-ByTmZ4GVOCdLLJDzJGJCevMd8R5s,1207
650
+ omlish/sql/api/rows.py,sha256=MEK9LNYEe8vLEJXQJD63MpnSOiE22cawJL-dUWQD6sU,1246
642
651
  omlish/sql/queries/__init__.py,sha256=N8oQFKY99g_MQhrPmvlBAkMeGIRURE9UxMO244mytzY,1332
643
652
  omlish/sql/queries/base.py,sha256=_8O3MbH_OEjBnhp2oIJUZ3ClaQ8l4Sj9BdPdsP0Ie-g,224
644
653
  omlish/sql/queries/binary.py,sha256=dcEzeEn104AMPuQ7QrJU2O-YCN3SUdxB5S4jaWKOUqY,2253
@@ -714,9 +723,9 @@ omlish/text/indent.py,sha256=YjtJEBYWuk8--b9JU_T6q4yxV85_TR7VEVr5ViRCFwk,1336
714
723
  omlish/text/minja.py,sha256=jZC-fp3Xuhx48ppqsf2Sf1pHbC0t8XBB7UpUUoOk2Qw,5751
715
724
  omlish/text/parts.py,sha256=JkNZpyR2tv2CNcTaWJJhpQ9E4F0yPR8P_YfDbZfMtwQ,6182
716
725
  omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
717
- omlish-0.0.0.dev238.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
718
- omlish-0.0.0.dev238.dist-info/METADATA,sha256=Fzwpe0Ssmu0yFpCL5MUoWK0b3rF72OFhaG7rCK_duxU,4176
719
- omlish-0.0.0.dev238.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
720
- omlish-0.0.0.dev238.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
721
- omlish-0.0.0.dev238.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
722
- omlish-0.0.0.dev238.dist-info/RECORD,,
726
+ omlish-0.0.0.dev240.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
727
+ omlish-0.0.0.dev240.dist-info/METADATA,sha256=Ok-6z9aF1wMJzIfXr6vec_L1ztsSkFtHnwfMZPOFi54,4176
728
+ omlish-0.0.0.dev240.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
729
+ omlish-0.0.0.dev240.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
730
+ omlish-0.0.0.dev240.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
731
+ omlish-0.0.0.dev240.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (75.8.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5