@@ -920,6 +920,7 @@ def _render_and_insert_snapshot(
920920 model = snapshot .model
921921 adapter = self .get_adapter (model .gateway )
922922 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
923+ is_snapshot_deployable = deployability_index .is_deployable (snapshot )
923924
924925 queries_or_dfs = self ._render_snapshot_for_evaluation (
925926 snapshot ,
@@ -943,6 +944,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
943944 execution_time = execution_time ,
944945 physical_properties = rendered_physical_properties ,
945946 render_kwargs = create_render_kwargs ,
947+ is_snapshot_deployable = is_snapshot_deployable ,
946948 )
947949 else :
948950 logger .info (
@@ -965,6 +967,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
965967 execution_time = execution_time ,
966968 physical_properties = rendered_physical_properties ,
967969 render_kwargs = create_render_kwargs ,
970+ is_snapshot_deployable = is_snapshot_deployable ,
968971 )
969972
970973 # DataFrames, unlike SQL expressions, can provide partial results by yielding dataframes. As a result,
@@ -1173,6 +1176,7 @@ def _migrate_target_table(
11731176 allow_additive_snapshots = allow_additive_snapshots ,
11741177 ignore_destructive = snapshot .model .on_destructive_change .is_ignore ,
11751178 ignore_additive = snapshot .model .on_additive_change .is_ignore ,
1179+ deployability_index = deployability_index ,
11761180 )
11771181 finally :
11781182 if snapshot .is_materialized :
@@ -1222,6 +1226,7 @@ def _promote_snapshot(
12221226 model = snapshot .model ,
12231227 environment = environment_naming_info .name ,
12241228 snapshots = snapshots ,
1229+ snapshot = snapshot ,
12251230 ** render_kwargs ,
12261231 )
12271232
@@ -1446,6 +1451,8 @@ def _execute_create(
14461451 is_snapshot_representative = is_snapshot_representative ,
14471452 dry_run = dry_run ,
14481453 physical_properties = rendered_physical_properties ,
1454+ snapshot = snapshot ,
1455+ deployability_index = deployability_index ,
14491456 )
14501457 if run_pre_post_statements :
14511458 adapter .execute (snapshot .model .render_post_statements (** create_render_kwargs ))
@@ -1688,6 +1695,7 @@ def _apply_grants(
16881695 model : Model ,
16891696 table_name : str ,
16901697 target_layer : GrantsTargetLayer ,
1698+ is_snapshot_deployable : bool = False ,
16911699 ) -> None :
16921700 """Apply grants for a model if grants are configured.
16931701
@@ -1699,6 +1707,7 @@ def _apply_grants(
16991707 model: The SQLMesh model containing grants configuration
17001708 table_name: The target table/view name to apply grants to
17011709 target_layer: The grants application layer (physical or virtual)
1710+ is_snapshot_deployable: Whether the snapshot is deployable (targeting production)
17021711 """
17031712 grants_config = model .grants
17041713 if grants_config is None :
@@ -1712,7 +1721,16 @@ def _apply_grants(
17121721 return
17131722
17141723 model_grants_target_layer = model .grants_target_layer
1715- if not (model_grants_target_layer .is_all or model_grants_target_layer == target_layer ):
1724+
1725+ is_prod_and_dev_only = is_snapshot_deployable and model .virtual_environment_mode .is_dev_only
1726+
1727+ if not (
1728+ model_grants_target_layer .is_all
1729+ or model_grants_target_layer == target_layer
1730+ # Always apply grants in production when VDE is dev_only regardless of target_layer
1731+ # since only physical tables are created in production
1732+ or is_prod_and_dev_only
1733+ ):
17161734 logger .debug (
17171735 f"Skipping grants application for model { model .name } in { target_layer } layer"
17181736 )
@@ -1830,11 +1848,15 @@ def promote(
18301848 view_properties = model .render_virtual_properties (** render_kwargs ),
18311849 )
18321850
1851+ snapshot = kwargs ["snapshot" ]
1852+ deployability_index = kwargs ["deployability_index" ]
1853+ is_snapshot_deployable = deployability_index .is_deployable (snapshot )
1854+
18331855 # Apply grants to the physical layer (referenced table / view) after promotion
1834- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
1856+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable )
18351857
18361858 # Apply grants to the virtual layer (view) after promotion
1837- self ._apply_grants (model , view_name , GrantsTargetLayer .VIRTUAL )
1859+ self ._apply_grants (model , view_name , GrantsTargetLayer .VIRTUAL , is_snapshot_deployable )
18381860
18391861 def demote (self , view_name : str , ** kwargs : t .Any ) -> None :
18401862 logger .info ("Dropping view '%s'" , view_name )
@@ -1895,7 +1917,10 @@ def create(
18951917
18961918 # Apply grants after table creation (unless explicitly skipped by caller)
18971919 if not skip_grants :
1898- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
1920+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
1921+ self ._apply_grants (
1922+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
1923+ )
18991924
19001925 def migrate (
19011926 self ,
@@ -1923,7 +1948,13 @@ def migrate(
19231948 self .adapter .alter_table (alter_operations )
19241949
19251950 # Apply grants after schema migration
1926- self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
1951+ deployability_index = kwargs .get ("deployability_index" )
1952+ is_snapshot_deployable = (
1953+ deployability_index .is_deployable (snapshot ) if deployability_index else False
1954+ )
1955+ self ._apply_grants (
1956+ snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
1957+ )
19271958
19281959 def delete (self , name : str , ** kwargs : t .Any ) -> None :
19291960 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
@@ -1975,7 +2006,8 @@ def _replace_query_for_model(
19752006
19762007 # Apply grants after table replacement (unless explicitly skipped by caller)
19772008 if not skip_grants :
1978- self ._apply_grants (model , name , GrantsTargetLayer .PHYSICAL )
2009+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2010+ self ._apply_grants (model , name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable )
19792011
19802012 def _get_target_and_source_columns (
19812013 self ,
@@ -2265,7 +2297,10 @@ def create(
22652297
22662298 if not skip_grants :
22672299 # Apply grants after seed table creation and data insertion
2268- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2300+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2301+ self ._apply_grants (
2302+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2303+ )
22692304 except Exception :
22702305 self .adapter .drop_table (table_name )
22712306 raise
@@ -2337,7 +2372,10 @@ def create(
23372372
23382373 if not skip_grants :
23392374 # Apply grants after SCD Type 2 table creation
2340- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2375+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2376+ self ._apply_grants (
2377+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2378+ )
23412379
23422380 def insert (
23432381 self ,
@@ -2463,7 +2501,8 @@ def insert(
24632501 )
24642502
24652503 # Apply grants after view creation / replacement
2466- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2504+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2505+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable )
24672506
24682507 def append (
24692508 self ,
@@ -2484,14 +2523,18 @@ def create(
24842523 skip_grants : bool ,
24852524 ** kwargs : t .Any ,
24862525 ) -> None :
2526+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2527+
24872528 if self .adapter .table_exists (table_name ):
24882529 # Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24892530 # binding support (because of DROP CASCADE).
24902531 logger .info ("View '%s' already exists" , table_name )
24912532
24922533 if not skip_grants :
24932534 # Always apply grants when present, even if view exists, to handle grants updates
2494- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2535+ self ._apply_grants (
2536+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2537+ )
24952538 return
24962539
24972540 logger .info ("Creating view '%s'" , table_name )
@@ -2517,7 +2560,9 @@ def create(
25172560
25182561 if not skip_grants :
25192562 # Apply grants after view creation
2520- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2563+ self ._apply_grants (
2564+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2565+ )
25212566
25222567 def migrate (
25232568 self ,
@@ -2546,7 +2591,13 @@ def migrate(
25462591 )
25472592
25482593 # Apply grants after view migration
2549- self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2594+ deployability_index = kwargs .get ("deployability_index" )
2595+ is_snapshot_deployable = (
2596+ deployability_index .is_deployable (snapshot ) if deployability_index else False
2597+ )
2598+ self ._apply_grants (
2599+ snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2600+ )
25502601
25512602 def delete (self , name : str , ** kwargs : t .Any ) -> None :
25522603 cascade = kwargs .pop ("cascade" , False )
@@ -2716,7 +2767,9 @@ def create(
27162767
27172768 # Apply grants after managed table creation
27182769 if not skip_grants :
2719- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2770+ self ._apply_grants (
2771+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2772+ )
27202773
27212774 elif not is_table_deployable :
27222775 # Only create the dev preview table as a normal table.
@@ -2756,7 +2809,9 @@ def insert(
27562809 column_descriptions = model .column_descriptions ,
27572810 table_format = model .table_format ,
27582811 )
2759- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2812+ self ._apply_grants (
2813+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2814+ )
27602815 elif not is_snapshot_deployable :
27612816 # Snapshot isnt deployable; update the preview table instead
27622817 # If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2806,8 +2861,13 @@ def migrate(
28062861 )
28072862
28082863 # Apply grants after verifying no schema changes
2809- # This ensures metadata-only grants changes are applied
2810- self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2864+ deployability_index = kwargs .get ("deployability_index" )
2865+ is_snapshot_deployable = (
2866+ deployability_index .is_deployable (snapshot ) if deployability_index else False
2867+ )
2868+ self ._apply_grants (
2869+ snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2870+ )
28112871
28122872 def delete (self , name : str , ** kwargs : t .Any ) -> None :
28132873 # a dev preview table is created as a normal table, so it needs to be dropped as a normal table
0 commit comments