sqlakit 0.14.0__tar.gz → 0.16.0__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.
- {sqlakit-0.14.0 → sqlakit-0.16.0}/PKG-INFO +1 -1
- {sqlakit-0.14.0 → sqlakit-0.16.0}/pyproject.toml +1 -1
- {sqlakit-0.14.0 → sqlakit-0.16.0}/pyproject.toml.orig +1 -1
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/_query.py +107 -5
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/asyncio/orm.py +24 -4
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/asyncio/sql.py +36 -13
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/orm.py +26 -4
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/sql.py +36 -13
- {sqlakit-0.14.0 → sqlakit-0.16.0}/LICENSE +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/README.md +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/__init__.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/_base.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/_cli.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/_db.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/_debugserver.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/_discovery.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/_model.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/_recording.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/_registry.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/_routing.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/_sql.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/asyncio/__init__.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/asyncio/_db.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/asyncio/_registry.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/debugserver.html +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/exceptions.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/py.typed +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/pytest_plugin.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/testing.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.16.0}/sqlakit/types.py +0 -0
|
@@ -24,9 +24,14 @@ from sqlalchemy.ext.compiler import compiles
|
|
|
24
24
|
from sqlalchemy.orm import (
|
|
25
25
|
InstrumentedAttribute,
|
|
26
26
|
contains_eager,
|
|
27
|
+
defer,
|
|
27
28
|
joinedload,
|
|
29
|
+
load_only,
|
|
28
30
|
selectinload,
|
|
29
31
|
subqueryload,
|
|
32
|
+
undefer,
|
|
33
|
+
undefer_group,
|
|
34
|
+
with_expression,
|
|
30
35
|
)
|
|
31
36
|
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
|
|
32
37
|
from sqlalchemy.sql import operators
|
|
@@ -112,6 +117,21 @@ RowT = TypeVar("RowT")
|
|
|
112
117
|
RowT_co = TypeVar("RowT_co", covariant=True)
|
|
113
118
|
|
|
114
119
|
|
|
120
|
+
def merged(
|
|
121
|
+
mapping: Mapping[str, Any] | None, keywords: Mapping[str, Any]
|
|
122
|
+
) -> Mapping[str, Any]:
|
|
123
|
+
"""Return the values a call was given, however they were passed.
|
|
124
|
+
|
|
125
|
+
A caller handed a mapping passes it as it is, and the keywords beside it
|
|
126
|
+
win, so one value can be replaced without copying the mapping.
|
|
127
|
+
"""
|
|
128
|
+
if not mapping:
|
|
129
|
+
return keywords
|
|
130
|
+
if not keywords:
|
|
131
|
+
return mapping
|
|
132
|
+
return {**mapping, **keywords}
|
|
133
|
+
|
|
134
|
+
|
|
115
135
|
class CaseInsensitive(sa.ColumnElement[Any]):
|
|
116
136
|
"""A column compared without regard to case, however the dialect does it.
|
|
117
137
|
|
|
@@ -466,28 +486,49 @@ class BaseQuery(Generic[ModelT]):
|
|
|
466
486
|
)
|
|
467
487
|
return query
|
|
468
488
|
|
|
469
|
-
def from_sql(
|
|
489
|
+
def from_sql(
|
|
490
|
+
self,
|
|
491
|
+
template: str,
|
|
492
|
+
/,
|
|
493
|
+
context: Mapping[str, Any] | None = None,
|
|
494
|
+
**values: Any, # noqa: ANN401
|
|
495
|
+
) -> Self:
|
|
470
496
|
"""Take the rows of a SQL template, mapped onto the model.
|
|
471
497
|
|
|
472
498
|
```python
|
|
473
499
|
User.query.from_sql("users/active.sql", team="red").all()
|
|
500
|
+
User.query.from_sql("users/active.sql", context=filters).all()
|
|
474
501
|
```
|
|
475
502
|
|
|
476
503
|
Read from the database this query runs on, and rendered for its dialect. As
|
|
477
504
|
with `from_statement`, nothing can be added afterwards and
|
|
478
505
|
``__query_filter__`` is not applied.
|
|
479
506
|
"""
|
|
480
|
-
|
|
507
|
+
rows = self.db.sql.from_file(template, context, **values)
|
|
508
|
+
return self.from_statement(rows.statement)
|
|
481
509
|
|
|
482
510
|
def where(self, *criteria: _ColumnExpressionArgument[bool]) -> Self:
|
|
483
511
|
"""Narrow the rows, as `Select.where` does."""
|
|
484
512
|
self._reject_statement("where")
|
|
485
513
|
return self.with_select(self._select.where(*criteria))
|
|
486
514
|
|
|
487
|
-
def filter_by(
|
|
488
|
-
|
|
515
|
+
def filter_by(
|
|
516
|
+
self,
|
|
517
|
+
values: Mapping[str, Any] | None = None,
|
|
518
|
+
/,
|
|
519
|
+
**fields: Any, # noqa: ANN401
|
|
520
|
+
) -> Self:
|
|
521
|
+
"""Narrow the rows by equality, as `Select.filter_by` does.
|
|
522
|
+
|
|
523
|
+
```python
|
|
524
|
+
db.query(User).filter_by(team="red")
|
|
525
|
+
db.query(User).filter_by(request.query_params)
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
The fields are keywords, a mapping, or both, as `create()` takes them.
|
|
529
|
+
"""
|
|
489
530
|
self._reject_statement("filter_by")
|
|
490
|
-
return self.with_select(self._select.filter_by(**values))
|
|
531
|
+
return self.with_select(self._select.filter_by(**merged(values, fields)))
|
|
491
532
|
|
|
492
533
|
def join(
|
|
493
534
|
self,
|
|
@@ -636,6 +677,67 @@ class BaseQuery(Generic[ModelT]):
|
|
|
636
677
|
"""Read a relationship from a join this query already makes."""
|
|
637
678
|
return self.options(_chain(contains_eager, keys))
|
|
638
679
|
|
|
680
|
+
def load_only(self, *columns: Any) -> Self: # noqa: ANN401
|
|
681
|
+
"""Load these columns of the row, and defer the rest.
|
|
682
|
+
|
|
683
|
+
```python
|
|
684
|
+
db.query(User).load_only(User.id, User.name).all()
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
The rows are still instances: a column left out is read from the
|
|
688
|
+
database when something touches it, one statement per instance, which
|
|
689
|
+
is the cost this trades the narrower row for. `only_columns` is the
|
|
690
|
+
other one: it gives the instances up and reads values.
|
|
691
|
+
"""
|
|
692
|
+
return self.options(load_only(*columns))
|
|
693
|
+
|
|
694
|
+
def defer(self, *columns: Any) -> Self: # noqa: ANN401
|
|
695
|
+
"""Leave these columns out of the row until something reads them.
|
|
696
|
+
|
|
697
|
+
```python
|
|
698
|
+
db.query(Post).defer(Post.body).all()
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
For the wide column of a table read for everything else.
|
|
702
|
+
"""
|
|
703
|
+
return self.options(*(defer(column) for column in columns))
|
|
704
|
+
|
|
705
|
+
def undefer(self, *columns: Any) -> Self: # noqa: ANN401
|
|
706
|
+
"""Load these columns with the row, though the model defers them.
|
|
707
|
+
|
|
708
|
+
```python
|
|
709
|
+
db.query(Post).undefer(Post.body).all()
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
The other side of `mapped_column(deferred=True)`, for the read that
|
|
713
|
+
wants the column after all.
|
|
714
|
+
"""
|
|
715
|
+
return self.options(*(undefer(column) for column in columns))
|
|
716
|
+
|
|
717
|
+
def undefer_group(self, name: str) -> Self:
|
|
718
|
+
"""Load the columns a model defers under this group name.
|
|
719
|
+
|
|
720
|
+
```python
|
|
721
|
+
db.query(Post).undefer_group("body").all()
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
The group is the one `mapped_column(deferred_group="body")` names, for
|
|
725
|
+
the columns a read wants together or not at all.
|
|
726
|
+
"""
|
|
727
|
+
return self.options(undefer_group(name))
|
|
728
|
+
|
|
729
|
+
def with_expression(self, key: Any, expression: Any) -> Self: # noqa: ANN401
|
|
730
|
+
"""Give a `query_expression()` attribute its value for this read.
|
|
731
|
+
|
|
732
|
+
```python
|
|
733
|
+
db.query(Post).with_expression(Post.comments, _comment_count()).all()
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
The attribute holds what this statement selects into it, so a count or
|
|
737
|
+
a window function arrives on the instance rather than beside it.
|
|
738
|
+
"""
|
|
739
|
+
return self.options(with_expression(key, expression))
|
|
740
|
+
|
|
639
741
|
def with_for_update(
|
|
640
742
|
self,
|
|
641
743
|
*,
|
|
@@ -28,6 +28,7 @@ from sqlakit._query import (
|
|
|
28
28
|
BaseQuery,
|
|
29
29
|
CursorPage,
|
|
30
30
|
Page,
|
|
31
|
+
merged,
|
|
31
32
|
one_row,
|
|
32
33
|
one_row_or_none,
|
|
33
34
|
orderable,
|
|
@@ -344,19 +345,28 @@ class Query(BaseQuery[ModelT]):
|
|
|
344
345
|
scalar=len(columns) == 1,
|
|
345
346
|
)
|
|
346
347
|
|
|
347
|
-
async def create(
|
|
348
|
+
async def create(
|
|
349
|
+
self,
|
|
350
|
+
values: Mapping[str, Any] | None = None,
|
|
351
|
+
/,
|
|
352
|
+
**fields: Any, # noqa: ANN401
|
|
353
|
+
) -> ModelT:
|
|
348
354
|
"""Write a new row, and return it as an instance.
|
|
349
355
|
|
|
350
356
|
```python
|
|
351
357
|
user = await User.query.create(name="ada", team="red")
|
|
358
|
+
user = await User.query.create(payload.model_dump())
|
|
352
359
|
```
|
|
353
360
|
|
|
361
|
+
The fields are keywords, a mapping, or both, where a keyword replaces
|
|
362
|
+
the value of that name.
|
|
363
|
+
|
|
354
364
|
The row goes through the session, so defaults, relationships and the identity
|
|
355
365
|
map behave as they do for a model that saves itself. What it adds is a write
|
|
356
366
|
that needs no model layer: `Query(User, db).create(...)` works on any mapped
|
|
357
367
|
class.
|
|
358
368
|
"""
|
|
359
|
-
instance = self.model(**values)
|
|
369
|
+
instance = self.model(**merged(values, fields))
|
|
360
370
|
self.db.session.add(instance)
|
|
361
371
|
await self._persist()
|
|
362
372
|
return instance
|
|
@@ -377,9 +387,17 @@ class Query(BaseQuery[ModelT]):
|
|
|
377
387
|
await self._persist()
|
|
378
388
|
return len(rows)
|
|
379
389
|
|
|
380
|
-
async def update(
|
|
390
|
+
async def update(
|
|
391
|
+
self,
|
|
392
|
+
values: Mapping[str, Any] | None = None,
|
|
393
|
+
/,
|
|
394
|
+
**fields: Any, # noqa: ANN401
|
|
395
|
+
) -> int:
|
|
381
396
|
"""Write these values to every matching row, and return how many.
|
|
382
397
|
|
|
398
|
+
A mapping, keywords, or both: `update({"team": "green"})` and
|
|
399
|
+
`update(team="green")` write the same statement.
|
|
400
|
+
|
|
383
401
|
One statement, so the session's objects are updated from the database
|
|
384
402
|
rather than in memory. Only the narrowing carries over.
|
|
385
403
|
|
|
@@ -387,7 +405,9 @@ class Query(BaseQuery[ModelT]):
|
|
|
387
405
|
BulkQueryError: if the query carries anything a statement drops.
|
|
388
406
|
|
|
389
407
|
"""
|
|
390
|
-
result = await self.db.session.execute(
|
|
408
|
+
result = await self.db.session.execute(
|
|
409
|
+
self._update_statement(merged(values, fields))
|
|
410
|
+
)
|
|
391
411
|
await self._persist()
|
|
392
412
|
return cast("CursorResult[Any]", result).rowcount
|
|
393
413
|
|
|
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Any, TypeVar, cast
|
|
|
5
5
|
import sqlalchemy as sa
|
|
6
6
|
from typing_extensions import Unpack
|
|
7
7
|
|
|
8
|
+
from sqlakit._query import merged
|
|
8
9
|
from sqlakit._sql import (
|
|
9
10
|
BaseSQLQuery,
|
|
10
11
|
Filter,
|
|
@@ -14,7 +15,7 @@ from sqlakit._sql import (
|
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
17
|
-
from collections.abc import AsyncIterator, Sequence
|
|
18
|
+
from collections.abc import AsyncIterator, Mapping, Sequence
|
|
18
19
|
|
|
19
20
|
from sqlalchemy.engine import Result, ScalarResult
|
|
20
21
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
|
@@ -50,27 +51,48 @@ class SQL:
|
|
|
50
51
|
def __repr__(self) -> str:
|
|
51
52
|
return f"{type(self).__name__}({self.db!r})"
|
|
52
53
|
|
|
53
|
-
def __call__(
|
|
54
|
+
def __call__(
|
|
55
|
+
self,
|
|
56
|
+
template: str,
|
|
57
|
+
/,
|
|
58
|
+
context: Mapping[str, Any] | None = None,
|
|
59
|
+
**values: Any, # noqa: ANN401
|
|
60
|
+
) -> SQLQuery:
|
|
54
61
|
"""Read the rows of a template. Short for `from_file`.
|
|
55
62
|
|
|
56
63
|
```python
|
|
57
64
|
await db.sql("users/active.sql", team="red").all()
|
|
58
65
|
```
|
|
59
66
|
"""
|
|
60
|
-
return self.from_file(template, **
|
|
61
|
-
|
|
62
|
-
def from_file(
|
|
67
|
+
return self.from_file(template, context, **values)
|
|
68
|
+
|
|
69
|
+
def from_file(
|
|
70
|
+
self,
|
|
71
|
+
template: str,
|
|
72
|
+
/,
|
|
73
|
+
context: Mapping[str, Any] | None = None,
|
|
74
|
+
**values: Any, # noqa: ANN401
|
|
75
|
+
) -> SQLQuery:
|
|
63
76
|
"""Read the rows of a template kept under the database's ``templates=``.
|
|
64
77
|
|
|
65
78
|
```python
|
|
66
79
|
await db.sql.from_file("users/active.sql", team="red").all()
|
|
80
|
+
await db.sql.from_file("users/active.sql", context=filters).all()
|
|
67
81
|
```
|
|
68
82
|
|
|
69
|
-
The keyword arguments are the template's context
|
|
83
|
+
The keyword arguments are the template's context, and ``context`` takes
|
|
84
|
+
the same values as a mapping, for values a caller was handed rather than
|
|
85
|
+
wrote. A value named `context` lives in that mapping.
|
|
70
86
|
"""
|
|
71
|
-
return SQLQuery(self.db, template, context)
|
|
72
|
-
|
|
73
|
-
def from_string(
|
|
87
|
+
return SQLQuery(self.db, template, merged(context, values))
|
|
88
|
+
|
|
89
|
+
def from_string(
|
|
90
|
+
self,
|
|
91
|
+
source: str,
|
|
92
|
+
/,
|
|
93
|
+
context: Mapping[str, Any] | None = None,
|
|
94
|
+
**values: Any, # noqa: ANN401
|
|
95
|
+
) -> SQLQuery:
|
|
74
96
|
"""Read the rows of SQL written out here rather than kept in a file.
|
|
75
97
|
|
|
76
98
|
```python
|
|
@@ -79,11 +101,12 @@ class SQL:
|
|
|
79
101
|
)
|
|
80
102
|
```
|
|
81
103
|
|
|
82
|
-
Values are named in `{{ }}` and passed by keyword, as
|
|
83
|
-
`:name` or a `?` binds nothing here, and
|
|
84
|
-
reaching the driver. It needs no
|
|
104
|
+
Values are named in `{{ }}` and passed by keyword, or as the ``context``
|
|
105
|
+
mapping, as in a template. A `:name` or a `?` binds nothing here, and
|
|
106
|
+
rendering says so rather than reaching the driver. It needs no
|
|
107
|
+
``templates=``.
|
|
85
108
|
"""
|
|
86
|
-
return SQLQuery(self.db, source, context, inline=True)
|
|
109
|
+
return SQLQuery(self.db, source, merged(context, values), inline=True)
|
|
87
110
|
|
|
88
111
|
def from_statement(self, statement: Executable) -> SQLQuery:
|
|
89
112
|
"""Read the rows of a statement built with SQLAlchemy.
|
|
@@ -29,6 +29,7 @@ from ._query import (
|
|
|
29
29
|
BaseQuery,
|
|
30
30
|
CursorPage,
|
|
31
31
|
Page,
|
|
32
|
+
merged,
|
|
32
33
|
one_row,
|
|
33
34
|
one_row_or_none,
|
|
34
35
|
orderable,
|
|
@@ -326,6 +327,10 @@ class Query(BaseQuery[ModelT]):
|
|
|
326
327
|
```python
|
|
327
328
|
names = User.query.where(User.is_active).only_columns(User.name).all()
|
|
328
329
|
```
|
|
330
|
+
|
|
331
|
+
One column arrives as values and several as tuples, and neither is an
|
|
332
|
+
instance. `load_only` is the other one: the rows stay instances, and
|
|
333
|
+
the columns it leaves out are read when something touches them.
|
|
329
334
|
"""
|
|
330
335
|
return ColumnQuery(
|
|
331
336
|
self.model,
|
|
@@ -334,19 +339,28 @@ class Query(BaseQuery[ModelT]):
|
|
|
334
339
|
scalar=len(columns) == 1,
|
|
335
340
|
)
|
|
336
341
|
|
|
337
|
-
def create(
|
|
342
|
+
def create(
|
|
343
|
+
self,
|
|
344
|
+
values: Mapping[str, Any] | None = None,
|
|
345
|
+
/,
|
|
346
|
+
**fields: Any, # noqa: ANN401
|
|
347
|
+
) -> ModelT:
|
|
338
348
|
"""Write a new row, and return it as an instance.
|
|
339
349
|
|
|
340
350
|
```python
|
|
341
351
|
user = User.query.create(name="ada", team="red")
|
|
352
|
+
user = User.query.create(payload.model_dump())
|
|
342
353
|
```
|
|
343
354
|
|
|
355
|
+
The fields are keywords, a mapping, or both, where a keyword replaces
|
|
356
|
+
the value of that name.
|
|
357
|
+
|
|
344
358
|
The row goes through the session, so defaults, relationships and the identity
|
|
345
359
|
map behave as they do for a model that saves itself. What it adds is a write
|
|
346
360
|
that needs no model layer: `Query(User, db).create(...)` works on any mapped
|
|
347
361
|
class.
|
|
348
362
|
"""
|
|
349
|
-
instance = self.model(**values)
|
|
363
|
+
instance = self.model(**merged(values, fields))
|
|
350
364
|
self.db.session.add(instance)
|
|
351
365
|
self._persist()
|
|
352
366
|
return instance
|
|
@@ -367,9 +381,17 @@ class Query(BaseQuery[ModelT]):
|
|
|
367
381
|
self._persist()
|
|
368
382
|
return len(rows)
|
|
369
383
|
|
|
370
|
-
def update(
|
|
384
|
+
def update(
|
|
385
|
+
self,
|
|
386
|
+
values: Mapping[str, Any] | None = None,
|
|
387
|
+
/,
|
|
388
|
+
**fields: Any, # noqa: ANN401
|
|
389
|
+
) -> int:
|
|
371
390
|
"""Write these values to every matching row, and return how many.
|
|
372
391
|
|
|
392
|
+
A mapping, keywords, or both: `update({"team": "green"})` and
|
|
393
|
+
`update(team="green")` write the same statement.
|
|
394
|
+
|
|
373
395
|
One statement, so the session's objects are updated from the database
|
|
374
396
|
rather than in memory. Only the narrowing carries over.
|
|
375
397
|
|
|
@@ -377,7 +399,7 @@ class Query(BaseQuery[ModelT]):
|
|
|
377
399
|
BulkQueryError: if the query carries anything a statement drops.
|
|
378
400
|
|
|
379
401
|
"""
|
|
380
|
-
result = self.db.session.execute(self._update_statement(values))
|
|
402
|
+
result = self.db.session.execute(self._update_statement(merged(values, fields)))
|
|
381
403
|
self._persist()
|
|
382
404
|
return cast("CursorResult[Any]", result).rowcount
|
|
383
405
|
|
|
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Any, TypeVar, cast
|
|
|
5
5
|
import sqlalchemy as sa
|
|
6
6
|
from typing_extensions import Unpack
|
|
7
7
|
|
|
8
|
+
from ._query import merged
|
|
8
9
|
from ._sql import (
|
|
9
10
|
BaseSQLQuery,
|
|
10
11
|
Filter,
|
|
@@ -14,7 +15,7 @@ from ._sql import (
|
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
17
|
-
from collections.abc import Iterator, Sequence
|
|
18
|
+
from collections.abc import Iterator, Mapping, Sequence
|
|
18
19
|
|
|
19
20
|
from sqlalchemy.engine import Result, ScalarResult
|
|
20
21
|
from sqlalchemy.sql import Executable
|
|
@@ -48,38 +49,60 @@ class SQL:
|
|
|
48
49
|
def __repr__(self) -> str:
|
|
49
50
|
return f"{type(self).__name__}({self.db!r})"
|
|
50
51
|
|
|
51
|
-
def __call__(
|
|
52
|
+
def __call__(
|
|
53
|
+
self,
|
|
54
|
+
template: str,
|
|
55
|
+
/,
|
|
56
|
+
context: Mapping[str, Any] | None = None,
|
|
57
|
+
**values: Any, # noqa: ANN401
|
|
58
|
+
) -> SQLQuery:
|
|
52
59
|
"""Read the rows of a template. Short for `from_file`.
|
|
53
60
|
|
|
54
61
|
```python
|
|
55
62
|
db.sql("users/active.sql", team="red").all()
|
|
56
63
|
```
|
|
57
64
|
"""
|
|
58
|
-
return self.from_file(template, **
|
|
59
|
-
|
|
60
|
-
def from_file(
|
|
65
|
+
return self.from_file(template, context, **values)
|
|
66
|
+
|
|
67
|
+
def from_file(
|
|
68
|
+
self,
|
|
69
|
+
template: str,
|
|
70
|
+
/,
|
|
71
|
+
context: Mapping[str, Any] | None = None,
|
|
72
|
+
**values: Any, # noqa: ANN401
|
|
73
|
+
) -> SQLQuery:
|
|
61
74
|
"""Read the rows of a template kept under the database's ``templates=``.
|
|
62
75
|
|
|
63
76
|
```python
|
|
64
77
|
db.sql.from_file("users/active.sql", team="red").all()
|
|
78
|
+
db.sql.from_file("users/active.sql", context=filters).all()
|
|
65
79
|
```
|
|
66
80
|
|
|
67
|
-
The keyword arguments are the template's context
|
|
81
|
+
The keyword arguments are the template's context, and ``context`` takes
|
|
82
|
+
the same values as a mapping, for values a caller was handed rather than
|
|
83
|
+
wrote. A value named `context` lives in that mapping.
|
|
68
84
|
"""
|
|
69
|
-
return SQLQuery(self.db, template, context)
|
|
70
|
-
|
|
71
|
-
def from_string(
|
|
85
|
+
return SQLQuery(self.db, template, merged(context, values))
|
|
86
|
+
|
|
87
|
+
def from_string(
|
|
88
|
+
self,
|
|
89
|
+
source: str,
|
|
90
|
+
/,
|
|
91
|
+
context: Mapping[str, Any] | None = None,
|
|
92
|
+
**values: Any, # noqa: ANN401
|
|
93
|
+
) -> SQLQuery:
|
|
72
94
|
"""Read the rows of SQL written out here rather than kept in a file.
|
|
73
95
|
|
|
74
96
|
```python
|
|
75
97
|
db.sql.from_string("SELECT id FROM users WHERE team = {{ team }}", team="red")
|
|
76
98
|
```
|
|
77
99
|
|
|
78
|
-
Values are named in `{{ }}` and passed by keyword, as
|
|
79
|
-
`:name` or a `?` binds nothing here, and
|
|
80
|
-
reaching the driver. It needs no
|
|
100
|
+
Values are named in `{{ }}` and passed by keyword, or as the ``context``
|
|
101
|
+
mapping, as in a template. A `:name` or a `?` binds nothing here, and
|
|
102
|
+
rendering says so rather than reaching the driver. It needs no
|
|
103
|
+
``templates=``.
|
|
81
104
|
"""
|
|
82
|
-
return SQLQuery(self.db, source, context, inline=True)
|
|
105
|
+
return SQLQuery(self.db, source, merged(context, values), inline=True)
|
|
83
106
|
|
|
84
107
|
def from_statement(self, statement: Executable) -> SQLQuery:
|
|
85
108
|
"""Read the rows of a statement built with SQLAlchemy.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|