Skip to content

Commit cfc115c

Browse files
committed
Switch impl to always_init_from_prod
1 parent f10161f commit cfc115c

File tree

7 files changed

+38
-75
lines changed

7 files changed

+38
-75
lines changed

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
@@ -221,7 +221,6 @@ def show_environment_difference_summary(
221221
self,
222222
context_diff: ContextDiff,
223223
no_diff: bool = True,
224-
environment: t.Optional[str] = None,
225224
) -> None:
226225
"""Displays a summary of differences for the environment."""
227226

@@ -648,7 +647,6 @@ def show_environment_difference_summary(
648647
self,
649648
context_diff: ContextDiff,
650649
no_diff: bool = True,
651-
environment: t.Optional[str] = None,
652650
) -> None:
653651
pass
654652

@@ -1528,7 +1526,6 @@ def show_environment_difference_summary(
15281526
self,
15291527
context_diff: ContextDiff,
15301528
no_diff: bool = True,
1531-
environment: t.Optional[str] = None,
15321529
) -> None:
15331530
"""Shows a summary of the environment differences.
15341531
@@ -1538,11 +1535,10 @@ def show_environment_difference_summary(
15381535
environment: The initial target environment
15391536
"""
15401537
if context_diff.is_new_environment:
1541-
new_environment = environment or context_diff.environment
15421538
msg = (
1543-
f"\n`{new_environment}` environment will be initialized"
1539+
f"\n`{context_diff.environment}` environment will be initialized"
15441540
if not context_diff.create_from_env_exists
1545-
else f"\nNew environment `{new_environment}` will be created from `{context_diff.create_from}`"
1541+
else f"\nNew environment `{context_diff.environment}` will be created from `{context_diff.create_from}`"
15461542
)
15471543
self._print(Tree(f"[bold]{msg}\n"))
15481544
if not context_diff.has_snapshot_changes:
@@ -1793,7 +1789,6 @@ def _prompt_categorize(
17931789
self.show_environment_difference_summary(
17941790
plan.context_diff,
17951791
no_diff=no_diff,
1796-
environment=plan_builder.environment_naming_info.name,
17971792
)
17981793

17991794
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:
@@ -3504,7 +3497,6 @@ def show_environment_difference_summary(
35043497
self,
35053498
context_diff: ContextDiff,
35063499
no_diff: bool = True,
3507-
environment: t.Optional[str] = None,
35083500
) -> None:
35093501
self._write("Environment Difference Summary:")
35103502

sqlmesh/core/context.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,7 +1484,7 @@ def plan_builder(
14841484
or (backfill_models is not None and not backfill_models),
14851485
ensure_finalized_snapshots=self.config.plan.use_finalized_state,
14861486
diff_rendered=diff_rendered,
1487-
always_compare_against_prod=self.config.plan.always_compare_against_prod,
1487+
always_init_from_prod=self.config.plan.always_init_from_prod,
14881488
)
14891489
modified_model_names = {
14901490
*context_diff.modified_snapshots,
@@ -1630,7 +1630,6 @@ def diff(self, environment: t.Optional[str] = None, detailed: bool = False) -> b
16301630
self.console.show_environment_difference_summary(
16311631
context_diff,
16321632
no_diff=not detailed,
1633-
environment=environment,
16341633
)
16351634
if context_diff.has_changes:
16361635
self.console.show_model_difference_summary(
@@ -2610,7 +2609,7 @@ def _context_diff(
26102609
force_no_diff: bool = False,
26112610
ensure_finalized_snapshots: bool = False,
26122611
diff_rendered: bool = False,
2613-
always_compare_against_prod: bool = False,
2612+
always_init_from_prod: bool = False,
26142613
) -> ContextDiff:
26152614
environment = Environment.sanitize_name(environment)
26162615
if force_no_diff:
@@ -2628,7 +2627,7 @@ def _context_diff(
26282627
environment_statements=self._environment_statements,
26292628
gateway_managed_virtual_layer=self.config.gateway_managed_virtual_layer,
26302629
infer_python_dependencies=self.config.infer_python_dependencies,
2631-
always_compare_against_prod=always_compare_against_prod,
2630+
always_init_from_prod=always_init_from_prod,
26322631
)
26332632

26342633
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
@@ -6212,7 +6212,7 @@ def test_render_path_instead_of_model(tmp_path: Path):
62126212

62136213

62146214
@use_terminal_console
6215-
def test_plan_always_compare_against_prod(mocker: MockerFixture, tmp_path: Path):
6215+
def test_plan_always_init_from_prod(tmp_path: Path):
62166216
def plan_with_output(ctx: Context, environment: str):
62176217
with patch.object(logger, "info") as mock_logger:
62186218
with capture_output() as output:
@@ -6224,11 +6224,6 @@ def plan_with_output(ctx: Context, environment: str):
62246224

62256225
return output
62266226

6227-
def assert_environments(ctx: Context, input_env: str, promote_env: str, diff_env: str):
6228-
plan_builder = ctx.plan_builder(input_env)
6229-
assert plan_builder.environment_naming_info.name == promote_env
6230-
assert plan_builder.build().context_diff.environment == diff_env
6231-
62326227
models_dir = tmp_path / "models"
62336228

62346229
logger = logging.getLogger("sqlmesh.core.state_sync.db.facade")
@@ -6237,31 +6232,26 @@ def assert_environments(ctx: Context, input_env: str, promote_env: str, diff_env
62376232
tmp_path, models_dir / "a.sql", "MODEL (name test.a, kind FULL); SELECT 1 AS col"
62386233
)
62396234

6240-
config = Config(plan=PlanConfig(always_compare_against_prod=True))
6235+
config = Config(plan=PlanConfig(always_init_from_prod=True))
62416236
ctx = Context(paths=[tmp_path], config=config)
62426237

62436238
# Case 1: Neither prod nor dev exists, so dev is initialized
62446239
output = plan_with_output(ctx, "dev")
62456240

62466241
assert """`dev` environment will be initialized""" in output.stdout
6247-
assert_environments(ctx, input_env="dev", promote_env="dev", diff_env="dev")
62486242

62496243
# Case 2: Prod does not exist, so dev is updated
62506244
create_temp_file(
62516245
tmp_path, models_dir / "a.sql", "MODEL (name test.a, kind FULL); SELECT 5 AS col"
62526246
)
62536247

62546248
output = plan_with_output(ctx, "dev")
6255-
6256-
assert_environments(ctx, input_env="dev", promote_env="dev", diff_env="dev")
6257-
assert "Differences from the `dev` environment" in output.stdout
6249+
assert "`dev` environment will be initialized" in output.stdout
62586250

62596251
# Case 3: Prod is initialized, so plan comparisons moving forward should be against prod
62606252
output = plan_with_output(ctx, "prod")
62616253
assert "`prod` environment will be initialized" in output.stdout
62626254

6263-
assert_environments(ctx, input_env="prod", promote_env="prod", diff_env="prod")
6264-
62656255
# Case 4: Dev is updated with a breaking change. Prod exists now so plan comparisons moving forward should be against prod
62666256
create_temp_file(
62676257
tmp_path, models_dir / "a.sql", "MODEL (name test.a, kind FULL); SELECT 10 AS col"
@@ -6270,14 +6260,13 @@ def assert_environments(ctx: Context, input_env: str, promote_env: str, diff_env
62706260

62716261
plan = ctx.plan_builder("dev").build()
62726262

6273-
assert_environments(ctx, input_env="dev", promote_env="dev", diff_env="prod")
6274-
62756263
assert (
62766264
next(iter(plan.context_diff.snapshots.values())).change_category
62776265
== SnapshotChangeCategory.BREAKING
62786266
)
62796267

62806268
output = plan_with_output(ctx, "dev")
6269+
assert "New environment `dev` will be created from `prod`" in output.stdout
62816270
assert "Differences from the `prod` environment" in output.stdout
62826271

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

62926281
plan = ctx.plan_builder("dev").build()
62936282

6294-
assert_environments(ctx, input_env="dev", promote_env="dev", diff_env="prod")
6295-
62966283
assert (
62976284
next(iter(plan.context_diff.snapshots.values())).change_category
62986285
== SnapshotChangeCategory.BREAKING
62996286
)
63006287

63016288
output = plan_with_output(ctx, "dev")
6289+
assert "New environment `dev` will be created from `prod`" in output.stdout
63026290
assert "Differences from the `prod` environment" in output.stdout
63036291

63046292
assert (

0 commit comments

Comments
 (0)