@@ -922,6 +922,7 @@ def _render_and_insert_snapshot(
922922 model = snapshot .model
923923 adapter = self .get_adapter (model .gateway )
924924 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
925+ is_snapshot_deployable = deployability_index .is_deployable (snapshot )
925926
926927 queries_or_dfs = self ._render_snapshot_for_evaluation (
927928 snapshot ,
@@ -945,6 +946,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
945946 execution_time = execution_time ,
946947 physical_properties = rendered_physical_properties ,
947948 render_kwargs = create_render_kwargs ,
949+ is_snapshot_deployable = is_snapshot_deployable ,
948950 )
949951 else :
950952 logger .info (
@@ -967,6 +969,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
967969 execution_time = execution_time ,
968970 physical_properties = rendered_physical_properties ,
969971 render_kwargs = create_render_kwargs ,
972+ is_snapshot_deployable = is_snapshot_deployable ,
970973 )
971974
972975 # DataFrames, unlike SQL expressions, can provide partial results by yielding dataframes. As a result,
@@ -1175,6 +1178,7 @@ def _migrate_target_table(
11751178 allow_additive_snapshots = allow_additive_snapshots ,
11761179 ignore_destructive = snapshot .model .on_destructive_change .is_ignore ,
11771180 ignore_additive = snapshot .model .on_additive_change .is_ignore ,
1181+ deployability_index = deployability_index ,
11781182 )
11791183 finally :
11801184 if snapshot .is_materialized :
@@ -1224,6 +1228,7 @@ def _promote_snapshot(
12241228 model = snapshot .model ,
12251229 environment = environment_naming_info .name ,
12261230 snapshots = snapshots ,
1231+ snapshot = snapshot ,
12271232 ** render_kwargs ,
12281233 )
12291234
@@ -1448,6 +1453,8 @@ def _execute_create(
14481453 is_snapshot_representative = is_snapshot_representative ,
14491454 dry_run = dry_run ,
14501455 physical_properties = rendered_physical_properties ,
1456+ snapshot = snapshot ,
1457+ deployability_index = deployability_index ,
14511458 )
14521459 if run_pre_post_statements :
14531460 adapter .execute (snapshot .model .render_post_statements (** create_render_kwargs ))
@@ -1690,6 +1697,7 @@ def _apply_grants(
16901697 model : Model ,
16911698 table_name : str ,
16921699 target_layer : GrantsTargetLayer ,
1700+ is_snapshot_deployable : bool = False ,
16931701 ) -> None :
16941702 """Apply grants for a model if grants are configured.
16951703
@@ -1701,6 +1709,7 @@ def _apply_grants(
17011709 model: The SQLMesh model containing grants configuration
17021710 table_name: The target table/view name to apply grants to
17031711 target_layer: The grants application layer (physical or virtual)
1712+ is_snapshot_deployable: Whether the snapshot is deployable (targeting production)
17041713 """
17051714 grants_config = model .grants
17061715 if grants_config is None :
@@ -1714,7 +1723,16 @@ def _apply_grants(
17141723 return
17151724
17161725 model_grants_target_layer = model .grants_target_layer
1717- if not (model_grants_target_layer .is_all or model_grants_target_layer == target_layer ):
1726+
1727+ is_prod_and_dev_only = is_snapshot_deployable and model .virtual_environment_mode .is_dev_only
1728+
1729+ if not (
1730+ model_grants_target_layer .is_all
1731+ or model_grants_target_layer == target_layer
1732+ # Always apply grants in production when VDE is dev_only regardless of target_layer
1733+ # since only physical tables are created in production
1734+ or is_prod_and_dev_only
1735+ ):
17181736 logger .debug (
17191737 f"Skipping grants application for model { model .name } in { target_layer } layer"
17201738 )
@@ -1832,11 +1850,15 @@ def promote(
18321850 view_properties = model .render_virtual_properties (** render_kwargs ),
18331851 )
18341852
1853+ snapshot = kwargs ["snapshot" ]
1854+ deployability_index = kwargs ["deployability_index" ]
1855+ is_snapshot_deployable = deployability_index .is_deployable (snapshot )
1856+
18351857 # Apply grants to the physical layer (referenced table / view) after promotion
1836- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
1858+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable )
18371859
18381860 # Apply grants to the virtual layer (view) after promotion
1839- self ._apply_grants (model , view_name , GrantsTargetLayer .VIRTUAL )
1861+ self ._apply_grants (model , view_name , GrantsTargetLayer .VIRTUAL , is_snapshot_deployable )
18401862
18411863 def demote (self , view_name : str , ** kwargs : t .Any ) -> None :
18421864 logger .info ("Dropping view '%s'" , view_name )
@@ -1897,7 +1919,10 @@ def create(
18971919
18981920 # Apply grants after table creation (unless explicitly skipped by caller)
18991921 if not skip_grants :
1900- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
1922+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
1923+ self ._apply_grants (
1924+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
1925+ )
19011926
19021927 def migrate (
19031928 self ,
@@ -1925,7 +1950,13 @@ def migrate(
19251950 self .adapter .alter_table (alter_operations )
19261951
19271952 # Apply grants after schema migration
1928- self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
1953+ deployability_index = kwargs .get ("deployability_index" )
1954+ is_snapshot_deployable = (
1955+ deployability_index .is_deployable (snapshot ) if deployability_index else False
1956+ )
1957+ self ._apply_grants (
1958+ snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
1959+ )
19291960
19301961 def delete (self , name : str , ** kwargs : t .Any ) -> None :
19311962 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
@@ -1977,7 +2008,8 @@ def _replace_query_for_model(
19772008
19782009 # Apply grants after table replacement (unless explicitly skipped by caller)
19792010 if not skip_grants :
1980- self ._apply_grants (model , name , GrantsTargetLayer .PHYSICAL )
2011+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2012+ self ._apply_grants (model , name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable )
19812013
19822014 def _get_target_and_source_columns (
19832015 self ,
@@ -2267,7 +2299,10 @@ def create(
22672299
22682300 if not skip_grants :
22692301 # Apply grants after seed table creation and data insertion
2270- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2302+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2303+ self ._apply_grants (
2304+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2305+ )
22712306 except Exception :
22722307 self .adapter .drop_table (table_name )
22732308 raise
@@ -2351,7 +2386,10 @@ def create(
23512386
23522387 if not skip_grants :
23532388 # Apply grants after SCD Type 2 table creation
2354- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2389+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2390+ self ._apply_grants (
2391+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2392+ )
23552393
23562394 def insert (
23572395 self ,
@@ -2477,7 +2515,8 @@ def insert(
24772515 )
24782516
24792517 # Apply grants after view creation / replacement
2480- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2518+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2519+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable )
24812520
24822521 def append (
24832522 self ,
@@ -2498,14 +2537,18 @@ def create(
24982537 skip_grants : bool ,
24992538 ** kwargs : t .Any ,
25002539 ) -> None :
2540+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2541+
25012542 if self .adapter .table_exists (table_name ):
25022543 # Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
25032544 # binding support (because of DROP CASCADE).
25042545 logger .info ("View '%s' already exists" , table_name )
25052546
25062547 if not skip_grants :
25072548 # Always apply grants when present, even if view exists, to handle grants updates
2508- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2549+ self ._apply_grants (
2550+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2551+ )
25092552 return
25102553
25112554 logger .info ("Creating view '%s'" , table_name )
@@ -2531,7 +2574,9 @@ def create(
25312574
25322575 if not skip_grants :
25332576 # Apply grants after view creation
2534- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2577+ self ._apply_grants (
2578+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2579+ )
25352580
25362581 def migrate (
25372582 self ,
@@ -2560,7 +2605,13 @@ def migrate(
25602605 )
25612606
25622607 # Apply grants after view migration
2563- self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2608+ deployability_index = kwargs .get ("deployability_index" )
2609+ is_snapshot_deployable = (
2610+ deployability_index .is_deployable (snapshot ) if deployability_index else False
2611+ )
2612+ self ._apply_grants (
2613+ snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2614+ )
25642615
25652616 def delete (self , name : str , ** kwargs : t .Any ) -> None :
25662617 cascade = kwargs .pop ("cascade" , False )
@@ -2730,7 +2781,9 @@ def create(
27302781
27312782 # Apply grants after managed table creation
27322783 if not skip_grants :
2733- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2784+ self ._apply_grants (
2785+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2786+ )
27342787
27352788 elif not is_table_deployable :
27362789 # Only create the dev preview table as a normal table.
@@ -2770,7 +2823,9 @@ def insert(
27702823 column_descriptions = model .column_descriptions ,
27712824 table_format = model .table_format ,
27722825 )
2773- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2826+ self ._apply_grants (
2827+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2828+ )
27742829 elif not is_snapshot_deployable :
27752830 # Snapshot isnt deployable; update the preview table instead
27762831 # If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2820,8 +2875,13 @@ def migrate(
28202875 )
28212876
28222877 # Apply grants after verifying no schema changes
2823- # This ensures metadata-only grants changes are applied
2824- self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2878+ deployability_index = kwargs .get ("deployability_index" )
2879+ is_snapshot_deployable = (
2880+ deployability_index .is_deployable (snapshot ) if deployability_index else False
2881+ )
2882+ self ._apply_grants (
2883+ snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2884+ )
28252885
28262886 def delete (self , name : str , ** kwargs : t .Any ) -> None :
28272887 # a dev preview table is created as a normal table, so it needs to be dropped as a normal table
0 commit comments