Skip to content

Commit 953c91b

Browse files
authored
Refactor policies implementation to support more than licenses (#1718)
Signed-off-by: tdruez <tdruez@nexb.com>
1 parent b06bb87 commit 953c91b

File tree

14 files changed

+149
-96
lines changed

14 files changed

+149
-96
lines changed

CHANGELOG.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ Changelog
44
v35.2.0 (unreleased)
55
--------------------
66

7+
- Refactor policies implementation to support more than licenses.
8+
The entire ``policies`` data is now stored on the ``ScanPipeConfig`` in place of the
9+
``license_policy_index``.
10+
Also, a new method ``get_policies_dict`` methods is now available on the ``Project``
11+
model to easily retrieve all the policies data as a dictionary.
12+
Renamed for clarity:
13+
* ``policy_index`` to ``license_policy_index``
14+
* ``policies_enabled`` to ``license_policies_enabled``
15+
https://github.com/aboutcode-org/scancode.io/pull/1718
16+
717
- Add support for SPDX license identifiers as ``license_key`` in license policies
818
``policies.yml`` file.
919
https://github.com/aboutcode-org/scancode.io/issues/1348

scancodeio/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@
292292
SCANCODEIO_WORKSPACE_LOCATION = tempfile.mkdtemp()
293293
SCANCODEIO_REQUIRE_AUTHENTICATION = True
294294
SCANCODEIO_SCAN_FILE_TIMEOUT = 120
295+
SCANCODEIO_POLICIES_FILE = None
295296
# The default password hasher is rather slow by design.
296297
# Using a faster hashing algorithm in the testing context to speed up the run.
297298
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]

scanpipe/apps.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
from licensedcode.models import load_licenses
4040

4141
from scanpipe.policies import load_policies_file
42-
from scanpipe.policies import make_license_policy_index
4342

4443
try:
4544
from importlib import metadata as importlib_metadata
@@ -61,7 +60,7 @@ def __init__(self, app_name, app_module):
6160

6261
# Mapping of registered pipeline names to pipeline classes.
6362
self._pipelines = {}
64-
self.license_policies_index = {}
63+
self.policies = {}
6564

6665
workspace_location = settings.SCANCODEIO_WORKSPACE_LOCATION
6766
self.workspace_path = Path(workspace_location).expanduser().resolve()
@@ -226,7 +225,7 @@ def get_scancode_licenses(self):
226225

227226
def set_policies(self):
228227
"""
229-
Compute and sets the `license_policies` on the app instance.
228+
Set the global app policies on the app instance.
230229
231230
If the policies file is available but not formatted properly or doesn't
232231
include the proper content, we want to raise an exception while the app
@@ -240,7 +239,7 @@ def set_policies(self):
240239
if policies_file.exists():
241240
policies = load_policies_file(policies_file)
242241
logger.debug(style.SUCCESS(f"Loaded policies from {policies_file}"))
243-
self.license_policies_index = make_license_policy_index(policies)
242+
self.policies = policies
244243
else:
245244
logger.debug(style.WARNING("Policies file not found."))
246245

scanpipe/models.py

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,37 +1495,40 @@ def has_single_resource(self):
14951495
"""
14961496
return self.resource_count == 1
14971497

1498-
def get_policy_index(self):
1498+
def get_policies_dict(self):
14991499
"""
1500-
Return the policy index for this project instance.
1500+
Load and return the policies from the following locations in that order:
15011501
1502-
The policies are loaded from the following locations in that order:
1503-
1. the project local settings
1504-
2. the "policies.yml" file in the project input/ directory
1505-
3. the global app settings license policies
1502+
1. project local settings (stored in the database)
1503+
2. "policies.yml" file in the project ``input/`` directory
1504+
3. global app settings policies, from SCANCODEIO_POLICIES_FILE setting
15061505
"""
15071506
if policies_from_settings := self.get_env("policies"):
15081507
policies_dict = policies_from_settings
15091508
if isinstance(policies_from_settings, str):
15101509
policies_dict = policies.load_policies_yaml(policies_from_settings)
1511-
return policies.make_license_policy_index(policies_dict)
1510+
return policies_dict
15121511

1513-
elif policies_file := self.get_input_policies_file():
1514-
policies_dict = policies.load_policies_file(policies_file)
1515-
return policies.make_license_policy_index(policies_dict)
1512+
elif project_input_policies_file := self.get_input_policies_file():
1513+
return policies.load_policies_file(project_input_policies_file)
15161514

1517-
else:
1518-
return scanpipe_app.license_policies_index
1515+
return scanpipe_app.policies
1516+
1517+
def get_license_policy_index(self):
1518+
"""Return the policy license index for this project instance."""
1519+
if policies_dict := self.get_policies_dict():
1520+
return policies.make_license_policy_index(policies_dict)
1521+
return {}
15191522

15201523
@cached_property
1521-
def policy_index(self):
1522-
"""Return the cached policy index for this project instance."""
1523-
return self.get_policy_index()
1524+
def license_policy_index(self):
1525+
"""Return the cached license policy index for this project instance."""
1526+
return self.get_license_policy_index()
15241527

15251528
@property
1526-
def policies_enabled(self):
1527-
"""Return True if the policies are enabled for this project."""
1528-
return bool(self.policy_index)
1529+
def license_policies_enabled(self):
1530+
"""Return True if the license policies are available for this project."""
1531+
return bool(self.license_policy_index)
15291532

15301533

15311534
class GroupingQuerySetMixin:
@@ -2540,7 +2543,7 @@ def save(self, codebase=None, *args, **kwargs):
25402543
``codebase`` is not used in this context but required for compatibility
25412544
with the commoncode.resource.Codebase class API.
25422545
"""
2543-
if self.policies_enabled:
2546+
if self.license_policies_enabled:
25442547
loaded_license_expression = getattr(self, "_loaded_license_expression", "")
25452548
license_expression = getattr(self, self.license_expression_field, "")
25462549
if license_expression != loaded_license_expression:
@@ -2566,28 +2569,29 @@ def from_db(cls, db, field_names, values):
25662569
return new
25672570

25682571
@property
2569-
def policy_index(self):
2570-
return self.project.policy_index
2572+
def license_policy_index(self):
2573+
return self.project.license_policy_index
25712574

25722575
@cached_property
2573-
def policies_enabled(self):
2574-
return self.project.policies_enabled
2576+
def license_policies_enabled(self):
2577+
return self.project.license_policies_enabled
25752578

25762579
def compute_compliance_alert(self):
25772580
"""
25782581
Compute and return the compliance_alert value from the license policies.
25792582
Chooses the most severe compliance_alert found among licenses.
25802583
"""
25812584
license_expression = getattr(self, self.license_expression_field, "")
2582-
policy_index = self.policy_index
2583-
if not license_expression or not policy_index:
2585+
license_policy_index = self.license_policy_index
2586+
if not license_expression or not license_policy_index:
25842587
return ""
25852588

25862589
licensing = get_licensing()
25872590
parsed_symbols = licensing.parse(license_expression, simple=True).symbols
25882591

25892592
alerts = [
2590-
self.get_alert_for_symbol(policy_index, symbol) for symbol in parsed_symbols
2593+
self.get_alert_for_symbol(license_policy_index, symbol)
2594+
for symbol in parsed_symbols
25912595
]
25922596
most_severe_alert = max(alerts, key=self.COMPLIANCE_SEVERITY_MAP.get)
25932597
return most_severe_alert or self.Compliance.OK

scanpipe/pipes/output.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ def to_xlsx(project):
541541
exclude_fields = XLSX_EXCLUDE_FIELDS.copy()
542542
output_file = project.get_output_file_path("results", "xlsx")
543543

544-
if not project.policies_enabled:
544+
if not project.license_policies_enabled:
545545
exclude_fields.append("compliance_alert")
546546

547547
model_names = [

scanpipe/templates/scanpipe/forms/project_settings_form.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@
153153
<p id="policies" class="panel-heading">Policies</p>
154154
<div class="panel-block is-block px-4">
155155
<div>
156-
{% if project.policies_enabled %}
156+
{% if project.license_policies_enabled %}
157157
<i class="fa-solid fa-check"></i>
158158
Policies are <strong>enabled</strong> for this project.
159159
{% else %}

scanpipe/templates/scanpipe/project_detail.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@
117117
<div hx-get="{% url 'project_resource_license_summary' project.slug %}" hx-trigger="load" hx-swap="outerHTML"></div>
118118
</div>
119119

120-
{% if policies_enabled %}
120+
{% if license_policies_enabled %}
121121
<div class="columns">
122122
<div hx-get="{% url 'project_compliance_panel' project.slug %}" hx-trigger="load" hx-swap="outerHTML"></div>
123123
</div>

scanpipe/tests/pipes/test_scancode.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,8 +467,8 @@ def test_scanpipe_max_file_size_works(self):
467467
self.assertEqual(resource1.status, flag.IGNORED_BY_MAX_FILE_SIZE)
468468

469469
def test_scanpipe_pipes_scancode_make_results_summary(self, regen=FIXTURES_REGEN):
470-
# Ensure the policies index is empty to avoid any side effect on results
471-
scanpipe_app.license_policies_index = None
470+
# Ensure the policies are empty to avoid any side effect on results
471+
scanpipe_app.policies = None
472472
# Run the scan_single_package pipeline to have a proper DB and local files setup
473473
pipeline_name = "scan_single_package"
474474
project1 = Project.objects.create(name="Analysis")

scanpipe/tests/test_apps.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import uuid
2424
from pathlib import Path
2525
from unittest import mock
26+
from unittest.mock import patch
2627

2728
from django.apps import apps
2829
from django.core.exceptions import ImproperlyConfigured
@@ -33,7 +34,7 @@
3334
from scanpipe.models import Project
3435
from scanpipe.models import Run
3536
from scanpipe.tests import filter_warnings
36-
from scanpipe.tests import license_policies_index
37+
from scanpipe.tests import global_policies
3738
from scanpipe.tests.pipelines.register_from_file import RegisterFromFile
3839

3940
scanpipe_app = apps.get_app_config("scanpipe")
@@ -43,26 +44,23 @@ class ScanPipeAppsTest(TestCase):
4344
data = Path(__file__).parent / "data"
4445
pipelines_location = Path(__file__).parent / "pipelines"
4546

46-
def test_scanpipe_apps_set_policies(self):
47-
scanpipe_app.license_policies_index = {}
48-
policies_files = None
49-
with override_settings(SCANCODEIO_POLICIES_FILE=policies_files):
47+
@patch.object(scanpipe_app, "policies", new_callable=dict)
48+
def test_scanpipe_apps_set_policies(self, mock_policies):
49+
# Case 1: No file set
50+
with override_settings(SCANCODEIO_POLICIES_FILE=None):
5051
scanpipe_app.set_policies()
51-
self.assertEqual({}, scanpipe_app.license_policies_index)
52+
self.assertEqual({}, scanpipe_app.policies)
5253

53-
scanpipe_app.license_policies_index = {}
54-
policies_files = "not_existing"
55-
with override_settings(SCANCODEIO_POLICIES_FILE=policies_files):
54+
# Case 2: Non-existing file
55+
with override_settings(SCANCODEIO_POLICIES_FILE="not_existing"):
5656
scanpipe_app.set_policies()
57-
self.assertEqual({}, scanpipe_app.license_policies_index)
57+
self.assertEqual({}, scanpipe_app.policies)
5858

59-
scanpipe_app.license_policies_index = {}
59+
# Case 3: Valid file
6060
policies_files = self.data / "policies" / "policies.yml"
6161
with override_settings(SCANCODEIO_POLICIES_FILE=str(policies_files)):
6262
scanpipe_app.set_policies()
63-
self.assertEqual(
64-
license_policies_index, scanpipe_app.license_policies_index
65-
)
63+
self.assertEqual(global_policies, scanpipe_app.policies)
6664

6765
def test_scanpipe_apps_register_pipeline_from_file(self):
6866
path = self.pipelines_location / "do_nothing.py"

scanpipe/tests/test_forms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ def test_scanpipe_forms_project_settings_form_policies(self):
230230
self.assertTrue(form.is_valid())
231231
project = form.save()
232232
self.assertEqual(policies_as_yaml.strip(), project.settings["policies"])
233-
self.assertEqual(license_policies_index, project.get_policy_index())
233+
self.assertEqual(license_policies_index, project.get_license_policy_index())
234234

235235
def test_scanpipe_forms_project_settings_form_purl(self):
236236
data_invalid_purl = {

0 commit comments

Comments
 (0)