sqlakit 0.7.2__tar.gz → 0.7.4__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.
Files changed (26) hide show
  1. {sqlakit-0.7.2 → sqlakit-0.7.4}/PKG-INFO +1 -1
  2. {sqlakit-0.7.2 → sqlakit-0.7.4}/pyproject.toml +1 -1
  3. {sqlakit-0.7.2 → sqlakit-0.7.4}/pyproject.toml.orig +1 -1
  4. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/_query.py +34 -10
  5. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/asyncio/orm.py +1 -0
  6. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/orm.py +1 -0
  7. {sqlakit-0.7.2 → sqlakit-0.7.4}/LICENSE +0 -0
  8. {sqlakit-0.7.2 → sqlakit-0.7.4}/README.md +0 -0
  9. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/__init__.py +0 -0
  10. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/_base.py +0 -0
  11. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/_db.py +0 -0
  12. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/_discovery.py +0 -0
  13. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/_model.py +0 -0
  14. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/_recording.py +0 -0
  15. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/_registry.py +0 -0
  16. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/_routing.py +0 -0
  17. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/_sql.py +0 -0
  18. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/asyncio/__init__.py +0 -0
  19. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/asyncio/_db.py +0 -0
  20. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/asyncio/_registry.py +0 -0
  21. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/asyncio/sql.py +0 -0
  22. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/exceptions.py +0 -0
  23. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/py.typed +0 -0
  24. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/sql.py +0 -0
  25. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/testing.py +0 -0
  26. {sqlakit-0.7.2 → sqlakit-0.7.4}/sqlakit/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlakit
3
- Version: 0.7.2
3
+ Version: 0.7.4
4
4
  Summary: A toolkit for SQLAlchemy applications.
5
5
  Keywords: sqlalchemy,database,orm,sql,asyncio
6
6
  Author: Anton Ruhlov
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sqlakit"
3
- version = "0.7.2"
3
+ version = "0.7.4"
4
4
  description = "A toolkit for SQLAlchemy applications."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sqlakit"
3
- version = "0.7.2"
3
+ version = "0.7.4"
4
4
  description = "A toolkit for SQLAlchemy applications."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -587,6 +587,7 @@ class BaseQuery(Generic[ModelT]):
587
587
  criteria,
588
588
  ignore_case=ignore_case,
589
589
  nulls=nulls,
590
+ model=self.model,
590
591
  )
591
592
  )
592
593
 
@@ -990,13 +991,14 @@ def _mapped_column(model: type[Any], name: str) -> Any: # noqa: ANN401
990
991
  return column
991
992
 
992
993
 
993
- def ordered(
994
+ def ordered( # noqa: PLR0913 - the shape of an ordering, not a call site
994
995
  select: sa.Select[Any],
995
996
  fields: Mapping[str, Any],
996
997
  criteria: Iterable[Any],
997
998
  *,
998
999
  ignore_case: bool | Sequence[str] = False,
999
1000
  nulls: str | None = None,
1001
+ model: type[Any] | None = None,
1000
1002
  ) -> sa.Select[Any]:
1001
1003
  """Return the statement ordered by these criteria, joining what they need.
1002
1004
 
@@ -1018,7 +1020,7 @@ def ordered(
1018
1020
  clauses = []
1019
1021
  joined: dict[str, Any] = {}
1020
1022
  for criterion in named:
1021
- clause, join = _ordering_for(criterion, fields, ignore_case=folded)
1023
+ clause, join = _ordering_for(criterion, fields, ignore_case=folded, model=model)
1022
1024
  clauses.append(_with_nulls(clause, nulls))
1023
1025
  if join is not None:
1024
1026
  target, onclause, outer = join
@@ -1033,6 +1035,7 @@ def _ordering_for(
1033
1035
  fields: Mapping[str, Any],
1034
1036
  *,
1035
1037
  ignore_case: bool | set[str],
1038
+ model: type[Any] | None = None,
1036
1039
  ) -> tuple[Any, Any]:
1037
1040
  """Return the clause a criterion stands for, and the table it needs."""
1038
1041
  if isinstance(criterion, OrderBy):
@@ -1045,7 +1048,7 @@ def _ordering_for(
1045
1048
  if isinstance(field, OrderBy):
1046
1049
  column, join = field.expression, (field.join, field.on, field.outer)
1047
1050
  else:
1048
- column, join = field, _join_for(field)
1051
+ column, join = field, _join_for(field, model)
1049
1052
  if ignore_case is True or (ignore_case is not False and name in ignore_case):
1050
1053
  column = _case_insensitive(column)
1051
1054
  return _sort_clause(column, descending=descending, nulls=nulls), join
@@ -1164,19 +1167,40 @@ def _flatten(criteria: Iterable[Any]) -> Iterator[Any]:
1164
1167
  yield criterion
1165
1168
 
1166
1169
 
1167
- def _join_for(field: Any) -> tuple[Any, None, bool] | None: # noqa: ANN401
1168
- """Return the table a field lives in, for a field that is a plain column.
1170
+ def _join_for(
1171
+ field: Any, # noqa: ANN401
1172
+ model: type[Any] | None,
1173
+ ) -> tuple[Any, None, bool] | None:
1174
+ """Return what to join for a field that is a plain column of another table.
1169
1175
 
1170
- A column of another table is reachable only through a join, and `SQLAlchemy`
1171
- works the condition out from the foreign key. An expression may name several
1172
- tables or none, so it is left alone: `OrderBy` says what to join for those,
1173
- as it does for an alias or a subquery.
1176
+ A relationship of the model that reaches that table is the join, condition
1177
+ and all, which is what a view with no foreign key needs. Failing that, the
1178
+ table itself, and `SQLAlchemy` works the condition out from the key.
1179
+
1180
+ An expression may name several tables or none, so it is left alone:
1181
+ `OrderBy` says what to join for those, as it does for an alias, a subquery,
1182
+ or a table two relationships reach.
1174
1183
  """
1175
1184
  column = _as_column(field)
1176
1185
  if not isinstance(column, sa.Column):
1177
1186
  return None
1178
1187
  table = getattr(column, "table", None)
1179
- return None if table is None else (table, None, True)
1188
+ if table is None:
1189
+ return None
1190
+ return (_relationship_to(model, table) or table, None, True)
1191
+
1192
+
1193
+ def _relationship_to(model: type[Any] | None, table: Any) -> Any: # noqa: ANN401
1194
+ """Return the model's one relationship that reaches this table, if there is one."""
1195
+ mapper = None if model is None else sa.inspect(model, raiseerr=False)
1196
+ if mapper is None:
1197
+ return None
1198
+ reaching = [
1199
+ getattr(model, relationship.key)
1200
+ for relationship in mapper.relationships
1201
+ if relationship.entity.persist_selectable is table
1202
+ ]
1203
+ return reaching[0] if len(reaching) == 1 else None
1180
1204
 
1181
1205
 
1182
1206
  def _reject_conflicting_join(
@@ -454,6 +454,7 @@ class ColumnQuery(Generic[RowT]):
454
454
  criteria,
455
455
  ignore_case=ignore_case,
456
456
  nulls=nulls,
457
+ model=self.model,
457
458
  )
458
459
  )
459
460
 
@@ -444,6 +444,7 @@ class ColumnQuery(Generic[RowT]):
444
444
  criteria,
445
445
  ignore_case=ignore_case,
446
446
  nulls=nulls,
447
+ model=self.model,
447
448
  )
448
449
  )
449
450
 
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