Skip to content

Commit ab09f9b

Browse files
authored
feat: store user provided plan flags (#4319)
1 parent ba90166 commit ab09f9b

4 files changed

Lines changed: 113 additions & 15 deletions

File tree

sqlmesh/core/context.py

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
NotificationTargetManager,
8989
)
9090
from sqlmesh.core.plan import Plan, PlanBuilder, SnapshotIntervals
91+
from sqlmesh.core.plan.definition import UserProvidedFlags
9192
from sqlmesh.core.reference import ReferenceGraph
9293
from sqlmesh.core.scheduler import Scheduler, CompletionStatus
9394
from sqlmesh.core.schema_loader import create_external_models_file
@@ -1161,11 +1162,11 @@ def plan(
11611162
end: t.Optional[TimeLike] = None,
11621163
execution_time: t.Optional[TimeLike] = None,
11631164
create_from: t.Optional[str] = None,
1164-
skip_tests: bool = False,
1165+
skip_tests: t.Optional[bool] = None,
11651166
restate_models: t.Optional[t.Iterable[str]] = None,
1166-
no_gaps: bool = False,
1167-
skip_backfill: bool = False,
1168-
empty_backfill: bool = False,
1167+
no_gaps: t.Optional[bool] = None,
1168+
skip_backfill: t.Optional[bool] = None,
1169+
empty_backfill: t.Optional[bool] = None,
11691170
forward_only: t.Optional[bool] = None,
11701171
allow_destructive_models: t.Optional[t.Collection[str]] = None,
11711172
no_prompts: t.Optional[bool] = None,
@@ -1178,9 +1179,9 @@ def plan(
11781179
categorizer_config: t.Optional[CategorizerConfig] = None,
11791180
enable_preview: t.Optional[bool] = None,
11801181
no_diff: t.Optional[bool] = None,
1181-
run: bool = False,
1182-
diff_rendered: bool = False,
1183-
skip_linter: bool = False,
1182+
run: t.Optional[bool] = None,
1183+
diff_rendered: t.Optional[bool] = None,
1184+
skip_linter: t.Optional[bool] = None,
11841185
) -> Plan:
11851186
"""Interactively creates a plan.
11861187
@@ -1278,11 +1279,11 @@ def plan_builder(
12781279
end: t.Optional[TimeLike] = None,
12791280
execution_time: t.Optional[TimeLike] = None,
12801281
create_from: t.Optional[str] = None,
1281-
skip_tests: bool = False,
1282+
skip_tests: t.Optional[bool] = None,
12821283
restate_models: t.Optional[t.Iterable[str]] = None,
1283-
no_gaps: bool = False,
1284-
skip_backfill: bool = False,
1285-
empty_backfill: bool = False,
1284+
no_gaps: t.Optional[bool] = None,
1285+
skip_backfill: t.Optional[bool] = None,
1286+
empty_backfill: t.Optional[bool] = None,
12861287
forward_only: t.Optional[bool] = None,
12871288
allow_destructive_models: t.Optional[t.Collection[str]] = None,
12881289
no_auto_categorization: t.Optional[bool] = None,
@@ -1292,9 +1293,9 @@ def plan_builder(
12921293
backfill_models: t.Optional[t.Collection[str]] = None,
12931294
categorizer_config: t.Optional[CategorizerConfig] = None,
12941295
enable_preview: t.Optional[bool] = None,
1295-
run: bool = False,
1296-
diff_rendered: bool = False,
1297-
skip_linter: bool = False,
1296+
run: t.Optional[bool] = None,
1297+
diff_rendered: t.Optional[bool] = None,
1298+
skip_linter: t.Optional[bool] = None,
12981299
) -> PlanBuilder:
12991300
"""Creates a plan builder.
13001301
@@ -1335,6 +1336,42 @@ def plan_builder(
13351336
Returns:
13361337
The plan builder.
13371338
"""
1339+
kwargs: t.Dict[str, t.Optional[UserProvidedFlags]] = {
1340+
"start": start,
1341+
"end": end,
1342+
"execution_time": execution_time,
1343+
"create_from": create_from,
1344+
"skip_tests": skip_tests,
1345+
"restate_models": list(restate_models) if restate_models is not None else None,
1346+
"no_gaps": no_gaps,
1347+
"skip_backfill": skip_backfill,
1348+
"empty_backfill": empty_backfill,
1349+
"forward_only": forward_only,
1350+
"allow_destructive_models": list(allow_destructive_models)
1351+
if allow_destructive_models is not None
1352+
else None,
1353+
"no_auto_categorization": no_auto_categorization,
1354+
"effective_from": effective_from,
1355+
"include_unmodified": include_unmodified,
1356+
"select_models": list(select_models) if select_models is not None else None,
1357+
"backfill_models": list(backfill_models) if backfill_models is not None else None,
1358+
"enable_preview": enable_preview,
1359+
"run": run,
1360+
"diff_rendered": diff_rendered,
1361+
"skip_linter": skip_linter,
1362+
}
1363+
user_provided_flags: t.Dict[str, UserProvidedFlags] = {
1364+
k: v for k, v in kwargs.items() if v is not None
1365+
}
1366+
1367+
skip_tests = skip_tests or False
1368+
no_gaps = no_gaps or False
1369+
skip_backfill = skip_backfill or False
1370+
empty_backfill = empty_backfill or False
1371+
run = run or False
1372+
diff_rendered = diff_rendered or False
1373+
skip_linter = skip_linter or False
1374+
13381375
environment = environment or self.config.default_target_environment
13391376
environment = Environment.sanitize_name(environment)
13401377
is_dev = environment != c.PROD
@@ -1469,6 +1506,7 @@ def plan_builder(
14691506
engine_schema_differ=self.engine_adapter.SCHEMA_DIFFER,
14701507
interval_end_per_model=max_interval_end_per_model,
14711508
console=self.console,
1509+
user_provided_flags=user_provided_flags,
14721510
)
14731511

14741512
def apply(

sqlmesh/core/plan/builder.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
)
1616
from sqlmesh.core.context_diff import ContextDiff
1717
from sqlmesh.core.environment import EnvironmentNamingInfo
18-
from sqlmesh.core.plan.definition import Plan, SnapshotMapping, earliest_interval_start
18+
from sqlmesh.core.plan.definition import (
19+
Plan,
20+
SnapshotMapping,
21+
UserProvidedFlags,
22+
earliest_interval_start,
23+
)
1924
from sqlmesh.core.schema_diff import SchemaDiffer, has_drop_alteration, get_dropped_column_names
2025
from sqlmesh.core.snapshot import (
2126
DeployabilityIndex,
@@ -107,6 +112,7 @@ def __init__(
107112
ensure_finalized_snapshots: bool = False,
108113
interval_end_per_model: t.Optional[t.Dict[str, int]] = None,
109114
console: t.Optional[PlanBuilderConsole] = None,
115+
user_provided_flags: t.Optional[t.Dict[str, UserProvidedFlags]] = None,
110116
):
111117
self._context_diff = context_diff
112118
self._no_gaps = no_gaps
@@ -134,6 +140,7 @@ def __init__(
134140
self._engine_schema_differ = engine_schema_differ
135141
self._console = console or get_console()
136142
self._choices: t.Dict[SnapshotId, SnapshotChangeCategory] = {}
143+
self._user_provided_flags = user_provided_flags
137144

138145
self._start = start
139146
if not self._start and (
@@ -280,6 +287,7 @@ def build(self) -> Plan:
280287
execution_time=self._execution_time,
281288
end_bounded=self._end_bounded,
282289
ensure_finalized_snapshots=self._ensure_finalized_snapshots,
290+
user_provided_flags=self._user_provided_flags,
283291
)
284292
self._latest_plan = plan
285293
return plan

sqlmesh/core/plan/definition.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from sqlmesh.utils.pydantic import PydanticModel
2929

3030
SnapshotMapping = t.Dict[SnapshotId, t.Set[SnapshotId]]
31+
UserProvidedFlags = t.Union[TimeLike, str, bool, t.List[str]]
3132

3233

3334
class Plan(PydanticModel, frozen=True):
@@ -63,6 +64,8 @@ class Plan(PydanticModel, frozen=True):
6364
effective_from: t.Optional[TimeLike] = None
6465
execution_time: t.Optional[TimeLike] = None
6566

67+
user_provided_flags: t.Optional[t.Dict[str, UserProvidedFlags]] = None
68+
6669
@cached_property
6770
def start(self) -> TimeLike:
6871
if self.provided_start is not None:
@@ -262,6 +265,7 @@ def to_evaluatable(self) -> EvaluatablePlan:
262265
if s.is_model and s.model.disable_restatement
263266
},
264267
environment_statements=self.context_diff.environment_statements,
268+
user_provided_flags=self.user_provided_flags,
265269
)
266270

267271
@cached_property
@@ -294,6 +298,7 @@ class EvaluatablePlan(PydanticModel):
294298
execution_time: t.Optional[TimeLike] = None
295299
disabled_restatement_models: t.Set[str]
296300
environment_statements: t.Optional[t.List[EnvironmentStatements]] = None
301+
user_provided_flags: t.Optional[t.Dict[str, UserProvidedFlags]] = None
297302

298303
def is_selected_for_backfill(self, model_fqn: str) -> bool:
299304
return self.models_to_backfill is None or model_fqn in self.models_to_backfill

tests/core/test_plan.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3105,3 +3105,50 @@ def test_set_choice_for_forward_only_model(make_snapshot):
31053105
plan.snapshots[updated_snapshot.snapshot_id].change_category
31063106
== SnapshotChangeCategory.FORWARD_ONLY
31073107
)
3108+
3109+
3110+
def test_user_provided_flags(sushi_context: Context):
3111+
expected_flags = {
3112+
"run": True,
3113+
"execution_time": "2025-01-01",
3114+
}
3115+
plan_a = sushi_context.plan(no_prompts=True, run=True, execution_time="2025-01-01")
3116+
assert plan_a.user_provided_flags == expected_flags
3117+
evaluatable_plan = plan_a.to_evaluatable()
3118+
assert evaluatable_plan.user_provided_flags == expected_flags
3119+
3120+
plan_b = sushi_context.plan()
3121+
assert plan_b.user_provided_flags == {}
3122+
evaluatable_plan_b = plan_b.to_evaluatable()
3123+
assert evaluatable_plan_b.user_provided_flags == {}
3124+
3125+
context_diff = ContextDiff(
3126+
environment="test_environment",
3127+
is_new_environment=True,
3128+
is_unfinalized_environment=False,
3129+
normalize_environment_name=True,
3130+
create_from="prod",
3131+
create_from_env_exists=True,
3132+
added=set(),
3133+
removed_snapshots={},
3134+
modified_snapshots={},
3135+
snapshots={},
3136+
new_snapshots={},
3137+
previous_plan_id=None,
3138+
previously_promoted_snapshot_ids=set(),
3139+
previous_finalized_snapshots=None,
3140+
previous_gateway_managed_virtual_layer=False,
3141+
gateway_managed_virtual_layer=False,
3142+
)
3143+
plan_builder = PlanBuilder(
3144+
context_diff,
3145+
DuckDBEngineAdapter.SCHEMA_DIFFER,
3146+
forward_only=True,
3147+
user_provided_flags={"forward_only": True},
3148+
).build()
3149+
assert plan_builder.user_provided_flags == {"forward_only": True}
3150+
plan_builder = PlanBuilder(
3151+
context_diff,
3152+
DuckDBEngineAdapter.SCHEMA_DIFFER,
3153+
).build()
3154+
assert plan_builder.user_provided_flags == None

0 commit comments

Comments
 (0)