scanoss 1.26.0__py3-none-any.whl → 1.26.2__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/__init__.py CHANGED
@@ -22,4 +22,4 @@ SPDX-License-Identifier: MIT
22
22
  THE SOFTWARE.
23
23
  """
24
24
 
25
- __version__ = '1.26.0'
25
+ __version__ = '1.26.2'
@@ -1 +1 @@
1
- date: 20250620142757, utime: 1750429677
1
+ date: 20250624181246, utime: 1750788766
@@ -35,12 +35,18 @@ class ComponentSummary(InspectBase):
35
35
  :param components: List of all components
36
36
  :return: Dict with license summary information
37
37
  """
38
+ # A component is considered unique by its combination of PURL (Package URL) and license
39
+ component_licenses = self._group_components_by_license(scan_components)
40
+ total_components = len(component_licenses)
41
+ # Get undeclared components
42
+ undeclared_components = len([c for c in component_licenses if c['status'] == 'pending'])
43
+
38
44
  components: list = []
39
- undeclared_components = 0
40
- total_components = 0
45
+ total_undeclared_files = 0
46
+ total_files_detected = 0
41
47
  for component in scan_components:
42
- total_components += component['count']
43
- undeclared_components += component['undeclared']
48
+ total_files_detected += component['count']
49
+ total_undeclared_files += component['undeclared']
44
50
  components.append({
45
51
  'purl': component['purl'],
46
52
  'version': component['version'],
@@ -50,10 +56,13 @@ class ComponentSummary(InspectBase):
50
56
  })
51
57
  ## End for loop components
52
58
  return {
53
- 'components': components,
54
- 'total': total_components,
55
- 'undeclared': undeclared_components,
56
- 'declared': total_components - undeclared_components,
59
+ "components": component_licenses,
60
+ 'totalComponents': total_components,
61
+ 'undeclaredComponents': undeclared_components,
62
+ 'declaredComponents': total_components - undeclared_components,
63
+ 'totalFilesDetected': total_files_detected,
64
+ 'totalFilesUndeclared': total_undeclared_files,
65
+ 'totalFilesDeclared': total_files_detected - total_undeclared_files,
57
66
  }
58
67
 
59
68
  def _get_components(self):
@@ -78,12 +78,14 @@ class Copyleft(PolicyCheck):
78
78
  :param components: List of components with copyleft licenses
79
79
  :return: Dictionary with formatted JSON details and summary
80
80
  """
81
+ # A component is considered unique by its combination of PURL (Package URL) and license
82
+ component_licenses = self._group_components_by_license(components)
81
83
  details = {}
82
84
  if len(components) > 0:
83
85
  details = {'components': components}
84
86
  return {
85
87
  'details': f'{json.dumps(details, indent=2)}\n',
86
- 'summary': f'{len(components)} component(s) with copyleft licenses were found.\n',
88
+ 'summary': f'{len(component_licenses)} component(s) with copyleft licenses were found.\n',
87
89
  }
88
90
 
89
91
  def _markdown(self, components: list) -> Dict[str, Any]:
@@ -93,24 +95,24 @@ class Copyleft(PolicyCheck):
93
95
  :param components: List of components with copyleft licenses
94
96
  :return: Dictionary with formatted Markdown details and summary
95
97
  """
96
- headers = ['Component', 'Version', 'License', 'URL', 'Copyleft']
98
+ # A component is considered unique by its combination of PURL (Package URL) and license
99
+ component_licenses = self._group_components_by_license(components)
100
+ headers = ['Component', 'License', 'URL', 'Copyleft']
97
101
  centered_columns = [1, 4]
98
102
  rows: [[]] = []
99
- for component in components:
100
- for lic in component['licenses']:
103
+ for comp_lic_item in component_licenses:
101
104
  row = [
102
- component['purl'],
103
- component['version'],
104
- lic['spdxid'],
105
- lic['url'],
106
- 'YES' if lic['copyleft'] else 'NO',
105
+ comp_lic_item['purl'],
106
+ comp_lic_item['spdxid'],
107
+ comp_lic_item['url'],
108
+ 'YES' if comp_lic_item['copyleft'] else 'NO',
107
109
  ]
108
110
  rows.append(row)
109
111
  # End license loop
110
112
  # End component loop
111
113
  return {
112
114
  'details': f'### Copyleft licenses\n{self.generate_table(headers, rows, centered_columns)}\n',
113
- 'summary': f'{len(components)} component(s) with copyleft licenses were found.\n',
115
+ 'summary': f'{len(component_licenses)} component(s) with copyleft licenses were found.\n',
114
116
  }
115
117
 
116
118
  def _jira_markdown(self, components: list) -> Dict[str, Any]:
@@ -120,24 +122,24 @@ class Copyleft(PolicyCheck):
120
122
  :param components: List of components with copyleft licenses
121
123
  :return: Dictionary with formatted Markdown details and summary
122
124
  """
123
- headers = ['Component', 'Version', 'License', 'URL', 'Copyleft']
125
+ # A component is considered unique by its combination of PURL (Package URL) and license
126
+ component_licenses = self._group_components_by_license(components)
127
+ headers = ['Component', 'License', 'URL', 'Copyleft']
124
128
  centered_columns = [1, 4]
125
129
  rows: [[]] = []
126
- for component in components:
127
- for lic in component['licenses']:
130
+ for comp_lic_item in component_licenses:
128
131
  row = [
129
- component['purl'],
130
- component['version'],
131
- lic['spdxid'],
132
- lic['url'],
133
- 'YES' if lic['copyleft'] else 'NO',
132
+ comp_lic_item['purl'],
133
+ comp_lic_item['spdxid'],
134
+ comp_lic_item['url'],
135
+ 'YES' if comp_lic_item['copyleft'] else 'NO',
134
136
  ]
135
137
  rows.append(row)
136
138
  # End license loop
137
139
  # End component loop
138
140
  return {
139
141
  'details': f'{self.generate_jira_table(headers, rows, centered_columns)}',
140
- 'summary': f'{len(components)} component(s) with copyleft licenses were found.\n',
142
+ 'summary': f'{len(component_licenses)} component(s) with copyleft licenses were found.\n',
141
143
  }
142
144
 
143
145
  def _filter_components_with_copyleft_licenses(self, components: list) -> list:
@@ -161,7 +163,6 @@ class Copyleft(PolicyCheck):
161
163
  lic.pop('count', None) # None is default value if key doesn't exist
162
164
 
163
165
  filtered_component['licenses'] = copyleft_licenses
164
- del filtered_component['status']
165
166
  filtered_components.append(filtered_component)
166
167
  # End component loop
167
168
  self.print_debug(f'Copyleft components: {filtered_components}')
@@ -372,6 +372,57 @@ class InspectBase(ScanossBase):
372
372
  self.print_debug("No priority sources found, returning all licenses as list")
373
373
  return licenses_data
374
374
 
375
+ def _group_components_by_license(self,components):
376
+ """
377
+ Groups components by their unique component-license pairs.
378
+
379
+ This method processes a list of components and creates unique entries for each
380
+ component-license combination. If a component has multiple licenses, it will create
381
+ separate entries for each license.
382
+
383
+ Args:
384
+ components: A list of component dictionaries. Each component should have:
385
+ - purl: Package URL identifying the component
386
+ - licenses: List of license dictionaries, each containing:
387
+ - spdxid: SPDX identifier for the license (optional)
388
+
389
+ Returns:
390
+ list: A list of dictionaries, each containing:
391
+ - purl: The component's package URL
392
+ - license: The SPDX identifier of the license (or 'Unknown' if not provided)
393
+ """
394
+ component_licenses: dict = {}
395
+ for component in components:
396
+ purl = component.get('purl', '')
397
+ status = component.get('status', '')
398
+ licenses = component.get('licenses', [])
399
+
400
+ # Component without license
401
+ if not licenses:
402
+ key = f'{purl}-unknown'
403
+ component_licenses[key] = {
404
+ 'purl': purl,
405
+ 'spdxid': 'unknown',
406
+ 'status': status,
407
+ 'copyleft': False,
408
+ 'url': '-',
409
+ }
410
+ continue
411
+
412
+ # Iterate over licenses component licenses
413
+ for lic in licenses:
414
+ spdxid = lic.get('spdxid', 'unknown')
415
+ if spdxid not in component_licenses:
416
+ key = f'{purl}-{spdxid}'
417
+ component_licenses[key] = {
418
+ 'purl': purl,
419
+ 'spdxid': spdxid,
420
+ 'status': status,
421
+ 'copyleft': lic['copyleft'],
422
+ 'url': lic['url'],
423
+ }
424
+ return list(component_licenses.values())
425
+
375
426
 
376
427
  #
377
428
  # End of PolicyCheck Class
@@ -23,7 +23,6 @@ SPDX-License-Identifier: MIT
23
23
  """
24
24
 
25
25
  import json
26
- from typing import Any, Dict
27
26
 
28
27
  from .inspect_base import InspectBase
29
28
 
@@ -73,35 +72,6 @@ class LicenseSummary(InspectBase):
73
72
  self.exclude = exclude
74
73
  self.explicit = explicit
75
74
 
76
- def _validate_license(self, license_data: Dict[str, Any]) -> bool:
77
- """
78
- Validate that a license has all required fields.
79
-
80
- :param license_data: Dictionary containing license information
81
- :return: True if license is valid, False otherwise
82
- """
83
- for field in self.REQUIRED_LICENSE_FIELDS:
84
- value = license_data.get(field)
85
- if value is None:
86
- self.print_debug(f'WARNING: {field} is empty in license: {license_data}')
87
- return False
88
- return True
89
-
90
- def _append_license(self, licenses: dict, new_license) -> None:
91
- """Add or update a license in the licenses' dictionary."""
92
- spdxid = new_license.get("spdxid")
93
- url = new_license.get("url")
94
- copyleft = new_license.get("copyleft")
95
- if spdxid not in licenses:
96
- licenses[spdxid] = {
97
- 'spdxid': spdxid,
98
- 'url': url,
99
- 'copyleft':copyleft,
100
- 'count': new_license.get("count"),
101
- }
102
- else:
103
- licenses[spdxid]['count'] += new_license.get("count")
104
-
105
75
  def _get_licenses_summary_from_components(self, components: list)-> dict:
106
76
  """
107
77
  Get a license summary from detected components.
@@ -109,27 +79,35 @@ class LicenseSummary(InspectBase):
109
79
  :param components: List of all components
110
80
  :return: Dict with license summary information
111
81
  """
82
+ # A component is considered unique by its combination of PURL (Package URL) and license
83
+ component_licenses = self._group_components_by_license(components)
84
+ license_component_count = {}
85
+ # Count license per component
86
+ for lic in component_licenses:
87
+ if lic['spdxid'] not in license_component_count:
88
+ license_component_count[lic['spdxid']] = 1
89
+ else:
90
+ license_component_count[lic['spdxid']] += 1
112
91
  licenses:dict = {}
113
- licenses_with_copyleft = 0
114
- total_licenses = 0
115
- for component in components:
116
- component_licenses = component.get("licenses", [])
117
- for lic in component_licenses:
118
- if not self._validate_license(lic):
119
- continue
120
- copyleft = lic.get("copyleft")
121
- ## Increment counters
122
- total_licenses += lic.get("count")
123
- if copyleft:
124
- licenses_with_copyleft += lic.get("count")
125
- ## Add license
126
- self._append_license(licenses, lic)
92
+ for comp_lic in component_licenses:
93
+ spdxid = comp_lic.get("spdxid")
94
+ url = comp_lic.get("url")
95
+ copyleft = comp_lic.get("copyleft")
96
+ if spdxid not in licenses:
97
+ licenses[spdxid] = {
98
+ 'spdxid': spdxid,
99
+ 'url': url,
100
+ 'copyleft': copyleft,
101
+ 'componentCount': license_component_count.get(spdxid, 0), # Append component count to license
102
+ }
127
103
  ## End for loop licenses
128
104
  ## End for loop components
105
+ detected_licenses = list(licenses.values())
106
+ licenses_with_copyleft = [lic for lic in detected_licenses if lic['copyleft']]
129
107
  return {
130
- 'licenses': list(licenses.values()),
131
- 'total': total_licenses,
132
- 'copyleft': licenses_with_copyleft
108
+ 'licenses': detected_licenses,
109
+ 'detectedLicenses': len(detected_licenses), # Count unique licenses. SPDXID is considered unique
110
+ 'detectedLicensesWithCopyleft': len(licenses_with_copyleft),
133
111
  }
134
112
 
135
113
 
@@ -80,7 +80,6 @@ class UndeclaredComponent(PolicyCheck):
80
80
  for component in components:
81
81
  if component['status'] == 'pending':
82
82
  # Remove unused keys
83
- del component['status']
84
83
  del component['count']
85
84
  del component['declared']
86
85
  del component['undeclared']
@@ -177,7 +176,7 @@ class UndeclaredComponent(PolicyCheck):
177
176
  # TODO look at using SpdxLite license name lookup method
178
177
  component_licenses = self._group_components_by_license(components)
179
178
  for component in component_licenses:
180
- rows.append([component.get('purl'), component.get('license')])
179
+ rows.append([component.get('purl'), component.get('spdxid')])
181
180
  return {
182
181
  'details': f'### Undeclared components\n{self.generate_table(headers, rows)}\n',
183
182
  'summary': self._get_summary(component_licenses),
@@ -195,7 +194,7 @@ class UndeclaredComponent(PolicyCheck):
195
194
  # TODO look at using SpdxLite license name lookup method
196
195
  component_licenses = self._group_components_by_license(components)
197
196
  for component in component_licenses:
198
- rows.append([component.get('purl'), component.get('license')])
197
+ rows.append([component.get('purl'), component.get('spdxid')])
199
198
  return {
200
199
  'details': f'{self.generate_jira_table(headers, rows)}',
201
200
  'summary': self._get_jira_summary(component_licenses),
@@ -265,36 +264,6 @@ class UndeclaredComponent(PolicyCheck):
265
264
  # Convert to list and process licenses
266
265
  return self._convert_components_to_list(components)
267
266
 
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())
297
-
298
267
  def run(self):
299
268
  """
300
269
  Run the undeclared component inspection process.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scanoss
3
- Version: 1.26.0
3
+ Version: 1.26.2
4
4
  Summary: Simple Python library to leverage the SCANOSS APIs
5
5
  Home-page: https://scanoss.com
6
6
  Author: SCANOSS
@@ -4,7 +4,7 @@ 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=hpFJV75fvMqM81JQ_5NLWJnNyG498vCtV_hWttywCao,1146
7
+ scanoss/__init__.py,sha256=xDDQJcWC9AfGANHfK3qAJabNjabXJyODs8piC_PNc6U,1146
8
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
@@ -57,17 +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=35_MQhM0yl3QDm4UJRvKeRtlQPWALTKWb429m0XN5go,40
60
+ scanoss/data/build_date.txt,sha256=JZiS5Od8jOg1I9NAinAP5k79mVNoARNi3bvCVs5X7XU,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
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
65
+ scanoss/inspection/component_summary.py,sha256=h1l3rF6NnoK0wMkS4ib6rDfcza2aqunyoMDbN2lw2G4,4049
66
+ scanoss/inspection/copyleft.py,sha256=iCArNWZo5TOX7K68e-YD_6mQNRDOE9V35UoSMs23_qY,9236
67
+ scanoss/inspection/inspect_base.py,sha256=wnE2KdATJFC2HqcPUTaKXUnM-3hBK0GZ98-wJE0VE-c,18170
68
+ scanoss/inspection/license_summary.py,sha256=T3I8E6ljqYF0ngIcY3Ke2WaNeCzrdpN0RQ02RG_3Thk,5763
69
69
  scanoss/inspection/policy_check.py,sha256=NS39dvePZbpusGRnEUKN9JFiMdSyva0msFE6On6bK8Q,8329
70
- scanoss/inspection/undeclared_component.py,sha256=4mo1CMBho_I-58j5H0x8A-9ZGVvrGsNzBDel48LZ8Z0,12866
70
+ scanoss/inspection/undeclared_component.py,sha256=qjc30agrIXU_07CJCgLvvFT-sQvJa1Hb0lS1UjM6aj8,11495
71
71
  scanoss/inspection/utils/license_utils.py,sha256=Zb6QLmVJb86lKCwZyBsmwakyAtY1SXa54kUyyKmWMqA,5093
72
72
  scanoss/scanners/__init__.py,sha256=D4C0lWLuNp8k_BjQZEc07WZcUgAvriVwQWOk063b0ZU,1122
73
73
  scanoss/scanners/container_scanner.py,sha256=leP4roes6B9B95F49mJ0P_F8WcKCQkvJgk9azWyJrjg,16294
@@ -79,9 +79,9 @@ scanoss/utils/abstract_presenter.py,sha256=teiDTxBj5jBMCk2T8i4l1BJPf_u4zBLWrtCTF
79
79
  scanoss/utils/crc64.py,sha256=TMrwQimSdE6imhFOUL7oAG6Kxu-8qMpGWMuMg8QpSVs,3169
80
80
  scanoss/utils/file.py,sha256=62cA9a17TU9ZvfA3FY5HY4-QOajJeSrc8S6xLA_f-3M,2980
81
81
  scanoss/utils/simhash.py,sha256=6iu8DOcecPAY36SZjCOzrrLMT9oIE7-gI6QuYwUQ7B0,5793
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,,
82
+ scanoss-1.26.2.dist-info/licenses/LICENSE,sha256=LLUaXoiyOroIbr5ubAyrxBOwSRLTm35ETO2FmLpy8QQ,1074
83
+ scanoss-1.26.2.dist-info/METADATA,sha256=ReKdCqWwRMsZl2IHLb2VpX7wxJPHfF4sXafR4uT94ak,6060
84
+ scanoss-1.26.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
85
+ scanoss-1.26.2.dist-info/entry_points.txt,sha256=Uy28xnaDL5KQ7V77sZD5VLDXPNxYYzSr5tsqtiXVzAs,48
86
+ scanoss-1.26.2.dist-info/top_level.txt,sha256=V11PrQ6Pnrc-nDF9xnisnJ8e6-i7HqSIKVNqduRWcL8,27
87
+ scanoss-1.26.2.dist-info/RECORD,,