@@ -42,7 +42,8 @@ async def ensure_required_zones(
4242 self ,
4343 web_unlocker_zone : str ,
4444 serp_zone : Optional [str ] = None ,
45- browser_zone : Optional [str ] = None
45+ browser_zone : Optional [str ] = None ,
46+ skip_verification : bool = False
4647 ) -> None :
4748 """
4849 Check if required zones exist and create them if they don't.
@@ -90,13 +91,42 @@ async def ensure_required_zones(
9091 # Create zones
9192 for zone_name , zone_type in zones_to_create :
9293 logger .info (f"Creating zone: { zone_name } (type: { zone_type } )" )
93- await self ._create_zone (zone_name , zone_type )
94- logger .info (f"Successfully created zone: { zone_name } " )
95-
96- # Verify zones were created
97- await self ._verify_zones_created ([zone [0 ] for zone in zones_to_create ])
94+ try :
95+ await self ._create_zone (zone_name , zone_type )
96+ logger .info (f"Successfully created zone: { zone_name } " )
97+ except AuthenticationError as e :
98+ # Re-raise with clear message - this is a permission issue
99+ logger .error (f"Failed to create zone '{ zone_name } ' due to insufficient permissions" )
100+ raise
101+ except ZoneError as e :
102+ # Log and re-raise zone errors
103+ logger .error (f"Failed to create zone '{ zone_name } ': { e } " )
104+ raise
98105
99- except (ZoneError , AuthenticationError , APIError ):
106+ # Verify zones were created (unless skipped)
107+ if not skip_verification :
108+ try :
109+ await self ._verify_zones_created ([zone [0 ] for zone in zones_to_create ])
110+ except ZoneError as e :
111+ # Log verification failure but don't fail the entire operation
112+ logger .warning (
113+ f"Zone verification failed: { e } . "
114+ f"Zones may have been created but aren't yet visible in the API. "
115+ f"Check your dashboard at https://brightdata.com/cp/zones"
116+ )
117+ # Don't re-raise - zones were likely created successfully
118+ else :
119+ logger .info ("Skipping zone verification (skip_verification=True)" )
120+
121+ except AuthenticationError as e :
122+ # Permission errors are critical - show clear message
123+ logger .error (
124+ "\n ❌ ZONE CREATION BLOCKED: API token lacks required permissions\n "
125+ f" Error: { e } \n "
126+ " Fix: Update your token permissions at https://brightdata.com/cp/setting/users"
127+ )
128+ raise
129+ except (ZoneError , APIError ):
100130 raise
101131 except Exception as e :
102132 logger .error (f"Unexpected error while ensuring zones exist: { e } " )
@@ -204,11 +234,36 @@ async def _create_zone(self, zone_name: str, zone_type: str) -> None:
204234 logger .info (f"Zone { zone_name } already exists - this is expected" )
205235 return
206236
207- # Handle authentication errors
237+ # Handle authentication/permission errors
208238 if response .status in (HTTP_UNAUTHORIZED , HTTP_FORBIDDEN ):
209- raise AuthenticationError (
210- f"Authentication failed ({ response .status } ) creating zone '{ zone_name } ': { error_text } "
211- )
239+ # Check for specific permission error
240+ if "permission" in error_text .lower () or "lacks the required" in error_text .lower ():
241+ error_msg = (
242+ f"\n { '=' * 70 } \n "
243+ f"❌ PERMISSION ERROR: Cannot create zone '{ zone_name } '\n "
244+ f"{ '=' * 70 } \n "
245+ f"Your API key lacks the required permissions for zone creation.\n \n "
246+ f"To fix this:\n "
247+ f" 1. Go to: https://brightdata.com/cp/setting/users\n "
248+ f" 2. Find your API token\n "
249+ f" 3. Enable 'Zone Management' or 'Create Zones' permission\n "
250+ f" 4. Save changes and try again\n \n "
251+ f"API Response: { error_text } \n "
252+ f"{ '=' * 70 } \n "
253+ )
254+ logger .error (error_msg )
255+ raise AuthenticationError (
256+ f"API key lacks permission to create zones. "
257+ f"Update permissions at https://brightdata.com/cp/setting/users"
258+ )
259+ else :
260+ # Generic auth error
261+ logger .error (
262+ f"Authentication failed ({ response .status } ) creating zone '{ zone_name } ': { error_text } "
263+ )
264+ raise AuthenticationError (
265+ f"Authentication failed ({ response .status } ) creating zone '{ zone_name } ': { error_text } "
266+ )
212267
213268 # Handle bad request
214269 if response .status == HTTP_BAD_REQUEST :
@@ -245,41 +300,55 @@ async def _verify_zones_created(self, zone_names: List[str]) -> None:
245300 """
246301 Verify that zones were successfully created by checking the zones list.
247302
303+ Note: Zones may take several seconds to appear in the API after creation.
304+ This method retries multiple times with exponential backoff.
305+
248306 Args:
249307 zone_names: List of zone names to verify
250308
251309 Raises:
252- ZoneError: If zone verification fails
310+ ZoneError: If zone verification fails after all retries
253311 """
254- max_attempts = 3
255- retry_delay = 1.0
312+ max_attempts = 5 # Increased from 3 to handle slower propagation
313+ base_delay = 2.0 # Increased from 1.0 for better reliability
256314
257315 for attempt in range (max_attempts ):
258316 try :
259- logger .info (f"Verifying zone creation (attempt { attempt + 1 } /{ max_attempts } )" )
260- await asyncio .sleep (retry_delay )
317+ # Calculate delay with exponential backoff
318+ wait_time = base_delay * (1.5 ** attempt ) if attempt > 0 else base_delay
319+ logger .info (f"Verifying zone creation (attempt { attempt + 1 } /{ max_attempts } ) after { wait_time :.1f} s..." )
320+ await asyncio .sleep (wait_time )
261321
262322 zones = await self ._get_zones ()
263323 existing_zone_names = {zone .get ('name' ) for zone in zones }
264324
265325 missing_zones = [name for name in zone_names if name not in existing_zone_names ]
266326
267327 if not missing_zones :
268- logger .info ("All zones verified successfully" )
328+ logger .info (f "All { len ( zone_names ) } zone(s) verified successfully" )
269329 return
270330
271331 if attempt == max_attempts - 1 :
272- raise ZoneError (
273- f"Zone verification failed: zones { missing_zones } not found after creation"
332+ # Final attempt failed - provide helpful error message
333+ error_msg = (
334+ f"Zone verification failed after { max_attempts } attempts: "
335+ f"zones { missing_zones } not found after creation. "
336+ f"The zones may have been created but are not yet visible in the API. "
337+ f"Please check your dashboard at https://brightdata.com/cp/zones"
274338 )
339+ logger .error (error_msg )
340+ raise ZoneError (error_msg )
275341
276- logger .warning (f"Zones not yet visible: { missing_zones } . Retrying verification..." )
342+ logger .warning (
343+ f"Zones not yet visible: { missing_zones } . "
344+ f"Retrying in { base_delay * (1.5 ** attempt ):.1f} s..."
345+ )
277346
278347 except ZoneError :
279348 if attempt == max_attempts - 1 :
280349 raise
281350 logger .warning (f"Zone verification attempt { attempt + 1 } failed, retrying..." )
282- await asyncio .sleep (retry_delay * (2 ** attempt ))
351+ await asyncio .sleep (base_delay * (1.5 ** attempt ))
283352
284353 async def list_zones (self ) -> List [Dict [str , Any ]]:
285354 """
0 commit comments