@@ -401,14 +401,7 @@ def _make_status_error(
401401 ) -> _exceptions .APIStatusError :
402402 raise NotImplementedError ()
403403
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 :
412405 custom_headers = options .headers or {}
413406 headers_dict = _merge_mappings (self .default_headers , custom_headers )
414407 self ._validate_headers (headers_dict , custom_headers )
@@ -420,6 +413,8 @@ def _build_headers(self, options: FinalRequestOptions) -> httpx.Headers:
420413 if idempotency_header and options .method .lower () != "get" and idempotency_header not in headers :
421414 headers [idempotency_header ] = options .idempotency_key or self ._idempotency_key ()
422415
416+ headers .setdefault ("x-stainless-retry-count" , str (retries_taken ))
417+
423418 return headers
424419
425420 def _prepare_url (self , url : str ) -> URL :
@@ -441,6 +436,8 @@ def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder:
441436 def _build_request (
442437 self ,
443438 options : FinalRequestOptions ,
439+ * ,
440+ retries_taken : int = 0 ,
444441 ) -> httpx .Request :
445442 if log .isEnabledFor (logging .DEBUG ):
446443 log .debug ("Request options: %s" , model_dump (options , exclude_unset = True ))
@@ -456,7 +453,7 @@ def _build_request(
456453 else :
457454 raise RuntimeError (f"Unexpected JSON data type, { type (json_data )} , cannot merge with `extra_body`" )
458455
459- headers = self ._build_headers (options )
456+ headers = self ._build_headers (options , retries_taken = retries_taken )
460457 params = _merge_mappings (self .default_query , options .params )
461458 content_type = headers .get ("Content-Type" )
462459 files = options .files
@@ -939,20 +936,25 @@ def request(
939936 stream : bool = False ,
940937 stream_cls : type [_StreamT ] | None = None ,
941938 ) -> 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+
942944 return self ._request (
943945 cast_to = cast_to ,
944946 options = options ,
945947 stream = stream ,
946948 stream_cls = stream_cls ,
947- remaining_retries = remaining_retries ,
949+ retries_taken = retries_taken ,
948950 )
949951
950952 def _request (
951953 self ,
952954 * ,
953955 cast_to : Type [ResponseT ],
954956 options : FinalRequestOptions ,
955- remaining_retries : int | None ,
957+ retries_taken : int ,
956958 stream : bool ,
957959 stream_cls : type [_StreamT ] | None ,
958960 ) -> ResponseT | _StreamT :
@@ -964,8 +966,8 @@ def _request(
964966 cast_to = self ._maybe_override_cast_to (cast_to , options )
965967 options = self ._prepare_options (options )
966968
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 )
969971 self ._prepare_request (request )
970972
971973 kwargs : HttpxSendArgs = {}
@@ -983,11 +985,11 @@ def _request(
983985 except httpx .TimeoutException as err :
984986 log .debug ("Encountered httpx.TimeoutException" , exc_info = True )
985987
986- if retries > 0 :
988+ if remaining_retries > 0 :
987989 return self ._retry_request (
988990 input_options ,
989991 cast_to ,
990- retries ,
992+ retries_taken = retries_taken ,
991993 stream = stream ,
992994 stream_cls = stream_cls ,
993995 response_headers = None ,
@@ -998,11 +1000,11 @@ def _request(
9981000 except Exception as err :
9991001 log .debug ("Encountered Exception" , exc_info = True )
10001002
1001- if retries > 0 :
1003+ if remaining_retries > 0 :
10021004 return self ._retry_request (
10031005 input_options ,
10041006 cast_to ,
1005- retries ,
1007+ retries_taken = retries_taken ,
10061008 stream = stream ,
10071009 stream_cls = stream_cls ,
10081010 response_headers = None ,
@@ -1026,13 +1028,13 @@ def _request(
10261028 except httpx .HTTPStatusError as err : # thrown on 4xx and 5xx status code
10271029 log .debug ("Encountered httpx.HTTPStatusError" , exc_info = True )
10281030
1029- if retries > 0 and self ._should_retry (err .response ):
1031+ if remaining_retries > 0 and self ._should_retry (err .response ):
10301032 err .response .close ()
10311033 return self ._retry_request (
10321034 input_options ,
10331035 cast_to ,
1034- retries ,
1035- err .response .headers ,
1036+ retries_taken = retries_taken ,
1037+ response_headers = err .response .headers ,
10361038 stream = stream ,
10371039 stream_cls = stream_cls ,
10381040 )
@@ -1051,26 +1053,26 @@ def _request(
10511053 response = response ,
10521054 stream = stream ,
10531055 stream_cls = stream_cls ,
1054- retries_taken = options . get_max_retries ( self . max_retries ) - retries ,
1056+ retries_taken = retries_taken ,
10551057 )
10561058
10571059 def _retry_request (
10581060 self ,
10591061 options : FinalRequestOptions ,
10601062 cast_to : Type [ResponseT ],
1061- remaining_retries : int ,
1062- response_headers : httpx .Headers | None ,
10631063 * ,
1064+ retries_taken : int ,
1065+ response_headers : httpx .Headers | None ,
10641066 stream : bool ,
10651067 stream_cls : type [_StreamT ] | None ,
10661068 ) -> ResponseT | _StreamT :
1067- remaining = remaining_retries - 1
1068- if remaining == 1 :
1069+ remaining_retries = options . get_max_retries ( self . max_retries ) - retries_taken
1070+ if remaining_retries == 1 :
10691071 log .debug ("1 retry left" )
10701072 else :
1071- log .debug ("%i retries left" , remaining )
1073+ log .debug ("%i retries left" , remaining_retries )
10721074
1073- timeout = self ._calculate_retry_timeout (remaining , options , response_headers )
1075+ timeout = self ._calculate_retry_timeout (remaining_retries , options , response_headers )
10741076 log .info ("Retrying request to %s in %f seconds" , options .url , timeout )
10751077
10761078 # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a
@@ -1080,7 +1082,7 @@ def _retry_request(
10801082 return self ._request (
10811083 options = options ,
10821084 cast_to = cast_to ,
1083- remaining_retries = remaining ,
1085+ retries_taken = retries_taken + 1 ,
10841086 stream = stream ,
10851087 stream_cls = stream_cls ,
10861088 )
@@ -1512,12 +1514,17 @@ async def request(
15121514 stream_cls : type [_AsyncStreamT ] | None = None ,
15131515 remaining_retries : Optional [int ] = None ,
15141516 ) -> ResponseT | _AsyncStreamT :
1517+ if remaining_retries is not None :
1518+ retries_taken = options .get_max_retries (self .max_retries ) - remaining_retries
1519+ else :
1520+ retries_taken = 0
1521+
15151522 return await self ._request (
15161523 cast_to = cast_to ,
15171524 options = options ,
15181525 stream = stream ,
15191526 stream_cls = stream_cls ,
1520- remaining_retries = remaining_retries ,
1527+ retries_taken = retries_taken ,
15211528 )
15221529
15231530 async def _request (
@@ -1527,7 +1534,7 @@ async def _request(
15271534 * ,
15281535 stream : bool ,
15291536 stream_cls : type [_AsyncStreamT ] | None ,
1530- remaining_retries : int | None ,
1537+ retries_taken : int ,
15311538 ) -> ResponseT | _AsyncStreamT :
15321539 if self ._platform is None :
15331540 # `get_platform` can make blocking IO calls so we
@@ -1542,8 +1549,8 @@ async def _request(
15421549 cast_to = self ._maybe_override_cast_to (cast_to , options )
15431550 options = await self ._prepare_options (options )
15441551
1545- retries = self . _remaining_retries ( remaining_retries , options )
1546- request = self ._build_request (options )
1552+ remaining_retries = options . get_max_retries ( self . max_retries ) - retries_taken
1553+ request = self ._build_request (options , retries_taken = retries_taken )
15471554 await self ._prepare_request (request )
15481555
15491556 kwargs : HttpxSendArgs = {}
@@ -1559,11 +1566,11 @@ async def _request(
15591566 except httpx .TimeoutException as err :
15601567 log .debug ("Encountered httpx.TimeoutException" , exc_info = True )
15611568
1562- if retries > 0 :
1569+ if remaining_retries > 0 :
15631570 return await self ._retry_request (
15641571 input_options ,
15651572 cast_to ,
1566- retries ,
1573+ retries_taken = retries_taken ,
15671574 stream = stream ,
15681575 stream_cls = stream_cls ,
15691576 response_headers = None ,
@@ -1574,11 +1581,11 @@ async def _request(
15741581 except Exception as err :
15751582 log .debug ("Encountered Exception" , exc_info = True )
15761583
1577- if retries > 0 :
1584+ if retries_taken > 0 :
15781585 return await self ._retry_request (
15791586 input_options ,
15801587 cast_to ,
1581- retries ,
1588+ retries_taken = retries_taken ,
15821589 stream = stream ,
15831590 stream_cls = stream_cls ,
15841591 response_headers = None ,
@@ -1596,13 +1603,13 @@ async def _request(
15961603 except httpx .HTTPStatusError as err : # thrown on 4xx and 5xx status code
15971604 log .debug ("Encountered httpx.HTTPStatusError" , exc_info = True )
15981605
1599- if retries > 0 and self ._should_retry (err .response ):
1606+ if remaining_retries > 0 and self ._should_retry (err .response ):
16001607 await err .response .aclose ()
16011608 return await self ._retry_request (
16021609 input_options ,
16031610 cast_to ,
1604- retries ,
1605- err .response .headers ,
1611+ retries_taken = retries_taken ,
1612+ response_headers = err .response .headers ,
16061613 stream = stream ,
16071614 stream_cls = stream_cls ,
16081615 )
@@ -1621,34 +1628,34 @@ async def _request(
16211628 response = response ,
16221629 stream = stream ,
16231630 stream_cls = stream_cls ,
1624- retries_taken = options . get_max_retries ( self . max_retries ) - retries ,
1631+ retries_taken = retries_taken ,
16251632 )
16261633
16271634 async def _retry_request (
16281635 self ,
16291636 options : FinalRequestOptions ,
16301637 cast_to : Type [ResponseT ],
1631- remaining_retries : int ,
1632- response_headers : httpx .Headers | None ,
16331638 * ,
1639+ retries_taken : int ,
1640+ response_headers : httpx .Headers | None ,
16341641 stream : bool ,
16351642 stream_cls : type [_AsyncStreamT ] | None ,
16361643 ) -> ResponseT | _AsyncStreamT :
1637- remaining = remaining_retries - 1
1638- if remaining == 1 :
1644+ remaining_retries = options . get_max_retries ( self . max_retries ) - retries_taken
1645+ if remaining_retries == 1 :
16391646 log .debug ("1 retry left" )
16401647 else :
1641- log .debug ("%i retries left" , remaining )
1648+ log .debug ("%i retries left" , remaining_retries )
16421649
1643- timeout = self ._calculate_retry_timeout (remaining , options , response_headers )
1650+ timeout = self ._calculate_retry_timeout (remaining_retries , options , response_headers )
16441651 log .info ("Retrying request to %s in %f seconds" , options .url , timeout )
16451652
16461653 await anyio .sleep (timeout )
16471654
16481655 return await self ._request (
16491656 options = options ,
16501657 cast_to = cast_to ,
1651- remaining_retries = remaining ,
1658+ retries_taken = retries_taken + 1 ,
16521659 stream = stream ,
16531660 stream_cls = stream_cls ,
16541661 )
0 commit comments