@@ -401,14 +401,7 @@ def _make_status_error(
401
401
) -> _exceptions .APIStatusError :
402
402
raise NotImplementedError ()
403
403
404
- def _remaining_retries (
405
- self ,
406
- remaining_retries : Optional [int ],
407
- options : FinalRequestOptions ,
408
- ) -> int :
409
- return remaining_retries if remaining_retries is not None else options .get_max_retries (self .max_retries )
410
-
411
- def _build_headers (self , options : FinalRequestOptions ) -> httpx .Headers :
404
+ def _build_headers (self , options : FinalRequestOptions , * , retries_taken : int = 0 ) -> httpx .Headers :
412
405
custom_headers = options .headers or {}
413
406
headers_dict = _merge_mappings (self .default_headers , custom_headers )
414
407
self ._validate_headers (headers_dict , custom_headers )
@@ -420,6 +413,8 @@ def _build_headers(self, options: FinalRequestOptions) -> httpx.Headers:
420
413
if idempotency_header and options .method .lower () != "get" and idempotency_header not in headers :
421
414
headers [idempotency_header ] = options .idempotency_key or self ._idempotency_key ()
422
415
416
+ headers .setdefault ("x-stainless-retry-count" , str (retries_taken ))
417
+
423
418
return headers
424
419
425
420
def _prepare_url (self , url : str ) -> URL :
@@ -441,6 +436,8 @@ def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder:
441
436
def _build_request (
442
437
self ,
443
438
options : FinalRequestOptions ,
439
+ * ,
440
+ retries_taken : int = 0 ,
444
441
) -> httpx .Request :
445
442
if log .isEnabledFor (logging .DEBUG ):
446
443
log .debug ("Request options: %s" , model_dump (options , exclude_unset = True ))
@@ -456,7 +453,7 @@ def _build_request(
456
453
else :
457
454
raise RuntimeError (f"Unexpected JSON data type, { type (json_data )} , cannot merge with `extra_body`" )
458
455
459
- headers = self ._build_headers (options )
456
+ headers = self ._build_headers (options , retries_taken = retries_taken )
460
457
params = _merge_mappings (self .default_query , options .params )
461
458
content_type = headers .get ("Content-Type" )
462
459
files = options .files
@@ -939,20 +936,25 @@ def request(
939
936
stream : bool = False ,
940
937
stream_cls : type [_StreamT ] | None = None ,
941
938
) -> ResponseT | _StreamT :
939
+ if remaining_retries is not None :
940
+ retries_taken = options .get_max_retries (self .max_retries ) - remaining_retries
941
+ else :
942
+ retries_taken = 0
943
+
942
944
return self ._request (
943
945
cast_to = cast_to ,
944
946
options = options ,
945
947
stream = stream ,
946
948
stream_cls = stream_cls ,
947
- remaining_retries = remaining_retries ,
949
+ retries_taken = retries_taken ,
948
950
)
949
951
950
952
def _request (
951
953
self ,
952
954
* ,
953
955
cast_to : Type [ResponseT ],
954
956
options : FinalRequestOptions ,
955
- remaining_retries : int | None ,
957
+ retries_taken : int ,
956
958
stream : bool ,
957
959
stream_cls : type [_StreamT ] | None ,
958
960
) -> ResponseT | _StreamT :
@@ -964,8 +966,8 @@ def _request(
964
966
cast_to = self ._maybe_override_cast_to (cast_to , options )
965
967
options = self ._prepare_options (options )
966
968
967
- retries = self . _remaining_retries ( remaining_retries , options )
968
- request = self ._build_request (options )
969
+ remaining_retries = options . get_max_retries ( self . max_retries ) - retries_taken
970
+ request = self ._build_request (options , retries_taken = retries_taken )
969
971
self ._prepare_request (request )
970
972
971
973
kwargs : HttpxSendArgs = {}
@@ -983,11 +985,11 @@ def _request(
983
985
except httpx .TimeoutException as err :
984
986
log .debug ("Encountered httpx.TimeoutException" , exc_info = True )
985
987
986
- if retries > 0 :
988
+ if remaining_retries > 0 :
987
989
return self ._retry_request (
988
990
input_options ,
989
991
cast_to ,
990
- retries ,
992
+ retries_taken = retries_taken ,
991
993
stream = stream ,
992
994
stream_cls = stream_cls ,
993
995
response_headers = None ,
@@ -998,11 +1000,11 @@ def _request(
998
1000
except Exception as err :
999
1001
log .debug ("Encountered Exception" , exc_info = True )
1000
1002
1001
- if retries > 0 :
1003
+ if remaining_retries > 0 :
1002
1004
return self ._retry_request (
1003
1005
input_options ,
1004
1006
cast_to ,
1005
- retries ,
1007
+ retries_taken = retries_taken ,
1006
1008
stream = stream ,
1007
1009
stream_cls = stream_cls ,
1008
1010
response_headers = None ,
@@ -1025,13 +1027,13 @@ def _request(
1025
1027
except httpx .HTTPStatusError as err : # thrown on 4xx and 5xx status code
1026
1028
log .debug ("Encountered httpx.HTTPStatusError" , exc_info = True )
1027
1029
1028
- if retries > 0 and self ._should_retry (err .response ):
1030
+ if remaining_retries > 0 and self ._should_retry (err .response ):
1029
1031
err .response .close ()
1030
1032
return self ._retry_request (
1031
1033
input_options ,
1032
1034
cast_to ,
1033
- retries ,
1034
- err .response .headers ,
1035
+ retries_taken = retries_taken ,
1036
+ response_headers = err .response .headers ,
1035
1037
stream = stream ,
1036
1038
stream_cls = stream_cls ,
1037
1039
)
@@ -1050,26 +1052,26 @@ def _request(
1050
1052
response = response ,
1051
1053
stream = stream ,
1052
1054
stream_cls = stream_cls ,
1053
- retries_taken = options . get_max_retries ( self . max_retries ) - retries ,
1055
+ retries_taken = retries_taken ,
1054
1056
)
1055
1057
1056
1058
def _retry_request (
1057
1059
self ,
1058
1060
options : FinalRequestOptions ,
1059
1061
cast_to : Type [ResponseT ],
1060
- remaining_retries : int ,
1061
- response_headers : httpx .Headers | None ,
1062
1062
* ,
1063
+ retries_taken : int ,
1064
+ response_headers : httpx .Headers | None ,
1063
1065
stream : bool ,
1064
1066
stream_cls : type [_StreamT ] | None ,
1065
1067
) -> ResponseT | _StreamT :
1066
- remaining = remaining_retries - 1
1067
- if remaining == 1 :
1068
+ remaining_retries = options . get_max_retries ( self . max_retries ) - retries_taken
1069
+ if remaining_retries == 1 :
1068
1070
log .debug ("1 retry left" )
1069
1071
else :
1070
- log .debug ("%i retries left" , remaining )
1072
+ log .debug ("%i retries left" , remaining_retries )
1071
1073
1072
- timeout = self ._calculate_retry_timeout (remaining , options , response_headers )
1074
+ timeout = self ._calculate_retry_timeout (remaining_retries , options , response_headers )
1073
1075
log .info ("Retrying request to %s in %f seconds" , options .url , timeout )
1074
1076
1075
1077
# In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a
@@ -1079,7 +1081,7 @@ def _retry_request(
1079
1081
return self ._request (
1080
1082
options = options ,
1081
1083
cast_to = cast_to ,
1082
- remaining_retries = remaining ,
1084
+ retries_taken = retries_taken + 1 ,
1083
1085
stream = stream ,
1084
1086
stream_cls = stream_cls ,
1085
1087
)
@@ -1511,12 +1513,17 @@ async def request(
1511
1513
stream_cls : type [_AsyncStreamT ] | None = None ,
1512
1514
remaining_retries : Optional [int ] = None ,
1513
1515
) -> ResponseT | _AsyncStreamT :
1516
+ if remaining_retries is not None :
1517
+ retries_taken = options .get_max_retries (self .max_retries ) - remaining_retries
1518
+ else :
1519
+ retries_taken = 0
1520
+
1514
1521
return await self ._request (
1515
1522
cast_to = cast_to ,
1516
1523
options = options ,
1517
1524
stream = stream ,
1518
1525
stream_cls = stream_cls ,
1519
- remaining_retries = remaining_retries ,
1526
+ retries_taken = retries_taken ,
1520
1527
)
1521
1528
1522
1529
async def _request (
@@ -1526,7 +1533,7 @@ async def _request(
1526
1533
* ,
1527
1534
stream : bool ,
1528
1535
stream_cls : type [_AsyncStreamT ] | None ,
1529
- remaining_retries : int | None ,
1536
+ retries_taken : int ,
1530
1537
) -> ResponseT | _AsyncStreamT :
1531
1538
if self ._platform is None :
1532
1539
# `get_platform` can make blocking IO calls so we
@@ -1541,8 +1548,8 @@ async def _request(
1541
1548
cast_to = self ._maybe_override_cast_to (cast_to , options )
1542
1549
options = await self ._prepare_options (options )
1543
1550
1544
- retries = self . _remaining_retries ( remaining_retries , options )
1545
- request = self ._build_request (options )
1551
+ remaining_retries = options . get_max_retries ( self . max_retries ) - retries_taken
1552
+ request = self ._build_request (options , retries_taken = retries_taken )
1546
1553
await self ._prepare_request (request )
1547
1554
1548
1555
kwargs : HttpxSendArgs = {}
@@ -1558,11 +1565,11 @@ async def _request(
1558
1565
except httpx .TimeoutException as err :
1559
1566
log .debug ("Encountered httpx.TimeoutException" , exc_info = True )
1560
1567
1561
- if retries > 0 :
1568
+ if remaining_retries > 0 :
1562
1569
return await self ._retry_request (
1563
1570
input_options ,
1564
1571
cast_to ,
1565
- retries ,
1572
+ retries_taken = retries_taken ,
1566
1573
stream = stream ,
1567
1574
stream_cls = stream_cls ,
1568
1575
response_headers = None ,
@@ -1573,11 +1580,11 @@ async def _request(
1573
1580
except Exception as err :
1574
1581
log .debug ("Encountered Exception" , exc_info = True )
1575
1582
1576
- if retries > 0 :
1583
+ if retries_taken > 0 :
1577
1584
return await self ._retry_request (
1578
1585
input_options ,
1579
1586
cast_to ,
1580
- retries ,
1587
+ retries_taken = retries_taken ,
1581
1588
stream = stream ,
1582
1589
stream_cls = stream_cls ,
1583
1590
response_headers = None ,
@@ -1595,13 +1602,13 @@ async def _request(
1595
1602
except httpx .HTTPStatusError as err : # thrown on 4xx and 5xx status code
1596
1603
log .debug ("Encountered httpx.HTTPStatusError" , exc_info = True )
1597
1604
1598
- if retries > 0 and self ._should_retry (err .response ):
1605
+ if remaining_retries > 0 and self ._should_retry (err .response ):
1599
1606
await err .response .aclose ()
1600
1607
return await self ._retry_request (
1601
1608
input_options ,
1602
1609
cast_to ,
1603
- retries ,
1604
- err .response .headers ,
1610
+ retries_taken = retries_taken ,
1611
+ response_headers = err .response .headers ,
1605
1612
stream = stream ,
1606
1613
stream_cls = stream_cls ,
1607
1614
)
@@ -1620,34 +1627,34 @@ async def _request(
1620
1627
response = response ,
1621
1628
stream = stream ,
1622
1629
stream_cls = stream_cls ,
1623
- retries_taken = options . get_max_retries ( self . max_retries ) - retries ,
1630
+ retries_taken = retries_taken ,
1624
1631
)
1625
1632
1626
1633
async def _retry_request (
1627
1634
self ,
1628
1635
options : FinalRequestOptions ,
1629
1636
cast_to : Type [ResponseT ],
1630
- remaining_retries : int ,
1631
- response_headers : httpx .Headers | None ,
1632
1637
* ,
1638
+ retries_taken : int ,
1639
+ response_headers : httpx .Headers | None ,
1633
1640
stream : bool ,
1634
1641
stream_cls : type [_AsyncStreamT ] | None ,
1635
1642
) -> ResponseT | _AsyncStreamT :
1636
- remaining = remaining_retries - 1
1637
- if remaining == 1 :
1643
+ remaining_retries = options . get_max_retries ( self . max_retries ) - retries_taken
1644
+ if remaining_retries == 1 :
1638
1645
log .debug ("1 retry left" )
1639
1646
else :
1640
- log .debug ("%i retries left" , remaining )
1647
+ log .debug ("%i retries left" , remaining_retries )
1641
1648
1642
- timeout = self ._calculate_retry_timeout (remaining , options , response_headers )
1649
+ timeout = self ._calculate_retry_timeout (remaining_retries , options , response_headers )
1643
1650
log .info ("Retrying request to %s in %f seconds" , options .url , timeout )
1644
1651
1645
1652
await anyio .sleep (timeout )
1646
1653
1647
1654
return await self ._request (
1648
1655
options = options ,
1649
1656
cast_to = cast_to ,
1650
- remaining_retries = remaining ,
1657
+ retries_taken = retries_taken + 1 ,
1651
1658
stream = stream ,
1652
1659
stream_cls = stream_cls ,
1653
1660
)
0 commit comments