diff --git a/scancodeio/settings.py b/scancodeio/settings.py
index 2ff10579fa..1d0310a11b 100644
--- a/scancodeio/settings.py
+++ b/scancodeio/settings.py
@@ -120,6 +120,7 @@
"resource": 100,
"package": 100,
"dependency": 100,
+ "license": 100,
"relation": 100,
},
)
diff --git a/scanpipe/api/serializers.py b/scanpipe/api/serializers.py
index 463838223c..6d849533b7 100644
--- a/scanpipe/api/serializers.py
+++ b/scanpipe/api/serializers.py
@@ -31,6 +31,7 @@
from scanpipe.models import CodebaseRelation
from scanpipe.models import CodebaseResource
from scanpipe.models import DiscoveredDependency
+from scanpipe.models import DiscoveredLicense
from scanpipe.models import DiscoveredPackage
from scanpipe.models import InputSource
from scanpipe.models import Project
@@ -469,6 +470,20 @@ class Meta:
]
+class DiscoveredLicenseSerializer(serializers.ModelSerializer):
+ compliance_alert = serializers.CharField()
+
+ class Meta:
+ model = DiscoveredLicense
+ fields = [
+ "detection_count",
+ "identifier",
+ "license_expression",
+ "license_expression_spdx",
+ "compliance_alert",
+ ]
+
+
class CodebaseRelationSerializer(serializers.ModelSerializer):
from_resource = serializers.ReadOnlyField(source="from_resource.path")
to_resource = serializers.ReadOnlyField(source="to_resource.path")
@@ -527,6 +542,7 @@ def get_model_serializer(model_class):
CodebaseResource: CodebaseResourceSerializer,
DiscoveredPackage: DiscoveredPackageSerializer,
DiscoveredDependency: DiscoveredDependencySerializer,
+ DiscoveredLicense: DiscoveredLicenseSerializer,
CodebaseRelation: CodebaseRelationSerializer,
ProjectMessage: ProjectMessageSerializer,
}.get(model_class, None)
diff --git a/scanpipe/filters.py b/scanpipe/filters.py
index 75d715f165..514b88a839 100644
--- a/scanpipe/filters.py
+++ b/scanpipe/filters.py
@@ -40,6 +40,7 @@
from scanpipe.models import CodebaseRelation
from scanpipe.models import CodebaseResource
from scanpipe.models import DiscoveredDependency
+from scanpipe.models import DiscoveredLicense
from scanpipe.models import DiscoveredPackage
from scanpipe.models import Project
from scanpipe.models import ProjectMessage
@@ -549,6 +550,7 @@ class ResourceFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
"related_from__from_resource__path",
],
)
+
compliance_alert = django_filters.ChoiceFilter(
choices=[(EMPTY_VAR, "None")] + CodebaseResource.Compliance.choices,
)
@@ -608,8 +610,8 @@ class Meta:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- license_expression_filer = self.filters["detected_license_expression"]
- license_expression_filer.extra["widget"] = HasValueDropdownWidget()
+ license_expression_filter = self.filters["detected_license_expression"]
+ license_expression_filter.extra["widget"] = HasValueDropdownWidget()
class IsVulnerable(django_filters.ChoiceFilter):
@@ -639,6 +641,19 @@ def filter(self, qs, value):
return super().filter(qs, value)
+class DiscoveredLicenseSearchFilter(QuerySearchFilter):
+ def filter(self, qs, value):
+ if not value:
+ return qs
+
+ search_fields = ["license_expression", "license_expression_spdx"]
+ lookups = Q()
+ for field_names in search_fields:
+ lookups |= Q(**{f"{field_names}__{self.lookup_expr}": value})
+
+ return qs.filter(lookups)
+
+
class GroupOrderingFilter(django_filters.OrderingFilter):
"""Add the ability to provide a group a fields to order by."""
@@ -816,6 +831,48 @@ class Meta:
]
+class LicenseFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
+ dropdown_widget_fields = [
+ "compliance_alert",
+ "license_expression",
+ "license_expression_spdx",
+ ]
+
+ search = DiscoveredLicenseSearchFilter(
+ label="Search", field_name="name", lookup_expr="icontains"
+ )
+ sort = GroupOrderingFilter(
+ label="Sort",
+ fields=[
+ "detection_count",
+ "identifier",
+ "license_expression",
+ "license_expression_spdx",
+ "compliance_alert",
+ ],
+ )
+ license_expression = django_filters.AllValuesFilter()
+ license_expression_spdx = django_filters.AllValuesFilter()
+ compliance_alert = django_filters.ChoiceFilter(
+ choices=[(EMPTY_VAR, "None")] + CodebaseResource.Compliance.choices,
+ )
+ is_license_clue = StrictBooleanFilter()
+ needs_review = StrictBooleanFilter()
+
+ class Meta:
+ model = DiscoveredLicense
+ fields = [
+ "search",
+ "identifier",
+ "detection_count",
+ "license_expression",
+ "license_expression_spdx",
+ "compliance_alert",
+ "is_license_clue",
+ "needs_review",
+ ]
+
+
class ProjectMessageFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
search = QuerySearchFilter(
label="Search", search_fields=["description"], lookup_expr="icontains"
diff --git a/scanpipe/migrations/0074_discovered_license_models.py b/scanpipe/migrations/0074_discovered_license_models.py
new file mode 100644
index 0000000000..f6e29d02e5
--- /dev/null
+++ b/scanpipe/migrations/0074_discovered_license_models.py
@@ -0,0 +1,185 @@
+# Generated by Django 5.0.2 on 2024-03-17 12:38
+
+import django.db.models.deletion
+import scanpipe.models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("scanpipe", "0073_add_sha1_git_checksum"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="DiscoveredLicense",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "compliance_alert",
+ models.CharField(
+ blank=True,
+ choices=[
+ ("ok", "Ok"),
+ ("warning", "Warning"),
+ ("error", "Error"),
+ ("missing", "Missing"),
+ ],
+ editable=False,
+ help_text="Indicates how the license expression complies with provided policies.",
+ max_length=10,
+ ),
+ ),
+ (
+ "license_expression",
+ models.TextField(
+ blank=True,
+ help_text="A license expression string using the SPDX license expression syntax and ScanCode license keys, the effective license expression for this license detection.",
+ ),
+ ),
+ (
+ "license_expression_spdx",
+ models.TextField(
+ blank=True,
+ help_text="SPDX license expression string with SPDX ids.",
+ ),
+ ),
+ (
+ "matches",
+ models.JSONField(
+ blank=True,
+ default=list,
+ help_text="List of license matches combined in this detection.",
+ verbose_name="Reference Matches",
+ ),
+ ),
+ (
+ "detection_log",
+ models.JSONField(
+ blank=True,
+ default=list,
+ help_text="A list of detection DetectionRule explaining how this detection was created.",
+ ),
+ ),
+ (
+ "identifier",
+ models.CharField(
+ blank=True,
+ help_text="An identifier unique for a license detection, containing the license expression and a UUID crafted from the match contents.",
+ max_length=1024,
+ ),
+ ),
+ (
+ "detection_count",
+ models.BigIntegerField(
+ blank=True,
+ help_text="Total number of this license detection discovered.",
+ null=True,
+ ),
+ ),
+ (
+ "file_regions",
+ models.JSONField(
+ blank=True,
+ default=list,
+ help_text="A list of file regions with resource path, start and end line details for each place this license detection was discovered at. Also contains whether this license was discovered from a file or from package metadata.",
+ verbose_name="Detection Locations",
+ ),
+ ),
+ (
+ "is_license_clue",
+ models.BooleanField(
+ default=False,
+ help_text='True if this is not a proper license detection which should be considered in the license_expression for the parent resource/package. A license match is considered as a clue if it could be a possiblefalse positives or the matched rule is tagged as a clue explicitly.',
+ ),
+ ),
+ (
+ "from_package",
+ models.BooleanField(
+ default=False,
+ help_text='True if this was discovered in a extracted license statement and False if this was discovered in a file.',
+ ),
+ ),
+ (
+ "needs_review",
+ models.BooleanField(
+ default=False,
+ help_text='True if this was license detection needs to be reviewed as there might be a license detection issue.',
+ ),
+ ),
+ (
+ "project",
+ models.ForeignKey(
+ editable=False,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="%(class)ss",
+ to="scanpipe.project",
+ ),
+ ),
+ (
+ "review_comments",
+ models.JSONField(
+ blank=True,
+ default=list,
+ help_text='A list of review comments for license detection issues which needs review. These descriptive comments are based on ambigous detection types and could also offers helpful suggestions on how to review/report these detection issues.',
+ verbose_name='Review Comments',
+ ),
+ ),
+ ],
+ options={
+ "ordering": ["detection_count", "identifier"],
+ "indexes": [
+ models.Index(
+ fields=["identifier"], name="scanpipe_di_identif_b533f3_idx"
+ ),
+ models.Index(
+ fields=["license_expression"],
+ name="scanpipe_di_license_33d11a_idx",
+ ),
+ models.Index(
+ fields=["license_expression_spdx"],
+ name="scanpipe_di_license_eb5e9d_idx",
+ ),
+ models.Index(
+ fields=["detection_count"],
+ name="scanpipe_di_detecti_d87ff1_idx",
+ ),
+ models.Index(
+ fields=['is_license_clue'],
+ name='scanpipe_di_is_lice_f4922a_idx'
+ ),
+ models.Index(
+ fields=['from_package'],
+ name='scanpipe_di_from_pa_6485b2_idx'
+ ),
+ models.Index(
+ fields=['needs_review'],
+ name='scanpipe_di_needs_r_5cff82_idx'
+ ),
+ ],
+ },
+ bases=(
+ scanpipe.models.UpdateMixin,
+ scanpipe.models.SaveProjectMessageMixin,
+ scanpipe.models.UpdateFromDataMixin,
+ models.Model,
+ ),
+ ),
+ migrations.AddConstraint(
+ model_name="discoveredlicense",
+ constraint=models.UniqueConstraint(
+ condition=models.Q(("identifier", ""), _negated=True),
+ fields=("project", "identifier"),
+ name="scanpipe_discoveredlicense_unique_license_id_within_project",
+ ),
+ ),
+ ]
diff --git a/scanpipe/models.py b/scanpipe/models.py
index 7bc6d12072..ac28627808 100644
--- a/scanpipe/models.py
+++ b/scanpipe/models.py
@@ -701,6 +701,7 @@ def delete_related_objects(self, keep_input=False, keep_labels=False):
self.projectmessages,
self.codebaserelations,
self.discovereddependencies,
+ self.discoveredlicenses,
self.codebaseresources,
self.runs,
]
@@ -1477,6 +1478,35 @@ def dependency_count(self):
"""Return the number of dependencies related to this project."""
return self.discovereddependencies.count()
+ @cached_property
+ def license_detections_count(self):
+ """Return the number of license detections in this project."""
+ return self.discoveredlicenses.count()
+
+ @cached_property
+ def package_compliance_alert_count(self):
+ """
+ Return the number of packages related to this project which have
+ a license compliance error alert.
+ """
+ return self.discoveredpackages.has_compliance_alert().count()
+
+ @cached_property
+ def license_compliance_alert_count(self):
+ """
+ Return the number of license detections related to this project
+ which have a license compliance error alert.
+ """
+ return self.discoveredlicenses.has_compliance_alert().count()
+
+ @cached_property
+ def resource_compliance_alert_count(self):
+ """
+ Return the number of codebase resources related to this project which have
+ a license compliance error alert.
+ """
+ return self.codebaseresources.has_compliance_alert().count()
+
@cached_property
def message_count(self):
"""Return the number of messages related to this project."""
@@ -2217,6 +2247,9 @@ def compliance_issues(self, severity):
return self.filter(compliance_alert__in=severity_mapping[severity])
+ def has_compliance_alert(self):
+ return self.filter(Q(compliance_alert__exact=CodebaseResource.Compliance.ERROR))
+
def convert_glob_to_django_regex(glob_pattern):
"""
@@ -2290,10 +2323,10 @@ def without_symlinks(self):
return self.filter(~Q(type=self.model.Type.SYMLINK))
def has_license_detections(self):
- return self.filter(~Q(license_detections=[]))
+ return self.filter(~Q(license_detections=[]) | ~Q(license_clues=[]))
def has_no_license_detections(self):
- return self.filter(license_detections=[])
+ return self.filter(Q(license_detections=[]) & Q(license_clues=[]))
def has_package_data(self):
return self.filter(~Q(package_data=[]))
@@ -2553,6 +2586,17 @@ def save(self, codebase=None, *args, **kwargs):
super().save(*args, **kwargs)
+ @property
+ def has_compliance_alert(self):
+ """
+ Returns True if this instance has a compliance alert of `ERROR`
+ for it's respective license_expression fields.
+ """
+ if self.compliance_alert == self.Compliance.ERROR:
+ return True
+
+ return False
+
@classmethod
def from_db(cls, db, field_names, values):
"""
@@ -3189,6 +3233,12 @@ def with_resources_count(self):
)
return self.annotate(resources_count=count_subquery)
+ def has_license_detections(self):
+ return self.filter(~Q(license_detections=[]) | ~Q(other_license_detections=[]))
+
+ def has_no_license_detections(self):
+ return self.filter(Q(license_detections=[]) & Q(other_license_detections=[]))
+
def only_package_url_fields(self, extra=None):
"""
Only select and return the UUID and PURL fields.
@@ -4145,6 +4195,225 @@ def as_spdx_package(self):
)
+class DiscoveredLicenseQuerySet(
+ ComplianceAlertQuerySetMixin,
+ ProjectRelatedQuerySet,
+):
+ def needs_review(self):
+ return self.filter(needs_review=True)
+
+ def does_not_need_review(self):
+ return self.filter(needs_review=False)
+
+ def order_by_count_and_expression(self):
+ """Order by detection count and license expression (identifer) fields."""
+ return self.order_by("-detection_count", "identifier")
+
+
+class AbstractLicenseDetection(models.Model):
+ """
+ These fields should be kept in line with
+ `licensedcode.detection.LicenseDetection`.
+ """
+
+ license_expression = models.TextField(
+ blank=True,
+ help_text=_(
+ "A license expression string using the SPDX license expression"
+ " syntax and ScanCode license keys, the effective license expression"
+ " for this license detection."
+ ),
+ )
+
+ license_expression_spdx = models.TextField(
+ blank=True,
+ help_text=_("SPDX license expression string with SPDX ids."),
+ )
+
+ matches = models.JSONField(
+ _("Reference Matches"),
+ default=list,
+ blank=True,
+ help_text=_("List of license matches combined in this detection."),
+ )
+
+ detection_log = models.JSONField(
+ default=list,
+ blank=True,
+ help_text=_(
+ "A list of detection DetectionRule explaining how "
+ "this detection was created."
+ ),
+ )
+
+ identifier = models.CharField(
+ max_length=1024,
+ blank=True,
+ help_text=_(
+ "An identifier unique for a license detection, containing the license "
+ "expression and a UUID crafted from the match contents."
+ ),
+ )
+
+ class Meta:
+ abstract = True
+
+
+class DiscoveredLicense(
+ ProjectRelatedModel,
+ SaveProjectMessageMixin,
+ UpdateFromDataMixin,
+ ComplianceAlertMixin,
+ AbstractLicenseDetection,
+):
+ """
+ A project's Discovered Licenses are the unique License Detection objects
+ discovered in the code under analysis.
+ """
+
+ license_expression_field = "license_expression"
+
+ from_package = models.BooleanField(
+ default=False,
+ help_text=_(
+ "True if this was discovered in a extracted license statement "
+ "and False if this was discovered in a file."
+ ),
+ )
+
+ is_license_clue = models.BooleanField(
+ default=False,
+ help_text=_(
+ "True if this is not a proper license detection which should be "
+ "considered in the license_expression for the parent resource/package. "
+ "A license match is considered as a clue if it could be a possible"
+ "false positives or the matched rule is tagged as a clue explicitly."
+ ),
+ )
+
+ detection_count = models.BigIntegerField(
+ blank=True,
+ null=True,
+ help_text=_("Total number of this license detection discovered."),
+ )
+
+ file_regions = models.JSONField(
+ _("Detection Locations"),
+ default=list,
+ blank=True,
+ help_text=_(
+ "A list of file regions with resource path, start and end line "
+ "details for each place this license detection was discovered at. "
+ "Also contains whether this license was discovered from a file or "
+ "from package metadata."
+ ),
+ )
+
+ needs_review = models.BooleanField(
+ default=False,
+ help_text=_(
+ "True if this was license detection needs to be reviewed "
+ "as there might be a license detection issue."
+ ),
+ )
+
+ review_comments = models.JSONField(
+ _("Review Comments"),
+ default=list,
+ blank=True,
+ help_text=_(
+ "A list of review comments for license detection issues which "
+ "needs review. These descriptive comments are based on ambigous "
+ "detection types and could also offers helpful suggestions on "
+ "how to review/report these detection issues."
+ ),
+ )
+
+ objects = DiscoveredLicenseQuerySet.as_manager()
+
+ class Meta:
+ ordering = ["detection_count", "identifier"]
+ indexes = [
+ models.Index(fields=["identifier"]),
+ models.Index(fields=["license_expression"]),
+ models.Index(fields=["license_expression_spdx"]),
+ models.Index(fields=["detection_count"]),
+ models.Index(fields=["is_license_clue"]),
+ models.Index(fields=["from_package"]),
+ models.Index(fields=["needs_review"]),
+ ]
+ constraints = [
+ models.UniqueConstraint(
+ fields=["project", "identifier"],
+ condition=~Q(identifier=""),
+ name="%(app_label)s_%(class)s_unique_license_id_within_project",
+ ),
+ ]
+
+ def __str__(self):
+ return self.identifier
+
+ @classmethod
+ def create_from_data(cls, project, detection_data, from_package=False):
+ """
+ Create and returns a DiscoveredLicense for a `project` from the
+ `detection_data`. If one of the values of the required fields is not
+ available, a "ProjectMessage" is created instead of a new
+ DiscoveredLicense instance.
+ """
+ detection_data = detection_data.copy()
+ required_fields = ["license_expression", "identifier", "matches"]
+ missing_values = [
+ field_name
+ for field_name in required_fields
+ if not detection_data.get(field_name)
+ ]
+
+ if missing_values:
+ message = (
+ f"No values for the following required fields: "
+ f"{', '.join(missing_values)}"
+ )
+
+ project.add_warning(
+ description=message,
+ model=cls,
+ details=detection_data,
+ )
+ return
+
+ cleaned_data = {
+ field_name: value
+ for field_name, value in detection_data.items()
+ if field_name in cls.model_fields() and value not in EMPTY_VALUES
+ }
+
+ discovered_license = cls(
+ project=project, from_package=from_package, **cleaned_data
+ )
+ # Using save_error=False to not capture potential errors at this level but
+ # rather in the CodebaseResource.create_and_add_license_data method so
+ # resource data can be injected in the ProjectMessage record.
+ discovered_license.save(save_error=False, capture_exception=False)
+ return discovered_license
+
+ def update_with_file_region(self, file_region, count_detection):
+ """
+ If the `file_region` is a new file region, include it in the
+ `file_regions` list and increase the `detection_count` by 1.
+ """
+ file_region_data = file_region.to_dict()
+ if file_region_data not in self.file_regions:
+ self.file_regions.append(file_region_data)
+ if count_detection:
+ if not self.detection_count:
+ self.detection_count = 1
+ else:
+ self.detection_count += 1
+
+ self.save(update_fields=["detection_count", "file_regions"])
+
+
def normalize_package_url_data(purl_mapping, ignore_nulls=False):
"""
Normalize a mapping of purl data so database queries with
diff --git a/scanpipe/pipelines/analyze_docker.py b/scanpipe/pipelines/analyze_docker.py
index 59d7a3d92c..1049d72059 100644
--- a/scanpipe/pipelines/analyze_docker.py
+++ b/scanpipe/pipelines/analyze_docker.py
@@ -42,6 +42,7 @@ def steps(cls):
cls.flag_ignored_resources,
cls.scan_for_application_packages,
cls.scan_for_files,
+ cls.collect_and_create_license_detections,
cls.analyze_scanned_files,
cls.flag_not_analyzed_codebase_resources,
)
diff --git a/scanpipe/pipelines/analyze_docker_windows.py b/scanpipe/pipelines/analyze_docker_windows.py
index d0809a51b7..90c0c5c8a8 100644
--- a/scanpipe/pipelines/analyze_docker_windows.py
+++ b/scanpipe/pipelines/analyze_docker_windows.py
@@ -45,6 +45,7 @@ def steps(cls):
cls.flag_ignored_resources,
cls.scan_for_application_packages,
cls.scan_for_files,
+ cls.collect_and_create_license_detections,
cls.analyze_scanned_files,
cls.flag_data_files_with_no_clues,
cls.flag_not_analyzed_codebase_resources,
diff --git a/scanpipe/pipelines/analyze_root_filesystem.py b/scanpipe/pipelines/analyze_root_filesystem.py
index 22e58481da..558b28c8df 100644
--- a/scanpipe/pipelines/analyze_root_filesystem.py
+++ b/scanpipe/pipelines/analyze_root_filesystem.py
@@ -45,6 +45,7 @@ def steps(cls):
cls.scan_for_application_packages,
cls.match_not_analyzed_to_system_packages,
cls.scan_for_files,
+ cls.collect_and_create_license_detections,
cls.analyze_scanned_files,
cls.flag_not_analyzed_codebase_resources,
)
@@ -121,6 +122,13 @@ def scan_for_files(self):
"""Scan unknown resources for copyrights, licenses, emails, and urls."""
scancode.scan_for_files(self.project, progress_logger=self.log)
+ def collect_and_create_license_detections(self):
+ """
+ Collect and create unique license detections from resources and
+ package data.
+ """
+ scancode.collect_and_create_license_detections(project=self.project)
+
def analyze_scanned_files(self):
"""Analyze single file scan results for completeness."""
flag.analyze_scanned_files(self.project)
diff --git a/scanpipe/pipelines/deploy_to_develop.py b/scanpipe/pipelines/deploy_to_develop.py
index 767f69c9ba..f53fbfa8e0 100644
--- a/scanpipe/pipelines/deploy_to_develop.py
+++ b/scanpipe/pipelines/deploy_to_develop.py
@@ -95,6 +95,7 @@ def steps(cls):
cls.remove_packages_without_resources,
cls.scan_unmapped_to_files,
cls.scan_mapped_from_for_files,
+ cls.collect_and_create_license_detections,
cls.flag_deployed_from_resources_with_missing_license,
cls.create_local_files_packages,
)
@@ -333,6 +334,13 @@ def scan_mapped_from_for_files(self):
scan_files = d2d.get_from_files_for_scanning(self.project.codebaseresources)
scancode.scan_for_files(self.project, scan_files, progress_logger=self.log)
+ def collect_and_create_license_detections(self):
+ """
+ Collect and create unique license detections from resources and
+ package data.
+ """
+ scancode.collect_and_create_license_detections(project=self.project)
+
def create_local_files_packages(self):
"""Create local-files packages for codebase resources not part of a package."""
d2d.create_local_files_packages(self.project)
diff --git a/scanpipe/pipelines/scan_codebase.py b/scanpipe/pipelines/scan_codebase.py
index d5bbe992c8..fd6580e456 100644
--- a/scanpipe/pipelines/scan_codebase.py
+++ b/scanpipe/pipelines/scan_codebase.py
@@ -45,6 +45,7 @@ def steps(cls):
cls.flag_ignored_resources,
cls.scan_for_application_packages,
cls.scan_for_files,
+ cls.collect_and_create_license_detections,
)
def copy_inputs_to_codebase_directory(self):
@@ -65,3 +66,10 @@ def scan_for_application_packages(self):
def scan_for_files(self):
"""Scan unknown resources for copyrights, licenses, emails, and urls."""
scancode.scan_for_files(self.project, progress_logger=self.log)
+
+ def collect_and_create_license_detections(self):
+ """
+ Collect and create unique license detections from resources and
+ package data.
+ """
+ scancode.collect_and_create_license_detections(project=self.project)
diff --git a/scanpipe/pipelines/scan_single_package.py b/scanpipe/pipelines/scan_single_package.py
index c8753b90de..605ef0ea5d 100644
--- a/scanpipe/pipelines/scan_single_package.py
+++ b/scanpipe/pipelines/scan_single_package.py
@@ -61,11 +61,14 @@ def steps(cls):
"info": True,
"license": True,
"license_text": True,
+ "license_diagnostics": True,
+ "license_text_diagnostics": True,
"license_references": True,
"package": True,
"url": True,
"classify": True,
"summary": True,
+ "todo": True,
}
def get_package_input(self):
diff --git a/scanpipe/pipes/__init__.py b/scanpipe/pipes/__init__.py
index 18a5d72c77..d3fe69f69a 100644
--- a/scanpipe/pipes/__init__.py
+++ b/scanpipe/pipes/__init__.py
@@ -36,6 +36,7 @@
from scanpipe.models import CodebaseRelation
from scanpipe.models import CodebaseResource
from scanpipe.models import DiscoveredDependency
+from scanpipe.models import DiscoveredLicense
from scanpipe.models import DiscoveredPackage
from scanpipe.pipes import scancode
@@ -320,6 +321,104 @@ def update_or_create_dependency(
return dependency
+def update_or_create_license_detection(
+ project,
+ detection_data,
+ resource_path=None,
+ from_package=False,
+ count_detection=True,
+ is_license_clue=False,
+ check_todo=False,
+):
+ """
+ Get, update or create a DiscoveredLicense object then return it.
+ Use the `project` and `detection_data` mapping to lookup and creates the
+ DiscoveredLicense using its detection identifier as a unique key.
+
+ Additonally if `resource_path` is passed, add the file region where
+ the license was detected to the DiscoveredLicense object, if not present
+ already. `from_package` is True if the license detection was in a
+ `extracted_license_statement` from a package metadata.
+ """
+ if is_license_clue:
+ detection_data = scancode.get_detection_data_from_clue(detection_data)
+
+ detection_identifier = detection_data["identifier"]
+ detection_data["is_license_clue"] = is_license_clue
+
+ license_detection = project.discoveredlicenses.get_or_none(
+ identifier=detection_identifier,
+ )
+ detection_data = _clean_license_detection_data(detection_data)
+
+ if license_detection:
+ license_detection.update_from_data(detection_data)
+ else:
+ license_detection = DiscoveredLicense.create_from_data(
+ project=project,
+ detection_data=detection_data,
+ from_package=from_package,
+ )
+
+ if not license_detection:
+ detection_data["resource_path"] = resource_path
+ project.add_error(
+ model="update_or_create_license_detection",
+ details=detection_data,
+ )
+ return
+
+ if resource_path:
+ file_region = scancode.get_file_region(
+ detection_data=detection_data,
+ resource_path=resource_path,
+ )
+ license_detection.update_with_file_region(
+ file_region=file_region,
+ count_detection=count_detection,
+ )
+
+ if check_todo:
+ scancode.check_license_detection_for_issues(license_detection)
+
+ return license_detection
+
+
+def _clean_license_detection_data(detection_data):
+ detection_data = detection_data.copy()
+ if "reference_matches" in detection_data:
+ matches = detection_data.pop("reference_matches")
+ detection_data["matches"] = matches
+
+ updated_matches = []
+ for match_data in detection_data["matches"]:
+ from_file_path = match_data["from_file"]
+ if from_file_path:
+ match_data["from_file"] = from_file_path.removeprefix("codebase/")
+
+ updated_matches.append(match_data)
+
+ detection_data["matches"] = updated_matches
+ return detection_data
+
+
+def update_license_detection_with_issue(project, todo_issue):
+ detection_data = todo_issue.get("detection")
+ if "identifier" not in detection_data:
+ return
+
+ detection_identifier = detection_data.get("identifier")
+ license_detection = project.discoveredlicenses.get_or_none(
+ identifier=detection_identifier,
+ )
+ if license_detection:
+ review_comments = todo_issue.get("review_comments").values()
+ license_detection.update(
+ needs_review=True,
+ review_comments=list(review_comments),
+ )
+
+
def get_dependencies(project, dependency_data):
"""
Given a `dependency_data` mapping, get a list of DiscoveredDependency objects
diff --git a/scanpipe/pipes/compliance.py b/scanpipe/pipes/compliance.py
index 0a0c0c776c..79be3f3f4f 100644
--- a/scanpipe/pipes/compliance.py
+++ b/scanpipe/pipes/compliance.py
@@ -105,6 +105,11 @@ def get_project_compliance_alerts(project, fail_level="error"):
.only(*PACKAGE_URL_FIELDS, "compliance_alert")
.order_by(*PACKAGE_URL_FIELDS)
)
+ licenses_qs = (
+ project.discoveredlicenses.compliance_issues(severity=fail_level)
+ .only("identifier", "compliance_alert")
+ .order_by("identifier")
+ )
resource_qs = (
project.codebaseresources.compliance_issues(severity=fail_level)
.only("path", "compliance_alert")
@@ -113,6 +118,7 @@ def get_project_compliance_alerts(project, fail_level="error"):
queryset_mapping = {
"packages": package_qs,
+ "license_detections": licenses_qs,
"resources": resource_qs,
}
diff --git a/scanpipe/pipes/input.py b/scanpipe/pipes/input.py
index 8defc41c6e..58ec2e5c96 100644
--- a/scanpipe/pipes/input.py
+++ b/scanpipe/pipes/input.py
@@ -35,6 +35,7 @@
from scanpipe.models import CodebaseRelation
from scanpipe.models import CodebaseResource
from scanpipe.models import DiscoveredDependency
+from scanpipe.models import DiscoveredLicense
from scanpipe.models import DiscoveredPackage
from scanpipe.pipes import scancode
from scanpipe.pipes.output import mappings_key_by_fieldname
@@ -97,25 +98,30 @@ def is_archive(location):
def load_inventory_from_toolkit_scan(project, input_location):
"""
- Create packages, dependencies, and resources loaded from the ScanCode-toolkit scan
- results located at ``input_location``.
+ Create license detections, packages, dependencies, and resources
+ loaded from the ScanCode-toolkit scan results located at ``input_location``.
"""
scanned_codebase = scancode.get_virtual_codebase(project, input_location)
+ scancode.create_discovered_licenses(project, scanned_codebase)
scancode.create_discovered_packages(project, scanned_codebase)
scancode.create_codebase_resources(project, scanned_codebase)
scancode.create_discovered_dependencies(
project, scanned_codebase, strip_datafile_path_root=True
)
+ scancode.load_todo_issues(project, scanned_codebase)
def load_inventory_from_scanpipe(project, scan_data, extra_data_prefix=None):
"""
- Create packages, dependencies, resources, and relations loaded from a ScanCode.io
- JSON output provided as ``scan_data``.
+ Create packages, dependencies, license detections, resources, and relations
+ loaded from a ScanCode.io JSON output provided as ``scan_data``.
An ``extra_data_prefix`` can be provided in case multiple input files are loaded
into the same project. The prefix is usually the filename of the input.
"""
+ for detection_data in scan_data.get("license_detections", []):
+ pipes.update_or_create_license_detection(project, detection_data)
+
for package_data in scan_data.get("packages", []):
pipes.update_or_create_package(project, package_data)
@@ -137,12 +143,14 @@ def load_inventory_from_scanpipe(project, scan_data, extra_data_prefix=None):
model_to_object_maker_func = {
DiscoveredPackage: pipes.update_or_create_package,
DiscoveredDependency: pipes.update_or_create_dependency,
+ DiscoveredLicense: pipes.update_or_create_license_detection,
CodebaseResource: pipes.update_or_create_resource,
CodebaseRelation: pipes.get_or_create_relation,
}
worksheet_name_to_model = {
"PACKAGES": DiscoveredPackage,
+ "LICENSE_DETECTIONS": DiscoveredLicense,
"RESOURCES": CodebaseResource,
"DEPENDENCIES": DiscoveredDependency,
"RELATIONS": CodebaseRelation,
diff --git a/scanpipe/pipes/scancode.py b/scanpipe/pipes/scancode.py
index f3501c9eca..8f169311d2 100644
--- a/scanpipe/pipes/scancode.py
+++ b/scanpipe/pipes/scancode.py
@@ -39,12 +39,20 @@
from commoncode import fileutils
from commoncode.resource import VirtualCodebase
from extractcode import api as extractcode_api
+from licensedcode.detection import DetectionCategory
+from licensedcode.detection import FileRegion
+from licensedcode.detection import LicenseDetectionFromResult
+from licensedcode.detection import LicenseMatchFromResult
+from licensedcode.detection import UniqueDetection
+from licensedcode.detection import get_ambiguous_license_detections_by_type
from packagedcode import get_package_handler
from packagedcode import models as packagedcode_models
from scancode import Scanner
from scancode import api as scancode_api
from scancode import cli as scancode_cli
from scancode.cli import run_scan as scancode_run_scan
+from summarycode.todo import ReviewComments
+from summarycode.todo import get_review_comments
from aboutcode.pipeline import LoopProgress
from scanpipe import pipes
@@ -469,6 +477,96 @@ def add_resource_to_package(package_uid, resource, project):
resource.discovered_packages.add(package)
+def collect_and_create_license_detections(project):
+ """
+ Create instances of DiscoveredLicense for `project` from the parsed
+ license detections present in the CodebaseResources and
+ DiscoveredPackages of `project`.
+ """
+ logger.info(f"Project {project} collect_license_detections:")
+
+ for resource in project.codebaseresources.has_license_detections():
+ logger.info(f" Processing: {resource.path} for licenses")
+
+ for detection_data in resource.license_detections:
+ pipes.update_or_create_license_detection(
+ project=project,
+ detection_data=detection_data,
+ resource_path=resource.path,
+ check_todo=True,
+ )
+
+ for clue_data in resource.license_clues:
+ pipes.update_or_create_license_detection(
+ project=project,
+ detection_data=clue_data,
+ resource_path=resource.path,
+ is_license_clue=True,
+ check_todo=True,
+ )
+
+ for resource in project.codebaseresources.has_package_data():
+ for package_mapping in resource.package_data:
+ package_data = packagedcode_models.PackageData.from_dict(
+ mapping=package_mapping,
+ )
+
+ for detection in package_data.license_detections:
+ pipes.update_or_create_license_detection(
+ project=project,
+ detection_data=detection,
+ resource_path=resource.path,
+ from_package=True,
+ check_todo=True,
+ )
+
+ for detection in package_data.other_license_detections:
+ pipes.update_or_create_license_detection(
+ project=project,
+ detection_data=detection,
+ resource_path=resource.path,
+ from_package=True,
+ check_todo=True,
+ )
+
+
+def get_detection_data_from_clue(clue_data):
+ """
+ From a LicenseMatch mapping, create a LicenseDetection mapping by
+ populating the identifier and license_expression fields.
+ """
+ license_match = LicenseMatchFromResult.from_dict(clue_data)
+ license_detection = LicenseDetectionFromResult.from_matches(
+ matches=[license_match],
+ analysis=DetectionCategory.LICENSE_CLUES.value,
+ )
+ license_detection.license_expression = license_match.rule.license_expression
+ license_detection.license_expression_spdx = (
+ license_match.rule.spdx_license_expression()
+ )
+ license_detection.identifier = license_detection.identifier_with_expression
+ return license_detection.to_dict(
+ include_text=True,
+ license_diagnostics=True,
+ license_text_diagnostics=True,
+ )
+
+
+def get_file_region(detection_data, resource_path):
+ """
+ From a LicenseDetection mapping `detection_data`, create a FileRegion
+ object containing information about where this license was detected
+ exactly in a codebase, with `resource_path`, with start and end lines.
+ """
+ start_line = min([match["start_line"] for match in detection_data["matches"]])
+ end_line = max([match["end_line"] for match in detection_data["matches"]])
+ return FileRegion(
+ path=resource_path,
+ start_line=start_line,
+ end_line=end_line,
+ )
+
+
def assemble_packages(project, progress_logger):
"""
Create instances of DiscoveredPackage and DiscoveredDependency for `project`
@@ -841,6 +939,42 @@ def create_codebase_resources(project, scanned_codebase):
discovered_package=package,
)
+ license_detections = getattr(scanned_resource, "license_detections", [])
+ for detection_data in license_detections:
+ detection_identifier = detection_data.get("identifier")
+ pipes.update_or_create_license_detection(
+ project=project,
+ detection_data=detection_data,
+ resource_path=resource_path,
+ count_detection=False,
+ )
+ logger.debug(f"Add {codebase_resource} to {detection_identifier}")
+
+ license_clues = getattr(scanned_resource, "license_clues", [])
+ for clue_data in license_clues:
+ pipes.update_or_create_license_detection(
+ project=project,
+ detection_data=clue_data,
+ resource_path=resource_path,
+ is_license_clue=True,
+ )
+ logger.debug(f"Add license clue at {codebase_resource}")
+
+ packages = getattr(scanned_resource, "package_data", [])
+ for package_data in packages:
+ license_detections = package_data.get("license_detections", [])
+ license_detections.extend(package_data.get("other_license_detections", []))
+ for detection_data in license_detections:
+ detection_identifier = detection_data.get("identifier")
+ pipes.update_or_create_license_detection(
+ project=project,
+ detection_data=detection_data,
+ resource_path=resource_path,
+ count_detection=False,
+ from_package=True,
+ )
+ logger.debug(f"Add {codebase_resource} to {detection_identifier}")
+
def create_discovered_packages(project, scanned_codebase):
"""
@@ -850,6 +984,16 @@ def create_discovered_packages(project, scanned_codebase):
if hasattr(scanned_codebase.attributes, "packages"):
for package_data in scanned_codebase.attributes.packages:
pipes.update_or_create_package(project, package_data)
+ license_detections = package_data.get("license_detections", [])
+ license_detections.extend(package_data.get("other_license_detections", []))
+
+ for license_detection in license_detections:
+ pipes.update_or_create_license_detection(
+ project=project,
+ detection_data=license_detection,
+ from_package=True,
+ count_detection=False,
+ )
def create_discovered_dependencies(
@@ -875,6 +1019,65 @@ def create_discovered_dependencies(
)
+def create_discovered_licenses(project, scanned_codebase):
+ """
+ Save the license detections of a ScanCode `scanned_codebase`
+ scancode.resource.Codebase object to the database as a DiscoveredLicense of
+ `project`.
+ """
+ if hasattr(scanned_codebase.attributes, "license_detections"):
+ for detection_data in scanned_codebase.attributes.license_detections:
+ pipes.update_or_create_license_detection(project, detection_data)
+
+
+def load_todo_issues(project, scanned_codebase):
+ if hasattr(scanned_codebase.attributes, "todo"):
+ for todo_issue in scanned_codebase.attributes.todo:
+ pipes.update_license_detection_with_issue(project, todo_issue)
+
+ license_clues = project.discoveredlicenses.filter(
+ is_license_clue=True,
+ )
+ license_clues.update(
+ needs_review=True,
+ review_comments=[ReviewComments.LICENSE_CLUES.value],
+ )
+
+
+def check_license_detection_for_issues(discovered_license):
+ file_regions = [
+ FileRegion(
+ path=file_region.get("path"),
+ start_line=file_region.get("start_line"),
+ end_line=file_region.get("end_line"),
+ )
+ for file_region in discovered_license.file_regions
+ ]
+ matches = [
+ LicenseMatchFromResult.from_dict(license_match)
+ for license_match in discovered_license.matches
+ ]
+ unique_detection = UniqueDetection(
+ identifier=discovered_license.identifier,
+ license_expression=discovered_license.license_expression,
+ license_expression_spdx=discovered_license.license_expression_spdx,
+ detection_count=discovered_license.detection_count,
+ detection_log=discovered_license.detection_log,
+ matches=matches,
+ file_regions=file_regions,
+ )
+ detections_by_issue_type = get_ambiguous_license_detections_by_type(
+ unique_license_detections=[unique_detection],
+ )
+ if detections_by_issue_type:
+ issue_type = next(iter(detections_by_issue_type))
+ review_comments = get_review_comments(detection_log=[issue_type])
+ discovered_license.update(
+ needs_review=True,
+ review_comments=list(review_comments.values()),
+ )
+
+
def set_codebase_resource_for_package(codebase_resource, discovered_package):
"""
Assign the `discovered_package` to the `codebase_resource` and set its
diff --git a/scanpipe/templates/scanpipe/includes/project_summary_level.html b/scanpipe/templates/scanpipe/includes/project_summary_level.html
index e2f0e25451..ac9da511ec 100644
--- a/scanpipe/templates/scanpipe/includes/project_summary_level.html
+++ b/scanpipe/templates/scanpipe/includes/project_summary_level.html
@@ -19,6 +19,12 @@
{{ project.vulnerable_package_count|intcomma }}
{% endif %}
+ {% if project.package_compliance_alert_count %}
+
+ {{ project.package_compliance_alert_count|intcomma }}
+
+
+ {% endif %}
{% else %}
0
{% endif %}
@@ -49,13 +55,32 @@
+ {% else %}
+ 0
+ {% endif %}
+
+
+
+
- {% include "scanpipe/includes/project_summary_level_item.html" with label="Resources" count=project.resource_count url=project_resources_url only %}
{% if project.relation_count %}
{% url 'project_relations' project.slug as project_relations_url %}
{% include "scanpipe/includes/project_summary_level_item.html" with label="Relations" count=project.relation_count url=project_relations_url only %}
diff --git a/scanpipe/templates/scanpipe/license_detection_detail.html b/scanpipe/templates/scanpipe/license_detection_detail.html
new file mode 100644
index 0000000000..eec34ad59d
--- /dev/null
+++ b/scanpipe/templates/scanpipe/license_detection_detail.html
@@ -0,0 +1,13 @@
+{% extends "scanpipe/base.html" %}
+{% load static %}
+
+{% block title %}ScanCode.io: {{ project.name }} - {{ object.name }}{% endblock %}
+
+{% block content %}
+
+ {% include 'scanpipe/includes/navbar_header.html' %}
+
{% include 'scanpipe/includes/messages.html' %}
+ {% include 'scanpipe/includes/breadcrumb_detail_view.html' with object_title=object.identifier url_name="project_licenses" %}
+ {% include 'scanpipe/tabset/tabset.html' %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/scanpipe/templates/scanpipe/license_detection_list.html b/scanpipe/templates/scanpipe/license_detection_list.html
new file mode 100644
index 0000000000..6ea52f5196
--- /dev/null
+++ b/scanpipe/templates/scanpipe/license_detection_list.html
@@ -0,0 +1,75 @@
+{% extends "scanpipe/base.html" %}
+
+{% block title %}ScanCode.io: {{ project.name }} - License Detections{% endblock %}
+
+{% block content %}
+
+
+
+
+
+ {% if is_paginated %}
+ {% include 'scanpipe/includes/pagination.html' with page_obj=page_obj %}
+ {% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/scanpipe/templates/scanpipe/package_list.html b/scanpipe/templates/scanpipe/package_list.html
index ee415673f8..95b2ae9220 100644
--- a/scanpipe/templates/scanpipe/package_list.html
+++ b/scanpipe/templates/scanpipe/package_list.html
@@ -30,6 +30,11 @@
{% endif %}
+ {% if package.has_compliance_alert %}
+
+
+
+ {% endif %}
diff --git a/scanpipe/templates/scanpipe/panels/license_detections_summary.html b/scanpipe/templates/scanpipe/panels/license_detections_summary.html
new file mode 100644
index 0000000000..d31b5e41ab
--- /dev/null
+++ b/scanpipe/templates/scanpipe/panels/license_detections_summary.html
@@ -0,0 +1,55 @@
+{% load humanize %}
+{% if license_detection_summary %}
+
+{% endif %}
\ No newline at end of file
diff --git a/scanpipe/templates/scanpipe/panels/resource_license_summary.html b/scanpipe/templates/scanpipe/panels/resource_license_summary.html
deleted file mode 100644
index 7af89ebe05..0000000000
--- a/scanpipe/templates/scanpipe/panels/resource_license_summary.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{% load humanize %}
-{% if resource_license_summary %}
-
-{% endif %}
\ No newline at end of file
diff --git a/scanpipe/templates/scanpipe/panels/scan_summary_panel.html b/scanpipe/templates/scanpipe/panels/scan_summary_panel.html
index 11088ade2a..0d69764b22 100644
--- a/scanpipe/templates/scanpipe/panels/scan_summary_panel.html
+++ b/scanpipe/templates/scanpipe/panels/scan_summary_panel.html
@@ -6,15 +6,80 @@
- {% for field_label, values in scan_summary.items %}
-
- |
- {{ field_label }}
- |
-
-
- {% for entry in values %}
- {% if entry.value %}
+
+ |
+ Declared license
+ |
+
+
+ {% for entry in scan_summary.declared_license_expression %}
+ {% if entry.value %}
+ -
+ {{ entry.value }}
+ {% if entry.count %}
+
+ {{ entry.count|intcomma }}
+
+ {% endif %}
+
+ {% endif %}
+ {% endfor %}
+
+ |
+
+
+ |
+ Declared holder
+ |
+
+
+ {% for entry in scan_summary.declared_holder %}
+ {% if entry.value %}
+ -
+ {{ entry.value }}
+ {% if entry.count %}
+
+ {{ entry.count|intcomma }}
+
+ {% endif %}
+
+ {% endif %}
+ {% endfor %}
+
+ |
+
+
+ |
+ Primary language
+ |
+
+
+ |
+
+
+ |
+ Other licenses
+ |
+
+
- |
-
- {% endfor %}
+
+ {% endif %}
+ {% endfor %}
+
+ |
+
+
+ |
+ Other holders
+ |
+
+
+ {% for entry in scan_summary.other_holders %}
+ {% if entry.value %}
+ -
+ {{ entry.value }}
+ {% if entry.count %}
+
+ {{ entry.count|intcomma }}
+
+ {% endif %}
+
+ {% endif %}
+ {% endfor %}
+
+ |
+
+
+ |
+ Other languages
+ |
+
+
+ |
+
+
+ |
+ Key Files
+ |
+
+
+ |
+
\ No newline at end of file
diff --git a/scanpipe/templates/scanpipe/project_detail.html b/scanpipe/templates/scanpipe/project_detail.html
index 91698c5e56..55106247b3 100644
--- a/scanpipe/templates/scanpipe/project_detail.html
+++ b/scanpipe/templates/scanpipe/project_detail.html
@@ -114,7 +114,7 @@
{% if license_policies_enabled %}
diff --git a/scanpipe/templates/scanpipe/tabset/tab_license_detections.html b/scanpipe/templates/scanpipe/tabset/tab_license_detections.html
new file mode 100644
index 0000000000..bff482ff43
--- /dev/null
+++ b/scanpipe/templates/scanpipe/tabset/tab_license_detections.html
@@ -0,0 +1,117 @@
+
+
+
+
+ | License expression |
+ Origin resource path |
+ Matched text |
+ Rule URL |
+ Score |
+ Matcher |
+ Match length |
+ Match coverage |
+ Rule relevance |
+
+
+
+ {% for match in tab_data.fields.matches.value %}
+
+ |
+ {{ match.license_expression }}
+ |
+
+ {{ match.from_file }}
+ |
+
+ {{ match.matched_text }}
+ |
+
+ {% if match.rule_url %}
+
+ {{ match.rule_identifier }}
+
+
+ {% else %}
+ {{ match.rule_identifier }}
+ {% endif %}
+ |
+
+ {{ match.score }}
+ |
+
+ {{ match.matcher }}
+ |
+
+ {{ match.matched_length }}
+ |
+
+ {{ match.match_coverage }}
+ |
+
+ {{ match.rule_relevance }}
+ |
+
+ {% endfor %}
+
+
+ {% if tab_data.fields.detection_log.value %}
+
+
+
+ | Detection log |
+
+
+
+ {% for log_entry in tab_data.fields.detection_log.value %}
+
+ |
+ {{ log_entry }}
+ |
+
+ {% endfor %}
+
+
+ {% endif %}
+ {% if tab_data.fields.review_comments.value %}
+
+
+
+ | Review Comments |
+
+
+
+ {% for comment in tab_data.fields.review_comments.value %}
+
+ |
+ {{ comment }}
+ |
+
+ {% endfor %}
+
+
+ {% endif %}
+
+
+
+ | Resource path |
+ Start line |
+ End line |
+
+
+
+ {% for file_region in tab_data.fields.file_regions.value %}
+
+ |
+ {{ file_region.path }}
+ |
+
+ {{ file_region.start_line }}
+ |
+
+ {{ file_region.end_line }}
+ |
+
+ {% endfor %}
+
+
+
\ No newline at end of file
diff --git a/scanpipe/templates/scanpipe/tabset/tab_package_detections.html b/scanpipe/templates/scanpipe/tabset/tab_package_detections.html
new file mode 100644
index 0000000000..c033a74a64
--- /dev/null
+++ b/scanpipe/templates/scanpipe/tabset/tab_package_detections.html
@@ -0,0 +1,87 @@
+
+
+
+
+ | Datafile paths |
+
+
+
+ {% for path in tab_data.fields.datafile_paths.value %}
+
+ |
+ {{ path }}
+ |
+
+ {% endfor %}
+
+
+
+
+
+ | Datasource IDs |
+
+
+
+ {% for id in tab_data.fields.datasource_ids.value %}
+
+ |
+ {{ id }}
+ |
+
+ {% endfor %}
+
+
+ {% if tab_data.fields.license_detections.value %}
+
+
+
+ | License detections |
+ License expression |
+ License expression SPDX |
+
+
+
+ {% for detection in tab_data.fields.license_detections.value %}
+
+ |
+ {{ detection.identifier }}
+ |
+
+ {{ detection.license_expression }}
+ |
+
+ {{ detection.license_expression_spdx }}
+ |
+
+ {% endfor %}
+
+
+ {% endif %}
+ {% if tab_data.fields.other_license_detections.value %}
+
+
+
+ | License detections |
+ License expression |
+ License expression SPDX |
+
+
+
+ {% for detection in tab_data.fields.other_license_detections.value %}
+
+ |
+ {{ detection.identifier }}
+
+ |
+
+ {{ detection.license_expression }}
+ |
+
+ {{ detection.license_expression_spdx }}
+ |
+
+ {% endfor %}
+
+
+ {% endif %}
+
\ No newline at end of file
diff --git a/scanpipe/templates/scanpipe/tabset/tab_packages.html b/scanpipe/templates/scanpipe/tabset/tab_packages.html
index 1370d741da..3231d4d865 100644
--- a/scanpipe/templates/scanpipe/tabset/tab_packages.html
+++ b/scanpipe/templates/scanpipe/tabset/tab_packages.html
@@ -18,6 +18,11 @@
{% endif %}
+ {% if package.has_compliance_alert %}
+
+
+
+ {% endif %}
|
{{ package.declared_license_expression }}
diff --git a/scanpipe/templates/scanpipe/tabset/tab_resource_detections.html b/scanpipe/templates/scanpipe/tabset/tab_resource_detections.html
new file mode 100644
index 0000000000..43bb635e4e
--- /dev/null
+++ b/scanpipe/templates/scanpipe/tabset/tab_resource_detections.html
@@ -0,0 +1,102 @@
+
+ {% if tab_data.fields.license_detections.value %}
+
+
+
+ | License detections |
+ License expression |
+ License expression SPDX |
+
+
+
+ {% for detection in tab_data.fields.license_detections.value %}
+
+ |
+ {{ detection.identifier }}
+ |
+
+ {{ detection.license_expression }}
+ |
+
+ {{ detection.license_expression_spdx }}
+ |
+
+ {% endfor %}
+
+
+ {% endif %}
+ {% if tab_data.fields.license_clues.value %}
+
+
+
+ | License expression |
+ License clue details |
+
+
+
+ {% for clue in tab_data.fields.license_clues.value %}
+
+ |
+ {{ clue.license_expression }}
+ |
+
+ {{ clue }}
+ |
+
+ {% endfor %}
+
+
+ {% endif %}
+ {% if tab_data.fields.emails.value %}
+
+
+
+ | Email |
+ Start line |
+ End line |
+
+
+
+ {% for email in tab_data.fields.emails.value %}
+
+ |
+ {{ email.email }}
+ |
+
+ {{ email.start_line }}
+ |
+
+ {{ email.end_line }}
+ |
+
+ {% endfor %}
+
+
+ {% endif %}
+ {% if tab_data.fields.urls.value %}
+
+
+
+ | URL |
+ Start line |
+ End line |
+
+
+
+ {% for url in tab_data.fields.urls.value %}
+
+ |
+ {{ url.url }}
+ |
+
+ {{ url.start_line }}
+ |
+
+ {{ url.end_line }}
+ |
+
+ {% endfor %}
+
+
+ {% endif %}
+
\ No newline at end of file
diff --git a/scanpipe/tests/data/asgiref/asgiref-3.3.0.spdx.json b/scanpipe/tests/data/asgiref/asgiref-3.3.0.spdx.json
index 07e04fd069..86bb3ba6f6 100644
--- a/scanpipe/tests/data/asgiref/asgiref-3.3.0.spdx.json
+++ b/scanpipe/tests/data/asgiref/asgiref-3.3.0.spdx.json
@@ -3,7 +3,7 @@
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "scancodeio_asgiref",
- "documentNamespace": "https://scancode.io/spdxdocs/1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "documentNamespace": "https://scancode.io/spdxdocs/804c3391-e6f9-415f-bb7a-cb6653853a46",
"creationInfo": {
"created": "2000-01-01T01:02:03Z",
"creators": [
@@ -14,7 +14,7 @@
"packages": [
{
"name": "asgiref",
- "SPDXID": "SPDXRef-scancodeio-discoveredpackage-b4e16c8a-f564-4379-9de9-ea2aaba08d94",
+ "SPDXID": "SPDXRef-scancodeio-discoveredpackage-9d0bdc32-1117-407a-9908-08d3558dc739",
"downloadLocation": "NOASSERTION",
"licenseConcluded": "BSD-3-Clause",
"copyrightText": "NOASSERTION",
@@ -33,7 +33,7 @@
},
{
"name": "asgiref",
- "SPDXID": "SPDXRef-scancodeio-discoveredpackage-80e083f1-7d05-432e-96f8-e6dfd9e494f0",
+ "SPDXID": "SPDXRef-scancodeio-discoveredpackage-7969de5e-5589-4441-bffa-a60e12b43280",
"downloadLocation": "NOASSERTION",
"licenseConcluded": "BSD-3-Clause",
"copyrightText": "NOASSERTION",
@@ -52,7 +52,7 @@
},
{
"name": "pytest",
- "SPDXID": "SPDXRef-scancodeio-discovereddependency-05f9bf8f-4da8-488e-9f48-6e183c4b813b",
+ "SPDXID": "SPDXRef-scancodeio-discovereddependency-4cff8bf8-197c-4698-a43a-5c793586c780",
"downloadLocation": "NOASSERTION",
"licenseConcluded": "NOASSERTION",
"copyrightText": "NOASSERTION",
@@ -68,7 +68,7 @@
},
{
"name": "pytest",
- "SPDXID": "SPDXRef-scancodeio-discovereddependency-43988fc2-bc0e-4c81-b083-7c5f21a7be50",
+ "SPDXID": "SPDXRef-scancodeio-discovereddependency-4c5c1313-3850-4f81-ac27-8d496080d667",
"downloadLocation": "NOASSERTION",
"licenseConcluded": "NOASSERTION",
"copyrightText": "NOASSERTION",
@@ -84,7 +84,7 @@
},
{
"name": "pytest-asyncio",
- "SPDXID": "SPDXRef-scancodeio-discovereddependency-ea25292c-05af-4982-9596-866c5de9d8cd",
+ "SPDXID": "SPDXRef-scancodeio-discovereddependency-f983278c-22f1-43e1-ba2b-a020d659531b",
"downloadLocation": "NOASSERTION",
"licenseConcluded": "NOASSERTION",
"copyrightText": "NOASSERTION",
@@ -100,7 +100,7 @@
},
{
"name": "pytest-asyncio",
- "SPDXID": "SPDXRef-scancodeio-discovereddependency-a0b6b6e7-5e75-4b69-9742-b04fe8a594a3",
+ "SPDXID": "SPDXRef-scancodeio-discovereddependency-98aeddb5-b81a-43d4-ac56-dc873a589fdf",
"downloadLocation": "NOASSERTION",
"licenseConcluded": "NOASSERTION",
"copyrightText": "NOASSERTION",
@@ -116,33 +116,33 @@
}
],
"documentDescribes": [
- "SPDXRef-scancodeio-discoveredpackage-b4e16c8a-f564-4379-9de9-ea2aaba08d94",
- "SPDXRef-scancodeio-discoveredpackage-80e083f1-7d05-432e-96f8-e6dfd9e494f0",
- "SPDXRef-scancodeio-discovereddependency-05f9bf8f-4da8-488e-9f48-6e183c4b813b",
- "SPDXRef-scancodeio-discovereddependency-43988fc2-bc0e-4c81-b083-7c5f21a7be50",
- "SPDXRef-scancodeio-discovereddependency-ea25292c-05af-4982-9596-866c5de9d8cd",
- "SPDXRef-scancodeio-discovereddependency-a0b6b6e7-5e75-4b69-9742-b04fe8a594a3"
+ "SPDXRef-scancodeio-discoveredpackage-9d0bdc32-1117-407a-9908-08d3558dc739",
+ "SPDXRef-scancodeio-discoveredpackage-7969de5e-5589-4441-bffa-a60e12b43280",
+ "SPDXRef-scancodeio-discovereddependency-4cff8bf8-197c-4698-a43a-5c793586c780",
+ "SPDXRef-scancodeio-discovereddependency-4c5c1313-3850-4f81-ac27-8d496080d667",
+ "SPDXRef-scancodeio-discovereddependency-f983278c-22f1-43e1-ba2b-a020d659531b",
+ "SPDXRef-scancodeio-discovereddependency-98aeddb5-b81a-43d4-ac56-dc873a589fdf"
],
"files": [],
"relationships": [
{
- "spdxElementId": "SPDXRef-scancodeio-discovereddependency-05f9bf8f-4da8-488e-9f48-6e183c4b813b",
- "relatedSpdxElement": "SPDXRef-scancodeio-discoveredpackage-b4e16c8a-f564-4379-9de9-ea2aaba08d94",
+ "spdxElementId": "SPDXRef-scancodeio-discovereddependency-4cff8bf8-197c-4698-a43a-5c793586c780",
+ "relatedSpdxElement": "SPDXRef-scancodeio-discoveredpackage-9d0bdc32-1117-407a-9908-08d3558dc739",
"relationshipType": "DEPENDENCY_OF"
},
{
- "spdxElementId": "SPDXRef-scancodeio-discovereddependency-43988fc2-bc0e-4c81-b083-7c5f21a7be50",
- "relatedSpdxElement": "SPDXRef-scancodeio-discoveredpackage-80e083f1-7d05-432e-96f8-e6dfd9e494f0",
+ "spdxElementId": "SPDXRef-scancodeio-discovereddependency-4c5c1313-3850-4f81-ac27-8d496080d667",
+ "relatedSpdxElement": "SPDXRef-scancodeio-discoveredpackage-7969de5e-5589-4441-bffa-a60e12b43280",
"relationshipType": "DEPENDENCY_OF"
},
{
- "spdxElementId": "SPDXRef-scancodeio-discovereddependency-ea25292c-05af-4982-9596-866c5de9d8cd",
- "relatedSpdxElement": "SPDXRef-scancodeio-discoveredpackage-b4e16c8a-f564-4379-9de9-ea2aaba08d94",
+ "spdxElementId": "SPDXRef-scancodeio-discovereddependency-f983278c-22f1-43e1-ba2b-a020d659531b",
+ "relatedSpdxElement": "SPDXRef-scancodeio-discoveredpackage-9d0bdc32-1117-407a-9908-08d3558dc739",
"relationshipType": "DEPENDENCY_OF"
},
{
- "spdxElementId": "SPDXRef-scancodeio-discovereddependency-a0b6b6e7-5e75-4b69-9742-b04fe8a594a3",
- "relatedSpdxElement": "SPDXRef-scancodeio-discoveredpackage-80e083f1-7d05-432e-96f8-e6dfd9e494f0",
+ "spdxElementId": "SPDXRef-scancodeio-discovereddependency-98aeddb5-b81a-43d4-ac56-dc873a589fdf",
+ "relatedSpdxElement": "SPDXRef-scancodeio-discoveredpackage-7969de5e-5589-4441-bffa-a60e12b43280",
"relationshipType": "DEPENDENCY_OF"
}
],
diff --git a/scanpipe/tests/data/asgiref/asgiref-3.3.0_fixtures.json b/scanpipe/tests/data/asgiref/asgiref-3.3.0_fixtures.json
index 91d9a866b7..12d2e50319 100644
--- a/scanpipe/tests/data/asgiref/asgiref-3.3.0_fixtures.json
+++ b/scanpipe/tests/data/asgiref/asgiref-3.3.0_fixtures.json
@@ -1,13 +1,13 @@
[
{
"model": "scanpipe.project",
- "pk": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "pk": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"fields": {
"extra_data": {},
- "created_date": "2025-06-30T19:56:37.439Z",
+ "created_date": "2025-07-16T15:27:02.698Z",
"name": "asgiref",
- "slug": "asgiref-1cdd3f3a",
- "work_directory": "/tmp/tmpw3r_lrtn/projects/asgiref-1cdd3f3a",
+ "slug": "asgiref-804c3391",
+ "work_directory": "/tmp/tmp7oqqjl0j/projects/asgiref-804c3391",
"is_archived": false,
"notes": "",
"settings": {},
@@ -16,17 +16,17 @@
},
{
"model": "scanpipe.run",
- "pk": "5bd1dcf6-b369-45a6-a2dc-dfe05f9a25cf",
+ "pk": "6188f85f-da57-473f-84e0-e75723714e2d",
"fields": {
"task_id": null,
"task_start_date": null,
"task_end_date": null,
"task_exitcode": null,
"task_output": "",
- "log": "2025-06-30 19:56:37.442 Pipeline [scan_codebase] starting\n2025-06-30 19:56:37.443 Step [download_missing_inputs] starting\n2025-06-30 19:56:37.444 Step [download_missing_inputs] completed in 0 seconds\n2025-06-30 19:56:37.445 Step [copy_inputs_to_codebase_directory] starting\n2025-06-30 19:56:37.446 Step [copy_inputs_to_codebase_directory] completed in 0 seconds\n2025-06-30 19:56:37.446 Step [extract_archives] starting\n2025-06-30 19:56:37.502 Step [extract_archives] completed in 0 seconds\n2025-06-30 19:56:37.504 Step [collect_and_create_codebase_resources] starting\n2025-06-30 19:56:37.705 Step [collect_and_create_codebase_resources] completed in 0 seconds\n2025-06-30 19:56:37.707 Step [flag_empty_files] starting\n2025-06-30 19:56:37.709 Step [flag_empty_files] completed in 0 seconds\n2025-06-30 19:56:37.710 Step [flag_ignored_resources] starting\n2025-06-30 19:56:37.716 Step [flag_ignored_resources] completed in 0 seconds\n2025-06-30 19:56:37.717 Step [scan_for_application_packages] starting\n2025-06-30 19:56:37.718 Collecting package data from resources:\n2025-06-30 19:56:37.800 Progress: 11% (2/18) ETA: 1 seconds\n2025-06-30 19:56:37.831 Progress: 22% (4/18)\n2025-06-30 19:56:37.834 Progress: 33% (6/18)\n2025-06-30 19:56:37.836 Progress: 44% (8/18)\n2025-06-30 19:56:37.837 Progress: 55% (10/18)\n2025-06-30 19:56:37.839 Progress: 66% (12/18)\n2025-06-30 19:56:37.844 Progress: 77% (14/18)\n2025-06-30 19:56:37.862 Progress: 88% (16/18)\n2025-06-30 19:56:41.912 Progress: 100% (18/18)\n2025-06-30 19:56:41.974 Assembling collected package data:\n2025-06-30 19:56:41.975 Progress: 0%\n2025-06-30 19:56:42.044 Step [scan_for_application_packages] completed in 4 seconds\n2025-06-30 19:56:42.045 Step [scan_for_files] starting\n2025-06-30 19:57:51.517 Progress: 12% (2/16) ETA: 509 seconds (8.5 minutes)\n2025-06-30 19:57:51.803 Progress: 25% (4/16) ETA: 209 seconds (3.5 minutes)\n2025-06-30 19:57:51.819 Progress: 37% (6/16) ETA: 119 seconds (2.0 minutes)\n2025-06-30 19:57:51.852 Progress: 50% (8/16) ETA: 70 seconds (1.2 minutes)\n2025-06-30 19:57:51.875 Progress: 62% (10/16) ETA: 43 seconds\n2025-06-30 19:57:51.899 Progress: 75% (12/16) ETA: 23 seconds\n2025-06-30 19:57:51.927 Progress: 87% (14/16) ETA: 10 seconds\n2025-06-30 19:57:52.377 Progress: 100% (16/16)\n2025-06-30 19:57:52.765 Step [scan_for_files] completed in 71 seconds (1.2 minutes)\n2025-06-30 19:57:52.767 Pipeline completed in 75 seconds (1.3 minutes)\n",
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "log": "2025-07-16 15:27:02.700 Pipeline [scan_codebase] starting\n2025-07-16 15:27:02.701 Step [download_missing_inputs] starting\n2025-07-16 15:27:02.702 Step [download_missing_inputs] completed in 0 seconds\n2025-07-16 15:27:02.703 Step [copy_inputs_to_codebase_directory] starting\n2025-07-16 15:27:02.703 Step [copy_inputs_to_codebase_directory] completed in 0 seconds\n2025-07-16 15:27:02.704 Step [extract_archives] starting\n2025-07-16 15:27:02.756 Step [extract_archives] completed in 0 seconds\n2025-07-16 15:27:02.758 Step [collect_and_create_codebase_resources] starting\n2025-07-16 15:27:02.929 Step [collect_and_create_codebase_resources] completed in 0 seconds\n2025-07-16 15:27:02.930 Step [flag_empty_files] starting\n2025-07-16 15:27:02.932 Step [flag_empty_files] completed in 0 seconds\n2025-07-16 15:27:02.933 Step [flag_ignored_resources] starting\n2025-07-16 15:27:02.935 Step [flag_ignored_resources] completed in 0 seconds\n2025-07-16 15:27:02.936 Step [scan_for_application_packages] starting\n2025-07-16 15:27:02.937 Collecting package data from resources:\n2025-07-16 15:27:02.978 Progress: 11% (2/18)\n2025-07-16 15:27:03.005 Progress: 22% (4/18)\n2025-07-16 15:27:03.014 Progress: 33% (6/18)\n2025-07-16 15:27:03.017 Progress: 44% (8/18)\n2025-07-16 15:27:03.019 Progress: 55% (10/18)\n2025-07-16 15:27:03.020 Progress: 66% (12/18)\n2025-07-16 15:27:03.021 Progress: 77% (14/18)\n2025-07-16 15:27:03.022 Progress: 88% (16/18)\n2025-07-16 15:27:06.312 Progress: 100% (18/18)\n2025-07-16 15:27:06.372 Assembling collected package data:\n2025-07-16 15:27:06.373 Progress: 0%\n2025-07-16 15:27:09.130 Step [scan_for_application_packages] completed in 6 seconds\n2025-07-16 15:27:09.131 Step [scan_for_files] starting\n2025-07-16 15:27:09.467 Progress: 12% (2/16) ETA: 2 seconds\n2025-07-16 15:27:09.475 Progress: 25% (4/16) ETA: 1 seconds\n2025-07-16 15:27:09.484 Progress: 37% (6/16) ETA: 1 seconds\n2025-07-16 15:27:09.700 Progress: 50% (8/16) ETA: 1 seconds\n2025-07-16 15:27:09.877 Progress: 62% (10/16)\n2025-07-16 15:27:09.933 Progress: 75% (12/16)\n2025-07-16 15:27:09.958 Progress: 87% (14/16)\n2025-07-16 15:27:10.158 Progress: 100% (16/16)\n2025-07-16 15:27:10.204 Step [scan_for_files] completed in 1 seconds\n2025-07-16 15:27:10.206 Step [collect_and_create_license_detections] starting\n2025-07-16 15:27:10.221 Step [collect_and_create_license_detections] completed in 0 seconds\n2025-07-16 15:27:10.222 Pipeline completed in 8 seconds\n",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"pipeline_name": "scan_codebase",
- "created_date": "2025-06-30T19:56:37.441Z",
+ "created_date": "2025-07-16T15:27:02.699Z",
"scancodeio_version": "",
"description": "Scan a codebase for application packages, licenses, and copyrights.",
"current_step": "",
@@ -43,7 +43,7 @@
"sha256": "a5098bc870b80e7b872bff60bb363c7f2c2c89078759f6c47b53ff8c525a152e",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -353,7 +353,7 @@
"sha256": "",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -398,7 +398,7 @@
"sha256": "",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -443,7 +443,7 @@
"sha256": "",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -488,7 +488,7 @@
"sha256": "6e89108c2cf0c0446174188f76f60465ae1c1f14f83427807df40d52a27cb2c8",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -533,12 +533,35 @@
"sha256": "b846415d1b514e9c1dff14a22deb906d794bc546ca6129f950a18cd091e2a669",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "detected_license_expression": "",
- "detected_license_expression_spdx": "",
- "license_detections": [],
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "detected_license_expression": "bsd-new",
+ "detected_license_expression_spdx": "BSD-3-Clause",
+ "license_detections": [
+ {
+ "matches": [
+ {
+ "score": 100.0,
+ "matcher": "2-aho",
+ "end_line": 27,
+ "rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/bsd-new_683.RULE",
+ "from_file": null,
+ "start_line": 4,
+ "matched_text": "Redistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n 1. Redistributions of source code must retain the above copyright notice,\n this list of conditions and the following disclaimer.\n\n 2. Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in the\n documentation and/or other materials provided with the distribution.\n\n 3. Neither the name of Django nor the names of its contributors may be used\n to endorse or promote products derived from this software without\n specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.",
+ "match_coverage": 100.0,
+ "matched_length": 214,
+ "rule_relevance": 100,
+ "rule_identifier": "bsd-new_683.RULE",
+ "license_expression": "bsd-new",
+ "license_expression_spdx": "BSD-3-Clause"
+ }
+ ],
+ "identifier": "bsd_new-72cae3bc-4423-3a9e-be84-ee8bb5120a4d",
+ "license_expression": "bsd-new",
+ "license_expression_spdx": "BSD-3-Clause"
+ }
+ ],
"license_clues": [],
- "percentage_of_license_text": null,
+ "percentage_of_license_text": 95.11,
"copyrights": [
{
"end_line": 1,
@@ -556,7 +579,7 @@
"authors": [],
"emails": [],
"urls": [],
- "compliance_alert": "",
+ "compliance_alert": "error",
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -590,7 +613,7 @@
"sha256": "70f98f4eb9f6068b192b5464fcdf69e29a8ff09962bfce84bbb052baeee44f33",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -900,7 +923,7 @@
"sha256": "11546323af45e6a5639bf620a9c4d73e74c0bf705f494af4595007b923f75e8a",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -945,7 +968,7 @@
"sha256": "2c1983592aa38f0bfb0afacc73ddc5b46ce10e8e89ceaa9fed1e5fc6361b608d",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -990,7 +1013,7 @@
"sha256": "30f49b9094bff904a42caeec32515715fe625a56dc48bd7c0e3d9988c0ad4bd7",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1035,7 +1058,7 @@
"sha256": "fa4651a3b79201a4dc44a4096cd49ec8f427e912ea0ee05c666357b413a8afe7",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1080,7 +1103,7 @@
"sha256": "ee0fcf4a8e6fa9df8a4643bb48e82892d496afce44b6c8b8aea2721755545e1c",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1125,7 +1148,7 @@
"sha256": "3151f66c476208c3154cb6c4fb557a2a253bab82f0ab33fb3c8b9f7976be9e33",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1170,7 +1193,7 @@
"sha256": "ddd445b778c097fc75c2bf69ad964cbadd3bd6999d1dd2306d39d401855e8e3e",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1215,7 +1238,7 @@
"sha256": "ddbc8d455eceb68fc583c67e7c4ad0277c867fb39095c51ec5b37f70342e8334",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1260,12 +1283,35 @@
"sha256": "126c3e3a8a75a517d2739612304607804cf5f34da63fa25d03a6f11f7edb6f2f",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "detected_license_expression": "",
- "detected_license_expression_spdx": "",
- "license_detections": [],
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "detected_license_expression": "apache-2.0",
+ "detected_license_expression_spdx": "Apache-2.0",
+ "license_detections": [
+ {
+ "matches": [
+ {
+ "score": 100.0,
+ "matcher": "2-aho",
+ "end_line": 2,
+ "rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/apache-2.0_174.RULE",
+ "from_file": null,
+ "start_line": 2,
+ "matched_text": "# under the Apache 2.0 license. You may see the original project at",
+ "match_coverage": 100.0,
+ "matched_length": 6,
+ "rule_relevance": 100,
+ "rule_identifier": "apache-2.0_174.RULE",
+ "license_expression": "apache-2.0",
+ "license_expression_spdx": "Apache-2.0"
+ }
+ ],
+ "identifier": "apache_2_0-5a90b5fa-5d10-5a98-d2a7-ef089c46a900",
+ "license_expression": "apache-2.0",
+ "license_expression_spdx": "Apache-2.0"
+ }
+ ],
"license_clues": [],
- "percentage_of_license_text": null,
+ "percentage_of_license_text": 1.22,
"copyrights": [],
"holders": [],
"authors": [],
@@ -1287,7 +1333,7 @@
"start_line": 83
}
],
- "compliance_alert": "",
+ "compliance_alert": "error",
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -1321,7 +1367,7 @@
"sha256": "f8bd1ea3fb8afddabb10f8efd66796d41446cad51168ef4d3c44b19c973d0ad0",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1366,7 +1412,7 @@
"sha256": "885267fee0fea687875a02ceb929ca095312d47aaa57e20e4ce382f397caaf4d",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1412,7 +1458,7 @@
"version": "3.3.0",
"qualifiers": "",
"subpath": "",
- "uuid": "b4e16c8a-f564-4379-9de9-ea2aaba08d94",
+ "uuid": "9d0bdc32-1117-407a-9908-08d3558dc739",
"md5": "",
"sha1": "",
"sha256": "",
@@ -1422,8 +1468,8 @@
"Documentation": "https://asgi.readthedocs.io/",
"Further Documentation": "https://docs.djangoproject.com/en/stable/topics/async/#async-adapter-functions"
},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "compliance_alert": "",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "compliance_alert": "error",
"affected_by_vulnerabilities": [],
"filename": "",
"primary_language": "Python",
@@ -1513,7 +1559,7 @@
],
"missing_resources": [],
"modified_resources": [],
- "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=4bb66559-05cb-4015-9f0a-4b08353b6b52",
+ "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=e62e0385-a279-4d5a-b2a5-7f0cfb21d7bd",
"keywords": [
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
@@ -1546,7 +1592,7 @@
"version": "3.3.0",
"qualifiers": "",
"subpath": "",
- "uuid": "80e083f1-7d05-432e-96f8-e6dfd9e494f0",
+ "uuid": "7969de5e-5589-4441-bffa-a60e12b43280",
"md5": "",
"sha1": "",
"sha256": "",
@@ -1556,8 +1602,8 @@
"Documentation": "https://asgi.readthedocs.io/",
"Further Documentation": "https://docs.djangoproject.com/en/stable/topics/async/#async-adapter-functions"
},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "compliance_alert": "",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "compliance_alert": "error",
"affected_by_vulnerabilities": [],
"filename": "",
"primary_language": "Python",
@@ -1647,7 +1693,7 @@
],
"missing_resources": [],
"modified_resources": [],
- "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790",
+ "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663",
"keywords": [
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
@@ -1693,10 +1739,10 @@
"version": "",
"qualifiers": "",
"subpath": "",
- "uuid": "05f9bf8f-4da8-488e-9f48-6e183c4b813b",
+ "uuid": "4cff8bf8-197c-4698-a43a-5c793586c780",
"affected_by_vulnerabilities": [],
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "dependency_uid": "pkg:pypi/pytest?uuid=7adc83d4-42ba-42eb-acf2-6a7be8295ba4",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "dependency_uid": "pkg:pypi/pytest?uuid=55081a17-fcd9-4032-9ef3-fca2f63bd943",
"for_package": 1,
"resolved_to_package": null,
"datafile_resource": 1,
@@ -1719,10 +1765,10 @@
"version": "",
"qualifiers": "",
"subpath": "",
- "uuid": "ea25292c-05af-4982-9596-866c5de9d8cd",
+ "uuid": "f983278c-22f1-43e1-ba2b-a020d659531b",
"affected_by_vulnerabilities": [],
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=194690bd-fe7e-49a7-880f-4e543f60deb8",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=4a12f26f-dc2c-42af-80d0-f719add6c4f5",
"for_package": 1,
"resolved_to_package": null,
"datafile_resource": 1,
@@ -1745,10 +1791,10 @@
"version": "",
"qualifiers": "",
"subpath": "",
- "uuid": "43988fc2-bc0e-4c81-b083-7c5f21a7be50",
+ "uuid": "4c5c1313-3850-4f81-ac27-8d496080d667",
"affected_by_vulnerabilities": [],
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "dependency_uid": "pkg:pypi/pytest?uuid=2f480940-dae0-455c-9ecc-3a558e082a8f",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "dependency_uid": "pkg:pypi/pytest?uuid=ea00e9bf-8060-43e7-9ee4-4617f438636d",
"for_package": 2,
"resolved_to_package": null,
"datafile_resource": 7,
@@ -1771,10 +1817,10 @@
"version": "",
"qualifiers": "",
"subpath": "",
- "uuid": "a0b6b6e7-5e75-4b69-9742-b04fe8a594a3",
+ "uuid": "98aeddb5-b81a-43d4-ac56-dc873a589fdf",
"affected_by_vulnerabilities": [],
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=60082b5f-bb24-4225-8fd9-0d7868dc6d2e",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=6ec16aca-ca98-4200-b1f9-021d8cd9b833",
"for_package": 2,
"resolved_to_package": null,
"datafile_resource": 7,
diff --git a/scanpipe/tests/data/asgiref/asgiref-3.3.0_scanpipe_output.json b/scanpipe/tests/data/asgiref/asgiref-3.3.0_scanpipe_output.json
index 0ff39cbfad..036360b7a2 100644
--- a/scanpipe/tests/data/asgiref/asgiref-3.3.0_scanpipe_output.json
+++ b/scanpipe/tests/data/asgiref/asgiref-3.3.0_scanpipe_output.json
@@ -2,18 +2,18 @@
"headers": [
{
"tool_name": "scanpipe",
- "tool_version": "v35.0.0-7-gbe98fc4e",
+ "tool_version": "v35.1.0-17-gde96a5af",
"other_tools": [
"pkg:pypi/scancode-toolkit@32.4.0"
],
"notice": "Generated with ScanCode.io and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied.\nNo content created from ScanCode.io should be considered or used as legal advice.\nConsult an Attorney for any legal advice.\nScanCode.io is a free software code scanning tool from nexB Inc. and others\nlicensed under the Apache License version 2.0.\nScanCode is a trademark of nexB Inc.\nVisit https://github.com/nexB/scancode.io for support and download.\n",
- "uuid": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "created_date": "2025-06-30T19:56:37.439Z",
+ "uuid": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "created_date": "2025-07-16T15:27:02.698Z",
"notes": "",
"settings": {},
"input_sources": [
{
- "uuid": "1240025f-b37e-473e-bdd6-77391b367f2a",
+ "uuid": "25a5874c-6913-4542-a301-8678a189c461",
"filename": "asgiref-3.3.0-py3-none-any.whl",
"download_url": "",
"is_uploaded": true,
@@ -30,15 +30,15 @@
"description": "Scan a codebase for application packages, licenses, and copyrights.",
"selected_groups": null,
"selected_steps": null,
- "uuid": "5bd1dcf6-b369-45a6-a2dc-dfe05f9a25cf",
- "created_date": "2025-06-30T19:56:37.441763Z",
+ "uuid": "6188f85f-da57-473f-84e0-e75723714e2d",
+ "created_date": "2025-07-16T15:27:02.699851Z",
"scancodeio_version": "",
"task_id": null,
"task_start_date": null,
"task_end_date": null,
"task_exitcode": null,
"task_output": "",
- "log": "2025-06-30 19:56:37.442 Pipeline [scan_codebase] starting\n2025-06-30 19:56:37.443 Step [download_missing_inputs] starting\n2025-06-30 19:56:37.444 Step [download_missing_inputs] completed in 0 seconds\n2025-06-30 19:56:37.445 Step [copy_inputs_to_codebase_directory] starting\n2025-06-30 19:56:37.446 Step [copy_inputs_to_codebase_directory] completed in 0 seconds\n2025-06-30 19:56:37.446 Step [extract_archives] starting\n2025-06-30 19:56:37.502 Step [extract_archives] completed in 0 seconds\n2025-06-30 19:56:37.504 Step [collect_and_create_codebase_resources] starting\n2025-06-30 19:56:37.705 Step [collect_and_create_codebase_resources] completed in 0 seconds\n2025-06-30 19:56:37.707 Step [flag_empty_files] starting\n2025-06-30 19:56:37.709 Step [flag_empty_files] completed in 0 seconds\n2025-06-30 19:56:37.710 Step [flag_ignored_resources] starting\n2025-06-30 19:56:37.716 Step [flag_ignored_resources] completed in 0 seconds\n2025-06-30 19:56:37.717 Step [scan_for_application_packages] starting\n2025-06-30 19:56:37.718 Collecting package data from resources:\n2025-06-30 19:56:37.800 Progress: 11% (2/18) ETA: 1 seconds\n2025-06-30 19:56:37.831 Progress: 22% (4/18)\n2025-06-30 19:56:37.834 Progress: 33% (6/18)\n2025-06-30 19:56:37.836 Progress: 44% (8/18)\n2025-06-30 19:56:37.837 Progress: 55% (10/18)\n2025-06-30 19:56:37.839 Progress: 66% (12/18)\n2025-06-30 19:56:37.844 Progress: 77% (14/18)\n2025-06-30 19:56:37.862 Progress: 88% (16/18)\n2025-06-30 19:56:41.912 Progress: 100% (18/18)\n2025-06-30 19:56:41.974 Assembling collected package data:\n2025-06-30 19:56:41.975 Progress: 0%\n2025-06-30 19:56:42.044 Step [scan_for_application_packages] completed in 4 seconds\n2025-06-30 19:56:42.045 Step [scan_for_files] starting\n2025-06-30 19:57:51.517 Progress: 12% (2/16) ETA: 509 seconds (8.5 minutes)\n2025-06-30 19:57:51.803 Progress: 25% (4/16) ETA: 209 seconds (3.5 minutes)\n2025-06-30 19:57:51.819 Progress: 37% (6/16) ETA: 119 seconds (2.0 minutes)\n2025-06-30 19:57:51.852 Progress: 50% (8/16) ETA: 70 seconds (1.2 minutes)\n2025-06-30 19:57:51.875 Progress: 62% (10/16) ETA: 43 seconds\n2025-06-30 19:57:51.899 Progress: 75% (12/16) ETA: 23 seconds\n2025-06-30 19:57:51.927 Progress: 87% (14/16) ETA: 10 seconds\n2025-06-30 19:57:52.377 Progress: 100% (16/16)\n2025-06-30 19:57:52.765 Step [scan_for_files] completed in 71 seconds (1.2 minutes)\n2025-06-30 19:57:52.767 Pipeline completed in 75 seconds (1.3 minutes)\n",
+ "log": "2025-07-16 15:27:02.700 Pipeline [scan_codebase] starting\n2025-07-16 15:27:02.701 Step [download_missing_inputs] starting\n2025-07-16 15:27:02.702 Step [download_missing_inputs] completed in 0 seconds\n2025-07-16 15:27:02.703 Step [copy_inputs_to_codebase_directory] starting\n2025-07-16 15:27:02.703 Step [copy_inputs_to_codebase_directory] completed in 0 seconds\n2025-07-16 15:27:02.704 Step [extract_archives] starting\n2025-07-16 15:27:02.756 Step [extract_archives] completed in 0 seconds\n2025-07-16 15:27:02.758 Step [collect_and_create_codebase_resources] starting\n2025-07-16 15:27:02.929 Step [collect_and_create_codebase_resources] completed in 0 seconds\n2025-07-16 15:27:02.930 Step [flag_empty_files] starting\n2025-07-16 15:27:02.932 Step [flag_empty_files] completed in 0 seconds\n2025-07-16 15:27:02.933 Step [flag_ignored_resources] starting\n2025-07-16 15:27:02.935 Step [flag_ignored_resources] completed in 0 seconds\n2025-07-16 15:27:02.936 Step [scan_for_application_packages] starting\n2025-07-16 15:27:02.937 Collecting package data from resources:\n2025-07-16 15:27:02.978 Progress: 11% (2/18)\n2025-07-16 15:27:03.005 Progress: 22% (4/18)\n2025-07-16 15:27:03.014 Progress: 33% (6/18)\n2025-07-16 15:27:03.017 Progress: 44% (8/18)\n2025-07-16 15:27:03.019 Progress: 55% (10/18)\n2025-07-16 15:27:03.020 Progress: 66% (12/18)\n2025-07-16 15:27:03.021 Progress: 77% (14/18)\n2025-07-16 15:27:03.022 Progress: 88% (16/18)\n2025-07-16 15:27:06.312 Progress: 100% (18/18)\n2025-07-16 15:27:06.372 Assembling collected package data:\n2025-07-16 15:27:06.373 Progress: 0%\n2025-07-16 15:27:09.130 Step [scan_for_application_packages] completed in 6 seconds\n2025-07-16 15:27:09.131 Step [scan_for_files] starting\n2025-07-16 15:27:09.467 Progress: 12% (2/16) ETA: 2 seconds\n2025-07-16 15:27:09.475 Progress: 25% (4/16) ETA: 1 seconds\n2025-07-16 15:27:09.484 Progress: 37% (6/16) ETA: 1 seconds\n2025-07-16 15:27:09.700 Progress: 50% (8/16) ETA: 1 seconds\n2025-07-16 15:27:09.877 Progress: 62% (10/16)\n2025-07-16 15:27:09.933 Progress: 75% (12/16)\n2025-07-16 15:27:09.958 Progress: 87% (14/16)\n2025-07-16 15:27:10.158 Progress: 100% (16/16)\n2025-07-16 15:27:10.204 Step [scan_for_files] completed in 1 seconds\n2025-07-16 15:27:10.206 Step [collect_and_create_license_detections] starting\n2025-07-16 15:27:10.221 Step [collect_and_create_license_detections] completed in 0 seconds\n2025-07-16 15:27:10.222 Pipeline completed in 8 seconds\n",
"execution_time": null
}
],
@@ -150,7 +150,7 @@
"other_license_expression_spdx": "",
"other_license_detections": [],
"extracted_license_statement": "license: BSD\nclassifiers:\n - 'License :: OSI Approved :: BSD License'\n",
- "compliance_alert": "",
+ "compliance_alert": "error",
"notice_text": "",
"source_packages": [],
"extra_data": {
@@ -158,7 +158,7 @@
"Documentation": "https://asgi.readthedocs.io/",
"Further Documentation": "https://docs.djangoproject.com/en/stable/topics/async/#async-adapter-functions"
},
- "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=4bb66559-05cb-4015-9f0a-4b08353b6b52",
+ "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=e62e0385-a279-4d5a-b2a5-7f0cfb21d7bd",
"is_private": false,
"is_virtual": false,
"datasource_ids": [
@@ -275,7 +275,7 @@
"other_license_expression_spdx": "",
"other_license_detections": [],
"extracted_license_statement": "license: BSD\nclassifiers:\n - 'License :: OSI Approved :: BSD License'\n",
- "compliance_alert": "",
+ "compliance_alert": "error",
"notice_text": "",
"source_packages": [],
"extra_data": {
@@ -283,7 +283,7 @@
"Documentation": "https://asgi.readthedocs.io/",
"Further Documentation": "https://docs.djangoproject.com/en/stable/topics/async/#async-adapter-functions"
},
- "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790",
+ "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663",
"is_private": false,
"is_virtual": false,
"datasource_ids": [
@@ -307,8 +307,8 @@
"is_optional": true,
"is_pinned": false,
"is_direct": true,
- "dependency_uid": "pkg:pypi/pytest?uuid=7adc83d4-42ba-42eb-acf2-6a7be8295ba4",
- "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=4bb66559-05cb-4015-9f0a-4b08353b6b52",
+ "dependency_uid": "pkg:pypi/pytest?uuid=55081a17-fcd9-4032-9ef3-fca2f63bd943",
+ "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=e62e0385-a279-4d5a-b2a5-7f0cfb21d7bd",
"resolved_to_package_uid": null,
"datafile_path": "asgiref-3.3.0-py3-none-any.whl",
"datasource_id": "pypi_wheel",
@@ -323,8 +323,8 @@
"is_optional": true,
"is_pinned": false,
"is_direct": true,
- "dependency_uid": "pkg:pypi/pytest?uuid=2f480940-dae0-455c-9ecc-3a558e082a8f",
- "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790",
+ "dependency_uid": "pkg:pypi/pytest?uuid=ea00e9bf-8060-43e7-9ee4-4617f438636d",
+ "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663",
"resolved_to_package_uid": null,
"datafile_path": "asgiref-3.3.0-py3-none-any.whl-extract/asgiref-3.3.0.dist-info/METADATA",
"datasource_id": "pypi_wheel_metadata",
@@ -339,8 +339,8 @@
"is_optional": true,
"is_pinned": false,
"is_direct": true,
- "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=194690bd-fe7e-49a7-880f-4e543f60deb8",
- "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=4bb66559-05cb-4015-9f0a-4b08353b6b52",
+ "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=4a12f26f-dc2c-42af-80d0-f719add6c4f5",
+ "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=e62e0385-a279-4d5a-b2a5-7f0cfb21d7bd",
"resolved_to_package_uid": null,
"datafile_path": "asgiref-3.3.0-py3-none-any.whl",
"datasource_id": "pypi_wheel",
@@ -355,8 +355,8 @@
"is_optional": true,
"is_pinned": false,
"is_direct": true,
- "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=60082b5f-bb24-4225-8fd9-0d7868dc6d2e",
- "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790",
+ "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=6ec16aca-ca98-4200-b1f9-021d8cd9b833",
+ "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663",
"resolved_to_package_uid": null,
"datafile_path": "asgiref-3.3.0-py3-none-any.whl-extract/asgiref-3.3.0.dist-info/METADATA",
"datasource_id": "pypi_wheel_metadata",
@@ -371,7 +371,7 @@
"name": "asgiref-3.3.0-py3-none-any.whl",
"status": "application-package",
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=4bb66559-05cb-4015-9f0a-4b08353b6b52"
+ "pkg:pypi/asgiref@3.3.0?uuid=e62e0385-a279-4d5a-b2a5-7f0cfb21d7bd"
],
"tag": "",
"extension": ".whl",
@@ -798,7 +798,7 @@
"name": "LICENSE",
"status": "scanned",
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
],
"tag": "",
"extension": "",
@@ -806,12 +806,35 @@
"mime_type": "text/plain",
"file_type": "ASCII text",
"programming_language": "",
- "detected_license_expression": "",
- "detected_license_expression_spdx": "",
- "license_detections": [],
+ "detected_license_expression": "bsd-new",
+ "detected_license_expression_spdx": "BSD-3-Clause",
+ "license_detections": [
+ {
+ "matches": [
+ {
+ "score": 100.0,
+ "matcher": "2-aho",
+ "end_line": 27,
+ "rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/bsd-new_683.RULE",
+ "from_file": null,
+ "start_line": 4,
+ "matched_text": "Redistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n 1. Redistributions of source code must retain the above copyright notice,\n this list of conditions and the following disclaimer.\n\n 2. Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in the\n documentation and/or other materials provided with the distribution.\n\n 3. Neither the name of Django nor the names of its contributors may be used\n to endorse or promote products derived from this software without\n specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.",
+ "match_coverage": 100.0,
+ "matched_length": 214,
+ "rule_relevance": 100,
+ "rule_identifier": "bsd-new_683.RULE",
+ "license_expression": "bsd-new",
+ "license_expression_spdx": "BSD-3-Clause"
+ }
+ ],
+ "identifier": "bsd_new-72cae3bc-4423-3a9e-be84-ee8bb5120a4d",
+ "license_expression": "bsd-new",
+ "license_expression_spdx": "BSD-3-Clause"
+ }
+ ],
"license_clues": [],
- "percentage_of_license_text": null,
- "compliance_alert": "",
+ "percentage_of_license_text": 95.11,
+ "compliance_alert": "error",
"copyrights": [
{
"end_line": 1,
@@ -852,7 +875,7 @@
"name": "METADATA",
"status": "application-package",
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
],
"tag": "",
"extension": "",
@@ -1159,7 +1182,7 @@
"name": "RECORD",
"status": "scanned",
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
],
"tag": "",
"extension": "",
@@ -1201,7 +1224,7 @@
"name": "top_level.txt",
"status": "scanned",
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
],
"tag": "",
"extension": ".txt",
@@ -1243,7 +1266,7 @@
"name": "WHEEL",
"status": "scanned",
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
],
"tag": "",
"extension": "",
@@ -1285,7 +1308,7 @@
"name": "compatibility.py",
"status": "scanned",
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
],
"tag": "",
"extension": ".py",
@@ -1327,7 +1350,7 @@
"name": "current_thread_executor.py",
"status": "scanned",
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
],
"tag": "",
"extension": ".py",
@@ -1369,7 +1392,7 @@
"name": "__init__.py",
"status": "scanned",
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
],
"tag": "",
"extension": ".py",
@@ -1411,7 +1434,7 @@
"name": "local.py",
"status": "scanned",
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
],
"tag": "",
"extension": ".py",
@@ -1453,7 +1476,7 @@
"name": "server.py",
"status": "scanned",
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
],
"tag": "",
"extension": ".py",
@@ -1495,7 +1518,7 @@
"name": "sync.py",
"status": "scanned",
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
],
"tag": "",
"extension": ".py",
@@ -1537,7 +1560,7 @@
"name": "testing.py",
"status": "scanned",
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
],
"tag": "",
"extension": ".py",
@@ -1579,7 +1602,7 @@
"name": "timeout.py",
"status": "scanned",
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
],
"tag": "",
"extension": ".py",
@@ -1587,12 +1610,35 @@
"mime_type": "text/x-script.python",
"file_type": "Python script, ASCII text executable",
"programming_language": "Python",
- "detected_license_expression": "",
- "detected_license_expression_spdx": "",
- "license_detections": [],
+ "detected_license_expression": "apache-2.0",
+ "detected_license_expression_spdx": "Apache-2.0",
+ "license_detections": [
+ {
+ "matches": [
+ {
+ "score": 100.0,
+ "matcher": "2-aho",
+ "end_line": 2,
+ "rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/apache-2.0_174.RULE",
+ "from_file": null,
+ "start_line": 2,
+ "matched_text": "# under the Apache 2.0 license. You may see the original project at",
+ "match_coverage": 100.0,
+ "matched_length": 6,
+ "rule_relevance": 100,
+ "rule_identifier": "apache-2.0_174.RULE",
+ "license_expression": "apache-2.0",
+ "license_expression_spdx": "Apache-2.0"
+ }
+ ],
+ "identifier": "apache_2_0-5a90b5fa-5d10-5a98-d2a7-ef089c46a900",
+ "license_expression": "apache-2.0",
+ "license_expression_spdx": "Apache-2.0"
+ }
+ ],
"license_clues": [],
- "percentage_of_license_text": null,
- "compliance_alert": "",
+ "percentage_of_license_text": 1.22,
+ "compliance_alert": "error",
"copyrights": [],
"holders": [],
"authors": [],
@@ -1637,7 +1683,7 @@
"name": "wsgi.py",
"status": "scanned",
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
],
"tag": "",
"extension": ".py",
diff --git a/scanpipe/tests/data/asgiref/asgiref-3.3.0_toolkit_scan.json b/scanpipe/tests/data/asgiref/asgiref-3.3.0_toolkit_scan.json
index 5981ab7ad6..c15f607f50 100644
--- a/scanpipe/tests/data/asgiref/asgiref-3.3.0_toolkit_scan.json
+++ b/scanpipe/tests/data/asgiref/asgiref-3.3.0_toolkit_scan.json
@@ -10,10 +10,10 @@
"--package": true
},
"notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.",
- "start_timestamp": "2025-06-30T195752.768874",
- "end_timestamp": "2025-06-30T195756.929632",
+ "start_timestamp": "2025-07-16T152710.223094",
+ "end_timestamp": "2025-07-16T152711.545101",
"output_format_version": "4.1.0",
- "duration": 4.160773515701294,
+ "duration": 1.322021245956421,
"message": null,
"errors": [],
"warnings": [],
@@ -21,8 +21,8 @@
"system_environment": {
"operating_system": "linux",
"cpu_architecture": "64",
- "platform": "Linux-5.15.0-141-generic-x86_64-with-glibc2.35",
- "platform_version": "#151-Ubuntu SMP Sun May 18 21:35:19 UTC 2025",
+ "platform": "Linux-5.15.0-143-generic-x86_64-with-glibc2.35",
+ "platform_version": "#153-Ubuntu SMP Fri Jun 13 19:10:45 UTC 2025",
"python_version": "3.10.12 (main, May 27 2025, 17:12:29) [GCC 11.4.0]"
},
"spdx_license_list_version": "3.26",
@@ -140,7 +140,7 @@
"repository_homepage_url": "https://pypi.org/project/asgiref",
"repository_download_url": "https://pypi.org/packages/source/a/asgiref/asgiref-3.3.0.tar.gz",
"api_data_url": "https://pypi.org/pypi/asgiref/3.3.0/json",
- "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=e8949b32-8d37-482a-8f0a-1e0fe8d3dac2",
+ "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=1688f96b-4f7c-4f59-9221-532b427b4560",
"datafile_paths": [
"codebase/asgiref-3.3.0-py3-none-any.whl"
],
@@ -258,7 +258,7 @@
"repository_homepage_url": "https://pypi.org/project/asgiref",
"repository_download_url": "https://pypi.org/packages/source/a/asgiref/asgiref-3.3.0.tar.gz",
"api_data_url": "https://pypi.org/pypi/asgiref/3.3.0/json",
- "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041",
+ "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4",
"datafile_paths": [
"codebase/asgiref-3.3.0-py3-none-any.whl-extract/asgiref-3.3.0.dist-info/METADATA"
],
@@ -279,8 +279,8 @@
"is_direct": true,
"resolved_package": {},
"extra_data": {},
- "dependency_uid": "pkg:pypi/pytest?uuid=f4533087-433f-431a-9ce8-e47e4d051fdc",
- "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=e8949b32-8d37-482a-8f0a-1e0fe8d3dac2",
+ "dependency_uid": "pkg:pypi/pytest?uuid=b152065e-125b-4bd1-8d60-f2de45b0ea14",
+ "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=1688f96b-4f7c-4f59-9221-532b427b4560",
"datafile_path": "codebase/asgiref-3.3.0-py3-none-any.whl",
"datasource_id": "pypi_wheel"
},
@@ -294,8 +294,8 @@
"is_direct": true,
"resolved_package": {},
"extra_data": {},
- "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=62bb86cc-68d1-432a-ae89-5f733b45ecc3",
- "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=e8949b32-8d37-482a-8f0a-1e0fe8d3dac2",
+ "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=e5c4c0c5-4671-44c5-b8c1-68d9acb07d8e",
+ "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=1688f96b-4f7c-4f59-9221-532b427b4560",
"datafile_path": "codebase/asgiref-3.3.0-py3-none-any.whl",
"datasource_id": "pypi_wheel"
},
@@ -309,8 +309,8 @@
"is_direct": true,
"resolved_package": {},
"extra_data": {},
- "dependency_uid": "pkg:pypi/pytest?uuid=6b229106-c7bd-4511-bbb3-e7e47d4eb237",
- "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041",
+ "dependency_uid": "pkg:pypi/pytest?uuid=3b443d57-63ce-4383-afe9-71ce4d1efbc6",
+ "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4",
"datafile_path": "codebase/asgiref-3.3.0-py3-none-any.whl-extract/asgiref-3.3.0.dist-info/METADATA",
"datasource_id": "pypi_wheel_metadata"
},
@@ -324,8 +324,8 @@
"is_direct": true,
"resolved_package": {},
"extra_data": {},
- "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=6fafad63-6d5e-40e0-8851-5e0eec53b71e",
- "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041",
+ "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=a7c13ec9-3218-4464-8844-2565c4b0a5ac",
+ "for_package_uid": "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4",
"datafile_path": "codebase/asgiref-3.3.0-py3-none-any.whl-extract/asgiref-3.3.0.dist-info/METADATA",
"datasource_id": "pypi_wheel_metadata"
}
@@ -464,7 +464,7 @@
"base_name": "asgiref-3.3.0-py3-none-any",
"extension": ".whl",
"size": 19948,
- "date": "2025-06-30",
+ "date": "2025-07-16",
"sha1": "c03f67211a311b13d1294ac8af7cb139ee34c4f9",
"md5": "5bce1df6dedc53a41a9a6b40d7b1699e",
"sha256": "a5098bc870b80e7b872bff60bb363c7f2c2c89078759f6c47b53ff8c525a152e",
@@ -745,7 +745,7 @@
}
],
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=e8949b32-8d37-482a-8f0a-1e0fe8d3dac2"
+ "pkg:pypi/asgiref@3.3.0?uuid=1688f96b-4f7c-4f59-9221-532b427b4560"
],
"detected_license_expression": null,
"detected_license_expression_spdx": null,
@@ -855,7 +855,7 @@
"is_script": false,
"package_data": [],
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041"
+ "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4"
],
"detected_license_expression": null,
"detected_license_expression_spdx": null,
@@ -893,7 +893,7 @@
"is_script": true,
"package_data": [],
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041"
+ "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4"
],
"detected_license_expression": null,
"detected_license_expression_spdx": null,
@@ -931,7 +931,7 @@
"is_script": true,
"package_data": [],
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041"
+ "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4"
],
"detected_license_expression": null,
"detected_license_expression_spdx": null,
@@ -969,7 +969,7 @@
"is_script": true,
"package_data": [],
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041"
+ "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4"
],
"detected_license_expression": null,
"detected_license_expression_spdx": null,
@@ -1007,7 +1007,7 @@
"is_script": true,
"package_data": [],
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041"
+ "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4"
],
"detected_license_expression": null,
"detected_license_expression_spdx": null,
@@ -1045,7 +1045,7 @@
"is_script": true,
"package_data": [],
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041"
+ "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4"
],
"detected_license_expression": null,
"detected_license_expression_spdx": null,
@@ -1083,7 +1083,7 @@
"is_script": true,
"package_data": [],
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041"
+ "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4"
],
"detected_license_expression": null,
"detected_license_expression_spdx": null,
@@ -1121,7 +1121,7 @@
"is_script": true,
"package_data": [],
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041"
+ "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4"
],
"detected_license_expression": "apache-2.0",
"detected_license_expression_spdx": "Apache-2.0",
@@ -1181,7 +1181,7 @@
"is_script": true,
"package_data": [],
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041"
+ "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4"
],
"detected_license_expression": null,
"detected_license_expression_spdx": null,
@@ -1255,7 +1255,7 @@
"is_script": false,
"package_data": [],
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041"
+ "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4"
],
"detected_license_expression": "bsd-new",
"detected_license_expression_spdx": "BSD-3-Clause",
@@ -1592,7 +1592,7 @@
}
],
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041"
+ "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4"
],
"detected_license_expression": "bsd-new",
"detected_license_expression_spdx": "BSD-3-Clause",
@@ -1679,7 +1679,7 @@
"is_script": false,
"package_data": [],
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041"
+ "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4"
],
"detected_license_expression": null,
"detected_license_expression_spdx": null,
@@ -1717,7 +1717,7 @@
"is_script": false,
"package_data": [],
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041"
+ "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4"
],
"detected_license_expression": null,
"detected_license_expression_spdx": null,
@@ -1755,7 +1755,7 @@
"is_script": false,
"package_data": [],
"for_packages": [
- "pkg:pypi/asgiref@3.3.0?uuid=b9e196d1-7cc1-4d20-a6f8-845e5e596041"
+ "pkg:pypi/asgiref@3.3.0?uuid=f91c7642-bbbc-4735-a558-274d192826f4"
],
"detected_license_expression": null,
"detected_license_expression_spdx": null,
diff --git a/scanpipe/tests/data/asgiref/asgiref-3.3.0_walk_test_fixtures.json b/scanpipe/tests/data/asgiref/asgiref-3.3.0_walk_test_fixtures.json
index 85f1ac3688..5d9a6b79e7 100644
--- a/scanpipe/tests/data/asgiref/asgiref-3.3.0_walk_test_fixtures.json
+++ b/scanpipe/tests/data/asgiref/asgiref-3.3.0_walk_test_fixtures.json
@@ -1,13 +1,13 @@
[
{
"model": "scanpipe.project",
- "pk": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "pk": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"fields": {
"extra_data": {},
- "created_date": "2025-06-30T19:56:37.439Z",
+ "created_date": "2025-07-16T15:27:02.698Z",
"name": "asgiref",
- "slug": "asgiref-1cdd3f3a",
- "work_directory": "/tmp/tmpw3r_lrtn/projects/asgiref-1cdd3f3a",
+ "slug": "asgiref-804c3391",
+ "work_directory": "/tmp/tmp7oqqjl0j/projects/asgiref-804c3391",
"is_archived": false,
"notes": "",
"settings": {},
@@ -16,17 +16,17 @@
},
{
"model": "scanpipe.run",
- "pk": "5bd1dcf6-b369-45a6-a2dc-dfe05f9a25cf",
+ "pk": "6188f85f-da57-473f-84e0-e75723714e2d",
"fields": {
"task_id": null,
"task_start_date": null,
"task_end_date": null,
"task_exitcode": null,
"task_output": "",
- "log": "2025-06-30 19:56:37.442 Pipeline [scan_codebase] starting\n2025-06-30 19:56:37.443 Step [download_missing_inputs] starting\n2025-06-30 19:56:37.444 Step [download_missing_inputs] completed in 0 seconds\n2025-06-30 19:56:37.445 Step [copy_inputs_to_codebase_directory] starting\n2025-06-30 19:56:37.446 Step [copy_inputs_to_codebase_directory] completed in 0 seconds\n2025-06-30 19:56:37.446 Step [extract_archives] starting\n2025-06-30 19:56:37.502 Step [extract_archives] completed in 0 seconds\n2025-06-30 19:56:37.504 Step [collect_and_create_codebase_resources] starting\n2025-06-30 19:56:37.705 Step [collect_and_create_codebase_resources] completed in 0 seconds\n2025-06-30 19:56:37.707 Step [flag_empty_files] starting\n2025-06-30 19:56:37.709 Step [flag_empty_files] completed in 0 seconds\n2025-06-30 19:56:37.710 Step [flag_ignored_resources] starting\n2025-06-30 19:56:37.716 Step [flag_ignored_resources] completed in 0 seconds\n2025-06-30 19:56:37.717 Step [scan_for_application_packages] starting\n2025-06-30 19:56:37.718 Collecting package data from resources:\n2025-06-30 19:56:37.800 Progress: 11% (2/18) ETA: 1 seconds\n2025-06-30 19:56:37.831 Progress: 22% (4/18)\n2025-06-30 19:56:37.834 Progress: 33% (6/18)\n2025-06-30 19:56:37.836 Progress: 44% (8/18)\n2025-06-30 19:56:37.837 Progress: 55% (10/18)\n2025-06-30 19:56:37.839 Progress: 66% (12/18)\n2025-06-30 19:56:37.844 Progress: 77% (14/18)\n2025-06-30 19:56:37.862 Progress: 88% (16/18)\n2025-06-30 19:56:41.912 Progress: 100% (18/18)\n2025-06-30 19:56:41.974 Assembling collected package data:\n2025-06-30 19:56:41.975 Progress: 0%\n2025-06-30 19:56:42.044 Step [scan_for_application_packages] completed in 4 seconds\n2025-06-30 19:56:42.045 Step [scan_for_files] starting\n2025-06-30 19:57:51.517 Progress: 12% (2/16) ETA: 509 seconds (8.5 minutes)\n2025-06-30 19:57:51.803 Progress: 25% (4/16) ETA: 209 seconds (3.5 minutes)\n2025-06-30 19:57:51.819 Progress: 37% (6/16) ETA: 119 seconds (2.0 minutes)\n2025-06-30 19:57:51.852 Progress: 50% (8/16) ETA: 70 seconds (1.2 minutes)\n2025-06-30 19:57:51.875 Progress: 62% (10/16) ETA: 43 seconds\n2025-06-30 19:57:51.899 Progress: 75% (12/16) ETA: 23 seconds\n2025-06-30 19:57:51.927 Progress: 87% (14/16) ETA: 10 seconds\n2025-06-30 19:57:52.377 Progress: 100% (16/16)\n2025-06-30 19:57:52.765 Step [scan_for_files] completed in 71 seconds (1.2 minutes)\n2025-06-30 19:57:52.767 Pipeline completed in 75 seconds (1.3 minutes)\n",
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "log": "2025-07-16 15:27:02.700 Pipeline [scan_codebase] starting\n2025-07-16 15:27:02.701 Step [download_missing_inputs] starting\n2025-07-16 15:27:02.702 Step [download_missing_inputs] completed in 0 seconds\n2025-07-16 15:27:02.703 Step [copy_inputs_to_codebase_directory] starting\n2025-07-16 15:27:02.703 Step [copy_inputs_to_codebase_directory] completed in 0 seconds\n2025-07-16 15:27:02.704 Step [extract_archives] starting\n2025-07-16 15:27:02.756 Step [extract_archives] completed in 0 seconds\n2025-07-16 15:27:02.758 Step [collect_and_create_codebase_resources] starting\n2025-07-16 15:27:02.929 Step [collect_and_create_codebase_resources] completed in 0 seconds\n2025-07-16 15:27:02.930 Step [flag_empty_files] starting\n2025-07-16 15:27:02.932 Step [flag_empty_files] completed in 0 seconds\n2025-07-16 15:27:02.933 Step [flag_ignored_resources] starting\n2025-07-16 15:27:02.935 Step [flag_ignored_resources] completed in 0 seconds\n2025-07-16 15:27:02.936 Step [scan_for_application_packages] starting\n2025-07-16 15:27:02.937 Collecting package data from resources:\n2025-07-16 15:27:02.978 Progress: 11% (2/18)\n2025-07-16 15:27:03.005 Progress: 22% (4/18)\n2025-07-16 15:27:03.014 Progress: 33% (6/18)\n2025-07-16 15:27:03.017 Progress: 44% (8/18)\n2025-07-16 15:27:03.019 Progress: 55% (10/18)\n2025-07-16 15:27:03.020 Progress: 66% (12/18)\n2025-07-16 15:27:03.021 Progress: 77% (14/18)\n2025-07-16 15:27:03.022 Progress: 88% (16/18)\n2025-07-16 15:27:06.312 Progress: 100% (18/18)\n2025-07-16 15:27:06.372 Assembling collected package data:\n2025-07-16 15:27:06.373 Progress: 0%\n2025-07-16 15:27:09.130 Step [scan_for_application_packages] completed in 6 seconds\n2025-07-16 15:27:09.131 Step [scan_for_files] starting\n2025-07-16 15:27:09.467 Progress: 12% (2/16) ETA: 2 seconds\n2025-07-16 15:27:09.475 Progress: 25% (4/16) ETA: 1 seconds\n2025-07-16 15:27:09.484 Progress: 37% (6/16) ETA: 1 seconds\n2025-07-16 15:27:09.700 Progress: 50% (8/16) ETA: 1 seconds\n2025-07-16 15:27:09.877 Progress: 62% (10/16)\n2025-07-16 15:27:09.933 Progress: 75% (12/16)\n2025-07-16 15:27:09.958 Progress: 87% (14/16)\n2025-07-16 15:27:10.158 Progress: 100% (16/16)\n2025-07-16 15:27:10.204 Step [scan_for_files] completed in 1 seconds\n2025-07-16 15:27:10.206 Step [collect_and_create_license_detections] starting\n2025-07-16 15:27:10.221 Step [collect_and_create_license_detections] completed in 0 seconds\n2025-07-16 15:27:10.222 Pipeline completed in 8 seconds\n",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"pipeline_name": "scan_codebase",
- "created_date": "2025-06-30T19:56:37.441Z",
+ "created_date": "2025-07-16T15:27:02.699Z",
"scancodeio_version": "",
"description": "Scan a codebase for application packages, licenses, and copyrights.",
"current_step": "",
@@ -43,7 +43,7 @@
"sha256": "a5098bc870b80e7b872bff60bb363c7f2c2c89078759f6c47b53ff8c525a152e",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -353,7 +353,7 @@
"sha256": "",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -398,7 +398,7 @@
"sha256": "",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -443,7 +443,7 @@
"sha256": "",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -488,7 +488,7 @@
"sha256": "6e89108c2cf0c0446174188f76f60465ae1c1f14f83427807df40d52a27cb2c8",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -533,12 +533,35 @@
"sha256": "b846415d1b514e9c1dff14a22deb906d794bc546ca6129f950a18cd091e2a669",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "detected_license_expression": "",
- "detected_license_expression_spdx": "",
- "license_detections": [],
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "detected_license_expression": "bsd-new",
+ "detected_license_expression_spdx": "BSD-3-Clause",
+ "license_detections": [
+ {
+ "matches": [
+ {
+ "score": 100.0,
+ "matcher": "2-aho",
+ "end_line": 27,
+ "rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/bsd-new_683.RULE",
+ "from_file": null,
+ "start_line": 4,
+ "matched_text": "Redistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n 1. Redistributions of source code must retain the above copyright notice,\n this list of conditions and the following disclaimer.\n\n 2. Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in the\n documentation and/or other materials provided with the distribution.\n\n 3. Neither the name of Django nor the names of its contributors may be used\n to endorse or promote products derived from this software without\n specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.",
+ "match_coverage": 100.0,
+ "matched_length": 214,
+ "rule_relevance": 100,
+ "rule_identifier": "bsd-new_683.RULE",
+ "license_expression": "bsd-new",
+ "license_expression_spdx": "BSD-3-Clause"
+ }
+ ],
+ "identifier": "bsd_new-72cae3bc-4423-3a9e-be84-ee8bb5120a4d",
+ "license_expression": "bsd-new",
+ "license_expression_spdx": "BSD-3-Clause"
+ }
+ ],
"license_clues": [],
- "percentage_of_license_text": null,
+ "percentage_of_license_text": 95.11,
"copyrights": [
{
"end_line": 1,
@@ -556,7 +579,7 @@
"authors": [],
"emails": [],
"urls": [],
- "compliance_alert": "",
+ "compliance_alert": "error",
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -590,7 +613,7 @@
"sha256": "70f98f4eb9f6068b192b5464fcdf69e29a8ff09962bfce84bbb052baeee44f33",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -900,7 +923,7 @@
"sha256": "11546323af45e6a5639bf620a9c4d73e74c0bf705f494af4595007b923f75e8a",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -945,7 +968,7 @@
"sha256": "2c1983592aa38f0bfb0afacc73ddc5b46ce10e8e89ceaa9fed1e5fc6361b608d",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -990,7 +1013,7 @@
"sha256": "30f49b9094bff904a42caeec32515715fe625a56dc48bd7c0e3d9988c0ad4bd7",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1035,7 +1058,7 @@
"sha256": "fa4651a3b79201a4dc44a4096cd49ec8f427e912ea0ee05c666357b413a8afe7",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1080,7 +1103,7 @@
"sha256": "ee0fcf4a8e6fa9df8a4643bb48e82892d496afce44b6c8b8aea2721755545e1c",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1125,7 +1148,7 @@
"sha256": "3151f66c476208c3154cb6c4fb557a2a253bab82f0ab33fb3c8b9f7976be9e33",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1170,7 +1193,7 @@
"sha256": "ddd445b778c097fc75c2bf69ad964cbadd3bd6999d1dd2306d39d401855e8e3e",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1215,7 +1238,7 @@
"sha256": "ddbc8d455eceb68fc583c67e7c4ad0277c867fb39095c51ec5b37f70342e8334",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1260,12 +1283,35 @@
"sha256": "126c3e3a8a75a517d2739612304607804cf5f34da63fa25d03a6f11f7edb6f2f",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "detected_license_expression": "",
- "detected_license_expression_spdx": "",
- "license_detections": [],
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "detected_license_expression": "apache-2.0",
+ "detected_license_expression_spdx": "Apache-2.0",
+ "license_detections": [
+ {
+ "matches": [
+ {
+ "score": 100.0,
+ "matcher": "2-aho",
+ "end_line": 2,
+ "rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/apache-2.0_174.RULE",
+ "from_file": null,
+ "start_line": 2,
+ "matched_text": "# under the Apache 2.0 license. You may see the original project at",
+ "match_coverage": 100.0,
+ "matched_length": 6,
+ "rule_relevance": 100,
+ "rule_identifier": "apache-2.0_174.RULE",
+ "license_expression": "apache-2.0",
+ "license_expression_spdx": "Apache-2.0"
+ }
+ ],
+ "identifier": "apache_2_0-5a90b5fa-5d10-5a98-d2a7-ef089c46a900",
+ "license_expression": "apache-2.0",
+ "license_expression_spdx": "Apache-2.0"
+ }
+ ],
"license_clues": [],
- "percentage_of_license_text": null,
+ "percentage_of_license_text": 1.22,
"copyrights": [],
"holders": [],
"authors": [],
@@ -1287,7 +1333,7 @@
"start_line": 83
}
],
- "compliance_alert": "",
+ "compliance_alert": "error",
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -1321,7 +1367,7 @@
"sha256": "f8bd1ea3fb8afddabb10f8efd66796d41446cad51168ef4d3c44b19c973d0ad0",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1366,7 +1412,7 @@
"sha256": "885267fee0fea687875a02ceb929ca095312d47aaa57e20e4ce382f397caaf4d",
"sha512": "",
"extra_data": {},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"detected_license_expression": "",
"detected_license_expression_spdx": "",
"license_detections": [],
@@ -1412,7 +1458,7 @@
"version": "3.3.0",
"qualifiers": "",
"subpath": "",
- "uuid": "b4e16c8a-f564-4379-9de9-ea2aaba08d94",
+ "uuid": "9d0bdc32-1117-407a-9908-08d3558dc739",
"md5": "",
"sha1": "",
"sha256": "",
@@ -1422,8 +1468,8 @@
"Documentation": "https://asgi.readthedocs.io/",
"Further Documentation": "https://docs.djangoproject.com/en/stable/topics/async/#async-adapter-functions"
},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "compliance_alert": "",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "compliance_alert": "error",
"affected_by_vulnerabilities": [],
"filename": "",
"primary_language": "Python",
@@ -1513,7 +1559,7 @@
],
"missing_resources": [],
"modified_resources": [],
- "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=4bb66559-05cb-4015-9f0a-4b08353b6b52",
+ "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=e62e0385-a279-4d5a-b2a5-7f0cfb21d7bd",
"keywords": [
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
@@ -1546,7 +1592,7 @@
"version": "3.3.0",
"qualifiers": "",
"subpath": "",
- "uuid": "80e083f1-7d05-432e-96f8-e6dfd9e494f0",
+ "uuid": "7969de5e-5589-4441-bffa-a60e12b43280",
"md5": "",
"sha1": "",
"sha256": "",
@@ -1556,8 +1602,8 @@
"Documentation": "https://asgi.readthedocs.io/",
"Further Documentation": "https://docs.djangoproject.com/en/stable/topics/async/#async-adapter-functions"
},
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "compliance_alert": "",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "compliance_alert": "error",
"affected_by_vulnerabilities": [],
"filename": "",
"primary_language": "Python",
@@ -1647,7 +1693,7 @@
],
"missing_resources": [],
"modified_resources": [],
- "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790",
+ "package_uid": "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663",
"keywords": [
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
@@ -1693,10 +1739,10 @@
"version": "",
"qualifiers": "",
"subpath": "",
- "uuid": "05f9bf8f-4da8-488e-9f48-6e183c4b813b",
+ "uuid": "4cff8bf8-197c-4698-a43a-5c793586c780",
"affected_by_vulnerabilities": [],
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "dependency_uid": "pkg:pypi/pytest?uuid=7adc83d4-42ba-42eb-acf2-6a7be8295ba4",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "dependency_uid": "pkg:pypi/pytest?uuid=55081a17-fcd9-4032-9ef3-fca2f63bd943",
"for_package": 1,
"resolved_to_package": null,
"datafile_resource": 1,
@@ -1719,10 +1765,10 @@
"version": "",
"qualifiers": "",
"subpath": "",
- "uuid": "ea25292c-05af-4982-9596-866c5de9d8cd",
+ "uuid": "f983278c-22f1-43e1-ba2b-a020d659531b",
"affected_by_vulnerabilities": [],
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=194690bd-fe7e-49a7-880f-4e543f60deb8",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=4a12f26f-dc2c-42af-80d0-f719add6c4f5",
"for_package": 1,
"resolved_to_package": null,
"datafile_resource": 1,
@@ -1745,10 +1791,10 @@
"version": "",
"qualifiers": "",
"subpath": "",
- "uuid": "43988fc2-bc0e-4c81-b083-7c5f21a7be50",
+ "uuid": "4c5c1313-3850-4f81-ac27-8d496080d667",
"affected_by_vulnerabilities": [],
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "dependency_uid": "pkg:pypi/pytest?uuid=2f480940-dae0-455c-9ecc-3a558e082a8f",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "dependency_uid": "pkg:pypi/pytest?uuid=ea00e9bf-8060-43e7-9ee4-4617f438636d",
"for_package": 2,
"resolved_to_package": null,
"datafile_resource": 7,
@@ -1771,10 +1817,10 @@
"version": "",
"qualifiers": "",
"subpath": "",
- "uuid": "a0b6b6e7-5e75-4b69-9742-b04fe8a594a3",
+ "uuid": "98aeddb5-b81a-43d4-ac56-dc873a589fdf",
"affected_by_vulnerabilities": [],
- "project": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
- "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=60082b5f-bb24-4225-8fd9-0d7868dc6d2e",
+ "project": "804c3391-e6f9-415f-bb7a-cb6653853a46",
+ "dependency_uid": "pkg:pypi/pytest-asyncio?uuid=6ec16aca-ca98-4200-b1f9-021d8cd9b833",
"for_package": 2,
"resolved_to_package": null,
"datafile_resource": 7,
diff --git a/scanpipe/tests/data/cyclonedx/asgiref-3.3.0.cdx.json b/scanpipe/tests/data/cyclonedx/asgiref-3.3.0.cdx.json
index 65d6d6c530..d354df7e99 100644
--- a/scanpipe/tests/data/cyclonedx/asgiref-3.3.0.cdx.json
+++ b/scanpipe/tests/data/cyclonedx/asgiref-3.3.0.cdx.json
@@ -6,7 +6,7 @@
"version": 1,
"metadata": {
"component": {
- "bom-ref": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd",
+ "bom-ref": "804c3391-e6f9-415f-bb7a-cb6653853a46",
"name": "asgiref",
"type": "library"
},
@@ -26,9 +26,16 @@
},
"components": [
{
- "bom-ref": "pkg:pypi/asgiref@3.3.0?uuid=4bb66559-05cb-4015-9f0a-4b08353b6b52",
+ "bom-ref": "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663",
"copyright": "",
"description": "ASGI specs, helper code, and adapters\nasgiref\n=======\n\n.. image:: https://api.travis-ci.org/django/asgiref.svg\n :target: https://travis-ci.org/django/asgiref\n\n.. image:: https://img.shields.io/pypi/v/asgiref.svg\n :target: https://pypi.python.org/pypi/asgiref\n\nASGI is a standard for Python asynchronous web apps and servers to communicate\nwith each other, and positioned as an asynchronous successor to WSGI. You can\nread more at https://asgi.readthedocs.io/en/latest/\n\nThis package includes ASGI base libraries, such as:\n\n* Sync-to-async and async-to-sync function wrappers, ``asgiref.sync``\n* Server base classes, ``asgiref.server``\n* A WSGI-to-ASGI adapter, in ``asgiref.wsgi``\n\n\nFunction wrappers\n-----------------\n\nThese allow you to wrap or decorate async or sync functions to call them from\nthe other style (so you can call async functions from a synchronous thread,\nor vice-versa).\n\nIn particular:\n\n* AsyncToSync lets a synchronous subthread stop and wait while the async\n function is called on the main thread's event loop, and then control is\n returned to the thread when the async function is finished.\n\n* SyncToAsync lets async code call a synchronous function, which is run in\n a threadpool and control returned to the async coroutine when the synchronous\n function completes.\n\nThe idea is to make it easier to call synchronous APIs from async code and\nasynchronous APIs from synchronous code so it's easier to transition code from\none style to the other. In the case of Channels, we wrap the (synchronous)\nDjango view system with SyncToAsync to allow it to run inside the (asynchronous)\nASGI server.\n\nNote that exactly what threads things run in is very specific, and aimed to\nkeep maximum compatibility with old synchronous code. See\n\"Synchronous code & Threads\" below for a full explanation. By default,\n``sync_to_async`` will run all synchronous code in the program in the same\nthread for safety reasons; you can disable this for more performance with\n``@sync_to_async(thread_sensitive=False)``, but make sure that your code does\nnot rely on anything bound to threads (like database connections) when you do.\n\n\nThreadlocal replacement\n-----------------------\n\nThis is a drop-in replacement for ``threading.local`` that works with both\nthreads and asyncio Tasks. Even better, it will proxy values through from a\ntask-local context to a thread-local context when you use ``sync_to_async``\nto run things in a threadpool, and vice-versa for ``async_to_sync``.\n\nIf you instead want true thread- and task-safety, you can set\n``thread_critical`` on the Local object to ensure this instead.\n\n\nServer base classes\n-------------------\n\nIncludes a ``StatelessServer`` class which provides all the hard work of\nwriting a stateless server (as in, does not handle direct incoming sockets\nbut instead consumes external streams or sockets to work out what is happening).\n\nAn example of such a server would be a chatbot server that connects out to\na central chat server and provides a \"connection scope\" per user chatting to\nit. There's only one actual connection, but the server has to separate things\ninto several scopes for easier writing of the code.\n\nYou can see an example of this being used in `frequensgi `_.\n\n\nWSGI-to-ASGI adapter\n--------------------\n\nAllows you to wrap a WSGI application so it appears as a valid ASGI application.\n\nSimply wrap it around your WSGI application like so::\n\n asgi_application = WsgiToAsgi(wsgi_application)\n\nThe WSGI application will be run in a synchronous threadpool, and the wrapped\nASGI application will be one that accepts ``http`` class messages.\n\nPlease note that not all extended features of WSGI may be supported (such as\nfile handles for incoming POST bodies).\n\n\nDependencies\n------------\n\n``asgiref`` requires Python 3.5 or higher.\n\n\nContributing\n------------\n\nPlease refer to the\n`main Channels contributing docs `_.\n\n\nTesting\n'''''''\n\nTo run tests, make sure you have installed the ``tests`` extra with the package::\n\n cd asgiref/\n pip install -e .[tests]\n pytest\n\n\nBuilding the documentation\n''''''''''''''''''''''''''\n\nThe documentation uses `Sphinx `_::\n\n cd asgiref/docs/\n pip install sphinx\n\nTo build the docs, you can use the default tools::\n\n sphinx-build -b html . _build/html # or `make html`, if you've got make set up\n cd _build/html\n python -m http.server\n\n...or you can use ``sphinx-autobuild`` to run a server and rebuild/reload\nyour documentation changes automatically::\n\n pip install sphinx-autobuild\n sphinx-autobuild . _build/html\n\n\nImplementation Details\n----------------------\n\nSynchronous code & threads\n''''''''''''''''''''''''''\n\nThe ``asgiref.sync`` module provides two wrappers that let you go between\nasynchronous and synchronous code at will, while taking care of the rough edges\nfor you.\n\nUnfortunately, the rough edges are numerous, and the code has to work especially\nhard to keep things in the same thread as much as possible. Notably, the\nrestrictions we are working with are:\n\n* All synchronous code called through ``SyncToAsync`` and marked with\n ``thread_sensitive`` should run in the same thread as each other (and if the\n outer layer of the program is synchronous, the main thread)\n\n* If a thread already has a running async loop, ``AsyncToSync`` can't run things\n on that loop if it's blocked on synchronous code that is above you in the\n call stack.\n\nThe first compromise you get to might be that ``thread_sensitive`` code should\njust run in the same thread and not spawn in a sub-thread, fulfilling the first\nrestriction, but that immediately runs you into the second restriction.\n\nThe only real solution is to essentially have a variant of ThreadPoolExecutor\nthat executes any ``thread_sensitive`` code on the outermost synchronous\nthread - either the main thread, or a single spawned subthread.\n\nThis means you now have two basic states:\n\n* If the outermost layer of your program is synchronous, then all async code\n run through ``AsyncToSync`` will run in a per-call event loop in arbitary\n sub-threads, while all ``thread_sensitive`` code will run in the main thread.\n\n* If the outermost layer of your program is asynchronous, then all async code\n runs on the main thread's event loop, and all ``thread_sensitive`` synchronous\n code will run in a single shared sub-thread.\n\nCruicially, this means that in both cases there is a thread which is a shared\nresource that all ``thread_sensitive`` code must run on, and there is a chance\nthat this thread is currently blocked on its own ``AsyncToSync`` call. Thus,\n``AsyncToSync`` needs to act as an executor for thread code while it's blocking.\n\nThe ``CurrentThreadExecutor`` class provides this functionality; rather than\nsimply waiting on a Future, you can call its ``run_until_future`` method and\nit will run submitted code until that Future is done. This means that code\ninside the call can then run code on your thread.\n\n\nMaintenance and Security\n------------------------\n\nTo report security issues, please contact security@djangoproject.com. For GPG\nsignatures and more security process information, see\nhttps://docs.djangoproject.com/en/dev/internals/security/.\n\nTo report bugs or request new features, please open a new GitHub issue.\n\nThis repository is part of the Channels project. For the shepherd and maintenance team, please see the\n`main Channels readme `_.",
+ "evidence": {
+ "licenses": [
+ {
+ "expression": "Apache-2.0 AND LicenseRef-test"
+ }
+ ]
+ },
"externalReferences": [
{
"type": "bom",
@@ -56,7 +63,7 @@
},
{
"name": "aboutcode:package_uid",
- "value": "pkg:pypi/asgiref@3.3.0?uuid=4bb66559-05cb-4015-9f0a-4b08353b6b52"
+ "value": "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
},
{
"name": "aboutcode:primary_language",
@@ -68,16 +75,9 @@
"version": "3.3.0"
},
{
- "bom-ref": "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790",
+ "bom-ref": "pkg:pypi/asgiref@3.3.0?uuid=e62e0385-a279-4d5a-b2a5-7f0cfb21d7bd",
"copyright": "",
"description": "ASGI specs, helper code, and adapters\nasgiref\n=======\n\n.. image:: https://api.travis-ci.org/django/asgiref.svg\n :target: https://travis-ci.org/django/asgiref\n\n.. image:: https://img.shields.io/pypi/v/asgiref.svg\n :target: https://pypi.python.org/pypi/asgiref\n\nASGI is a standard for Python asynchronous web apps and servers to communicate\nwith each other, and positioned as an asynchronous successor to WSGI. You can\nread more at https://asgi.readthedocs.io/en/latest/\n\nThis package includes ASGI base libraries, such as:\n\n* Sync-to-async and async-to-sync function wrappers, ``asgiref.sync``\n* Server base classes, ``asgiref.server``\n* A WSGI-to-ASGI adapter, in ``asgiref.wsgi``\n\n\nFunction wrappers\n-----------------\n\nThese allow you to wrap or decorate async or sync functions to call them from\nthe other style (so you can call async functions from a synchronous thread,\nor vice-versa).\n\nIn particular:\n\n* AsyncToSync lets a synchronous subthread stop and wait while the async\n function is called on the main thread's event loop, and then control is\n returned to the thread when the async function is finished.\n\n* SyncToAsync lets async code call a synchronous function, which is run in\n a threadpool and control returned to the async coroutine when the synchronous\n function completes.\n\nThe idea is to make it easier to call synchronous APIs from async code and\nasynchronous APIs from synchronous code so it's easier to transition code from\none style to the other. In the case of Channels, we wrap the (synchronous)\nDjango view system with SyncToAsync to allow it to run inside the (asynchronous)\nASGI server.\n\nNote that exactly what threads things run in is very specific, and aimed to\nkeep maximum compatibility with old synchronous code. See\n\"Synchronous code & Threads\" below for a full explanation. By default,\n``sync_to_async`` will run all synchronous code in the program in the same\nthread for safety reasons; you can disable this for more performance with\n``@sync_to_async(thread_sensitive=False)``, but make sure that your code does\nnot rely on anything bound to threads (like database connections) when you do.\n\n\nThreadlocal replacement\n-----------------------\n\nThis is a drop-in replacement for ``threading.local`` that works with both\nthreads and asyncio Tasks. Even better, it will proxy values through from a\ntask-local context to a thread-local context when you use ``sync_to_async``\nto run things in a threadpool, and vice-versa for ``async_to_sync``.\n\nIf you instead want true thread- and task-safety, you can set\n``thread_critical`` on the Local object to ensure this instead.\n\n\nServer base classes\n-------------------\n\nIncludes a ``StatelessServer`` class which provides all the hard work of\nwriting a stateless server (as in, does not handle direct incoming sockets\nbut instead consumes external streams or sockets to work out what is happening).\n\nAn example of such a server would be a chatbot server that connects out to\na central chat server and provides a \"connection scope\" per user chatting to\nit. There's only one actual connection, but the server has to separate things\ninto several scopes for easier writing of the code.\n\nYou can see an example of this being used in `frequensgi `_.\n\n\nWSGI-to-ASGI adapter\n--------------------\n\nAllows you to wrap a WSGI application so it appears as a valid ASGI application.\n\nSimply wrap it around your WSGI application like so::\n\n asgi_application = WsgiToAsgi(wsgi_application)\n\nThe WSGI application will be run in a synchronous threadpool, and the wrapped\nASGI application will be one that accepts ``http`` class messages.\n\nPlease note that not all extended features of WSGI may be supported (such as\nfile handles for incoming POST bodies).\n\n\nDependencies\n------------\n\n``asgiref`` requires Python 3.5 or higher.\n\n\nContributing\n------------\n\nPlease refer to the\n`main Channels contributing docs `_.\n\n\nTesting\n'''''''\n\nTo run tests, make sure you have installed the ``tests`` extra with the package::\n\n cd asgiref/\n pip install -e .[tests]\n pytest\n\n\nBuilding the documentation\n''''''''''''''''''''''''''\n\nThe documentation uses `Sphinx `_::\n\n cd asgiref/docs/\n pip install sphinx\n\nTo build the docs, you can use the default tools::\n\n sphinx-build -b html . _build/html # or `make html`, if you've got make set up\n cd _build/html\n python -m http.server\n\n...or you can use ``sphinx-autobuild`` to run a server and rebuild/reload\nyour documentation changes automatically::\n\n pip install sphinx-autobuild\n sphinx-autobuild . _build/html\n\n\nImplementation Details\n----------------------\n\nSynchronous code & threads\n''''''''''''''''''''''''''\n\nThe ``asgiref.sync`` module provides two wrappers that let you go between\nasynchronous and synchronous code at will, while taking care of the rough edges\nfor you.\n\nUnfortunately, the rough edges are numerous, and the code has to work especially\nhard to keep things in the same thread as much as possible. Notably, the\nrestrictions we are working with are:\n\n* All synchronous code called through ``SyncToAsync`` and marked with\n ``thread_sensitive`` should run in the same thread as each other (and if the\n outer layer of the program is synchronous, the main thread)\n\n* If a thread already has a running async loop, ``AsyncToSync`` can't run things\n on that loop if it's blocked on synchronous code that is above you in the\n call stack.\n\nThe first compromise you get to might be that ``thread_sensitive`` code should\njust run in the same thread and not spawn in a sub-thread, fulfilling the first\nrestriction, but that immediately runs you into the second restriction.\n\nThe only real solution is to essentially have a variant of ThreadPoolExecutor\nthat executes any ``thread_sensitive`` code on the outermost synchronous\nthread - either the main thread, or a single spawned subthread.\n\nThis means you now have two basic states:\n\n* If the outermost layer of your program is synchronous, then all async code\n run through ``AsyncToSync`` will run in a per-call event loop in arbitary\n sub-threads, while all ``thread_sensitive`` code will run in the main thread.\n\n* If the outermost layer of your program is asynchronous, then all async code\n runs on the main thread's event loop, and all ``thread_sensitive`` synchronous\n code will run in a single shared sub-thread.\n\nCruicially, this means that in both cases there is a thread which is a shared\nresource that all ``thread_sensitive`` code must run on, and there is a chance\nthat this thread is currently blocked on its own ``AsyncToSync`` call. Thus,\n``AsyncToSync`` needs to act as an executor for thread code while it's blocking.\n\nThe ``CurrentThreadExecutor`` class provides this functionality; rather than\nsimply waiting on a Future, you can call its ``run_until_future`` method and\nit will run submitted code until that Future is done. This means that code\ninside the call can then run code on your thread.\n\n\nMaintenance and Security\n------------------------\n\nTo report security issues, please contact security@djangoproject.com. For GPG\nsignatures and more security process information, see\nhttps://docs.djangoproject.com/en/dev/internals/security/.\n\nTo report bugs or request new features, please open a new GitHub issue.\n\nThis repository is part of the Channels project. For the shepherd and maintenance team, please see the\n`main Channels readme `_.",
- "evidence": {
- "licenses": [
- {
- "expression": "Apache-2.0 AND LicenseRef-test"
- }
- ]
- },
"externalReferences": [
{
"type": "bom",
@@ -105,7 +105,7 @@
},
{
"name": "aboutcode:package_uid",
- "value": "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "value": "pkg:pypi/asgiref@3.3.0?uuid=e62e0385-a279-4d5a-b2a5-7f0cfb21d7bd"
},
{
"name": "aboutcode:primary_language",
@@ -120,23 +120,23 @@
"dependencies": [
{
"dependsOn": [
- "pkg:pypi/asgiref@3.3.0?uuid=4bb66559-05cb-4015-9f0a-4b08353b6b52",
- "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663",
+ "pkg:pypi/asgiref@3.3.0?uuid=e62e0385-a279-4d5a-b2a5-7f0cfb21d7bd"
],
- "ref": "1cdd3f3a-eea9-4c9c-b78e-9fa6bcde9cfd"
+ "ref": "804c3391-e6f9-415f-bb7a-cb6653853a46"
},
{
- "ref": "pkg:pypi/asgiref@3.3.0?uuid=4bb66559-05cb-4015-9f0a-4b08353b6b52"
+ "ref": "pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
},
{
- "ref": "pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "ref": "pkg:pypi/asgiref@3.3.0?uuid=e62e0385-a279-4d5a-b2a5-7f0cfb21d7bd"
}
],
"vulnerabilities": [
{
"affects": [
{
- "ref": "urn:cdx:pkg:pypi/asgiref@3.3.0?uuid=71afaea0-f335-42cb-9fba-23706d270790"
+ "ref": "urn:cdx:pkg:pypi/asgiref@3.3.0?uuid=078ee2a1-aa92-4f80-8032-8af0b3c26663"
}
],
"bom-ref": "BomRef",
diff --git a/scanpipe/tests/data/manifests/openpdf-parent-1.3.11_scan_package.json b/scanpipe/tests/data/manifests/openpdf-parent-1.3.11_scan_package.json
index f79ab96468..eab30481fb 100644
--- a/scanpipe/tests/data/manifests/openpdf-parent-1.3.11_scan_package.json
+++ b/scanpipe/tests/data/manifests/openpdf-parent-1.3.11_scan_package.json
@@ -8,11 +8,14 @@
"--info": true,
"--license": true,
"--license-text": true,
+ "--license-diagnostics": true,
+ "--license-text-diagnostics": true,
"--license-references": true,
"--package": true,
"--url": true,
"--classify": true,
- "--summary": true
+ "--summary": true,
+ "--todo": true
},
"notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.",
"output_format_version": "4.1.0",
@@ -52,6 +55,7 @@
],
"other_languages": []
},
+ "todo": [],
"packages": [
{
"type": "maven",
@@ -313,6 +317,7 @@
"license_expression": "lgpl-3.0",
"license_expression_spdx": "LGPL-3.0-only",
"detection_count": 1,
+ "detection_log": [],
"reference_matches": [
{
"license_expression": "lgpl-3.0",
@@ -336,6 +341,7 @@
"license_expression": "mpl-2.0",
"license_expression_spdx": "MPL-2.0",
"detection_count": 1,
+ "detection_log": [],
"reference_matches": [
{
"license_expression": "mpl-2.0",
@@ -359,6 +365,7 @@
"license_expression": "mpl-2.0 OR lgpl-3.0",
"license_expression_spdx": "MPL-2.0 OR LGPL-3.0-only",
"detection_count": 1,
+ "detection_log": [],
"reference_matches": [
{
"license_expression": "mpl-2.0 OR lgpl-3.0",
@@ -373,7 +380,8 @@
"rule_relevance": 95,
"rule_identifier": "mpl-2.0_or_lgpl-3.0_1.RULE",
"rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/mpl-2.0_or_lgpl-3.0_1.RULE",
- "matched_text": " \n \n GNU General Lesser Public License (LGPL) version 3.0\n http://www.gnu.org/licenses/lgpl.html\n repo\n \n \n Mozilla Public License Version 2.0\n http://www.mozilla.org/MPL/2.0/\n repo\n \n "
+ "matched_text": " \n \n GNU General Lesser Public License (LGPL) version 3.0\n http://www.gnu.org/licenses/lgpl.html\n repo\n \n \n Mozilla Public License Version 2.0\n http://www.mozilla.org/MPL/2.0/\n repo\n \n ",
+ "matched_text_diagnostics": "licenses>\n \n GNU General Lesser Public License (LGPL) version 3.0\n http://www.gnu.org/licenses/lgpl.html\n repo\n \n \n Mozilla Public License Version 2.0\n http://www.mozilla.org/MPL/2.0/\n repo\n \n "
}
]
}
@@ -582,6 +590,7 @@
"is_archive": false,
"is_media": false,
"is_source": false,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -622,6 +631,7 @@
"is_archive": false,
"is_media": false,
"is_source": false,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -861,9 +871,11 @@
"rule_relevance": 95,
"rule_identifier": "mpl-2.0_or_lgpl-3.0_1.RULE",
"rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/mpl-2.0_or_lgpl-3.0_1.RULE",
- "matched_text": " \n \n GNU General Lesser Public License (LGPL) version 3.0\n http://www.gnu.org/licenses/lgpl.html\n repo\n \n \n Mozilla Public License Version 2.0\n http://www.mozilla.org/MPL/2.0/\n repo\n \n "
+ "matched_text": " \n \n GNU General Lesser Public License (LGPL) version 3.0\n http://www.gnu.org/licenses/lgpl.html\n repo\n \n \n Mozilla Public License Version 2.0\n http://www.mozilla.org/MPL/2.0/\n repo\n \n ",
+ "matched_text_diagnostics": "licenses>\n \n GNU General Lesser Public License (LGPL) version 3.0\n http://www.gnu.org/licenses/lgpl.html\n repo\n \n \n Mozilla Public License Version 2.0\n http://www.mozilla.org/MPL/2.0/\n repo\n \n "
}
],
+ "detection_log": [],
"identifier": "mpl_2_0_or_lgpl_3_0-7b07902d-2b7f-9c7e-aa21-3ae2142743ba"
}
],
diff --git a/scanpipe/tests/data/scancode/is-npm-1.0.0_scan_package.json b/scanpipe/tests/data/scancode/is-npm-1.0.0_scan_package.json
index 638f95e236..6636558fc7 100644
--- a/scanpipe/tests/data/scancode/is-npm-1.0.0_scan_package.json
+++ b/scanpipe/tests/data/scancode/is-npm-1.0.0_scan_package.json
@@ -8,11 +8,14 @@
"--info": true,
"--license": true,
"--license-text": true,
+ "--license-diagnostics": true,
+ "--license-text-diagnostics": true,
"--license-references": true,
"--package": true,
"--url": true,
"--classify": true,
- "--summary": true
+ "--summary": true,
+ "--todo": true
},
"notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.",
"output_format_version": "4.1.0",
@@ -52,6 +55,7 @@
],
"other_languages": []
},
+ "todo": [],
"packages": [
{
"type": "npm",
@@ -165,6 +169,7 @@
"license_expression": "mit",
"license_expression_spdx": "MIT",
"detection_count": 2,
+ "detection_log": [],
"reference_matches": [
{
"license_expression": "mit",
@@ -179,7 +184,8 @@
"rule_relevance": 100,
"rule_identifier": "mit_30.RULE",
"rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/mit_30.RULE",
- "matched_text": " \"license\": \"MIT\","
+ "matched_text": " \"license\": \"MIT\",",
+ "matched_text_diagnostics": "license\": \"MIT\","
}
]
},
@@ -188,6 +194,7 @@
"license_expression": "mit",
"license_expression_spdx": "MIT",
"detection_count": 1,
+ "detection_log": [],
"reference_matches": [
{
"license_expression": "mit",
@@ -327,6 +334,7 @@
"is_archive": false,
"is_media": false,
"is_source": false,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -365,6 +373,7 @@
"is_archive": false,
"is_media": false,
"is_source": false,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -403,6 +412,7 @@
"is_archive": false,
"is_media": false,
"is_source": true,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -443,6 +453,7 @@
"is_archive": false,
"is_media": false,
"is_source": false,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -570,9 +581,11 @@
"rule_relevance": 100,
"rule_identifier": "mit_30.RULE",
"rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/mit_30.RULE",
- "matched_text": " \"license\": \"MIT\","
+ "matched_text": " \"license\": \"MIT\",",
+ "matched_text_diagnostics": "license\": \"MIT\","
}
],
+ "detection_log": [],
"identifier": "mit-3fce6ea2-8abd-6c6b-3ede-a37af7c6efee"
}
],
@@ -621,6 +634,7 @@
"is_archive": false,
"is_media": false,
"is_source": false,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": true,
@@ -651,9 +665,11 @@
"rule_relevance": 100,
"rule_identifier": "mit_30.RULE",
"rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/mit_30.RULE",
- "matched_text": "## License\n\nMIT \u00a9 [Sindre Sorhus](http://sindresorhus.com)"
+ "matched_text": "## License\n\nMIT \u00a9 [Sindre Sorhus](http://sindresorhus.com)",
+ "matched_text_diagnostics": "License\n\nMIT \u00a9 ["
}
],
+ "detection_log": [],
"identifier": "mit-3fce6ea2-8abd-6c6b-3ede-a37af7c6efee"
}
],
diff --git a/scanpipe/tests/data/scancode/is-npm-1.0.0_scan_package_summary.json b/scanpipe/tests/data/scancode/is-npm-1.0.0_scan_package_summary.json
index 1b1c0d16d1..1edcaf9391 100644
--- a/scanpipe/tests/data/scancode/is-npm-1.0.0_scan_package_summary.json
+++ b/scanpipe/tests/data/scancode/is-npm-1.0.0_scan_package_summary.json
@@ -82,10 +82,12 @@
"rule_relevance": 100,
"rule_identifier": "mit_30.RULE",
"license_expression": "mit",
- "license_expression_spdx": "MIT"
+ "license_expression_spdx": "MIT",
+ "matched_text_diagnostics": "License\n\nMIT \u00a9 ["
}
],
"identifier": "mit-3fce6ea2-8abd-6c6b-3ede-a37af7c6efee",
+ "detection_log": [],
"license_expression": "mit",
"license_expression_spdx": "MIT"
}
diff --git a/scanpipe/tests/data/scancode/is-npm-1.0.0_summary.json b/scanpipe/tests/data/scancode/is-npm-1.0.0_summary.json
index be5a2ab12c..70d60653e5 100644
--- a/scanpipe/tests/data/scancode/is-npm-1.0.0_summary.json
+++ b/scanpipe/tests/data/scancode/is-npm-1.0.0_summary.json
@@ -85,10 +85,12 @@
"rule_relevance": 100,
"rule_identifier": "mit_30.RULE",
"license_expression": "mit",
- "license_expression_spdx": "MIT"
+ "license_expression_spdx": "MIT",
+ "matched_text_diagnostics": "License\n\nMIT \u00a9 ["
}
],
"identifier": "mit-3fce6ea2-8abd-6c6b-3ede-a37af7c6efee",
+ "detection_log": [],
"license_expression": "mit",
"license_expression_spdx": "MIT"
}
diff --git a/scanpipe/tests/data/scancode/multiple-is-npm-1.0.0_scan_package.json b/scanpipe/tests/data/scancode/multiple-is-npm-1.0.0_scan_package.json
index 793a069583..498915297f 100644
--- a/scanpipe/tests/data/scancode/multiple-is-npm-1.0.0_scan_package.json
+++ b/scanpipe/tests/data/scancode/multiple-is-npm-1.0.0_scan_package.json
@@ -8,11 +8,14 @@
"--info": true,
"--license": true,
"--license-text": true,
+ "--license-diagnostics": true,
+ "--license-text-diagnostics": true,
"--license-references": true,
"--package": true,
"--url": true,
"--classify": true,
- "--summary": true
+ "--summary": true,
+ "--todo": true
},
"notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.",
"output_format_version": "4.1.0",
@@ -52,6 +55,7 @@
],
"other_languages": []
},
+ "todo": [],
"packages": [
{
"type": "npm",
@@ -268,6 +272,7 @@
"license_expression": "mit",
"license_expression_spdx": "MIT",
"detection_count": 4,
+ "detection_log": [],
"reference_matches": [
{
"license_expression": "mit",
@@ -282,7 +287,8 @@
"rule_relevance": 100,
"rule_identifier": "mit_30.RULE",
"rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/mit_30.RULE",
- "matched_text": " \"license\": \"MIT\","
+ "matched_text": " \"license\": \"MIT\",",
+ "matched_text_diagnostics": "license\": \"MIT\","
}
]
},
@@ -291,6 +297,7 @@
"license_expression": "mit",
"license_expression_spdx": "MIT",
"detection_count": 2,
+ "detection_log": [],
"reference_matches": [
{
"license_expression": "mit",
@@ -430,6 +437,7 @@
"is_archive": false,
"is_media": false,
"is_source": false,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -468,6 +476,7 @@
"is_archive": false,
"is_media": false,
"is_source": false,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -506,6 +515,7 @@
"is_archive": false,
"is_media": false,
"is_source": true,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -546,6 +556,7 @@
"is_archive": false,
"is_media": false,
"is_source": false,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -584,6 +595,7 @@
"is_archive": false,
"is_media": false,
"is_source": false,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -622,6 +634,7 @@
"is_archive": false,
"is_media": false,
"is_source": true,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -662,6 +675,7 @@
"is_archive": false,
"is_media": false,
"is_source": false,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -789,9 +803,11 @@
"rule_relevance": 100,
"rule_identifier": "mit_30.RULE",
"rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/mit_30.RULE",
- "matched_text": " \"license\": \"MIT\","
+ "matched_text": " \"license\": \"MIT\",",
+ "matched_text_diagnostics": "license\": \"MIT\","
}
],
+ "detection_log": [],
"identifier": "mit-3fce6ea2-8abd-6c6b-3ede-a37af7c6efee"
}
],
@@ -840,6 +856,7 @@
"is_archive": false,
"is_media": false,
"is_source": false,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": true,
@@ -870,9 +887,11 @@
"rule_relevance": 100,
"rule_identifier": "mit_30.RULE",
"rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/mit_30.RULE",
- "matched_text": "## License\n\nMIT \u00a9 [Sindre Sorhus](http://sindresorhus.com)"
+ "matched_text": "## License\n\nMIT \u00a9 [Sindre Sorhus](http://sindresorhus.com)",
+ "matched_text_diagnostics": "License\n\nMIT \u00a9 ["
}
],
+ "detection_log": [],
"identifier": "mit-3fce6ea2-8abd-6c6b-3ede-a37af7c6efee"
}
],
@@ -936,6 +955,7 @@
"is_archive": false,
"is_media": false,
"is_source": false,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": false,
@@ -1063,9 +1083,11 @@
"rule_relevance": 100,
"rule_identifier": "mit_30.RULE",
"rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/mit_30.RULE",
- "matched_text": " \"license\": \"MIT\","
+ "matched_text": " \"license\": \"MIT\",",
+ "matched_text_diagnostics": "license\": \"MIT\","
}
],
+ "detection_log": [],
"identifier": "mit-3fce6ea2-8abd-6c6b-3ede-a37af7c6efee"
}
],
@@ -1114,6 +1136,7 @@
"is_archive": false,
"is_media": false,
"is_source": false,
+ "for_todo": [],
"is_legal": false,
"is_manifest": false,
"is_readme": true,
@@ -1144,9 +1167,11 @@
"rule_relevance": 100,
"rule_identifier": "mit_30.RULE",
"rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/mit_30.RULE",
- "matched_text": "## License\n\nMIT \u00a9 [Sindre Sorhus](http://sindresorhus.com)"
+ "matched_text": "## License\n\nMIT \u00a9 [Sindre Sorhus](http://sindresorhus.com)",
+ "matched_text_diagnostics": "License\n\nMIT \u00a9 ["
}
],
+ "detection_log": [],
"identifier": "mit-3fce6ea2-8abd-6c6b-3ede-a37af7c6efee"
}
],
diff --git a/scanpipe/tests/data/scancode/multiple-is-npm-1.0.0_scan_package_summary.json b/scanpipe/tests/data/scancode/multiple-is-npm-1.0.0_scan_package_summary.json
index b056d8fb41..ffa3eb1f1f 100644
--- a/scanpipe/tests/data/scancode/multiple-is-npm-1.0.0_scan_package_summary.json
+++ b/scanpipe/tests/data/scancode/multiple-is-npm-1.0.0_scan_package_summary.json
@@ -106,10 +106,12 @@
"rule_relevance": 100,
"rule_identifier": "mit_30.RULE",
"license_expression": "mit",
- "license_expression_spdx": "MIT"
+ "license_expression_spdx": "MIT",
+ "matched_text_diagnostics": "License\n\nMIT \u00a9 ["
}
],
"identifier": "mit-3fce6ea2-8abd-6c6b-3ede-a37af7c6efee",
+ "detection_log": [],
"license_expression": "mit",
"license_expression_spdx": "MIT"
}
diff --git a/scanpipe/tests/data/scancode/package_assembly_codebase.json b/scanpipe/tests/data/scancode/package_assembly_codebase.json
index 11b9135e92..ce0266a650 100644
--- a/scanpipe/tests/data/scancode/package_assembly_codebase.json
+++ b/scanpipe/tests/data/scancode/package_assembly_codebase.json
@@ -8,10 +8,10 @@
"--package": true
},
"notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.",
- "start_timestamp": "2025-06-30T195757.062637",
- "end_timestamp": "2025-06-30T195757.374873",
+ "start_timestamp": "2025-07-16T152711.658601",
+ "end_timestamp": "2025-07-16T152711.886384",
"output_format_version": "4.1.0",
- "duration": 0.31224966049194336,
+ "duration": 0.2277970314025879,
"message": null,
"errors": [],
"warnings": [],
@@ -19,8 +19,8 @@
"system_environment": {
"operating_system": "linux",
"cpu_architecture": "64",
- "platform": "Linux-5.15.0-141-generic-x86_64-with-glibc2.35",
- "platform_version": "#151-Ubuntu SMP Sun May 18 21:35:19 UTC 2025",
+ "platform": "Linux-5.15.0-143-generic-x86_64-with-glibc2.35",
+ "platform_version": "#153-Ubuntu SMP Fri Jun 13 19:10:45 UTC 2025",
"python_version": "3.10.12 (main, May 27 2025, 17:12:29) [GCC 11.4.0]"
},
"spdx_license_list_version": "3.26",
@@ -91,7 +91,7 @@
"repository_homepage_url": "https://www.npmjs.com/package/test",
"repository_download_url": "https://registry.npmjs.org/test/-/test-0.1.0.tgz",
"api_data_url": "https://registry.npmjs.org/test/0.1.0",
- "package_uid": "pkg:npm/test@0.1.0?uuid=46bc67bc-2555-4267-847b-97bb17199aa3",
+ "package_uid": "pkg:npm/test@0.1.0?uuid=ea1e6923-1857-4092-9a5a-37dad26b5e52",
"datafile_paths": [
"package_assembly_codebase.tar.gz-extract/test/get_package_resources/package.json"
],
@@ -278,7 +278,7 @@
}
],
"for_packages": [
- "pkg:npm/test@0.1.0?uuid=46bc67bc-2555-4267-847b-97bb17199aa3"
+ "pkg:npm/test@0.1.0?uuid=ea1e6923-1857-4092-9a5a-37dad26b5e52"
],
"files_count": 0,
"dirs_count": 0,
@@ -308,7 +308,7 @@
"is_script": false,
"package_data": [],
"for_packages": [
- "pkg:npm/test@0.1.0?uuid=46bc67bc-2555-4267-847b-97bb17199aa3"
+ "pkg:npm/test@0.1.0?uuid=ea1e6923-1857-4092-9a5a-37dad26b5e52"
],
"files_count": 0,
"dirs_count": 0,
diff --git a/scanpipe/tests/pipes/test_output.py b/scanpipe/tests/pipes/test_output.py
index 5b7b217e6f..3434c0243c 100644
--- a/scanpipe/tests/pipes/test_output.py
+++ b/scanpipe/tests/pipes/test_output.py
@@ -338,7 +338,7 @@ def test_scanpipe_pipes_outputs_to_cyclonedx(self, regen=FIXTURES_REGEN):
project = Project.objects.get(name="asgiref")
package = project.discoveredpackages.get(
- uuid="80e083f1-7d05-432e-96f8-e6dfd9e494f0"
+ uuid="7969de5e-5589-4441-bffa-a60e12b43280"
)
package.other_license_expression_spdx = "Apache-2.0 AND LicenseRef-test"
diff --git a/scanpipe/tests/test_models.py b/scanpipe/tests/test_models.py
index 67601d6014..ef3f08039e 100644
--- a/scanpipe/tests/test_models.py
+++ b/scanpipe/tests/test_models.py
@@ -179,6 +179,7 @@ def test_scanpipe_project_model_delete_related_objects(self):
"scanpipe.CodebaseRelation": 0,
"scanpipe.CodebaseResource": 1,
"scanpipe.DiscoveredDependency": 0,
+ "scanpipe.DiscoveredLicense": 0,
"scanpipe.DiscoveredPackage": 1,
"scanpipe.DiscoveredPackage_codebase_resources": 1,
"scanpipe.InputSource": 0,
@@ -2704,12 +2705,18 @@ def test_scanpipe_codebase_resource_queryset_elfs(self):
def test_scanpipe_model_codebase_resource_compliance_alert_queryset_mixin(self):
severities = CodebaseResource.Compliance
make_resource_file(self.project1)
- make_resource_file(self.project1, compliance_alert=severities.OK)
- warning = make_resource_file(self.project1, compliance_alert=severities.WARNING)
- error = make_resource_file(self.project1, compliance_alert=severities.ERROR)
- missing = make_resource_file(self.project1, compliance_alert=severities.MISSING)
+ make_resource_file(self.project1, path="ok", compliance_alert=severities.OK)
+ warning = make_resource_file(
+ self.project1, path="warning", compliance_alert=severities.WARNING
+ )
+ error = make_resource_file(
+ self.project1, path="error", compliance_alert=severities.ERROR
+ )
+ missing = make_resource_file(
+ self.project1, path="missing", compliance_alert=severities.MISSING
+ )
- qs = CodebaseResource.objects.order_by("compliance_alert")
+ qs = self.project1.codebaseresources.order_by("path")
self.assertQuerySetEqual(qs.compliance_issues(severities.ERROR), [error])
self.assertQuerySetEqual(
qs.compliance_issues(severities.WARNING), [error, warning]
diff --git a/scanpipe/tests/test_views.py b/scanpipe/tests/test_views.py
index a2739ff2cd..192da4f016 100644
--- a/scanpipe/tests/test_views.py
+++ b/scanpipe/tests/test_views.py
@@ -611,16 +611,17 @@ def test_scanpipe_views_project_details_get_scan_summary_data(self):
scan_summary = self.data / "scancode" / "is-npm-1.0.0_scan_package_summary.json"
scan_summary_json = json.loads(scan_summary.read_text())
- scan_summary_data = get_scan_summary_data(scan_summary_json)
+ scan_summary_data = get_scan_summary_data(self.project1, scan_summary_json)
- self.assertEqual(6, len(scan_summary_data))
+ self.assertEqual(7, len(scan_summary_data))
expected = [
- "Declared license",
- "Declared holder",
- "Primary language",
- "Other licenses",
- "Other holders",
- "Other languages",
+ "declared_license_expression",
+ "declared_holder",
+ "primary_language",
+ "other_license_expressions",
+ "other_holders",
+ "other_languages",
+ "key_file_licenses",
]
self.assertEqual(expected, list(scan_summary_data.keys()))
@@ -1075,8 +1076,10 @@ def test_scanpipe_views_codebase_resource_details_view_tabset(self):
self.assertContains(response, 'id="tab-others"')
self.assertContains(response, 'data-target="tab-viewer"')
self.assertContains(response, 'id="tab-viewer"')
- self.assertNotContains(response, 'data-target="tab-detection"')
- self.assertNotContains(response, 'id="tab-detection"')
+ self.assertNotContains(response, 'data-target="tab-terms"')
+ self.assertNotContains(response, 'id="tab-terms"')
+ self.assertNotContains(response, 'data-target="tab-resource-detection"')
+ self.assertNotContains(response, 'id="tab-resource-detection"')
self.assertNotContains(response, 'data-target="tab-packages"')
self.assertNotContains(response, 'id="tab-packages"')
self.assertNotContains(response, 'data-target="tab-relations"')
@@ -1094,10 +1097,8 @@ def test_scanpipe_views_codebase_resource_details_view_tabset(self):
map_type="path",
)
response = self.client.get(resource1.get_absolute_url())
- self.assertContains(response, 'data-target="tab-detection"')
- self.assertContains(response, 'id="tab-detection"')
- self.assertContains(response, 'data-target="tab-packages"')
- self.assertContains(response, 'id="tab-packages"')
+ self.assertContains(response, 'data-target="tab-terms"')
+ self.assertContains(response, 'id="tab-terms"')
self.assertContains(response, 'data-target="tab-relations"')
self.assertContains(response, 'id="tab-relations"')
self.assertContains(response, 'data-target="tab-extra_data"')
@@ -1155,7 +1156,7 @@ def test_scanpipe_views_codebase_resource_views(self):
with self.assertNumQueries(8):
self.client.get(url)
- with self.assertNumQueries(7):
+ with self.assertNumQueries(8):
self.client.get(resource1.get_absolute_url())
def test_scanpipe_views_discovered_package_views(self):
diff --git a/scanpipe/urls.py b/scanpipe/urls.py
index c760becbf8..5efe6fdd76 100644
--- a/scanpipe/urls.py
+++ b/scanpipe/urls.py
@@ -56,6 +56,11 @@
views.DiscoveredPackageDetailsView.as_view(),
name="package_detail",
),
+ path(
+ "project//license_detections//",
+ views.DiscoveredLicenseDetailsView.as_view(),
+ name="license_detail",
+ ),
path(
"project//dependencies//",
views.DiscoveredDependencyDetailsView.as_view(),
@@ -66,6 +71,11 @@
views.DiscoveredPackageListView.as_view(),
name="project_packages",
),
+ path(
+ "project//license_detections/",
+ views.DiscoveredLicenseListView.as_view(),
+ name="project_licenses",
+ ),
path(
"project//dependencies/",
views.DiscoveredDependencyListView.as_view(),
@@ -212,9 +222,9 @@
name="project_resource_status_summary",
),
path(
- "project//resource_license_summary/",
- views.ProjectResourceLicenseSummaryView.as_view(),
- name="project_resource_license_summary",
+ "project//license_detection_summary/",
+ views.ProjectLicenseDetectionSummaryView.as_view(),
+ name="project_license_detection_summary",
),
path(
"project//compliance_panel/",
diff --git a/scanpipe/views.py b/scanpipe/views.py
index 9f4ac5202f..150fd0e13f 100644
--- a/scanpipe/views.py
+++ b/scanpipe/views.py
@@ -70,6 +70,7 @@
from scanpipe.api.serializers import DiscoveredDependencySerializer
from scanpipe.filters import PAGE_VAR
from scanpipe.filters import DependencyFilterSet
+from scanpipe.filters import LicenseFilterSet
from scanpipe.filters import PackageFilterSet
from scanpipe.filters import ProjectFilterSet
from scanpipe.filters import ProjectMessageFilterSet
@@ -92,6 +93,7 @@
from scanpipe.models import CodebaseRelation
from scanpipe.models import CodebaseResource
from scanpipe.models import DiscoveredDependency
+from scanpipe.models import DiscoveredLicense
from scanpipe.models import DiscoveredPackage
from scanpipe.models import Project
from scanpipe.models import ProjectMessage
@@ -181,12 +183,12 @@
SCAN_SUMMARY_FIELDS = [
- ("Declared license", "declared_license_expression"),
- ("Declared holder", "declared_holder"),
- ("Primary language", "primary_language"),
- ("Other licenses", "other_license_expressions"),
- ("Other holders", "other_holders"),
- ("Other languages", "other_languages"),
+ "declared_license_expression",
+ "declared_holder",
+ "primary_language",
+ "other_license_expressions",
+ "other_holders",
+ "other_languages",
]
@@ -333,7 +335,9 @@ def get_field_value(self, field_name, render_func=None):
"""
Return the formatted value of the specified `field_name` from the object.
- By default, JSON types (list and dict) are rendered as YAML.
+ By default, JSON types (list and dict) are rendered as YAML,
+ except some fields which are used for a more complex tabular
+ representation with links to other views.
If a `render_func` is provided, it will take precedence and be used for
rendering the value.
"""
@@ -345,9 +349,25 @@ def get_field_value(self, field_name, render_func=None):
if isinstance(field_value, Manager):
return list(field_value.all())
+ # We need these as mappings
+ detection_fields = [
+ "license_detections",
+ "other_license_detections",
+ "license_clues",
+ "matches",
+ "file_regions",
+ "urls",
+ "emails",
+ "datafile_paths",
+ "datasource_ids",
+ "detection_log",
+ "review_comments",
+ ]
+
if isinstance(field_value, list | dict):
- with suppress(Exception):
- field_value = render_as_yaml(field_value)
+ if field_name not in detection_fields:
+ with suppress(Exception):
+ field_value = render_as_yaml(field_value)
return field_value
@@ -726,11 +746,12 @@ def get_license_clarity_data(scan_summary_json):
]
@staticmethod
- def get_scan_summary_data(scan_summary_json):
+ def get_scan_summary_data(project, scan_summary_json):
summary_data = {}
- for field_label, field_name in SCAN_SUMMARY_FIELDS:
- field_data = scan_summary_json.get(field_name)
+ for field_name, field_data in scan_summary_json.items():
+ if field_name not in SCAN_SUMMARY_FIELDS:
+ continue
if type(field_data) is list:
# Do not include `None` entries
@@ -739,7 +760,13 @@ def get_scan_summary_data(scan_summary_json):
# Converts single value type into common data-structure
values = [{"value": field_data}]
- summary_data[field_label] = values
+ summary_data[field_name] = values
+
+ key_files = project.codebaseresources.filter(is_key_file=True)
+ summary_data["key_file_licenses"] = {
+ key_file.path: key_file.detected_license_expression
+ for key_file in key_files
+ }
return summary_data
@@ -797,7 +824,7 @@ def get_context_data(self, **kwargs):
with suppress(json.decoder.JSONDecodeError):
scan_summary_json = json.loads(scan_summary_file.read_text())
license_clarity = self.get_license_clarity_data(scan_summary_json)
- scan_summary = self.get_scan_summary_data(scan_summary_json)
+ scan_summary = self.get_scan_summary_data(project, scan_summary_json)
codebase_root = sorted(
project.codebase_path.glob("*"),
@@ -1076,42 +1103,80 @@ def get_context_data(self, **kwargs):
return context
-class ProjectResourceLicenseSummaryView(ConditionalLoginRequired, generic.DetailView):
+class ProjectLicenseDetectionSummaryView(ConditionalLoginRequired, generic.DetailView):
model = Project
- template_name = "scanpipe/panels/resource_license_summary.html"
+ template_name = "scanpipe/panels/license_detections_summary.html"
@staticmethod
- def get_resource_license_summary(project, limit=10):
+ def get_license_detection_summary(project, limit=10):
+ proper_license_detections = project.discoveredlicenses.filter(
+ is_license_clue=False,
+ )
license_counter = count_group_by(
- project.codebaseresources.files(), "detected_license_expression"
+ proper_license_detections, "license_expression"
)
if list(license_counter.keys()) == [""]:
- return
+ return None, None, None
# Order the license list by the number of detections, higher first
sorted_by_count = dict(
sorted(license_counter.items(), key=operator.itemgetter(1), reverse=True)
)
- # Remove the "no licenses" entry from the top list
- no_licenses = sorted_by_count.pop("", None)
-
# Keep the top entries
top_licenses = dict(list(sorted_by_count.items())[:limit])
- # Add the "no licenses" entry at the end
- if no_licenses:
- top_licenses[""] = no_licenses
+ # Also get count for detections with
+ expressions_with_compliance_alert = []
+ for license_expression in top_licenses.keys():
+ has_compliance_alert = (
+ proper_license_detections.filter(license_expression=license_expression)
+ .has_compliance_alert()
+ .exists()
+ )
+ if has_compliance_alert:
+ expressions_with_compliance_alert.append(license_expression)
- return top_licenses
+ total_counts = {
+ "with_compliance_error": (
+ proper_license_detections.has_compliance_alert().count()
+ ),
+ "needs_review": proper_license_detections.needs_review().count(),
+ "all": proper_license_detections.count(),
+ }
+
+ license_clues = project.discoveredlicenses.filter(
+ is_license_clue=True,
+ )
+ clue_counts = {}
+ if license_clues.exists():
+ clue_counts = {
+ "with_compliance_error": (license_clues.has_compliance_alert().count()),
+ "needs_review": license_clues.needs_review().count(),
+ "all": license_clues.count(),
+ }
+
+ return (
+ top_licenses,
+ expressions_with_compliance_alert,
+ total_counts,
+ license_clues,
+ clue_counts,
+ )
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- summary = self.get_resource_license_summary(project=self.object)
- context["resource_license_summary"] = summary
- context["project_resources_url"] = reverse(
- "project_resources", args=[self.object.slug]
+ summary, expressions, counts, clues, clue_counts = (
+ self.get_license_detection_summary(project=self.object)
+ )
+ context["license_detection_summary"] = summary
+ context["expressions_with_compliance_alert"] = expressions
+ context["total_counts"] = counts
+ context["license_clues"] = clues
+ context["clue_counts"] = clue_counts
+ context["project_licenses_url"] = reverse(
+ "project_licenses", args=[self.object.slug]
)
return context
@@ -1786,6 +1851,55 @@ def get_queryset(self):
return super().get_queryset().order_by("dependency_uid")
+class DiscoveredLicenseListView(
+ ConditionalLoginRequired,
+ ProjectRelatedViewMixin,
+ TableColumnsMixin,
+ ExportXLSXMixin,
+ PaginatedFilterView,
+):
+ model = DiscoveredLicense
+ filterset_class = LicenseFilterSet
+ template_name = "scanpipe/license_detection_list.html"
+ paginate_by = settings.SCANCODEIO_PAGINATE_BY.get("license", 10)
+ table_columns = [
+ "identifier",
+ {
+ "field_name": "license_expression",
+ "filter_fieldname": "license_expression",
+ },
+ {
+ "field_name": "license_expression_spdx",
+ "filter_fieldname": "license_expression_spdx",
+ },
+ "detection_count",
+ "is_license_clue",
+ "needs_review",
+ {
+ "field_name": "compliance_alert",
+ "filter_fieldname": "compliance_alert",
+ },
+ ]
+
+ def get_queryset(self):
+ return (
+ super()
+ .get_queryset()
+ .only(
+ "detection_count",
+ "license_expression",
+ "license_expression_spdx",
+ "compliance_alert",
+ )
+ .order_by_count_and_expression()
+ )
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context["display_compliance_alert"] = self.get_project().policies_enabled
+ return context
+
+
class ProjectMessageListView(
ConditionalLoginRequired,
ProjectRelatedViewMixin,
@@ -1935,23 +2049,24 @@ class CodebaseResourceDetailsView(
"disable_condition": do_not_disable,
"display_condition": is_displayable_image_type,
},
- "detection": {
+ "terms": {
"fields": [
"detected_license_expression",
{
"field_name": "detected_license_expression_spdx",
"label": "Detected license expression (SPDX)",
},
- "license_detections",
- "license_clues",
"percentage_of_license_text",
- "copyrights",
- "holders",
- "authors",
- "emails",
- "urls",
+ {"field_name": "copyrights", "render_func": render_as_yaml},
+ {"field_name": "holders", "render_func": render_as_yaml},
+ {"field_name": "authors", "render_func": render_as_yaml},
],
+ "icon_class": "fa-solid fa-file-contract",
+ },
+ "detection": {
+ "fields": ["license_detections", "license_clues", "emails", "urls"],
"icon_class": "fa-solid fa-search",
+ "template": "scanpipe/tabset/tab_resource_detections.html",
},
"packages": {
"fields": ["discovered_packages"],
@@ -2178,10 +2293,18 @@ class DiscoveredPackageDetailsView(
"copyright",
"holder",
"notice_text",
+ ],
+ "icon_class": "fa-solid fa-file-contract",
+ },
+ "detection": {
+ "fields": [
+ "datasource_ids",
+ "datafile_paths",
"license_detections",
"other_license_detections",
],
- "icon_class": "fa-solid fa-file-contract",
+ "icon_class": "fa-solid fa-search",
+ "template": "scanpipe/tabset/tab_package_detections.html",
},
"resources": {
"fields": ["codebase_resources"],
@@ -2342,6 +2465,35 @@ def get_context_data(self, **kwargs):
return context
+class DiscoveredLicenseDetailsView(
+ ConditionalLoginRequired,
+ ProjectRelatedViewMixin,
+ TabSetMixin,
+ generic.DetailView,
+):
+ model = DiscoveredLicense
+ model_label = "license_detections"
+ slug_field = "identifier"
+ slug_url_kwarg = "identifier"
+ template_name = "scanpipe/license_detection_detail.html"
+ tabset = {
+ "essentials": {
+ "fields": [
+ "license_expression",
+ "license_expression_spdx",
+ "identifier",
+ "detection_count",
+ ],
+ "icon_class": "fa-solid fa-info-circle",
+ },
+ "detection": {
+ "fields": ["matches", "detection_log", "review_comments", "file_regions"],
+ "icon_class": "fa-solid fa-search",
+ "template": "scanpipe/tabset/tab_license_detections.html",
+ },
+ }
+
+
@conditional_login_required
def run_detail_view(request, uuid):
template = "scanpipe/modals/run_modal_content.html"
|