@@ -912,6 +912,7 @@ def _render_and_insert_snapshot(
912912 model = snapshot .model
913913 adapter = self .get_adapter (model .gateway )
914914 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
915+ is_snapshot_deployable = deployability_index .is_deployable (snapshot )
915916
916917 queries_or_dfs = self ._render_snapshot_for_evaluation (
917918 snapshot ,
@@ -935,6 +936,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
935936 execution_time = execution_time ,
936937 physical_properties = rendered_physical_properties ,
937938 render_kwargs = create_render_kwargs ,
939+ is_snapshot_deployable = is_snapshot_deployable ,
938940 )
939941 else :
940942 logger .info (
@@ -957,6 +959,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
957959 execution_time = execution_time ,
958960 physical_properties = rendered_physical_properties ,
959961 render_kwargs = create_render_kwargs ,
962+ is_snapshot_deployable = is_snapshot_deployable ,
960963 )
961964
962965 # DataFrames, unlike SQL expressions, can provide partial results by yielding dataframes. As a result,
@@ -1155,6 +1158,7 @@ def _migrate_target_table(
11551158 allow_additive_snapshots = allow_additive_snapshots ,
11561159 ignore_destructive = snapshot .model .on_destructive_change .is_ignore ,
11571160 ignore_additive = snapshot .model .on_additive_change .is_ignore ,
1161+ deployability_index = deployability_index ,
11581162 )
11591163 finally :
11601164 if snapshot .is_materialized :
@@ -1204,6 +1208,7 @@ def _promote_snapshot(
12041208 model = snapshot .model ,
12051209 environment = environment_naming_info .name ,
12061210 snapshots = snapshots ,
1211+ snapshot = snapshot ,
12071212 ** render_kwargs ,
12081213 )
12091214
@@ -1428,6 +1433,8 @@ def _execute_create(
14281433 is_snapshot_representative = is_snapshot_representative ,
14291434 dry_run = dry_run ,
14301435 physical_properties = rendered_physical_properties ,
1436+ snapshot = snapshot ,
1437+ deployability_index = deployability_index ,
14311438 )
14321439 if run_pre_post_statements :
14331440 adapter .execute (snapshot .model .render_post_statements (** create_render_kwargs ))
@@ -1669,6 +1676,7 @@ def _apply_grants(
16691676 model : Model ,
16701677 table_name : str ,
16711678 target_layer : GrantsTargetLayer ,
1679+ is_snapshot_deployable : bool = False ,
16721680 ) -> None :
16731681 """Apply grants for a model if grants are configured.
16741682
@@ -1680,6 +1688,7 @@ def _apply_grants(
16801688 model: The SQLMesh model containing grants configuration
16811689 table_name: The target table/view name to apply grants to
16821690 target_layer: The grants application layer (physical or virtual)
1691+ is_snapshot_deployable: Whether the snapshot is deployable (targeting production)
16831692 """
16841693 grants_config = model .grants
16851694 if grants_config is None :
@@ -1693,7 +1702,16 @@ def _apply_grants(
16931702 return
16941703
16951704 model_grants_target_layer = model .grants_target_layer
1696- if not (model_grants_target_layer .is_all or model_grants_target_layer == target_layer ):
1705+
1706+ is_prod_and_dev_only = is_snapshot_deployable and model .virtual_environment_mode .is_dev_only
1707+
1708+ if not (
1709+ model_grants_target_layer .is_all
1710+ or model_grants_target_layer == target_layer
1711+ # Always apply grants in production when VDE is dev_only regardless of target_layer
1712+ # since only physical tables are created in production
1713+ or is_prod_and_dev_only
1714+ ):
16971715 logger .debug (
16981716 f"Skipping grants application for model { model .name } in { target_layer } layer"
16991717 )
@@ -1811,11 +1829,15 @@ def promote(
18111829 view_properties = model .render_virtual_properties (** render_kwargs ),
18121830 )
18131831
1832+ snapshot = kwargs ["snapshot" ]
1833+ deployability_index = kwargs ["deployability_index" ]
1834+ is_snapshot_deployable = deployability_index .is_deployable (snapshot )
1835+
18141836 # Apply grants to the physical layer (referenced table / view) after promotion
1815- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
1837+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable )
18161838
18171839 # Apply grants to the virtual layer (view) after promotion
1818- self ._apply_grants (model , view_name , GrantsTargetLayer .VIRTUAL )
1840+ self ._apply_grants (model , view_name , GrantsTargetLayer .VIRTUAL , is_snapshot_deployable )
18191841
18201842 def demote (self , view_name : str , ** kwargs : t .Any ) -> None :
18211843 logger .info ("Dropping view '%s'" , view_name )
@@ -1876,7 +1898,10 @@ def create(
18761898
18771899 # Apply grants after table creation (unless explicitly skipped by caller)
18781900 if not skip_grants :
1879- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
1901+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
1902+ self ._apply_grants (
1903+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
1904+ )
18801905
18811906 def migrate (
18821907 self ,
@@ -1904,7 +1929,13 @@ def migrate(
19041929 self .adapter .alter_table (alter_operations )
19051930
19061931 # Apply grants after schema migration
1907- self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
1932+ deployability_index = kwargs .get ("deployability_index" )
1933+ is_snapshot_deployable = (
1934+ deployability_index .is_deployable (snapshot ) if deployability_index else False
1935+ )
1936+ self ._apply_grants (
1937+ snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
1938+ )
19081939
19091940 def delete (self , name : str , ** kwargs : t .Any ) -> None :
19101941 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
@@ -1956,7 +1987,8 @@ def _replace_query_for_model(
19561987
19571988 # Apply grants after table replacement (unless explicitly skipped by caller)
19581989 if not skip_grants :
1959- self ._apply_grants (model , name , GrantsTargetLayer .PHYSICAL )
1990+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
1991+ self ._apply_grants (model , name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable )
19601992
19611993 def _get_target_and_source_columns (
19621994 self ,
@@ -2246,7 +2278,10 @@ def create(
22462278
22472279 if not skip_grants :
22482280 # Apply grants after seed table creation and data insertion
2249- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2281+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2282+ self ._apply_grants (
2283+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2284+ )
22502285 except Exception :
22512286 self .adapter .drop_table (table_name )
22522287 raise
@@ -2318,7 +2353,10 @@ def create(
23182353
23192354 if not skip_grants :
23202355 # Apply grants after SCD Type 2 table creation
2321- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2356+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2357+ self ._apply_grants (
2358+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2359+ )
23222360
23232361 def insert (
23242362 self ,
@@ -2444,7 +2482,8 @@ def insert(
24442482 )
24452483
24462484 # Apply grants after view creation / replacement
2447- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2485+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2486+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable )
24482487
24492488 def append (
24502489 self ,
@@ -2465,14 +2504,18 @@ def create(
24652504 skip_grants : bool ,
24662505 ** kwargs : t .Any ,
24672506 ) -> None :
2507+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2508+
24682509 if self .adapter .table_exists (table_name ):
24692510 # Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24702511 # binding support (because of DROP CASCADE).
24712512 logger .info ("View '%s' already exists" , table_name )
24722513
24732514 if not skip_grants :
24742515 # Always apply grants when present, even if view exists, to handle grants updates
2475- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2516+ self ._apply_grants (
2517+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2518+ )
24762519 return
24772520
24782521 logger .info ("Creating view '%s'" , table_name )
@@ -2498,7 +2541,9 @@ def create(
24982541
24992542 if not skip_grants :
25002543 # Apply grants after view creation
2501- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2544+ self ._apply_grants (
2545+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2546+ )
25022547
25032548 def migrate (
25042549 self ,
@@ -2527,7 +2572,13 @@ def migrate(
25272572 )
25282573
25292574 # Apply grants after view migration
2530- self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2575+ deployability_index = kwargs .get ("deployability_index" )
2576+ is_snapshot_deployable = (
2577+ deployability_index .is_deployable (snapshot ) if deployability_index else False
2578+ )
2579+ self ._apply_grants (
2580+ snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2581+ )
25312582
25322583 def delete (self , name : str , ** kwargs : t .Any ) -> None :
25332584 cascade = kwargs .pop ("cascade" , False )
@@ -2697,7 +2748,9 @@ def create(
26972748
26982749 # Apply grants after managed table creation
26992750 if not skip_grants :
2700- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2751+ self ._apply_grants (
2752+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2753+ )
27012754
27022755 elif not is_table_deployable :
27032756 # Only create the dev preview table as a normal table.
@@ -2737,7 +2790,9 @@ def insert(
27372790 column_descriptions = model .column_descriptions ,
27382791 table_format = model .table_format ,
27392792 )
2740- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2793+ self ._apply_grants (
2794+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2795+ )
27412796 elif not is_snapshot_deployable :
27422797 # Snapshot isnt deployable; update the preview table instead
27432798 # If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2787,8 +2842,13 @@ def migrate(
27872842 )
27882843
27892844 # Apply grants after verifying no schema changes
2790- # This ensures metadata-only grants changes are applied
2791- self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2845+ deployability_index = kwargs .get ("deployability_index" )
2846+ is_snapshot_deployable = (
2847+ deployability_index .is_deployable (snapshot ) if deployability_index else False
2848+ )
2849+ self ._apply_grants (
2850+ snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2851+ )
27922852
27932853 def delete (self , name : str , ** kwargs : t .Any ) -> None :
27942854 # a dev preview table is created as a normal table, so it needs to be dropped as a normal table
0 commit comments