44from typing import Any , Dict , List , Optional , Union
55from dataclasses import dataclass , asdict , field
66import urllib .parse
7+ import math
8+ import time
79from ..core .dedupe import Dedupe
810from ..core .enums import unknown_enum_value
911from ..utils import IntegrationType , Utils
12+ from ..exceptions import APIFailure , APITimeout
1013
1114log = logging .getLogger ("socketdev" )
1215
@@ -960,15 +963,71 @@ def stream_diff(
960963 )
961964 return {}
962965
963- def stream (self , org_slug : str , full_scan_id : str , use_types : bool = False ) -> Union [dict , FullScanStreamResponse ]:
964- path = "orgs/" + org_slug + "/full-scans/" + full_scan_id
965- response = self .api .do_request (path = path , method = "GET" )
966+ def stream (
967+ self ,
968+ org_slug : str ,
969+ full_scan_id : str ,
970+ use_types : bool = False ,
971+ * ,
972+ poll_timeout_seconds : Optional [float ] = None ,
973+ poll_initial_interval_seconds : float = 5.0 ,
974+ poll_max_interval_seconds : float = 10.0 ,
975+ ) -> Union [dict , FullScanStreamResponse ]:
976+ request_timeout = self .api .request_timeout
977+ timeout = request_timeout if poll_timeout_seconds is None else poll_timeout_seconds
978+ values = (timeout , poll_initial_interval_seconds , poll_max_interval_seconds , request_timeout )
979+ if any (
980+ isinstance (value , bool )
981+ or not isinstance (value , (int , float ))
982+ or not math .isfinite (value )
983+ or value <= 0
984+ for value in values
985+ ):
986+ raise ValueError ("Full scan polling timeout and intervals must be positive finite numbers" )
987+ if poll_initial_interval_seconds > poll_max_interval_seconds :
988+ raise ValueError ("Initial polling interval must not exceed maximum polling interval" )
989+
990+ path = (
991+ "orgs/" + urllib .parse .quote (org_slug , safe = "" ) + "/full-scans/"
992+ + urllib .parse .quote (full_scan_id , safe = "" ) + "?cached=true"
993+ )
994+ deadline = time .monotonic () + timeout
995+ interval = poll_initial_interval_seconds
996+ while True :
997+ remaining = deadline - time .monotonic ()
998+ if remaining <= 0 :
999+ raise APITimeout ("Timed out waiting for full scan results" )
1000+ try :
1001+ response = self .api .do_request (
1002+ path = path ,
1003+ method = "GET" ,
1004+ request_timeout = min (30.0 , float (request_timeout ), remaining ),
1005+ )
1006+ except APIFailure as error :
1007+ if not error .is_transient_error ():
1008+ raise
1009+ remaining = deadline - time .monotonic ()
1010+ if remaining <= 0 :
1011+ raise APITimeout ("Timed out waiting for full scan results" ) from error
1012+ time .sleep (min (interval , remaining ))
1013+ interval = min (poll_max_interval_seconds , interval * 1.5 )
1014+ continue
1015+ if response .status_code == 202 :
1016+ response .close ()
1017+ remaining = deadline - time .monotonic ()
1018+ if remaining <= 0 :
1019+ raise APITimeout ("Timed out waiting for full scan results" )
1020+ time .sleep (min (interval , remaining ))
1021+ interval = min (poll_max_interval_seconds , interval * 1.5 )
1022+ continue
1023+ break
9661024
9671025 if response .status_code == 200 :
9681026 try :
9691027 stream_str = []
9701028 artifacts = {}
9711029 result = response .text
1030+ response .close ()
9721031 result = result .strip ('"' ).strip ()
9731032 for line in result .split ("\n " ):
9741033 if line != '"' and line != "" and line is not None :
@@ -993,6 +1052,7 @@ def stream(self, org_slug: str, full_scan_id: str, use_types: bool = False) -> U
9931052 return artifacts
9941053
9951054 except Exception as e :
1055+ response .close ()
9961056 error_message = f"Error parsing stream response: { str (e )} "
9971057 log .error (error_message )
9981058 if use_types :
@@ -1001,7 +1061,10 @@ def stream(self, org_slug: str, full_scan_id: str, use_types: bool = False) -> U
10011061 )
10021062 return {}
10031063
1004- error_message = response .json ().get ("error" , {}).get ("message" , "Unknown error" )
1064+ try :
1065+ error_message = response .json ().get ("error" , {}).get ("message" , "Unknown error" )
1066+ finally :
1067+ response .close ()
10051068 log .error (f"Error streaming full scan: { response .status_code } , message: { error_message } " )
10061069 if use_types :
10071070 return FullScanStreamResponse .from_dict (
0 commit comments