half-orm 0.18.9__tar.gz → 0.18.10__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 (37) hide show
  1. {half_orm-0.18.9/half_orm.egg-info → half_orm-0.18.10}/PKG-INFO +1 -1
  2. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/relation.py +42 -6
  3. half_orm-0.18.10/half_orm/version.txt +1 -0
  4. {half_orm-0.18.9 → half_orm-0.18.10/half_orm.egg-info}/PKG-INFO +1 -1
  5. half_orm-0.18.9/half_orm/version.txt +0 -1
  6. {half_orm-0.18.9 → half_orm-0.18.10}/AUTHORS +0 -0
  7. {half_orm-0.18.9 → half_orm-0.18.10}/LICENSE +0 -0
  8. {half_orm-0.18.9 → half_orm-0.18.10}/README.md +0 -0
  9. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/__init__.py +0 -0
  10. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/__main__.py +0 -0
  11. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/cli.py +0 -0
  12. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/cli_utils.py +0 -0
  13. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/field.py +0 -0
  14. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/field_errors.py +0 -0
  15. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/fkey.py +0 -0
  16. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/hotest.py +0 -0
  17. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/model.py +0 -0
  18. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/model_errors.py +0 -0
  19. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/null.py +0 -0
  20. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/pg_meta.py +0 -0
  21. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/relation_errors.py +0 -0
  22. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/relation_factory.py +0 -0
  23. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/sql_adapter.py +0 -0
  24. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/sql_ast.py +0 -0
  25. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/transaction.py +0 -0
  26. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/utils.py +0 -0
  27. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm.egg-info/SOURCES.txt +0 -0
  28. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm.egg-info/dependency_links.txt +0 -0
  29. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm.egg-info/entry_points.txt +0 -0
  30. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm.egg-info/requires.txt +0 -0
  31. {half_orm-0.18.9 → half_orm-0.18.10}/half_orm.egg-info/top_level.txt +0 -0
  32. {half_orm-0.18.9 → half_orm-0.18.10}/pyproject.toml +0 -0
  33. {half_orm-0.18.9 → half_orm-0.18.10}/setup.cfg +0 -0
  34. {half_orm-0.18.9 → half_orm-0.18.10}/setup.py +0 -0
  35. {half_orm-0.18.9 → half_orm-0.18.10}/test/test_cli.py +0 -0
  36. {half_orm-0.18.9 → half_orm-0.18.10}/test/test_main.py +0 -0
  37. {half_orm-0.18.9 → half_orm-0.18.10}/test/test_sql_ast.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: half_orm
3
- Version: 0.18.9
3
+ Version: 0.18.10
4
4
  Summary: A simple PostgreSQL to Python mapper.
5
5
  Home-page: https://github.com/half-orm/half-orm
6
6
  Author: Joël Maïzi
@@ -429,9 +429,15 @@ class Relation:
429
429
  def ho_assert_is_singleton(self):
430
430
  """Assert that this predicate identifies exactly one row, without querying the database.
431
431
 
432
- A predicate is a *singleton* when every field of a unique identifier
433
- (primary key or any ``UNIQUE NOT NULL`` constraint) is set with the
434
- ``=`` comparator. The check is purely structural no SQL is executed.
432
+ A predicate is a *singleton* when:
433
+
434
+ * every field of a unique identifier (primary key or any
435
+ ``UNIQUE NOT NULL`` constraint) is set with the ``=`` comparator, **or**
436
+ * a FK join constrains a unique identifier of this relation: the fields
437
+ on *this* side of the join form a PK or UNIQUE NOT NULL, and the
438
+ corresponding fields on the joined relation are all fixed with ``=``.
439
+
440
+ The check is purely structural — no SQL is executed.
435
441
 
436
442
  Returns:
437
443
  self — for chaining before a write operation.
@@ -465,6 +471,22 @@ class Relation:
465
471
  for ukey in self._ho_ukeys:
466
472
  if _fully_set(ukey):
467
473
  return self
474
+ # A FK join uniquely identifies self when:
475
+ # - the fields on self involved in the join form a PK or UNIQUE NOT NULL, AND
476
+ # - the corresponding fields on the joined relation are all fixed with '='.
477
+ def _all_eq(names, rel):
478
+ return all(
479
+ rel._ho_fields.get(n.strip('"')) is not None
480
+ and rel._ho_fields[n.strip('"')].is_set()
481
+ and rel._ho_fields[n.strip('"')]._comp() == '='
482
+ for n in names
483
+ )
484
+ for fkey, fk_rel in self._ho_join_to.items():
485
+ self_fields = frozenset(fkey.names)
486
+ on_pk = bool(self._ho_pkey) and self_fields == frozenset(self._ho_pkey.keys())
487
+ on_ukey = any(self_fields == frozenset(uk.keys()) for uk in self._ho_ukeys)
488
+ if (on_pk or on_ukey) and _all_eq(fkey.fk_names, fk_rel):
489
+ return self
468
490
  if not self._ho_pkey and not self._ho_ukeys:
469
491
  raise relation_errors.NotASingletonError(
470
492
  f"{self.__class__.__name__} has no primary key or unique NOT NULL constraint.")
@@ -896,10 +918,24 @@ Fkeys = {"""
896
918
  def ho_is_set(self):
897
919
  """Return True if one field at least is set or if self has been
898
920
  constrained by at least one of its foreign keys or self is the
899
- result of a combination of Relations (using set operators).
921
+ result of a combination of Relations (using set operators) where
922
+ at least one operand is itself constrained.
900
923
  """
901
- joined_to = bool(self._ho_join_to)
902
- return (joined_to or bool(self._ho_set_operators.operator) or bool(self._ho_neg) or
924
+ joined_to = any(jt_.ho_is_set() for jt_ in self._ho_join_to.values())
925
+ op = self._ho_set_operators.operator
926
+ if op:
927
+ left_set = self._ho_set_operators.left.ho_is_set()
928
+ right = self._ho_set_operators.right
929
+ right_set = right is not None and right.ho_is_set()
930
+ if op == "or":
931
+ # A() | B → all rows if any operand is unconstrained
932
+ set_op_constrained = left_set and right_set
933
+ else:
934
+ # "and" / "and not": unconstrained operand is transparent
935
+ set_op_constrained = left_set or right_set
936
+ else:
937
+ set_op_constrained = False
938
+ return (joined_to or set_op_constrained or bool(self._ho_neg) or
903
939
  bool({field for field in self._ho_fields.values() if field.is_set()}))
904
940
 
905
941
  def __get_set_fields(self):
@@ -0,0 +1 @@
1
+ 0.18.10
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: half_orm
3
- Version: 0.18.9
3
+ Version: 0.18.10
4
4
  Summary: A simple PostgreSQL to Python mapper.
5
5
  Home-page: https://github.com/half-orm/half-orm
6
6
  Author: Joël Maïzi
@@ -1 +0,0 @@
1
- 0.18.9
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