7272    DEFAULT_TIMEOUT ,
7373    DEFAULT_MAX_RETRIES ,
7474    RAW_RESPONSE_HEADER ,
75+     STREAMED_RAW_RESPONSE_HEADER ,
7576)
7677from  ._streaming  import  Stream , AsyncStream 
7778from  ._exceptions  import  (
@@ -363,14 +364,21 @@ def _make_status_error_from_response(
363364        self ,
364365        response : httpx .Response ,
365366    ) ->  APIStatusError :
366-         err_text  =  response .text .strip ()
367-         body  =  err_text 
367+         if  response .is_closed  and  not  response .is_stream_consumed :
368+             # We can't read the response body as it has been closed 
369+             # before it was read. This can happen if an event hook 
370+             # raises a status error. 
371+             body  =  None 
372+             err_msg  =  f"Error code: { response .status_code }  
373+         else :
374+             err_text  =  response .text .strip ()
375+             body  =  err_text 
368376
369-         try :
370-             body  =  json .loads (err_text )
371-             err_msg  =  f"Error code: { response .status_code } { body }  
372-         except  Exception :
373-             err_msg  =  err_text  or  f"Error code: { response .status_code }  
377+              try :
378+                  body  =  json .loads (err_text )
379+                  err_msg  =  f"Error code: { response .status_code } { body }  
380+              except  Exception :
381+                  err_msg  =  err_text  or  f"Error code: { response .status_code }  
374382
375383        return  self ._make_status_error (err_msg , body = body , response = response )
376384
@@ -534,6 +542,12 @@ def _process_response_data(
534542        except  pydantic .ValidationError  as  err :
535543            raise  APIResponseValidationError (response = response , body = data ) from  err 
536544
545+     def  _should_stream_response_body (self , * , request : httpx .Request ) ->  bool :
546+         if  request .headers .get (STREAMED_RAW_RESPONSE_HEADER ) ==  "true" :
547+             return  True 
548+ 
549+         return  False 
550+ 
537551    @property  
538552    def  qs (self ) ->  Querystring :
539553        return  Querystring ()
@@ -606,7 +620,7 @@ def _calculate_retry_timeout(
606620            if  response_headers  is  not None :
607621                retry_header  =  response_headers .get ("retry-after" )
608622                try :
609-                     retry_after  =  int (retry_header )
623+                     retry_after  =  float (retry_header )
610624                except  Exception :
611625                    retry_date_tuple  =  email .utils .parsedate_tz (retry_header )
612626                    if  retry_date_tuple  is  None :
@@ -862,14 +876,21 @@ def _request(
862876        request  =  self ._build_request (options )
863877        self ._prepare_request (request )
864878
879+         response  =  None 
880+ 
865881        try :
866-             response  =  self ._client .send (request , auth = self .custom_auth , stream = stream )
882+             response  =  self ._client .send (
883+                 request ,
884+                 auth = self .custom_auth ,
885+                 stream = stream  or  self ._should_stream_response_body (request = request ),
886+             )
867887            log .debug (
868888                'HTTP Request: %s %s "%i %s"' , request .method , request .url , response .status_code , response .reason_phrase 
869889            )
870890            response .raise_for_status ()
871891        except  httpx .HTTPStatusError  as  err :  # thrown on 4xx and 5xx status code 
872892            if  retries  >  0  and  self ._should_retry (err .response ):
893+                 err .response .close ()
873894                return  self ._retry_request (
874895                    options ,
875896                    cast_to ,
@@ -881,27 +902,39 @@ def _request(
881902
882903            # If the response is streamed then we need to explicitly read the response 
883904            # to completion before attempting to access the response text. 
884-             err .response .read ()
905+             if  not  err .response .is_closed :
906+                 err .response .read ()
907+ 
885908            raise  self ._make_status_error_from_response (err .response ) from  None 
886909        except  httpx .TimeoutException  as  err :
910+             if  response  is  not None :
911+                 response .close ()
912+ 
887913            if  retries  >  0 :
888914                return  self ._retry_request (
889915                    options ,
890916                    cast_to ,
891917                    retries ,
892918                    stream = stream ,
893919                    stream_cls = stream_cls ,
920+                     response_headers = response .headers  if  response  is  not None  else  None ,
894921                )
922+ 
895923            raise  APITimeoutError (request = request ) from  err 
896924        except  Exception  as  err :
925+             if  response  is  not None :
926+                 response .close ()
927+ 
897928            if  retries  >  0 :
898929                return  self ._retry_request (
899930                    options ,
900931                    cast_to ,
901932                    retries ,
902933                    stream = stream ,
903934                    stream_cls = stream_cls ,
935+                     response_headers = response .headers  if  response  is  not None  else  None ,
904936                )
937+ 
905938            raise  APIConnectionError (request = request ) from  err 
906939
907940        return  self ._process_response (
@@ -917,7 +950,7 @@ def _retry_request(
917950        options : FinalRequestOptions ,
918951        cast_to : Type [ResponseT ],
919952        remaining_retries : int ,
920-         response_headers : Optional [ httpx .Headers ]  =  None ,
953+         response_headers : httpx .Headers   |  None ,
921954        * ,
922955        stream : bool ,
923956        stream_cls : type [_StreamT ] |  None ,
@@ -1303,14 +1336,21 @@ async def _request(
13031336        request  =  self ._build_request (options )
13041337        await  self ._prepare_request (request )
13051338
1339+         response  =  None 
1340+ 
13061341        try :
1307-             response  =  await  self ._client .send (request , auth = self .custom_auth , stream = stream )
1342+             response  =  await  self ._client .send (
1343+                 request ,
1344+                 auth = self .custom_auth ,
1345+                 stream = stream  or  self ._should_stream_response_body (request = request ),
1346+             )
13081347            log .debug (
13091348                'HTTP Request: %s %s "%i %s"' , request .method , request .url , response .status_code , response .reason_phrase 
13101349            )
13111350            response .raise_for_status ()
13121351        except  httpx .HTTPStatusError  as  err :  # thrown on 4xx and 5xx status code 
13131352            if  retries  >  0  and  self ._should_retry (err .response ):
1353+                 await  err .response .aclose ()
13141354                return  await  self ._retry_request (
13151355                    options ,
13161356                    cast_to ,
@@ -1322,19 +1362,39 @@ async def _request(
13221362
13231363            # If the response is streamed then we need to explicitly read the response 
13241364            # to completion before attempting to access the response text. 
1325-             await  err .response .aread ()
1365+             if  not  err .response .is_closed :
1366+                 await  err .response .aread ()
1367+ 
13261368            raise  self ._make_status_error_from_response (err .response ) from  None 
1327-         except  httpx .ConnectTimeout  as  err :
1328-             if  retries  >  0 :
1329-                 return  await  self ._retry_request (options , cast_to , retries , stream = stream , stream_cls = stream_cls )
1330-             raise  APITimeoutError (request = request ) from  err 
13311369        except  httpx .TimeoutException  as  err :
1370+             if  response  is  not None :
1371+                 await  response .aclose ()
1372+ 
13321373            if  retries  >  0 :
1333-                 return  await  self ._retry_request (options , cast_to , retries , stream = stream , stream_cls = stream_cls )
1374+                 return  await  self ._retry_request (
1375+                     options ,
1376+                     cast_to ,
1377+                     retries ,
1378+                     stream = stream ,
1379+                     stream_cls = stream_cls ,
1380+                     response_headers = response .headers  if  response  is  not None  else  None ,
1381+                 )
1382+ 
13341383            raise  APITimeoutError (request = request ) from  err 
13351384        except  Exception  as  err :
1385+             if  response  is  not None :
1386+                 await  response .aclose ()
1387+ 
13361388            if  retries  >  0 :
1337-                 return  await  self ._retry_request (options , cast_to , retries , stream = stream , stream_cls = stream_cls )
1389+                 return  await  self ._retry_request (
1390+                     options ,
1391+                     cast_to ,
1392+                     retries ,
1393+                     stream = stream ,
1394+                     stream_cls = stream_cls ,
1395+                     response_headers = response .headers  if  response  is  not None  else  None ,
1396+                 )
1397+ 
13381398            raise  APIConnectionError (request = request ) from  err 
13391399
13401400        return  self ._process_response (
@@ -1350,7 +1410,7 @@ async def _retry_request(
13501410        options : FinalRequestOptions ,
13511411        cast_to : Type [ResponseT ],
13521412        remaining_retries : int ,
1353-         response_headers : Optional [ httpx .Headers ]  =  None ,
1413+         response_headers : httpx .Headers   |  None ,
13541414        * ,
13551415        stream : bool ,
13561416        stream_cls : type [_AsyncStreamT ] |  None ,
0 commit comments