dataquery-sdk 0.1.2__tar.gz → 0.1.4__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.
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/CHANGELOG.md +5 -1
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/PKG-INFO +1 -1
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery/__init__.py +1 -1
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery/client.py +3 -2
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery/config.py +3 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery/dataquery.py +232 -13
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery/models.py +1 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery_sdk.egg-info/PKG-INFO +1 -1
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/CONTRIBUTING.md +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/LICENSE +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/MANIFEST.in +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/README.md +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery/auth.py +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery/auto_download.py +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery/cli.py +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery/connection_pool.py +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery/exceptions.py +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery/logging_config.py +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery/py.typed +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery/rate_limiter.py +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery/retry.py +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery/utils.py +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery_sdk.egg-info/SOURCES.txt +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery_sdk.egg-info/dependency_links.txt +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery_sdk.egg-info/entry_points.txt +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery_sdk.egg-info/requires.txt +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/dataquery_sdk.egg-info/top_level.txt +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/pyproject.toml +0 -0
- {dataquery_sdk-0.1.2 → dataquery_sdk-0.1.4}/setup.cfg +0 -0
|
@@ -20,4 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
20
20
|
## [0.1.1] - 2026-02-18
|
|
21
21
|
- Non blocking IO
|
|
22
22
|
## [0.1.2] - 2026-02-18
|
|
23
|
-
- File already exists status added
|
|
23
|
+
- File already exists status added
|
|
24
|
+
## [0.1.3] - 2026-02-21
|
|
25
|
+
- Historical file download added
|
|
26
|
+
## [0.1.4] - 2026-03-23
|
|
27
|
+
- Introduced circuit breaker environment variable (`DATAQUERY_CIRCUIT_BREAKER_THRESHOLD`)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dataquery-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Python SDK for DATAQUERY Data API - Query, download, and check availability of economic data files
|
|
5
5
|
Author-email: DATAQUERY SDK Team <support@dataquery.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/dataquery/dataquery-sdk
|
|
@@ -134,6 +134,7 @@ class DataQueryClient:
|
|
|
134
134
|
max_delay=300.0,
|
|
135
135
|
strategy=RetryStrategy.EXPONENTIAL,
|
|
136
136
|
enable_circuit_breaker=True,
|
|
137
|
+
circuit_breaker_threshold=self.config.circuit_breaker_threshold,
|
|
137
138
|
retryable_exceptions=[
|
|
138
139
|
ConnectionError,
|
|
139
140
|
TimeoutError,
|
|
@@ -786,7 +787,7 @@ class DataQueryClient:
|
|
|
786
787
|
|
|
787
788
|
# Validate num_parts if provided
|
|
788
789
|
if num_parts is not None and (num_parts is None or num_parts <= 0):
|
|
789
|
-
num_parts =
|
|
790
|
+
num_parts = 1
|
|
790
791
|
|
|
791
792
|
params = {"file-group-id": file_group_id}
|
|
792
793
|
if file_datetime:
|
|
@@ -847,7 +848,7 @@ class DataQueryClient:
|
|
|
847
848
|
file_group_id: str,
|
|
848
849
|
file_datetime: Optional[str] = None,
|
|
849
850
|
options: Optional[DownloadOptions] = None,
|
|
850
|
-
num_parts: int =
|
|
851
|
+
num_parts: int = 1,
|
|
851
852
|
progress_callback: Optional[Callable] = None,
|
|
852
853
|
) -> DownloadResult:
|
|
853
854
|
"""
|
|
@@ -44,6 +44,7 @@ class EnvConfig:
|
|
|
44
44
|
"TIMEOUT": "6000.0",
|
|
45
45
|
"MAX_RETRIES": "3",
|
|
46
46
|
"RETRY_DELAY": "1.0",
|
|
47
|
+
"CIRCUIT_BREAKER_THRESHOLD": "5",
|
|
47
48
|
# Connection Pooling
|
|
48
49
|
"POOL_CONNECTIONS": "10",
|
|
49
50
|
"POOL_MAXSIZE": "20",
|
|
@@ -116,6 +117,7 @@ class EnvConfig:
|
|
|
116
117
|
"timeout": "DATAQUERY_TIMEOUT",
|
|
117
118
|
"max_retries": "DATAQUERY_MAX_RETRIES",
|
|
118
119
|
"retry_delay": "DATAQUERY_RETRY_DELAY",
|
|
120
|
+
"circuit_breaker_threshold": "DATAQUERY_CIRCUIT_BREAKER_THRESHOLD",
|
|
119
121
|
# Connection Pooling
|
|
120
122
|
"pool_connections": "DATAQUERY_POOL_CONNECTIONS",
|
|
121
123
|
"pool_maxsize": "DATAQUERY_POOL_MAXSIZE",
|
|
@@ -279,6 +281,7 @@ class EnvConfig:
|
|
|
279
281
|
timeout=cls.get_float("TIMEOUT"),
|
|
280
282
|
max_retries=cls.get_int("MAX_RETRIES"),
|
|
281
283
|
retry_delay=cls.get_float("RETRY_DELAY"),
|
|
284
|
+
circuit_breaker_threshold=cls.get_int("CIRCUIT_BREAKER_THRESHOLD"),
|
|
282
285
|
# Connection pooling
|
|
283
286
|
pool_connections=cls.get_int("POOL_CONNECTIONS"),
|
|
284
287
|
pool_maxsize=cls.get_int("POOL_MAXSIZE"),
|
|
@@ -7,8 +7,10 @@ for all API interactions, encapsulating the client and providing high-level oper
|
|
|
7
7
|
|
|
8
8
|
import asyncio
|
|
9
9
|
import time
|
|
10
|
+
from calendar import monthrange
|
|
11
|
+
from datetime import date, datetime
|
|
10
12
|
from pathlib import Path
|
|
11
|
-
from typing import Any, Callable, Dict, List, Optional, Union
|
|
13
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
12
14
|
|
|
13
15
|
import structlog
|
|
14
16
|
from dotenv import load_dotenv
|
|
@@ -317,7 +319,7 @@ class DataQuery:
|
|
|
317
319
|
file_datetime: Optional[str] = None,
|
|
318
320
|
destination_path: Optional[Path] = None,
|
|
319
321
|
options: Optional[DownloadOptions] = None,
|
|
320
|
-
num_parts: int =
|
|
322
|
+
num_parts: int = 1,
|
|
321
323
|
progress_callback: Optional[Callable] = None,
|
|
322
324
|
) -> DownloadResult:
|
|
323
325
|
"""
|
|
@@ -357,7 +359,7 @@ class DataQuery:
|
|
|
357
359
|
|
|
358
360
|
client = self._ensure_client()
|
|
359
361
|
# Pass parameters in correct order: file_group_id, file_datetime, options, num_parts, progress_callback
|
|
360
|
-
# Use default num_parts=
|
|
362
|
+
# Use default num_parts=1
|
|
361
363
|
return await client.download_file_async(file_group_id, file_datetime, options, num_parts, progress_callback)
|
|
362
364
|
|
|
363
365
|
async def list_available_files_async(
|
|
@@ -778,7 +780,7 @@ class DataQuery:
|
|
|
778
780
|
DownloadOptions(
|
|
779
781
|
destination_path=destination_path,
|
|
780
782
|
create_directories=True,
|
|
781
|
-
overwrite_existing=
|
|
783
|
+
overwrite_existing=self.client_config.overwrite_existing,
|
|
782
784
|
chunk_size=8192,
|
|
783
785
|
max_retries=3,
|
|
784
786
|
retry_delay=1.0,
|
|
@@ -824,7 +826,7 @@ class DataQuery:
|
|
|
824
826
|
end_date: str,
|
|
825
827
|
destination_dir: Path = Path("./downloads"),
|
|
826
828
|
max_concurrent: int = 25, # Full 25 TPS capacity
|
|
827
|
-
num_parts: int =
|
|
829
|
+
num_parts: int = 1,
|
|
828
830
|
progress_callback: Optional[Callable] = None,
|
|
829
831
|
delay_between_downloads: float = 0.04, # 25 TPS (1/25 = 0.04s)
|
|
830
832
|
max_retries: int = 3,
|
|
@@ -1020,7 +1022,7 @@ class DataQuery:
|
|
|
1020
1022
|
result
|
|
1021
1023
|
and hasattr(result, "status")
|
|
1022
1024
|
and hasattr(result, "file_group_id")
|
|
1023
|
-
and result.status.value
|
|
1025
|
+
and result.status.value in ("completed", "already_exists")
|
|
1024
1026
|
):
|
|
1025
1027
|
successful.append(result)
|
|
1026
1028
|
else:
|
|
@@ -1057,7 +1059,7 @@ class DataQuery:
|
|
|
1057
1059
|
result
|
|
1058
1060
|
and hasattr(result, "status")
|
|
1059
1061
|
and hasattr(result, "file_group_id")
|
|
1060
|
-
and result.status.value
|
|
1062
|
+
and result.status.value in ("completed", "already_exists")
|
|
1061
1063
|
):
|
|
1062
1064
|
successful.append(result)
|
|
1063
1065
|
logger.info(
|
|
@@ -1162,6 +1164,188 @@ class DataQuery:
|
|
|
1162
1164
|
)
|
|
1163
1165
|
raise
|
|
1164
1166
|
|
|
1167
|
+
@staticmethod
|
|
1168
|
+
def _split_into_monthly_ranges(from_date: str, to_date: str) -> List[Tuple[str, str]]:
|
|
1169
|
+
"""Split a date range into monthly chunks.
|
|
1170
|
+
|
|
1171
|
+
Args:
|
|
1172
|
+
from_date: Start date in YYYYMMDD format
|
|
1173
|
+
to_date: End date in YYYYMMDD format
|
|
1174
|
+
|
|
1175
|
+
Returns:
|
|
1176
|
+
List of (start_date, end_date) tuples, each covering at most one month.
|
|
1177
|
+
"""
|
|
1178
|
+
start = datetime.strptime(from_date, "%Y%m%d").date()
|
|
1179
|
+
end = datetime.strptime(to_date, "%Y%m%d").date()
|
|
1180
|
+
|
|
1181
|
+
ranges: List[Tuple[str, str]] = []
|
|
1182
|
+
current = start
|
|
1183
|
+
|
|
1184
|
+
while current <= end:
|
|
1185
|
+
last_day = monthrange(current.year, current.month)[1]
|
|
1186
|
+
month_end = date(current.year, current.month, last_day)
|
|
1187
|
+
chunk_end = min(month_end, end)
|
|
1188
|
+
ranges.append((current.strftime("%Y%m%d"), chunk_end.strftime("%Y%m%d")))
|
|
1189
|
+
|
|
1190
|
+
if current.month == 12:
|
|
1191
|
+
current = date(current.year + 1, 1, 1)
|
|
1192
|
+
else:
|
|
1193
|
+
current = date(current.year, current.month + 1, 1)
|
|
1194
|
+
|
|
1195
|
+
return ranges
|
|
1196
|
+
|
|
1197
|
+
async def download_historical_async(
|
|
1198
|
+
self,
|
|
1199
|
+
group_id: str,
|
|
1200
|
+
start_date: str,
|
|
1201
|
+
end_date: str,
|
|
1202
|
+
destination_dir: Path = Path("./downloads"),
|
|
1203
|
+
max_concurrent: int = 5,
|
|
1204
|
+
num_parts: int = 1,
|
|
1205
|
+
delay_between_downloads: float = 0.04,
|
|
1206
|
+
max_retries: int = 3,
|
|
1207
|
+
progress_callback: Optional[Callable] = None,
|
|
1208
|
+
chunk_delay: float = 2.0,
|
|
1209
|
+
) -> Dict[str, Any]:
|
|
1210
|
+
"""
|
|
1211
|
+
Download files for a group across a large date range by splitting into monthly chunks.
|
|
1212
|
+
|
|
1213
|
+
If the date range is within a single month it downloads directly.
|
|
1214
|
+
If the range spans multiple months it splits into monthly chunks and downloads
|
|
1215
|
+
each month sequentially, with a configurable delay between chunks to allow
|
|
1216
|
+
rate limits to recover.
|
|
1217
|
+
|
|
1218
|
+
Args:
|
|
1219
|
+
group_id: Group ID to download files from
|
|
1220
|
+
start_date: Start date in YYYYMMDD format (e.g. "20240101")
|
|
1221
|
+
end_date: End date in YYYYMMDD format (e.g. "20251231")
|
|
1222
|
+
destination_dir: Destination directory for downloads
|
|
1223
|
+
max_concurrent: Maximum concurrent file downloads per chunk (default: 5)
|
|
1224
|
+
num_parts: Number of parallel parts per file download (default: 1)
|
|
1225
|
+
delay_between_downloads: Delay in seconds between file downloads (default: 0.04 for 25 TPS)
|
|
1226
|
+
max_retries: Maximum retry attempts for failed downloads (default: 3)
|
|
1227
|
+
progress_callback: Optional progress callback for individual files
|
|
1228
|
+
chunk_delay: Delay in seconds between monthly chunks (default: 2.0)
|
|
1229
|
+
|
|
1230
|
+
Returns:
|
|
1231
|
+
Dictionary with aggregated download results and per-chunk details.
|
|
1232
|
+
"""
|
|
1233
|
+
operation_start = time.time()
|
|
1234
|
+
|
|
1235
|
+
monthly_ranges = self._split_into_monthly_ranges(start_date, end_date)
|
|
1236
|
+
|
|
1237
|
+
logger.info(
|
|
1238
|
+
"Starting historical download with monthly chunking",
|
|
1239
|
+
group_id=group_id,
|
|
1240
|
+
start_date=start_date,
|
|
1241
|
+
end_date=end_date,
|
|
1242
|
+
monthly_chunks=len(monthly_ranges),
|
|
1243
|
+
)
|
|
1244
|
+
|
|
1245
|
+
chunk_results: List[Dict[str, Any]] = []
|
|
1246
|
+
total_files = 0
|
|
1247
|
+
total_success = 0
|
|
1248
|
+
total_failed = 0
|
|
1249
|
+
|
|
1250
|
+
for i, (chunk_start, chunk_end) in enumerate(monthly_ranges):
|
|
1251
|
+
logger.info(
|
|
1252
|
+
f"Downloading chunk {i + 1}/{len(monthly_ranges)}",
|
|
1253
|
+
chunk_start=chunk_start,
|
|
1254
|
+
chunk_end=chunk_end,
|
|
1255
|
+
)
|
|
1256
|
+
|
|
1257
|
+
chunk_time = time.time()
|
|
1258
|
+
try:
|
|
1259
|
+
report = await self.run_group_download_async(
|
|
1260
|
+
group_id=group_id,
|
|
1261
|
+
start_date=chunk_start,
|
|
1262
|
+
end_date=chunk_end,
|
|
1263
|
+
destination_dir=destination_dir,
|
|
1264
|
+
max_concurrent=max_concurrent,
|
|
1265
|
+
num_parts=num_parts,
|
|
1266
|
+
delay_between_downloads=delay_between_downloads,
|
|
1267
|
+
max_retries=max_retries,
|
|
1268
|
+
progress_callback=progress_callback,
|
|
1269
|
+
)
|
|
1270
|
+
|
|
1271
|
+
chunk_elapsed = time.time() - chunk_time
|
|
1272
|
+
success = report.get("successful_downloads", 0)
|
|
1273
|
+
failed = report.get("failed_downloads", 0)
|
|
1274
|
+
files = report.get("total_files", 0)
|
|
1275
|
+
|
|
1276
|
+
total_files += files
|
|
1277
|
+
total_success += success
|
|
1278
|
+
total_failed += failed
|
|
1279
|
+
|
|
1280
|
+
chunk_results.append(
|
|
1281
|
+
{
|
|
1282
|
+
"chunk": i + 1,
|
|
1283
|
+
"start_date": chunk_start,
|
|
1284
|
+
"end_date": chunk_end,
|
|
1285
|
+
"total_files": files,
|
|
1286
|
+
"successful_downloads": success,
|
|
1287
|
+
"failed_downloads": failed,
|
|
1288
|
+
"elapsed_seconds": round(chunk_elapsed, 2),
|
|
1289
|
+
"report": report,
|
|
1290
|
+
}
|
|
1291
|
+
)
|
|
1292
|
+
|
|
1293
|
+
logger.info(
|
|
1294
|
+
f"Chunk {i + 1}/{len(monthly_ranges)} completed",
|
|
1295
|
+
success=success,
|
|
1296
|
+
failed=failed,
|
|
1297
|
+
total=files,
|
|
1298
|
+
elapsed=f"{chunk_elapsed:.1f}s",
|
|
1299
|
+
)
|
|
1300
|
+
|
|
1301
|
+
except Exception as e:
|
|
1302
|
+
chunk_elapsed = time.time() - chunk_time
|
|
1303
|
+
logger.error(
|
|
1304
|
+
f"Chunk {i + 1}/{len(monthly_ranges)} failed",
|
|
1305
|
+
chunk_start=chunk_start,
|
|
1306
|
+
chunk_end=chunk_end,
|
|
1307
|
+
error=str(e),
|
|
1308
|
+
)
|
|
1309
|
+
chunk_results.append(
|
|
1310
|
+
{
|
|
1311
|
+
"chunk": i + 1,
|
|
1312
|
+
"start_date": chunk_start,
|
|
1313
|
+
"end_date": chunk_end,
|
|
1314
|
+
"error": str(e),
|
|
1315
|
+
"elapsed_seconds": round(chunk_elapsed, 2),
|
|
1316
|
+
}
|
|
1317
|
+
)
|
|
1318
|
+
|
|
1319
|
+
# Delay between chunks to let rate limits recover
|
|
1320
|
+
if i < len(monthly_ranges) - 1:
|
|
1321
|
+
await asyncio.sleep(chunk_delay)
|
|
1322
|
+
|
|
1323
|
+
total_elapsed = time.time() - operation_start
|
|
1324
|
+
total_minutes = total_elapsed / 60.0
|
|
1325
|
+
|
|
1326
|
+
chunks_with_errors = [f"{c['start_date']}-{c['end_date']}" for c in chunk_results if "error" in c]
|
|
1327
|
+
|
|
1328
|
+
summary = {
|
|
1329
|
+
"group_id": group_id,
|
|
1330
|
+
"start_date": start_date,
|
|
1331
|
+
"end_date": end_date,
|
|
1332
|
+
"monthly_chunks": len(monthly_ranges),
|
|
1333
|
+
"total_files": total_files,
|
|
1334
|
+
"successful_downloads": total_success,
|
|
1335
|
+
"failed_downloads": total_failed,
|
|
1336
|
+
"success_rate": (total_success / total_files * 100) if total_files else 0.0,
|
|
1337
|
+
"total_time_seconds": round(total_elapsed, 2),
|
|
1338
|
+
"total_time_minutes": round(total_minutes, 2),
|
|
1339
|
+
"total_time_formatted": (
|
|
1340
|
+
f"{int(total_minutes)}m {int(total_elapsed % 60)}s" if total_minutes >= 1 else f"{total_elapsed:.1f}s"
|
|
1341
|
+
),
|
|
1342
|
+
"chunks_with_errors": chunks_with_errors,
|
|
1343
|
+
"chunk_results": chunk_results,
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
logger.info("Historical download completed", **{k: v for k, v in summary.items() if k != "chunk_results"})
|
|
1347
|
+
return summary
|
|
1348
|
+
|
|
1165
1349
|
async def _download_file_parallel(
|
|
1166
1350
|
self,
|
|
1167
1351
|
file_group_id: str,
|
|
@@ -1207,14 +1391,17 @@ class DataQuery:
|
|
|
1207
1391
|
validate_file_datetime(file_datetime)
|
|
1208
1392
|
|
|
1209
1393
|
if num_parts is None or num_parts <= 0:
|
|
1210
|
-
num_parts =
|
|
1394
|
+
num_parts = 1
|
|
1211
1395
|
|
|
1212
1396
|
# Check if range downloads are disabled - use single stream instead
|
|
1213
1397
|
if not client.config.enable_range_downloads:
|
|
1214
1398
|
return await client.download_file_async(
|
|
1215
1399
|
file_group_id=file_group_id,
|
|
1216
1400
|
file_datetime=file_datetime,
|
|
1217
|
-
options=DownloadOptions(
|
|
1401
|
+
options=DownloadOptions(
|
|
1402
|
+
destination_path=destination_path,
|
|
1403
|
+
overwrite_existing=client.config.overwrite_existing,
|
|
1404
|
+
),
|
|
1218
1405
|
progress_callback=progress_callback,
|
|
1219
1406
|
)
|
|
1220
1407
|
|
|
@@ -1224,7 +1411,10 @@ class DataQuery:
|
|
|
1224
1411
|
params["file-datetime"] = file_datetime
|
|
1225
1412
|
|
|
1226
1413
|
# Determine destination directory
|
|
1227
|
-
download_options = DownloadOptions(
|
|
1414
|
+
download_options = DownloadOptions(
|
|
1415
|
+
destination_path=destination_path,
|
|
1416
|
+
overwrite_existing=client.config.overwrite_existing,
|
|
1417
|
+
)
|
|
1228
1418
|
if download_options.destination_path:
|
|
1229
1419
|
resolved_path = Path(download_options.destination_path)
|
|
1230
1420
|
if resolved_path.suffix:
|
|
@@ -1912,7 +2102,7 @@ class DataQuery:
|
|
|
1912
2102
|
file_datetime: Optional[str] = None,
|
|
1913
2103
|
destination_path: Optional[Path] = None,
|
|
1914
2104
|
options: Optional[DownloadOptions] = None,
|
|
1915
|
-
num_parts: int =
|
|
2105
|
+
num_parts: int = 1,
|
|
1916
2106
|
progress_callback: Optional[Callable] = None,
|
|
1917
2107
|
) -> DownloadResult:
|
|
1918
2108
|
"""
|
|
@@ -2223,7 +2413,7 @@ class DataQuery:
|
|
|
2223
2413
|
end_date: str,
|
|
2224
2414
|
destination_dir: Path = Path("./downloads"),
|
|
2225
2415
|
max_concurrent: int = 5,
|
|
2226
|
-
num_parts: int =
|
|
2416
|
+
num_parts: int = 1,
|
|
2227
2417
|
progress_callback: Optional[Callable] = None,
|
|
2228
2418
|
delay_between_downloads: float = 1.0,
|
|
2229
2419
|
) -> dict:
|
|
@@ -2241,6 +2431,35 @@ class DataQuery:
|
|
|
2241
2431
|
)
|
|
2242
2432
|
)
|
|
2243
2433
|
|
|
2434
|
+
def download_historical(
|
|
2435
|
+
self,
|
|
2436
|
+
group_id: str,
|
|
2437
|
+
start_date: str,
|
|
2438
|
+
end_date: str,
|
|
2439
|
+
destination_dir: Path = Path("./downloads"),
|
|
2440
|
+
max_concurrent: int = 5,
|
|
2441
|
+
num_parts: int = 1,
|
|
2442
|
+
delay_between_downloads: float = 0.04,
|
|
2443
|
+
max_retries: int = 3,
|
|
2444
|
+
progress_callback: Optional[Callable] = None,
|
|
2445
|
+
chunk_delay: float = 2.0,
|
|
2446
|
+
) -> Dict[str, Any]:
|
|
2447
|
+
"""Synchronous wrapper for download_historical_async."""
|
|
2448
|
+
return self._run_sync(
|
|
2449
|
+
self.download_historical_async(
|
|
2450
|
+
group_id=group_id,
|
|
2451
|
+
start_date=start_date,
|
|
2452
|
+
end_date=end_date,
|
|
2453
|
+
destination_dir=destination_dir,
|
|
2454
|
+
max_concurrent=max_concurrent,
|
|
2455
|
+
num_parts=num_parts,
|
|
2456
|
+
delay_between_downloads=delay_between_downloads,
|
|
2457
|
+
max_retries=max_retries,
|
|
2458
|
+
progress_callback=progress_callback,
|
|
2459
|
+
chunk_delay=chunk_delay,
|
|
2460
|
+
)
|
|
2461
|
+
)
|
|
2462
|
+
|
|
2244
2463
|
def cleanup(self):
|
|
2245
2464
|
"""Synchronous cleanup resources and ensure proper shutdown."""
|
|
2246
2465
|
if self._client:
|
|
@@ -2287,7 +2506,7 @@ class DataQuery:
|
|
|
2287
2506
|
file_datetime: Optional[str] = None,
|
|
2288
2507
|
destination_path: Optional[Path] = None,
|
|
2289
2508
|
options: Optional[DownloadOptions] = None,
|
|
2290
|
-
num_parts: int =
|
|
2509
|
+
num_parts: int = 1,
|
|
2291
2510
|
progress_callback: Optional[Callable] = None,
|
|
2292
2511
|
) -> DownloadResult:
|
|
2293
2512
|
"""Synchronous wrapper for download_file with _sync suffix."""
|
|
@@ -74,6 +74,7 @@ class ClientConfig(BaseModel):
|
|
|
74
74
|
timeout: float = Field(default=600.0, description="Default request timeout in seconds")
|
|
75
75
|
max_retries: int = Field(default=3, description="Maximum retry attempts")
|
|
76
76
|
retry_delay: float = Field(default=1.0, description="Delay between retries in seconds")
|
|
77
|
+
circuit_breaker_threshold: int = Field(default=5, description="Number of failures before circuit breaker opens")
|
|
77
78
|
|
|
78
79
|
# Connection pooling
|
|
79
80
|
pool_connections: int = Field(default=10, description="Number of connection pools")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dataquery-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Python SDK for DATAQUERY Data API - Query, download, and check availability of economic data files
|
|
5
5
|
Author-email: DATAQUERY SDK Team <support@dataquery.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/dataquery/dataquery-sdk
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|