Skip to content

Commit ee82545

Browse files
revise to only prompt once; move logic to console
1 parent 96e6615 commit ee82545

File tree

3 files changed

+107
-90
lines changed

3 files changed

+107
-90
lines changed

sqlmesh/core/console.py

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,19 @@ class DestroyConsole(abc.ABC):
186186
"""Console for describing a destroy operation"""
187187

188188
@abc.abstractmethod
189-
def start_destroy(self) -> bool:
189+
def start_destroy(
190+
self,
191+
schemas_to_delete: t.Optional[t.Set[str]] = None,
192+
views_to_delete: t.Optional[t.Set[str]] = None,
193+
tables_to_delete: t.Optional[t.Set[str]] = None,
194+
) -> bool:
190195
"""Start a destroy operation.
191196
197+
Args:
198+
schemas_to_delete: Set of schemas that will be deleted
199+
views_to_delete: Set of views that will be deleted
200+
tables_to_delete: Set of tables that will be deleted
201+
192202
Returns:
193203
Whether or not the destroy operation should proceed
194204
"""
@@ -830,7 +840,12 @@ def print_connection_config(
830840
) -> None:
831841
pass
832842

833-
def start_destroy(self) -> bool:
843+
def start_destroy(
844+
self,
845+
schemas_to_delete: t.Optional[t.Set[str]] = None,
846+
views_to_delete: t.Optional[t.Set[str]] = None,
847+
tables_to_delete: t.Optional[t.Set[str]] = None,
848+
) -> bool:
834849
return True
835850

836851
def stop_destroy(self, success: bool = True) -> None:
@@ -1282,22 +1297,57 @@ def stop_cleanup(self, success: bool = False) -> None:
12821297
else:
12831298
self.log_error("Cleanup failed!")
12841299

1285-
def start_destroy(self) -> bool:
1300+
def start_destroy(
1301+
self,
1302+
schemas_to_delete: t.Optional[t.Set[str]] = None,
1303+
views_to_delete: t.Optional[t.Set[str]] = None,
1304+
tables_to_delete: t.Optional[t.Set[str]] = None,
1305+
) -> bool:
12861306
self.log_error(
12871307
(
12881308
"!!! EXTREME CAUTION: DESTRUCTIVE OPERATION !!!\n\n"
1289-
"The 'destroy' command will PERMANENTLY DELETE:\n"
1309+
"The 'destroy' command will DELETE:\n"
12901310
" • ALL state tables and metadata\n"
12911311
" • ALL SQLMesh cache and build artifacts\n"
12921312
" • ALL tables and views in the project's schemas/datasets\n"
12931313
" • ALL schemas/datasets managed by SQLMesh in this project\n\n"
12941314
"!!! WARNING: This includes external tables created or managed by other tools !!!\n\n"
1295-
"The operation is irreversible and may disrupt any currently running or scheduled plans.\n"
1315+
"The operation may disrupt any currently running or scheduled plans.\n"
12961316
"Only use this command when you intend to COMPLETELY DESTROY the project.\n"
12971317
)
12981318
)
1299-
if not self._confirm("Do you understand the risks and want to see what will be deleted?"):
1300-
self.log_error("Destroy aborted!")
1319+
1320+
# Display what will be deleted
1321+
if schemas_to_delete or views_to_delete or tables_to_delete:
1322+
self.log_error("\n" + "=" * 50 + "\n")
1323+
if schemas_to_delete:
1324+
self.log_error("Schemas to be deleted:")
1325+
for schema in sorted(schemas_to_delete):
1326+
self.log_error(f" • {schema}")
1327+
1328+
if views_to_delete:
1329+
self.log_error("\nEnvironment views to be deleted:")
1330+
for view in sorted(views_to_delete):
1331+
self.log_error(f" • {view}")
1332+
1333+
if tables_to_delete:
1334+
self.log_error("\nSnapshot tables to be deleted:")
1335+
for table in sorted(tables_to_delete):
1336+
self.log_error(f" • {table}")
1337+
1338+
self.log_error("\nAll SQLMesh state tables will be deleted")
1339+
self.log_error("\n" + "=" * 50 + "\n")
1340+
1341+
# Final confirmation with stronger warning
1342+
self.log_error(
1343+
"!!! WARNING: This action will DELETE ALL the above resources managed by SQLMesh\n"
1344+
"AND potentially external resources created by other tools in these schemas !!!\n"
1345+
)
1346+
1347+
if not self._confirm(
1348+
"Do you understand the risks and are you ABSOLUTELY SURE you want to proceed with deletion?"
1349+
):
1350+
self.log_error("Destroy operation cancelled.")
13011351
return False
13021352
return True
13031353

sqlmesh/core/context.py

Lines changed: 44 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,50 @@ def run_janitor(self, ignore_ttl: bool) -> bool:
858858
def destroy(self) -> bool:
859859
success = False
860860

861-
if self.console.start_destroy():
861+
# Collect resources to be deleted
862+
environments = self.state_reader.get_environments()
863+
schemas_to_delete = set()
864+
tables_to_delete = set()
865+
views_to_delete = set()
866+
all_snapshot_infos = set()
867+
868+
# For each environment find schemas and tables
869+
for environment in environments:
870+
all_snapshot_infos.update(environment.snapshots)
871+
snapshots = self.state_reader.get_snapshots(environment.snapshots).values()
872+
for snapshot in snapshots:
873+
if snapshot.is_model and not snapshot.is_symbolic:
874+
# Get the appropriate adapter
875+
if environment.gateway_managed and snapshot.model_gateway:
876+
adapter = self.engine_adapters.get(
877+
snapshot.model_gateway, self.engine_adapter
878+
)
879+
else:
880+
adapter = self.engine_adapter
881+
882+
if environment.suffix_target.is_schema or environment.suffix_target.is_catalog:
883+
schema = snapshot.qualified_view_name.schema_for_environment(
884+
environment.naming_info, dialect=adapter.dialect
885+
)
886+
catalog = snapshot.qualified_view_name.catalog_for_environment(
887+
environment.naming_info, dialect=adapter.dialect
888+
)
889+
if catalog:
890+
schemas_to_delete.add(f"{catalog}.{schema}")
891+
else:
892+
schemas_to_delete.add(schema)
893+
894+
if environment.suffix_target.is_table:
895+
view_name = snapshot.qualified_view_name.for_environment(
896+
environment.naming_info, dialect=adapter.dialect
897+
)
898+
views_to_delete.add(view_name)
899+
900+
# Add snapshot tables
901+
table_name = snapshot.table_name()
902+
tables_to_delete.add(table_name)
903+
904+
if self.console.start_destroy(schemas_to_delete, views_to_delete, tables_to_delete):
862905
try:
863906
success = self._destroy()
864907
finally:
@@ -2723,80 +2766,6 @@ def _context_diff(
27232766
)
27242767

27252768
def _destroy(self) -> bool:
2726-
environments = self.state_reader.get_environments()
2727-
2728-
schemas_to_delete = set()
2729-
tables_to_delete = set()
2730-
views_to_delete = set()
2731-
all_snapshot_infos = set()
2732-
2733-
# For each environment find schemas and tables
2734-
for environment in environments:
2735-
all_snapshot_infos.update(environment.snapshots)
2736-
snapshots = self.state_reader.get_snapshots(environment.snapshots).values()
2737-
for snapshot in snapshots:
2738-
if snapshot.is_model and not snapshot.is_symbolic:
2739-
# Get the appropriate adapter
2740-
if environment.gateway_managed and snapshot.model_gateway:
2741-
adapter = self.engine_adapters.get(
2742-
snapshot.model_gateway, self.engine_adapter
2743-
)
2744-
else:
2745-
adapter = self.engine_adapter
2746-
2747-
if environment.suffix_target.is_schema or environment.suffix_target.is_catalog:
2748-
schema = snapshot.qualified_view_name.schema_for_environment(
2749-
environment.naming_info, dialect=adapter.dialect
2750-
)
2751-
catalog = snapshot.qualified_view_name.catalog_for_environment(
2752-
environment.naming_info, dialect=adapter.dialect
2753-
)
2754-
if catalog:
2755-
schemas_to_delete.add(f"{catalog}.{schema}")
2756-
else:
2757-
schemas_to_delete.add(schema)
2758-
2759-
if environment.suffix_target.is_table:
2760-
view_name = snapshot.qualified_view_name.for_environment(
2761-
environment.naming_info, dialect=adapter.dialect
2762-
)
2763-
views_to_delete.add(view_name)
2764-
2765-
# Add snapshot tables
2766-
table_name = snapshot.table_name()
2767-
tables_to_delete.add(table_name)
2768-
2769-
# Display what will be deleted
2770-
self.console.log_error("\n" + "=" * 50 + "\n")
2771-
if schemas_to_delete:
2772-
self.console.log_error("Schemas to be deleted:")
2773-
for schema in sorted(schemas_to_delete):
2774-
self.console.log_error(f" • {schema}")
2775-
2776-
if views_to_delete:
2777-
self.console.log_error("\nEnvironment views to be deleted:")
2778-
for view in sorted(views_to_delete):
2779-
self.console.log_error(f" • {view}")
2780-
2781-
if tables_to_delete:
2782-
self.console.log_error("\nSnapshot tables to be deleted:")
2783-
for table in sorted(tables_to_delete):
2784-
self.console.log_error(f" • {table}")
2785-
2786-
self.console.log_error("\nAll SQLMesh state tables will be deleted")
2787-
self.console.log_error("\n" + "=" * 50 + "\n")
2788-
2789-
# Final confirmation with stronger warning
2790-
self.console.log_error(
2791-
"!!! CRITICAL WARNING: This action will PERMANENTLY DELETE ALL the above resources!\n"
2792-
"This includes ALL tables, views and schemas managed by SQLMesh AND potentially\n"
2793-
"external resources created by other tools in these schemas. This action is IRREVERSIBLE!\n"
2794-
)
2795-
2796-
if not self.console._confirm("Are you ABSOLUTELY SURE you want to proceed with deletion?"): # type: ignore
2797-
self.console.log_error("Destroy operation cancelled.")
2798-
return False
2799-
28002769
# Invalidate all environments, including prod
28012770
for environment in self.state_reader.get_environments():
28022771
self.state_sync.invalidate_environment(name=environment.name, protect_prod=False)

tests/integrations/jupyter/test_magics.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -900,31 +900,29 @@ def test_destroy(
900900
expected_messages = [
901901
(
902902
"!!! EXTREME CAUTION: DESTRUCTIVE OPERATION !!!\n\n"
903-
"The 'destroy' command will PERMANENTLY DELETE:\n"
903+
"The 'destroy' command will DELETE:\n"
904904
" • ALL state tables and metadata\n"
905905
" • ALL SQLMesh cache and build artifacts\n"
906906
" • ALL tables and views in the project's schemas/datasets\n"
907907
" • ALL schemas/datasets managed by SQLMesh in this project\n\n"
908908
"!!! WARNING: This includes external tables created or managed by other tools !!!\n\n"
909-
"The operation is irreversible and may disrupt any currently running or scheduled plans.\n"
909+
"The operation may disrupt any currently running or scheduled plans.\n"
910910
"Only use this command when you intend to COMPLETELY DESTROY the project."
911911
),
912-
"Do you understand the risks and want to see what will be deleted? [y/n]:",
913912
"Schemas to be deleted:",
914913
"• memory.sushi",
915914
"Snapshot tables to be deleted:",
916915
"All SQLMesh state tables will be deleted",
916+
"==================================================",
917917
(
918-
"!!! CRITICAL WARNING: This action will PERMANENTLY DELETE ALL the above resources!\n"
919-
"This includes ALL tables, views and schemas managed by SQLMesh AND potentially\n"
920-
"external resources created by other tools in these schemas. This action is IRREVERSIBLE!"
918+
"!!! WARNING: This action will DELETE ALL the above resources managed by SQLMesh\n"
919+
"AND potentially external resources created by other tools in these schemas !!!"
921920
),
922-
"Are you ABSOLUTELY SURE you want to proceed with deletion? [y/n]:",
921+
"Do you understand the risks and are you ABSOLUTELY SURE you want to proceed with deletion? [y/n]:",
923922
"Environment 'prod' invalidated.",
924923
"Deleted object memory.sushi",
925924
'Deleted object "memory"."raw"."model1"',
926925
'Deleted object "memory"."raw"."model2"',
927-
'Deleted object "memory"."raw"."demographics"',
928926
"State tables removed.",
929927
"Destroy completed successfully.",
930928
]

0 commit comments

Comments
 (0)