Skip to content

Commit 7c6a549

Browse files
committed
Added Zone Creation Endpoint and Tests
1 parent db58809 commit 7c6a549

File tree

7 files changed

+981
-8
lines changed

7 files changed

+981
-8
lines changed

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,57 @@ print(f"Zones: {info['zone_count']}")
426426
print(f"Active zones: {[z['name'] for z in info['zones']]}")
427427
```
428428

429+
### Zone Management
430+
431+
The SDK can automatically create required zones if they don't exist, or you can manage zones manually.
432+
433+
#### Automatic Zone Creation
434+
435+
Enable automatic zone creation when initializing the client:
436+
437+
```python
438+
client = BrightDataClient(
439+
token="your_token",
440+
auto_create_zones=True # Automatically create zones if missing
441+
)
442+
443+
# Zones are created on first API call
444+
async with client:
445+
# sdk_unlocker, sdk_serp, and sdk_browser zones created automatically if needed
446+
result = await client.scrape.amazon.products(url="...")
447+
```
448+
449+
#### Manual Zone Management
450+
451+
List and manage zones programmatically:
452+
453+
```python
454+
# List all zones
455+
zones = await client.list_zones()
456+
zones = client.list_zones_sync() # Synchronous version
457+
458+
for zone in zones:
459+
print(f"Zone: {zone['name']} (Type: {zone.get('type', 'unknown')})")
460+
461+
# Advanced: Use ZoneManager directly
462+
from brightdata import ZoneManager
463+
464+
async with client.engine:
465+
zone_manager = ZoneManager(client.engine)
466+
467+
# Ensure specific zones exist
468+
await zone_manager.ensure_required_zones(
469+
web_unlocker_zone="my_custom_zone",
470+
serp_zone="my_serp_zone"
471+
)
472+
```
473+
474+
**Zone Creation API:**
475+
- Endpoint: `POST https://api.brightdata.com/zone`
476+
- Zones are created via the Bright Data API
477+
- Supported zone types: `unblocker`, `serp`, `browser`
478+
- Automatically handles duplicate zones gracefully
479+
429480
### Result Objects
430481

431482
All operations return rich result objects with timing and metadata:

examples/zone_management_demo.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
"""
2+
Zone Management Demo - Demonstrates zone creation and management features.
3+
4+
This example shows how to:
5+
1. List existing zones
6+
2. Enable automatic zone creation
7+
3. Use ZoneManager for advanced zone management
8+
"""
9+
10+
import asyncio
11+
import os
12+
from brightdata import BrightDataClient, ZoneManager
13+
14+
15+
async def demo_list_zones():
16+
"""List all zones in the account."""
17+
print("\n" + "=" * 60)
18+
print("DEMO 1: List Zones")
19+
print("=" * 60)
20+
21+
client = BrightDataClient()
22+
23+
# List all zones
24+
zones = await client.list_zones()
25+
26+
print(f"\nFound {len(zones)} zones in your account:")
27+
for zone in zones:
28+
zone_name = zone.get('name', 'Unknown')
29+
zone_type = zone.get('type', 'unknown')
30+
zone_status = zone.get('status', 'unknown')
31+
print(f" - {zone_name}")
32+
print(f" Type: {zone_type}")
33+
print(f" Status: {zone_status}")
34+
print()
35+
36+
37+
async def demo_auto_create_zones():
38+
"""Demonstrate automatic zone creation."""
39+
print("\n" + "=" * 60)
40+
print("DEMO 2: Automatic Zone Creation")
41+
print("=" * 60)
42+
43+
# Create client with auto zone creation enabled
44+
client = BrightDataClient(auto_create_zones=True)
45+
46+
print("\nClient configured with auto_create_zones=True")
47+
print("Required zones will be created automatically on first API call:")
48+
print(" - sdk_unlocker (Web Unlocker)")
49+
print(" - sdk_serp (SERP API)")
50+
print(" - sdk_browser (Browser API)")
51+
52+
# Zones will be created when entering context manager
53+
async with client:
54+
print("\n✓ Zones ensured (created if missing)")
55+
56+
# List zones to confirm
57+
zones = await client.list_zones()
58+
zone_names = [z.get('name') for z in zones]
59+
60+
print(f"\nZones now in account ({len(zones)} total):")
61+
for name in zone_names:
62+
print(f" - {name}")
63+
64+
65+
async def demo_zone_manager_advanced():
66+
"""Demonstrate advanced zone management with ZoneManager."""
67+
print("\n" + "=" * 60)
68+
print("DEMO 3: Advanced Zone Management")
69+
print("=" * 60)
70+
71+
client = BrightDataClient()
72+
73+
async with client.engine:
74+
zone_manager = ZoneManager(client.engine)
75+
76+
print("\nUsing ZoneManager for fine-grained control...")
77+
78+
# List zones
79+
zones = await zone_manager.list_zones()
80+
print(f"\nCurrent zones: {len(zones)}")
81+
82+
# Ensure specific zones exist
83+
print("\nEnsuring custom zones exist...")
84+
print(" - my_web_unlocker (unblocker)")
85+
print(" - my_serp_api (serp)")
86+
87+
try:
88+
await zone_manager.ensure_required_zones(
89+
web_unlocker_zone="my_web_unlocker",
90+
serp_zone="my_serp_api"
91+
)
92+
print("\n✓ Zones ensured successfully")
93+
except Exception as e:
94+
print(f"\n✗ Zone creation failed: {e}")
95+
96+
# List zones again
97+
zones = await zone_manager.list_zones()
98+
print(f"\nZones after creation: {len(zones)}")
99+
for zone in zones:
100+
print(f" - {zone.get('name')}")
101+
102+
103+
async def demo_sync_methods():
104+
"""Demonstrate synchronous zone listing."""
105+
print("\n" + "=" * 60)
106+
print("DEMO 4: Synchronous Zone Listing")
107+
print("=" * 60)
108+
109+
client = BrightDataClient()
110+
111+
print("\nUsing synchronous method for convenience...")
112+
113+
# Synchronous version (blocks until complete)
114+
zones = client.list_zones_sync()
115+
116+
print(f"\nFound {len(zones)} zones (synchronous call):")
117+
for zone in zones[:5]: # Show first 5
118+
print(f" - {zone.get('name')}: {zone.get('type', 'unknown')}")
119+
120+
if len(zones) > 5:
121+
print(f" ... and {len(zones) - 5} more")
122+
123+
124+
async def main():
125+
"""Run all zone management demos."""
126+
print("\n" + "=" * 60)
127+
print("BRIGHT DATA SDK - ZONE MANAGEMENT DEMOS")
128+
print("=" * 60)
129+
130+
# Check for API token
131+
if not os.getenv("BRIGHTDATA_API_TOKEN"):
132+
print("\n⚠️ Warning: BRIGHTDATA_API_TOKEN not set")
133+
print("Please set your API token as an environment variable:")
134+
print(" export BRIGHTDATA_API_TOKEN='your_token_here'")
135+
return
136+
137+
try:
138+
# Demo 1: List zones
139+
await demo_list_zones()
140+
141+
# Demo 2: Auto-create zones
142+
# Note: Uncomment to test zone creation
143+
# await demo_auto_create_zones()
144+
145+
# Demo 3: Advanced zone management
146+
# Note: Uncomment to test custom zone creation
147+
# await demo_zone_manager_advanced()
148+
149+
# Demo 4: Sync methods
150+
await demo_sync_methods()
151+
152+
print("\n" + "=" * 60)
153+
print("DEMOS COMPLETE")
154+
print("=" * 60)
155+
156+
except Exception as e:
157+
print(f"\n❌ Error running demos: {e}")
158+
import traceback
159+
traceback.print_exc()
160+
161+
162+
if __name__ == "__main__":
163+
asyncio.run(main())

src/brightdata/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@
2626
SSLError,
2727
)
2828

29-
# Export WebUnlockerService for advanced usage
29+
# Export services for advanced usage
3030
from .api.web_unlocker import WebUnlockerService
31+
from .core.zone_manager import ZoneManager
3132

3233
__all__ = [
3334
"__version__",
@@ -51,4 +52,5 @@
5152
"SSLError",
5253
# Services
5354
"WebUnlockerService",
55+
"ZoneManager",
5456
]

src/brightdata/client.py

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
pass
2121

2222
from .core.engine import AsyncEngine
23+
from .core.zone_manager import ZoneManager
2324
from .api.web_unlocker import WebUnlockerService
2425
from .api.scrape_service import ScrapeService, GenericScraper
2526
from .api.search_service import SearchService
@@ -134,9 +135,11 @@ def __init__(
134135
self._search_service: Optional[SearchService] = None
135136
self._crawler_service: Optional[CrawlerService] = None
136137
self._web_unlocker_service: Optional[WebUnlockerService] = None
138+
self._zone_manager: Optional[ZoneManager] = None
137139
self._is_connected = False
138140
self._account_info: Optional[Dict[str, Any]] = None
139-
141+
self._zones_ensured = False
142+
140143
if validate_token:
141144
self._validate_token_sync()
142145

@@ -198,8 +201,32 @@ def _validate_token_sync(self) -> None:
198201
f"Failed to validate token: {str(e)}\n"
199202
f"Check your token at: https://brightdata.com/cp/api_keys"
200203
)
201-
202-
204+
205+
async def _ensure_zones(self) -> None:
206+
"""
207+
Ensure required zones exist if auto_create_zones is enabled.
208+
209+
This is called automatically before the first API request.
210+
Only runs once per client instance.
211+
212+
Raises:
213+
ZoneError: If zone creation fails
214+
AuthenticationError: If API token lacks permissions
215+
"""
216+
if self._zones_ensured or not self.auto_create_zones:
217+
return
218+
219+
if self._zone_manager is None:
220+
self._zone_manager = ZoneManager(self.engine)
221+
222+
await self._zone_manager.ensure_required_zones(
223+
web_unlocker_zone=self.web_unlocker_zone,
224+
serp_zone=self.serp_zone,
225+
browser_zone=self.browser_zone
226+
)
227+
self._zones_ensured = True
228+
229+
203230
@property
204231
def scrape(self) -> ScrapeService:
205232
"""
@@ -380,8 +407,34 @@ def test_connection_sync(self) -> bool:
380407
return asyncio.run(self.test_connection())
381408
except Exception:
382409
return False
383-
384-
410+
411+
async def list_zones(self) -> List[Dict[str, Any]]:
412+
"""
413+
List all active zones in your Bright Data account.
414+
415+
Returns:
416+
List of zone dictionaries with their configurations
417+
418+
Raises:
419+
ZoneError: If zone listing fails
420+
AuthenticationError: If authentication fails
421+
422+
Example:
423+
>>> zones = await client.list_zones()
424+
>>> print(f"Found {len(zones)} zones")
425+
>>> for zone in zones:
426+
... print(f" - {zone['name']}: {zone.get('type', 'unknown')}")
427+
"""
428+
async with self.engine:
429+
if self._zone_manager is None:
430+
self._zone_manager = ZoneManager(self.engine)
431+
return await self._zone_manager.list_zones()
432+
433+
def list_zones_sync(self) -> List[Dict[str, Any]]:
434+
"""Synchronous version of list_zones()."""
435+
return asyncio.run(self.list_zones())
436+
437+
385438
async def scrape_url_async(
386439
self,
387440
url: Union[str, List[str]],
@@ -419,6 +472,7 @@ def scrape_url(self, *args, **kwargs) -> Union[ScrapeResult, List[ScrapeResult]]
419472
async def __aenter__(self):
420473
"""Async context manager entry."""
421474
await self.engine.__aenter__()
475+
await self._ensure_zones()
422476
return self
423477

424478
async def __aexit__(self, exc_type, exc_val, exc_tb):

0 commit comments

Comments
 (0)