asyncpg-typed 0.1.0__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.
- asyncpg_typed/__init__.py +827 -0
- asyncpg_typed/py.typed +0 -0
- asyncpg_typed-0.1.0.dist-info/METADATA +187 -0
- asyncpg_typed-0.1.0.dist-info/RECORD +8 -0
- asyncpg_typed-0.1.0.dist-info/WHEEL +5 -0
- asyncpg_typed-0.1.0.dist-info/licenses/LICENSE +21 -0
- asyncpg_typed-0.1.0.dist-info/top_level.txt +1 -0
- asyncpg_typed-0.1.0.dist-info/zip-safe +1 -0
|
@@ -0,0 +1,827 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type-safe queries for asyncpg.
|
|
3
|
+
|
|
4
|
+
:see: https://github.com/hunyadi/asyncpg_typed
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
__author__ = "Levente Hunyadi"
|
|
9
|
+
__copyright__ = "Copyright 2025, Levente Hunyadi"
|
|
10
|
+
__license__ = "MIT"
|
|
11
|
+
__maintainer__ = "Levente Hunyadi"
|
|
12
|
+
__status__ = "Production"
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
import typing
|
|
16
|
+
from abc import abstractmethod
|
|
17
|
+
from collections.abc import Iterable, Sequence
|
|
18
|
+
from datetime import date, datetime, time
|
|
19
|
+
from decimal import Decimal
|
|
20
|
+
from functools import reduce
|
|
21
|
+
from io import StringIO
|
|
22
|
+
from types import UnionType
|
|
23
|
+
from typing import Any, Generic, TypeAlias, TypeVar, Union, get_args, get_origin, overload
|
|
24
|
+
from uuid import UUID
|
|
25
|
+
|
|
26
|
+
import asyncpg
|
|
27
|
+
from asyncpg.prepared_stmt import PreparedStatement
|
|
28
|
+
|
|
29
|
+
if sys.version_info < (3, 11):
|
|
30
|
+
from typing_extensions import TypeVarTuple, Unpack
|
|
31
|
+
else:
|
|
32
|
+
from typing import TypeVarTuple, Unpack
|
|
33
|
+
|
|
34
|
+
# list of supported data types
|
|
35
|
+
DATA_TYPES: list[type[Any]] = [bool, int, float, Decimal, date, time, datetime, str, bytes, UUID]
|
|
36
|
+
|
|
37
|
+
# maximum number of inbound query parameters
|
|
38
|
+
NUM_ARGS = 8
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def is_union_type(tp: Any) -> bool:
|
|
42
|
+
"""
|
|
43
|
+
Returns `True` if `tp` is a union type such as `A | B` or `Union[A, B]`.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
origin = get_origin(tp)
|
|
47
|
+
return origin is Union or origin is UnionType
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def is_optional_type(tp: Any) -> bool:
|
|
51
|
+
"""
|
|
52
|
+
Returns `True` if `tp` is an optional type such as `T | None`, `Optional[T]` or `Union[T, None]`.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
return is_union_type(tp) and any(a is type(None) for a in get_args(tp))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def is_standard_type(tp: Any) -> bool:
|
|
59
|
+
"""
|
|
60
|
+
Returns `True` if the type represents a built-in or a well-known standard type.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
return tp.__module__ == "builtins" or tp.__module__ == UnionType.__module__
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def make_union_type(tpl: list[Any]) -> UnionType:
|
|
67
|
+
"""
|
|
68
|
+
Creates a `UnionType` (a.k.a. `A | B | C`) dynamically at run time.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
if len(tpl) < 2:
|
|
72
|
+
raise ValueError("expected: at least two types to make a `UnionType`")
|
|
73
|
+
|
|
74
|
+
return reduce(lambda a, b: a | b, tpl)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_required_type(tp: Any) -> Any:
|
|
78
|
+
"""
|
|
79
|
+
Removes `None` from an optional type (i.e. a union type that has `None` as a member).
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
if not is_optional_type(tp):
|
|
83
|
+
return tp
|
|
84
|
+
|
|
85
|
+
tpl = [a for a in get_args(tp) if a is not type(None)]
|
|
86
|
+
if len(tpl) > 1:
|
|
87
|
+
return make_union_type(tpl)
|
|
88
|
+
elif len(tpl) > 0:
|
|
89
|
+
return tpl[0]
|
|
90
|
+
else:
|
|
91
|
+
return type(None)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# maps PostgreSQL internal type names to Python types
|
|
95
|
+
_name_to_type: dict[str, Any] = {
|
|
96
|
+
"bool": bool,
|
|
97
|
+
"int2": int,
|
|
98
|
+
"int4": int,
|
|
99
|
+
"int8": int,
|
|
100
|
+
"float4": float,
|
|
101
|
+
"float8": float,
|
|
102
|
+
"numeric": Decimal,
|
|
103
|
+
"date": date,
|
|
104
|
+
"time": time,
|
|
105
|
+
"timetz": time,
|
|
106
|
+
"timestamp": datetime,
|
|
107
|
+
"timestamptz": datetime,
|
|
108
|
+
"bpchar": str,
|
|
109
|
+
"varchar": str,
|
|
110
|
+
"text": str,
|
|
111
|
+
"bytea": bytes,
|
|
112
|
+
"json": str,
|
|
113
|
+
"jsonb": str,
|
|
114
|
+
"uuid": UUID,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def check_data_type(name: str, data_type: type[Any]) -> bool:
|
|
119
|
+
"""
|
|
120
|
+
Verifies if the Python target type can represent the PostgreSQL source type.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
expected_type = _name_to_type.get(name)
|
|
124
|
+
required_type = get_required_type(data_type)
|
|
125
|
+
|
|
126
|
+
if expected_type is not None:
|
|
127
|
+
return expected_type == required_type
|
|
128
|
+
if is_standard_type(required_type):
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
# user-defined type registered with `conn.set_type_codec()`
|
|
132
|
+
return True
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class _SQLPlaceholder:
|
|
136
|
+
ordinal: int
|
|
137
|
+
data_type: type[Any]
|
|
138
|
+
|
|
139
|
+
def __init__(self, ordinal: int, data_type: type[Any]) -> None:
|
|
140
|
+
self.ordinal = ordinal
|
|
141
|
+
self.data_type = data_type
|
|
142
|
+
|
|
143
|
+
def __repr__(self) -> str:
|
|
144
|
+
return f"{self.__class__.__name__}({self.ordinal}, {self.data_type!r})"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class _SQLObject:
|
|
148
|
+
"""
|
|
149
|
+
Associates input and output type information with a SQL statement.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
parameter_data_types: tuple[_SQLPlaceholder, ...]
|
|
153
|
+
resultset_data_types: tuple[type[Any], ...]
|
|
154
|
+
required: int
|
|
155
|
+
|
|
156
|
+
def __init__(
|
|
157
|
+
self,
|
|
158
|
+
*,
|
|
159
|
+
args: type[Any] | None = None,
|
|
160
|
+
resultset: type[Any] | None = None,
|
|
161
|
+
) -> None:
|
|
162
|
+
if args is not None:
|
|
163
|
+
if get_origin(args) is tuple:
|
|
164
|
+
self.parameter_data_types = tuple(_SQLPlaceholder(ordinal, arg) for ordinal, arg in enumerate(get_args(args), start=1))
|
|
165
|
+
else:
|
|
166
|
+
self.parameter_data_types = (_SQLPlaceholder(1, args),)
|
|
167
|
+
else:
|
|
168
|
+
self.parameter_data_types = ()
|
|
169
|
+
|
|
170
|
+
if resultset is not None:
|
|
171
|
+
if get_origin(resultset) is tuple:
|
|
172
|
+
self.resultset_data_types = get_args(resultset)
|
|
173
|
+
else:
|
|
174
|
+
self.resultset_data_types = (resultset,)
|
|
175
|
+
else:
|
|
176
|
+
self.resultset_data_types = ()
|
|
177
|
+
|
|
178
|
+
# create a bit-field of required types (1: required; 0: optional)
|
|
179
|
+
required = 0
|
|
180
|
+
for index, data_type in enumerate(self.resultset_data_types):
|
|
181
|
+
required |= (not is_optional_type(data_type)) << index
|
|
182
|
+
self.required = required
|
|
183
|
+
|
|
184
|
+
def _raise_required_is_none(self, row: tuple[Any, ...], row_index: int | None = None) -> None:
|
|
185
|
+
"""
|
|
186
|
+
Raises an error with the index of the first column value that is of a required type but has been assigned a value of `None`.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
for col_index in range(len(row)):
|
|
190
|
+
if (self.required >> col_index & 1) and row[col_index] is None:
|
|
191
|
+
if row_index is not None:
|
|
192
|
+
row_col_spec = f"row #{row_index} and column #{col_index}"
|
|
193
|
+
else:
|
|
194
|
+
row_col_spec = f"column #{col_index}"
|
|
195
|
+
raise TypeError(f"expected: {self.resultset_data_types[col_index]} in {row_col_spec}; got: NULL")
|
|
196
|
+
|
|
197
|
+
def check_rows(self, rows: list[tuple[Any, ...]]) -> None:
|
|
198
|
+
"""
|
|
199
|
+
Verifies if declared types match actual value types in a resultset.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
if not rows:
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
required = self.required
|
|
206
|
+
if not required:
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
match len(rows[0]):
|
|
210
|
+
case 1:
|
|
211
|
+
for r, row in enumerate(rows):
|
|
212
|
+
if required & (row[0] is None):
|
|
213
|
+
self._raise_required_is_none(row, r)
|
|
214
|
+
case 2:
|
|
215
|
+
for r, row in enumerate(rows):
|
|
216
|
+
a, b = row
|
|
217
|
+
if required & ((a is None) | (b is None) << 1):
|
|
218
|
+
self._raise_required_is_none(row, r)
|
|
219
|
+
case 3:
|
|
220
|
+
for r, row in enumerate(rows):
|
|
221
|
+
a, b, c = row
|
|
222
|
+
if required & ((a is None) | (b is None) << 1 | (c is None) << 2):
|
|
223
|
+
self._raise_required_is_none(row, r)
|
|
224
|
+
case 4:
|
|
225
|
+
for r, row in enumerate(rows):
|
|
226
|
+
a, b, c, d = row
|
|
227
|
+
if required & ((a is None) | (b is None) << 1 | (c is None) << 2 | (d is None) << 3):
|
|
228
|
+
self._raise_required_is_none(row, r)
|
|
229
|
+
case 5:
|
|
230
|
+
for r, row in enumerate(rows):
|
|
231
|
+
a, b, c, d, e = row
|
|
232
|
+
if required & ((a is None) | (b is None) << 1 | (c is None) << 2 | (d is None) << 3 | (e is None) << 4):
|
|
233
|
+
self._raise_required_is_none(row, r)
|
|
234
|
+
case 6:
|
|
235
|
+
for r, row in enumerate(rows):
|
|
236
|
+
a, b, c, d, e, f = row
|
|
237
|
+
if required & ((a is None) | (b is None) << 1 | (c is None) << 2 | (d is None) << 3 | (e is None) << 4 | (f is None) << 5):
|
|
238
|
+
self._raise_required_is_none(row, r)
|
|
239
|
+
case 7:
|
|
240
|
+
for r, row in enumerate(rows):
|
|
241
|
+
a, b, c, d, e, f, g = row
|
|
242
|
+
if required & ((a is None) | (b is None) << 1 | (c is None) << 2 | (d is None) << 3 | (e is None) << 4 | (f is None) << 5 | (g is None) << 6):
|
|
243
|
+
self._raise_required_is_none(row, r)
|
|
244
|
+
case 8:
|
|
245
|
+
for r, row in enumerate(rows):
|
|
246
|
+
a, b, c, d, e, f, g, h = row
|
|
247
|
+
if required & ((a is None) | (b is None) << 1 | (c is None) << 2 | (d is None) << 3 | (e is None) << 4 | (f is None) << 5 | (g is None) << 6 | (h is None) << 7):
|
|
248
|
+
self._raise_required_is_none(row, r)
|
|
249
|
+
case _:
|
|
250
|
+
for r, row in enumerate(rows):
|
|
251
|
+
self._raise_required_is_none(row, r)
|
|
252
|
+
|
|
253
|
+
def check_row(self, row: tuple[Any, ...]) -> None:
|
|
254
|
+
"""
|
|
255
|
+
Verifies if declared types match actual value types in a single row.
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
required = self.required
|
|
259
|
+
if not required:
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
match len(row):
|
|
263
|
+
case 1:
|
|
264
|
+
if required & (row[0] is None):
|
|
265
|
+
self._raise_required_is_none(row)
|
|
266
|
+
case 2:
|
|
267
|
+
a, b = row
|
|
268
|
+
if required & ((a is None) | (b is None) << 1):
|
|
269
|
+
self._raise_required_is_none(row)
|
|
270
|
+
case 3:
|
|
271
|
+
a, b, c = row
|
|
272
|
+
if required & ((a is None) | (b is None) << 1 | (c is None) << 2):
|
|
273
|
+
self._raise_required_is_none(row)
|
|
274
|
+
case 4:
|
|
275
|
+
a, b, c, d = row
|
|
276
|
+
if required & ((a is None) | (b is None) << 1 | (c is None) << 2 | (d is None) << 3):
|
|
277
|
+
self._raise_required_is_none(row)
|
|
278
|
+
case 5:
|
|
279
|
+
a, b, c, d, e = row
|
|
280
|
+
if required & ((a is None) | (b is None) << 1 | (c is None) << 2 | (d is None) << 3 | (e is None) << 4):
|
|
281
|
+
self._raise_required_is_none(row)
|
|
282
|
+
case 6:
|
|
283
|
+
a, b, c, d, e, f = row
|
|
284
|
+
if required & ((a is None) | (b is None) << 1 | (c is None) << 2 | (d is None) << 3 | (e is None) << 4 | (f is None) << 5):
|
|
285
|
+
self._raise_required_is_none(row)
|
|
286
|
+
case 7:
|
|
287
|
+
a, b, c, d, e, f, g = row
|
|
288
|
+
if required & ((a is None) | (b is None) << 1 | (c is None) << 2 | (d is None) << 3 | (e is None) << 4 | (f is None) << 5 | (g is None) << 6):
|
|
289
|
+
self._raise_required_is_none(row)
|
|
290
|
+
case 8:
|
|
291
|
+
a, b, c, d, e, f, g, h = row
|
|
292
|
+
if required & ((a is None) | (b is None) << 1 | (c is None) << 2 | (d is None) << 3 | (e is None) << 4 | (f is None) << 5 | (g is None) << 6 | (h is None) << 7):
|
|
293
|
+
self._raise_required_is_none(row)
|
|
294
|
+
case _:
|
|
295
|
+
self._raise_required_is_none(row)
|
|
296
|
+
|
|
297
|
+
def check_value(self, value: Any) -> None:
|
|
298
|
+
"""
|
|
299
|
+
Verifies if the declared type matches the actual value type.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
if self.required and value is None:
|
|
303
|
+
raise TypeError(f"expected: {self.resultset_data_types[0]}; got: NULL")
|
|
304
|
+
|
|
305
|
+
@abstractmethod
|
|
306
|
+
def query(self) -> str:
|
|
307
|
+
"""
|
|
308
|
+
Returns a SQL query string with PostgreSQL ordinal placeholders.
|
|
309
|
+
"""
|
|
310
|
+
...
|
|
311
|
+
|
|
312
|
+
def __repr__(self) -> str:
|
|
313
|
+
return f"{self.__class__.__name__}({self.query()!r})"
|
|
314
|
+
|
|
315
|
+
def __str__(self) -> str:
|
|
316
|
+
return self.query()
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
if sys.version_info >= (3, 14):
|
|
320
|
+
from string.templatelib import Interpolation, Template # type: ignore[import-not-found]
|
|
321
|
+
|
|
322
|
+
SQLExpression: TypeAlias = Template | str
|
|
323
|
+
|
|
324
|
+
class _SQLTemplate(_SQLObject):
|
|
325
|
+
"""
|
|
326
|
+
A SQL query specified with the Python t-string syntax.
|
|
327
|
+
"""
|
|
328
|
+
|
|
329
|
+
strings: tuple[str, ...]
|
|
330
|
+
placeholders: tuple[_SQLPlaceholder, ...]
|
|
331
|
+
|
|
332
|
+
def __init__(
|
|
333
|
+
self,
|
|
334
|
+
template: Template,
|
|
335
|
+
*,
|
|
336
|
+
args: type[Any] | None = None,
|
|
337
|
+
resultset: type[Any] | None = None,
|
|
338
|
+
) -> None:
|
|
339
|
+
super().__init__(args=args, resultset=resultset)
|
|
340
|
+
|
|
341
|
+
for ip in template.interpolations:
|
|
342
|
+
if ip.conversion is not None:
|
|
343
|
+
raise TypeError(f"interpolation `{ip.expression}` expected to apply no conversion")
|
|
344
|
+
if ip.format_spec:
|
|
345
|
+
raise TypeError(f"interpolation `{ip.expression}` expected to apply no format spec")
|
|
346
|
+
if not isinstance(ip.value, int):
|
|
347
|
+
raise TypeError(f"interpolation `{ip.expression}` expected to evaluate to an integer")
|
|
348
|
+
|
|
349
|
+
self.strings = template.strings
|
|
350
|
+
|
|
351
|
+
if args is not None:
|
|
352
|
+
|
|
353
|
+
def _to_placeholder(ip: Interpolation) -> _SQLPlaceholder:
|
|
354
|
+
ordinal = int(ip.value)
|
|
355
|
+
if not (0 < ordinal <= len(self.parameter_data_types)):
|
|
356
|
+
raise IndexError(f"interpolation `{ip.expression}` is an ordinal out of range; expected: 0 < value <= {len(self.parameter_data_types)}")
|
|
357
|
+
return self.parameter_data_types[int(ip.value) - 1]
|
|
358
|
+
|
|
359
|
+
self.placeholders = tuple(_to_placeholder(ip) for ip in template.interpolations)
|
|
360
|
+
else:
|
|
361
|
+
self.placeholders = ()
|
|
362
|
+
|
|
363
|
+
def query(self) -> str:
|
|
364
|
+
buf = StringIO()
|
|
365
|
+
for s, p in zip(self.strings[:-1], self.placeholders, strict=True):
|
|
366
|
+
buf.write(s)
|
|
367
|
+
buf.write(f"${p.ordinal}")
|
|
368
|
+
buf.write(self.strings[-1])
|
|
369
|
+
return buf.getvalue()
|
|
370
|
+
|
|
371
|
+
else:
|
|
372
|
+
SQLExpression = str
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
class _SQLString(_SQLObject):
|
|
376
|
+
"""
|
|
377
|
+
A SQL query specified as a plain string (e.g. f-string).
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
sql: str
|
|
381
|
+
|
|
382
|
+
def __init__(
|
|
383
|
+
self,
|
|
384
|
+
sql: str,
|
|
385
|
+
*,
|
|
386
|
+
args: type[Any] | None = None,
|
|
387
|
+
resultset: type[Any] | None = None,
|
|
388
|
+
) -> None:
|
|
389
|
+
super().__init__(args=args, resultset=resultset)
|
|
390
|
+
self.sql = sql
|
|
391
|
+
|
|
392
|
+
def query(self) -> str:
|
|
393
|
+
return self.sql
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
class _SQL:
|
|
397
|
+
"""
|
|
398
|
+
Represents a SQL statement with associated type information.
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
Connection: TypeAlias = asyncpg.Connection | asyncpg.pool.PoolConnectionProxy
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
class _SQLImpl(_SQL):
|
|
406
|
+
"""
|
|
407
|
+
Forwards input data to an `asyncpg.PreparedStatement`, and validates output data (if necessary).
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
sql: _SQLObject
|
|
411
|
+
|
|
412
|
+
def __init__(self, sql: _SQLObject) -> None:
|
|
413
|
+
self.sql = sql
|
|
414
|
+
|
|
415
|
+
def __str__(self) -> str:
|
|
416
|
+
return str(self.sql)
|
|
417
|
+
|
|
418
|
+
def __repr__(self) -> str:
|
|
419
|
+
return repr(self.sql)
|
|
420
|
+
|
|
421
|
+
async def _prepare(self, connection: Connection) -> PreparedStatement:
|
|
422
|
+
stmt = await connection.prepare(self.sql.query())
|
|
423
|
+
|
|
424
|
+
for attr, data_type in zip(stmt.get_attributes(), self.sql.resultset_data_types, strict=True):
|
|
425
|
+
if not check_data_type(attr.type.name, data_type):
|
|
426
|
+
raise TypeError(f"expected: {data_type} in column `{attr.name}`; got: `{attr.type.kind}` of `{attr.type.name}`")
|
|
427
|
+
|
|
428
|
+
return stmt
|
|
429
|
+
|
|
430
|
+
async def execute(self, connection: asyncpg.Connection, *args: Any) -> None:
|
|
431
|
+
await connection.execute(self.sql.query(), *args)
|
|
432
|
+
|
|
433
|
+
async def executemany(self, connection: asyncpg.Connection, args: Iterable[Sequence[Any]]) -> None:
|
|
434
|
+
stmt = await self._prepare(connection)
|
|
435
|
+
await stmt.executemany(args)
|
|
436
|
+
|
|
437
|
+
async def fetch(self, connection: asyncpg.Connection, *args: Any) -> list[tuple[Any, ...]]:
|
|
438
|
+
stmt = await self._prepare(connection)
|
|
439
|
+
rows = await stmt.fetch(*args)
|
|
440
|
+
resultset = [tuple(value for value in row) for row in rows]
|
|
441
|
+
self.sql.check_rows(resultset)
|
|
442
|
+
return resultset
|
|
443
|
+
|
|
444
|
+
async def fetchmany(self, connection: asyncpg.Connection, args: Iterable[Sequence[Any]]) -> list[tuple[Any, ...]]:
|
|
445
|
+
stmt = await self._prepare(connection)
|
|
446
|
+
rows = await stmt.fetchmany(args) # type: ignore[arg-type, call-arg] # pyright: ignore[reportCallIssue]
|
|
447
|
+
rows = typing.cast(list[asyncpg.Record], rows)
|
|
448
|
+
resultset = [tuple(value for value in row) for row in rows]
|
|
449
|
+
self.sql.check_rows(resultset)
|
|
450
|
+
return resultset
|
|
451
|
+
|
|
452
|
+
async def fetchrow(self, connection: asyncpg.Connection, *args: Any) -> tuple[Any, ...] | None:
|
|
453
|
+
stmt = await self._prepare(connection)
|
|
454
|
+
row = await stmt.fetchrow(*args)
|
|
455
|
+
if row is None:
|
|
456
|
+
return None
|
|
457
|
+
resultset = tuple(value for value in row)
|
|
458
|
+
self.sql.check_row(resultset)
|
|
459
|
+
return resultset
|
|
460
|
+
|
|
461
|
+
async def fetchval(self, connection: asyncpg.Connection, *args: Any) -> Any:
|
|
462
|
+
stmt = await self._prepare(connection)
|
|
463
|
+
value = await stmt.fetchval(*args)
|
|
464
|
+
self.sql.check_value(value)
|
|
465
|
+
return value
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
### START OF AUTO-GENERATED BLOCK ###
|
|
469
|
+
|
|
470
|
+
PS = TypeVar("PS", bool, bool | None, int, int | None, float, float | None, Decimal, Decimal | None, date, date | None, time, time | None, datetime, datetime | None, str, str | None, bytes, bytes | None, UUID, UUID | None)
|
|
471
|
+
P1 = TypeVar("P1")
|
|
472
|
+
P2 = TypeVar("P2")
|
|
473
|
+
P3 = TypeVar("P3")
|
|
474
|
+
P4 = TypeVar("P4")
|
|
475
|
+
P5 = TypeVar("P5")
|
|
476
|
+
P6 = TypeVar("P6")
|
|
477
|
+
P7 = TypeVar("P7")
|
|
478
|
+
P8 = TypeVar("P8")
|
|
479
|
+
RS = TypeVar("RS", bool, bool | None, int, int | None, float, float | None, Decimal, Decimal | None, date, date | None, time, time | None, datetime, datetime | None, str, str | None, bytes, bytes | None, UUID, UUID | None)
|
|
480
|
+
R1 = TypeVar("R1")
|
|
481
|
+
R2 = TypeVar("R2")
|
|
482
|
+
RX = TypeVarTuple("RX")
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
class SQL_P0(_SQL):
|
|
486
|
+
@abstractmethod
|
|
487
|
+
async def execute(self, connection: Connection) -> None: ...
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
class SQL_P0_RS(Generic[R1], SQL_P0):
|
|
491
|
+
@abstractmethod
|
|
492
|
+
async def fetch(self, connection: Connection) -> list[tuple[R1]]: ...
|
|
493
|
+
@abstractmethod
|
|
494
|
+
async def fetchrow(self, connection: Connection) -> tuple[R1] | None: ...
|
|
495
|
+
@abstractmethod
|
|
496
|
+
async def fetchval(self, connection: Connection) -> R1: ...
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
class SQL_P0_RX(Generic[R1, R2, Unpack[RX]], SQL_P0):
|
|
500
|
+
@abstractmethod
|
|
501
|
+
async def fetch(self, connection: Connection) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
502
|
+
@abstractmethod
|
|
503
|
+
async def fetchrow(self, connection: Connection) -> tuple[R1, R2, Unpack[RX]] | None: ...
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
class SQL_P1(Generic[P1], _SQL):
|
|
507
|
+
@abstractmethod
|
|
508
|
+
async def execute(self, connection: Connection, arg1: P1) -> None: ...
|
|
509
|
+
@abstractmethod
|
|
510
|
+
async def executemany(self, connection: Connection, args: Iterable[tuple[P1]]) -> None: ...
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
class SQL_P1_RS(Generic[P1, R1], SQL_P1[P1]):
|
|
514
|
+
@abstractmethod
|
|
515
|
+
async def fetch(self, connection: Connection, arg1: P1) -> list[tuple[R1]]: ...
|
|
516
|
+
@abstractmethod
|
|
517
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1]]) -> list[tuple[R1]]: ...
|
|
518
|
+
@abstractmethod
|
|
519
|
+
async def fetchrow(self, connection: Connection, arg1: P1) -> tuple[R1] | None: ...
|
|
520
|
+
@abstractmethod
|
|
521
|
+
async def fetchval(self, connection: Connection, arg1: P1) -> R1: ...
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
class SQL_P1_RX(Generic[P1, R1, R2, Unpack[RX]], SQL_P1[P1]):
|
|
525
|
+
@abstractmethod
|
|
526
|
+
async def fetch(self, connection: Connection, arg1: P1) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
527
|
+
@abstractmethod
|
|
528
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
529
|
+
@abstractmethod
|
|
530
|
+
async def fetchrow(self, connection: Connection, arg1: P1) -> tuple[R1, R2, Unpack[RX]] | None: ...
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
class SQL_P2(Generic[P1, P2], _SQL):
|
|
534
|
+
@abstractmethod
|
|
535
|
+
async def execute(self, connection: Connection, arg1: P1, arg2: P2) -> None: ...
|
|
536
|
+
@abstractmethod
|
|
537
|
+
async def executemany(self, connection: Connection, args: Iterable[tuple[P1, P2]]) -> None: ...
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
class SQL_P2_RS(Generic[P1, P2, R1], SQL_P2[P1, P2]):
|
|
541
|
+
@abstractmethod
|
|
542
|
+
async def fetch(self, connection: Connection, arg1: P1, arg2: P2) -> list[tuple[R1]]: ...
|
|
543
|
+
@abstractmethod
|
|
544
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2]]) -> list[tuple[R1]]: ...
|
|
545
|
+
@abstractmethod
|
|
546
|
+
async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2) -> tuple[R1] | None: ...
|
|
547
|
+
@abstractmethod
|
|
548
|
+
async def fetchval(self, connection: Connection, arg1: P1, arg2: P2) -> R1: ...
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
class SQL_P2_RX(Generic[P1, P2, R1, R2, Unpack[RX]], SQL_P2[P1, P2]):
|
|
552
|
+
@abstractmethod
|
|
553
|
+
async def fetch(self, connection: Connection, arg1: P1, arg2: P2) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
554
|
+
@abstractmethod
|
|
555
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
556
|
+
@abstractmethod
|
|
557
|
+
async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2) -> tuple[R1, R2, Unpack[RX]] | None: ...
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
class SQL_P3(Generic[P1, P2, P3], _SQL):
|
|
561
|
+
@abstractmethod
|
|
562
|
+
async def execute(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3) -> None: ...
|
|
563
|
+
@abstractmethod
|
|
564
|
+
async def executemany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3]]) -> None: ...
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
class SQL_P3_RS(Generic[P1, P2, P3, R1], SQL_P3[P1, P2, P3]):
|
|
568
|
+
@abstractmethod
|
|
569
|
+
async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3) -> list[tuple[R1]]: ...
|
|
570
|
+
@abstractmethod
|
|
571
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3]]) -> list[tuple[R1]]: ...
|
|
572
|
+
@abstractmethod
|
|
573
|
+
async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3) -> tuple[R1] | None: ...
|
|
574
|
+
@abstractmethod
|
|
575
|
+
async def fetchval(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3) -> R1: ...
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
class SQL_P3_RX(Generic[P1, P2, P3, R1, R2, Unpack[RX]], SQL_P3[P1, P2, P3]):
|
|
579
|
+
@abstractmethod
|
|
580
|
+
async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
581
|
+
@abstractmethod
|
|
582
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
583
|
+
@abstractmethod
|
|
584
|
+
async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3) -> tuple[R1, R2, Unpack[RX]] | None: ...
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
class SQL_P4(Generic[P1, P2, P3, P4], _SQL):
|
|
588
|
+
@abstractmethod
|
|
589
|
+
async def execute(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4) -> None: ...
|
|
590
|
+
@abstractmethod
|
|
591
|
+
async def executemany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4]]) -> None: ...
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
class SQL_P4_RS(Generic[P1, P2, P3, P4, R1], SQL_P4[P1, P2, P3, P4]):
|
|
595
|
+
@abstractmethod
|
|
596
|
+
async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4) -> list[tuple[R1]]: ...
|
|
597
|
+
@abstractmethod
|
|
598
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4]]) -> list[tuple[R1]]: ...
|
|
599
|
+
@abstractmethod
|
|
600
|
+
async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4) -> tuple[R1] | None: ...
|
|
601
|
+
@abstractmethod
|
|
602
|
+
async def fetchval(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4) -> R1: ...
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
class SQL_P4_RX(Generic[P1, P2, P3, P4, R1, R2, Unpack[RX]], SQL_P4[P1, P2, P3, P4]):
|
|
606
|
+
@abstractmethod
|
|
607
|
+
async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
608
|
+
@abstractmethod
|
|
609
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
610
|
+
@abstractmethod
|
|
611
|
+
async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4) -> tuple[R1, R2, Unpack[RX]] | None: ...
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
class SQL_P5(Generic[P1, P2, P3, P4, P5], _SQL):
|
|
615
|
+
@abstractmethod
|
|
616
|
+
async def execute(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5) -> None: ...
|
|
617
|
+
@abstractmethod
|
|
618
|
+
async def executemany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5]]) -> None: ...
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
class SQL_P5_RS(Generic[P1, P2, P3, P4, P5, R1], SQL_P5[P1, P2, P3, P4, P5]):
|
|
622
|
+
@abstractmethod
|
|
623
|
+
async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5) -> list[tuple[R1]]: ...
|
|
624
|
+
@abstractmethod
|
|
625
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5]]) -> list[tuple[R1]]: ...
|
|
626
|
+
@abstractmethod
|
|
627
|
+
async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5) -> tuple[R1] | None: ...
|
|
628
|
+
@abstractmethod
|
|
629
|
+
async def fetchval(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5) -> R1: ...
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
class SQL_P5_RX(Generic[P1, P2, P3, P4, P5, R1, R2, Unpack[RX]], SQL_P5[P1, P2, P3, P4, P5]):
|
|
633
|
+
@abstractmethod
|
|
634
|
+
async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
635
|
+
@abstractmethod
|
|
636
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
637
|
+
@abstractmethod
|
|
638
|
+
async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5) -> tuple[R1, R2, Unpack[RX]] | None: ...
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
class SQL_P6(Generic[P1, P2, P3, P4, P5, P6], _SQL):
|
|
642
|
+
@abstractmethod
|
|
643
|
+
async def execute(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6) -> None: ...
|
|
644
|
+
@abstractmethod
|
|
645
|
+
async def executemany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6]]) -> None: ...
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
class SQL_P6_RS(Generic[P1, P2, P3, P4, P5, P6, R1], SQL_P6[P1, P2, P3, P4, P5, P6]):
|
|
649
|
+
@abstractmethod
|
|
650
|
+
async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6) -> list[tuple[R1]]: ...
|
|
651
|
+
@abstractmethod
|
|
652
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6]]) -> list[tuple[R1]]: ...
|
|
653
|
+
@abstractmethod
|
|
654
|
+
async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6) -> tuple[R1] | None: ...
|
|
655
|
+
@abstractmethod
|
|
656
|
+
async def fetchval(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6) -> R1: ...
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
class SQL_P6_RX(Generic[P1, P2, P3, P4, P5, P6, R1, R2, Unpack[RX]], SQL_P6[P1, P2, P3, P4, P5, P6]):
|
|
660
|
+
@abstractmethod
|
|
661
|
+
async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
662
|
+
@abstractmethod
|
|
663
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
664
|
+
@abstractmethod
|
|
665
|
+
async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6) -> tuple[R1, R2, Unpack[RX]] | None: ...
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
class SQL_P7(Generic[P1, P2, P3, P4, P5, P6, P7], _SQL):
|
|
669
|
+
@abstractmethod
|
|
670
|
+
async def execute(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7) -> None: ...
|
|
671
|
+
@abstractmethod
|
|
672
|
+
async def executemany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6, P7]]) -> None: ...
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
class SQL_P7_RS(Generic[P1, P2, P3, P4, P5, P6, P7, R1], SQL_P7[P1, P2, P3, P4, P5, P6, P7]):
|
|
676
|
+
@abstractmethod
|
|
677
|
+
async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7) -> list[tuple[R1]]: ...
|
|
678
|
+
@abstractmethod
|
|
679
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6, P7]]) -> list[tuple[R1]]: ...
|
|
680
|
+
@abstractmethod
|
|
681
|
+
async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7) -> tuple[R1] | None: ...
|
|
682
|
+
@abstractmethod
|
|
683
|
+
async def fetchval(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7) -> R1: ...
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
class SQL_P7_RX(Generic[P1, P2, P3, P4, P5, P6, P7, R1, R2, Unpack[RX]], SQL_P7[P1, P2, P3, P4, P5, P6, P7]):
|
|
687
|
+
@abstractmethod
|
|
688
|
+
async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
689
|
+
@abstractmethod
|
|
690
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6, P7]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
691
|
+
@abstractmethod
|
|
692
|
+
async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7) -> tuple[R1, R2, Unpack[RX]] | None: ...
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
class SQL_P8(Generic[P1, P2, P3, P4, P5, P6, P7, P8], _SQL):
|
|
696
|
+
@abstractmethod
|
|
697
|
+
async def execute(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8) -> None: ...
|
|
698
|
+
@abstractmethod
|
|
699
|
+
async def executemany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6, P7, P8]]) -> None: ...
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
class SQL_P8_RS(Generic[P1, P2, P3, P4, P5, P6, P7, P8, R1], SQL_P8[P1, P2, P3, P4, P5, P6, P7, P8]):
|
|
703
|
+
@abstractmethod
|
|
704
|
+
async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8) -> list[tuple[R1]]: ...
|
|
705
|
+
@abstractmethod
|
|
706
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6, P7, P8]]) -> list[tuple[R1]]: ...
|
|
707
|
+
@abstractmethod
|
|
708
|
+
async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8) -> tuple[R1] | None: ...
|
|
709
|
+
@abstractmethod
|
|
710
|
+
async def fetchval(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8) -> R1: ...
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
class SQL_P8_RX(Generic[P1, P2, P3, P4, P5, P6, P7, P8, R1, R2, Unpack[RX]], SQL_P8[P1, P2, P3, P4, P5, P6, P7, P8]):
|
|
714
|
+
@abstractmethod
|
|
715
|
+
async def fetch(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
716
|
+
@abstractmethod
|
|
717
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[P1, P2, P3, P4, P5, P6, P7, P8]]) -> list[tuple[R1, R2, Unpack[RX]]]: ...
|
|
718
|
+
@abstractmethod
|
|
719
|
+
async def fetchrow(self, connection: Connection, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8) -> tuple[R1, R2, Unpack[RX]] | None: ...
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
@overload
|
|
723
|
+
def sql(stmt: SQLExpression) -> SQL_P0: ...
|
|
724
|
+
@overload
|
|
725
|
+
def sql(stmt: SQLExpression, *, resultset: type[RS]) -> SQL_P0_RS[RS]: ...
|
|
726
|
+
@overload
|
|
727
|
+
def sql(stmt: SQLExpression, *, resultset: type[tuple[R1]]) -> SQL_P0_RS[R1]: ...
|
|
728
|
+
@overload
|
|
729
|
+
def sql(stmt: SQLExpression, *, resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P0_RX[R1, R2, Unpack[RX]]: ...
|
|
730
|
+
@overload
|
|
731
|
+
def sql(stmt: SQLExpression, *, args: type[PS]) -> SQL_P1[PS]: ...
|
|
732
|
+
@overload
|
|
733
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1]]) -> SQL_P1[P1]: ...
|
|
734
|
+
@overload
|
|
735
|
+
def sql(stmt: SQLExpression, *, args: type[PS], resultset: type[RS]) -> SQL_P1_RS[PS, RS]: ...
|
|
736
|
+
@overload
|
|
737
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1]], resultset: type[tuple[R1]]) -> SQL_P1_RS[P1, R1]: ...
|
|
738
|
+
@overload
|
|
739
|
+
def sql(stmt: SQLExpression, *, args: type[PS], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P1_RX[PS, R1, R2, Unpack[RX]]: ...
|
|
740
|
+
@overload
|
|
741
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P1_RX[P1, R1, R2, Unpack[RX]]: ...
|
|
742
|
+
@overload
|
|
743
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2]]) -> SQL_P2[P1, P2]: ...
|
|
744
|
+
@overload
|
|
745
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2]], resultset: type[RS]) -> SQL_P2_RS[P1, P2, RS]: ...
|
|
746
|
+
@overload
|
|
747
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2]], resultset: type[tuple[R1]]) -> SQL_P2_RS[P1, P2, R1]: ...
|
|
748
|
+
@overload
|
|
749
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P2_RX[P1, P2, R1, R2, Unpack[RX]]: ...
|
|
750
|
+
@overload
|
|
751
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3]]) -> SQL_P3[P1, P2, P3]: ...
|
|
752
|
+
@overload
|
|
753
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3]], resultset: type[RS]) -> SQL_P3_RS[P1, P2, P3, RS]: ...
|
|
754
|
+
@overload
|
|
755
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3]], resultset: type[tuple[R1]]) -> SQL_P3_RS[P1, P2, P3, R1]: ...
|
|
756
|
+
@overload
|
|
757
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P3_RX[P1, P2, P3, R1, R2, Unpack[RX]]: ...
|
|
758
|
+
@overload
|
|
759
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4]]) -> SQL_P4[P1, P2, P3, P4]: ...
|
|
760
|
+
@overload
|
|
761
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4]], resultset: type[RS]) -> SQL_P4_RS[P1, P2, P3, P4, RS]: ...
|
|
762
|
+
@overload
|
|
763
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4]], resultset: type[tuple[R1]]) -> SQL_P4_RS[P1, P2, P3, P4, R1]: ...
|
|
764
|
+
@overload
|
|
765
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P4_RX[P1, P2, P3, P4, R1, R2, Unpack[RX]]: ...
|
|
766
|
+
@overload
|
|
767
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5]]) -> SQL_P5[P1, P2, P3, P4, P5]: ...
|
|
768
|
+
@overload
|
|
769
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5]], resultset: type[RS]) -> SQL_P5_RS[P1, P2, P3, P4, P5, RS]: ...
|
|
770
|
+
@overload
|
|
771
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5]], resultset: type[tuple[R1]]) -> SQL_P5_RS[P1, P2, P3, P4, P5, R1]: ...
|
|
772
|
+
@overload
|
|
773
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P5_RX[P1, P2, P3, P4, P5, R1, R2, Unpack[RX]]: ...
|
|
774
|
+
@overload
|
|
775
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6]]) -> SQL_P6[P1, P2, P3, P4, P5, P6]: ...
|
|
776
|
+
@overload
|
|
777
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6]], resultset: type[RS]) -> SQL_P6_RS[P1, P2, P3, P4, P5, P6, RS]: ...
|
|
778
|
+
@overload
|
|
779
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6]], resultset: type[tuple[R1]]) -> SQL_P6_RS[P1, P2, P3, P4, P5, P6, R1]: ...
|
|
780
|
+
@overload
|
|
781
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P6_RX[P1, P2, P3, P4, P5, P6, R1, R2, Unpack[RX]]: ...
|
|
782
|
+
@overload
|
|
783
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7]]) -> SQL_P7[P1, P2, P3, P4, P5, P6, P7]: ...
|
|
784
|
+
@overload
|
|
785
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7]], resultset: type[RS]) -> SQL_P7_RS[P1, P2, P3, P4, P5, P6, P7, RS]: ...
|
|
786
|
+
@overload
|
|
787
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7]], resultset: type[tuple[R1]]) -> SQL_P7_RS[P1, P2, P3, P4, P5, P6, P7, R1]: ...
|
|
788
|
+
@overload
|
|
789
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P7_RX[P1, P2, P3, P4, P5, P6, P7, R1, R2, Unpack[RX]]: ...
|
|
790
|
+
@overload
|
|
791
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7, P8]]) -> SQL_P8[P1, P2, P3, P4, P5, P6, P7, P8]: ...
|
|
792
|
+
@overload
|
|
793
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7, P8]], resultset: type[RS]) -> SQL_P8_RS[P1, P2, P3, P4, P5, P6, P7, P8, RS]: ...
|
|
794
|
+
@overload
|
|
795
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7, P8]], resultset: type[tuple[R1]]) -> SQL_P8_RS[P1, P2, P3, P4, P5, P6, P7, P8, R1]: ...
|
|
796
|
+
@overload
|
|
797
|
+
def sql(stmt: SQLExpression, *, args: type[tuple[P1, P2, P3, P4, P5, P6, P7, P8]], resultset: type[tuple[R1, R2, Unpack[RX]]]) -> SQL_P8_RX[P1, P2, P3, P4, P5, P6, P7, P8, R1, R2, Unpack[RX]]: ...
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
### END OF AUTO-GENERATED BLOCK ###
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
def sql(
|
|
804
|
+
stmt: SQLExpression,
|
|
805
|
+
*,
|
|
806
|
+
args: type[Any] | None = None,
|
|
807
|
+
resultset: type[Any] | None = None,
|
|
808
|
+
) -> _SQL:
|
|
809
|
+
"""
|
|
810
|
+
Creates a SQL statement with associated type information.
|
|
811
|
+
|
|
812
|
+
:param stmt: SQL statement as a string or template.
|
|
813
|
+
:param args: Type signature for input parameters. Use the type for a single parameter (e.g. `int`) or `tuple[...]` for multiple parameters.
|
|
814
|
+
:param resultset: Type signature for output data. Use the type for a single parameter (e.g. `int`) or `tuple[...]` for multiple parameters.
|
|
815
|
+
"""
|
|
816
|
+
|
|
817
|
+
if sys.version_info >= (3, 14):
|
|
818
|
+
obj: _SQLObject
|
|
819
|
+
match stmt:
|
|
820
|
+
case Template():
|
|
821
|
+
obj = _SQLTemplate(stmt, args=args, resultset=resultset)
|
|
822
|
+
case str():
|
|
823
|
+
obj = _SQLString(stmt, args=args, resultset=resultset)
|
|
824
|
+
else:
|
|
825
|
+
obj = _SQLString(stmt, args=args, resultset=resultset)
|
|
826
|
+
|
|
827
|
+
return _SQLImpl(obj)
|
asyncpg_typed/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: asyncpg_typed
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Type-safe queries for asyncpg
|
|
5
|
+
Author-email: Levente Hunyadi <hunyadi@gmail.com>
|
|
6
|
+
Maintainer-email: Levente Hunyadi <hunyadi@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/hunyadi/asyncpg_typed
|
|
9
|
+
Project-URL: Source, https://github.com/hunyadi/asyncpg_typed
|
|
10
|
+
Keywords: asyncpg,typed,database-client
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Programming Language :: SQL
|
|
22
|
+
Classifier: Topic :: Database
|
|
23
|
+
Classifier: Topic :: Utilities
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: asyncpg>=0.31
|
|
29
|
+
Requires-Dist: typing-extensions>=4.15; python_version < "3.11"
|
|
30
|
+
Provides-Extra: vector
|
|
31
|
+
Requires-Dist: asyncpg-vector>=0.1; extra == "vector"
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: asyncpg-stubs>=0.31; extra == "dev"
|
|
34
|
+
Requires-Dist: build>=1.3; extra == "dev"
|
|
35
|
+
Requires-Dist: mypy>=1.19; extra == "dev"
|
|
36
|
+
Requires-Dist: ruff>=0.14; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# Type-safe queries for asyncpg
|
|
40
|
+
|
|
41
|
+
[asyncpg](https://magicstack.github.io/asyncpg/current/) is a high-performance database client to connect to a PostgreSQL server, and execute SQL statements using the async/await paradigm in Python. The library exposes a `Connection` object, which has methods like `execute` and `fetch` that run SQL queries against the database. Unfortunately, these methods take the query as a plain `str`, arguments as `object`, and the resultset is exposed as a `Record`, which is a `tuple`/`dict` hybrid whose `get` and indexer have a return type of `Any`. There is no mechanism to check compatibility of input or output arguments, even if their types are preliminarily known.
|
|
42
|
+
|
|
43
|
+
This Python library provides "compile-time" validation for SQL queries that linters and type checkers can enforce. By creating a generic `SQL` object and associating input and output type information with the query, the signatures of `execute` and `fetch` reveal the exact expected and returned types.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## Motivating example
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
# create a typed object, setting expected and returned types
|
|
50
|
+
select_where_sql = sql(
|
|
51
|
+
"""--sql
|
|
52
|
+
SELECT boolean_value, integer_value, string_value
|
|
53
|
+
FROM sample_data
|
|
54
|
+
WHERE boolean_value = $1 AND integer_value > $2
|
|
55
|
+
ORDER BY integer_value;
|
|
56
|
+
""",
|
|
57
|
+
args=tuple[bool, int],
|
|
58
|
+
resultset=tuple[bool, int, str | None],
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
conn = await asyncpg.connect(host="localhost", port=5432, user="postgres", password="postgres")
|
|
62
|
+
try:
|
|
63
|
+
# ✅ Valid signature
|
|
64
|
+
rows = await select_where_sql.fetch(conn, False, 2)
|
|
65
|
+
|
|
66
|
+
# ✅ Type of "rows" is "list[tuple[bool, int, str | None]]"
|
|
67
|
+
reveal_type(rows)
|
|
68
|
+
|
|
69
|
+
# ⚠️ Argument missing for parameter "arg2"
|
|
70
|
+
rows = await select_where_sql.fetch(conn, False)
|
|
71
|
+
|
|
72
|
+
# ⚠️ Argument of type "float" cannot be assigned to parameter "arg2" of type "int" in function "fetch"; "float" is not assignable to "int"
|
|
73
|
+
rows = await select_where_sql.fetch(conn, False, 3.14)
|
|
74
|
+
|
|
75
|
+
finally:
|
|
76
|
+
await conn.close()
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
## Syntax
|
|
81
|
+
|
|
82
|
+
### Creating a SQL object
|
|
83
|
+
|
|
84
|
+
Instantiate a SQL object with the `sql` function:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
def sql(
|
|
88
|
+
stmt: str | string.templatelib.Template,
|
|
89
|
+
*,
|
|
90
|
+
args: None | type[P1] | type[tuple[P1, P2]] | type[tuple[P1, P2, P3]] | ... = None,
|
|
91
|
+
resultset: None | type[R1] | type[tuple[R1, R2]] | type[tuple[R1, R2, R3]] | ... = None
|
|
92
|
+
) -> _SQL: ...
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The parameter `stmt` represents a SQL expression, either as a string (including an *f-string*) or a template (i.e. a *t-string*).
|
|
96
|
+
|
|
97
|
+
If the expression is a string, it can have PostgreSQL parameter placeholders such as `$1`, `$2` or `$3`:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
f"INSERT INTO table_name (col_1, col_2, col_3) VALUES ($1, $2, $3);"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
If the expression is a *t-string*, it can have replacement fields that evaluate to integers:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
t"INSERT INTO table_name (col_1, col_2, col_3) VALUES ({1}, {2}, {3});"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The parameters `args` and `resultset` take a series type `P` or `R`, which may be any of the following:
|
|
110
|
+
|
|
111
|
+
* (required) simple type
|
|
112
|
+
* optional simple type (`T | None`)
|
|
113
|
+
* `tuple` of several (required or optional) simple types.
|
|
114
|
+
|
|
115
|
+
Simple types include:
|
|
116
|
+
|
|
117
|
+
* `bool`
|
|
118
|
+
* `int`
|
|
119
|
+
* `float`
|
|
120
|
+
* `decimal.Decimal`
|
|
121
|
+
* `datetime.date`
|
|
122
|
+
* `datetime.time`
|
|
123
|
+
* `datetime.datetime`
|
|
124
|
+
* `str`
|
|
125
|
+
* `bytes`
|
|
126
|
+
* `uuid.UUID`
|
|
127
|
+
|
|
128
|
+
Types are grouped together with `tuple`:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
tuple[bool, int, str | None]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Passing a simple type directly (e.g. `type[T]`) is for convenience, and is equivalent to passing a one-element tuple of the same simple type (i.e. `type[tuple[T]]`).
|
|
135
|
+
|
|
136
|
+
The number of types in `args` must correspond to the number of query parameters. (This is validated on calling `sql(...)` for the *t-string* syntax.) The number of types in `resultset` must correspond to the number of columns returned by the query.
|
|
137
|
+
|
|
138
|
+
Both `args` and `resultset` types must be compatible with their corresponding PostgreSQL query parameter types and resultset column types, respectively. The following table shows the mapping between PostgreSQL and Python types.
|
|
139
|
+
|
|
140
|
+
| PostgreSQL type | Python type |
|
|
141
|
+
| ----------------- | ------------------ |
|
|
142
|
+
| `bool` | `bool` |
|
|
143
|
+
| `smallint` | `int` |
|
|
144
|
+
| `integer` | `int` |
|
|
145
|
+
| `bigint` | `int` |
|
|
146
|
+
| `real`/`float4` | `float` |
|
|
147
|
+
| `double`/`float8` | `float` |
|
|
148
|
+
| `decimal` | `Decimal` |
|
|
149
|
+
| `numeric` | `Decimal` |
|
|
150
|
+
| `date` | `date` |
|
|
151
|
+
| `time` | `time` (naive) |
|
|
152
|
+
| `timetz` | `time` (tz) |
|
|
153
|
+
| `timestamp` | `datetime` (naive) |
|
|
154
|
+
| `timestamptz` | `datetime` (tz) |
|
|
155
|
+
| `char(N)` | `str` |
|
|
156
|
+
| `varchar(N)` | `str` |
|
|
157
|
+
| `text` | `str` |
|
|
158
|
+
| `bytea` | `bytes` |
|
|
159
|
+
| `json` | `str` |
|
|
160
|
+
| `jsonb` | `str` |
|
|
161
|
+
| `uuid` | `UUID` |
|
|
162
|
+
|
|
163
|
+
### Using a SQL object
|
|
164
|
+
|
|
165
|
+
The function `sql` returns an object that derives from the base class `_SQL` and is specific to the number and types of parameters passed in `args` and `resultset`.
|
|
166
|
+
|
|
167
|
+
The following functions are available on SQL objects:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
async def execute(self, connection: Connection, *args: *P) -> None: ...
|
|
171
|
+
async def executemany(self, connection: Connection, args: Iterable[tuple[*P]]) -> None: ...
|
|
172
|
+
async def fetch(self, connection: Connection, *args: *P) -> list[tuple[*R]]: ...
|
|
173
|
+
async def fetchmany(self, connection: Connection, args: Iterable[tuple[*P]]) -> list[tuple[*R]]: ...
|
|
174
|
+
async def fetchrow(self, connection: Connection, *args: *P) -> tuple[*R] | None: ...
|
|
175
|
+
async def fetchval(self, connection: Connection, *args: *P) -> R1: ...
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`Connection` may be an `asyncpg.Connection` or an `asyncpg.pool.PoolConnectionProxy` acquired from a connection pool.
|
|
179
|
+
|
|
180
|
+
`*P` and `*R` denote several types (a type pack) corresponding to those listed in `args` and `resultset`, respectively.
|
|
181
|
+
|
|
182
|
+
Only those functions are prompted on code completion that make sense in the context of the given number of input and output arguments. Specifically, `fetchval` is available only for a single type passed to `resultset`, and `executemany` and `fetchmany` are available only if the query takes (one or more) parameters.
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
## Run-time behavior
|
|
186
|
+
|
|
187
|
+
When a call such as `sql.executemany(conn, records)` or `sql.fetch(conn, param1, param2)` is made on a `SQL` object at run time, the library invokes `connection.prepare(sql)` to create a `PreparedStatement` and compares the actual statement signature against the expected Python types. Unfortunately, PostgreSQL doesn't propagate nullability via prepared statements: resultset types that are declared as required (e.g. `T` as opposed to `T | None`) are validated at run time.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
asyncpg_typed/__init__.py,sha256=6F8tV2H1ayXFptYmsXLEi3puqKT-U904qamONeCJXUA,36489
|
|
2
|
+
asyncpg_typed/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
asyncpg_typed-0.1.0.dist-info/licenses/LICENSE,sha256=rx4jD36wX8TyLZaR2HEOJ6TphFPjKUqoCSSYWzwWNRk,1093
|
|
4
|
+
asyncpg_typed-0.1.0.dist-info/METADATA,sha256=ti6ld6HyUOodNUCmbNru0xiUu5mNHM_Z2TiDvQm4CNA,8429
|
|
5
|
+
asyncpg_typed-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
+
asyncpg_typed-0.1.0.dist-info/top_level.txt,sha256=T0X1nWnXRTi5a5oTErGy572ORDbM9UV9wfhRXWLsaoY,14
|
|
7
|
+
asyncpg_typed-0.1.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
|
8
|
+
asyncpg_typed-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Levente Hunyadi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
asyncpg_typed
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|