Repository navigation
refactor(domain): merge the desired and observed constraint classes - #418
Merged
Merged
Conversation
A key constraint is the same concept whether it comes from a declaration or a catalog read, but constraints.py modelled each kind as a class pair kept comparable by hand-rolled equality mixins. All the split bought was a typed non-optional name on the observed side. Each pair is now one frozen dataclass whose name is a compare=False field, so generated equality gives the documented semantics — names are creation preferences, not structural identity — for free. The rule the Observed* types encoded moves to ObservedTable: an observed constraint must carry its catalog name. The differ's drop sites narrow the now optional name explicitly, backed by the Identifier check the drop actions already perform. The Observed* names were internal (exported from domain.model, which no user-facing module re-exports), so nothing public changes.
* refactor(domain): drop the primary key positionally, not by name The compiler already emits ALTER TABLE ... DROP PRIMARY KEY IF EXISTS — the platform addresses a primary key positionally, a table having at most one — so the name on DropPrimaryKey was never compiled. It existed only to fill the action's subject, and it forced the differ to narrow the observed key's optional name through _require_catalog_name. DropPrimaryKey now carries the observed key's columns instead, mirroring SetPrimaryKey: subject and diff entries report what is being dropped rather than a catalog-owned name. Both primary-key narrows disappear with the field. The foreign-key drop genuinely needs the catalog name (DROP CONSTRAINT is the only unambiguous form: two FKs may share local columns), so its check stays — inlined at its single site now the helper has no other callers. * refactor(domain): state the foreign-key name narrow as an assert ObservedTable already raises when an observed foreign key is nameless, so the differ's ValueError duplicated an invariant enforced upstream and could never fire. An assert says what is true here: this branch is unreachable, and the check exists only to narrow str | None for mypy.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Merges each desired/observed constraint class pair into one class:
PrimaryKeyConstraintandForeignKeyConstraintnow serve both sides, withname: str | None = field(default=None, compare=False). Deleted:ObservedPrimaryKeyConstraint,ObservedForeignKeyConstraint, and the_PrimaryKey/_ForeignKeyequality mixins.Why
constraints.pycarried seven entities for two concepts. Four of them existedto buy exactly one thing — a typed non-optional
nameat the differ's two dropsites — and the price was hand-rolled
__eq__/__hash__(generated dataclassequality is class-strict, and the differ compares desired against observed),
typing-fiction annotations on the mixins,
eq=Falseon all four dataclasses,and
isinstancere-checks inObservedTable.__post_init__anyway.With one class per kind,
compare=Falsegives the documented semantics —names are creation preferences, not structural identity — straight from
generated equality and hashing.
How the old guarantees are kept
system to
ObservedTable.__post_init__: a nameless constraint is rejectedwith
ValueError, same boundary and same count of runtime checks as the oldisinstancegate.DropPrimaryKey/DropForeignKeytakename: str) discharge the now-optional name with an explicit_require_catalog_namenarrow, backed by theIdentifiercheck both dropactions already perform at construction.
One deliberate behaviour widening:
ObservedTablenow accepts any constraintthat carries a name, rather than demanding a distinct class. That is the point
of the merge — the class split never encoded anything beyond name presence.
Nothing changes on the public
delta_engine.schemasurface; theObserved*names were exported from
domain.modelonly.Tests
test_observed_table_requires_observed_constraint_typesreworked intotest_observed_table_requires_named_constraints(unnamed constraint →ValueError), pinning the relocated boundary.classes no longer enforce it; the table-level test above owns it now.
as_observed_primary_key/as_observed_foreign_keysprojections became identity functions, so they are deleted and fixtures pass
constraints straight through.
Gate: 1400 passed (baseline 1402 — the deleted class-level test was 2
parametrized cases, now covered at the table level), ruff check/format clean,
mypy clean (180 files), all 7 import contracts kept. Net −146 lines.
🤖 Generated with Claude Code