2222 THE SOFTWARE.
2323"""
2424
25+ import concurrent .futures
26+ import json
2527import os
2628import uuid
29+ from urllib .parse import urlparse
2730
2831import grpc
29- import json
30-
3132from google .protobuf .json_format import MessageToDict , ParseDict
3233from pypac .parser import PACFile
3334from pypac .resolver import ProxyResolver
34- from urllib .parse import urlparse
3535
36- from .api .components .v2 .scanoss_components_pb2_grpc import ComponentsStub
37- from .api .cryptography .v2 .scanoss_cryptography_pb2_grpc import CryptographyStub
38- from .api .dependencies .v2 .scanoss_dependencies_pb2_grpc import DependenciesStub
39- from .api .vulnerabilities .v2 .scanoss_vulnerabilities_pb2_grpc import VulnerabilitiesStub
40- from .api .provenance .v2 .scanoss_provenance_pb2_grpc import ProvenanceStub
41- from .api .semgrep .v2 .scanoss_semgrep_pb2_grpc import SemgrepStub
42- from .api .cryptography .v2 .scanoss_cryptography_pb2 import AlgorithmResponse
43- from .api .dependencies .v2 .scanoss_dependencies_pb2 import DependencyRequest , DependencyResponse
44- from .api .common .v2 .scanoss_common_pb2 import EchoRequest , EchoResponse , StatusResponse , StatusCode , PurlRequest
45- from .api .vulnerabilities .v2 .scanoss_vulnerabilities_pb2 import VulnerabilityResponse
46- from .api .semgrep .v2 .scanoss_semgrep_pb2 import SemgrepResponse
36+ from . import __version__
37+ from .api .common .v2 .scanoss_common_pb2 import (
38+ EchoRequest ,
39+ EchoResponse ,
40+ PurlRequest ,
41+ StatusCode ,
42+ StatusResponse ,
43+ )
4744from .api .components .v2 .scanoss_components_pb2 import (
4845 CompSearchRequest ,
4946 CompSearchResponse ,
5047 CompVersionRequest ,
5148 CompVersionResponse ,
5249)
50+ from .api .components .v2 .scanoss_components_pb2_grpc import ComponentsStub
51+ from .api .cryptography .v2 .scanoss_cryptography_pb2 import AlgorithmResponse
52+ from .api .cryptography .v2 .scanoss_cryptography_pb2_grpc import CryptographyStub
53+ from .api .dependencies .v2 .scanoss_dependencies_pb2 import DependencyRequest
54+ from .api .dependencies .v2 .scanoss_dependencies_pb2_grpc import DependenciesStub
5355from .api .provenance .v2 .scanoss_provenance_pb2 import ProvenanceResponse
56+ from .api .provenance .v2 .scanoss_provenance_pb2_grpc import ProvenanceStub
57+ from .api .semgrep .v2 .scanoss_semgrep_pb2 import SemgrepResponse
58+ from .api .semgrep .v2 .scanoss_semgrep_pb2_grpc import SemgrepStub
59+ from .api .vulnerabilities .v2 .scanoss_vulnerabilities_pb2 import VulnerabilityResponse
60+ from .api .vulnerabilities .v2 .scanoss_vulnerabilities_pb2_grpc import VulnerabilitiesStub
5461from .scanossbase import ScanossBase
55- from . import __version__
5662
5763DEFAULT_URL = 'https://api.osskb.org' # default free service URL
5864DEFAULT_URL2 = 'https://api.scanoss.com' # default premium service URL
5965SCANOSS_GRPC_URL = os .environ .get ('SCANOSS_GRPC_URL' ) if os .environ .get ('SCANOSS_GRPC_URL' ) else DEFAULT_URL
6066SCANOSS_API_KEY = os .environ .get ('SCANOSS_API_KEY' ) if os .environ .get ('SCANOSS_API_KEY' ) else ''
6167
68+ MAX_CONCURRENT_REQUESTS = 5
69+
6270
6371class ScanossGrpc (ScanossBase ):
6472 """
6573 Client for gRPC functionality
6674 """
6775
68- def __init__ (
76+ def __init__ ( # noqa: PLR0913
6977 self ,
7078 url : str = None ,
7179 debug : bool = False ,
@@ -222,31 +230,54 @@ def get_dependencies_json(self, dependencies: dict, depth: int = 1) -> dict:
222230 :return: Server response or None
223231 """
224232 if not dependencies :
225- self .print_stderr (f 'ERROR: No message supplied to send to gRPC service.' )
233+ self .print_stderr ('ERROR: No message supplied to send to gRPC service.' )
226234 return None
227- request_id = str (uuid .uuid4 ())
228- resp : DependencyResponse
229- try :
230- files_json = dependencies .get ('files' )
231- if files_json is None or len (files_json ) == 0 :
232- self .print_stderr (f'ERROR: No dependency data supplied to send to gRPC service.' )
235+
236+ files_json = dependencies .get ('files' )
237+
238+ if files_json is None or len (files_json ) == 0 :
239+ self .print_stderr ('ERROR: No dependency data supplied to send to gRPC service.' )
240+ return None
241+
242+ def process_file (file ):
243+ request_id = str (uuid .uuid4 ())
244+ try :
245+ file_request = {'files' : [file ]}
246+
247+ request = ParseDict (file_request , DependencyRequest ())
248+ request .depth = depth
249+ metadata = self .metadata [:]
250+ metadata .append (('x-request-id' , request_id ))
251+ self .print_debug (f'Sending dependency data for decoration (rqId: { request_id } )...' )
252+ resp = self .dependencies_stub .GetDependencies (request , metadata = metadata , timeout = self .timeout )
253+
254+ return MessageToDict (resp , preserving_proto_field_name = True )
255+ except Exception as e :
256+ self .print_stderr (
257+ f'ERROR: { e .__class__ .__name__ } Problem encountered sending gRPC message (rqId: { request_id } ): { e } '
258+ )
233259 return None
234- request = ParseDict (dependencies , DependencyRequest ()) # Parse the JSON/Dict into the dependency object
235- request .depth = depth
236- metadata = self .metadata [:]
237- metadata .append (('x-request-id' , request_id )) # Set a Request ID
238- self .print_debug (f'Sending dependency data for decoration (rqId: { request_id } )...' )
239- resp = self .dependencies_stub .GetDependencies (request , metadata = metadata , timeout = self .timeout )
240- except Exception as e :
241- self .print_stderr (
242- f'ERROR: { e .__class__ .__name__ } Problem encountered sending gRPC message (rqId: { request_id } ): { e } '
243- )
244- else :
245- if resp :
246- if not self ._check_status_response (resp .status , request_id ):
247- return None
248- return MessageToDict (resp , preserving_proto_field_name = True ) # Convert gRPC response to a dictionary
249- return None
260+
261+ all_responses = []
262+ with concurrent .futures .ThreadPoolExecutor (max_workers = MAX_CONCURRENT_REQUESTS ) as executor :
263+ future_to_file = {executor .submit (process_file , file ): file for file in files_json }
264+
265+ for future in concurrent .futures .as_completed (future_to_file ):
266+ response = future .result ()
267+ if response :
268+ all_responses .append (response )
269+
270+ SUCCESS_STATUS = 'SUCCESS'
271+
272+ merged_response = {'files' : [], 'status' : {'status' : SUCCESS_STATUS , 'message' : 'Success' }}
273+ for response in all_responses :
274+ if response :
275+ if 'files' in response and len (response ['files' ]) > 0 :
276+ merged_response ['files' ].append (response ['files' ][0 ])
277+ # Overwrite the status if the any of the responses was not successful
278+ if 'status' in response and response ['status' ]['status' ] != SUCCESS_STATUS :
279+ merged_response ['status' ] = response ['status' ]
280+ return merged_response
250281
251282 def get_crypto_json (self , purls : dict ) -> dict :
252283 """
@@ -255,7 +286,7 @@ def get_crypto_json(self, purls: dict) -> dict:
255286 :return: Server response or None
256287 """
257288 if not purls :
258- self .print_stderr (f 'ERROR: No message supplied to send to gRPC service.' )
289+ self .print_stderr ('ERROR: No message supplied to send to gRPC service.' )
259290 return None
260291 request_id = str (uuid .uuid4 ())
261292 resp : AlgorithmResponse
@@ -285,7 +316,7 @@ def get_vulnerabilities_json(self, purls: dict) -> dict:
285316 :return: Server response or None
286317 """
287318 if not purls :
288- self .print_stderr (f 'ERROR: No message supplied to send to gRPC service.' )
319+ self .print_stderr ('ERROR: No message supplied to send to gRPC service.' )
289320 return None
290321 request_id = str (uuid .uuid4 ())
291322 resp : VulnerabilityResponse
@@ -315,7 +346,7 @@ def get_semgrep_json(self, purls: dict) -> dict:
315346 :return: Server response or None
316347 """
317348 if not purls :
318- self .print_stderr (f 'ERROR: No message supplied to send to gRPC service.' )
349+ self .print_stderr ('ERROR: No message supplied to send to gRPC service.' )
319350 return None
320351 request_id = str (uuid .uuid4 ())
321352 resp : SemgrepResponse
@@ -345,7 +376,7 @@ def search_components_json(self, search: dict) -> dict:
345376 :return: Server response or None
346377 """
347378 if not search :
348- self .print_stderr (f 'ERROR: No message supplied to send to gRPC service.' )
379+ self .print_stderr ('ERROR: No message supplied to send to gRPC service.' )
349380 return None
350381 request_id = str (uuid .uuid4 ())
351382 resp : CompSearchResponse
@@ -375,7 +406,7 @@ def get_component_versions_json(self, search: dict) -> dict:
375406 :return: Server response or None
376407 """
377408 if not search :
378- self .print_stderr (f 'ERROR: No message supplied to send to gRPC service.' )
409+ self .print_stderr ('ERROR: No message supplied to send to gRPC service.' )
379410 return None
380411 request_id = str (uuid .uuid4 ())
381412 resp : CompVersionResponse
@@ -404,18 +435,22 @@ def _check_status_response(self, status_response: StatusResponse, request_id: st
404435 :param status_response: Status Response
405436 :return: True if successful, False otherwise
406437 """
438+
439+ SUCCEDED_WITH_WARNINGS_STATUS_CODE = 2
440+ FAILED_STATUS_CODE = 3
441+
407442 if not status_response :
408443 self .print_stderr (f'Warning: No status response supplied (rqId: { request_id } ). Assuming it was ok.' )
409444 return True
410445 self .print_debug (f'Checking response status (rqId: { request_id } ): { status_response } ' )
411446 status_code : StatusCode = status_response .status
412447 if status_code > 1 :
413448 ret_val = False # default to failed
414- msg = " Unsuccessful"
415- if status_code == 2 :
416- msg = " Succeeded with warnings"
449+ msg = ' Unsuccessful'
450+ if status_code == SUCCEDED_WITH_WARNINGS_STATUS_CODE :
451+ msg = ' Succeeded with warnings'
417452 ret_val = True # No need to fail as it succeeded with warnings
418- elif status_code == 3 :
453+ elif status_code == FAILED_STATUS_CODE :
419454 msg = 'Failed with warnings'
420455 self .print_stderr (f'{ msg } (rqId: { request_id } - status: { status_code } ): { status_response .message } ' )
421456 return ret_val
@@ -428,10 +463,10 @@ def _get_proxy_config(self):
428463 :param self:
429464 """
430465 if self .grpc_proxy :
431- self .print_debug (f 'Setting GRPC (grpc_proxy) proxy...' )
466+ self .print_debug ('Setting GRPC (grpc_proxy) proxy...' )
432467 os .environ ['grpc_proxy' ] = self .grpc_proxy
433468 elif self .proxy :
434- self .print_debug (f 'Setting GRPC (http_proxy/https_proxy) proxies...' )
469+ self .print_debug ('Setting GRPC (http_proxy/https_proxy) proxies...' )
435470 os .environ ['http_proxy' ] = self .proxy
436471 os .environ ['https_proxy' ] = self .proxy
437472 elif self .pac :
@@ -450,7 +485,7 @@ def get_provenance_json(self, purls: dict) -> dict:
450485 :return: Server response or None
451486 """
452487 if not purls :
453- self .print_stderr (f 'ERROR: No message supplied to send to gRPC service.' )
488+ self .print_stderr ('ERROR: No message supplied to send to gRPC service.' )
454489 return None
455490 request_id = str (uuid .uuid4 ())
456491 resp : ProvenanceResponse
@@ -461,15 +496,18 @@ def get_provenance_json(self, purls: dict) -> dict:
461496 self .print_debug (f'Sending data for provenance decoration (rqId: { request_id } )...' )
462497 resp = self .provenance_stub .GetComponentProvenance (request , metadata = metadata , timeout = self .timeout )
463498 except Exception as e :
464- self .print_stderr (f'ERROR: { e .__class__ .__name__ } Problem encountered sending gRPC message '
465- f'(rqId: { request_id } ): { e } ' )
499+ self .print_stderr (
500+ f'ERROR: { e .__class__ .__name__ } Problem encountered sending gRPC message (rqId: { request_id } ): { e } '
501+ )
466502 else :
467503 if resp :
468504 if not self ._check_status_response (resp .status , request_id ):
469505 return None
470506 resp_dict = MessageToDict (resp , preserving_proto_field_name = True ) # Convert gRPC response to a dict
471507 return resp_dict
472508 return None
509+
510+
473511#
474512# End of ScanossGrpc Class
475513#
0 commit comments