@@ -400,14 +400,7 @@ def _make_status_error(
400400 ) -> _exceptions .APIStatusError :
401401 raise NotImplementedError ()
402402
403- def _remaining_retries (
404- self ,
405- remaining_retries : Optional [int ],
406- options : FinalRequestOptions ,
407- ) -> int :
408- return remaining_retries if remaining_retries is not None else options .get_max_retries (self .max_retries )
409-
410- def _build_headers (self , options : FinalRequestOptions ) -> httpx .Headers :
403+ def _build_headers (self , options : FinalRequestOptions , * , retries_taken : int = 0 ) -> httpx .Headers :
411404 custom_headers = options .headers or {}
412405 headers_dict = _merge_mappings (self .default_headers , custom_headers )
413406 self ._validate_headers (headers_dict , custom_headers )
@@ -419,6 +412,8 @@ def _build_headers(self, options: FinalRequestOptions) -> httpx.Headers:
419412 if idempotency_header and options .method .lower () != "get" and idempotency_header not in headers :
420413 headers [idempotency_header ] = options .idempotency_key or self ._idempotency_key ()
421414
415+ headers .setdefault ("x-stainless-retry-count" , str (retries_taken ))
416+
422417 return headers
423418
424419 def _prepare_url (self , url : str ) -> URL :
@@ -440,6 +435,8 @@ def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder:
440435 def _build_request (
441436 self ,
442437 options : FinalRequestOptions ,
438+ * ,
439+ retries_taken : int = 0 ,
443440 ) -> httpx .Request :
444441 if log .isEnabledFor (logging .DEBUG ):
445442 log .debug ("Request options: %s" , model_dump (options , exclude_unset = True ))
@@ -455,7 +452,7 @@ def _build_request(
455452 else :
456453 raise RuntimeError (f"Unexpected JSON data type, { type (json_data )} , cannot merge with `extra_body`" )
457454
458- headers = self ._build_headers (options )
455+ headers = self ._build_headers (options , retries_taken = retries_taken )
459456 params = _merge_mappings (self .default_query , options .params )
460457 content_type = headers .get ("Content-Type" )
461458 files = options .files
@@ -938,20 +935,25 @@ def request(
938935 stream : bool = False ,
939936 stream_cls : type [_StreamT ] | None = None ,
940937 ) -> ResponseT | _StreamT :
938+ if remaining_retries is not None :
939+ retries_taken = options .get_max_retries (self .max_retries ) - remaining_retries
940+ else :
941+ retries_taken = 0
942+
941943 return self ._request (
942944 cast_to = cast_to ,
943945 options = options ,
944946 stream = stream ,
945947 stream_cls = stream_cls ,
946- remaining_retries = remaining_retries ,
948+ retries_taken = retries_taken ,
947949 )
948950
949951 def _request (
950952 self ,
951953 * ,
952954 cast_to : Type [ResponseT ],
953955 options : FinalRequestOptions ,
954- remaining_retries : int | None ,
956+ retries_taken : int ,
955957 stream : bool ,
956958 stream_cls : type [_StreamT ] | None ,
957959 ) -> ResponseT | _StreamT :
@@ -963,8 +965,8 @@ def _request(
963965 cast_to = self ._maybe_override_cast_to (cast_to , options )
964966 options = self ._prepare_options (options )
965967
966- retries = self . _remaining_retries ( remaining_retries , options )
967- request = self ._build_request (options )
968+ remaining_retries = options . get_max_retries ( self . max_retries ) - retries_taken
969+ request = self ._build_request (options , retries_taken = retries_taken )
968970 self ._prepare_request (request )
969971
970972 kwargs : HttpxSendArgs = {}
@@ -982,11 +984,11 @@ def _request(
982984 except httpx .TimeoutException as err :
983985 log .debug ("Encountered httpx.TimeoutException" , exc_info = True )
984986
985- if retries > 0 :
987+ if remaining_retries > 0 :
986988 return self ._retry_request (
987989 input_options ,
988990 cast_to ,
989- retries ,
991+ retries_taken = retries_taken ,
990992 stream = stream ,
991993 stream_cls = stream_cls ,
992994 response_headers = None ,
@@ -997,11 +999,11 @@ def _request(
997999 except Exception as err :
9981000 log .debug ("Encountered Exception" , exc_info = True )
9991001
1000- if retries > 0 :
1002+ if remaining_retries > 0 :
10011003 return self ._retry_request (
10021004 input_options ,
10031005 cast_to ,
1004- retries ,
1006+ retries_taken = retries_taken ,
10051007 stream = stream ,
10061008 stream_cls = stream_cls ,
10071009 response_headers = None ,
@@ -1024,13 +1026,13 @@ def _request(
10241026 except httpx .HTTPStatusError as err : # thrown on 4xx and 5xx status code
10251027 log .debug ("Encountered httpx.HTTPStatusError" , exc_info = True )
10261028
1027- if retries > 0 and self ._should_retry (err .response ):
1029+ if remaining_retries > 0 and self ._should_retry (err .response ):
10281030 err .response .close ()
10291031 return self ._retry_request (
10301032 input_options ,
10311033 cast_to ,
1032- retries ,
1033- err .response .headers ,
1034+ retries_taken = retries_taken ,
1035+ response_headers = err .response .headers ,
10341036 stream = stream ,
10351037 stream_cls = stream_cls ,
10361038 )
@@ -1049,26 +1051,26 @@ def _request(
10491051 response = response ,
10501052 stream = stream ,
10511053 stream_cls = stream_cls ,
1052- retries_taken = options . get_max_retries ( self . max_retries ) - retries ,
1054+ retries_taken = retries_taken ,
10531055 )
10541056
10551057 def _retry_request (
10561058 self ,
10571059 options : FinalRequestOptions ,
10581060 cast_to : Type [ResponseT ],
1059- remaining_retries : int ,
1060- response_headers : httpx .Headers | None ,
10611061 * ,
1062+ retries_taken : int ,
1063+ response_headers : httpx .Headers | None ,
10621064 stream : bool ,
10631065 stream_cls : type [_StreamT ] | None ,
10641066 ) -> ResponseT | _StreamT :
1065- remaining = remaining_retries - 1
1066- if remaining == 1 :
1067+ remaining_retries = options . get_max_retries ( self . max_retries ) - retries_taken
1068+ if remaining_retries == 1 :
10671069 log .debug ("1 retry left" )
10681070 else :
1069- log .debug ("%i retries left" , remaining )
1071+ log .debug ("%i retries left" , remaining_retries )
10701072
1071- timeout = self ._calculate_retry_timeout (remaining , options , response_headers )
1073+ timeout = self ._calculate_retry_timeout (remaining_retries , options , response_headers )
10721074 log .info ("Retrying request to %s in %f seconds" , options .url , timeout )
10731075
10741076 # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a
@@ -1078,7 +1080,7 @@ def _retry_request(
10781080 return self ._request (
10791081 options = options ,
10801082 cast_to = cast_to ,
1081- remaining_retries = remaining ,
1083+ retries_taken = retries_taken + 1 ,
10821084 stream = stream ,
10831085 stream_cls = stream_cls ,
10841086 )
@@ -1496,12 +1498,17 @@ async def request(
14961498 stream_cls : type [_AsyncStreamT ] | None = None ,
14971499 remaining_retries : Optional [int ] = None ,
14981500 ) -> ResponseT | _AsyncStreamT :
1501+ if remaining_retries is not None :
1502+ retries_taken = options .get_max_retries (self .max_retries ) - remaining_retries
1503+ else :
1504+ retries_taken = 0
1505+
14991506 return await self ._request (
15001507 cast_to = cast_to ,
15011508 options = options ,
15021509 stream = stream ,
15031510 stream_cls = stream_cls ,
1504- remaining_retries = remaining_retries ,
1511+ retries_taken = retries_taken ,
15051512 )
15061513
15071514 async def _request (
@@ -1511,7 +1518,7 @@ async def _request(
15111518 * ,
15121519 stream : bool ,
15131520 stream_cls : type [_AsyncStreamT ] | None ,
1514- remaining_retries : int | None ,
1521+ retries_taken : int ,
15151522 ) -> ResponseT | _AsyncStreamT :
15161523 if self ._platform is None :
15171524 # `get_platform` can make blocking IO calls so we
@@ -1526,8 +1533,8 @@ async def _request(
15261533 cast_to = self ._maybe_override_cast_to (cast_to , options )
15271534 options = await self ._prepare_options (options )
15281535
1529- retries = self . _remaining_retries ( remaining_retries , options )
1530- request = self ._build_request (options )
1536+ remaining_retries = options . get_max_retries ( self . max_retries ) - retries_taken
1537+ request = self ._build_request (options , retries_taken = retries_taken )
15311538 await self ._prepare_request (request )
15321539
15331540 kwargs : HttpxSendArgs = {}
@@ -1543,11 +1550,11 @@ async def _request(
15431550 except httpx .TimeoutException as err :
15441551 log .debug ("Encountered httpx.TimeoutException" , exc_info = True )
15451552
1546- if retries > 0 :
1553+ if remaining_retries > 0 :
15471554 return await self ._retry_request (
15481555 input_options ,
15491556 cast_to ,
1550- retries ,
1557+ retries_taken = retries_taken ,
15511558 stream = stream ,
15521559 stream_cls = stream_cls ,
15531560 response_headers = None ,
@@ -1558,11 +1565,11 @@ async def _request(
15581565 except Exception as err :
15591566 log .debug ("Encountered Exception" , exc_info = True )
15601567
1561- if retries > 0 :
1568+ if retries_taken > 0 :
15621569 return await self ._retry_request (
15631570 input_options ,
15641571 cast_to ,
1565- retries ,
1572+ retries_taken = retries_taken ,
15661573 stream = stream ,
15671574 stream_cls = stream_cls ,
15681575 response_headers = None ,
@@ -1580,13 +1587,13 @@ async def _request(
15801587 except httpx .HTTPStatusError as err : # thrown on 4xx and 5xx status code
15811588 log .debug ("Encountered httpx.HTTPStatusError" , exc_info = True )
15821589
1583- if retries > 0 and self ._should_retry (err .response ):
1590+ if remaining_retries > 0 and self ._should_retry (err .response ):
15841591 await err .response .aclose ()
15851592 return await self ._retry_request (
15861593 input_options ,
15871594 cast_to ,
1588- retries ,
1589- err .response .headers ,
1595+ retries_taken = retries_taken ,
1596+ response_headers = err .response .headers ,
15901597 stream = stream ,
15911598 stream_cls = stream_cls ,
15921599 )
@@ -1605,34 +1612,34 @@ async def _request(
16051612 response = response ,
16061613 stream = stream ,
16071614 stream_cls = stream_cls ,
1608- retries_taken = options . get_max_retries ( self . max_retries ) - retries ,
1615+ retries_taken = retries_taken ,
16091616 )
16101617
16111618 async def _retry_request (
16121619 self ,
16131620 options : FinalRequestOptions ,
16141621 cast_to : Type [ResponseT ],
1615- remaining_retries : int ,
1616- response_headers : httpx .Headers | None ,
16171622 * ,
1623+ retries_taken : int ,
1624+ response_headers : httpx .Headers | None ,
16181625 stream : bool ,
16191626 stream_cls : type [_AsyncStreamT ] | None ,
16201627 ) -> ResponseT | _AsyncStreamT :
1621- remaining = remaining_retries - 1
1622- if remaining == 1 :
1628+ remaining_retries = options . get_max_retries ( self . max_retries ) - retries_taken
1629+ if remaining_retries == 1 :
16231630 log .debug ("1 retry left" )
16241631 else :
1625- log .debug ("%i retries left" , remaining )
1632+ log .debug ("%i retries left" , remaining_retries )
16261633
1627- timeout = self ._calculate_retry_timeout (remaining , options , response_headers )
1634+ timeout = self ._calculate_retry_timeout (remaining_retries , options , response_headers )
16281635 log .info ("Retrying request to %s in %f seconds" , options .url , timeout )
16291636
16301637 await anyio .sleep (timeout )
16311638
16321639 return await self ._request (
16331640 options = options ,
16341641 cast_to = cast_to ,
1635- remaining_retries = remaining ,
1642+ retries_taken = retries_taken + 1 ,
16361643 stream = stream ,
16371644 stream_cls = stream_cls ,
16381645 )
0 commit comments