@@ -1263,47 +1263,84 @@ All code examples in this plan follow these guidelines and must be maintained th
1263
1263
1264
1264
import logging
1265
1265
import time
1266
- import typing as t
1266
+ from typing import Any, Dict, Optional, Union, Tuple, List, TypeVar, cast
1267
1267
from urllib.parse import urlparse
1268
+ import dataclasses
1268
1269
1269
1270
import requests
1270
1271
from requests.exceptions import ConnectionError, Timeout
1271
1272
1272
- from vcspull.exc import NetworkError
1273
+ from vcspull.exc import NetworkError, ErrorCode
1273
1274
1274
1275
log = logging.getLogger(__name__)
1275
1276
1276
1277
1278
+ @dataclasses.dataclass
1277
1279
class RetryStrategy:
1278
1280
"""Strategy for retrying network operations."""
1279
1281
1280
- def __init__(self, max_retries=3, initial_delay=1.0, backoff_factor=2.0):
1281
- self.max_retries = max_retries
1282
- self.initial_delay = initial_delay
1283
- self.backoff_factor = backoff_factor
1282
+ max_retries: int = 3
1283
+ initial_delay: float = 1.0
1284
+ backoff_factor: float = 2.0
1285
+
1286
+ def get_delay(self, attempt: int) -> float:
1287
+ """
1288
+ Get delay for a specific retry attempt.
1284
1289
1285
- def get_delay(self, attempt):
1286
- """Get delay for a specific retry attempt."""
1290
+ Parameters
1291
+ ----------
1292
+ attempt : int
1293
+ Current attempt number (1-based)
1294
+
1295
+ Returns
1296
+ -------
1297
+ float
1298
+ Delay in seconds
1299
+ """
1287
1300
return self.initial_delay * (self.backoff_factor ** (attempt - 1))
1288
1301
1289
1302
1303
+ ResponseType = TypeVar('ResponseType')
1304
+
1305
+
1290
1306
class NetworkManager:
1291
1307
"""Manager for network operations."""
1292
1308
1293
- def __init__(self, session=None, retry_strategy=None):
1309
+ def __init__(
1310
+ self,
1311
+ *,
1312
+ session: Optional[requests.Session] = None,
1313
+ retry_strategy: Optional[RetryStrategy] = None
1314
+ ) -> None:
1315
+ """
1316
+ Initialize network manager.
1317
+
1318
+ Parameters
1319
+ ----------
1320
+ session : requests.Session, optional
1321
+ Session to use for requests
1322
+ retry_strategy : RetryStrategy, optional
1323
+ Strategy for retrying failed requests
1324
+ """
1294
1325
self.session = session or requests.Session()
1295
1326
self.retry_strategy = retry_strategy or RetryStrategy()
1296
1327
1297
- def request(self, method, url, **kwargs):
1298
- """Perform HTTP request with retry logic.
1328
+ def request(
1329
+ self,
1330
+ method: str,
1331
+ url: str,
1332
+ **kwargs: Any
1333
+ ) -> requests.Response:
1334
+ """
1335
+ Perform HTTP request with retry logic.
1299
1336
1300
1337
Parameters
1301
1338
----------
1302
1339
method : str
1303
1340
HTTP method (GET, POST, etc.)
1304
1341
url : str
1305
1342
URL to request
1306
- **kwargs
1343
+ **kwargs : Any
1307
1344
Additional parameters for requests
1308
1345
1309
1346
Returns
@@ -1324,7 +1361,7 @@ All code examples in this plan follow these guidelines and must be maintained th
1324
1361
1325
1362
# Initialize retry counter
1326
1363
attempt = 0
1327
- last_exception = None
1364
+ last_exception: Optional[NetworkError] = None
1328
1365
1329
1366
while attempt < max_retries:
1330
1367
attempt += 1
@@ -1340,7 +1377,8 @@ All code examples in this plan follow these guidelines and must be maintained th
1340
1377
f"Server error: {response.status_code}",
1341
1378
url=url,
1342
1379
status_code=response.status_code,
1343
- retry_count=attempt
1380
+ retry_count=attempt,
1381
+ error_code=ErrorCode.NETWORK_UNREACHABLE
1344
1382
)
1345
1383
continue
1346
1384
elif response.status_code == 429:
@@ -1349,7 +1387,8 @@ All code examples in this plan follow these guidelines and must be maintained th
1349
1387
"Rate limited",
1350
1388
url=url,
1351
1389
status_code=429,
1352
- retry_count=attempt
1390
+ retry_count=attempt,
1391
+ error_code=ErrorCode.RATE_LIMITED
1353
1392
)
1354
1393
# Get retry-after header if available
1355
1394
retry_after = response.headers.get('Retry-After')
@@ -1368,7 +1407,8 @@ All code examples in this plan follow these guidelines and must be maintained th
1368
1407
raise NetworkError(
1369
1408
f"Client error: {response.status_code}",
1370
1409
url=url,
1371
- status_code=response.status_code
1410
+ status_code=response.status_code,
1411
+ error_code=ErrorCode.NETWORK_UNREACHABLE
1372
1412
)
1373
1413
1374
1414
# Success
@@ -1380,7 +1420,11 @@ All code examples in this plan follow these guidelines and must be maintained th
1380
1420
last_exception = NetworkError(
1381
1421
f"Network error: {str(e)}",
1382
1422
url=url,
1383
- retry_count=attempt
1423
+ retry_count=attempt,
1424
+ error_code=(
1425
+ ErrorCode.TIMEOUT if isinstance(e, Timeout)
1426
+ else ErrorCode.CONNECTION_REFUSED
1427
+ )
1384
1428
)
1385
1429
1386
1430
# Wait before retrying
@@ -1393,19 +1437,83 @@ All code examples in this plan follow these guidelines and must be maintained th
1393
1437
if last_exception:
1394
1438
raise last_exception
1395
1439
else:
1396
- raise NetworkError(f"Failed after {max_retries} attempts", url=url)
1440
+ raise NetworkError(
1441
+ f"Failed after {max_retries} attempts",
1442
+ url=url,
1443
+ error_code=ErrorCode.NETWORK_UNREACHABLE
1444
+ )
1445
+
1446
+ def get(
1447
+ self,
1448
+ url: str,
1449
+ **kwargs: Any
1450
+ ) -> requests.Response:
1451
+ """
1452
+ Perform HTTP GET request.
1453
+
1454
+ Parameters
1455
+ ----------
1456
+ url : str
1457
+ URL to request
1458
+ **kwargs : Any
1459
+ Additional parameters for requests
1397
1460
1398
- def get(self, url, **kwargs):
1399
- """Perform HTTP GET request."""
1461
+ Returns
1462
+ -------
1463
+ requests.Response
1464
+ Response object
1465
+ """
1400
1466
return self.request('GET', url, **kwargs)
1401
1467
1402
- def post(self, url, **kwargs):
1403
- """Perform HTTP POST request."""
1468
+ def post(
1469
+ self,
1470
+ url: str,
1471
+ **kwargs: Any
1472
+ ) -> requests.Response:
1473
+ """
1474
+ Perform HTTP POST request.
1475
+
1476
+ Parameters
1477
+ ----------
1478
+ url : str
1479
+ URL to request
1480
+ **kwargs : Any
1481
+ Additional parameters for requests
1482
+
1483
+ Returns
1484
+ -------
1485
+ requests.Response
1486
+ Response object
1487
+ """
1404
1488
return self.request('POST', url, **kwargs)
1405
1489
1406
1490
1407
- def perform_request(url, auth=None, retry_strategy=None, **kwargs):
1408
- """Perform HTTP request with configurable retry strategy."""
1491
+ def perform_request(
1492
+ url: str,
1493
+ *,
1494
+ auth: Optional[Tuple[str, str]] = None,
1495
+ retry_strategy: Optional[RetryStrategy] = None,
1496
+ **kwargs: Any
1497
+ ) -> requests.Response:
1498
+ """
1499
+ Perform HTTP request with configurable retry strategy.
1500
+
1501
+ Parameters
1502
+ ----------
1503
+ url : str
1504
+ URL to request
1505
+ auth : Tuple[str, str], optional
1506
+ Authentication credentials (username, password)
1507
+ retry_strategy : RetryStrategy, optional
1508
+ Strategy for retrying failed requests
1509
+ **kwargs : Any
1510
+ Additional parameters for requests
1511
+
1512
+ Returns
1513
+ -------
1514
+ requests.Response
1515
+ Response object
1516
+ """
1409
1517
manager = NetworkManager(retry_strategy=retry_strategy)
1410
1518
return manager.get(url, auth=auth, **kwargs)
1411
1519
```
0 commit comments