@@ -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
@@ -2339,7 +2374,10 @@ def create(
23392374
23402375 if not skip_grants :
23412376 # Apply grants after SCD Type 2 table creation
2342- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2377+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2378+ self ._apply_grants (
2379+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2380+ )
23432381
23442382 def insert (
23452383 self ,
@@ -2465,7 +2503,8 @@ def insert(
24652503 )
24662504
24672505 # Apply grants after view creation / replacement
2468- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2506+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2507+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable )
24692508
24702509 def append (
24712510 self ,
@@ -2486,14 +2525,18 @@ def create(
24862525 skip_grants : bool ,
24872526 ** kwargs : t .Any ,
24882527 ) -> None :
2528+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2529+
24892530 if self .adapter .table_exists (table_name ):
24902531 # Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24912532 # binding support (because of DROP CASCADE).
24922533 logger .info ("View '%s' already exists" , table_name )
24932534
24942535 if not skip_grants :
24952536 # Always apply grants when present, even if view exists, to handle grants updates
2496- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2537+ self ._apply_grants (
2538+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2539+ )
24972540 return
24982541
24992542 logger .info ("Creating view '%s'" , table_name )
@@ -2519,7 +2562,9 @@ def create(
25192562
25202563 if not skip_grants :
25212564 # Apply grants after view creation
2522- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2565+ self ._apply_grants (
2566+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2567+ )
25232568
25242569 def migrate (
25252570 self ,
@@ -2548,7 +2593,13 @@ def migrate(
25482593 )
25492594
25502595 # Apply grants after view migration
2551- self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2596+ deployability_index = kwargs .get ("deployability_index" )
2597+ is_snapshot_deployable = (
2598+ deployability_index .is_deployable (snapshot ) if deployability_index else False
2599+ )
2600+ self ._apply_grants (
2601+ snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2602+ )
25522603
25532604 def delete (self , name : str , ** kwargs : t .Any ) -> None :
25542605 cascade = kwargs .pop ("cascade" , False )
@@ -2718,7 +2769,9 @@ def create(
27182769
27192770 # Apply grants after managed table creation
27202771 if not skip_grants :
2721- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2772+ self ._apply_grants (
2773+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2774+ )
27222775
27232776 elif not is_table_deployable :
27242777 # Only create the dev preview table as a normal table.
@@ -2758,7 +2811,9 @@ def insert(
27582811 column_descriptions = model .column_descriptions ,
27592812 table_format = model .table_format ,
27602813 )
2761- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2814+ self ._apply_grants (
2815+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2816+ )
27622817 elif not is_snapshot_deployable :
27632818 # Snapshot isnt deployable; update the preview table instead
27642819 # If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2808,8 +2863,13 @@ def migrate(
28082863 )
28092864
28102865 # Apply grants after verifying no schema changes
2811- # This ensures metadata-only grants changes are applied
2812- self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2866+ deployability_index = kwargs .get ("deployability_index" )
2867+ is_snapshot_deployable = (
2868+ deployability_index .is_deployable (snapshot ) if deployability_index else False
2869+ )
2870+ self ._apply_grants (
2871+ snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2872+ )
28132873
28142874 def delete (self , name : str , ** kwargs : t .Any ) -> None :
28152875 # a dev preview table is created as a normal table, so it needs to be dropped as a normal table
0 commit comments