scanoss 1.20.4__py3-none-any.whl → 1.20.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
scanoss/scanossgrpc.py CHANGED
@@ -22,50 +22,58 @@ SPDX-License-Identifier: MIT
22
22
  THE SOFTWARE.
23
23
  """
24
24
 
25
+ import concurrent.futures
26
+ import json
25
27
  import os
26
28
  import uuid
29
+ from urllib.parse import urlparse
27
30
 
28
31
  import grpc
29
- import json
30
-
31
32
  from google.protobuf.json_format import MessageToDict, ParseDict
32
33
  from pypac.parser import PACFile
33
34
  from pypac.resolver import ProxyResolver
34
- from urllib.parse import urlparse
35
35
 
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
+ )
47
44
  from .api.components.v2.scanoss_components_pb2 import (
48
45
  CompSearchRequest,
49
46
  CompSearchResponse,
50
47
  CompVersionRequest,
51
48
  CompVersionResponse,
52
49
  )
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
53
55
  from .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
54
61
  from .scanossbase import ScanossBase
55
- from . import __version__
56
62
 
57
63
  DEFAULT_URL = 'https://api.osskb.org' # default free service URL
58
64
  DEFAULT_URL2 = 'https://api.scanoss.com' # default premium service URL
59
65
  SCANOSS_GRPC_URL = os.environ.get('SCANOSS_GRPC_URL') if os.environ.get('SCANOSS_GRPC_URL') else DEFAULT_URL
60
66
  SCANOSS_API_KEY = os.environ.get('SCANOSS_API_KEY') if os.environ.get('SCANOSS_API_KEY') else ''
61
67
 
68
+ MAX_CONCURRENT_REQUESTS = 5
69
+
62
70
 
63
71
  class ScanossGrpc(ScanossBase):
64
72
  """
65
73
  Client for gRPC functionality
66
74
  """
67
75
 
68
- def __init__(
76
+ def __init__( # noqa: PLR0913, PLR0915
69
77
  self,
70
78
  url: str = None,
71
79
  debug: bool = False,
@@ -78,6 +86,7 @@ class ScanossGrpc(ScanossBase):
78
86
  proxy: str = None,
79
87
  grpc_proxy: str = None,
80
88
  pac: PACFile = None,
89
+ req_headers: dict = None,
81
90
  ):
82
91
  """
83
92
 
@@ -95,22 +104,29 @@ class ScanossGrpc(ScanossBase):
95
104
  grpc_proxy='http://<ip>:<port>'
96
105
  """
97
106
  super().__init__(debug, trace, quiet)
98
- self.url = url if url else SCANOSS_GRPC_URL
107
+ self.url = url
99
108
  self.api_key = api_key if api_key else SCANOSS_API_KEY
100
- if self.api_key and not url and not os.environ.get('SCANOSS_GRPC_URL'):
101
- self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium
102
- self.url = self.url.lower()
103
- self.orig_url = self.url # Used for proxy lookup
104
109
  self.timeout = timeout
105
110
  self.proxy = proxy
106
111
  self.grpc_proxy = grpc_proxy
107
112
  self.pac = pac
113
+ self.req_headers = req_headers
108
114
  self.metadata = []
115
+
116
+
109
117
  if self.api_key:
110
118
  self.metadata.append(('x-api-key', api_key)) # Set API key if we have one
111
119
  if ver_details:
112
120
  self.metadata.append(('x-scanoss-client', ver_details))
113
121
  self.metadata.append(('user-agent', f'scanoss-py/{__version__}'))
122
+ self.load_generic_headers()
123
+
124
+ self.url = url if url else SCANOSS_GRPC_URL
125
+ if self.api_key and not url and not os.environ.get('SCANOSS_GRPC_URL'):
126
+ self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium
127
+ self.url = self.url.lower()
128
+ self.orig_url = self.url # Used for proxy lookup
129
+
114
130
  secure = True if self.url.startswith('https:') else False # Is it a secure connection?
115
131
  if self.url.startswith('http'):
116
132
  u = urlparse(self.url)
@@ -222,31 +238,54 @@ class ScanossGrpc(ScanossBase):
222
238
  :return: Server response or None
223
239
  """
224
240
  if not dependencies:
225
- self.print_stderr(f'ERROR: No message supplied to send to gRPC service.')
241
+ self.print_stderr('ERROR: No message supplied to send to gRPC service.')
226
242
  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.')
243
+
244
+ files_json = dependencies.get('files')
245
+
246
+ if files_json is None or len(files_json) == 0:
247
+ self.print_stderr('ERROR: No dependency data supplied to send to gRPC service.')
248
+ return None
249
+
250
+ def process_file(file):
251
+ request_id = str(uuid.uuid4())
252
+ try:
253
+ file_request = {'files': [file]}
254
+
255
+ request = ParseDict(file_request, DependencyRequest())
256
+ request.depth = depth
257
+ metadata = self.metadata[:]
258
+ metadata.append(('x-request-id', request_id))
259
+ self.print_debug(f'Sending dependency data for decoration (rqId: {request_id})...')
260
+ resp = self.dependencies_stub.GetDependencies(request, metadata=metadata, timeout=self.timeout)
261
+
262
+ return MessageToDict(resp, preserving_proto_field_name=True)
263
+ except Exception as e:
264
+ self.print_stderr(
265
+ f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}'
266
+ )
233
267
  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
268
+
269
+ all_responses = []
270
+ with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_CONCURRENT_REQUESTS) as executor:
271
+ future_to_file = {executor.submit(process_file, file): file for file in files_json}
272
+
273
+ for future in concurrent.futures.as_completed(future_to_file):
274
+ response = future.result()
275
+ if response:
276
+ all_responses.append(response)
277
+
278
+ SUCCESS_STATUS = 'SUCCESS'
279
+
280
+ merged_response = {'files': [], 'status': {'status': SUCCESS_STATUS, 'message': 'Success'}}
281
+ for response in all_responses:
282
+ if response:
283
+ if 'files' in response and len(response['files']) > 0:
284
+ merged_response['files'].append(response['files'][0])
285
+ # Overwrite the status if the any of the responses was not successful
286
+ if 'status' in response and response['status']['status'] != SUCCESS_STATUS:
287
+ merged_response['status'] = response['status']
288
+ return merged_response
250
289
 
251
290
  def get_crypto_json(self, purls: dict) -> dict:
252
291
  """
@@ -255,7 +294,7 @@ class ScanossGrpc(ScanossBase):
255
294
  :return: Server response or None
256
295
  """
257
296
  if not purls:
258
- self.print_stderr(f'ERROR: No message supplied to send to gRPC service.')
297
+ self.print_stderr('ERROR: No message supplied to send to gRPC service.')
259
298
  return None
260
299
  request_id = str(uuid.uuid4())
261
300
  resp: AlgorithmResponse
@@ -285,7 +324,7 @@ class ScanossGrpc(ScanossBase):
285
324
  :return: Server response or None
286
325
  """
287
326
  if not purls:
288
- self.print_stderr(f'ERROR: No message supplied to send to gRPC service.')
327
+ self.print_stderr('ERROR: No message supplied to send to gRPC service.')
289
328
  return None
290
329
  request_id = str(uuid.uuid4())
291
330
  resp: VulnerabilityResponse
@@ -315,7 +354,7 @@ class ScanossGrpc(ScanossBase):
315
354
  :return: Server response or None
316
355
  """
317
356
  if not purls:
318
- self.print_stderr(f'ERROR: No message supplied to send to gRPC service.')
357
+ self.print_stderr('ERROR: No message supplied to send to gRPC service.')
319
358
  return None
320
359
  request_id = str(uuid.uuid4())
321
360
  resp: SemgrepResponse
@@ -345,7 +384,7 @@ class ScanossGrpc(ScanossBase):
345
384
  :return: Server response or None
346
385
  """
347
386
  if not search:
348
- self.print_stderr(f'ERROR: No message supplied to send to gRPC service.')
387
+ self.print_stderr('ERROR: No message supplied to send to gRPC service.')
349
388
  return None
350
389
  request_id = str(uuid.uuid4())
351
390
  resp: CompSearchResponse
@@ -375,7 +414,7 @@ class ScanossGrpc(ScanossBase):
375
414
  :return: Server response or None
376
415
  """
377
416
  if not search:
378
- self.print_stderr(f'ERROR: No message supplied to send to gRPC service.')
417
+ self.print_stderr('ERROR: No message supplied to send to gRPC service.')
379
418
  return None
380
419
  request_id = str(uuid.uuid4())
381
420
  resp: CompVersionResponse
@@ -404,6 +443,10 @@ class ScanossGrpc(ScanossBase):
404
443
  :param status_response: Status Response
405
444
  :return: True if successful, False otherwise
406
445
  """
446
+
447
+ SUCCEDED_WITH_WARNINGS_STATUS_CODE = 2
448
+ FAILED_STATUS_CODE = 3
449
+
407
450
  if not status_response:
408
451
  self.print_stderr(f'Warning: No status response supplied (rqId: {request_id}). Assuming it was ok.')
409
452
  return True
@@ -411,11 +454,11 @@ class ScanossGrpc(ScanossBase):
411
454
  status_code: StatusCode = status_response.status
412
455
  if status_code > 1:
413
456
  ret_val = False # default to failed
414
- msg = "Unsuccessful"
415
- if status_code == 2:
416
- msg = "Succeeded with warnings"
457
+ msg = 'Unsuccessful'
458
+ if status_code == SUCCEDED_WITH_WARNINGS_STATUS_CODE:
459
+ msg = 'Succeeded with warnings'
417
460
  ret_val = True # No need to fail as it succeeded with warnings
418
- elif status_code == 3:
461
+ elif status_code == FAILED_STATUS_CODE:
419
462
  msg = 'Failed with warnings'
420
463
  self.print_stderr(f'{msg} (rqId: {request_id} - status: {status_code}): {status_response.message}')
421
464
  return ret_val
@@ -428,10 +471,10 @@ class ScanossGrpc(ScanossBase):
428
471
  :param self:
429
472
  """
430
473
  if self.grpc_proxy:
431
- self.print_debug(f'Setting GRPC (grpc_proxy) proxy...')
474
+ self.print_debug('Setting GRPC (grpc_proxy) proxy...')
432
475
  os.environ['grpc_proxy'] = self.grpc_proxy
433
476
  elif self.proxy:
434
- self.print_debug(f'Setting GRPC (http_proxy/https_proxy) proxies...')
477
+ self.print_debug('Setting GRPC (http_proxy/https_proxy) proxies...')
435
478
  os.environ['http_proxy'] = self.proxy
436
479
  os.environ['https_proxy'] = self.proxy
437
480
  elif self.pac:
@@ -450,7 +493,7 @@ class ScanossGrpc(ScanossBase):
450
493
  :return: Server response or None
451
494
  """
452
495
  if not purls:
453
- self.print_stderr(f'ERROR: No message supplied to send to gRPC service.')
496
+ self.print_stderr('ERROR: No message supplied to send to gRPC service.')
454
497
  return None
455
498
  request_id = str(uuid.uuid4())
456
499
  resp: ProvenanceResponse
@@ -461,8 +504,9 @@ class ScanossGrpc(ScanossBase):
461
504
  self.print_debug(f'Sending data for provenance decoration (rqId: {request_id})...')
462
505
  resp = self.provenance_stub.GetComponentProvenance(request, metadata=metadata, timeout=self.timeout)
463
506
  except Exception as e:
464
- self.print_stderr(f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message '
465
- f'(rqId: {request_id}): {e}')
507
+ self.print_stderr(
508
+ f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}'
509
+ )
466
510
  else:
467
511
  if resp:
468
512
  if not self._check_status_response(resp.status, request_id):
@@ -470,6 +514,21 @@ class ScanossGrpc(ScanossBase):
470
514
  resp_dict = MessageToDict(resp, preserving_proto_field_name=True) # Convert gRPC response to a dict
471
515
  return resp_dict
472
516
  return None
517
+
518
+ def load_generic_headers(self):
519
+ """
520
+ Adds custom headers from req_headers to metadata.
521
+
522
+ If x-api-key is present and no URL is configured (directly or via
523
+ environment), sets URL to the premium endpoint (DEFAULT_URL2).
524
+ """
525
+ if self.req_headers: # Load generic headers
526
+ for key, value in self.req_headers.items():
527
+ if key == 'x-api-key': # Set premium URL if x-api-key header is set
528
+ if not self.url and not os.environ.get('SCANOSS_GRPC_URL'):
529
+ self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium
530
+ self.api_key = value
531
+ self.metadata.append((key, value))
473
532
  #
474
533
  # End of ScanossGrpc Class
475
534
  #