@@ -918,6 +918,7 @@ def _render_and_insert_snapshot(
918918 model = snapshot .model
919919 adapter = self .get_adapter (model .gateway )
920920 evaluation_strategy = _evaluation_strategy (snapshot , adapter )
921+ is_snapshot_deployable = deployability_index .is_deployable (snapshot )
921922
922923 queries_or_dfs = self ._render_snapshot_for_evaluation (
923924 snapshot ,
@@ -941,6 +942,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
941942 execution_time = execution_time ,
942943 physical_properties = rendered_physical_properties ,
943944 render_kwargs = create_render_kwargs ,
945+ is_snapshot_deployable = is_snapshot_deployable ,
944946 )
945947 else :
946948 logger .info (
@@ -963,6 +965,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
963965 execution_time = execution_time ,
964966 physical_properties = rendered_physical_properties ,
965967 render_kwargs = create_render_kwargs ,
968+ is_snapshot_deployable = is_snapshot_deployable ,
966969 )
967970
968971 # DataFrames, unlike SQL expressions, can provide partial results by yielding dataframes. As a result,
@@ -1169,6 +1172,7 @@ def _migrate_target_table(
11691172 allow_additive_snapshots = allow_additive_snapshots ,
11701173 ignore_destructive = snapshot .model .on_destructive_change .is_ignore ,
11711174 ignore_additive = snapshot .model .on_additive_change .is_ignore ,
1175+ deployability_index = deployability_index ,
11721176 )
11731177 finally :
11741178 if snapshot .is_materialized :
@@ -1218,6 +1222,7 @@ def _promote_snapshot(
12181222 model = snapshot .model ,
12191223 environment = environment_naming_info .name ,
12201224 snapshots = snapshots ,
1225+ snapshot = snapshot ,
12211226 ** render_kwargs ,
12221227 )
12231228
@@ -1442,6 +1447,8 @@ def _execute_create(
14421447 is_snapshot_representative = is_snapshot_representative ,
14431448 dry_run = dry_run ,
14441449 physical_properties = rendered_physical_properties ,
1450+ snapshot = snapshot ,
1451+ deployability_index = deployability_index ,
14451452 )
14461453 if run_pre_post_statements :
14471454 adapter .execute (snapshot .model .render_post_statements (** create_render_kwargs ))
@@ -1684,6 +1691,7 @@ def _apply_grants(
16841691 model : Model ,
16851692 table_name : str ,
16861693 target_layer : GrantsTargetLayer ,
1694+ is_snapshot_deployable : bool = False ,
16871695 ) -> None :
16881696 """Apply grants for a model if grants are configured.
16891697
@@ -1695,6 +1703,7 @@ def _apply_grants(
16951703 model: The SQLMesh model containing grants configuration
16961704 table_name: The target table/view name to apply grants to
16971705 target_layer: The grants application layer (physical or virtual)
1706+ is_snapshot_deployable: Whether the snapshot is deployable (targeting production)
16981707 """
16991708 grants_config = model .grants
17001709 if grants_config is None :
@@ -1708,7 +1717,16 @@ def _apply_grants(
17081717 return
17091718
17101719 model_grants_target_layer = model .grants_target_layer
1711- if not (model_grants_target_layer .is_all or model_grants_target_layer == target_layer ):
1720+
1721+ is_prod_and_dev_only = is_snapshot_deployable and model .virtual_environment_mode .is_dev_only
1722+
1723+ if not (
1724+ model_grants_target_layer .is_all
1725+ or model_grants_target_layer == target_layer
1726+ # Always apply grants in production when VDE is dev_only regardless of target_layer
1727+ # since only physical tables are created in production
1728+ or is_prod_and_dev_only
1729+ ):
17121730 logger .debug (
17131731 f"Skipping grants application for model { model .name } in { target_layer } layer"
17141732 )
@@ -1826,11 +1844,15 @@ def promote(
18261844 view_properties = model .render_virtual_properties (** render_kwargs ),
18271845 )
18281846
1847+ snapshot = kwargs ["snapshot" ]
1848+ deployability_index = kwargs ["deployability_index" ]
1849+ is_snapshot_deployable = deployability_index .is_deployable (snapshot )
1850+
18291851 # Apply grants to the physical layer (referenced table / view) after promotion
1830- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
1852+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable )
18311853
18321854 # Apply grants to the virtual layer (view) after promotion
1833- self ._apply_grants (model , view_name , GrantsTargetLayer .VIRTUAL )
1855+ self ._apply_grants (model , view_name , GrantsTargetLayer .VIRTUAL , is_snapshot_deployable )
18341856
18351857 def demote (self , view_name : str , ** kwargs : t .Any ) -> None :
18361858 logger .info ("Dropping view '%s'" , view_name )
@@ -1891,7 +1913,10 @@ def create(
18911913
18921914 # Apply grants after table creation (unless explicitly skipped by caller)
18931915 if not skip_grants :
1894- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
1916+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
1917+ self ._apply_grants (
1918+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
1919+ )
18951920
18961921 def migrate (
18971922 self ,
@@ -1919,7 +1944,13 @@ def migrate(
19191944 self .adapter .alter_table (alter_operations )
19201945
19211946 # Apply grants after schema migration
1922- self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
1947+ deployability_index = kwargs .get ("deployability_index" )
1948+ is_snapshot_deployable = (
1949+ deployability_index .is_deployable (snapshot ) if deployability_index else False
1950+ )
1951+ self ._apply_grants (
1952+ snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
1953+ )
19231954
19241955 def delete (self , name : str , ** kwargs : t .Any ) -> None :
19251956 _check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
@@ -1971,7 +2002,8 @@ def _replace_query_for_model(
19712002
19722003 # Apply grants after table replacement (unless explicitly skipped by caller)
19732004 if not skip_grants :
1974- self ._apply_grants (model , name , GrantsTargetLayer .PHYSICAL )
2005+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2006+ self ._apply_grants (model , name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable )
19752007
19762008 def _get_target_and_source_columns (
19772009 self ,
@@ -2261,7 +2293,10 @@ def create(
22612293
22622294 if not skip_grants :
22632295 # Apply grants after seed table creation and data insertion
2264- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2296+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2297+ self ._apply_grants (
2298+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2299+ )
22652300 except Exception :
22662301 self .adapter .drop_table (table_name )
22672302 raise
@@ -2333,7 +2368,10 @@ def create(
23332368
23342369 if not skip_grants :
23352370 # Apply grants after SCD Type 2 table creation
2336- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2371+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2372+ self ._apply_grants (
2373+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2374+ )
23372375
23382376 def insert (
23392377 self ,
@@ -2459,7 +2497,8 @@ def insert(
24592497 )
24602498
24612499 # Apply grants after view creation / replacement
2462- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2500+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2501+ self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable )
24632502
24642503 def append (
24652504 self ,
@@ -2480,14 +2519,18 @@ def create(
24802519 skip_grants : bool ,
24812520 ** kwargs : t .Any ,
24822521 ) -> None :
2522+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2523+
24832524 if self .adapter .table_exists (table_name ):
24842525 # Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
24852526 # binding support (because of DROP CASCADE).
24862527 logger .info ("View '%s' already exists" , table_name )
24872528
24882529 if not skip_grants :
24892530 # Always apply grants when present, even if view exists, to handle grants updates
2490- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2531+ self ._apply_grants (
2532+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2533+ )
24912534 return
24922535
24932536 logger .info ("Creating view '%s'" , table_name )
@@ -2513,7 +2556,9 @@ def create(
25132556
25142557 if not skip_grants :
25152558 # Apply grants after view creation
2516- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2559+ self ._apply_grants (
2560+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2561+ )
25172562
25182563 def migrate (
25192564 self ,
@@ -2542,7 +2587,13 @@ def migrate(
25422587 )
25432588
25442589 # Apply grants after view migration
2545- self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2590+ deployability_index = kwargs .get ("deployability_index" )
2591+ is_snapshot_deployable = (
2592+ deployability_index .is_deployable (snapshot ) if deployability_index else False
2593+ )
2594+ self ._apply_grants (
2595+ snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2596+ )
25462597
25472598 def delete (self , name : str , ** kwargs : t .Any ) -> None :
25482599 cascade = kwargs .pop ("cascade" , False )
@@ -2712,7 +2763,9 @@ def create(
27122763
27132764 # Apply grants after managed table creation
27142765 if not skip_grants :
2715- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2766+ self ._apply_grants (
2767+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2768+ )
27162769
27172770 elif not is_table_deployable :
27182771 # Only create the dev preview table as a normal table.
@@ -2752,7 +2805,9 @@ def insert(
27522805 column_descriptions = model .column_descriptions ,
27532806 table_format = model .table_format ,
27542807 )
2755- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2808+ self ._apply_grants (
2809+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2810+ )
27562811 elif not is_snapshot_deployable :
27572812 # Snapshot isnt deployable; update the preview table instead
27582813 # If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
@@ -2802,8 +2857,13 @@ def migrate(
28022857 )
28032858
28042859 # Apply grants after verifying no schema changes
2805- # This ensures metadata-only grants changes are applied
2806- self ._apply_grants (snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL )
2860+ deployability_index = kwargs .get ("deployability_index" )
2861+ is_snapshot_deployable = (
2862+ deployability_index .is_deployable (snapshot ) if deployability_index else False
2863+ )
2864+ self ._apply_grants (
2865+ snapshot .model , target_table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2866+ )
28072867
28082868 def delete (self , name : str , ** kwargs : t .Any ) -> None :
28092869 # a dev preview table is created as a normal table, so it needs to be dropped as a normal table
0 commit comments