Skip to content

Commit 3329ee1

Browse files
committed
Switch impl to always_init_from_prod
1 parent fd6a42c commit 3329ee1

7 files changed

Lines changed: 38 additions & 75 deletions

File tree

docs/guides/configuration.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -386,15 +386,15 @@ Example showing default values:
386386

387387
By default, SQLMesh compares the current state of project files to the target `<env>` environment when `sqlmesh plan <env>` is run. However, a common expectation is that local changes should always be compared to the production environment.
388388

389-
The `always_compare_against_prod` boolean plan option can alter this behavior. When enabled, SQLMesh will always attempt to compare against the production environment; If that does not exist, SQLMesh will fall back to comparing against the target environment.
389+
The `always_init_from_prod` boolean plan option can alter this behavior. When enabled, SQLMesh will always attempt to compare against the production environment; If that does not exist, SQLMesh will fall back to comparing against the target environment.
390390

391391
**NOTE:**: Upon succesfull plan application, changes are still promoted to the target `<env>` environment.
392392

393393
=== "YAML"
394394

395395
```yaml linenums="1"
396396
plan:
397-
always_compare_against_prod: True
397+
always_init_from_prod: True
398398
```
399399

400400
=== "Python"
@@ -416,7 +416,7 @@ The `always_compare_against_prod` boolean plan option can alter this behavior. W
416416

417417
#### Change Categorization Example
418418

419-
Consider this scenario with `always_compare_against_prod` enabled:
419+
Consider this scenario with `always_init_from_prod` enabled:
420420

421421
1. Initial state in `prod`:
422422
```sql
@@ -426,7 +426,7 @@ SELECT 1 AS col
426426

427427
1. First (breaking) change in `dev`:
428428
```sql
429-
MODEL (name test.a, kind FULL);
429+
MODEL (name sqlmesh_example__dev.test_model, kind FULL);
430430
SELECT 2 AS col
431431
```
432432

@@ -454,13 +454,15 @@ SELECT 2 AS col
454454

455455
3. Second (metadata) change in `dev`:
456456
```sql
457-
MODEL (name test.a, kind FULL, owner 'John Doe');
457+
MODEL (name sqlmesh_example__dev.test_model, kind FULL, owner 'John Doe');
458458
SELECT 5 AS col
459459
```
460460

461461
??? "Output plan example #2"
462462

463463
```bash
464+
New environment `dev` will be created from `prod`
465+
464466
Differences from the `prod` environment:
465467

466468
Models:

docs/reference/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ Configuration for the `sqlmesh plan` command.
8080
| `enable_preview` | Indicates whether to enable [data preview](../concepts/plans.md#data-preview) for forward-only models when targeting a development environment (Default: True, except for dbt projects where the target engine does not support cloning) | Boolean | N |
8181
| `no_diff` | Don't show diffs for changed models (Default: False) | boolean | N |
8282
| `no_prompts` | Disables interactive prompts in CLI (Default: True) | boolean | N |
83-
83+
| `always_init_from_prod` | Always recreates the target environment from the environment specified in `create_from` (by default `prod`) (Default: False) | boolean | N |
8484
## Run
8585

8686
Configuration for the `sqlmesh run` command. Please note that this is only applicable when configured with the [builtin](#builtin) scheduler.

sqlmesh/core/config/plan.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class PlanConfig(BaseConfig):
2020
auto_apply: Whether to automatically apply the new plan after creation.
2121
use_finalized_state: Whether to compare against the latest finalized environment state, or to use
2222
whatever state the target environment is currently in.
23-
always_compare_against_prod: Whether to always compare against production when planning, even if the target environment exists.
23+
always_init_from_prod: Whether to always recreate the target environment from the prod environment.
2424
"""
2525

2626
forward_only: bool = False
@@ -31,4 +31,4 @@ class PlanConfig(BaseConfig):
3131
no_prompts: bool = True
3232
auto_apply: bool = False
3333
use_finalized_state: bool = False
34-
always_compare_against_prod: bool = False
34+
always_init_from_prod: bool = False

sqlmesh/core/console.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,6 @@ def show_environment_difference_summary(
219219
self,
220220
context_diff: ContextDiff,
221221
no_diff: bool = True,
222-
environment: t.Optional[str] = None,
223222
) -> None:
224223
"""Displays a summary of differences for the environment."""
225224

@@ -646,7 +645,6 @@ def show_environment_difference_summary(
646645
self,
647646
context_diff: ContextDiff,
648647
no_diff: bool = True,
649-
environment: t.Optional[str] = None,
650648
) -> None:
651649
pass
652650

@@ -1526,7 +1524,6 @@ def show_environment_difference_summary(
15261524
self,
15271525
context_diff: ContextDiff,
15281526
no_diff: bool = True,
1529-
environment: t.Optional[str] = None,
15301527
) -> None:
15311528
"""Shows a summary of the environment differences.
15321529
@@ -1536,11 +1533,10 @@ def show_environment_difference_summary(
15361533
environment: The initial target environment
15371534
"""
15381535
if context_diff.is_new_environment:
1539-
new_environment = environment or context_diff.environment
15401536
msg = (
1541-
f"\n`{new_environment}` environment will be initialized"
1537+
f"\n`{context_diff.environment}` environment will be initialized"
15421538
if not context_diff.create_from_env_exists
1543-
else f"\nNew environment `{new_environment}` will be created from `{context_diff.create_from}`"
1539+
else f"\nNew environment `{context_diff.environment}` will be created from `{context_diff.create_from}`"
15441540
)
15451541
self._print(Tree(f"[bold]{msg}\n"))
15461542
if not context_diff.has_snapshot_changes:
@@ -1791,7 +1787,6 @@ def _prompt_categorize(
17911787
self.show_environment_difference_summary(
17921788
plan.context_diff,
17931789
no_diff=no_diff,
1794-
environment=plan_builder.environment_naming_info.name,
17951790
)
17961791

17971792
if plan.context_diff.has_changes:
@@ -2904,7 +2899,6 @@ def show_environment_difference_summary(
29042899
self,
29052900
context_diff: ContextDiff,
29062901
no_diff: bool = True,
2907-
environment: t.Optional[str] = None,
29082902
) -> None:
29092903
"""Shows a summary of the environment differences.
29102904
@@ -2914,11 +2908,10 @@ def show_environment_difference_summary(
29142908
environment: The initial target environment
29152909
"""
29162910
if context_diff.is_new_environment:
2917-
new_environment = environment or context_diff.environment
29182911
msg = (
2919-
f"\n**`{new_environment}` environment will be initialized**"
2912+
f"\n**`{context_diff.environment}` environment will be initialized**"
29202913
if not context_diff.create_from_env_exists
2921-
else f"\n**New environment `{new_environment}` will be created from `{context_diff.create_from}`**"
2914+
else f"\n**New environment `{context_diff.environment}` will be created from `{context_diff.create_from}`**"
29222915
)
29232916
self._print(msg)
29242917
if not context_diff.has_snapshot_changes:
@@ -3510,7 +3503,6 @@ def show_environment_difference_summary(
35103503
self,
35113504
context_diff: ContextDiff,
35123505
no_diff: bool = True,
3513-
environment: t.Optional[str] = None,
35143506
) -> None:
35153507
self._write("Environment Difference Summary:")
35163508

sqlmesh/core/context.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1487,7 +1487,7 @@ def plan_builder(
14871487
or (backfill_models is not None and not backfill_models),
14881488
ensure_finalized_snapshots=self.config.plan.use_finalized_state,
14891489
diff_rendered=diff_rendered,
1490-
always_compare_against_prod=self.config.plan.always_compare_against_prod,
1490+
always_init_from_prod=self.config.plan.always_init_from_prod,
14911491
)
14921492
modified_model_names = {
14931493
*context_diff.modified_snapshots,
@@ -1644,7 +1644,6 @@ def diff(self, environment: t.Optional[str] = None, detailed: bool = False) -> b
16441644
self.console.show_environment_difference_summary(
16451645
context_diff,
16461646
no_diff=not detailed,
1647-
environment=environment,
16481647
)
16491648
if context_diff.has_changes:
16501649
self.console.show_model_difference_summary(
@@ -2631,7 +2630,7 @@ def _context_diff(
26312630
force_no_diff: bool = False,
26322631
ensure_finalized_snapshots: bool = False,
26332632
diff_rendered: bool = False,
2634-
always_compare_against_prod: bool = False,
2633+
always_init_from_prod: bool = False,
26352634
) -> ContextDiff:
26362635
environment = Environment.sanitize_name(environment)
26372636
if force_no_diff:
@@ -2649,7 +2648,7 @@ def _context_diff(
26492648
environment_statements=self._environment_statements,
26502649
gateway_managed_virtual_layer=self.config.gateway_managed_virtual_layer,
26512650
infer_python_dependencies=self.config.infer_python_dependencies,
2652-
always_compare_against_prod=always_compare_against_prod,
2651+
always_init_from_prod=always_init_from_prod,
26532652
)
26542653

26552654
def _destroy(self) -> None:

sqlmesh/core/context_diff.py

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def create(
104104
environment_statements: t.Optional[t.List[EnvironmentStatements]] = [],
105105
gateway_managed_virtual_layer: bool = False,
106106
infer_python_dependencies: bool = True,
107-
always_compare_against_prod: bool = False,
107+
always_init_from_prod: bool = False,
108108
) -> ContextDiff:
109109
"""Create a ContextDiff object.
110110
@@ -129,35 +129,25 @@ def create(
129129
Returns:
130130
The ContextDiff object.
131131
"""
132-
initial_environment_name = environment.lower()
133-
initial_env = state_reader.get_environment(initial_environment_name)
134-
132+
environment = environment.lower()
133+
existing_env = state_reader.get_environment(environment)
135134
create_from_env_exists = False
136-
if initial_env is None or initial_env.expired:
137-
initial_env = state_reader.get_environment(create_from.lower())
138135

139-
if not initial_env and create_from != c.PROD:
136+
if existing_env is None or existing_env.expired or always_init_from_prod:
137+
env = state_reader.get_environment(create_from.lower())
138+
139+
if not env and create_from != c.PROD:
140140
get_console().log_warning(
141141
f"The environment name '{create_from}' was passed to the `plan` command's `--create-from` argument, but '{create_from}' does not exist. Initializing new environment '{environment}' from scratch."
142142
)
143143

144144
is_new_environment = True
145-
create_from_env_exists = initial_env is not None
145+
create_from_env_exists = env is not None
146146
previously_promoted_snapshot_ids = set()
147147
else:
148+
env = existing_env
148149
is_new_environment = False
149-
previously_promoted_snapshot_ids = {
150-
s.snapshot_id for s in initial_env.promoted_snapshots
151-
}
152-
153-
# Find the proper environment to diff against, this might be different than the initial (i.e user provided) environment
154-
# e.g it will default to prod if the plan option `always_compare_against_prod` is set.
155-
environment = _get_diff_environment(environment, state_reader, always_compare_against_prod)
156-
env = (
157-
initial_env
158-
if (initial_environment_name == environment)
159-
else state_reader.get_environment(environment)
160-
)
150+
previously_promoted_snapshot_ids = {s.snapshot_id for s in env.promoted_snapshots}
161151

162152
environment_snapshot_infos = []
163153
if env:
@@ -233,6 +223,11 @@ def create(
233223

234224
previous_environment_statements = state_reader.get_environment_statements(environment)
235225

226+
if existing_env and always_init_from_prod:
227+
previous_plan_id: t.Optional[str] = existing_env.plan_id
228+
else:
229+
previous_plan_id = env.plan_id if env and not is_new_environment else None
230+
236231
return ContextDiff(
237232
environment=environment,
238233
is_new_environment=is_new_environment,
@@ -245,9 +240,7 @@ def create(
245240
modified_snapshots=modified_snapshots,
246241
snapshots=merged_snapshots,
247242
new_snapshots=new_snapshots,
248-
previous_plan_id=initial_env.plan_id
249-
if initial_env and not is_new_environment
250-
else None,
243+
previous_plan_id=previous_plan_id,
251244
previously_promoted_snapshot_ids=previously_promoted_snapshot_ids,
252245
previous_finalized_snapshots=env.previous_finalized_snapshots if env else None,
253246
previous_requirements=env.requirements if env else {},
@@ -494,17 +487,6 @@ def text_diff(self, name: str) -> str:
494487
return ""
495488

496489

497-
def _get_diff_environment(
498-
environment: str, state_reader: StateReader, always_compare_against_prod: bool = False
499-
) -> str:
500-
if always_compare_against_prod:
501-
prod = state_reader.get_environment(c.PROD)
502-
if prod:
503-
environment = c.PROD
504-
505-
return environment.lower()
506-
507-
508490
def _build_requirements(
509491
provided_requirements: t.Dict[str, str],
510492
excluded_requirements: t.Set[str],

tests/core/test_integration.py

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6256,7 +6256,7 @@ def test_render_path_instead_of_model(tmp_path: Path):
62566256

62576257

62586258
@use_terminal_console
6259-
def test_plan_always_compare_against_prod(mocker: MockerFixture, tmp_path: Path):
6259+
def test_plan_always_init_from_prod(tmp_path: Path):
62606260
def plan_with_output(ctx: Context, environment: str):
62616261
with patch.object(logger, "info") as mock_logger:
62626262
with capture_output() as output:
@@ -6268,11 +6268,6 @@ def plan_with_output(ctx: Context, environment: str):
62686268

62696269
return output
62706270

6271-
def assert_environments(ctx: Context, input_env: str, promote_env: str, diff_env: str):
6272-
plan_builder = ctx.plan_builder(input_env)
6273-
assert plan_builder.environment_naming_info.name == promote_env
6274-
assert plan_builder.build().context_diff.environment == diff_env
6275-
62766271
models_dir = tmp_path / "models"
62776272

62786273
logger = logging.getLogger("sqlmesh.core.state_sync.db.facade")
@@ -6281,31 +6276,26 @@ def assert_environments(ctx: Context, input_env: str, promote_env: str, diff_env
62816276
tmp_path, models_dir / "a.sql", "MODEL (name test.a, kind FULL); SELECT 1 AS col"
62826277
)
62836278

6284-
config = Config(plan=PlanConfig(always_compare_against_prod=True))
6279+
config = Config(plan=PlanConfig(always_init_from_prod=True))
62856280
ctx = Context(paths=[tmp_path], config=config)
62866281

62876282
# Case 1: Neither prod nor dev exists, so dev is initialized
62886283
output = plan_with_output(ctx, "dev")
62896284

62906285
assert """`dev` environment will be initialized""" in output.stdout
6291-
assert_environments(ctx, input_env="dev", promote_env="dev", diff_env="dev")
62926286

62936287
# Case 2: Prod does not exist, so dev is updated
62946288
create_temp_file(
62956289
tmp_path, models_dir / "a.sql", "MODEL (name test.a, kind FULL); SELECT 5 AS col"
62966290
)
62976291

62986292
output = plan_with_output(ctx, "dev")
6299-
6300-
assert_environments(ctx, input_env="dev", promote_env="dev", diff_env="dev")
6301-
assert "Differences from the `dev` environment" in output.stdout
6293+
assert "`dev` environment will be initialized" in output.stdout
63026294

63036295
# Case 3: Prod is initialized, so plan comparisons moving forward should be against prod
63046296
output = plan_with_output(ctx, "prod")
63056297
assert "`prod` environment will be initialized" in output.stdout
63066298

6307-
assert_environments(ctx, input_env="prod", promote_env="prod", diff_env="prod")
6308-
63096299
# Case 4: Dev is updated with a breaking change. Prod exists now so plan comparisons moving forward should be against prod
63106300
create_temp_file(
63116301
tmp_path, models_dir / "a.sql", "MODEL (name test.a, kind FULL); SELECT 10 AS col"
@@ -6314,14 +6304,13 @@ def assert_environments(ctx: Context, input_env: str, promote_env: str, diff_env
63146304

63156305
plan = ctx.plan_builder("dev").build()
63166306

6317-
assert_environments(ctx, input_env="dev", promote_env="dev", diff_env="prod")
6318-
63196307
assert (
63206308
next(iter(plan.context_diff.snapshots.values())).change_category
63216309
== SnapshotChangeCategory.BREAKING
63226310
)
63236311

63246312
output = plan_with_output(ctx, "dev")
6313+
assert "New environment `dev` will be created from `prod`" in output.stdout
63256314
assert "Differences from the `prod` environment" in output.stdout
63266315

63276316
# Case 5: Dev is updated with a metadata change, but comparison against prod shows both the previous and the current changes
@@ -6335,14 +6324,13 @@ def assert_environments(ctx: Context, input_env: str, promote_env: str, diff_env
63356324

63366325
plan = ctx.plan_builder("dev").build()
63376326

6338-
assert_environments(ctx, input_env="dev", promote_env="dev", diff_env="prod")
6339-
63406327
assert (
63416328
next(iter(plan.context_diff.snapshots.values())).change_category
63426329
== SnapshotChangeCategory.BREAKING
63436330
)
63446331

63456332
output = plan_with_output(ctx, "dev")
6333+
assert "New environment `dev` will be created from `prod`" in output.stdout
63466334
assert "Differences from the `prod` environment" in output.stdout
63476335

63486336
assert (

0 commit comments

Comments
 (0)