33import typing as t
44import logging
55import requests
6+ import time
67from functools import cached_property
78from sqlglot import exp
89from tenacity import retry , stop_after_attempt , wait_exponential , retry_if_result
1516from sqlmesh .utils .errors import SQLMeshError
1617from sqlmesh .utils .connection_pool import ConnectionPool
1718
19+
1820if t .TYPE_CHECKING :
1921 from sqlmesh .core ._typing import TableName
2022
@@ -172,8 +174,17 @@ def __init__(self, tenant_id: str, workspace_id: str, client_id: str, client_sec
172174 self .client_secret = client_secret
173175 self .workspace_id = workspace_id
174176
175- def create_warehouse (self , warehouse_name : str ) -> None :
177+ def create_warehouse (
178+ self , warehouse_name : str , if_not_exists : bool = True , attempt : int = 0
179+ ) -> None :
176180 """Create a catalog (warehouse) in Microsoft Fabric via REST API."""
181+
182+ # attempt count is arbitrary, it essentially equates to 5 minutes of 30 second waits
183+ if attempt > 10 :
184+ raise SQLMeshError (
185+ f"Gave up waiting for Fabric warehouse { warehouse_name } to become available"
186+ )
187+
177188 logger .info (f"Creating Fabric warehouse: { warehouse_name } " )
178189
179190 request_data = {
@@ -182,7 +193,34 @@ def create_warehouse(self, warehouse_name: str) -> None:
182193 }
183194
184195 response = self .session .post (self ._endpoint_url ("warehouses" ), json = request_data )
185- response .raise_for_status ()
196+
197+ if (
198+ if_not_exists
199+ and response .status_code == 400
200+ and (errorCode := response .json ().get ("errorCode" , None ))
201+ ):
202+ if errorCode == "ItemDisplayNameAlreadyInUse" :
203+ logger .warning (f"Fabric warehouse { warehouse_name } already exists" )
204+ return
205+ if errorCode == "ItemDisplayNameNotAvailableYet" :
206+ logger .warning (f"Fabric warehouse { warehouse_name } is still spinning up; waiting" )
207+ # Fabric error message is something like:
208+ # - "Requested 'circleci_51d7087e__dev' is not available yet and is expected to become available in the upcoming minutes."
209+ # This seems to happen if a catalog is dropped and then a new one with the same name is immediately created.
210+ # There appears to be some delayed async process on the Fabric side that actually drops the warehouses and frees up the names to be used again
211+ time .sleep (30 )
212+ return self .create_warehouse (
213+ warehouse_name = warehouse_name , if_not_exists = if_not_exists , attempt = attempt + 1
214+ )
215+
216+ try :
217+ response .raise_for_status ()
218+ except :
219+ # the important information to actually debug anything is in the response body which Requests never prints
220+ logger .exception (
221+ f"Failed to create warehouse { warehouse_name } . status: { response .status_code } , body: { response .text } "
222+ )
223+ raise
186224
187225 # Handle direct success (201) or async creation (202)
188226 if response .status_code == 201 :
@@ -197,11 +235,12 @@ def create_warehouse(self, warehouse_name: str) -> None:
197235 logger .error (f"Unexpected response from Fabric API: { response } \n { response .text } " )
198236 raise SQLMeshError (f"Unable to create warehouse: { response } " )
199237
200- def delete_warehouse (self , warehouse_name : str ) -> None :
238+ def delete_warehouse (self , warehouse_name : str , if_exists : bool = True ) -> None :
201239 """Drop a catalog (warehouse) in Microsoft Fabric via REST API."""
202240 logger .info (f"Deleting Fabric warehouse: { warehouse_name } " )
203241
204242 # Get the warehouse ID by listing warehouses
243+ # TODO: handle continuationUri for pagination, ref: https://learn.microsoft.com/en-us/rest/api/fabric/warehouse/items/list-warehouses?tabs=HTTP#warehouses
205244 response = self .session .get (self ._endpoint_url ("warehouses" ))
206245 response .raise_for_status ()
207246
@@ -213,9 +252,12 @@ def delete_warehouse(self, warehouse_name: str) -> None:
213252 warehouse_id = warehouse_name_to_id .get (warehouse_name , None )
214253
215254 if not warehouse_id :
216- logger .error (
255+ logger .warning (
217256 f"Fabric warehouse does not exist: { warehouse_name } \n (available warehouses: { ', ' .join (warehouse_name_to_id )} )"
218257 )
258+ if if_exists :
259+ return
260+
219261 raise SQLMeshError (
220262 f"Unable to delete Fabric warehouse { warehouse_name } as it doesnt exist"
221263 )
0 commit comments