sqlakit 0.7.0__tar.gz → 0.7.2__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.7.0 → sqlakit-0.7.2}/PKG-INFO +1 -1
- {sqlakit-0.7.0 → sqlakit-0.7.2}/pyproject.toml +1 -1
- {sqlakit-0.7.0 → sqlakit-0.7.2}/pyproject.toml.orig +1 -1
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/__init__.py +2 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/_query.py +31 -1
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/asyncio/sql.py +3 -3
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/exceptions.py +13 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/sql.py +3 -3
- {sqlakit-0.7.0 → sqlakit-0.7.2}/LICENSE +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/README.md +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/_base.py +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/_db.py +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/_discovery.py +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/_model.py +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/_recording.py +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/_registry.py +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/_routing.py +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/_sql.py +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/asyncio/__init__.py +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/asyncio/_db.py +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/asyncio/_registry.py +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/asyncio/orm.py +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/orm.py +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/py.typed +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/testing.py +0 -0
- {sqlakit-0.7.0 → sqlakit-0.7.2}/sqlakit/types.py +0 -0
|
@@ -18,6 +18,7 @@ from .exceptions import (
|
|
|
18
18
|
AsyncFilterError,
|
|
19
19
|
BulkQueryError,
|
|
20
20
|
ConflictingDatabaseUrlError,
|
|
21
|
+
ConflictingJoinError,
|
|
21
22
|
DatabaseAlreadyConfiguredError,
|
|
22
23
|
DatabaseNotConfiguredError,
|
|
23
24
|
DefaultAliasError,
|
|
@@ -69,6 +70,7 @@ __all__ = [
|
|
|
69
70
|
"AsyncFilterError",
|
|
70
71
|
"BulkQueryError",
|
|
71
72
|
"ConflictingDatabaseUrlError",
|
|
73
|
+
"ConflictingJoinError",
|
|
72
74
|
"CursorPage",
|
|
73
75
|
"Database",
|
|
74
76
|
"DatabaseAlreadyConfiguredError",
|
|
@@ -37,6 +37,7 @@ from typing_extensions import TypeVar
|
|
|
37
37
|
from ._model import resolve_alias, soft_delete_column
|
|
38
38
|
from .exceptions import (
|
|
39
39
|
BulkQueryError,
|
|
40
|
+
ConflictingJoinError,
|
|
40
41
|
InstanceNotFoundError,
|
|
41
42
|
InvalidCursorError,
|
|
42
43
|
InvalidNullsError,
|
|
@@ -465,7 +466,7 @@ class BaseQuery(Generic[ModelT]):
|
|
|
465
466
|
)
|
|
466
467
|
return query
|
|
467
468
|
|
|
468
|
-
def from_sql(self, template: str, **context: Any) -> Self: # noqa: ANN401
|
|
469
|
+
def from_sql(self, template: str, /, **context: Any) -> Self: # noqa: ANN401
|
|
469
470
|
"""Take the rows of a SQL template, mapped onto the model.
|
|
470
471
|
|
|
471
472
|
```python
|
|
@@ -1015,11 +1016,13 @@ def ordered(
|
|
|
1015
1016
|
else {_field_named(one, fields) for one in ignore_case}
|
|
1016
1017
|
)
|
|
1017
1018
|
clauses = []
|
|
1019
|
+
joined: dict[str, Any] = {}
|
|
1018
1020
|
for criterion in named:
|
|
1019
1021
|
clause, join = _ordering_for(criterion, fields, ignore_case=folded)
|
|
1020
1022
|
clauses.append(_with_nulls(clause, nulls))
|
|
1021
1023
|
if join is not None:
|
|
1022
1024
|
target, onclause, outer = join
|
|
1025
|
+
_reject_conflicting_join(joined, target, onclause)
|
|
1023
1026
|
if not _is_joined(select, target):
|
|
1024
1027
|
select = select.join(target, onclause, isouter=outer)
|
|
1025
1028
|
return select.order_by(*clauses)
|
|
@@ -1176,6 +1179,33 @@ def _join_for(field: Any) -> tuple[Any, None, bool] | None: # noqa: ANN401
|
|
|
1176
1179
|
return None if table is None else (table, None, True)
|
|
1177
1180
|
|
|
1178
1181
|
|
|
1182
|
+
def _reject_conflicting_join(
|
|
1183
|
+
joined: dict[str, Any],
|
|
1184
|
+
target: Any, # noqa: ANN401
|
|
1185
|
+
onclause: Any, # noqa: ANN401
|
|
1186
|
+
) -> None:
|
|
1187
|
+
"""Refuse a second join of one table on another condition.
|
|
1188
|
+
|
|
1189
|
+
A statement joins a table once, so the second condition would be dropped
|
|
1190
|
+
and the field would order by the first one's rows.
|
|
1191
|
+
|
|
1192
|
+
Raises:
|
|
1193
|
+
ConflictingJoinError: if the table is already joined on something else.
|
|
1194
|
+
|
|
1195
|
+
"""
|
|
1196
|
+
identity = _join_identity(target)
|
|
1197
|
+
if identity is None:
|
|
1198
|
+
return
|
|
1199
|
+
if identity not in joined:
|
|
1200
|
+
joined[identity] = onclause
|
|
1201
|
+
return
|
|
1202
|
+
first = joined[identity]
|
|
1203
|
+
if first is None or onclause is None:
|
|
1204
|
+
return
|
|
1205
|
+
if not first.compare(onclause):
|
|
1206
|
+
raise ConflictingJoinError(identity)
|
|
1207
|
+
|
|
1208
|
+
|
|
1179
1209
|
def _is_joined(select: sa.Select[Any], target: Any) -> bool: # noqa: ANN401
|
|
1180
1210
|
"""Whether this statement already reaches what a field needs."""
|
|
1181
1211
|
wanted = _join_identity(target)
|
|
@@ -46,7 +46,7 @@ class SQL:
|
|
|
46
46
|
def __repr__(self) -> str:
|
|
47
47
|
return f"{type(self).__name__}({self.db!r})"
|
|
48
48
|
|
|
49
|
-
def __call__(self, template: str, **context: Any) -> SQLQuery: # noqa: ANN401
|
|
49
|
+
def __call__(self, template: str, /, **context: Any) -> SQLQuery: # noqa: ANN401
|
|
50
50
|
"""Read the rows of a template. Short for `from_file`.
|
|
51
51
|
|
|
52
52
|
```python
|
|
@@ -55,7 +55,7 @@ class SQL:
|
|
|
55
55
|
"""
|
|
56
56
|
return self.from_file(template, **context)
|
|
57
57
|
|
|
58
|
-
def from_file(self, template: str, **context: Any) -> SQLQuery: # noqa: ANN401
|
|
58
|
+
def from_file(self, template: str, /, **context: Any) -> SQLQuery: # noqa: ANN401
|
|
59
59
|
"""Read the rows of a template kept under the database's ``templates=``.
|
|
60
60
|
|
|
61
61
|
```python
|
|
@@ -66,7 +66,7 @@ class SQL:
|
|
|
66
66
|
"""
|
|
67
67
|
return SQLQuery(self.db, template, context)
|
|
68
68
|
|
|
69
|
-
def from_string(self, source: str, **context: Any) -> SQLQuery: # noqa: ANN401
|
|
69
|
+
def from_string(self, source: str, /, **context: Any) -> SQLQuery: # noqa: ANN401
|
|
70
70
|
"""Read the rows of SQL written out here rather than kept in a file.
|
|
71
71
|
|
|
72
72
|
```python
|
|
@@ -9,6 +9,7 @@ __all__ = [
|
|
|
9
9
|
"AsyncFilterError",
|
|
10
10
|
"BulkQueryError",
|
|
11
11
|
"ConflictingDatabaseUrlError",
|
|
12
|
+
"ConflictingJoinError",
|
|
12
13
|
"DatabaseAlreadyConfiguredError",
|
|
13
14
|
"DatabaseNotConfiguredError",
|
|
14
15
|
"DefaultAliasError",
|
|
@@ -426,6 +427,18 @@ class InvalidOrderFieldError(SQLAKitError, TypeError):
|
|
|
426
427
|
)
|
|
427
428
|
|
|
428
429
|
|
|
430
|
+
class ConflictingJoinError(SQLAKitError, TypeError):
|
|
431
|
+
"""Raised when two ordering fields join one table on different conditions."""
|
|
432
|
+
|
|
433
|
+
def __init__(self, table: str) -> None:
|
|
434
|
+
self.table = table
|
|
435
|
+
super().__init__(
|
|
436
|
+
f"Two ordering fields join `{table}` on different conditions, and a "
|
|
437
|
+
f"statement joins it once. Alias it with `sqlalchemy.orm.aliased` so "
|
|
438
|
+
f"that each field joins an alias of its own."
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
|
|
429
442
|
class InvalidNullsError(SQLAKitError, ValueError):
|
|
430
443
|
"""Raised when ``order_by`` is told to put the nulls somewhere else.
|
|
431
444
|
|
|
@@ -45,7 +45,7 @@ class SQL:
|
|
|
45
45
|
def __repr__(self) -> str:
|
|
46
46
|
return f"{type(self).__name__}({self.db!r})"
|
|
47
47
|
|
|
48
|
-
def __call__(self, template: str, **context: Any) -> SQLQuery: # noqa: ANN401
|
|
48
|
+
def __call__(self, template: str, /, **context: Any) -> SQLQuery: # noqa: ANN401
|
|
49
49
|
"""Read the rows of a template. Short for `from_file`.
|
|
50
50
|
|
|
51
51
|
```python
|
|
@@ -54,7 +54,7 @@ class SQL:
|
|
|
54
54
|
"""
|
|
55
55
|
return self.from_file(template, **context)
|
|
56
56
|
|
|
57
|
-
def from_file(self, template: str, **context: Any) -> SQLQuery: # noqa: ANN401
|
|
57
|
+
def from_file(self, template: str, /, **context: Any) -> SQLQuery: # noqa: ANN401
|
|
58
58
|
"""Read the rows of a template kept under the database's ``templates=``.
|
|
59
59
|
|
|
60
60
|
```python
|
|
@@ -65,7 +65,7 @@ class SQL:
|
|
|
65
65
|
"""
|
|
66
66
|
return SQLQuery(self.db, template, context)
|
|
67
67
|
|
|
68
|
-
def from_string(self, source: str, **context: Any) -> SQLQuery: # noqa: ANN401
|
|
68
|
+
def from_string(self, source: str, /, **context: Any) -> SQLQuery: # noqa: ANN401
|
|
69
69
|
"""Read the rows of SQL written out here rather than kept in a file.
|
|
70
70
|
|
|
71
71
|
```python
|
|
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
|