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.
- {half_orm-0.18.9/half_orm.egg-info → half_orm-0.18.10}/PKG-INFO +1 -1
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/relation.py +42 -6
- half_orm-0.18.10/half_orm/version.txt +1 -0
- {half_orm-0.18.9 → half_orm-0.18.10/half_orm.egg-info}/PKG-INFO +1 -1
- half_orm-0.18.9/half_orm/version.txt +0 -1
- {half_orm-0.18.9 → half_orm-0.18.10}/AUTHORS +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/LICENSE +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/README.md +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/__init__.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/__main__.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/cli.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/cli_utils.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/field.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/field_errors.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/fkey.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/hotest.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/model.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/model_errors.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/null.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/pg_meta.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/relation_errors.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/relation_factory.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/sql_adapter.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/sql_ast.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/transaction.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm/utils.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm.egg-info/SOURCES.txt +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm.egg-info/dependency_links.txt +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm.egg-info/entry_points.txt +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm.egg-info/requires.txt +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/half_orm.egg-info/top_level.txt +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/pyproject.toml +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/setup.cfg +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/setup.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/test/test_cli.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/test/test_main.py +0 -0
- {half_orm-0.18.9 → half_orm-0.18.10}/test/test_sql_ast.py +0 -0
|
@@ -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
|
|
433
|
-
|
|
434
|
-
|
|
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 =
|
|
902
|
-
|
|
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 +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
|
|
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
|