scanoss 1.25.1__py3-none-any.whl → 1.26.0__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.
@@ -1,7 +1,7 @@
1
1
  """
2
2
  SPDX-License-Identifier: MIT
3
3
 
4
- Copyright (c) 2024, SCANOSS
4
+ Copyright (c) 2025, SCANOSS
5
5
 
6
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  of this software and associated documentation files (the "Software"), to deal
@@ -22,13 +22,11 @@ SPDX-License-Identifier: MIT
22
22
  THE SOFTWARE.
23
23
  """
24
24
 
25
- import json
26
- import os.path
27
25
  from abc import abstractmethod
28
26
  from enum import Enum
29
27
  from typing import Any, Callable, Dict, List
30
28
 
31
- from ..scanossbase import ScanossBase
29
+ from .inspect_base import InspectBase
32
30
  from .utils.license_utils import LicenseUtil
33
31
 
34
32
 
@@ -45,34 +43,11 @@ class PolicyStatus(Enum):
45
43
  SUCCESS = 0
46
44
  FAIL = 1
47
45
  ERROR = 2
48
-
49
-
50
46
  #
51
47
  # End of PolicyStatus Class
52
48
  #
53
49
 
54
-
55
- class ComponentID(Enum):
56
- """
57
- Enumeration representing different types of software components.
58
-
59
- Attributes:
60
- FILE (str): Represents a file component (value: "file").
61
- SNIPPET (str): Represents a code snippet component (value: "snippet").
62
- DEPENDENCY (str): Represents a dependency component (value: "dependency").
63
- """
64
-
65
- FILE = 'file'
66
- SNIPPET = 'snippet'
67
- DEPENDENCY = 'dependency'
68
-
69
-
70
- #
71
- # End of ComponentID Class
72
- #
73
-
74
-
75
- class PolicyCheck(ScanossBase):
50
+ class PolicyCheck(InspectBase):
76
51
  """
77
52
  A base class for implementing various software policy checks.
78
53
 
@@ -83,27 +58,24 @@ class PolicyCheck(ScanossBase):
83
58
  VALID_FORMATS (set): A set of valid output formats ('md', 'json').
84
59
 
85
60
  Inherits from:
86
- ScanossBase: A base class providing common functionality for SCANOSS-related operations.
61
+ InspectBase: A base class providing common functionality for SCANOSS-related operations.
87
62
  """
88
-
89
63
  VALID_FORMATS = {'md', 'json', 'jira_md'}
90
-
91
- def __init__( # noqa: PLR0913
92
- self,
93
- debug: bool = False,
94
- trace: bool = True,
95
- quiet: bool = False,
96
- filepath: str = None,
97
- format_type: str = None,
98
- status: str = None,
99
- output: str = None,
100
- name: str = None,
64
+ def __init__( # noqa: PLR0913
65
+ self,
66
+ debug: bool = False,
67
+ trace: bool = True,
68
+ quiet: bool = False,
69
+ filepath: str = None,
70
+ format_type: str = None,
71
+ status: str = None,
72
+ output: str = None,
73
+ name: str = None,
101
74
  ):
102
- super().__init__(debug, trace, quiet)
75
+ super().__init__(debug, trace, quiet, filepath, output)
103
76
  self.license_util = LicenseUtil()
104
77
  self.filepath = filepath
105
78
  self.name = name
106
- self.output = output
107
79
  self.format_type = format_type
108
80
  self.status = status
109
81
  self.results = self._load_input_file()
@@ -166,147 +138,6 @@ class PolicyCheck(ScanossBase):
166
138
  """
167
139
  pass
168
140
 
169
- @abstractmethod
170
- def _get_components(self):
171
- """
172
- Retrieve and process components from the preloaded results.
173
-
174
- This method performs the following steps:
175
- 1. Checks if the results have been previously loaded (self.results).
176
- 2. Extracts and processes components from the loaded results.
177
-
178
- :return: A list of processed components, or None if an error occurred during any step.
179
-
180
- Possible reasons for returning None include:
181
- - Results not loaded (self.results is None)
182
- - Failure to extract components from the results
183
-
184
- Note:
185
- - This method assumes that the results have been previously loaded and stored in self.results.
186
- - Implementations must extract components (e.g. via `_get_components_data`,
187
- `_get_dependencies_data`, or other helpers).
188
- - If `self.results` is `None`, simply return `None`.
189
- """
190
- pass
191
-
192
-
193
- def _append_component(
194
- self, components: Dict[str, Any], new_component: Dict[str, Any], id: str, status: str
195
- ) -> Dict[str, Any]:
196
- """
197
- Append a new component to the component's dictionary.
198
-
199
- This function creates a new entry in the components dictionary for the given component,
200
- or updates an existing entry if the component already exists. It also processes the
201
- licenses associated with the component.
202
-
203
- :param components: The existing dictionary of components
204
- :param new_component: The new component to be added or updated
205
- :param id: The new component ID
206
- :param status: The new component status
207
- :return: The updated components dictionary
208
- """
209
- # Determine the component key and purl based on component type
210
- if id in [ComponentID.FILE.value, ComponentID.SNIPPET.value]:
211
- purl = new_component['purl'][0] # Take the first purl for these component types
212
- else:
213
- purl = new_component['purl']
214
-
215
- component_key = f'{purl}@{new_component["version"]}'
216
- components[component_key] = {
217
- 'purl': purl,
218
- 'version': new_component['version'],
219
- 'licenses': {},
220
- 'status': status,
221
- }
222
- if not new_component.get('licenses'):
223
- self.print_debug(f'WARNING: Results missing licenses. Skipping: {new_component}')
224
- return components
225
- # Process licenses for this component
226
- for license_item in new_component['licenses']:
227
- if license_item.get('name'):
228
- spdxid = license_item['name']
229
- components[component_key]['licenses'][spdxid] = {
230
- 'spdxid': spdxid,
231
- 'copyleft': self.license_util.is_copyleft(spdxid),
232
- 'url': self.license_util.get_spdx_url(spdxid),
233
- }
234
- return components
235
-
236
- def _get_components_data(self, results: Dict[str, Any], components: Dict[str, Any]) -> Dict[str, Any]:
237
- """
238
- Extract and process file and snippet components from results.
239
-
240
- :param results: A dictionary containing the raw results of a component scan
241
- :param components: Existing components dictionary to update
242
- :return: Updated components dictionary with file and snippet data
243
- """
244
- for component in results.values():
245
- for c in component:
246
- component_id = c.get('id')
247
- if not component_id:
248
- self.print_debug(f'WARNING: Result missing id. Skipping: {c}')
249
- continue
250
- ## Skip dependency
251
- if component_id == ComponentID.DEPENDENCY.value:
252
- continue
253
- status = c.get('status')
254
- if not status:
255
- self.print_debug(f'WARNING: Result missing status. Skipping: {c}')
256
- continue
257
- if component_id in [ComponentID.FILE.value, ComponentID.SNIPPET.value]:
258
- if not c.get('purl'):
259
- self.print_debug(f'WARNING: Result missing purl. Skipping: {c}')
260
- continue
261
- if len(c.get('purl')) <= 0:
262
- self.print_debug(f'WARNING: Result missing purls. Skipping: {c}')
263
- continue
264
- if not c.get('version'):
265
- self.print_msg(f'WARNING: Result missing version. Skipping: {c}')
266
- continue
267
- component_key = f'{c["purl"][0]}@{c["version"]}'
268
- if component_key not in components:
269
- components = self._append_component(components, c, component_id, status)
270
- # End component loop
271
- # End components loop
272
- return components
273
-
274
- def _get_dependencies_data(self, results: Dict[str, Any], components: Dict[str, Any]) -> Dict[str, Any]:
275
- """
276
- Extract and process dependency components from results.
277
-
278
- :param results: A dictionary containing the raw results of a component scan
279
- :param components: Existing components dictionary to update
280
- :return: Updated components dictionary with dependency data
281
- """
282
- for component in results.values():
283
- for c in component:
284
- component_id = c.get('id')
285
- if not component_id:
286
- self.print_debug(f'WARNING: Result missing id. Skipping: {c}')
287
- continue
288
- status = c.get('status')
289
- if not status:
290
- self.print_debug(f'WARNING: Result missing status. Skipping: {c}')
291
- continue
292
- if component_id == ComponentID.DEPENDENCY.value:
293
- if c.get('dependencies') is None:
294
- continue
295
- for dependency in c['dependencies']:
296
- if not dependency.get('purl'):
297
- self.print_debug(f'WARNING: Dependency result missing purl. Skipping: {dependency}')
298
- continue
299
- if not dependency.get('version'):
300
- self.print_msg(f'WARNING: Dependency result missing version. Skipping: {dependency}')
301
- continue
302
- component_key = f'{dependency["purl"]}@{dependency["version"]}'
303
- if component_key not in components:
304
- components = self._append_component(components, dependency, component_id, status)
305
- # End dependency loop
306
- # End component loop
307
- # End of result loop
308
- return components
309
-
310
141
  def generate_table(self, headers, rows, centered_columns=None):
311
142
  """
312
143
  Generate a Markdown table.
@@ -393,24 +224,6 @@ class PolicyCheck(ScanossBase):
393
224
  self.print_stderr(f'ERROR: Invalid format "{self.format_type}". Valid formats are: {valid_formats_str}')
394
225
  return False
395
226
  return True
396
-
397
- def _load_input_file(self):
398
- """
399
- Load the result.json file
400
-
401
- Returns:
402
- Dict[str, Any]: The parsed JSON data
403
- """
404
- if not os.path.exists(self.filepath):
405
- self.print_stderr(f'ERROR: The file "{self.filepath}" does not exist.')
406
- return None
407
- with open(self.filepath, 'r') as jsonfile:
408
- try:
409
- return json.load(jsonfile)
410
- except Exception as e:
411
- self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
412
- return None
413
-
414
227
  #
415
228
  # End of PolicyCheck Class
416
- #
229
+ #
@@ -1,7 +1,7 @@
1
1
  """
2
2
  SPDX-License-Identifier: MIT
3
3
 
4
- Copyright (c) 2024, SCANOSS
4
+ Copyright (c) 2025, SCANOSS
5
5
 
6
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  of this software and associated documentation files (the "Software"), to deal
@@ -79,7 +79,14 @@ class UndeclaredComponent(PolicyCheck):
79
79
  undeclared_components = []
80
80
  for component in components:
81
81
  if component['status'] == 'pending':
82
+ # Remove unused keys
82
83
  del component['status']
84
+ del component['count']
85
+ del component['declared']
86
+ del component['undeclared']
87
+ for lic in component['licenses']:
88
+ lic.pop('count', None) # None is default value if key doesn't exist
89
+ lic.pop('source', None) # None is default value if key doesn't exist
83
90
  undeclared_components.append(component)
84
91
  # end component loop
85
92
  return undeclared_components
@@ -148,12 +155,14 @@ class UndeclaredComponent(PolicyCheck):
148
155
  :param components: List of undeclared components
149
156
  :return: Dictionary with formatted JSON details and summary
150
157
  """
158
+ # Use component grouped by licenses to generate the summary
159
+ component_licenses = self._group_components_by_license(components)
151
160
  details = {}
152
161
  if len(components) > 0:
153
162
  details = {'components': components}
154
163
  return {
155
164
  'details': f'{json.dumps(details, indent=2)}\n',
156
- 'summary': self._get_summary(components),
165
+ 'summary': self._get_summary(component_licenses),
157
166
  }
158
167
 
159
168
  def _markdown(self, components: list) -> Dict[str, Any]:
@@ -163,15 +172,15 @@ class UndeclaredComponent(PolicyCheck):
163
172
  :param components: List of undeclared components
164
173
  :return: Dictionary with formatted Markdown details and summary
165
174
  """
166
- headers = ['Component', 'Version', 'License']
175
+ headers = ['Component', 'License']
167
176
  rows: [[]] = []
168
177
  # TODO look at using SpdxLite license name lookup method
169
- for component in components:
170
- licenses = ' - '.join(lic.get('spdxid', 'Unknown') for lic in component['licenses'])
171
- rows.append([component['purl'], component['version'], licenses])
178
+ component_licenses = self._group_components_by_license(components)
179
+ for component in component_licenses:
180
+ rows.append([component.get('purl'), component.get('license')])
172
181
  return {
173
182
  'details': f'### Undeclared components\n{self.generate_table(headers, rows)}\n',
174
- 'summary': self._get_summary(components),
183
+ 'summary': self._get_summary(component_licenses),
175
184
  }
176
185
 
177
186
  def _jira_markdown(self, components: list) -> Dict[str, Any]:
@@ -181,15 +190,15 @@ class UndeclaredComponent(PolicyCheck):
181
190
  :param components: List of undeclared components
182
191
  :return: Dictionary with formatted Markdown details and summary
183
192
  """
184
- headers = ['Component', 'Version', 'License']
193
+ headers = ['Component', 'License']
185
194
  rows: [[]] = []
186
195
  # TODO look at using SpdxLite license name lookup method
187
- for component in components:
188
- licenses = ' - '.join(lic.get('spdxid', 'Unknown') for lic in component['licenses'])
189
- rows.append([component['purl'], component['version'], licenses])
196
+ component_licenses = self._group_components_by_license(components)
197
+ for component in component_licenses:
198
+ rows.append([component.get('purl'), component.get('license')])
190
199
  return {
191
200
  'details': f'{self.generate_jira_table(headers, rows)}',
192
- 'summary': self._get_jira_summary(components),
201
+ 'summary': self._get_jira_summary(component_licenses),
193
202
  }
194
203
 
195
204
  def _get_unique_components(self, components: list) -> list:
@@ -254,10 +263,37 @@ class UndeclaredComponent(PolicyCheck):
254
263
  # Extract file and snippet components
255
264
  components = self._get_components_data(self.results, components)
256
265
  # Convert to list and process licenses
257
- results_list = list(components.values())
258
- for component in results_list:
259
- component['licenses'] = list(component['licenses'].values())
260
- return results_list
266
+ return self._convert_components_to_list(components)
267
+
268
+ def _group_components_by_license(self,components):
269
+ """
270
+ Groups components by their unique component-license pairs.
271
+
272
+ This method processes a list of components and creates unique entries for each
273
+ component-license combination. If a component has multiple licenses, it will create
274
+ separate entries for each license.
275
+
276
+ Args:
277
+ components: A list of component dictionaries. Each component should have:
278
+ - purl: Package URL identifying the component
279
+ - licenses: List of license dictionaries, each containing:
280
+ - spdxid: SPDX identifier for the license (optional)
281
+
282
+ Returns:
283
+ list: A list of dictionaries, each containing:
284
+ - purl: The component's package URL
285
+ - license: The SPDX identifier of the license (or 'Unknown' if not provided)
286
+ """
287
+ component_licenses: dict = {}
288
+ for component in components:
289
+ for lic in component['licenses']:
290
+ spdxid = lic.get('spdxid', 'Unknown')
291
+ key = f'{component["purl"]}-{spdxid}'
292
+ component_licenses[key] = {
293
+ 'purl': component['purl'],
294
+ 'license': spdxid,
295
+ }
296
+ return list(component_licenses.values())
261
297
 
262
298
  def run(self):
263
299
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scanoss
3
- Version: 1.25.1
3
+ Version: 1.26.0
4
4
  Summary: Simple Python library to leverage the SCANOSS APIs
5
5
  Home-page: https://scanoss.com
6
6
  Author: SCANOSS
@@ -4,8 +4,8 @@ protoc_gen_swagger/options/annotations_pb2.py,sha256=b25EDD6gssUWnFby9gxgcpLIROT
4
4
  protoc_gen_swagger/options/annotations_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
5
5
  protoc_gen_swagger/options/openapiv2_pb2.py,sha256=vYElGp8E1vGHszvWqX97zNG9GFJ7u2QcdK9ouq0XdyI,14939
6
6
  protoc_gen_swagger/options/openapiv2_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
7
- scanoss/__init__.py,sha256=zoc5i9zIZ6i9BDBRPHBqmnNWz1ctPZAr9pCu2Pamvlg,1146
8
- scanoss/cli.py,sha256=SAB0xuHjEEw20YtYAPSZwrQaVf4JUsm8NRcVArMzd4U,69099
7
+ scanoss/__init__.py,sha256=hpFJV75fvMqM81JQ_5NLWJnNyG498vCtV_hWttywCao,1146
8
+ scanoss/cli.py,sha256=yjK4oawNzecarQYYlkElOiHFDDAZx_zKdSXf_gQvqXk,72678
9
9
  scanoss/components.py,sha256=b0R9DdKuXqyQiw5nZZwjQ6NJXBr1U9gyx1RI2FP9ozA,14511
10
10
  scanoss/constants.py,sha256=FWCZG8gQputKwV7XwvW1GuwDXL4wDLQyVRGdwygg578,320
11
11
  scanoss/cryptography.py,sha256=Q39MOCscP-OFvrnPXaPOMFFkc8OKnf3mC3SgZYEtCog,9407
@@ -57,14 +57,17 @@ scanoss/api/vulnerabilities/__init__.py,sha256=IFrDk_DTJgKSZmmU-nuLXuq_s8sQZlrSC
57
57
  scanoss/api/vulnerabilities/v2/__init__.py,sha256=IFrDk_DTJgKSZmmU-nuLXuq_s8sQZlrSCHhIDMJT4r0,1122
58
58
  scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py,sha256=CFhF80av8tenGvn9AIsGEtRJPuV2dC_syA5JLZb2lDw,5464
59
59
  scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py,sha256=HlS4k4Zmx6RIAqaO9I96jD-eyF5yU6Xx04pVm7pdqOg,6864
60
- scanoss/data/build_date.txt,sha256=DgM1g6ceMq5HR3AeQ6XArgq0PfNCFKqT1ujuejYzZaI,40
60
+ scanoss/data/build_date.txt,sha256=35_MQhM0yl3QDm4UJRvKeRtlQPWALTKWb429m0XN5go,40
61
61
  scanoss/data/scanoss-settings-schema.json,sha256=ClkRYAkjAN0Sk704G8BE_Ok006oQ6YnIGmX84CF8h9w,8798
62
62
  scanoss/data/spdx-exceptions.json,sha256=s7UTYxC7jqQXr11YBlIWYCNwN6lRDFTR33Y8rpN_dA4,17953
63
63
  scanoss/data/spdx-licenses.json,sha256=A6Z0q82gaTLtnopBfzeIVZjJFxkdRW1g2TuumQc-lII,228794
64
- scanoss/inspection/__init__.py,sha256=0hjb5ktavp7utJzFhGMPImPaZiHWgilM2HwvTp5lXJE,1122
65
- scanoss/inspection/copyleft.py,sha256=lOOq3-HwIvYVd0a_A0o2pG-m0rtFBHxXjEBLtHpRJto,8671
66
- scanoss/inspection/policy_check.py,sha256=ldUcKBXD74P4ldO417GP5PqJHE-BagDPBDOK-e3KZco,16009
67
- scanoss/inspection/undeclared_component.py,sha256=7WRLdDSf_XWtDMlvKRo3nvBy_041gQ2xR2zXqFCzmz0,11155
64
+ scanoss/inspection/__init__.py,sha256=D4C0lWLuNp8k_BjQZEc07WZcUgAvriVwQWOk063b0ZU,1122
65
+ scanoss/inspection/component_summary.py,sha256=gSLTsyGVZ4R1-Pqo7-mcb4AbdGjcNVhHs_Jm5_GdFGU,3443
66
+ scanoss/inspection/copyleft.py,sha256=oM9xSRovDI4TPansDKg2Qa_omo09nr78-vAYcEgxyJY,8854
67
+ scanoss/inspection/inspect_base.py,sha256=Q943VydELN1fzV4tNQQZGBlOd4T_v8chCDoOoJgHRMM,16086
68
+ scanoss/inspection/license_summary.py,sha256=bq81K8N40b9CaZGawBku_Kxcz31lLhHW173v7NJ4S4s,6305
69
+ scanoss/inspection/policy_check.py,sha256=NS39dvePZbpusGRnEUKN9JFiMdSyva0msFE6On6bK8Q,8329
70
+ scanoss/inspection/undeclared_component.py,sha256=4mo1CMBho_I-58j5H0x8A-9ZGVvrGsNzBDel48LZ8Z0,12866
68
71
  scanoss/inspection/utils/license_utils.py,sha256=Zb6QLmVJb86lKCwZyBsmwakyAtY1SXa54kUyyKmWMqA,5093
69
72
  scanoss/scanners/__init__.py,sha256=D4C0lWLuNp8k_BjQZEc07WZcUgAvriVwQWOk063b0ZU,1122
70
73
  scanoss/scanners/container_scanner.py,sha256=leP4roes6B9B95F49mJ0P_F8WcKCQkvJgk9azWyJrjg,16294
@@ -76,9 +79,9 @@ scanoss/utils/abstract_presenter.py,sha256=teiDTxBj5jBMCk2T8i4l1BJPf_u4zBLWrtCTF
76
79
  scanoss/utils/crc64.py,sha256=TMrwQimSdE6imhFOUL7oAG6Kxu-8qMpGWMuMg8QpSVs,3169
77
80
  scanoss/utils/file.py,sha256=62cA9a17TU9ZvfA3FY5HY4-QOajJeSrc8S6xLA_f-3M,2980
78
81
  scanoss/utils/simhash.py,sha256=6iu8DOcecPAY36SZjCOzrrLMT9oIE7-gI6QuYwUQ7B0,5793
79
- scanoss-1.25.1.dist-info/licenses/LICENSE,sha256=LLUaXoiyOroIbr5ubAyrxBOwSRLTm35ETO2FmLpy8QQ,1074
80
- scanoss-1.25.1.dist-info/METADATA,sha256=yIG7_hq018z-_68-M0jwt6hSb3oK5yol9Wku4k3m2V4,6060
81
- scanoss-1.25.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
82
- scanoss-1.25.1.dist-info/entry_points.txt,sha256=Uy28xnaDL5KQ7V77sZD5VLDXPNxYYzSr5tsqtiXVzAs,48
83
- scanoss-1.25.1.dist-info/top_level.txt,sha256=V11PrQ6Pnrc-nDF9xnisnJ8e6-i7HqSIKVNqduRWcL8,27
84
- scanoss-1.25.1.dist-info/RECORD,,
82
+ scanoss-1.26.0.dist-info/licenses/LICENSE,sha256=LLUaXoiyOroIbr5ubAyrxBOwSRLTm35ETO2FmLpy8QQ,1074
83
+ scanoss-1.26.0.dist-info/METADATA,sha256=cj8e2LBPd_4muBEw4dz9d-YMP9bJM9zfKD-SR_iKZmc,6060
84
+ scanoss-1.26.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
85
+ scanoss-1.26.0.dist-info/entry_points.txt,sha256=Uy28xnaDL5KQ7V77sZD5VLDXPNxYYzSr5tsqtiXVzAs,48
86
+ scanoss-1.26.0.dist-info/top_level.txt,sha256=V11PrQ6Pnrc-nDF9xnisnJ8e6-i7HqSIKVNqduRWcL8,27
87
+ scanoss-1.26.0.dist-info/RECORD,,