@@ -918,6 +918,7 @@ def _render_and_insert_snapshot(
918
918
model = snapshot .model
919
919
adapter = self .get_adapter (model .gateway )
920
920
evaluation_strategy = _evaluation_strategy (snapshot , adapter )
921
+ is_snapshot_deployable = deployability_index .is_deployable (snapshot )
921
922
922
923
queries_or_dfs = self ._render_snapshot_for_evaluation (
923
924
snapshot ,
@@ -941,6 +942,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
941
942
execution_time = execution_time ,
942
943
physical_properties = rendered_physical_properties ,
943
944
render_kwargs = create_render_kwargs ,
945
+ is_snapshot_deployable = is_snapshot_deployable ,
944
946
)
945
947
else :
946
948
logger .info (
@@ -963,6 +965,7 @@ def apply(query_or_df: QueryOrDF, index: int = 0) -> None:
963
965
execution_time = execution_time ,
964
966
physical_properties = rendered_physical_properties ,
965
967
render_kwargs = create_render_kwargs ,
968
+ is_snapshot_deployable = is_snapshot_deployable ,
966
969
)
967
970
968
971
# DataFrames, unlike SQL expressions, can provide partial results by yielding dataframes. As a result,
@@ -1169,6 +1172,7 @@ def _migrate_target_table(
1169
1172
allow_additive_snapshots = allow_additive_snapshots ,
1170
1173
ignore_destructive = snapshot .model .on_destructive_change .is_ignore ,
1171
1174
ignore_additive = snapshot .model .on_additive_change .is_ignore ,
1175
+ deployability_index = deployability_index ,
1172
1176
)
1173
1177
finally :
1174
1178
if snapshot .is_materialized :
@@ -1218,6 +1222,7 @@ def _promote_snapshot(
1218
1222
model = snapshot .model ,
1219
1223
environment = environment_naming_info .name ,
1220
1224
snapshots = snapshots ,
1225
+ snapshot = snapshot ,
1221
1226
** render_kwargs ,
1222
1227
)
1223
1228
@@ -1442,6 +1447,8 @@ def _execute_create(
1442
1447
is_snapshot_representative = is_snapshot_representative ,
1443
1448
dry_run = dry_run ,
1444
1449
physical_properties = rendered_physical_properties ,
1450
+ snapshot = snapshot ,
1451
+ deployability_index = deployability_index ,
1445
1452
)
1446
1453
if run_pre_post_statements :
1447
1454
adapter .execute (snapshot .model .render_post_statements (** create_render_kwargs ))
@@ -1684,6 +1691,7 @@ def _apply_grants(
1684
1691
model : Model ,
1685
1692
table_name : str ,
1686
1693
target_layer : GrantsTargetLayer ,
1694
+ is_snapshot_deployable : bool = False ,
1687
1695
) -> None :
1688
1696
"""Apply grants for a model if grants are configured.
1689
1697
@@ -1695,6 +1703,7 @@ def _apply_grants(
1695
1703
model: The SQLMesh model containing grants configuration
1696
1704
table_name: The target table/view name to apply grants to
1697
1705
target_layer: The grants application layer (physical or virtual)
1706
+ is_snapshot_deployable: Whether the snapshot is deployable (targeting production)
1698
1707
"""
1699
1708
grants_config = model .grants
1700
1709
if grants_config is None :
@@ -1708,7 +1717,16 @@ def _apply_grants(
1708
1717
return
1709
1718
1710
1719
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
+ ):
1712
1730
logger .debug (
1713
1731
f"Skipping grants application for model { model .name } in { target_layer } layer"
1714
1732
)
@@ -1826,11 +1844,15 @@ def promote(
1826
1844
view_properties = model .render_virtual_properties (** render_kwargs ),
1827
1845
)
1828
1846
1847
+ snapshot = kwargs ["snapshot" ]
1848
+ deployability_index = kwargs ["deployability_index" ]
1849
+ is_snapshot_deployable = deployability_index .is_deployable (snapshot )
1850
+
1829
1851
# 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 )
1831
1853
1832
1854
# 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 )
1834
1856
1835
1857
def demote (self , view_name : str , ** kwargs : t .Any ) -> None :
1836
1858
logger .info ("Dropping view '%s'" , view_name )
@@ -1891,7 +1913,10 @@ def create(
1891
1913
1892
1914
# Apply grants after table creation (unless explicitly skipped by caller)
1893
1915
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
+ )
1895
1920
1896
1921
def migrate (
1897
1922
self ,
@@ -1919,7 +1944,13 @@ def migrate(
1919
1944
self .adapter .alter_table (alter_operations )
1920
1945
1921
1946
# 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
+ )
1923
1954
1924
1955
def delete (self , name : str , ** kwargs : t .Any ) -> None :
1925
1956
_check_table_db_is_physical_schema (name , kwargs ["physical_schema" ])
@@ -1971,7 +2002,8 @@ def _replace_query_for_model(
1971
2002
1972
2003
# Apply grants after table replacement (unless explicitly skipped by caller)
1973
2004
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 )
1975
2007
1976
2008
def _get_target_and_source_columns (
1977
2009
self ,
@@ -2261,7 +2293,10 @@ def create(
2261
2293
2262
2294
if not skip_grants :
2263
2295
# 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
+ )
2265
2300
except Exception :
2266
2301
self .adapter .drop_table (table_name )
2267
2302
raise
@@ -2333,7 +2368,10 @@ def create(
2333
2368
2334
2369
if not skip_grants :
2335
2370
# 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
+ )
2337
2375
2338
2376
def insert (
2339
2377
self ,
@@ -2459,7 +2497,8 @@ def insert(
2459
2497
)
2460
2498
2461
2499
# 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 )
2463
2502
2464
2503
def append (
2465
2504
self ,
@@ -2480,14 +2519,18 @@ def create(
2480
2519
skip_grants : bool ,
2481
2520
** kwargs : t .Any ,
2482
2521
) -> None :
2522
+ is_snapshot_deployable : bool = kwargs ["is_snapshot_deployable" ]
2523
+
2483
2524
if self .adapter .table_exists (table_name ):
2484
2525
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
2485
2526
# binding support (because of DROP CASCADE).
2486
2527
logger .info ("View '%s' already exists" , table_name )
2487
2528
2488
2529
if not skip_grants :
2489
2530
# 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
+ )
2491
2534
return
2492
2535
2493
2536
logger .info ("Creating view '%s'" , table_name )
@@ -2513,7 +2556,9 @@ def create(
2513
2556
2514
2557
if not skip_grants :
2515
2558
# 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
+ )
2517
2562
2518
2563
def migrate (
2519
2564
self ,
@@ -2542,7 +2587,13 @@ def migrate(
2542
2587
)
2543
2588
2544
2589
# 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
+ )
2546
2597
2547
2598
def delete (self , name : str , ** kwargs : t .Any ) -> None :
2548
2599
cascade = kwargs .pop ("cascade" , False )
@@ -2712,7 +2763,9 @@ def create(
2712
2763
2713
2764
# Apply grants after managed table creation
2714
2765
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
+ )
2716
2769
2717
2770
elif not is_table_deployable :
2718
2771
# Only create the dev preview table as a normal table.
@@ -2752,7 +2805,9 @@ def insert(
2752
2805
column_descriptions = model .column_descriptions ,
2753
2806
table_format = model .table_format ,
2754
2807
)
2755
- self ._apply_grants (model , table_name , GrantsTargetLayer .PHYSICAL )
2808
+ self ._apply_grants (
2809
+ model , table_name , GrantsTargetLayer .PHYSICAL , is_snapshot_deployable
2810
+ )
2756
2811
elif not is_snapshot_deployable :
2757
2812
# Snapshot isnt deployable; update the preview table instead
2758
2813
# 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(
2802
2857
)
2803
2858
2804
2859
# 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
+ )
2807
2867
2808
2868
def delete (self , name : str , ** kwargs : t .Any ) -> None :
2809
2869
# a dev preview table is created as a normal table, so it needs to be dropped as a normal table
0 commit comments