sql-athame 0.3.16__py3-none-any.whl → 0.4.0a2__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.
- sql_athame/base.py +8 -16
- sql_athame/dataclasses.py +92 -18
- sql_athame/sqlalchemy.py +0 -1
- {sql_athame-0.3.16.dist-info → sql_athame-0.4.0a2.dist-info}/METADATA +6 -5
- sql_athame-0.4.0a2.dist-info/RECORD +11 -0
- {sql_athame-0.3.16.dist-info → sql_athame-0.4.0a2.dist-info}/WHEEL +1 -1
- sql_athame-0.3.16.dist-info/RECORD +0 -11
- {sql_athame-0.3.16.dist-info → sql_athame-0.4.0a2.dist-info}/LICENSE +0 -0
sql_athame/base.py
CHANGED
@@ -106,14 +106,12 @@ class Fragment:
|
|
106
106
|
@overload
|
107
107
|
def prep_query(
|
108
108
|
self, allow_slots: Literal[True]
|
109
|
-
) -> Tuple[str, List[Union[Placeholder, Slot]]]:
|
110
|
-
... # pragma: no cover
|
109
|
+
) -> Tuple[str, List[Union[Placeholder, Slot]]]: ... # pragma: no cover
|
111
110
|
|
112
111
|
@overload
|
113
112
|
def prep_query(
|
114
113
|
self, allow_slots: Literal[False] = False
|
115
|
-
) -> Tuple[str, List[Placeholder]]:
|
116
|
-
... # pragma: no cover
|
114
|
+
) -> Tuple[str, List[Placeholder]]: ... # pragma: no cover
|
117
115
|
|
118
116
|
def prep_query(self, allow_slots: bool = False) -> Tuple[str, List[Any]]:
|
119
117
|
parts: List[FlatPart] = []
|
@@ -228,12 +226,10 @@ class SQLFormatter:
|
|
228
226
|
return lit(f"{quote_identifier(name)}")
|
229
227
|
|
230
228
|
@overload
|
231
|
-
def all(self, parts: Iterable[Fragment]) -> Fragment:
|
232
|
-
... # pragma: no cover
|
229
|
+
def all(self, parts: Iterable[Fragment]) -> Fragment: ... # pragma: no cover
|
233
230
|
|
234
231
|
@overload
|
235
|
-
def all(self, *parts: Fragment) -> Fragment:
|
236
|
-
... # pragma: no cover
|
232
|
+
def all(self, *parts: Fragment) -> Fragment: ... # pragma: no cover
|
237
233
|
|
238
234
|
def all(self, *parts) -> Fragment: # type: ignore
|
239
235
|
if parts and not isinstance(parts[0], Fragment):
|
@@ -241,12 +237,10 @@ class SQLFormatter:
|
|
241
237
|
return any_all(list(parts), "AND", "TRUE")
|
242
238
|
|
243
239
|
@overload
|
244
|
-
def any(self, parts: Iterable[Fragment]) -> Fragment:
|
245
|
-
... # pragma: no cover
|
240
|
+
def any(self, parts: Iterable[Fragment]) -> Fragment: ... # pragma: no cover
|
246
241
|
|
247
242
|
@overload
|
248
|
-
def any(self, *parts: Fragment) -> Fragment:
|
249
|
-
... # pragma: no cover
|
243
|
+
def any(self, *parts: Fragment) -> Fragment: ... # pragma: no cover
|
250
244
|
|
251
245
|
def any(self, *parts) -> Fragment: # type: ignore
|
252
246
|
if parts and not isinstance(parts[0], Fragment):
|
@@ -254,12 +248,10 @@ class SQLFormatter:
|
|
254
248
|
return any_all(list(parts), "OR", "FALSE")
|
255
249
|
|
256
250
|
@overload
|
257
|
-
def list(self, parts: Iterable[Fragment]) -> Fragment:
|
258
|
-
... # pragma: no cover
|
251
|
+
def list(self, parts: Iterable[Fragment]) -> Fragment: ... # pragma: no cover
|
259
252
|
|
260
253
|
@overload
|
261
|
-
def list(self, *parts: Fragment) -> Fragment:
|
262
|
-
... # pragma: no cover
|
254
|
+
def list(self, *parts: Fragment) -> Fragment: ... # pragma: no cover
|
263
255
|
|
264
256
|
def list(self, *parts) -> Fragment: # type: ignore
|
265
257
|
if parts and not isinstance(parts[0], Fragment):
|
sql_athame/dataclasses.py
CHANGED
@@ -54,7 +54,7 @@ def model_field_metadata(
|
|
54
54
|
def model_field(
|
55
55
|
*, type: str, constraints: Union[str, Iterable[str]] = (), **kwargs: Any
|
56
56
|
) -> Any:
|
57
|
-
return field(**kwargs, metadata=model_field_metadata(type, constraints))
|
57
|
+
return field(**kwargs, metadata=model_field_metadata(type, constraints))
|
58
58
|
|
59
59
|
|
60
60
|
sql_create_type_map = {
|
@@ -100,17 +100,36 @@ class ModelBase(Mapping[str, Any]):
|
|
100
100
|
_cache: Dict[tuple, Any]
|
101
101
|
table_name: str
|
102
102
|
primary_key_names: Tuple[str, ...]
|
103
|
+
array_safe_insert: bool
|
103
104
|
|
104
105
|
def __init_subclass__(
|
105
|
-
cls,
|
106
|
+
cls,
|
107
|
+
*,
|
108
|
+
table_name: str,
|
109
|
+
primary_key: Union[FieldNames, str] = (),
|
110
|
+
insert_multiple_mode: str = "unnest",
|
111
|
+
**kwargs: Any,
|
106
112
|
):
|
107
113
|
cls._cache = {}
|
108
114
|
cls.table_name = table_name
|
115
|
+
if insert_multiple_mode == "array_safe":
|
116
|
+
cls.array_safe_insert = True
|
117
|
+
elif insert_multiple_mode == "unnest":
|
118
|
+
cls.array_safe_insert = False
|
119
|
+
else:
|
120
|
+
raise ValueError("Unknown `insert_multiple_mode`")
|
109
121
|
if isinstance(primary_key, str):
|
110
122
|
cls.primary_key_names = (primary_key,)
|
111
123
|
else:
|
112
124
|
cls.primary_key_names = tuple(primary_key)
|
113
125
|
|
126
|
+
@classmethod
|
127
|
+
def _fields(cls):
|
128
|
+
# wrapper to ignore typing weirdness: 'Argument 1 to "fields"
|
129
|
+
# has incompatible type "..."; expected "DataclassInstance |
|
130
|
+
# type[DataclassInstance]"'
|
131
|
+
return fields(cls) # type: ignore
|
132
|
+
|
114
133
|
@classmethod
|
115
134
|
def _cached(cls, key: tuple, thunk: Callable[[], U]) -> U:
|
116
135
|
try:
|
@@ -139,7 +158,7 @@ class ModelBase(Mapping[str, Any]):
|
|
139
158
|
try:
|
140
159
|
return cls._column_info[column] # type: ignore
|
141
160
|
except AttributeError:
|
142
|
-
cls._column_info = {f.name: column_info_for_field(f) for f in
|
161
|
+
cls._column_info = {f.name: column_info_for_field(f) for f in cls._fields()}
|
143
162
|
return cls._column_info[column]
|
144
163
|
|
145
164
|
@classmethod
|
@@ -152,7 +171,7 @@ class ModelBase(Mapping[str, Any]):
|
|
152
171
|
|
153
172
|
@classmethod
|
154
173
|
def field_names(cls, *, exclude: FieldNamesSet = ()) -> List[str]:
|
155
|
-
return [f.name for f in
|
174
|
+
return [f.name for f in cls._fields() if f.name not in exclude]
|
156
175
|
|
157
176
|
@classmethod
|
158
177
|
def field_names_sql(
|
@@ -171,7 +190,7 @@ class ModelBase(Mapping[str, Any]):
|
|
171
190
|
) -> Callable[[T], List[Any]]:
|
172
191
|
env: Dict[str, Any] = dict()
|
173
192
|
func = ["def get_field_values(self): return ["]
|
174
|
-
for f in
|
193
|
+
for f in cls._fields():
|
175
194
|
if f.name not in exclude:
|
176
195
|
func.append(f"self.{f.name},")
|
177
196
|
func += ["]"]
|
@@ -200,23 +219,23 @@ class ModelBase(Mapping[str, Any]):
|
|
200
219
|
def from_tuple(
|
201
220
|
cls: Type[T], tup: tuple, *, offset: int = 0, exclude: FieldNamesSet = ()
|
202
221
|
) -> T:
|
203
|
-
names = (f.name for f in
|
222
|
+
names = (f.name for f in cls._fields() if f.name not in exclude)
|
204
223
|
kwargs = {name: tup[offset] for offset, name in enumerate(names, start=offset)}
|
205
|
-
return cls(**kwargs)
|
224
|
+
return cls(**kwargs)
|
206
225
|
|
207
226
|
@classmethod
|
208
227
|
def from_dict(
|
209
228
|
cls: Type[T], dct: Dict[str, Any], *, exclude: FieldNamesSet = ()
|
210
229
|
) -> T:
|
211
|
-
names = {f.name for f in
|
230
|
+
names = {f.name for f in cls._fields() if f.name not in exclude}
|
212
231
|
kwargs = {k: v for k, v in dct.items() if k in names}
|
213
|
-
return cls(**kwargs)
|
232
|
+
return cls(**kwargs)
|
214
233
|
|
215
234
|
@classmethod
|
216
235
|
def ensure_model(cls: Type[T], row: Union[T, Mapping[str, Any]]) -> T:
|
217
236
|
if isinstance(row, cls):
|
218
237
|
return row
|
219
|
-
return cls(**row)
|
238
|
+
return cls(**row)
|
220
239
|
|
221
240
|
@classmethod
|
222
241
|
def create_table_sql(cls) -> Fragment:
|
@@ -226,7 +245,7 @@ class ModelBase(Mapping[str, Any]):
|
|
226
245
|
sql.identifier(f.name),
|
227
246
|
sql.literal(cls.column_info(f.name).create_table_string()),
|
228
247
|
)
|
229
|
-
for f in
|
248
|
+
for f in cls._fields()
|
230
249
|
]
|
231
250
|
if cls.primary_key_names:
|
232
251
|
entries += [sql("PRIMARY KEY ({})", sql.list(cls.primary_key_names_sql()))]
|
@@ -278,7 +297,7 @@ class ModelBase(Mapping[str, Any]):
|
|
278
297
|
*cls.select_sql(order_by=order_by, for_update=for_update, where=where),
|
279
298
|
prefetch=prefetch,
|
280
299
|
):
|
281
|
-
yield cls(**row)
|
300
|
+
yield cls(**row)
|
282
301
|
|
283
302
|
@classmethod
|
284
303
|
async def select(
|
@@ -289,7 +308,7 @@ class ModelBase(Mapping[str, Any]):
|
|
289
308
|
where: Where = (),
|
290
309
|
) -> List[T]:
|
291
310
|
return [
|
292
|
-
cls(**row)
|
311
|
+
cls(**row)
|
293
312
|
for row in await connection_or_pool.fetch(
|
294
313
|
*cls.select_sql(order_by=order_by, for_update=for_update, where=where)
|
295
314
|
)
|
@@ -310,7 +329,7 @@ class ModelBase(Mapping[str, Any]):
|
|
310
329
|
cls: Type[T], connection_or_pool: Union[Connection, Pool], **kwargs: Any
|
311
330
|
) -> T:
|
312
331
|
row = await connection_or_pool.fetchrow(*cls.create_sql(**kwargs))
|
313
|
-
return cls(**row)
|
332
|
+
return cls(**row)
|
314
333
|
|
315
334
|
def insert_sql(self, exclude: FieldNamesSet = ()) -> Fragment:
|
316
335
|
cached = self._cached(
|
@@ -400,26 +419,76 @@ class ModelBase(Mapping[str, Any]):
|
|
400
419
|
)
|
401
420
|
|
402
421
|
@classmethod
|
403
|
-
|
422
|
+
def insert_multiple_array_safe_sql(cls: Type[T], rows: Iterable[T]) -> Fragment:
|
423
|
+
return sql(
|
424
|
+
"INSERT INTO {table} ({fields}) VALUES {values}",
|
425
|
+
table=cls.table_name_sql(),
|
426
|
+
fields=sql.list(cls.field_names_sql()),
|
427
|
+
values=sql.list(
|
428
|
+
sql("({})", sql.list(row.field_values_sql(default_none=True)))
|
429
|
+
for row in rows
|
430
|
+
),
|
431
|
+
)
|
432
|
+
|
433
|
+
@classmethod
|
434
|
+
async def insert_multiple_unnest(
|
404
435
|
cls: Type[T], connection_or_pool: Union[Connection, Pool], rows: Iterable[T]
|
405
436
|
) -> str:
|
406
437
|
return await connection_or_pool.execute(*cls.insert_multiple_sql(rows))
|
407
438
|
|
408
439
|
@classmethod
|
409
|
-
async def
|
440
|
+
async def insert_multiple_array_safe(
|
441
|
+
cls: Type[T], connection_or_pool: Union[Connection, Pool], rows: Iterable[T]
|
442
|
+
) -> str:
|
443
|
+
for chunk in chunked(rows, 100):
|
444
|
+
last = await connection_or_pool.execute(
|
445
|
+
*cls.insert_multiple_array_safe_sql(chunk)
|
446
|
+
)
|
447
|
+
return last
|
448
|
+
|
449
|
+
@classmethod
|
450
|
+
async def insert_multiple(
|
451
|
+
cls: Type[T], connection_or_pool: Union[Connection, Pool], rows: Iterable[T]
|
452
|
+
) -> str:
|
453
|
+
if cls.array_safe_insert:
|
454
|
+
return await cls.insert_multiple_array_safe(connection_or_pool, rows)
|
455
|
+
else:
|
456
|
+
return await cls.insert_multiple_unnest(connection_or_pool, rows)
|
457
|
+
|
458
|
+
@classmethod
|
459
|
+
async def upsert_multiple_unnest(
|
410
460
|
cls: Type[T], connection_or_pool: Union[Connection, Pool], rows: Iterable[T]
|
411
461
|
) -> str:
|
412
462
|
return await connection_or_pool.execute(
|
413
463
|
*cls.upsert_sql(cls.insert_multiple_sql(rows))
|
414
464
|
)
|
415
465
|
|
466
|
+
@classmethod
|
467
|
+
async def upsert_multiple_array_safe(
|
468
|
+
cls: Type[T], connection_or_pool: Union[Connection, Pool], rows: Iterable[T]
|
469
|
+
) -> str:
|
470
|
+
for chunk in chunked(rows, 100):
|
471
|
+
last = await connection_or_pool.execute(
|
472
|
+
*cls.upsert_sql(cls.insert_multiple_array_safe_sql(chunk))
|
473
|
+
)
|
474
|
+
return last
|
475
|
+
|
476
|
+
@classmethod
|
477
|
+
async def upsert_multiple(
|
478
|
+
cls: Type[T], connection_or_pool: Union[Connection, Pool], rows: Iterable[T]
|
479
|
+
) -> str:
|
480
|
+
if cls.array_safe_insert:
|
481
|
+
return await cls.upsert_multiple_array_safe(connection_or_pool, rows)
|
482
|
+
else:
|
483
|
+
return await cls.upsert_multiple_unnest(connection_or_pool, rows)
|
484
|
+
|
416
485
|
@classmethod
|
417
486
|
def _get_equal_ignoring_fn(
|
418
487
|
cls: Type[T], ignore: FieldNamesSet = ()
|
419
488
|
) -> Callable[[T, T], bool]:
|
420
489
|
env: Dict[str, Any] = dict()
|
421
490
|
func = ["def equal_ignoring(a, b):"]
|
422
|
-
for f in
|
491
|
+
for f in cls._fields():
|
423
492
|
if f.name not in ignore:
|
424
493
|
func.append(f" if a.{f.name} != b.{f.name}: return False")
|
425
494
|
func += [" return True"]
|
@@ -473,7 +542,7 @@ class ModelBase(Mapping[str, Any]):
|
|
473
542
|
"def differences_ignoring(a, b):",
|
474
543
|
" diffs = []",
|
475
544
|
]
|
476
|
-
for f in
|
545
|
+
for f in cls._fields():
|
477
546
|
if f.name not in ignore:
|
478
547
|
func.append(
|
479
548
|
f" if a.{f.name} != b.{f.name}: diffs.append({repr(f.name)})"
|
@@ -523,3 +592,8 @@ class ModelBase(Mapping[str, Any]):
|
|
523
592
|
await cls.delete_multiple(connection, deleted)
|
524
593
|
|
525
594
|
return created, updated_triples, deleted
|
595
|
+
|
596
|
+
|
597
|
+
def chunked(lst, n):
|
598
|
+
for i in range(0, len(lst), n):
|
599
|
+
yield lst[i : i + n]
|
sql_athame/sqlalchemy.py
CHANGED
@@ -1,19 +1,20 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql-athame
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0a2
|
4
4
|
Summary: Python tool for slicing and dicing SQL
|
5
5
|
Home-page: https://github.com/bdowning/sql-athame
|
6
6
|
License: MIT
|
7
7
|
Author: Brian Downing
|
8
8
|
Author-email: bdowning@lavos.net
|
9
|
-
Requires-Python: >=3.
|
9
|
+
Requires-Python: >=3.9,<4.0
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
12
|
-
Classifier: Programming Language :: Python :: 3.7
|
13
|
-
Classifier: Programming Language :: Python :: 3.8
|
14
12
|
Classifier: Programming Language :: Python :: 3.9
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
15
16
|
Provides-Extra: asyncpg
|
16
|
-
Requires-Dist: asyncpg; extra == "asyncpg"
|
17
|
+
Requires-Dist: asyncpg ; extra == "asyncpg"
|
17
18
|
Requires-Dist: typing-extensions
|
18
19
|
Project-URL: Repository, https://github.com/bdowning/sql-athame
|
19
20
|
Description-Content-Type: text/markdown
|
@@ -0,0 +1,11 @@
|
|
1
|
+
sql_athame/__init__.py,sha256=rzUQcbzmj3qkPZpL9jI_ALTRv-e1pAV4jSCryWkutlk,130
|
2
|
+
sql_athame/base.py,sha256=fSnHQhh5ULeJ5q32RVUAvpWtF0qoY61B2gEEP59Nrpo,10350
|
3
|
+
sql_athame/dataclasses.py,sha256=oo1NnUrd-THKVRtTnAK08P4UnFIUBsVJHrOO5tgfi_I,19697
|
4
|
+
sql_athame/escape.py,sha256=LXExbiYtc407yDU4vPieyY2Pq5nypsJFfBc_2-gsbUg,743
|
5
|
+
sql_athame/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
+
sql_athame/sqlalchemy.py,sha256=c-pCLE11hTh5I19rY1Vp5E7P7lAaj9i-i7ko2L8rlF4,1305
|
7
|
+
sql_athame/types.py,sha256=7P4OyY0ezRlb2UDD9lpdXiLChnhQcBvHWaG_PKy3jmE,412
|
8
|
+
sql_athame-0.4.0a2.dist-info/LICENSE,sha256=xqV29vPFqITcKifYrGPgVIBjq4fdmLSwY3gRUtDKafg,1076
|
9
|
+
sql_athame-0.4.0a2.dist-info/METADATA,sha256=_2r7ZYOb5St8muq7EnfoiNEQJA1eskEibMjn7JHkcxI,12845
|
10
|
+
sql_athame-0.4.0a2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
11
|
+
sql_athame-0.4.0a2.dist-info/RECORD,,
|
@@ -1,11 +0,0 @@
|
|
1
|
-
sql_athame/__init__.py,sha256=rzUQcbzmj3qkPZpL9jI_ALTRv-e1pAV4jSCryWkutlk,130
|
2
|
-
sql_athame/base.py,sha256=JIe2oUtADAuF1wAsrK2-VoA8ZbcXIECMNEamdd7f8-Y,10414
|
3
|
-
sql_athame/dataclasses.py,sha256=d_QRwLulhxtRKtOlUkjuBcMYv_Zd2ioWLr8y-yP2GJ4,17208
|
4
|
-
sql_athame/escape.py,sha256=LXExbiYtc407yDU4vPieyY2Pq5nypsJFfBc_2-gsbUg,743
|
5
|
-
sql_athame/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
sql_athame/sqlalchemy.py,sha256=3ivXs3GqoGebJig20XIaCZQcdccB-dOrLVq2uztIpb8,1306
|
7
|
-
sql_athame/types.py,sha256=7P4OyY0ezRlb2UDD9lpdXiLChnhQcBvHWaG_PKy3jmE,412
|
8
|
-
sql_athame-0.3.16.dist-info/LICENSE,sha256=xqV29vPFqITcKifYrGPgVIBjq4fdmLSwY3gRUtDKafg,1076
|
9
|
-
sql_athame-0.3.16.dist-info/WHEEL,sha256=V7iVckP-GYreevsTDnv1eAinQt_aArwnAxmnP0gygBY,83
|
10
|
-
sql_athame-0.3.16.dist-info/METADATA,sha256=9gdIZYfTND8xXnu0-5LaEYE5KdlvtNrO5Fu7TELIbjo,12790
|
11
|
-
sql_athame-0.3.16.dist-info/RECORD,,
|
File without changes
|