4141GLOBAL_SONARCLOUD_URL  =  "https://sonarcloud.io" 
4242US_SONARCLOUD_URL  =  "https://sonarqube.us" 
4343
44+ UNAUTHORIZED_STATUS_CODES  =  (401 , 403 )
45+ 
46+ ACCEPT_JSON  =  {"Accept" : "application/json" }
47+ ACCEPT_OCTET_STREAM  =  {"Accept" : "application/octet-stream" }
48+ 
4449
4550@dataclass (frozen = True ) 
4651class  SQVersion :
@@ -92,7 +97,7 @@ def __post_init__(self):
9297
9398@dataclass (frozen = True ) 
9499class  JRE :
95-     id : str 
100+     id : Optional [ str ] 
96101    filename : str 
97102    sha256 : str 
98103    java_path : str 
@@ -103,7 +108,7 @@ class JRE:
103108    @staticmethod  
104109    def  from_dict (dict : dict ) ->  "JRE" :
105110        return  JRE (
106-             id = dict [ "id" ] ,
111+             id = dict . get ( "id" ,  None ) ,
107112            filename = dict ["filename" ],
108113            sha256 = dict ["sha256" ],
109114            java_path = dict ["javaPath" ],
@@ -182,6 +187,7 @@ def __call__(self, r):
182187class  EngineInfo :
183188    filename : str 
184189    sha256 : str 
190+     download_url : Optional [str ] =  None 
185191
186192
187193class  SonarQubeApi :
@@ -193,7 +199,7 @@ def __raise_exception(self, exception: Exception) -> NoReturn:
193199        if  (
194200            isinstance (exception , requests .RequestException )
195201            and  exception .response  is  not   None 
196-             and  exception .response .status_code  ==   401 
202+             and  exception .response .status_code  in   UNAUTHORIZED_STATUS_CODES 
197203        ):
198204            raise  SonarQubeApiUnauthroizedException .create_default (self .base_urls .base_url ) from  exception 
199205        else :
@@ -215,14 +221,14 @@ def get_analysis_version(self) -> SQVersion:
215221
216222    def  get_analysis_engine (self ) ->  EngineInfo :
217223        try :
218-             res  =  requests .get (
219-                 f"{ self .base_urls .api_base_url }  /analysis/engine" , headers = {"Accept" : "application/json" }, auth = self .auth 
220-             )
224+             res  =  requests .get (f"{ self .base_urls .api_base_url }  /analysis/engine" , headers = ACCEPT_JSON , auth = self .auth )
221225            res .raise_for_status ()
222226            json  =  res .json ()
223227            if  "filename"  not  in   json  or  "sha256"  not  in   json :
224228                raise  SonarQubeApiException ("Invalid response from the server" )
225-             return  EngineInfo (filename = json ["filename" ], sha256 = json ["sha256" ])
229+             return  EngineInfo (
230+                 filename = json ["filename" ], sha256 = json ["sha256" ], download_url = json .get ("downloadUrl" , None )
231+             )
226232        except  requests .RequestException  as  e :
227233            self .__raise_exception (e )
228234
@@ -234,7 +240,7 @@ def download_analysis_engine(self, handle: typing.BinaryIO) -> None:
234240        try :
235241            res  =  requests .get (
236242                f"{ self .base_urls .api_base_url }  /analysis/engine" ,
237-                 headers = { "Accept" :  "application/octet-stream" } ,
243+                 headers = ACCEPT_OCTET_STREAM ,
238244                auth = self .auth ,
239245            )
240246            self .__download_file (res , handle )
@@ -247,7 +253,7 @@ def get_analysis_jres(self, os: OsStr, arch: ArchStr) -> list[JRE]:
247253            res  =  requests .get (
248254                f"{ self .base_urls .api_base_url }  /analysis/jres" ,
249255                auth = self .auth ,
250-                 headers = { "Accept" :  "application/json" } ,
256+                 headers = ACCEPT_JSON ,
251257                params = params ,
252258            )
253259            res .raise_for_status ()
@@ -265,13 +271,27 @@ def download_analysis_jre(self, id: str, handle: typing.BinaryIO) -> None:
265271        try :
266272            res  =  requests .get (
267273                f"{ self .base_urls .api_base_url }  /analysis/jres/{ id }  " ,
268-                 headers = { "Accept" :  "application/octet-stream" } ,
274+                 headers = ACCEPT_OCTET_STREAM ,
269275                auth = self .auth ,
270276            )
271277            self .__download_file (res , handle )
272278        except  requests .RequestException  as  e :
273279            self .__raise_exception (e )
274280
281+     def  download_file_from_url (self , url : str , handle : typing .BinaryIO ) ->  None :
282+         """ 
283+         This method can raise a SonarQubeApiException if the server doesn't respond successfully. 
284+         Alternative, if the file IO fails, an IOError or OSError can be raised. 
285+         """ 
286+         try :
287+             res  =  requests .get (
288+                 url ,
289+                 headers = ACCEPT_OCTET_STREAM ,
290+             )
291+             self .__download_file (res , handle )
292+         except  requests .RequestException  as  e :
293+             self .__raise_exception (e )
294+ 
275295    def  __download_file (self , res : requests .Response , handle : typing .BinaryIO ) ->  None :
276296        res .raise_for_status ()
277297        for  chunk  in  res .iter_content (chunk_size = 128 ):
0 commit comments