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