Skip to content

Commit 5e314ee

Browse files
authored
Merge branch 'main' into fix/to-datetime-out-of-range
2 parents 6bcb274 + 7a09da6 commit 5e314ee

4 files changed

Lines changed: 46 additions & 3 deletions

File tree

sqlmesh/cli/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ def run(ctx: click.Context, environment: t.Optional[str] = None, **kwargs: t.Any
641641
def invalidate(ctx: click.Context, environment: str, **kwargs: t.Any) -> None:
642642
"""Invalidate the target environment, forcing its removal during the next run of the janitor process."""
643643
context = ctx.obj
644-
context.invalidate_environment(environment, **kwargs)
644+
context.invalidate_environment(environment, must_exist=True, **kwargs)
645645

646646

647647
@cli.command("janitor")

sqlmesh/core/context.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1870,15 +1870,24 @@ def apply(
18701870
)
18711871

18721872
@python_api_analytics
1873-
def invalidate_environment(self, name: str, sync: bool = False) -> None:
1873+
def invalidate_environment(
1874+
self, name: str, sync: bool = False, must_exist: bool = False
1875+
) -> None:
18741876
"""Invalidates the target environment by setting its expiration timestamp to now.
18751877
18761878
Args:
18771879
name: The name of the environment to invalidate.
18781880
sync: If True, the call blocks until the environment is deleted. Otherwise, the environment will
18791881
be deleted asynchronously by the janitor process.
1882+
must_exist: If True, raise if the environment doesn't exist instead of silently doing nothing.
1883+
Used by the user-facing entry points, where a mistyped name should be reported rather than
1884+
look like it succeeded. Internal callers such as
1885+
`GithubController.try_invalidate_pr_environment` rely on the default no-op behavior, since
1886+
a PR environment may never have been created.
18801887
"""
18811888
name = Environment.sanitize_name(name)
1889+
if must_exist and self.state_sync.get_environment(name) is None:
1890+
raise SQLMeshError(f"Environment '{name}' was not found.")
18821891
self.state_sync.invalidate_environment(name)
18831892
if sync:
18841893
self._cleanup_environments(name=name)

sqlmesh/magics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -983,7 +983,7 @@ def diff(self, context: Context, line: str) -> None:
983983
def invalidate(self, context: Context, line: str) -> None:
984984
"""Invalidate the target environment, forcing its removal during the next run of the janitor process."""
985985
args = parse_argstring(self.invalidate, line)
986-
context.invalidate_environment(args.environment)
986+
context.invalidate_environment(args.environment, must_exist=True)
987987

988988
@magic_arguments()
989989
@argument(

tests/core/test_context.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1928,6 +1928,40 @@ def test_invalidate_environment_no_sync_skips_cleanup(sushi_context, mocker: Moc
19281928
state_sync_mock.delete_expired_environments.assert_not_called()
19291929

19301930

1931+
def test_invalidate_environment_nonexistent_raises(sushi_context, mocker: MockerFixture) -> None:
1932+
"""Invalidating an environment that does not exist should error instead of
1933+
reporting success, so a mistyped name is caught rather than silently accepted."""
1934+
state_sync_mock = mocker.patch.object(
1935+
type(sushi_context), "state_sync", new_callable=mocker.PropertyMock
1936+
).return_value
1937+
state_sync_mock.get_environment.return_value = None
1938+
1939+
with pytest.raises(SQLMeshError, match="Environment 'doesnotexist' was not found"):
1940+
sushi_context.invalidate_environment("doesnotexist", must_exist=True)
1941+
1942+
state_sync_mock.invalidate_environment.assert_not_called()
1943+
1944+
1945+
def test_invalidate_environment_nonexistent_is_a_noop_by_default(
1946+
sushi_context, mocker: MockerFixture
1947+
) -> None:
1948+
"""Without must_exist, invalidating a missing environment stays a no-op.
1949+
1950+
Internal callers depend on this. `GithubController.try_invalidate_pr_environment`
1951+
invalidates the PR environment after a prod deploy, and that environment may never
1952+
have been created — a forward-only deploy, for instance. Raising there turns a
1953+
routine cleanup into a failed deploy.
1954+
"""
1955+
state_sync_mock = mocker.patch.object(
1956+
type(sushi_context), "state_sync", new_callable=mocker.PropertyMock
1957+
).return_value
1958+
state_sync_mock.get_environment.return_value = None
1959+
1960+
sushi_context.invalidate_environment("doesnotexist")
1961+
1962+
state_sync_mock.invalidate_environment.assert_called_once_with("doesnotexist")
1963+
1964+
19311965
@pytest.mark.slow
19321966
def test_plan_default_end(sushi_context_pre_scheduling: Context):
19331967
prod_plan_builder = sushi_context_pre_scheduling.plan_builder("prod")

0 commit comments

Comments
 (0)