scanoss 1.26.0__tar.gz → 1.26.2__tar.gz

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.
Files changed (100) hide show
  1. {scanoss-1.26.0/src/scanoss.egg-info → scanoss-1.26.2}/PKG-INFO +1 -1
  2. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/__init__.py +1 -1
  3. scanoss-1.26.2/src/scanoss/data/build_date.txt +1 -0
  4. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/component_summary.py +17 -8
  5. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/copyleft.py +21 -20
  6. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/inspect_base.py +51 -0
  7. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/license_summary.py +25 -47
  8. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/undeclared_component.py +2 -33
  9. {scanoss-1.26.0 → scanoss-1.26.2/src/scanoss.egg-info}/PKG-INFO +1 -1
  10. {scanoss-1.26.0 → scanoss-1.26.2}/tests/test_policy_inspect.py +49 -28
  11. {scanoss-1.26.0 → scanoss-1.26.2}/tests/test_spdxlite.py +2 -2
  12. scanoss-1.26.0/src/scanoss/data/build_date.txt +0 -1
  13. {scanoss-1.26.0 → scanoss-1.26.2}/LICENSE +0 -0
  14. {scanoss-1.26.0 → scanoss-1.26.2}/PACKAGE.md +0 -0
  15. {scanoss-1.26.0 → scanoss-1.26.2}/README.md +0 -0
  16. {scanoss-1.26.0 → scanoss-1.26.2}/pyproject.toml +0 -0
  17. {scanoss-1.26.0 → scanoss-1.26.2}/setup.cfg +0 -0
  18. {scanoss-1.26.0 → scanoss-1.26.2}/src/protoc_gen_swagger/__init__.py +0 -0
  19. {scanoss-1.26.0 → scanoss-1.26.2}/src/protoc_gen_swagger/options/__init__.py +0 -0
  20. {scanoss-1.26.0 → scanoss-1.26.2}/src/protoc_gen_swagger/options/annotations_pb2.py +0 -0
  21. {scanoss-1.26.0 → scanoss-1.26.2}/src/protoc_gen_swagger/options/annotations_pb2_grpc.py +0 -0
  22. {scanoss-1.26.0 → scanoss-1.26.2}/src/protoc_gen_swagger/options/openapiv2_pb2.py +0 -0
  23. {scanoss-1.26.0 → scanoss-1.26.2}/src/protoc_gen_swagger/options/openapiv2_pb2_grpc.py +0 -0
  24. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/__init__.py +0 -0
  25. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/common/__init__.py +0 -0
  26. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/common/v2/__init__.py +0 -0
  27. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/common/v2/scanoss_common_pb2.py +0 -0
  28. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/common/v2/scanoss_common_pb2_grpc.py +0 -0
  29. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/components/__init__.py +0 -0
  30. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/components/v2/__init__.py +0 -0
  31. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/components/v2/scanoss_components_pb2.py +0 -0
  32. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/components/v2/scanoss_components_pb2_grpc.py +0 -0
  33. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +0 -0
  34. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +0 -0
  35. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/dependencies/__init__.py +0 -0
  36. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/dependencies/v2/__init__.py +0 -0
  37. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +0 -0
  38. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +0 -0
  39. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/geoprovenance/__init__.py +0 -0
  40. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/geoprovenance/v2/__init__.py +0 -0
  41. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +0 -0
  42. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +0 -0
  43. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/scanning/__init__.py +0 -0
  44. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/scanning/v2/__init__.py +0 -0
  45. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/scanning/v2/scanoss_scanning_pb2.py +0 -0
  46. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +0 -0
  47. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/semgrep/__init__.py +0 -0
  48. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/semgrep/v2/__init__.py +0 -0
  49. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +0 -0
  50. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +0 -0
  51. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/vulnerabilities/__init__.py +0 -0
  52. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/vulnerabilities/v2/__init__.py +0 -0
  53. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +0 -0
  54. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +0 -0
  55. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/cli.py +0 -0
  56. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/components.py +0 -0
  57. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/constants.py +0 -0
  58. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/cryptography.py +0 -0
  59. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/csvoutput.py +0 -0
  60. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/cyclonedx.py +0 -0
  61. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/data/scanoss-settings-schema.json +0 -0
  62. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/data/spdx-exceptions.json +0 -0
  63. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/data/spdx-licenses.json +0 -0
  64. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/file_filters.py +0 -0
  65. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/filecount.py +0 -0
  66. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/__init__.py +0 -0
  67. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/policy_check.py +0 -0
  68. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/utils/license_utils.py +0 -0
  69. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/results.py +0 -0
  70. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scancodedeps.py +0 -0
  71. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanner.py +0 -0
  72. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanners/__init__.py +0 -0
  73. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanners/container_scanner.py +0 -0
  74. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanners/folder_hasher.py +0 -0
  75. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanners/scanner_config.py +0 -0
  76. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanners/scanner_hfh.py +0 -0
  77. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanoss_settings.py +0 -0
  78. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanossapi.py +0 -0
  79. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanossbase.py +0 -0
  80. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanossgrpc.py +0 -0
  81. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanpostprocessor.py +0 -0
  82. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scantype.py +0 -0
  83. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/spdxlite.py +0 -0
  84. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/threadeddependencies.py +0 -0
  85. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/threadedscanning.py +0 -0
  86. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/utils/__init__.py +0 -0
  87. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/utils/abstract_presenter.py +0 -0
  88. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/utils/crc64.py +0 -0
  89. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/utils/file.py +0 -0
  90. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/utils/simhash.py +0 -0
  91. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/winnowing.py +0 -0
  92. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss.egg-info/SOURCES.txt +0 -0
  93. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss.egg-info/dependency_links.txt +0 -0
  94. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss.egg-info/entry_points.txt +0 -0
  95. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss.egg-info/requires.txt +0 -0
  96. {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss.egg-info/top_level.txt +0 -0
  97. {scanoss-1.26.0 → scanoss-1.26.2}/tests/test_csv_output.py +0 -0
  98. {scanoss-1.26.0 → scanoss-1.26.2}/tests/test_file_filters.py +0 -0
  99. {scanoss-1.26.0 → scanoss-1.26.2}/tests/test_scan_post_processor.py +0 -0
  100. {scanoss-1.26.0 → scanoss-1.26.2}/tests/test_winnowing.py +0 -0
@@ -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
@@ -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'
@@ -0,0 +1 @@
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
@@ -145,10 +145,10 @@ class MyTestCase(unittest.TestCase):
145
145
  copyleft = Copyleft(filepath=input_file_name, format_type='md', explicit='MIT')
146
146
  status, results = copyleft.run()
147
147
  expected_detail_output = (
148
- '### Copyleft licenses \n | Component | Version | License | URL | Copyleft |\n'
149
- ' | - | :-: | - | - | :-: |\n'
150
- ' | pkg:npm/%40electron/rebuild | 3.7.0 | MIT | https://spdx.org/licenses/MIT.html | YES |\n'
151
- '| pkg:npm/%40emotion/react | 11.13.3 | MIT | https://spdx.org/licenses/MIT.html | YES | \n'
148
+ '### Copyleft licenses \n | Component | License | URL | Copyleft |\n'
149
+ ' | - | :-: | - | - |\n'
150
+ ' | pkg:npm/%40electron/rebuild | MIT | https://spdx.org/licenses/MIT.html | YES |\n'
151
+ '| pkg:npm/%40emotion/react | MIT | https://spdx.org/licenses/MIT.html | YES | \n'
152
152
  )
153
153
  expected_summary_output = '2 component(s) with copyleft licenses were found.\n'
154
154
  self.assertEqual(
@@ -181,11 +181,14 @@ class MyTestCase(unittest.TestCase):
181
181
  status, results = undeclared.run()
182
182
  details = json.loads(results['details'])
183
183
  summary = results['summary']
184
- expected_summary_output = """2 undeclared component(s) were found.
184
+ expected_summary_output = """3 undeclared component(s) were found.
185
185
  Add the following snippet into your `sbom.json` file
186
186
  ```json
187
187
  {
188
188
  "components":[
189
+ {
190
+ "purl": "pkg:github/scanoss/jenkins-pipeline-example"
191
+ },
189
192
  {
190
193
  "purl": "pkg:github/scanoss/scanner.c"
191
194
  },
@@ -195,7 +198,7 @@ class MyTestCase(unittest.TestCase):
195
198
  ]
196
199
  }```
197
200
  """
198
- self.assertEqual(len(details['components']), 3)
201
+ self.assertEqual(len(details['components']), 4)
199
202
  self.assertEqual(
200
203
  re.sub(r'\s|\\(?!`)|\\(?=`)', '', summary), re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_summary_output)
201
204
  )
@@ -216,14 +219,18 @@ class MyTestCase(unittest.TestCase):
216
219
  expected_details_output = """ ### Undeclared components
217
220
  | Component | License |
218
221
  | - | - |
222
+ | pkg:github/scanoss/jenkins-pipeline-example | unknown |
219
223
  | pkg:github/scanoss/scanner.c | GPL-2.0-only |
220
224
  | pkg:github/scanoss/wfp | GPL-2.0-only | """
221
225
 
222
- expected_summary_output = """2 undeclared component(s) were found.
226
+ expected_summary_output = """3 undeclared component(s) were found.
223
227
  Add the following snippet into your `sbom.json` file
224
228
  ```json
225
229
  {
226
230
  "components":[
231
+ {
232
+ "purl": "pkg:github/scanoss/jenkins-pipeline-example"
233
+ },
227
234
  {
228
235
  "purl": "pkg:github/scanoss/scanner.c"
229
236
  },
@@ -256,16 +263,20 @@ class MyTestCase(unittest.TestCase):
256
263
  expected_details_output = """ ### Undeclared components
257
264
  | Component | License |
258
265
  | - | - |
266
+ | pkg:github/scanoss/jenkins-pipeline-example | unknown |
259
267
  | pkg:github/scanoss/scanner.c | GPL-2.0-only |
260
268
  | pkg:github/scanoss/wfp | GPL-2.0-only | """
261
269
 
262
- expected_summary_output = """2 undeclared component(s) were found.
270
+ expected_summary_output = """3 undeclared component(s) were found.
263
271
  Add the following snippet into your `scanoss.json` file
264
272
 
265
273
  ```json
266
274
  {
267
275
  "bom": {
268
276
  "include": [
277
+ {
278
+ "purl": "pkg:github/scanoss/jenkins-pipeline-example"
279
+ },
269
280
  {
270
281
  "purl": "pkg:github/scanoss/scanner.c"
271
282
  },
@@ -296,13 +307,16 @@ class MyTestCase(unittest.TestCase):
296
307
  status, results = undeclared.run()
297
308
  details = json.loads(results['details'])
298
309
  summary = results['summary']
299
- expected_summary_output = """2 undeclared component(s) were found.
310
+ expected_summary_output = """3 undeclared component(s) were found.
300
311
  Add the following snippet into your `scanoss.json` file
301
312
 
302
313
  ```json
303
314
  {
304
315
  "bom": {
305
316
  "include": [
317
+ {
318
+ "purl": "pkg:github/scanoss/jenkins-pipeline-example"
319
+ },
306
320
  {
307
321
  "purl": "pkg:github/scanoss/scanner.c"
308
322
  },
@@ -314,7 +328,7 @@ class MyTestCase(unittest.TestCase):
314
328
  }
315
329
  ```"""
316
330
  self.assertEqual(status, 0)
317
- self.assertEqual(len(details['components']), 3)
331
+ self.assertEqual(len(details['components']), 4)
318
332
  self.assertEqual(
319
333
  re.sub(r'\s|\\(?!`)|\\(?=`)', '', summary), re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_summary_output)
320
334
  )
@@ -328,15 +342,19 @@ class MyTestCase(unittest.TestCase):
328
342
  details = results['details']
329
343
  summary = results['summary']
330
344
  expected_details_output = """|*Component*|*License*|
345
+ |pkg:github/scanoss/jenkins-pipeline-example|unknown|
331
346
  |pkg:github/scanoss/scanner.c|GPL-2.0-only|
332
347
  |pkg:github/scanoss/wfp|GPL-2.0-only|
333
348
  """
334
- expected_summary_output = """2 undeclared component(s) were found.
349
+ expected_summary_output = """3 undeclared component(s) were found.
335
350
  Add the following snippet into your `scanoss.json` file
336
351
  {code:json}
337
352
  {
338
353
  "bom": {
339
354
  "include": [
355
+ {
356
+ "purl": "pkg:github/scanoss/jenkins-pipeline-example"
357
+ },
340
358
  {
341
359
  "purl": "pkg:github/scanoss/scanner.c"
342
360
  },
@@ -359,12 +377,10 @@ Add the following snippet into your `scanoss.json` file
359
377
  copyleft = Copyleft(filepath=input_file_name, format_type='jira_md')
360
378
  status, results = copyleft.run()
361
379
  details = results['details']
362
- expected_details_output = """|*Component*|*Version*|*License*|*URL*|*Copyleft*|
363
- |pkg:github/scanoss/scanner.c|1.3.3|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
364
- |pkg:github/scanoss/scanner.c|1.1.4|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
365
- |pkg:github/scanoss/engine|5.4.0|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
366
- |pkg:github/scanoss/wfp|6afc1f6|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
367
- |pkg:github/scanoss/engine|4.0.4|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
380
+ expected_details_output = """|*Component*|*License*|*URL*|*Copyleft*|
381
+ |pkg:github/scanoss/scanner.c|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
382
+ |pkg:github/scanoss/engine|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
383
+ |pkg:github/scanoss/wfp|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
368
384
  """
369
385
  self.assertEqual(status, 0)
370
386
  self.assertEqual(expected_details_output, details)
@@ -375,9 +391,8 @@ Add the following snippet into your `scanoss.json` file
375
391
  input_file_name = os.path.join(script_dir, 'data', file_name)
376
392
  i_license_summary = LicenseSummary(filepath=input_file_name)
377
393
  license_summary = i_license_summary.run()
378
- self.assertEqual(license_summary['total'], 9)
379
- self.assertEqual(license_summary['copyleft'], 7)
380
- self.assertEqual(len(license_summary['licenses']), 2)
394
+ self.assertEqual(license_summary['detectedLicenses'], 3)
395
+ self.assertEqual(license_summary['detectedLicensesWithCopyleft'], 1)
381
396
 
382
397
  def test_inspect_license_summary_with_empty_result(self):
383
398
  script_dir = os.path.dirname(os.path.abspath(__file__))
@@ -385,8 +400,8 @@ Add the following snippet into your `scanoss.json` file
385
400
  input_file_name = os.path.join(script_dir, 'data', file_name)
386
401
  i_license_summary = LicenseSummary(filepath=input_file_name)
387
402
  license_summary = i_license_summary.run()
388
- self.assertEqual(license_summary['total'], 0)
389
- self.assertEqual(license_summary['copyleft'], 0)
403
+ self.assertEqual(license_summary['detectedLicenses'], 0)
404
+ self.assertEqual(license_summary['detectedLicensesWithCopyleft'], 0)
390
405
  self.assertEqual(len(license_summary['licenses']), 0)
391
406
 
392
407
  def test_inspect_component_summary(self):
@@ -396,9 +411,12 @@ Add the following snippet into your `scanoss.json` file
396
411
  i_component_summary = ComponentSummary(filepath=input_file_name)
397
412
  component_summary = i_component_summary.run()
398
413
  print(component_summary)
399
- self.assertEqual(component_summary['total'], 7)
400
- self.assertEqual(component_summary['undeclared'], 5)
401
- self.assertEqual(component_summary['declared'], 2)
414
+ self.assertEqual(component_summary['totalComponents'], 4)
415
+ self.assertEqual(component_summary['undeclaredComponents'], 3)
416
+ self.assertEqual(component_summary['declaredComponents'], 1)
417
+ self.assertEqual(component_summary['totalFilesDetected'], 8)
418
+ self.assertEqual(component_summary['totalFilesUndeclared'], 6)
419
+ self.assertEqual(component_summary['totalFilesDeclared'], 2)
402
420
 
403
421
  def test_inspect_component_summary_empty_result(self):
404
422
  script_dir = os.path.dirname(os.path.abspath(__file__))
@@ -406,10 +424,13 @@ Add the following snippet into your `scanoss.json` file
406
424
  input_file_name = os.path.join(script_dir, 'data', file_name)
407
425
  i_component_summary = ComponentSummary(filepath=input_file_name)
408
426
  component_summary = i_component_summary.run()
409
- self.assertEqual(component_summary['total'], 0)
410
- self.assertEqual(component_summary['undeclared'], 0)
411
- self.assertEqual(component_summary['declared'], 0)
427
+ self.assertEqual(component_summary['totalComponents'], 0)
428
+ self.assertEqual(component_summary['undeclaredComponents'], 0)
429
+ self.assertEqual(component_summary['declaredComponents'], 0)
412
430
  self.assertEqual(len(component_summary['components']), 0)
431
+ self.assertEqual(component_summary['totalFilesDetected'], 0)
432
+ self.assertEqual(component_summary['totalFilesUndeclared'], 0)
433
+ self.assertEqual(component_summary['totalFilesDeclared'], 0)
413
434
 
414
435
  if __name__ == '__main__':
415
436
  unittest.main()
@@ -58,8 +58,8 @@ class MyTestCase(unittest.TestCase):
58
58
  self.assertEqual(name, "SCANOSS-SBOM")
59
59
  self.assertEqual(organization, "Organization: SCANOSS")
60
60
  self.assertEqual(creation_info_comment, "SBOM Build information - SBOM Type: Build")
61
- self.assertEqual(len(document_describes), 5)
62
- self.assertEqual(len(packages), 5)
61
+ self.assertEqual(len(document_describes), 6)
62
+ self.assertEqual(len(packages), 6)
63
63
 
64
64
  for package in packages:
65
65
  for checksum in package.get("checksums", []):
@@ -1 +0,0 @@
1
- date: 20250620142757, utime: 1750429677
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes