thordata-sdk 0.8.0__tar.gz → 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {thordata_sdk-0.8.0/src/thordata_sdk.egg-info → thordata_sdk-1.0.1}/PKG-INFO +2 -6
  2. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/README.md +1 -5
  3. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/pyproject.toml +1 -1
  4. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/src/thordata/__init__.py +1 -1
  5. thordata_sdk-1.0.1/src/thordata/_example_utils.py +76 -0
  6. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/src/thordata/_utils.py +14 -31
  7. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/src/thordata/async_client.py +153 -92
  8. thordata_sdk-1.0.1/src/thordata/client.py +980 -0
  9. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/src/thordata/models.py +57 -7
  10. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/src/thordata/retry.py +2 -3
  11. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1/src/thordata_sdk.egg-info}/PKG-INFO +2 -6
  12. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/src/thordata_sdk.egg-info/SOURCES.txt +1 -0
  13. thordata_sdk-1.0.1/tests/test_async_client.py +83 -0
  14. thordata_sdk-0.8.0/src/thordata/client.py +0 -1858
  15. thordata_sdk-0.8.0/tests/test_async_client.py +0 -73
  16. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/LICENSE +0 -0
  17. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/setup.cfg +0 -0
  18. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/src/thordata/demo.py +0 -0
  19. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/src/thordata/enums.py +0 -0
  20. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/src/thordata/exceptions.py +0 -0
  21. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/src/thordata_sdk.egg-info/dependency_links.txt +0 -0
  22. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/src/thordata_sdk.egg-info/requires.txt +0 -0
  23. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/src/thordata_sdk.egg-info/top_level.txt +0 -0
  24. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/tests/test_async_client_errors.py +0 -0
  25. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/tests/test_client.py +0 -0
  26. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/tests/test_client_errors.py +0 -0
  27. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/tests/test_enums.py +0 -0
  28. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/tests/test_examples.py +0 -0
  29. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/tests/test_exceptions.py +0 -0
  30. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/tests/test_models.py +0 -0
  31. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/tests/test_spec_parity.py +0 -0
  32. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/tests/test_task_status_and_wait.py +0 -0
  33. {thordata_sdk-0.8.0 → thordata_sdk-1.0.1}/tests/test_user_agent.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thordata-sdk
3
- Version: 0.8.0
3
+ Version: 1.0.1
4
4
  Summary: The Official Python SDK for Thordata - AI Data Infrastructure & Proxy Network.
5
5
  Author-email: Thordata Developer Team <support@thordata.com>
6
6
  License: MIT
@@ -87,14 +87,10 @@ Set environment variables:
87
87
  # Required for Scraper APIs (SERP, Universal, Tasks)
88
88
  export THORDATA_SCRAPER_TOKEN=your_token
89
89
 
90
- # Required for Public/Location APIs (Dashboard -> My Account)
90
+ # Public/Location APIs (Dashboard -> My account -> API)
91
91
  export THORDATA_PUBLIC_TOKEN=your_public_token
92
92
  export THORDATA_PUBLIC_KEY=your_public_key
93
93
 
94
- # Required for Public API NEW (Dashboard -> Public API NEW)
95
- # If not set, SDK falls back to PUBLIC_TOKEN/KEY
96
- export THORDATA_SIGN=your_sign
97
- export THORDATA_API_KEY=your_api_key
98
94
  ```
99
95
 
100
96
  ---
@@ -44,14 +44,10 @@ Set environment variables:
44
44
  # Required for Scraper APIs (SERP, Universal, Tasks)
45
45
  export THORDATA_SCRAPER_TOKEN=your_token
46
46
 
47
- # Required for Public/Location APIs (Dashboard -> My Account)
47
+ # Public/Location APIs (Dashboard -> My account -> API)
48
48
  export THORDATA_PUBLIC_TOKEN=your_public_token
49
49
  export THORDATA_PUBLIC_KEY=your_public_key
50
50
 
51
- # Required for Public API NEW (Dashboard -> Public API NEW)
52
- # If not set, SDK falls back to PUBLIC_TOKEN/KEY
53
- export THORDATA_SIGN=your_sign
54
- export THORDATA_API_KEY=your_api_key
55
51
  ```
56
52
 
57
53
  ---
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "thordata-sdk"
7
- version = "0.8.0"
7
+ version = "1.0.1"
8
8
  description = "The Official Python SDK for Thordata - AI Data Infrastructure & Proxy Network."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -35,7 +35,7 @@ Async Usage:
35
35
  >>> asyncio.run(main())
36
36
  """
37
37
 
38
- __version__ = "0.8.0"
38
+ __version__ = "1.0.1"
39
39
  __author__ = "Thordata Developer Team"
40
40
  __email__ = "support@thordata.com"
41
41
 
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Any, Iterable, Optional
7
+
8
+ try:
9
+ from dotenv import load_dotenv
10
+ except Exception: # pragma: no cover
11
+ load_dotenv = None
12
+
13
+
14
+ def load_env() -> None:
15
+ """Load .env from repo root if python-dotenv is installed."""
16
+ if load_dotenv is None:
17
+ return
18
+ repo_root = Path(__file__).resolve().parents[2]
19
+ load_dotenv(dotenv_path=repo_root / ".env")
20
+
21
+
22
+ def env(name: str) -> str:
23
+ return (os.getenv(name) or "").strip()
24
+
25
+
26
+ def skip_if_missing(required: Iterable[str], *, tip: Optional[str] = None) -> bool:
27
+ missing = [k for k in required if not env(k)]
28
+ if not missing:
29
+ return False
30
+ print("Skipping live example: missing env:", ", ".join(missing))
31
+ if tip:
32
+ print(tip)
33
+ else:
34
+ print("Tip: copy .env.example to .env and fill values, then re-run.")
35
+ return True
36
+
37
+
38
+ def parse_json_env(name: str, default: str = "{}") -> Any:
39
+ raw = env(name) or default
40
+ return json.loads(raw)
41
+
42
+
43
+ def normalize_task_parameters(raw: Any) -> dict[str, Any]:
44
+ """Accept {..} or [{..}] and return a single dict for create_scraper_task(parameters=...)."""
45
+ if isinstance(raw, list):
46
+ if not raw:
47
+ raise ValueError("Task parameters JSON array must not be empty")
48
+ raw = raw[0]
49
+ if not isinstance(raw, dict):
50
+ raise ValueError("Task parameters must be a JSON object (or array of objects)")
51
+ return raw
52
+
53
+
54
+ def output_dir() -> Path:
55
+ """Return output dir for examples; defaults to examples/output (ignored by git)."""
56
+ repo_root = Path(__file__).resolve().parents[2]
57
+ d = env("THORDATA_OUTPUT_DIR") or str(repo_root / "examples" / "output")
58
+ p = Path(d)
59
+ p.mkdir(parents=True, exist_ok=True)
60
+ return p
61
+
62
+
63
+ def write_text(filename: str, content: str) -> Path:
64
+ p = output_dir() / filename
65
+ p.write_text(content, encoding="utf-8", errors="replace")
66
+ return p
67
+
68
+
69
+ def write_json(filename: str, data: Any) -> Path:
70
+ p = output_dir() / filename
71
+ p.write_text(
72
+ json.dumps(data, ensure_ascii=False, indent=2),
73
+ encoding="utf-8",
74
+ errors="replace",
75
+ )
76
+ return p
@@ -9,6 +9,7 @@ from __future__ import annotations
9
9
  import base64
10
10
  import json
11
11
  import logging
12
+ import platform
12
13
  from typing import Any, Dict
13
14
 
14
15
  logger = logging.getLogger(__name__)
@@ -171,37 +172,19 @@ def extract_error_message(payload: Any) -> str:
171
172
 
172
173
  def build_user_agent(sdk_version: str, http_client: str) -> str:
173
174
  """
174
- Build a default User-Agent for the SDK.
175
-
176
- Args:
177
- sdk_version: SDK version string.
178
- http_client: "requests" or "aiohttp" (or any identifier).
179
-
180
- Returns:
181
- A User-Agent string.
182
- """
183
- import platform
184
-
185
- py = platform.python_version()
186
- system = platform.system()
187
- return f"thordata-python-sdk/{sdk_version} (python {py}; {system}; {http_client})"
188
-
189
-
190
- def build_sign_headers(sign: str, api_key: str) -> Dict[str, str]:
175
+ Build a standardized User-Agent for the SDK.
176
+ Format: thordata-python-sdk/{version} python/{py_ver} ({system}/{release}; {machine})
191
177
  """
192
- Build headers for Public API NEW (sign + apiKey authentication).
178
+ py_ver = platform.python_version()
179
+ system = platform.system() or "unknown"
180
+ release = platform.release() or "unknown"
181
+ machine = platform.machine() or "unknown"
193
182
 
194
- This is a different authentication system from token+key.
183
+ # Clean up strings to avoid UA parsing issues (remove newlines, etc)
184
+ system = system.replace(";", "").strip()
195
185
 
196
- Args:
197
- sign: The sign value from Dashboard (immutable).
198
- api_key: The apiKey value from Dashboard (can be changed).
199
-
200
- Returns:
201
- Headers dict with sign, apiKey, and Content-Type.
202
- """
203
- return {
204
- "sign": sign,
205
- "apiKey": api_key,
206
- "Content-Type": "application/x-www-form-urlencoded",
207
- }
186
+ return (
187
+ f"thordata-python-sdk/{sdk_version} "
188
+ f"python/{py_ver} "
189
+ f"({system}/{release}; {machine}; {http_client})"
190
+ )
@@ -25,7 +25,7 @@ from __future__ import annotations
25
25
  import asyncio
26
26
  import logging
27
27
  import os
28
- from datetime import date, datetime
28
+ from datetime import date
29
29
  from typing import Any, Dict, List, Optional, Union
30
30
 
31
31
  import aiohttp
@@ -35,7 +35,6 @@ from ._utils import (
35
35
  build_auth_headers,
36
36
  build_builder_headers,
37
37
  build_public_api_headers,
38
- build_sign_headers,
39
38
  build_user_agent,
40
39
  decode_base64_image,
41
40
  extract_error_message,
@@ -51,14 +50,15 @@ from .exceptions import (
51
50
  from .models import (
52
51
  CommonSettings,
53
52
  ProxyConfig,
53
+ ProxyProduct,
54
54
  ProxyServer,
55
- ProxyUser,
56
55
  ProxyUserList,
57
56
  ScraperTaskConfig,
58
57
  SerpRequest,
59
58
  UniversalScrapeRequest,
60
59
  UsageStatistics,
61
60
  VideoTaskConfig,
61
+ WhitelistProxyConfig,
62
62
  )
63
63
  from .retry import RetryConfig
64
64
 
@@ -100,8 +100,6 @@ class AsyncThordataClient:
100
100
  scraper_token: str,
101
101
  public_token: Optional[str] = None,
102
102
  public_key: Optional[str] = None,
103
- sign: Optional[str] = None,
104
- api_key: Optional[str] = None,
105
103
  proxy_host: str = "pr.thordata.net",
106
104
  proxy_port: int = 9999,
107
105
  timeout: int = 30,
@@ -121,18 +119,9 @@ class AsyncThordataClient:
121
119
  self.public_token = public_token
122
120
  self.public_key = public_key
123
121
 
124
- # Automatic Fallback Logic: If sign/api_key is not provided, try using public_token/key
125
- self.sign = sign or os.getenv("THORDATA_SIGN") or self.public_token
126
- self.api_key = api_key or os.getenv("THORDATA_API_KEY") or self.public_key
127
-
128
- # Public API authentication
129
- self.sign = sign or os.getenv("THORDATA_SIGN")
130
- self.api_key = api_key or os.getenv("THORDATA_API_KEY")
131
-
132
122
  # Proxy configuration
133
123
  self._proxy_host = proxy_host
134
124
  self._proxy_port = proxy_port
135
- self._default_timeout = aiohttp.ClientTimeout(total=timeout)
136
125
 
137
126
  # Timeout configuration
138
127
  self._default_timeout = aiohttp.ClientTimeout(total=timeout)
@@ -141,19 +130,13 @@ class AsyncThordataClient:
141
130
  # Retry configuration
142
131
  self._retry_config = retry_config or RetryConfig()
143
132
 
144
- # Authentication mode
133
+ # Authentication mode (for scraping APIs)
145
134
  self._auth_mode = auth_mode.lower()
146
135
  if self._auth_mode not in ("bearer", "header_token"):
147
136
  raise ThordataConfigError(
148
137
  f"Invalid auth_mode: {auth_mode}. Must be 'bearer' or 'header_token'."
149
138
  )
150
139
 
151
- # Pre-calculate proxy auth
152
- self._proxy_url = f"http://{proxy_host}:{proxy_port}"
153
- self._proxy_auth = aiohttp.BasicAuth(
154
- login=f"td-customer-{scraper_token}", password=""
155
- )
156
-
157
140
  # Base URLs (allow override via args or env vars for testing and custom routing)
158
141
  scraperapi_base = (
159
142
  scraperapi_base_url
@@ -179,6 +162,7 @@ class AsyncThordataClient:
179
162
  or self.LOCATIONS_URL
180
163
  ).rstrip("/")
181
164
 
165
+ # Keep these env overrides for now
182
166
  gateway_base = os.getenv(
183
167
  "THORDATA_GATEWAY_BASE_URL", "https://api.thordata.com/api/gateway"
184
168
  )
@@ -193,8 +177,11 @@ class AsyncThordataClient:
193
177
  self._builder_url = f"{scraperapi_base}/builder"
194
178
  self._video_builder_url = f"{scraperapi_base}/video_builder"
195
179
  self._universal_url = f"{universalapi_base}/request"
180
+
196
181
  self._status_url = f"{web_scraper_api_base}/tasks-status"
197
182
  self._download_url = f"{web_scraper_api_base}/tasks-download"
183
+ self._list_url = f"{web_scraper_api_base}/tasks-list"
184
+
198
185
  self._locations_base_url = locations_base
199
186
  self._usage_stats_url = (
200
187
  f"{locations_base.replace('/locations', '')}/account/usage-statistics"
@@ -202,16 +189,17 @@ class AsyncThordataClient:
202
189
  self._proxy_users_url = (
203
190
  f"{locations_base.replace('/locations', '')}/proxy-users"
204
191
  )
192
+
205
193
  whitelist_base = os.getenv(
206
194
  "THORDATA_WHITELIST_BASE_URL", "https://api.thordata.com/api"
207
195
  )
208
196
  self._whitelist_url = f"{whitelist_base}/whitelisted-ips"
197
+
209
198
  proxy_api_base = os.getenv(
210
199
  "THORDATA_PROXY_API_BASE_URL", "https://api.thordata.com/api"
211
200
  )
212
201
  self._proxy_list_url = f"{proxy_api_base}/proxy/proxy-list"
213
202
  self._proxy_expiration_url = f"{proxy_api_base}/proxy/expiration-time"
214
- self._list_url = f"{web_scraper_api_base}/tasks-list"
215
203
 
216
204
  # Session initialized lazily
217
205
  self._session: Optional[aiohttp.ClientSession] = None
@@ -271,11 +259,26 @@ class AsyncThordataClient:
271
259
 
272
260
  logger.debug(f"Async Proxy GET: {url}")
273
261
 
274
- if proxy_config:
275
- proxy_url, proxy_auth = proxy_config.to_aiohttp_config()
276
- else:
277
- proxy_url = self._proxy_url
278
- proxy_auth = self._proxy_auth
262
+ if proxy_config is None:
263
+ proxy_config = self._get_default_proxy_config_from_env()
264
+
265
+ if proxy_config is None:
266
+ raise ThordataConfigError(
267
+ "Proxy credentials are missing. "
268
+ "Pass proxy_config=ProxyConfig(username=..., password=..., product=...) "
269
+ "or set THORDATA_RESIDENTIAL_USERNAME/THORDATA_RESIDENTIAL_PASSWORD (or DATACENTER/MOBILE)."
270
+ )
271
+
272
+ # aiohttp has limited support for "https://" proxies (TLS to proxy / TLS-in-TLS).
273
+ # Your account's proxy endpoint requires HTTPS proxy, so we explicitly block here
274
+ # to avoid confusing "it always fails" behavior.
275
+ if getattr(proxy_config, "protocol", "http").lower() == "https":
276
+ raise ThordataConfigError(
277
+ "Proxy Network requires an HTTPS proxy endpoint (TLS to proxy) for your account. "
278
+ "aiohttp support for 'https://' proxies is limited and may fail. "
279
+ "Please use ThordataClient.get/post (sync client) for Proxy Network requests."
280
+ )
281
+ proxy_url, proxy_auth = proxy_config.to_aiohttp_config()
279
282
 
280
283
  try:
281
284
  return await session.get(
@@ -312,11 +315,26 @@ class AsyncThordataClient:
312
315
 
313
316
  logger.debug(f"Async Proxy POST: {url}")
314
317
 
315
- if proxy_config:
316
- proxy_url, proxy_auth = proxy_config.to_aiohttp_config()
317
- else:
318
- proxy_url = self._proxy_url
319
- proxy_auth = self._proxy_auth
318
+ if proxy_config is None:
319
+ proxy_config = self._get_default_proxy_config_from_env()
320
+
321
+ if proxy_config is None:
322
+ raise ThordataConfigError(
323
+ "Proxy credentials are missing. "
324
+ "Pass proxy_config=ProxyConfig(username=..., password=..., product=...) "
325
+ "or set THORDATA_RESIDENTIAL_USERNAME/THORDATA_RESIDENTIAL_PASSWORD (or DATACENTER/MOBILE)."
326
+ )
327
+
328
+ # aiohttp has limited support for "https://" proxies (TLS to proxy / TLS-in-TLS).
329
+ # Your account's proxy endpoint requires HTTPS proxy, so we explicitly block here
330
+ # to avoid confusing "it always fails" behavior.
331
+ if getattr(proxy_config, "protocol", "http").lower() == "https":
332
+ raise ThordataConfigError(
333
+ "Proxy Network requires an HTTPS proxy endpoint (TLS to proxy) for your account. "
334
+ "aiohttp support for 'https://' proxies is limited and may fail. "
335
+ "Please use ThordataClient.get/post (sync client) for Proxy Network requests."
336
+ )
337
+ proxy_url, proxy_auth = proxy_config.to_aiohttp_config()
320
338
 
321
339
  try:
322
340
  return await session.post(
@@ -973,23 +991,14 @@ class AsyncThordataClient:
973
991
 
974
992
  async def get_residential_balance(self) -> Dict[str, Any]:
975
993
  """
976
- Get residential proxy balance (Public API NEW).
977
-
978
- Requires sign and apiKey credentials.
994
+ Get residential proxy balance.
979
995
 
980
- Returns:
981
- Dict with 'balance' (bytes) and 'expire_time' (timestamp).
996
+ Uses public_token/public_key.
982
997
  """
983
- if not self.sign or not self.api_key:
984
- raise ThordataConfigError(
985
- "sign and api_key are required for Public API NEW. "
986
- "Set THORDATA_SIGN and THORDATA_API_KEY environment variables."
987
- )
988
-
989
998
  session = self._get_session()
990
- headers = build_sign_headers(self.sign, self.api_key)
999
+ headers = self._build_gateway_headers()
991
1000
 
992
- logger.info("Async getting residential proxy balance (API NEW)")
1001
+ logger.info("Async getting residential proxy balance")
993
1002
 
994
1003
  try:
995
1004
  async with session.post(
@@ -1025,26 +1034,13 @@ class AsyncThordataClient:
1025
1034
  end_time: Union[str, int],
1026
1035
  ) -> Dict[str, Any]:
1027
1036
  """
1028
- Get residential proxy usage records (Public API NEW).
1029
-
1030
- Args:
1031
- start_time: Start timestamp (Unix timestamp or YYYY-MM-DD HH:MM:SS).
1032
- end_time: End timestamp (Unix timestamp or YYYY-MM-DD HH:MM:SS).
1037
+ Get residential proxy usage records.
1033
1038
 
1034
- Returns:
1035
- Dict with usage data including 'all_flow', 'all_used_flow', 'data' list.
1039
+ Uses public_token/public_key.
1036
1040
  """
1037
- if not self.sign or not self.api_key:
1038
- raise ThordataConfigError(
1039
- "sign and api_key are required for Public API NEW."
1040
- )
1041
-
1042
1041
  session = self._get_session()
1043
- headers = build_sign_headers(self.sign, self.api_key)
1044
- payload = {
1045
- "start_time": str(start_time),
1046
- "end_time": str(end_time),
1047
- }
1042
+ headers = self._build_gateway_headers()
1043
+ payload = {"start_time": str(start_time), "end_time": str(end_time)}
1048
1044
 
1049
1045
  logger.info(f"Async getting residential usage: {start_time} to {end_time}")
1050
1046
 
@@ -1296,20 +1292,14 @@ class AsyncThordataClient:
1296
1292
 
1297
1293
  async def get_isp_regions(self) -> List[Dict[str, Any]]:
1298
1294
  """
1299
- Get available ISP proxy regions (Public API NEW).
1295
+ Get available ISP proxy regions.
1300
1296
 
1301
- Returns:
1302
- List of regions with id, continent, country, city, num, pricing.
1297
+ Uses public_token/public_key.
1303
1298
  """
1304
- if not self.sign or not self.api_key:
1305
- raise ThordataConfigError(
1306
- "sign and api_key are required for Public API NEW."
1307
- )
1308
-
1309
1299
  session = self._get_session()
1310
- headers = build_sign_headers(self.sign, self.api_key)
1300
+ headers = self._build_gateway_headers()
1311
1301
 
1312
- logger.info("Async getting ISP regions (API NEW)")
1302
+ logger.info("Async getting ISP regions")
1313
1303
 
1314
1304
  try:
1315
1305
  async with session.post(
@@ -1341,20 +1331,14 @@ class AsyncThordataClient:
1341
1331
 
1342
1332
  async def list_isp_proxies(self) -> List[Dict[str, Any]]:
1343
1333
  """
1344
- List ISP proxies (Public API NEW).
1334
+ List ISP proxies.
1345
1335
 
1346
- Returns:
1347
- List of ISP proxies with ip, port, user, pwd, startTime, expireTime.
1336
+ Uses public_token/public_key.
1348
1337
  """
1349
- if not self.sign or not self.api_key:
1350
- raise ThordataConfigError(
1351
- "sign and api_key are required for Public API NEW."
1352
- )
1353
-
1354
1338
  session = self._get_session()
1355
- headers = build_sign_headers(self.sign, self.api_key)
1339
+ headers = self._build_gateway_headers()
1356
1340
 
1357
- logger.info("Async listing ISP proxies (API NEW)")
1341
+ logger.info("Async listing ISP proxies")
1358
1342
 
1359
1343
  try:
1360
1344
  async with session.post(
@@ -1386,20 +1370,14 @@ class AsyncThordataClient:
1386
1370
 
1387
1371
  async def get_wallet_balance(self) -> Dict[str, Any]:
1388
1372
  """
1389
- Get wallet balance for ISP proxies (Public API NEW).
1373
+ Get wallet balance for ISP proxies.
1390
1374
 
1391
- Returns:
1392
- Dict with 'walletBalance'.
1375
+ Uses public_token/public_key.
1393
1376
  """
1394
- if not self.sign or not self.api_key:
1395
- raise ThordataConfigError(
1396
- "sign and api_key are required for Public API NEW."
1397
- )
1398
-
1399
1377
  session = self._get_session()
1400
- headers = build_sign_headers(self.sign, self.api_key)
1378
+ headers = self._build_gateway_headers()
1401
1379
 
1402
- logger.info("Async getting wallet balance (API NEW)")
1380
+ logger.info("Async getting wallet balance")
1403
1381
 
1404
1382
  try:
1405
1383
  async with session.post(
@@ -1592,3 +1570,86 @@ class AsyncThordataClient:
1592
1570
  "public_token and public_key are required for this operation. "
1593
1571
  "Please provide them when initializing AsyncThordataClient."
1594
1572
  )
1573
+
1574
+ def _get_proxy_endpoint_overrides(
1575
+ self, product: ProxyProduct
1576
+ ) -> tuple[Optional[str], Optional[int], str]:
1577
+ prefix = product.value.upper()
1578
+
1579
+ host = os.getenv(f"THORDATA_{prefix}_PROXY_HOST") or os.getenv(
1580
+ "THORDATA_PROXY_HOST"
1581
+ )
1582
+ port_raw = os.getenv(f"THORDATA_{prefix}_PROXY_PORT") or os.getenv(
1583
+ "THORDATA_PROXY_PORT"
1584
+ )
1585
+ protocol = (
1586
+ os.getenv(f"THORDATA_{prefix}_PROXY_PROTOCOL")
1587
+ or os.getenv("THORDATA_PROXY_PROTOCOL")
1588
+ or "http"
1589
+ )
1590
+
1591
+ port: Optional[int] = None
1592
+ if port_raw:
1593
+ try:
1594
+ port = int(port_raw)
1595
+ except ValueError:
1596
+ port = None
1597
+
1598
+ return host or None, port, protocol
1599
+
1600
+ def _get_default_proxy_config_from_env(self) -> Optional[ProxyConfig]:
1601
+ u = os.getenv("THORDATA_RESIDENTIAL_USERNAME")
1602
+ p = os.getenv("THORDATA_RESIDENTIAL_PASSWORD")
1603
+ if u and p:
1604
+ host, port, protocol = self._get_proxy_endpoint_overrides(
1605
+ ProxyProduct.RESIDENTIAL
1606
+ )
1607
+ return ProxyConfig(
1608
+ username=u,
1609
+ password=p,
1610
+ product=ProxyProduct.RESIDENTIAL,
1611
+ host=host,
1612
+ port=port,
1613
+ protocol=protocol,
1614
+ )
1615
+
1616
+ u = os.getenv("THORDATA_DATACENTER_USERNAME")
1617
+ p = os.getenv("THORDATA_DATACENTER_PASSWORD")
1618
+ if u and p:
1619
+ host, port, protocol = self._get_proxy_endpoint_overrides(
1620
+ ProxyProduct.DATACENTER
1621
+ )
1622
+ return ProxyConfig(
1623
+ username=u,
1624
+ password=p,
1625
+ product=ProxyProduct.DATACENTER,
1626
+ host=host,
1627
+ port=port,
1628
+ protocol=protocol,
1629
+ )
1630
+
1631
+ u = os.getenv("THORDATA_MOBILE_USERNAME")
1632
+ p = os.getenv("THORDATA_MOBILE_PASSWORD")
1633
+ if u and p:
1634
+ host, port, protocol = self._get_proxy_endpoint_overrides(
1635
+ ProxyProduct.MOBILE
1636
+ )
1637
+ return ProxyConfig(
1638
+ username=u,
1639
+ password=p,
1640
+ product=ProxyProduct.MOBILE,
1641
+ host=host,
1642
+ port=port,
1643
+ protocol=protocol,
1644
+ )
1645
+
1646
+ return None
1647
+
1648
+ def _build_gateway_headers(self) -> Dict[str, str]:
1649
+ """
1650
+ Headers for gateway-style endpoints.
1651
+
1652
+ Per our SDK rule: ONLY public_token/public_key exist.
1653
+ """
1654
+ self._require_public_credentials()
1655
+ return build_public_api_headers(self.public_token or "", self.public_key or "")