sqlakit 0.14.0__tar.gz → 0.15.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.15.0}/PKG-INFO +1 -1
- {sqlakit-0.14.0 → sqlakit-0.15.0}/pyproject.toml +1 -1
- {sqlakit-0.14.0 → sqlakit-0.15.0}/pyproject.toml.orig +1 -1
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/_query.py +25 -2
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/asyncio/orm.py +24 -4
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/asyncio/sql.py +36 -13
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/orm.py +22 -4
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/sql.py +36 -13
- {sqlakit-0.14.0 → sqlakit-0.15.0}/LICENSE +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/README.md +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/__init__.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/_base.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/_cli.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/_db.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/_debugserver.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/_discovery.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/_model.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/_recording.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/_registry.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/_routing.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/_sql.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/asyncio/__init__.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/asyncio/_db.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/asyncio/_registry.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/debugserver.html +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/exceptions.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/py.typed +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/pytest_plugin.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/testing.py +0 -0
- {sqlakit-0.14.0 → sqlakit-0.15.0}/sqlakit/types.py +0 -0
|
@@ -112,6 +112,21 @@ RowT = TypeVar("RowT")
|
|
|
112
112
|
RowT_co = TypeVar("RowT_co", covariant=True)
|
|
113
113
|
|
|
114
114
|
|
|
115
|
+
def merged(
|
|
116
|
+
mapping: Mapping[str, Any] | None, keywords: Mapping[str, Any]
|
|
117
|
+
) -> Mapping[str, Any]:
|
|
118
|
+
"""Return the values a call was given, however they were passed.
|
|
119
|
+
|
|
120
|
+
A caller handed a mapping passes it as it is, and the keywords beside it
|
|
121
|
+
win, so one value can be replaced without copying the mapping.
|
|
122
|
+
"""
|
|
123
|
+
if not mapping:
|
|
124
|
+
return keywords
|
|
125
|
+
if not keywords:
|
|
126
|
+
return mapping
|
|
127
|
+
return {**mapping, **keywords}
|
|
128
|
+
|
|
129
|
+
|
|
115
130
|
class CaseInsensitive(sa.ColumnElement[Any]):
|
|
116
131
|
"""A column compared without regard to case, however the dialect does it.
|
|
117
132
|
|
|
@@ -466,18 +481,26 @@ class BaseQuery(Generic[ModelT]):
|
|
|
466
481
|
)
|
|
467
482
|
return query
|
|
468
483
|
|
|
469
|
-
def from_sql(
|
|
484
|
+
def from_sql(
|
|
485
|
+
self,
|
|
486
|
+
template: str,
|
|
487
|
+
/,
|
|
488
|
+
context: Mapping[str, Any] | None = None,
|
|
489
|
+
**values: Any, # noqa: ANN401
|
|
490
|
+
) -> Self:
|
|
470
491
|
"""Take the rows of a SQL template, mapped onto the model.
|
|
471
492
|
|
|
472
493
|
```python
|
|
473
494
|
User.query.from_sql("users/active.sql", team="red").all()
|
|
495
|
+
User.query.from_sql("users/active.sql", context=filters).all()
|
|
474
496
|
```
|
|
475
497
|
|
|
476
498
|
Read from the database this query runs on, and rendered for its dialect. As
|
|
477
499
|
with `from_statement`, nothing can be added afterwards and
|
|
478
500
|
``__query_filter__`` is not applied.
|
|
479
501
|
"""
|
|
480
|
-
|
|
502
|
+
rows = self.db.sql.from_file(template, context, **values)
|
|
503
|
+
return self.from_statement(rows.statement)
|
|
481
504
|
|
|
482
505
|
def where(self, *criteria: _ColumnExpressionArgument[bool]) -> Self:
|
|
483
506
|
"""Narrow the rows, as `Select.where` does."""
|
|
@@ -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,
|
|
@@ -334,19 +335,28 @@ class Query(BaseQuery[ModelT]):
|
|
|
334
335
|
scalar=len(columns) == 1,
|
|
335
336
|
)
|
|
336
337
|
|
|
337
|
-
def create(
|
|
338
|
+
def create(
|
|
339
|
+
self,
|
|
340
|
+
values: Mapping[str, Any] | None = None,
|
|
341
|
+
/,
|
|
342
|
+
**fields: Any, # noqa: ANN401
|
|
343
|
+
) -> ModelT:
|
|
338
344
|
"""Write a new row, and return it as an instance.
|
|
339
345
|
|
|
340
346
|
```python
|
|
341
347
|
user = User.query.create(name="ada", team="red")
|
|
348
|
+
user = User.query.create(payload.model_dump())
|
|
342
349
|
```
|
|
343
350
|
|
|
351
|
+
The fields are keywords, a mapping, or both, where a keyword replaces
|
|
352
|
+
the value of that name.
|
|
353
|
+
|
|
344
354
|
The row goes through the session, so defaults, relationships and the identity
|
|
345
355
|
map behave as they do for a model that saves itself. What it adds is a write
|
|
346
356
|
that needs no model layer: `Query(User, db).create(...)` works on any mapped
|
|
347
357
|
class.
|
|
348
358
|
"""
|
|
349
|
-
instance = self.model(**values)
|
|
359
|
+
instance = self.model(**merged(values, fields))
|
|
350
360
|
self.db.session.add(instance)
|
|
351
361
|
self._persist()
|
|
352
362
|
return instance
|
|
@@ -367,9 +377,17 @@ class Query(BaseQuery[ModelT]):
|
|
|
367
377
|
self._persist()
|
|
368
378
|
return len(rows)
|
|
369
379
|
|
|
370
|
-
def update(
|
|
380
|
+
def update(
|
|
381
|
+
self,
|
|
382
|
+
values: Mapping[str, Any] | None = None,
|
|
383
|
+
/,
|
|
384
|
+
**fields: Any, # noqa: ANN401
|
|
385
|
+
) -> int:
|
|
371
386
|
"""Write these values to every matching row, and return how many.
|
|
372
387
|
|
|
388
|
+
A mapping, keywords, or both: `update({"team": "green"})` and
|
|
389
|
+
`update(team="green")` write the same statement.
|
|
390
|
+
|
|
373
391
|
One statement, so the session's objects are updated from the database
|
|
374
392
|
rather than in memory. Only the narrowing carries over.
|
|
375
393
|
|
|
@@ -377,7 +395,7 @@ class Query(BaseQuery[ModelT]):
|
|
|
377
395
|
BulkQueryError: if the query carries anything a statement drops.
|
|
378
396
|
|
|
379
397
|
"""
|
|
380
|
-
result = self.db.session.execute(self._update_statement(values))
|
|
398
|
+
result = self.db.session.execute(self._update_statement(merged(values, fields)))
|
|
381
399
|
self._persist()
|
|
382
400
|
return cast("CursorResult[Any]", result).rowcount
|
|
383
401
|
|
|
@@ -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
|