Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ exclude = [
]

[tool.ruff.lint]
# Pyflakes + pycodestyle errors + isort (import sorting). Explicit, so newer
# ruff releases that broaden the "no select" default (e.g. datetimez, simplify,
# bugbear) don't silently start failing CI with unrelated findings.
select = ["E4", "E7", "E9", "F", "I"]
ignore = [
"E402", # Module level import not at top of file (common in notebooks/scripts)
"F841", # Local variable assigned but never used (common in tests)
Expand Down
86 changes: 43 additions & 43 deletions src/brightdata/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Bright Data Python SDK - Modern async-first SDK for Bright Data APIs."""

from importlib.metadata import version, PackageNotFoundError
from importlib.metadata import PackageNotFoundError, version

try:
__version__ = version("brightdata-sdk")
Expand All @@ -9,79 +9,79 @@
__version__ = "0.0.0.dev"

# Export main client (async)
from .browser.service import BrowserService
from .client import BrightDataClient
from .core.zone_manager import ZoneManager

# Export sync client adapter
from .sync_client import SyncBrightDataClient
# Export Discover API models
from .discover.models import DiscoverJob, DiscoverResult, DiscoverSnapshot

# Export exceptions
from .exceptions import (
APIError,
AuthenticationError,
BrightDataError,
NetworkError,
SSLError,
ValidationError,
ZoneError,
)

# Export result models
from .models import (
BaseResult,
ScrapeResult,
SearchResult,
CrawlResult,
Result,
ScrapeResult,
SearchResult,
)

# Export job model for manual trigger/poll/fetch
from .scrapers.job import ScrapeJob

# Export Discover API models
from .discover.models import DiscoverResult, DiscoverJob, DiscoverSnapshot

# Export payload models (dataclasses)
from .payloads import (
# Base
BasePayload,
URLPayload,
# Amazon
AmazonProductPayload,
AmazonReviewPayload,
AmazonSellerPayload,
# LinkedIn
LinkedInProfilePayload,
LinkedInJobPayload,
LinkedInCompanyPayload,
LinkedInPostPayload,
LinkedInProfileSearchPayload,
LinkedInJobSearchPayload,
LinkedInPostSearchPayload,
# Base
BasePayload,
# ChatGPT
ChatGPTPromptPayload,
FacebookCommentsPayload,
FacebookPostPayload,
FacebookPostsGroupPayload,
# Facebook
FacebookPostsProfilePayload,
FacebookPostsGroupPayload,
FacebookPostPayload,
FacebookCommentsPayload,
FacebookReelsPayload,
InstagramCommentPayload,
InstagramPostPayload,
InstagramPostsDiscoverPayload,
# Instagram
InstagramProfilePayload,
InstagramPostPayload,
InstagramCommentPayload,
InstagramReelPayload,
InstagramPostsDiscoverPayload,
InstagramReelsDiscoverPayload,
)

# Export exceptions
from .exceptions import (
BrightDataError,
ValidationError,
AuthenticationError,
APIError,
ZoneError,
NetworkError,
SSLError,
LinkedInCompanyPayload,
LinkedInJobPayload,
LinkedInJobSearchPayload,
LinkedInPostPayload,
LinkedInPostSearchPayload,
# LinkedIn
LinkedInProfilePayload,
LinkedInProfileSearchPayload,
URLPayload,
)

# Export Scraper Studio models
from .scraper_studio.models import ScraperStudioJob, JobStatus
from .scraper_studio.models import JobStatus, ScraperStudioJob
from .scraper_studio.service import ScraperStudioService

# Export job model for manual trigger/poll/fetch
from .scrapers.job import ScrapeJob

# Export sync client adapter
from .sync_client import SyncBrightDataClient

# Export services for advanced usage
from .web_unlocker.service import WebUnlockerService
from .scraper_studio.service import ScraperStudioService
from .browser.service import BrowserService
from .core.zone_manager import ZoneManager

__all__ = [
"__version__",
Expand Down
4 changes: 1 addition & 3 deletions src/brightdata/browser/service.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""Browser API service — builds CDP WebSocket URLs for Playwright/Puppeteer."""

from typing import Optional


class BrowserService:
"""
Expand Down Expand Up @@ -43,7 +41,7 @@ def __init__(
self._host = host
self._port = port

def get_connect_url(self, country: Optional[str] = None) -> str:
def get_connect_url(self, country: str | None = None) -> str:
"""
Return the CDP WebSocket URL for connecting to a remote browser.

Expand Down
3 changes: 1 addition & 2 deletions src/brightdata/cli_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import os
import sys
from pathlib import Path
from typing import Optional


def _cli_credentials_path() -> Path:
Expand All @@ -32,7 +31,7 @@ def _cli_credentials_path() -> Path:
return base / "brightdata-cli" / "credentials.json"


def read_cli_credentials() -> Optional[str]:
def read_cli_credentials() -> str | None:
"""
Return the API key stored by `brightdata login`, or None if unavailable.

Expand Down
101 changes: 51 additions & 50 deletions src/brightdata/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
- Follow principle of least surprise - common patterns from other SDKs
"""

import os
import asyncio
import os
import warnings
from typing import Optional, Dict, Any, Union, List
from datetime import datetime, timezone
from typing import Any

try:
from dotenv import load_dotenv
Expand All @@ -21,22 +21,23 @@
except ImportError:
pass

from http import HTTPStatus

from .browser.service import BrowserService
from .cli_credentials import read_cli_credentials
from .core.engine import AsyncEngine
from .core.zone_manager import ZoneManager
from .web_unlocker.service import WebUnlockerService
from .scrapers.service import ScrapeService
from .serp.service import SearchService
from .crawler.service import CrawlerService
from .scraper_studio.service import ScraperStudioService
from .browser.service import BrowserService
from .discover.service import DiscoverService
from .discover.models import DiscoverResult, DiscoverJob
from .datasets import DatasetsClient
from .discover.models import DiscoverJob, DiscoverResult
from .discover.service import DiscoverService
from .exceptions import APIError, AuthenticationError, ValidationError
from .models import ScrapeResult
from .scraper_studio.service import ScraperStudioService
from .scrapers.service import ScrapeService
from .serp.service import SearchService
from .types import AccountInfo
from .cli_credentials import read_cli_credentials
from http import HTTPStatus
from .exceptions import ValidationError, AuthenticationError, APIError
from .web_unlocker.service import WebUnlockerService


class BrightDataClient:
Expand Down Expand Up @@ -75,20 +76,20 @@ class BrightDataClient:

def __init__(
self,
token: Optional[str] = None,
token: str | None = None,
timeout: int = DEFAULT_TIMEOUT,
web_unlocker_zone: Optional[str] = None,
serp_zone: Optional[str] = None,
browser_username: Optional[str] = None,
browser_password: Optional[str] = None,
browser_host: Optional[str] = None,
browser_port: Optional[int] = None,
web_unlocker_zone: str | None = None,
serp_zone: str | None = None,
browser_username: str | None = None,
browser_password: str | None = None,
browser_host: str | None = None,
browser_port: int | None = None,
auto_create_zones: bool = True,
validate_token: bool = False,
rate_limit: Optional[float] = None,
rate_limit: float | None = None,
rate_period: float = 1.0,
ssl_verify: bool = True,
ssl_ca_cert: Optional[str] = None,
ssl_ca_cert: str | None = None,
):
"""
Initialize Bright Data client.
Expand Down Expand Up @@ -153,17 +154,17 @@ def __init__(
auth_source=self.auth_source,
)

self._scrape_service: Optional[ScrapeService] = None
self._search_service: Optional[SearchService] = None
self._crawler_service: Optional[CrawlerService] = None
self._web_unlocker_service: Optional[WebUnlockerService] = None
self._datasets_client: Optional[DatasetsClient] = None
self._scraper_studio_service: Optional[ScraperStudioService] = None
self._browser_service: Optional[BrowserService] = None
self._discover_service: Optional[DiscoverService] = None
self._zone_manager: Optional[ZoneManager] = None
self._scrape_service: ScrapeService | None = None
self._search_service: SearchService | None = None
self._crawler_service: CrawlerService | None = None
self._web_unlocker_service: WebUnlockerService | None = None
self._datasets_client: DatasetsClient | None = None
self._scraper_studio_service: ScraperStudioService | None = None
self._browser_service: BrowserService | None = None
self._discover_service: DiscoverService | None = None
self._zone_manager: ZoneManager | None = None
self._is_connected = False
self._account_info: Optional[Dict[str, Any]] = None
self._account_info: dict[str, Any] | None = None
self._zones_ensured = False

# Store for validation during __aenter__
Expand All @@ -182,7 +183,7 @@ def _ensure_initialized(self) -> None:
"Use: async with BrightDataClient() as client: ..."
)

def _load_token(self, token: Optional[str]) -> tuple:
def _load_token(self, token: str | None) -> tuple:
"""
Resolve the API token and record where it came from.

Expand Down Expand Up @@ -542,9 +543,9 @@ async def get_account_info(self, refresh: bool = False) -> AccountInfo:
except (AuthenticationError, APIError):
raise
except Exception as e:
raise APIError(f"Unexpected error getting account info: {str(e)}")
raise APIError(f"Unexpected error getting account info: {e!s}")

async def list_zones(self) -> List[Dict[str, Any]]:
async def list_zones(self) -> list[dict[str, Any]]:
"""
List all active zones in your Bright Data account.

Expand Down Expand Up @@ -597,16 +598,16 @@ async def delete_zone(self, zone_name: str) -> None:

async def scrape_url(
self,
url: Union[str, List[str]],
zone: Optional[str] = None,
url: str | list[str],
zone: str | None = None,
country: str = "",
response_format: str = "raw",
method: str = "GET",
timeout: Optional[int] = None,
timeout: int | None = None,
mode: str = "sync",
poll_interval: int = 2,
poll_timeout: int = 30,
) -> Union[ScrapeResult, List[ScrapeResult]]:
) -> ScrapeResult | list[ScrapeResult]:
"""
Direct scraping method (flat API).

Expand Down Expand Up @@ -644,13 +645,13 @@ async def scrape_url(
async def discover(
self,
query: str,
intent: Optional[str] = None,
intent: str | None = None,
include_content: bool = False,
country: Optional[str] = None,
city: Optional[str] = None,
language: Optional[str] = None,
filter_keywords: Optional[List[str]] = None,
num_results: Optional[int] = None,
country: str | None = None,
city: str | None = None,
language: str | None = None,
filter_keywords: list[str] | None = None,
num_results: int | None = None,
format: str = "json",
timeout: int = 60,
poll_interval: int = 2,
Expand Down Expand Up @@ -708,13 +709,13 @@ async def discover(
async def discover_trigger(
self,
query: str,
intent: Optional[str] = None,
intent: str | None = None,
include_content: bool = False,
country: Optional[str] = None,
city: Optional[str] = None,
language: Optional[str] = None,
filter_keywords: Optional[List[str]] = None,
num_results: Optional[int] = None,
country: str | None = None,
city: str | None = None,
language: str | None = None,
filter_keywords: list[str] | None = None,
num_results: int | None = None,
format: str = "json",
) -> DiscoverJob:
"""
Expand Down
Loading