Skip to content

Commit

Permalink
Consolidate the usage of get_expression_as_spdx #63
Browse files Browse the repository at this point in the history
Signed-off-by: tdruez <tdruez@nexb.com>
  • Loading branch information
tdruez committed Jul 4, 2024
1 parent a330c44 commit cde63ee
Show file tree
Hide file tree
Showing 6 changed files with 36 additions and 36 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ Release notes
properly checked and return a validation error when incorrect.
https://github.com/nexB/dejacode/issues/63

- Use the declared_license_expression_spdx value in SPDX outputs.
https://github.com/nexB/dejacode/issues/63

### Version 5.1.0

- Upgrade Python version to 3.12 and Django to 5.0.x
Expand Down
47 changes: 19 additions & 28 deletions component_catalog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,25 +187,6 @@ def get_license_expression_linked_with_policy(self):
if license_expression:
return format_html('<span class="license-expression">{}</span>', license_expression)

def get_license_expression_spdx_id(self):
"""
Return the license_expression formatted for SPDX compatibility.
This includes a workaround for a SPDX spec limitation, where license exceptions
that do not exist in the SPDX list cannot be provided as "LicenseRef-" in the
"hasExtractedLicensingInfos".
The current fix is to use AND rather than WITH for any exception that is a
"LicenseRef-".
See discussion at https://github.com/spdx/tools-java/issues/73
Note: A new get_expression_as_spdx function is available and should be used in
place of this one.
"""
expression = self.get_license_expression("{symbol.spdx_id}")
if expression:
return expression.replace("WITH LicenseRef-", "AND LicenseRef-")

def _get_primary_license(self):
"""
Return the primary license key of this instance or None. The primary license is
Expand All @@ -221,14 +202,28 @@ def _get_primary_license(self):
primary_license = cached_property(_get_primary_license)

def get_expression_as_spdx(self, expression):
"""
Return the license_expression formatted for SPDX compatibility.
This includes a workaround for a SPDX spec limitation, where license exceptions
that do not exist in the SPDX list cannot be provided as "LicenseRef-" in the
"hasExtractedLicensingInfos".
The current fix is to use AND rather than WITH for any exception that is a
"LicenseRef-".
See discussion at https://github.com/spdx/tools-java/issues/73
"""
if not expression:
return

try:
return get_expression_as_spdx(expression, self.dataspace)
expression_as_spdx = get_expression_as_spdx(expression, self.dataspace)
except ExpressionError as e:
return str(e)

if expression_as_spdx:
return expression_as_spdx.replace("WITH LicenseRef-", "AND LicenseRef-")

@property
def concluded_license_expression_spdx(self):
return self.get_expression_as_spdx(self.license_expression)
Expand Down Expand Up @@ -820,7 +815,7 @@ def as_cyclonedx(self, license_expression_spdx=None):
urls=[self.owner.homepage_url],
)

expression_spdx = license_expression_spdx or self.get_license_expression_spdx_id()
expression_spdx = license_expression_spdx or self.concluded_license_expression_spdx
licenses = []
if expression_spdx:
# Using the LicenseExpression directly as the make_with_expression method
Expand Down Expand Up @@ -1345,13 +1340,11 @@ def as_spdx(self, license_concluded=None):
if self.notice_text:
attribution_texts.append(self.notice_text)

license_expression_spdx = self.get_license_expression_spdx_id()

return spdx.Package(
name=self.name,
spdx_id=f"dejacode-{self._meta.model_name}-{self.uuid}",
supplier=self.owner.as_spdx() if self.owner else "",
license_concluded=license_concluded or license_expression_spdx,
license_concluded=license_concluded or self.concluded_license_expression_spdx,
license_declared=self.declared_license_expression_spdx,
copyright_text=self.copyright,
version=self.version,
Expand Down Expand Up @@ -2248,13 +2241,11 @@ def as_spdx(self, license_concluded=None):
if cpe_external_ref := self.get_spdx_cpe_external_ref():
external_refs.append(cpe_external_ref)

license_expression_spdx = self.get_license_expression_spdx_id()

return spdx.Package(
name=self.name or self.filename,
spdx_id=f"dejacode-{self._meta.model_name}-{self.uuid}",
download_location=self.download_url,
license_concluded=license_concluded or license_expression_spdx,
license_concluded=license_concluded or self.concluded_license_expression_spdx,
license_declared=self.declared_license_expression_spdx,
copyright_text=self.copyright,
version=self.version,
Expand All @@ -2273,7 +2264,7 @@ def get_spdx_packages(self):

def as_cyclonedx(self, license_expression_spdx=None):
"""Return this Package as an CycloneDX Component entry."""
expression_spdx = license_expression_spdx or self.get_license_expression_spdx_id()
expression_spdx = license_expression_spdx or self.concluded_license_expression_spdx

licenses = []
if expression_spdx:
Expand Down
8 changes: 4 additions & 4 deletions component_catalog/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,29 +419,29 @@ def test_component_license_expression_linked(self):
)
self.assertEqual(expected, self.component1.get_license_expression_linked())

def test_component_get_license_expression_spdx_id(self):
def test_component_concluded_license_expression_spdx(self):
self.license1.spdx_license_key = "SPDX-1"
self.license1.save()

expression = "{} AND {}".format(self.license1.key, self.license2.key)
self.component1.license_expression = expression
self.component1.save()
expected = "SPDX-1 AND LicenseRef-dejacode-license2"
self.assertEqual(expected, self.component1.get_license_expression_spdx_id())
self.assertEqual(expected, self.component1.concluded_license_expression_spdx)

expression = "{} WITH {}".format(self.license1.key, self.license2.key)
self.component1.license_expression = expression
self.component1.save()
# WITH is replaced by AND for "LicenseRef-" exceptions
expected = "SPDX-1 AND LicenseRef-dejacode-license2"
self.assertEqual(expected, self.component1.get_license_expression_spdx_id())
self.assertEqual(expected, self.component1.concluded_license_expression_spdx)

self.license2.spdx_license_key = "SPDX-2"
self.license2.save()
self.component1 = Component.objects.get(pk=self.component1.pk)
# WITH is kept for exceptions in SPDX list
expected = "SPDX-1 WITH SPDX-2"
self.assertEqual(expected, self.component1.get_license_expression_spdx_id())
self.assertEqual(expected, self.component1.concluded_license_expression_spdx)

def test_component_model_license_expression_spdx_properties(self):
self.license1.spdx_license_key = "SPDX-1"
Expand Down
4 changes: 2 additions & 2 deletions component_catalog/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,7 @@ def test_component_catalog_details_view_num_queries(self):
History.log_change(self.basic_user, self.component1, "Changed version.")
History.log_change(self.nexb_user, self.component1, "Changed notes.")

with self.assertNumQueries(31):
with self.assertNumQueries(32):
self.client.get(url)

def test_component_catalog_details_view_package_tab_fields_visibility(self):
Expand Down Expand Up @@ -1265,7 +1265,7 @@ def test_package_list_multi_send_about_files_view(self):

def test_package_details_view_num_queries(self):
self.client.login(username=self.super_user.username, password="secret")
with self.assertNumQueries(26):
with self.assertNumQueries(27):
self.client.get(self.package1.get_absolute_url())

def test_package_details_view_content(self):
Expand Down
6 changes: 6 additions & 0 deletions license_library/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from django.apps import apps
from django.core import validators
from django.core.cache import caches
from django.core.exceptions import MultipleObjectsReturned
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError
Expand Down Expand Up @@ -812,6 +813,11 @@ class Meta:
def __str__(self):
return f"{self.short_name} ({self.key})"

def save(self, *args, **kwargs):
"""Clear the licensing cache on License object changes."""
super().save(*args, **kwargs)
caches["licensing"].clear()

def clean(self, from_api=False):
if self.is_active is False and self.spdx_license_key:
raise ValidationError("A deprecated license must not have an SPDX license key.")
Expand Down
4 changes: 2 additions & 2 deletions product_portfolio/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,12 +642,12 @@ def as_spdx(self):
while the component/package license is set in the `license_declared`.
"""
return self.related_component_or_package.as_spdx(
license_concluded=self.get_license_expression_spdx_id(),
license_concluded=self.concluded_license_expression_spdx,
)

def as_cyclonedx(self):
return self.related_component_or_package.as_cyclonedx(
license_expression_spdx=self.get_license_expression_spdx_id(),
license_expression_spdx=self.concluded_license_expression_spdx,
)

@cached_property
Expand Down

0 comments on commit cde63ee

Please sign in to comment.