sql-athame 0.3.16__tar.gz → 0.4.0a2__tar.gz
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-0.3.16 → sql_athame-0.4.0a2}/PKG-INFO +6 -5
- {sql-athame-0.3.16 → sql_athame-0.4.0a2}/pyproject.toml +3 -3
- {sql-athame-0.3.16 → sql_athame-0.4.0a2}/sql_athame/base.py +8 -16
- {sql-athame-0.3.16 → sql_athame-0.4.0a2}/sql_athame/dataclasses.py +92 -18
- {sql-athame-0.3.16 → sql_athame-0.4.0a2}/sql_athame/sqlalchemy.py +0 -1
- sql-athame-0.3.16/setup.py +0 -34
- {sql-athame-0.3.16 → sql_athame-0.4.0a2}/LICENSE +0 -0
- {sql-athame-0.3.16 → sql_athame-0.4.0a2}/README.md +0 -0
- {sql-athame-0.3.16 → sql_athame-0.4.0a2}/sql_athame/__init__.py +0 -0
- {sql-athame-0.3.16 → sql_athame-0.4.0a2}/sql_athame/escape.py +0 -0
- {sql-athame-0.3.16 → sql_athame-0.4.0a2}/sql_athame/py.typed +0 -0
- {sql-athame-0.3.16 → sql_athame-0.4.0a2}/sql_athame/types.py +0 -0
@@ -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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "sql-athame"
|
3
|
-
version = "0.
|
3
|
+
version = "0.4.0-alpha-2"
|
4
4
|
description = "Python tool for slicing and dicing SQL"
|
5
5
|
authors = ["Brian Downing <bdowning@lavos.net>"]
|
6
6
|
license = "MIT"
|
@@ -12,12 +12,12 @@ repository = "https://github.com/bdowning/sql-athame"
|
|
12
12
|
asyncpg = ["asyncpg"]
|
13
13
|
|
14
14
|
[tool.poetry.dependencies]
|
15
|
-
python = "^3.
|
15
|
+
python = "^3.9"
|
16
16
|
asyncpg = { version = "*", optional = true }
|
17
17
|
typing-extensions = "*"
|
18
18
|
|
19
19
|
[tool.poetry.dev-dependencies]
|
20
|
-
black =
|
20
|
+
black = "*"
|
21
21
|
isort = "*"
|
22
22
|
pytest = "*"
|
23
23
|
mypy = "*"
|
@@ -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):
|
@@ -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-0.3.16/setup.py
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
from setuptools import setup
|
3
|
-
|
4
|
-
packages = \
|
5
|
-
['sql_athame']
|
6
|
-
|
7
|
-
package_data = \
|
8
|
-
{'': ['*']}
|
9
|
-
|
10
|
-
install_requires = \
|
11
|
-
['typing-extensions']
|
12
|
-
|
13
|
-
extras_require = \
|
14
|
-
{'asyncpg': ['asyncpg']}
|
15
|
-
|
16
|
-
setup_kwargs = {
|
17
|
-
'name': 'sql-athame',
|
18
|
-
'version': '0.3.16',
|
19
|
-
'description': 'Python tool for slicing and dicing SQL',
|
20
|
-
'long_description': '# sql-athame\n\nPython tool for slicing and dicing SQL. Its intended target is\nPostgres with _asyncpg_, though it also includes support for rendering\nto a SQLAlchemy `TextClause`.\n\n## Base query builder\n\n### Example\n\n```python\nfrom sql_athame import sql\n\n\ndef get_orders(query):\n where = []\n\n if "id" in query:\n where.append(sql("id = {}", query["id"]))\n if "eventId" in query:\n where.append(sql("event_id = {}", query["eventId"]))\n if "startTime" in query:\n where.append(sql("start_time = {}", query["startTime"]))\n if "from" in query:\n where.append(sql("start_time >= {}", query["from"]))\n if "until" in query:\n where.append(sql("start_time < {}", query["until"]))\n\n return sql("SELECT * FROM orders WHERE {}", sql.all(where))\n\n\n>>> list(get_orders({}))\n[\'SELECT * FROM orders WHERE TRUE\']\n\n>>> list(get_orders({"id": "xyzzy"}))\n[\'SELECT * FROM orders WHERE (id = $1)\', \'xyzzy\']\n\n>>> list(get_orders({"eventId": "plugh", "from": "2019-05-01", "until": "2019-08-26"}))\n[\'SELECT * FROM orders WHERE (event_id = $1) AND (start_time >= $2) AND (start_time < $3)\',\n \'plugh\',\n \'2019-05-01\',\n \'2019-08-26\']\n\n\nsuperquery = sql(\n """\n SELECT *\n FROM ({subquery}) sq\n JOIN other_table ot ON (ot.id = sq.id)\n WHERE ot.foo = {foo}\n LIMIT {limit}\n """,\n subquery=get_orders({"id": "xyzzy"}),\n foo="bork",\n limit=50,\n)\n\n\n>>> list(superquery)\n[\'SELECT * FROM (SELECT * FROM orders WHERE (id = $1)) sq JOIN other_table ot ON (ot.id = sq.id) WHERE ot.foo = $2 LIMIT $3\',\n \'xyzzy\',\n \'bork\',\n 50]\n```\n\n### API reference\n\n```python\nfrom sql_athame import sql\n```\n\n#### sql(fmt: str, \\*args, \\*\\*kwargs) -> Fragment\n\nCreates a SQL `Fragment` from the `fmt` string. The `fmt` string\ncontains literal SQL and may contain positional references, marked by\n`{}`, and named references, marked by `{name}`. Positional references\n_must_ have a matching argument in `*args`. Named references _may_\nhave a matching argument in `**kwargs`; if a named reference is not\nfullfilled by `**kwargs` it remains as a named _slot_ to be filled\nlater.\n\nIf a referenced argument is a `Fragment`, it is substituted into the\nSQL along with all of its embedded placeholders if any. Otherwise, it\nis treated as a placeholder value and substituted in place as a\nplaceholder.\n\n#### Fragment.query(self) -> Tuple[str, List[Any]]\n\nRenders a SQL `Fragment` into a query string and list of placeholder\nparameters.\n\n```python\n>>> q = sql("SELECT * FROM tbl WHERE qty > {qty}", qty=10)\n>>> q.query()\n(\'SELECT * FROM tbl WHERE qty > $1\', [10])\n```\n\nIf there are any unfilled _slots_ `ValueError` will be raised.\n\n```python\n>>> q = sql("SELECT * FROM tbl WHERE qty > {qty}")\n>>> q.query()\nValueError: Unfilled slot: \'qty\'\n>>> q.fill(qty=10).query()\n(\'SELECT * FROM tbl WHERE qty > $1\', [10])\n```\n\n#### Fragment.\\_\\_iter\\_\\_(self) -> Iterator[Any]\n\nA `Fragment` is an iterable which will return the query string\nfollowed by the placeholder parameters as returned by\n`Fragment.query(self)`. This matches the `(query, *args)` argument\npattern of the _asyncpg_ API:\n\n```python\nq = sql("SELECT * FROM tbl WHERE qty > {}", 10)\nawait conn.fetch(*q)\n```\n\n#### sql.list(parts: Iterable[Fragment]) -> Fragment\n#### sql.list(*parts: Fragment) -> Fragment\n\nCreates a SQL `Fragment` joining the fragments in `parts` together\nwith commas.\n\n```python\n>>> cols = [sql("a"), sql("b"), sql("c")]\n>>> list(sql("SELECT {cols} FROM tbl", cols=sql.list(cols)))\n[\'SELECT a, b, c FROM tbl\']\n```\n\n#### sql.all(parts: Iterable[Fragment]) -> Fragment\n#### sql.all(*parts: Fragment) -> Fragment\n\nCreates a SQL `Fragment` joining the fragments in `parts` together\nwith `AND`. If `parts` is empty, returns `TRUE`.\n\n```python\n>>> where = [sql("a = {}", 42), sql("x <> {}", "foo")]\n>>> list(sql("SELECT * FROM tbl WHERE {}", sql.all(where)))\n[\'SELECT * FROM tbl WHERE (a = $1) AND (x <> $2)\', 42, \'foo\']\n>>> list(sql("SELECT * FROM tbl WHERE {}", sql.all([])))\n[\'SELECT * FROM tbl WHERE TRUE\']\n```\n\n#### sql.any(parts: Iterable[Fragment]) -> Fragment\n#### sql.any(*parts: Fragment) -> Fragment\n\nCreates a SQL `Fragment` joining the fragments in `parts` together\nwith `OR`. If `parts` is empty, returns `FALSE`.\n\n```python\n>>> where = [sql("a = {}", 42), sql("x <> {}", "foo")]\n>>> list(sql("SELECT * FROM tbl WHERE {}", sql.any(where)))\n[\'SELECT * FROM tbl WHERE (a = $1) OR (x <> $2)\', 42, \'foo\']\n>>> list(sql("SELECT * FROM tbl WHERE {}", sql.any([])))\n[\'SELECT * FROM tbl WHERE FALSE\']\n```\n\n#### Fragment.join(self, parts: Iterable[Fragment]) -> Fragment\n\nCreates a SQL `Fragment` by joining the fragments in `parts` together\nwith `self`.\n\n```python\n>>> clauses = [sql("WHEN {} THEN {}", a, b) for a, b in ((sql("a"), 1), (sql("b"), 2))]\n>>> case = sql("CASE {clauses} END", clauses=sql(" ").join(clauses))\n>>> list(case)\n[\'CASE WHEN a THEN $1 WHEN b THEN $2 END\', 1, 2]\n```\n\n#### sql.literal(text: str) -> Fragment\n\nCreates a SQL `Fragment` with the literal SQL `text`. No substitution\nof any kind is performed. **Be very careful of SQL injection.**\n\n#### sql.identifier(name: str, prefix: Optional[str] = None) -> Fragment\n\nCreates a SQL `Fragment` with a quoted identifier name, optionally\nwith a dotted prefix.\n\n```python\n>>> list(sql("SELECT {a} FROM tbl", a=sql.identifier("a", prefix="tbl")))\n[\'SELECT "tbl"."a" FROM tbl\']\n```\n\n#### sql.value(value: Any) -> Fragment\n\nCreates a SQL `Fragment` with a single placeholder to `value`.\nEquivalent to:\n\n```python\nsql("{}", value)\n```\n\n#### sql.escape(value: Any) -> Fragment\n\nCreates a SQL `Fragment` with `value` escaped and embedded into the\nSQL. Types currently supported are strings, floats, ints, UUIDs,\n`None`, and sequences of the above.\n\n```python\n>>> list(sql("SELECT * FROM tbl WHERE qty = ANY({})", sql.escape([1, 3, 5])))\n[\'SELECT * FROM tbl WHERE qty = ANY(ARRAY[1, 3, 5])\']\n```\n\nCompare to with a placeholder:\n\n```python\n>>> list(sql("SELECT * FROM tbl WHERE qty = ANY({})", [1, 3, 5]))\n[\'SELECT * FROM tbl WHERE qty = ANY($1)\', [1, 3, 5]]\n```\n\n"Burning" an invariant value into the query can potentially help the\nquery optimizer.\n\n#### sql.slot(name: str) -> Fragment\n\nCreates a SQL `Fragment` with a single empty _slot_ named `name`.\nEquivalent to:\n\n```python\nsql("{name}")\n```\n\n#### sql.unnest(data: Iterable[Sequence[Any]], types: Iterable[str]) -> Fragment\n\nCreates a SQL `Fragment` containing an `UNNEST` expression with\nassociated data.\n\nThe data is specified in tuples (in the "several database columns"\nsense, not necessarily the Python sense) in `data`, and the tuple\nPostgres types must be specified in `types`. The data is transposed\ninto the correct form for `UNNEST` and embedded in placeholders in the\n`Fragment`.\n\n```python\n>>> list(sql("SELECT * FROM {}", sql.unnest([("a", 1), ("b", 2), ("c", 3)], ["text", "integer"])))\n[\'SELECT * FROM UNNEST($1::text[], $2::integer[])\', (\'a\', \'b\', \'c\'), (1, 2, 3)]\n```\n\n#### Fragment.fill(self, \\*\\*kwargs) -> Fragment\n\nCreates a SQL `Fragment` by filling any empty _slots_ in `self` with\n`kwargs`. Similar to `sql` subtitution, if a value is a `Fragment` it\nis substituted in-place, otherwise it is substituted as a placeholder.\n\n#### Fragment.compile(self) -> Callable[..., Fragment]\n\nCreates a function that when called with `**kwargs` will create a SQL\n`Fragment` equivalent to calling `self.fill(**kwargs)`. This is\noptimized to do as much work as possible up front and can be\nconsiderably faster if repeated often.\n\n#### Fragment.prepare(self) -> Tuple[str, Callable[..., List[Any]]]\n\nRenders `self` into a SQL query string; returns that string and a\nfunction that when called with `**kwargs` containing the unfilled\nslots of `self` will return a list containing the placeholder values\nfor `self` as filled with `**kwargs`.\n\n```python\n>>> query, query_args = sql("UPDATE tbl SET foo={foo}, bar={bar} WHERE baz < {baz}", baz=10).prepare()\n>>> query\n\'UPDATE tbl SET foo=$1, bar=$2 WHERE baz < $3\'\n>>> query_args(foo=1, bar=2)\n[1, 2, 10]\n>>> query_args(bar=42, foo=3)\n[3, 42, 10]\n```\n\nAs the name implies this is intended to be used in prepared\nstatements:\n\n```python\nquery, query_args = sql("UPDATE tbl SET foo={foo}, bar={bar} WHERE baz < {baz}", baz=10).prepare()\nstmt = await conn.prepare(query)\nawait stmt.execute(*query_args(foo=1, bar=2))\nawait stmt.execute(*query_args(bar=42, foo=3))\n```\n\n#### Fragment.sqlalchemy_text(self) -> sqlalchemy.sql.expression.TextClause\n\nRenders `self` into a SQLAlchemy `TextClause`. Placeholder values\nwill be bound with `bindparams`. Unfilled slots will be included as\nunbound parameters with their keys equal to the slot names. A\nplaceholder value may be a SQLAlchemy `BindParameter`, in which case\nits value and type are used for the parameter created by this (and the\nkey name is ignored); this allow control of the SQLAlchemy type used\nfor the parameter.\n\nRequires SQLAlchemy to be installed, otherwise raises `ImportError`.\n\n```python\n>>> query = sql("SELECT * FROM tbl WHERE column = {}", 42)\n>>> stmt = query.sqlalchemy_text()\n>>> stmt._bindparams\n{\'_arg_0_140685932797232\': BindParameter(\'_arg_0_140685932797232\', 42, type_=Integer())}\n>>> conn.execute(stmt).fetchall()\n\n>>> query = sql("SELECT * FROM tbl WHERE column = {}", bindparam("ignored", 42, type_=Float()))\n>>> stmt = query.sqlalchemy_text()\n>>> stmt._bindparams\n{\'_arg_0_140294062155280\': BindParameter(\'_arg_0_140294062155280\', 42, type_=Float())}\n>>> conn.execute(stmt).fetchall()\n\n>>> query = sql("SELECT * FROM tbl WHERE column = {val}")\n>>> stmt = query.sqlalchemy_text()\n>>> stmt._bindparams\n{\'val\': BindParameter(\'val\', None, type_=NullType())}\n>>> conn.execute(stmt, val=42).fetchall()\n```\n\n## Dataclass helpers\n\n### Example\n\n```python\nfrom dataclasses import dataclass, field\nfrom datetime import date\nfrom uuid import UUID, uuid4\nfrom typing import Optional\n\nfrom sql_athame import ModelBase, model_field_metadata as MD, sql\n\n\n@dataclass\nclass Person(ModelBase, table_name="people", primary_key="id"):\n id: UUID\n name: str\n birthday: date\n title: Optional[str] = None\n extra: Optional[dict] = field(default=None, metadata=MD(type="JSONB"))\n\n\n>>> list(Person.create_table_sql())\n[\'CREATE TABLE IF NOT EXISTS "people" (\'\n \'"id" UUID NOT NULL, \'\n \'"name" TEXT NOT NULL, \'\n \'"birthday" DATE NOT NULL, \'\n \'"title" TEXT, \'\n \'"extra" JSONB, \'\n \'PRIMARY KEY ("id"))\']\n\n>>> list(Person.select_sql(where=sql("title = {}", "Director")))\n[\'SELECT "id", "name", "birthday", "title", "extra" FROM "people" WHERE title = $1\',\n \'Director\']\n\n>>> people = [Person(uuid4(), "Bob", date(1974, 4, 3)), Person(uuid4(), "Anne", date(1985, 5, 4), extra={"awesome": "yes"})]\n>>> people\n[Person(id=UUID(\'8ecfe514-8fa3-48e5-8edb-fa810f5ed3f0\'), name=\'Bob\', birthday=datetime.date(1974, 4, 3), title=None, extra=None),\n Person(id=UUID(\'8d9bfadc-0026-4f6d-ae1b-ed89d1077f66\'), name=\'Anne\', birthday=datetime.date(1985, 5, 4), title=None, extra={\'awesome\': \'yes\'})]\n\n>>> list(Person.insert_multiple_sql(people))\n[\'INSERT INTO "people" ("id", "name", "birthday", "title", "extra")\'\n \' SELECT * FROM UNNEST($1::UUID[], $2::TEXT[], $3::DATE[], $4::TEXT[], $5::TEXT[]::JSONB[])\',\n (UUID(\'8ecfe514-8fa3-48e5-8edb-fa810f5ed3f0\'),\n UUID(\'8d9bfadc-0026-4f6d-ae1b-ed89d1077f66\')),\n (\'Bob\', \'Anne\'),\n (datetime.date(1974, 4, 3), datetime.date(1985, 5, 4)),\n (None, None),\n [None, \'{"awesome": "yes"}\']]\n\n>>> list(Person.upsert_sql(Person.insert_multiple_sql(people)))\n[\'INSERT INTO "people" ("id", "name", "birthday", "title", "extra")\'\n \' SELECT * FROM UNNEST($1::UUID[], $2::TEXT[], $3::DATE[], $4::TEXT[], $5::TEXT[]::JSONB[])\'\n \' ON CONFLICT ("id") DO UPDATE\'\n \' SET "name"=EXCLUDED."name", "birthday"=EXCLUDED."birthday", "title"=EXCLUDED."title", "extra"=EXCLUDED."extra"\',\n (UUID(\'8ecfe514-8fa3-48e5-8edb-fa810f5ed3f0\'),\n UUID(\'8d9bfadc-0026-4f6d-ae1b-ed89d1077f66\')),\n (\'Bob\', \'Anne\'),\n (datetime.date(1974, 4, 3), datetime.date(1985, 5, 4)),\n (None, None),\n [None, \'{"awesome": "yes"}\']]\n```\n\n### API reference\n\nTODO, for now read the [source](sql_athame/dataclasses.py).\n\n## License\n\nMIT.\n\n---\nCopyright (c) 2019, 2020 Brian Downing\n',
|
21
|
-
'author': 'Brian Downing',
|
22
|
-
'author_email': 'bdowning@lavos.net',
|
23
|
-
'maintainer': None,
|
24
|
-
'maintainer_email': None,
|
25
|
-
'url': 'https://github.com/bdowning/sql-athame',
|
26
|
-
'packages': packages,
|
27
|
-
'package_data': package_data,
|
28
|
-
'install_requires': install_requires,
|
29
|
-
'extras_require': extras_require,
|
30
|
-
'python_requires': '>=3.7,<4.0',
|
31
|
-
}
|
32
|
-
|
33
|
-
|
34
|
-
setup(**setup_kwargs)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|