1- from typing import Any , Dict , Generic , List , Union , cast , TypedDict
1+ from typing import Any , Dict , Generic , List , Union , cast , TypedDict , Generator
22import requests
33from typing_extensions import Literal , TypeVar
44from .exceptions import NoContentError , raise_for_code_and_type
5+ import json
56
67RequestVerb = Literal ["get" , "post" , "put" , "patch" , "delete" ]
78
@@ -24,6 +25,7 @@ def __init__(
2425 verb : RequestVerb ,
2526 headers : Dict [str , str ] = {"Content-Type" : "application/json" },
2627 data : Union [bytes , None ] = None ,
28+ stream : Union [bool , None ] = False ,
2729 ):
2830 self .path = path
2931 self .params = params
@@ -33,6 +35,7 @@ def __init__(
3335 self .data = data
3436 self .headers = headers
3537 self .disable_request_logging = config .get ("disable_request_logging" )
38+ self .stream = stream
3639
3740 def perform (self ) -> Union [T , None ]:
3841 """Is the main function that makes the HTTP request
@@ -152,6 +155,75 @@ def __get_headers(self) -> Dict[Any, Any]:
152155
153156 return _headers
154157
158+ def perform_streaming (self ) -> Generator [Union [T , str ], None , None ]:
159+ """Is the main function that makes the HTTP request
160+ to the JigsawStack API. It uses the path, params, and verb attributes
161+ to make the request.
162+
163+ Returns:
164+ Generator[bytes, None, None]: A generator of bytes
165+
166+ Raises:
167+ requests.HTTPError: If the request fails
168+ """
169+ resp = self .make_request (url = f"{ self .api_url } { self .path } " )
170+
171+ # delete calls do not return a body
172+ if resp .text == "" :
173+ return None
174+
175+ # this is a safety net, if we get here it means the JigsawStack API is having issues
176+ # and most likely the gateway is returning htmls
177+ if "application/json" not in resp .headers ["content-type" ]:
178+ raise_for_code_and_type (
179+ code = 500 ,
180+ message = "Failed to parse JigsawStack API response. Please try again." ,
181+ )
182+
183+ if resp .status_code != 200 :
184+ error = resp .json ()
185+ raise_for_code_and_type (
186+ code = resp .status_code ,
187+ message = error .get ("message" ),
188+ err = error .get ("error" ),
189+ )
190+
191+ def try_parse_data (chunk : bytes ) -> Union [T , str ]:
192+ if not chunk :
193+ return chunk
194+ # Decode bytes to text
195+ text = chunk .decode ("utf-8" )
196+
197+ try :
198+ # Try to parse as JSON
199+ return json .loads (text )
200+ except json .JSONDecodeError :
201+ # Return as text if not valid JSON
202+ return text
203+
204+ # Yield content in chunks
205+ def chunk_generator ():
206+ for chunk in resp .iter_content (chunk_size = 1024 ): # 1KB chunks
207+ if chunk : # Filter out keep-alive new chunks
208+ yield try_parse_data (chunk )
209+
210+ return chunk_generator ()
211+
212+ def perform_with_content_streaming (self ) -> T :
213+ """
214+ Perform an HTTP request and return the response content as a streaming response.
215+
216+ Returns:
217+ T: The content of the response
218+
219+ Raises:
220+ NoContentError: If the response content is `None`.
221+ """
222+ resp = self .perform_streaming ()
223+ if resp is None :
224+ raise NoContentError ()
225+ return resp
226+
155227 def make_request (self , url : str ) -> requests .Response :
156228 """make_request is a helper function that makes the actual
157229 HTTP request to the JigsawStack API.
@@ -183,6 +255,7 @@ def make_request(self, url: str) -> requests.Response:
183255 json = params ,
184256 headers = headers ,
185257 data = data ,
258+ stream = self .stream ,
186259 )
187260 except requests .HTTPError as e :
188261 raise e
0 commit comments