refactor phase 5: API surface — one query(), eager validation, YAML schema, no how= - #302
Merged
Merged
Conversation
hussainsultan
force-pushed
the
refactor/phase4-serialization
branch
from
August 18, 2026 23:15
495bbba to
8eeb595
Compare
hussainsultan
force-pushed
the
refactor/phase5-api-surface
branch
from
August 18, 2026 23:15
6d6792e to
e352f20
Compare
hussainsultan
force-pushed
the
refactor/phase4-serialization
branch
from
August 18, 2026 23:27
8eeb595 to
9be1b17
Compare
hussainsultan
force-pushed
the
refactor/phase5-api-surface
branch
from
August 18, 2026 23:27
e352f20 to
0ebde8b
Compare
hussainsultan
force-pushed
the
refactor/phase4-serialization
branch
from
August 18, 2026 23:37
9be1b17 to
048161e
Compare
hussainsultan
force-pushed
the
refactor/phase5-api-surface
branch
from
August 18, 2026 23:37
0ebde8b to
893448d
Compare
hussainsultan
force-pushed
the
refactor/phase4-serialization
branch
from
August 18, 2026 23:44
048161e to
29bcdc4
Compare
hussainsultan
force-pushed
the
refactor/phase5-api-surface
branch
from
August 18, 2026 23:44
893448d to
1e14d07
Compare
hussainsultan
force-pushed
the
refactor/phase4-serialization
branch
from
August 18, 2026 23:50
29bcdc4 to
e790de3
Compare
hussainsultan
force-pushed
the
refactor/phase5-api-surface
branch
from
August 18, 2026 23:50
1e14d07 to
1fb09b6
Compare
hussainsultan
force-pushed
the
refactor/phase4-serialization
branch
from
August 19, 2026 00:05
e790de3 to
a0865e5
Compare
hussainsultan
force-pushed
the
refactor/phase5-api-surface
branch
from
August 19, 2026 00:05
1fb09b6 to
4e40e80
Compare
…base The four drifted copies (SemanticModel / SemanticJoin / SemanticFilter / op-level SemanticJoinOp — the last missing time_grains entirely) are replaced by a single documented implementation on the base class, so every semantic expression accepts the same parameters. Grouped and aggregated results gain .query()/.compare_periods() for free. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unknown names now raise UnknownFieldError at query construction with did-you-mean suggestions, instead of surfacing at execute() as a raw backend error listing physical columns. Raw table columns and unique suffix matches on joined models remain valid dimension spellings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unknown top-level model keys (the classic 'dimension:' / 'measurez:' typos that used to load silently as empty sections) now raise DefinitionError naming the model, the key, and accepted spellings with did-you-mean hints; field sections must be mappings; and smallest_time_grain values are validated at load instead of at query time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
join_one/join_many accepted how= but raised for anything except 'left' — dead surface that only added a way to get an error. The parameter is gone from every wrapper, the op-level methods, YAML (where a non-left how now fails at load with the left-join+filter guidance), and serialization reconstruction (stored 'how' values are informational; semantic joins are always LEFT, join_cross carries how='cross' internally). Tests, examples, and docs updated. Two ground-truth near-misses caught by the suites and reverted: the sweep initially stripped how='left' from pandas merge / raw ibis join fixtures, which default to INNER — the soundness suite flagged the changed row counts immediately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- to_tagged/from_tagged move to boring_semantic_layer.serialization (xorq-interop jargon out of the front door); to_untagged stays — it's the documented plain-ibis escape hatch and examples use it - the five graph_* helpers demote to boring_semantic_layer.graph_utils - .name/.description defined once on the SemanticTable base: aggregates and limits now answer None instead of raising AttributeError blaming ibis's Table Examples and docs updated to the new import paths. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hussainsultan
force-pushed
the
refactor/phase5-api-surface
branch
from
August 19, 2026 00:34
4e40e80 to
f5cdede
Compare
hussainsultan
changed the base branch from
refactor/phase4-serialization
to
main
August 19, 2026 00:34
malloy_interop.py imports boring_semantic_layer.malloy (exists only on feat/malloy-interop) and breaks make examples + lint on this branch standalone; all three return to untracked local files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hussainsultan
marked this pull request as ready for review
August 19, 2026 00:43
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.
Phase 5 of the refactor, stacked on #301. Per maintainer direction this PR makes clean breaking changes (no deprecation shims) with tests, examples, and docs updated in-step. Full suite: 1743 passed, exit 0;
make examples-coregreen; SQL snapshots byte-identical.One
query(), onecompare_periods()The four drifted copies (SemanticModel / SemanticJoin / SemanticFilter / op-level SemanticJoinOp — the last silently missing
time_grains) are replaced by one documented implementation on theSemanticTablebase. Every semantic expression now accepts the identical parameter set, and grouped/aggregated results gain.query()/.compare_periods()where they previously raised AttributeError.Eager validation with suggestions
query()now fails at construction on unknown dimension/measure names:XorqTypeError: Column 'revenu' is not found in table. Existing columns: 'region', 'amount'— at execute, leaking physical columnsUnknownFieldError: Unknown measure 'revenu'. Declared measures: ['revenue']. Did you mean: measure 'revenue'?— at constructionRaw table columns and unique suffix matches on joined models remain valid dimension spellings, so nothing legitimate got stricter.
YAML configs are schema-validated at load
The audit's silent-typo class is closed:
dimension:/measurez:used to load as models with zero dimensions; they now raiseDefinitionErrornaming the model, the key, and accepted spellings with did-you-mean hints. Field sections must be mappings,smallest_time_grainvalues are validated at load (not first query), and a non-lefthow:in a YAML join fails at load with the left-join+filter guidance.how=is gonejoin_one/join_manyacceptedhow=only to raise for anything but"left"— dead surface. The parameter is removed from every wrapper, the op-level methods, YAML, and serialization reconstruction (storedhowvalues are informational; semantic joins are always LEFT,join_crosscarrieshow="cross"internally).Worth reading in the diff: the removal sweep initially stripped
how="left"from two pandas merge / raw-ibis join fixtures, which default to INNER — the soundness suites flagged the changed row counts immediately and those fixtures were restored untouched. That's the invariant suites doing exactly their job.Namespace + protocol
to_tagged/from_taggedmove toboring_semantic_layer.serialization; the fivegraph_*helpers demote toboring_semantic_layer.graph_utils.to_untaggedstays top-level (the documented plain-ibis escape hatch, used by examples)..name/.descriptionare defined once on theSemanticTablebase: aggregates/limits answerNoneinstead of raisingAttributeError: 'Table' object has no attribute 'name'.Deferred (named, not forgotten)
Fluent
time_grain/time_range/havingmethods (the JSON path — and now every expression's.query()— already provides them),index()returning an expression wrapper, StrEnums for grains/directions, and the full docstring pass overexpr.py's public surface.🤖 Generated with Claude Code