Skip to content

Commit 61d0f6f

Browse files
committed
black formatter
1 parent 3cf2006 commit 61d0f6f

File tree

14 files changed

+200
-126
lines changed

14 files changed

+200
-126
lines changed

.sphinx/conf.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
import os
1111
import sys
1212

13-
sys.path.insert(0, os.path.abspath('../'))
14-
pyproject = toml.load('../pyproject.toml')
13+
sys.path.insert(0, os.path.abspath("../"))
14+
pyproject = toml.load("../pyproject.toml")
1515

16-
project = 'ProxyProviders'
17-
copyright = '2025, David Teather'
18-
author = 'David Teather'
19-
release = pyproject['project']['version']
16+
project = "ProxyProviders"
17+
copyright = "2025, David Teather"
18+
author = "David Teather"
19+
release = pyproject["project"]["version"]
2020

2121
# -- General configuration ---------------------------------------------------
2222
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
@@ -33,20 +33,19 @@
3333

3434
autosummary_generate = True
3535

36-
autodoc_default_flags = ['members', 'undoc-members', 'show-inheritance']
37-
autodoc_member_order = 'bysource'
36+
autodoc_default_flags = ["members", "undoc-members", "show-inheritance"]
37+
autodoc_member_order = "bysource"
3838

3939
templates_path = ["_templates"]
4040
exclude_patterns = ["docs", "Thumbs.db", ".DS_Store"]
4141

4242
napoleon_google_docstring = True
4343

4444

45-
4645
# -- Options for HTML output -------------------------------------------------
4746
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
4847

4948
html_theme = "sphinx_rtd_theme"
5049
html_baseurl = "https://davidteather.github.io/proxyproviders/"
5150

52-
source_suffix = {".rst": "restructuredtext", ".md": "markdown", ".txt": "rst"}
51+
source_suffix = {".rst": "restructuredtext", ".md": "markdown", ".txt": "rst"}

examples/request_with_proxy_provider.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ def request_with_proxy(provider: ProxyProvider):
1717
r = requests.get("https://httpbin.org/ip", proxies=requests_proxy)
1818
return r.json()
1919

20+
2021
webshare = Webshare(api_key="your_api_key")
2122
brightdata = BrightData(api_key="your_api_key", zone="your_zone")
2223

2324
print(f"Your IP: {request_with_proxy(None)}")
2425
print(f"Webshare: {request_with_proxy(webshare)}")
25-
print(f"BrightData: {request_with_proxy(brightdata)}")
26+
print(f"BrightData: {request_with_proxy(brightdata)}")

proxyproviders/exceptions.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,35 @@
11
from typing import Optional
2+
23
"""
34
All custom exceptions for the ProxyProvider classes are defined here.
45
"""
56

7+
68
class ProxyProviderException(Exception):
79
"""Base class for all ProxyProvider errors."""
10+
811
pass
912

13+
1014
class ProxyFetchException(ProxyProviderException):
1115
"""Raised when there is an error fetching proxies from the provider."""
16+
1217
def __init__(self, message: str, status_code: Optional[int] = None):
1318
self.message = message
1419
self.status_code = status_code
1520
super().__init__(self.message)
1621

22+
1723
class ProxyConversionException(ProxyProviderException):
1824
"""Raised when there is an error converting proxy data to a standardized format."""
25+
1926
pass
2027

28+
2129
class ProxyInvalidResponseException(ProxyProviderException):
2230
"""Raised when the provider returns an invalid response."""
31+
2332
def __init__(self, response: str):
2433
self.response = response
2534
self.message = f"Invalid response received: {response}"
2635
super().__init__(self.message)
27-

proxyproviders/models/proxy.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,34 @@
22
from typing import Optional, List
33
from datetime import datetime
44

5+
56
@dataclass
67
class Proxy:
78
"""Our shared data model for a proxy object across all providers."""
8-
9+
910
id: str
1011
"""A unique identifier for the proxy"""
11-
12+
1213
username: str
1314
"""The username required for authenticating with the proxy"""
14-
15+
1516
password: str
1617
"""The password required for authenticating with the proxy"""
17-
18+
1819
proxy_address: str
1920
"""The IP address or domain name of the proxy"""
20-
21+
2122
port: int
2223
"""The port number through which the proxy connection is established"""
23-
24+
2425
country_code: Optional[str] = None
2526
"""The country code where the proxy is located, e.g., 'US', 'FR'. Optional"""
26-
27+
2728
city_name: Optional[str] = None
2829
"""The city name where the proxy is located, e.g., 'New York', 'Paris'. Optional"""
29-
30+
3031
created_at: Optional[datetime] = None
3132
"""The timestamp when the proxy was created. Optional"""
32-
33+
3334
protocols: Optional[List[str]] = None
3435
"""A list of connection protocols supported by the proxy, e.g., ['http', 'https']"""

proxyproviders/providers/brightdata.py

Lines changed: 70 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import requests
22
from typing import List, Dict, Optional
33
from ..proxy_provider import ProxyProvider, ProxyConfig
4-
from ..exceptions import ProxyFetchException,ProxyInvalidResponseException
4+
from ..exceptions import ProxyFetchException, ProxyInvalidResponseException
55
from ..models.proxy import Proxy
66
import threading
77

8+
89
class BrightData(ProxyProvider):
910
"""BrightData (formerly luminati) is a proxy provider that offers residential and datacenter proxies.
1011
1112
Create an account `here <https://get.brightdata.com/davidteather>`_ (affiliate link).
12-
13+
1314
You can find your API key in the account settings `here <https://brightdata.com/cp/setting/users>`_ then create "add token" with scope "limit" (`BrightData article <https://docs.brightdata.com/general/account/api-token>`_ for more info)
1415
1516
The BrightData API documentation is `here <https://docs.brightdata.com/api-reference/account-management-api/Get_active_Zones?playground=open>`_
@@ -36,12 +37,20 @@ class BrightData(ProxyProvider):
3637
proxy_provider = BrightData(api_key="your-api-key", zone="my_zone", use_super_proxy=False)
3738
proxies = proxy_provider.list_proxies() # returns multiple proxies for each IP in the zone (potentially thousands)
3839
"""
40+
3941
_BASE_URL = "https://api.brightdata.com"
4042
_SUPER_PROXY_ADDRESS = "brd.superproxy.io"
4143
_SUPER_PROXY_PORT = 33335
42-
_PROTOCOLS: List[str] = ["http", "https"] # BrightData supports both HTTP and HTTPS
43-
44-
def __init__(self, api_key: str, zone: str, username_suffix: Optional[str] = None, use_super_proxy: Optional[bool] = True, config: Optional[ProxyConfig] = None):
44+
_PROTOCOLS: List[str] = ["http", "https"] # BrightData supports both HTTP and HTTPS
45+
46+
def __init__(
47+
self,
48+
api_key: str,
49+
zone: str,
50+
username_suffix: Optional[str] = None,
51+
use_super_proxy: Optional[bool] = True,
52+
config: Optional[ProxyConfig] = None,
53+
):
4554
super().__init__(config)
4655
self.api_key = api_key
4756
self.zone = zone
@@ -57,15 +66,17 @@ def _fetch_proxies(self) -> List[Proxy]:
5766

5867
if self.use_super_proxy:
5968
# Let the super proxy handle the IP rotation
60-
return [Proxy(
61-
id="super",
62-
username=username,
63-
password=passwords["passwords"][0],
64-
proxy_address=self._SUPER_PROXY_ADDRESS,
65-
port=self._SUPER_PROXY_PORT,
66-
protocols=self._PROTOCOLS,
67-
)]
68-
69+
return [
70+
Proxy(
71+
id="super",
72+
username=username,
73+
password=passwords["passwords"][0],
74+
proxy_address=self._SUPER_PROXY_ADDRESS,
75+
port=self._SUPER_PROXY_PORT,
76+
protocols=self._PROTOCOLS,
77+
)
78+
]
79+
6980
proxies = []
7081

7182
# Fetch all IPs in the zone, and create a proxy for each
@@ -75,22 +86,23 @@ def _fetch_proxies(self) -> List[Proxy]:
7586
for ip in ips:
7687
ip_targeted_username = username + f"-ip-{ip['ip']}"
7788

78-
proxies.append(Proxy(
79-
id=ip["ip"],
80-
username=ip_targeted_username,
81-
password=passwords["passwords"][0],
82-
proxy_address=self._SUPER_PROXY_ADDRESS,
83-
port=self._SUPER_PROXY_PORT,
84-
country_code=ip["country"],
85-
protocols=self._PROTOCOLS,
86-
))
89+
proxies.append(
90+
Proxy(
91+
id=ip["ip"],
92+
username=ip_targeted_username,
93+
password=passwords["passwords"][0],
94+
proxy_address=self._SUPER_PROXY_ADDRESS,
95+
port=self._SUPER_PROXY_PORT,
96+
country_code=ip["country"],
97+
protocols=self._PROTOCOLS,
98+
)
99+
)
87100

88101
return proxies
89102

90-
91103
def get_active_zones(self) -> Dict:
92104
"""Fetches active zones from BrightData API.
93-
105+
94106
Response:
95107
96108
.. code-block:: json
@@ -104,27 +116,26 @@ def get_active_zones(self) -> Dict:
104116
105117
"""
106118
return self._make_request("/zone/get_active_zones", "GET")
107-
119+
108120
def get_zone_username(self, zone: str) -> str:
109121
"""Fetches zone username for the given zone ID from BrightData API.
110-
122+
111123
Note: this isn't directly an API endpoint, I'm sort of reconstructing some things here and it seems to behave a little weird.
112124
113125
:param zone: The zone ID to fetch username for
114126
"""
115127

116128
data = self._make_request(f"/status", "GET", params={"zone": zone})
117-
129+
118130
customer = data.get("customer")
119131
if not customer:
120132
raise ProxyInvalidResponseException("Failed to fetch customer data")
121-
122-
return f"brd-customer-{customer}-zone-{zone}"
123133

134+
return f"brd-customer-{customer}-zone-{zone}"
124135

125136
def get_zone_passwords(self, zone: str) -> Dict:
126137
"""Fetches zone passwords from BrightData API.
127-
138+
128139
:param zone: The zone ID to fetch passwords for
129140
130141
Response:
@@ -141,10 +152,10 @@ def get_zone_passwords(self, zone: str) -> Dict:
141152
142153
"""
143154
return self._make_request(f"/zone/passwords", "GET", params={"zone": zone})
144-
155+
145156
def list_all_ips_in_zone(self, zone: str, country: Optional[str] = None) -> Dict:
146157
"""Fetches all IPs in a zone from BrightData API.
147-
158+
148159
:param zone: The zone ID to fetch IPs for
149160
:param country: Optional 2-letter country code to filter IPs by
150161
@@ -159,13 +170,23 @@ def list_all_ips_in_zone(self, zone: str, country: Optional[str] = None) -> Dict
159170
"country": "US",
160171
}
161172
]
162-
173+
163174
"""
164-
return self._make_request(f"/zone/route_ips", "GET", params={"zone": zone, "list_countries": True, "country": country})
165-
166-
def _make_request(self, path: str, method: str, params: Optional[Dict] = None, json: Optional[Dict] = None) -> Dict:
175+
return self._make_request(
176+
f"/zone/route_ips",
177+
"GET",
178+
params={"zone": zone, "list_countries": True, "country": country},
179+
)
180+
181+
def _make_request(
182+
self,
183+
path: str,
184+
method: str,
185+
params: Optional[Dict] = None,
186+
json: Optional[Dict] = None,
187+
) -> Dict:
167188
"""Makes a request to the BrightData API.
168-
189+
169190
:param path: The path to the endpoint
170191
:param method: The HTTP method to use
171192
:param params: Optional parameters to include in the request
@@ -177,15 +198,20 @@ def _make_request(self, path: str, method: str, params: Optional[Dict] = None, j
177198
}
178199

179200
url = f"{self._BASE_URL}{path}"
180-
response = requests.request(method, url, headers=headers, params=params, json=json)
201+
response = requests.request(
202+
method, url, headers=headers, params=params, json=json
203+
)
181204

182205
if response.status_code != 200:
183-
raise ProxyFetchException(f"Failed to fetch from BrightData, got status code {response.status_code}, text: {response.text}")
206+
raise ProxyFetchException(
207+
f"Failed to fetch from BrightData, got status code {response.status_code}, text: {response.text}"
208+
)
184209

185-
try:
210+
try:
186211
data = response.json()
187212
except Exception as e:
188-
raise ProxyInvalidResponseException(f"Failed to parse response: {str(e)}") from e
189-
213+
raise ProxyInvalidResponseException(
214+
f"Failed to parse response: {str(e)}"
215+
) from e
190216

191-
return data
217+
return data

0 commit comments

Comments
 (0)