omlish 0.0.0.dev85__py3-none-any.whl → 0.0.0.dev87__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/formats/json/stream/lex.py +33 -17
- omlish/http/sse.py +3 -0
- omlish/sql/_abc.py +15 -15
- omlish/sql/dbapi.py +143 -0
- omlish/sql/params.py +159 -0
- omlish/sql/queries/marshal.py +4 -0
- omlish/sql/queries/rendering.py +35 -7
- {omlish-0.0.0.dev85.dist-info → omlish-0.0.0.dev87.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev85.dist-info → omlish-0.0.0.dev87.dist-info}/RECORD +14 -12
- {omlish-0.0.0.dev85.dist-info → omlish-0.0.0.dev87.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev85.dist-info → omlish-0.0.0.dev87.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev85.dist-info → omlish-0.0.0.dev87.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev85.dist-info → omlish-0.0.0.dev87.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
@@ -52,14 +52,18 @@ SCALAR_VALUE_TYPES: tuple[type, ...] = tuple(
|
|
52
52
|
##
|
53
53
|
|
54
54
|
|
55
|
+
class Position(ta.NamedTuple):
|
56
|
+
ofs: int
|
57
|
+
line: int
|
58
|
+
col: int
|
59
|
+
|
60
|
+
|
55
61
|
class Token(ta.NamedTuple):
|
56
62
|
kind: TokenKind
|
57
63
|
value: ScalarValue
|
58
64
|
raw: str | None
|
59
65
|
|
60
|
-
|
61
|
-
line: int
|
62
|
-
col: int
|
66
|
+
pos: Position
|
63
67
|
|
64
68
|
def __iter__(self):
|
65
69
|
raise TypeError
|
@@ -94,9 +98,7 @@ CONST_TOKENS: ta.Mapping[str, tuple[TokenKind, str | float | None]] = {
|
|
94
98
|
class JsonLexError(Exception):
|
95
99
|
message: str
|
96
100
|
|
97
|
-
|
98
|
-
line: int
|
99
|
-
col: int
|
101
|
+
pos: Position
|
100
102
|
|
101
103
|
|
102
104
|
class JsonStreamLexer(GenMachine[str, Token]):
|
@@ -108,13 +110,21 @@ class JsonStreamLexer(GenMachine[str, Token]):
|
|
108
110
|
self._include_raw = include_raw
|
109
111
|
|
110
112
|
self._ofs = 0
|
111
|
-
self._line =
|
113
|
+
self._line = 1
|
112
114
|
self._col = 0
|
113
115
|
|
114
116
|
self._buf = io.StringIO()
|
115
117
|
|
116
118
|
super().__init__(self._do_main())
|
117
119
|
|
120
|
+
@property
|
121
|
+
def pos(self) -> Position:
|
122
|
+
return Position(
|
123
|
+
self._ofs,
|
124
|
+
self._line,
|
125
|
+
self._col,
|
126
|
+
)
|
127
|
+
|
118
128
|
def _char_in(self, c: str) -> str:
|
119
129
|
if c and len(c) != 1:
|
120
130
|
raise ValueError(c)
|
@@ -134,14 +144,13 @@ class JsonStreamLexer(GenMachine[str, Token]):
|
|
134
144
|
kind: TokenKind,
|
135
145
|
value: ScalarValue,
|
136
146
|
raw: str,
|
147
|
+
pos: Position,
|
137
148
|
) -> ta.Sequence[Token]:
|
138
149
|
tok = Token(
|
139
150
|
kind,
|
140
151
|
value,
|
141
152
|
raw if self._include_raw else None,
|
142
|
-
|
143
|
-
self._line,
|
144
|
-
self._col,
|
153
|
+
pos,
|
145
154
|
)
|
146
155
|
return (tok,)
|
147
156
|
|
@@ -152,7 +161,7 @@ class JsonStreamLexer(GenMachine[str, Token]):
|
|
152
161
|
return raw
|
153
162
|
|
154
163
|
def _raise(self, msg: str) -> ta.NoReturn:
|
155
|
-
raise JsonLexError(msg, self.
|
164
|
+
raise JsonLexError(msg, self.pos)
|
156
165
|
|
157
166
|
def _do_main(self):
|
158
167
|
while True:
|
@@ -165,7 +174,7 @@ class JsonStreamLexer(GenMachine[str, Token]):
|
|
165
174
|
continue
|
166
175
|
|
167
176
|
if c in CONTROL_TOKENS:
|
168
|
-
yield self._make_tok(CONTROL_TOKENS[c], c, c)
|
177
|
+
yield self._make_tok(CONTROL_TOKENS[c], c, c, self.pos)
|
169
178
|
continue
|
170
179
|
|
171
180
|
if c == '"':
|
@@ -180,8 +189,11 @@ class JsonStreamLexer(GenMachine[str, Token]):
|
|
180
189
|
self._raise(f'Unexpected character: {c}')
|
181
190
|
|
182
191
|
def _do_string(self):
|
192
|
+
check.state(self._buf.tell() == 0)
|
183
193
|
self._buf.write('"')
|
184
194
|
|
195
|
+
pos = self.pos
|
196
|
+
|
185
197
|
last = None
|
186
198
|
while True:
|
187
199
|
try:
|
@@ -199,13 +211,16 @@ class JsonStreamLexer(GenMachine[str, Token]):
|
|
199
211
|
|
200
212
|
raw = self._flip_buf()
|
201
213
|
sv = json.loads(raw)
|
202
|
-
yield self._make_tok('STRING', sv, raw)
|
214
|
+
yield self._make_tok('STRING', sv, raw, pos)
|
203
215
|
|
204
216
|
return self._do_main()
|
205
217
|
|
206
218
|
def _do_number(self, c: str):
|
219
|
+
check.state(self._buf.tell() == 0)
|
207
220
|
self._buf.write(c)
|
208
221
|
|
222
|
+
pos = self.pos
|
223
|
+
|
209
224
|
while True:
|
210
225
|
try:
|
211
226
|
c = self._char_in((yield None)) # noqa
|
@@ -240,7 +255,7 @@ class JsonStreamLexer(GenMachine[str, Token]):
|
|
240
255
|
self._raise(f'Invalid number format: {raw}')
|
241
256
|
|
242
257
|
tk, tv = CONST_TOKENS[raw]
|
243
|
-
yield self._make_tok(tk, tv, raw)
|
258
|
+
yield self._make_tok(tk, tv, raw, pos)
|
244
259
|
|
245
260
|
return self._do_main()
|
246
261
|
|
@@ -250,7 +265,7 @@ class JsonStreamLexer(GenMachine[str, Token]):
|
|
250
265
|
nv = float(raw)
|
251
266
|
else:
|
252
267
|
nv = int(raw)
|
253
|
-
yield self._make_tok('NUMBER', nv, raw)
|
268
|
+
yield self._make_tok('NUMBER', nv, raw, pos)
|
254
269
|
|
255
270
|
#
|
256
271
|
|
@@ -258,7 +273,7 @@ class JsonStreamLexer(GenMachine[str, Token]):
|
|
258
273
|
return None
|
259
274
|
|
260
275
|
if c in CONTROL_TOKENS:
|
261
|
-
yield self._make_tok(CONTROL_TOKENS[c], c, c)
|
276
|
+
yield self._make_tok(CONTROL_TOKENS[c], c, c, pos)
|
262
277
|
|
263
278
|
elif not c.isspace():
|
264
279
|
self._raise(f'Unexpected character after number: {c}')
|
@@ -266,6 +281,7 @@ class JsonStreamLexer(GenMachine[str, Token]):
|
|
266
281
|
return self._do_main()
|
267
282
|
|
268
283
|
def _do_const(self, c: str):
|
284
|
+
pos = self.pos
|
269
285
|
raw = c
|
270
286
|
while True:
|
271
287
|
try:
|
@@ -280,6 +296,6 @@ class JsonStreamLexer(GenMachine[str, Token]):
|
|
280
296
|
self._raise(f'Invalid literal: {raw}')
|
281
297
|
|
282
298
|
tk, tv = CONST_TOKENS[raw]
|
283
|
-
yield self._make_tok(tk, tv, raw)
|
299
|
+
yield self._make_tok(tk, tv, raw, pos)
|
284
300
|
|
285
301
|
return self._do_main()
|
omlish/http/sse.py
CHANGED
omlish/sql/_abc.py
CHANGED
@@ -1,20 +1,20 @@
|
|
1
1
|
import typing as ta
|
2
2
|
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
str,
|
8
|
-
|
9
|
-
int | None,
|
10
|
-
int | None,
|
11
|
-
int | None,
|
12
|
-
int | None,
|
13
|
-
bool | None,
|
4
|
+
DbapiTypeCode: ta.TypeAlias = ta.Any | None
|
5
|
+
|
6
|
+
DbapiColumnDescription: ta.TypeAlias = tuple[
|
7
|
+
str, # name
|
8
|
+
DbapiTypeCode, # type_code
|
9
|
+
int | None, # display_size
|
10
|
+
int | None, # internal_size
|
11
|
+
int | None, # precision
|
12
|
+
int | None, # scale
|
13
|
+
bool | None, # null_ok
|
14
14
|
]
|
15
15
|
|
16
16
|
|
17
|
-
class
|
17
|
+
class DbapiConnection(ta.Protocol):
|
18
18
|
def close(self) -> object: ...
|
19
19
|
|
20
20
|
def commit(self) -> object: ...
|
@@ -22,12 +22,12 @@ class DBAPIConnection(ta.Protocol):
|
|
22
22
|
# optional:
|
23
23
|
# def rollback(self) -> ta.Any: ...
|
24
24
|
|
25
|
-
def cursor(self) -> '
|
25
|
+
def cursor(self) -> 'DbapiCursor': ...
|
26
26
|
|
27
27
|
|
28
|
-
class
|
28
|
+
class DbapiCursor(ta.Protocol):
|
29
29
|
@property
|
30
|
-
def description(self) -> ta.Sequence[
|
30
|
+
def description(self) -> ta.Sequence[DbapiColumnDescription] | None: ...
|
31
31
|
|
32
32
|
@property
|
33
33
|
def rowcount(self) -> int: ...
|
@@ -60,6 +60,6 @@ class DBAPICursor(ta.Protocol):
|
|
60
60
|
|
61
61
|
arraysize: int
|
62
62
|
|
63
|
-
def setinputsizes(self, sizes: ta.Sequence[
|
63
|
+
def setinputsizes(self, sizes: ta.Sequence[DbapiTypeCode | int | None]) -> object: ...
|
64
64
|
|
65
65
|
def setoutputsize(self, size: int, column: int = ...) -> object: ...
|
omlish/sql/dbapi.py
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
"""
|
2
|
+
https://peps.python.org/pep-0249/
|
3
|
+
|
4
|
+
==
|
5
|
+
|
6
|
+
apilevel = '2.0'
|
7
|
+
|
8
|
+
threadsafety:
|
9
|
+
0 - Threads may not share the module.
|
10
|
+
1 - Threads may share the module, but not connections.
|
11
|
+
2 - Threads may share the module and connections.
|
12
|
+
3 - Threads may share the module, connections and cursors.
|
13
|
+
|
14
|
+
paramstyle:
|
15
|
+
qmark - Question mark style, e.g. ...WHERE name=?
|
16
|
+
numeric - Numeric, positional style, e.g. ...WHERE name=:1
|
17
|
+
named - Named style, e.g. ...WHERE name=:name
|
18
|
+
format - ANSI C printf format codes, e.g. ...WHERE name=%s
|
19
|
+
pyformat - Python extended format codes, e.g. ...WHERE name=%(name)s
|
20
|
+
|
21
|
+
Exception
|
22
|
+
| Warning
|
23
|
+
| Error
|
24
|
+
| InterfaceError
|
25
|
+
| DatabaseError
|
26
|
+
| DataError
|
27
|
+
| OperationalError
|
28
|
+
| IntegrityError
|
29
|
+
| InternalError
|
30
|
+
| ProgrammingError
|
31
|
+
| NotSupportedError
|
32
|
+
|
33
|
+
Date(year, month, day)
|
34
|
+
Time(hour, minute, second)
|
35
|
+
Timestamp(year, month, day, hour, minute, second)
|
36
|
+
DateFromTicks(ticks)
|
37
|
+
TimeFromTicks(ticks)
|
38
|
+
TimestampFromTicks(ticks)
|
39
|
+
Binary(string)
|
40
|
+
|
41
|
+
STRING type
|
42
|
+
BINARY type
|
43
|
+
NUMBER type
|
44
|
+
DATETIME type
|
45
|
+
ROWID type
|
46
|
+
"""
|
47
|
+
import dataclasses as dc
|
48
|
+
import enum
|
49
|
+
|
50
|
+
from .. import lang
|
51
|
+
from .params import ParamStyle
|
52
|
+
|
53
|
+
|
54
|
+
class DbapiDialect(enum.Enum):
|
55
|
+
SQLITE = 'sqlite'
|
56
|
+
MYSQL = 'mysql'
|
57
|
+
POSTGRES = 'postgres'
|
58
|
+
SNOWFLAKE = 'snowflake'
|
59
|
+
|
60
|
+
|
61
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
62
|
+
class DbapiDriver:
|
63
|
+
name: str
|
64
|
+
dialect: DbapiDialect
|
65
|
+
param_style: ParamStyle
|
66
|
+
|
67
|
+
package_name: str | None = None
|
68
|
+
module_name: str | None = None
|
69
|
+
|
70
|
+
|
71
|
+
class DbapiDrivers(lang.Namespace):
|
72
|
+
SQLITE3 = DbapiDriver(
|
73
|
+
name='sqlite3',
|
74
|
+
dialect=DbapiDialect.SQLITE,
|
75
|
+
param_style=ParamStyle.QMARK,
|
76
|
+
)
|
77
|
+
|
78
|
+
SQLEAN = DbapiDriver(
|
79
|
+
name='sqlean',
|
80
|
+
dialect=DbapiDialect.SQLITE,
|
81
|
+
param_style=ParamStyle.QMARK,
|
82
|
+
package_name='sqlean.py',
|
83
|
+
)
|
84
|
+
|
85
|
+
DUCKDB = DbapiDriver(
|
86
|
+
name='duckdb',
|
87
|
+
dialect=DbapiDialect.SQLITE,
|
88
|
+
param_style=ParamStyle.QMARK,
|
89
|
+
)
|
90
|
+
|
91
|
+
#
|
92
|
+
|
93
|
+
PYMYSQL = DbapiDriver(
|
94
|
+
name='pymysql',
|
95
|
+
dialect=DbapiDialect.MYSQL,
|
96
|
+
param_style=ParamStyle.PYFORMAT,
|
97
|
+
)
|
98
|
+
|
99
|
+
MYSQL = DbapiDriver(
|
100
|
+
name='mysql',
|
101
|
+
dialect=DbapiDialect.MYSQL,
|
102
|
+
param_style=ParamStyle.PYFORMAT,
|
103
|
+
package_name='mysql-connector-python',
|
104
|
+
module_name='mysql.connector',
|
105
|
+
)
|
106
|
+
|
107
|
+
MYSQLCLIENT = DbapiDriver(
|
108
|
+
name='mysqlclient',
|
109
|
+
dialect=DbapiDialect.MYSQL,
|
110
|
+
param_style=ParamStyle.FORMAT,
|
111
|
+
package_name='mysqlclient',
|
112
|
+
module_name='MySQLdb',
|
113
|
+
)
|
114
|
+
|
115
|
+
#
|
116
|
+
|
117
|
+
PG8000 = DbapiDriver(
|
118
|
+
name='pg8000',
|
119
|
+
dialect=DbapiDialect.POSTGRES,
|
120
|
+
param_style=ParamStyle.FORMAT,
|
121
|
+
)
|
122
|
+
|
123
|
+
PSYCOPG2 = DbapiDriver(
|
124
|
+
name='psycopg2',
|
125
|
+
dialect=DbapiDialect.POSTGRES,
|
126
|
+
param_style=ParamStyle.PYFORMAT,
|
127
|
+
)
|
128
|
+
|
129
|
+
PSYCOPG = DbapiDriver(
|
130
|
+
name='psycopg',
|
131
|
+
dialect=DbapiDialect.POSTGRES,
|
132
|
+
param_style=ParamStyle.PYFORMAT,
|
133
|
+
)
|
134
|
+
|
135
|
+
#
|
136
|
+
|
137
|
+
SNOWFLAKE = DbapiDriver(
|
138
|
+
name='snowflake',
|
139
|
+
dialect=DbapiDialect.SNOWFLAKE,
|
140
|
+
param_style=ParamStyle.PYFORMAT,
|
141
|
+
package_name='snowflake-connector-python',
|
142
|
+
module_name='snowflake.connector',
|
143
|
+
)
|
omlish/sql/params.py
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
import abc
|
2
|
+
import enum
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
from .. import check
|
6
|
+
from .. import lang
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
|
11
|
+
|
12
|
+
ParamKey: ta.TypeAlias = str | int
|
13
|
+
|
14
|
+
|
15
|
+
class ParamsPreparer(lang.Abstract):
|
16
|
+
@abc.abstractmethod
|
17
|
+
def add(self, k: ParamKey) -> str:
|
18
|
+
raise NotImplementedError
|
19
|
+
|
20
|
+
@abc.abstractmethod
|
21
|
+
def prepare(self) -> lang.Args:
|
22
|
+
raise NotImplementedError
|
23
|
+
|
24
|
+
|
25
|
+
class LinearParamsPreparer(ParamsPreparer):
|
26
|
+
def __init__(self, placeholder: str) -> None:
|
27
|
+
super().__init__()
|
28
|
+
self._placeholder = placeholder
|
29
|
+
self._args: list[ParamKey] = []
|
30
|
+
|
31
|
+
def add(self, k: ParamKey) -> str:
|
32
|
+
self._args.append(k)
|
33
|
+
return self._placeholder
|
34
|
+
|
35
|
+
def prepare(self) -> lang.Args:
|
36
|
+
return lang.Args(*self._args)
|
37
|
+
|
38
|
+
|
39
|
+
class NumericParamsPreparer(ParamsPreparer):
|
40
|
+
def __init__(self) -> None:
|
41
|
+
super().__init__()
|
42
|
+
self._args: list[ParamKey] = []
|
43
|
+
self._str_by_key: dict[ParamKey, str] = {}
|
44
|
+
self._pos_by_name: dict[str, int] = {}
|
45
|
+
self._pos_by_id: dict[int, int] = {}
|
46
|
+
|
47
|
+
def add(self, k: ParamKey) -> str:
|
48
|
+
try:
|
49
|
+
return self._str_by_key[k]
|
50
|
+
except KeyError:
|
51
|
+
pass
|
52
|
+
|
53
|
+
pos = len(self._args)
|
54
|
+
if isinstance(k, str):
|
55
|
+
check.not_in(k, self._pos_by_name)
|
56
|
+
self._pos_by_name[k] = pos
|
57
|
+
elif isinstance(k, int):
|
58
|
+
check.not_in(k, self._pos_by_id)
|
59
|
+
self._pos_by_id[k] = pos
|
60
|
+
else:
|
61
|
+
raise TypeError(f'Invalid key type: {type(k)}')
|
62
|
+
|
63
|
+
self._args.append(k)
|
64
|
+
ret = self._str_by_key[k] = f':{pos + 1}'
|
65
|
+
return ret
|
66
|
+
|
67
|
+
def prepare(self) -> lang.Args:
|
68
|
+
return lang.Args(*self._args)
|
69
|
+
|
70
|
+
|
71
|
+
class NamedParamsPreparer(ParamsPreparer):
|
72
|
+
def __init__(
|
73
|
+
self,
|
74
|
+
render: ta.Callable[[str], str],
|
75
|
+
*,
|
76
|
+
anon: ta.Callable[[int], str] | None = None,
|
77
|
+
) -> None:
|
78
|
+
super().__init__()
|
79
|
+
self._render = render
|
80
|
+
self._anon = anon if anon is not None else self.underscore_anon
|
81
|
+
self._kwargs: dict[str, ParamKey] = {}
|
82
|
+
self._str_by_key: dict[ParamKey, str] = {}
|
83
|
+
self._kwargs_by_name: dict[str, str] = {}
|
84
|
+
self._kwargs_by_id: dict[int, str] = {}
|
85
|
+
|
86
|
+
@staticmethod
|
87
|
+
def render_named(kwarg: str) -> str:
|
88
|
+
return f':{kwarg}'
|
89
|
+
|
90
|
+
@staticmethod
|
91
|
+
def render_pyformat(kwarg: str) -> str:
|
92
|
+
return f'%({kwarg})s'
|
93
|
+
|
94
|
+
@staticmethod
|
95
|
+
def underscore_anon(n: int) -> str:
|
96
|
+
return f'_{n}'
|
97
|
+
|
98
|
+
def add(self, k: ParamKey) -> str:
|
99
|
+
try:
|
100
|
+
return self._str_by_key[k]
|
101
|
+
except KeyError:
|
102
|
+
pass
|
103
|
+
|
104
|
+
kwarg: str
|
105
|
+
if isinstance(k, str):
|
106
|
+
check.not_in(k, self._kwargs_by_name)
|
107
|
+
kwarg = k # TODO: collisions
|
108
|
+
self._kwargs_by_name[k] = kwarg
|
109
|
+
elif isinstance(k, int):
|
110
|
+
check.not_in(k, self._kwargs_by_id)
|
111
|
+
kwarg = self._anon(len(self._kwargs_by_id)) # TODO: collisions
|
112
|
+
self._kwargs_by_id[k] = kwarg
|
113
|
+
else:
|
114
|
+
raise TypeError(f'Invalid key type: {type(k)}')
|
115
|
+
|
116
|
+
self._kwargs[kwarg] = k
|
117
|
+
ret = self._str_by_key[k] = self._render(kwarg)
|
118
|
+
return ret
|
119
|
+
|
120
|
+
def prepare(self) -> lang.Args:
|
121
|
+
return lang.Args(**self._kwargs)
|
122
|
+
|
123
|
+
|
124
|
+
##
|
125
|
+
|
126
|
+
|
127
|
+
class ParamStyle(enum.Enum):
|
128
|
+
QMARK = 'qmark' # Question mark style, e.g. ...WHERE name=?
|
129
|
+
FORMAT = 'format' # ANSI C printf format codes, e.g. ...WHERE name=%s
|
130
|
+
|
131
|
+
NUMERIC = 'numeric' # Numeric, positional style, e.g. ...WHERE name=:1
|
132
|
+
|
133
|
+
NAMED = 'named' # Named style, e.g. ...WHERE name=:name
|
134
|
+
PYFORMAT = 'pyformat' # Python extended format codes, e.g. ...WHERE name=%(name)s
|
135
|
+
|
136
|
+
|
137
|
+
def make_params_preparer(style: ParamStyle) -> ParamsPreparer:
|
138
|
+
if style is ParamStyle.QMARK:
|
139
|
+
return LinearParamsPreparer('?')
|
140
|
+
elif style is ParamStyle.FORMAT:
|
141
|
+
return LinearParamsPreparer('%s')
|
142
|
+
elif style is ParamStyle.NUMERIC:
|
143
|
+
return NumericParamsPreparer()
|
144
|
+
elif style is ParamStyle.NAMED:
|
145
|
+
return NamedParamsPreparer(NamedParamsPreparer.render_named)
|
146
|
+
elif style is ParamStyle.PYFORMAT:
|
147
|
+
return NamedParamsPreparer(NamedParamsPreparer.render_pyformat)
|
148
|
+
else:
|
149
|
+
raise ValueError(style)
|
150
|
+
|
151
|
+
|
152
|
+
##
|
153
|
+
|
154
|
+
|
155
|
+
def substitute_params(args: lang.Args, values: ta.Mapping[ParamKey, ta.Any]) -> lang.Args:
|
156
|
+
return lang.Args(
|
157
|
+
*[values[a] for a in args.args],
|
158
|
+
**{k: values[v] for k, v in args.kwargs.items()},
|
159
|
+
)
|
omlish/sql/queries/marshal.py
CHANGED
omlish/sql/queries/rendering.py
CHANGED
@@ -15,17 +15,21 @@ def needs_parens(self, e: Expr) -> bool:
|
|
15
15
|
else:
|
16
16
|
raise TypeError(e)
|
17
17
|
"""
|
18
|
+
import dataclasses as dc
|
18
19
|
import io
|
19
20
|
import typing as ta
|
20
21
|
|
21
22
|
from ... import dispatch
|
22
23
|
from ... import lang
|
24
|
+
from ..params import ParamStyle
|
25
|
+
from ..params import make_params_preparer
|
23
26
|
from .base import Node
|
24
27
|
from .binary import Binary
|
25
28
|
from .binary import BinaryOp
|
26
29
|
from .binary import BinaryOps
|
27
30
|
from .exprs import Literal
|
28
31
|
from .exprs import NameExpr
|
32
|
+
from .exprs import Param
|
29
33
|
from .idents import Ident
|
30
34
|
from .inserts import Insert
|
31
35
|
from .inserts import Values
|
@@ -40,20 +44,40 @@ from .unary import UnaryOp
|
|
40
44
|
from .unary import UnaryOps
|
41
45
|
|
42
46
|
|
47
|
+
@dc.dataclass(frozen=True)
|
48
|
+
class RenderedQuery(lang.Final):
|
49
|
+
s: str
|
50
|
+
args: lang.Args
|
51
|
+
|
52
|
+
|
43
53
|
class Renderer(lang.Abstract):
|
44
|
-
def __init__(
|
54
|
+
def __init__(
|
55
|
+
self,
|
56
|
+
out: ta.TextIO,
|
57
|
+
*,
|
58
|
+
param_style: ParamStyle | None = None,
|
59
|
+
) -> None:
|
45
60
|
super().__init__()
|
46
61
|
self._out = out
|
62
|
+
self._param_style = param_style if param_style is not None else self.default_param_style
|
63
|
+
|
64
|
+
self._params_preparer = make_params_preparer(self._param_style)
|
65
|
+
|
66
|
+
default_param_style: ta.ClassVar[ParamStyle] = ParamStyle.PYFORMAT
|
67
|
+
|
68
|
+
def args(self) -> lang.Args:
|
69
|
+
return self._params_preparer.prepare()
|
47
70
|
|
48
71
|
@dispatch.method
|
49
|
-
def render(self, o: ta.Any) ->
|
72
|
+
def render(self, o: ta.Any) -> str:
|
50
73
|
raise TypeError(o)
|
51
74
|
|
52
75
|
@classmethod
|
53
|
-
def render_str(cls, o: ta.Any, *args: ta.Any, **kwargs: ta.Any) ->
|
76
|
+
def render_str(cls, o: ta.Any, *args: ta.Any, **kwargs: ta.Any) -> RenderedQuery:
|
54
77
|
out = io.StringIO()
|
55
|
-
cls(out, *args, **kwargs)
|
56
|
-
|
78
|
+
pp = cls(out, *args, **kwargs)
|
79
|
+
pp.render(o)
|
80
|
+
return RenderedQuery(out.getvalue(), pp.args())
|
57
81
|
|
58
82
|
|
59
83
|
class StdRenderer(Renderer):
|
@@ -94,6 +118,10 @@ class StdRenderer(Renderer):
|
|
94
118
|
def render_name_expr(self, o: NameExpr) -> None:
|
95
119
|
self.render(o.n)
|
96
120
|
|
121
|
+
@Renderer.render.register
|
122
|
+
def render_param(self, o: Param) -> None:
|
123
|
+
self._out.write(self._params_preparer.add(o.n if o.n is not None else id(o)))
|
124
|
+
|
97
125
|
# idents
|
98
126
|
|
99
127
|
@Renderer.render.register
|
@@ -201,5 +229,5 @@ class StdRenderer(Renderer):
|
|
201
229
|
self._out.write(sfx)
|
202
230
|
|
203
231
|
|
204
|
-
def render(n: Node) ->
|
205
|
-
return StdRenderer.render_str(n)
|
232
|
+
def render(n: Node, **kwargs: ta.Any) -> RenderedQuery:
|
233
|
+
return StdRenderer.render_str(n, **kwargs)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
omlish/.manifests.json,sha256=hTFp9tvE72BxKloIq1s1SS0LRQlIsvMtO69Sbc47rKg,1704
|
2
|
-
omlish/__about__.py,sha256=
|
2
|
+
omlish/__about__.py,sha256=Q99g7Qxo_-Dt6G8U5KsZ2FKuyTgYrxju6TU7S9xtiok,3345
|
3
3
|
omlish/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
omlish/argparse.py,sha256=Dc73G8lyoQBLvXhMYUbzQUh4SJu_OTvKUXjSUxq_ang,7499
|
5
5
|
omlish/c3.py,sha256=4vogWgwPb8TbNS2KkZxpoWbwjj7MuHG2lQG-hdtkvjI,8062
|
@@ -201,7 +201,7 @@ omlish/formats/json/cli/cli.py,sha256=GkdNokklRuDWiXAIai1wijSBFVJlpdlNLTQ3Lyucos
|
|
201
201
|
omlish/formats/json/cli/formats.py,sha256=tqEZKby4HeafGcaUs-m8B-2ZV12dRo40rzL-V99cp00,1714
|
202
202
|
omlish/formats/json/stream/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
203
203
|
omlish/formats/json/stream/build.py,sha256=6deXBxTx1toFAPShz2Jo5OiXxH5Y4ppG8gDPRFoUgjA,2461
|
204
|
-
omlish/formats/json/stream/lex.py,sha256=
|
204
|
+
omlish/formats/json/stream/lex.py,sha256=01K_M9na7-3xv0h3VDBobLXuTS-w4YesNJ6GhOaDtYA,6494
|
205
205
|
omlish/formats/json/stream/parse.py,sha256=WkbW7tvcdrTSluKhw70nPvjsq943eryVcjx8FSz78tM,5198
|
206
206
|
omlish/formats/json/stream/render.py,sha256=B9ZNuBiDJOT25prhIsZu1ICKjxk4eMPwpgQF37NPufs,3212
|
207
207
|
omlish/graphs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -224,7 +224,7 @@ omlish/http/headers.py,sha256=ZMmjrEiYjzo0YTGyK0YsvjdwUazktGqzVVYorY4fd44,5081
|
|
224
224
|
omlish/http/json.py,sha256=9XwAsl4966Mxrv-1ytyCqhcE6lbBJw-0_tFZzGszgHE,7440
|
225
225
|
omlish/http/multipart.py,sha256=R9ycpHsXRcmh0uoc43aYb7BdWL-8kSQHe7J-M81aQZM,2240
|
226
226
|
omlish/http/sessions.py,sha256=VZ_WS5uiQG5y7i3u8oKuQMqf8dPKUOjFm_qk_0OvI8c,4793
|
227
|
-
omlish/http/sse.py,sha256=
|
227
|
+
omlish/http/sse.py,sha256=MDs9RvxQXoQliImcc6qK1ERajEYM7Q1l8xmr-9ceNBc,2315
|
228
228
|
omlish/http/wsgi.py,sha256=czZsVUX-l2YTlMrUjKN49wRoP4rVpS0qpeBn4O5BoMY,948
|
229
229
|
omlish/inject/__init__.py,sha256=JQ7x8l9MjU-kJ5ap7cPVq7SY7zbbCIrjyJAF0UeE5-s,1886
|
230
230
|
omlish/inject/binder.py,sha256=H8AQ4ecmBOtDL8fMgrU1yUJl1gBADLNcdysRbvO8Wso,4167
|
@@ -396,8 +396,10 @@ omlish/specs/openapi/__init__.py,sha256=zilQhafjvteRDF_TUIRgF293dBC6g-TJChmUb6T9
|
|
396
396
|
omlish/specs/openapi/marshal.py,sha256=ob_qUbT9-de86KhPjFccl_NP0liQcXK7Ao-b5Hn0VQA,2133
|
397
397
|
omlish/specs/openapi/openapi.py,sha256=y4h04jeB7ORJSVrcy7apaBdpwLjIyscv1Ub5SderH2c,12682
|
398
398
|
omlish/sql/__init__.py,sha256=TpZLsEJKJzvJ0eMzuV8hwOJJbkxBCV1RZPUMLAVB6io,173
|
399
|
-
omlish/sql/_abc.py,sha256=
|
399
|
+
omlish/sql/_abc.py,sha256=kiOitW_ZhTXrJanJ582wD7o9K69v6HXqDPkxuHEAxrc,1606
|
400
|
+
omlish/sql/dbapi.py,sha256=j7iTExICb5as82j3OaxQ4ZBqmz0mrpaPCYot2k9JxCc,3127
|
400
401
|
omlish/sql/dbs.py,sha256=lpdFmm2vTwLoBiVYGj9yPsVcTEYYNCxlYZZpjfChzkY,1870
|
402
|
+
omlish/sql/params.py,sha256=Z4VPet6GhNqD1T_MXSWSHkdy3cpUEhST-OplC4B_fYI,4433
|
401
403
|
omlish/sql/qualifiedname.py,sha256=rlW3gVmyucJbqwcxj_7BfK4X2HoXrMroZT2H45zPgJQ,2264
|
402
404
|
omlish/sql/alchemy/__init__.py,sha256=1ruDMiviH5fjevn2xVki-QspcE9O3VPy4hxOqpHjI2s,224
|
403
405
|
omlish/sql/alchemy/asyncs.py,sha256=C1bIzz9m2Qbgl4qYGQt_FRQg4BKRcxUqMsVg9RtnTPg,3689
|
@@ -412,12 +414,12 @@ omlish/sql/queries/building.py,sha256=dIQyEqNef2egKAf5qO_aZvXxlAEfxIGWS23qvJ-ozq
|
|
412
414
|
omlish/sql/queries/exprs.py,sha256=kAI5PmvfJ-TqEzzc9H4_womFShT1eA0jWSZH2j2Wg-c,1802
|
413
415
|
omlish/sql/queries/idents.py,sha256=erW6fE9UapuvW1ZeLfGFz7yuW6zzktWIWmOuAHeF8_g,496
|
414
416
|
omlish/sql/queries/inserts.py,sha256=PS3oqGjDgnjUd4sxHVpfKDQnsjG4_6KTseRzWyiJQl0,1229
|
415
|
-
omlish/sql/queries/marshal.py,sha256=
|
417
|
+
omlish/sql/queries/marshal.py,sha256=uLN_gOqiYHBQioZEvg7OREycY0bzKwzBw5mnbg_0cio,2938
|
416
418
|
omlish/sql/queries/multi.py,sha256=7x6x-4jnPzxA6ZasBjnjFuhHFpWt5rGCua3UvuTMIJ0,837
|
417
419
|
omlish/sql/queries/names.py,sha256=YiIyS6ehYMYrdLlUxMawV_Xf2zdi7RwVO9Qsxr_W4_4,772
|
418
420
|
omlish/sql/queries/ops.py,sha256=B7IDfjr2DW5LJhWoNaY1WW90BJhe5ZtmxIELhWXbW-0,129
|
419
421
|
omlish/sql/queries/relations.py,sha256=-d3n-dN17c3TPMZmzSplhWawllUzdq12XPDAuzoeMEQ,838
|
420
|
-
omlish/sql/queries/rendering.py,sha256=
|
422
|
+
omlish/sql/queries/rendering.py,sha256=QegTkbSGlGKen4U4XB2lqjpkt1WGaZUmXmVR2gm8UiU,5944
|
421
423
|
omlish/sql/queries/selects.py,sha256=EcHlyKl5kGSY1d3GVxnImhGCTB6WvwQnlSA9eZanBqU,1364
|
422
424
|
omlish/sql/queries/stmts.py,sha256=pBqwD7dRlqMu6uh6vR3xaWOEgbZCcFWbOQ9ryYd17T4,441
|
423
425
|
omlish/sql/queries/unary.py,sha256=MEYBDZn_H0bexmUrJeONOv5-gIpYowUaXOsEHeQM4ks,1144
|
@@ -454,9 +456,9 @@ omlish/text/delimit.py,sha256=ubPXcXQmtbOVrUsNh5gH1mDq5H-n1y2R4cPL5_DQf68,4928
|
|
454
456
|
omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,3296
|
455
457
|
omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
|
456
458
|
omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
|
457
|
-
omlish-0.0.0.
|
458
|
-
omlish-0.0.0.
|
459
|
-
omlish-0.0.0.
|
460
|
-
omlish-0.0.0.
|
461
|
-
omlish-0.0.0.
|
462
|
-
omlish-0.0.0.
|
459
|
+
omlish-0.0.0.dev87.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
460
|
+
omlish-0.0.0.dev87.dist-info/METADATA,sha256=qGRlUmzNWBRYtFD05J2YIWoeH_zcIAR6kKj3hBa6x9s,3987
|
461
|
+
omlish-0.0.0.dev87.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
|
462
|
+
omlish-0.0.0.dev87.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
|
463
|
+
omlish-0.0.0.dev87.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
|
464
|
+
omlish-0.0.0.dev87.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|