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 +2 -2
- omlish/dataclasses/static.py +16 -3
- omlish/lang/__init__.py +6 -3
- omlish/lang/imports.py +0 -43
- omlish/lite/imports.py +47 -0
- omlish/manifests/types.py +1 -1
- omlish/sql/abc.py +7 -0
- omlish/sql/api/__init__.py +0 -0
- omlish/sql/api/base.py +89 -0
- omlish/sql/api/columns.py +90 -0
- omlish/sql/api/dbapi.py +105 -0
- omlish/sql/api/errors.py +24 -0
- omlish/sql/api/funcs.py +73 -0
- omlish/sql/api/queries.py +63 -0
- omlish/sql/api/rows.py +48 -0
- {omlish-0.0.0.dev238.dist-info → omlish-0.0.0.dev240.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev238.dist-info → omlish-0.0.0.dev240.dist-info}/RECORD +21 -12
- {omlish-0.0.0.dev238.dist-info → omlish-0.0.0.dev240.dist-info}/WHEEL +1 -1
- {omlish-0.0.0.dev238.dist-info → omlish-0.0.0.dev240.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev238.dist-info → omlish-0.0.0.dev240.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev238.dist-info → omlish-0.0.0.dev240.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/dataclasses/static.py
CHANGED
@@ -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
|
148
|
-
#
|
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
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
|
omlish/sql/api/dbapi.py
ADDED
@@ -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
|
omlish/sql/api/errors.py
ADDED
@@ -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
|
omlish/sql/api/funcs.py
ADDED
@@ -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,5 +1,5 @@
|
|
1
1
|
omlish/.manifests.json,sha256=vQTAIvR8OblSq-uP2GUfnbei0RnmAnM5j0T1-OToh9E,8253
|
2
|
-
omlish/__about__.py,sha256
|
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=
|
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=
|
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=
|
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=
|
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=
|
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.
|
718
|
-
omlish-0.0.0.
|
719
|
-
omlish-0.0.0.
|
720
|
-
omlish-0.0.0.
|
721
|
-
omlish-0.0.0.
|
722
|
-
omlish-0.0.0.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|