Skip to content

Commit c02f93b

Browse files
revise to only prompt once; move logic to console
1 parent 495e96a commit c02f93b

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
"""
@@ -824,7 +834,12 @@ def print_connection_config(
824834
) -> None:
825835
pass
826836

827-
def start_destroy(self) -> bool:
837+
def start_destroy(
838+
self,
839+
schemas_to_delete: t.Optional[t.Set[str]] = None,
840+
views_to_delete: t.Optional[t.Set[str]] = None,
841+
tables_to_delete: t.Optional[t.Set[str]] = None,
842+
) -> bool:
828843
return True
829844

830845
def stop_destroy(self, success: bool = True) -> None:
@@ -1276,22 +1291,57 @@ def stop_cleanup(self, success: bool = False) -> None:
12761291
else:
12771292
self.log_error("Cleanup failed!")
12781293

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

sqlmesh/core/context.py

Lines changed: 44 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -840,7 +840,50 @@ def run_janitor(self, ignore_ttl: bool) -> bool:
840840
def destroy(self) -> bool:
841841
success = False
842842

843-
if self.console.start_destroy():
843+
# Collect resources to be deleted
844+
environments = self.state_reader.get_environments()
845+
schemas_to_delete = set()
846+
tables_to_delete = set()
847+
views_to_delete = set()
848+
all_snapshot_infos = set()
849+
850+
# For each environment find schemas and tables
851+
for environment in environments:
852+
all_snapshot_infos.update(environment.snapshots)
853+
snapshots = self.state_reader.get_snapshots(environment.snapshots).values()
854+
for snapshot in snapshots:
855+
if snapshot.is_model and not snapshot.is_symbolic:
856+
# Get the appropriate adapter
857+
if environment.gateway_managed and snapshot.model_gateway:
858+
adapter = self.engine_adapters.get(
859+
snapshot.model_gateway, self.engine_adapter
860+
)
861+
else:
862+
adapter = self.engine_adapter
863+
864+
if environment.suffix_target.is_schema or environment.suffix_target.is_catalog:
865+
schema = snapshot.qualified_view_name.schema_for_environment(
866+
environment.naming_info, dialect=adapter.dialect
867+
)
868+
catalog = snapshot.qualified_view_name.catalog_for_environment(
869+
environment.naming_info, dialect=adapter.dialect
870+
)
871+
if catalog:
872+
schemas_to_delete.add(f"{catalog}.{schema}")
873+
else:
874+
schemas_to_delete.add(schema)
875+
876+
if environment.suffix_target.is_table:
877+
view_name = snapshot.qualified_view_name.for_environment(
878+
environment.naming_info, dialect=adapter.dialect
879+
)
880+
views_to_delete.add(view_name)
881+
882+
# Add snapshot tables
883+
table_name = snapshot.table_name()
884+
tables_to_delete.add(table_name)
885+
886+
if self.console.start_destroy(schemas_to_delete, views_to_delete, tables_to_delete):
844887
try:
845888
success = self._destroy()
846889
finally:
@@ -2705,80 +2748,6 @@ def _context_diff(
27052748
)
27062749

27072750
def _destroy(self) -> bool:
2708-
environments = self.state_reader.get_environments()
2709-
2710-
schemas_to_delete = set()
2711-
tables_to_delete = set()
2712-
views_to_delete = set()
2713-
all_snapshot_infos = set()
2714-
2715-
# For each environment find schemas and tables
2716-
for environment in environments:
2717-
all_snapshot_infos.update(environment.snapshots)
2718-
snapshots = self.state_reader.get_snapshots(environment.snapshots).values()
2719-
for snapshot in snapshots:
2720-
if snapshot.is_model and not snapshot.is_symbolic:
2721-
# Get the appropriate adapter
2722-
if environment.gateway_managed and snapshot.model_gateway:
2723-
adapter = self.engine_adapters.get(
2724-
snapshot.model_gateway, self.engine_adapter
2725-
)
2726-
else:
2727-
adapter = self.engine_adapter
2728-
2729-
if environment.suffix_target.is_schema or environment.suffix_target.is_catalog:
2730-
schema = snapshot.qualified_view_name.schema_for_environment(
2731-
environment.naming_info, dialect=adapter.dialect
2732-
)
2733-
catalog = snapshot.qualified_view_name.catalog_for_environment(
2734-
environment.naming_info, dialect=adapter.dialect
2735-
)
2736-
if catalog:
2737-
schemas_to_delete.add(f"{catalog}.{schema}")
2738-
else:
2739-
schemas_to_delete.add(schema)
2740-
2741-
if environment.suffix_target.is_table:
2742-
view_name = snapshot.qualified_view_name.for_environment(
2743-
environment.naming_info, dialect=adapter.dialect
2744-
)
2745-
views_to_delete.add(view_name)
2746-
2747-
# Add snapshot tables
2748-
table_name = snapshot.table_name()
2749-
tables_to_delete.add(table_name)
2750-
2751-
# Display what will be deleted
2752-
self.console.log_error("\n" + "=" * 50 + "\n")
2753-
if schemas_to_delete:
2754-
self.console.log_error("Schemas to be deleted:")
2755-
for schema in sorted(schemas_to_delete):
2756-
self.console.log_error(f" • {schema}")
2757-
2758-
if views_to_delete:
2759-
self.console.log_error("\nEnvironment views to be deleted:")
2760-
for view in sorted(views_to_delete):
2761-
self.console.log_error(f" • {view}")
2762-
2763-
if tables_to_delete:
2764-
self.console.log_error("\nSnapshot tables to be deleted:")
2765-
for table in sorted(tables_to_delete):
2766-
self.console.log_error(f" • {table}")
2767-
2768-
self.console.log_error("\nAll SQLMesh state tables will be deleted")
2769-
self.console.log_error("\n" + "=" * 50 + "\n")
2770-
2771-
# Final confirmation with stronger warning
2772-
self.console.log_error(
2773-
"!!! CRITICAL WARNING: This action will PERMANENTLY DELETE ALL the above resources!\n"
2774-
"This includes ALL tables, views and schemas managed by SQLMesh AND potentially\n"
2775-
"external resources created by other tools in these schemas. This action is IRREVERSIBLE!\n"
2776-
)
2777-
2778-
if not self.console._confirm("Are you ABSOLUTELY SURE you want to proceed with deletion?"): # type: ignore
2779-
self.console.log_error("Destroy operation cancelled.")
2780-
return False
2781-
27822751
# Invalidate all environments, including prod
27832752
for environment in self.state_reader.get_environments():
27842753
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)