Conversation
📝 WalkthroughWalkthroughThis pull request adds support for variable placeholder substitution in filter expressions. Filter strings can now reference Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
slayer/engine/enrichment.py (1)
441-450: Consider moving the import to the top of the file.The
substitute_variablesimport is placed inside the conditional block, butslayer.core.queryis already imported at line 27 (from slayer.core.query import SlayerQuery). Moving this import to the top with the other imports from the same module would be more consistent with the coding guidelines and wouldn't risk circular import issues.As per coding guidelines: "Place imports at the top of files"
Suggested change
At line 27, extend the existing import:
-from slayer.core.query import SlayerQuery +from slayer.core.query import SlayerQuery, substitute_variablesThen simplify lines 443-448:
if query.variables and query_filters: - from slayer.core.query import substitute_variables - query_filters = [ substitute_variables(filter_str=f, variables=query.variables) for f in query_filters ]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@slayer/engine/enrichment.py` around lines 441 - 450, Move the from slayer.core.query import substitute_variables import to the top of slayer/engine/enrichment.py alongside the existing from slayer.core.query import SlayerQuery import so all imports from slayer.core.query are co-located; then remove the in-block import and keep the conditional logic that calls substitute_variables (used with query.filters and query.variables) unchanged, ensuring you only reference substitute_variables in the query-level filter substitution block.tests/test_models.py (1)
571-579: Consider adding an integration test for the full enrichment flow.The unit tests thoroughly cover
substitute_variablesin isolation, buttest_variable_in_slayer_queryonly verifies that thevariablesfield is accepted and stored onSlayerQuery. There's no test verifying that variables are actually substituted during query enrichment and affect the final SQL output.An integration test in
tests/integration/that runs a query withfilterscontaining placeholders andvariablesproviding values would catch regressions in the enrichment.py integration point (lines 443-448).Would you like me to draft an integration test case that verifies the end-to-end flow?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/test_models.py` around lines 571 - 579, Add an integration test that verifies variables are substituted through the full enrichment flow: create a SlayerQuery with a filters string containing a placeholder like "{val}" and variables={"val":"completed"}, pass that SlayerQuery into the enrichment entrypoint that uses substitute_variables (the function in enrichment.py that consumes SlayerQuery and returns the enriched SQL), then assert the returned/enriched SQL contains the substituted value (e.g., "status = 'completed'") to ensure end-to-end substitution works.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@slayer/engine/enrichment.py`:
- Around line 441-450: Move the from slayer.core.query import
substitute_variables import to the top of slayer/engine/enrichment.py alongside
the existing from slayer.core.query import SlayerQuery import so all imports
from slayer.core.query are co-located; then remove the in-block import and keep
the conditional logic that calls substitute_variables (used with query.filters
and query.variables) unchanged, ensuring you only reference substitute_variables
in the query-level filter substitution block.
In `@tests/test_models.py`:
- Around line 571-579: Add an integration test that verifies variables are
substituted through the full enrichment flow: create a SlayerQuery with a
filters string containing a placeholder like "{val}" and
variables={"val":"completed"}, pass that SlayerQuery into the enrichment
entrypoint that uses substitute_variables (the function in enrichment.py that
consumes SlayerQuery and returns the enriched SQL), then assert the
returned/enriched SQL contains the substituted value (e.g., "status =
'completed'") to ensure end-to-end substitution works.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 6469f151-9e61-4595-b5a7-4d69c4edf782
📒 Files selected for processing (5)
CLAUDE.mddocs/concepts/queries.mdslayer/core/query.pyslayer/engine/enrichment.pytests/test_models.py
Five new CodeRabbit comments after the previous batches landed:
* slayer/cli.py: bridge async storage.get_datasource and storage.save_model
with run_sync (B6-1, Critical). Every other storage.* call site in this
file already does so; import-dbt was the lone exception, with the result
that --include-hidden-models always failed the datasource lookup
(coroutine != None) and even plain `slayer import-dbt` printed
"Imported model: ..." without ever persisting anything.
* slayer/dbt/parser.py: add keyword-only `include_regular_models: bool =
False` to parse_dbt_project (B6-2, Major). Previously the manifest was
always loaded, which can invoke `dbt parse` (slow) or fail noisily when
dbt-core isn't installed — making the "optional" hidden-model import
path non-optional. CLI passes through args.include_hidden_models.
* slayer/engine/enrichment.py: propagate model_name_str through every
Enriched* construction site (B6-3, Major). Batch 1 fixed filter
resolution to use model_name_str but EnrichedMeasure.model_name,
EnrichedQuery.model_name, and the Dimension/TimeDimension helpers
still used model.name — mismatched aliases would render invalid SQL
in any divergent path. In production today the two are always equal
so this was latent; my Batch 1 regression test exposes the case.
* slayer/sql/generator.py: project a per-measure boolean match-flag
column from the ranked subquery (B6-4, Major). _build_agg() previously
re-emitted measure.filter_sql in the OUTER MAX(CASE WHEN ...). Batch 2
injected joins inside the subquery so the inner ROW_NUMBER OVER
resolved, but the outer aggregate had no scope on the joined tables.
Now _build_last_ranked_from also projects `_match_f<idx>` columns and
the outer MAX(CASE) tests `_match_f<idx> = 1` instead.
* docs/examples/01_dynamic/dynamic.md: subject-verb typo "audience are"
→ "audience is" (B6-5).
Plus regression tests for every real-bug fix:
* TestImportDbtCli::test_models_are_persisted_to_storage (B6-1)
* TestRegularModelsOptIn::{test_default_does_not_load_manifest,
test_opt_in_loads_manifest} (B6-2)
* TestFilteredMeasures::test_filtered_measure_source_alias_propagates_to_generated_sql (B6-3)
* TestFilteredMeasures::test_filtered_last_outer_aggregate_uses_match_flag_not_joined_filter (B6-4)
Plus also merges in origin/main (PR #34 "Variables in filters") cleanly.
Full pytest: 681 passed (was 676 baseline post-merge). ruff clean.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary by CodeRabbit
Release Notes
New Features
{variable}placeholders for dynamic values sourced from a newvariablesquery parameter.{{and}}.Documentation