Skip to content

Commit a7c1bf5

Browse files
authored
Refactor the policies related code to its own module #386 (#1430)
Signed-off-by: tdruez <tdruez@nexb.com>
1 parent f5a20a6 commit a7c1bf5

File tree

8 files changed

+191
-33
lines changed

8 files changed

+191
-33
lines changed

CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ v34.9.0 (unreleased)
1010
- Add a new ``list-pipelines`` management command.
1111
https://github.com/aboutcode-org/scancode.io/issues/1397
1212

13+
- Refactor the policies related code to its own module.
14+
https://github.com/aboutcode-org/scancode.io/issues/386
15+
1316
v34.8.3 (2024-10-30)
1417
--------------------
1518

scanpipe/apps.py

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@
3535
from django.utils.functional import cached_property
3636
from django.utils.translation import gettext_lazy as _
3737

38-
import saneyaml
3938
from licensedcode.models import load_licenses
4039

40+
from scanpipe.policies import load_policies_file
41+
from scanpipe.policies import make_license_policy_index
42+
4143
try:
4244
from importlib import metadata as importlib_metadata
4345
except ImportError:
@@ -218,31 +220,21 @@ def set_policies(self):
218220
"""
219221
Compute and sets the `license_policies` on the app instance.
220222
221-
If the policies file is available but formatted properly or doesn't
223+
If the policies file is available but not formatted properly or doesn't
222224
include the proper content, we want to raise an exception while the app
223225
is loading to warn system admins about the issue.
224226
"""
225-
policies_file_location = getattr(settings, "SCANCODEIO_POLICIES_FILE", None)
226-
227-
if policies_file_location:
228-
policies_file = Path(policies_file_location).expanduser()
229-
230-
if policies_file.exists():
231-
logger.debug(style.SUCCESS(f"Load policies from {policies_file}"))
232-
policies = saneyaml.load(policies_file.read_text())
233-
license_policies = policies.get("license_policies", [])
234-
self.license_policies_index = self.get_policies_index(
235-
policies_list=license_policies,
236-
key="license_key",
237-
)
238-
239-
else:
240-
logger.debug(style.WARNING("Policies file not found."))
241-
242-
@staticmethod
243-
def get_policies_index(policies_list, key):
244-
"""Return an inverted index by `key` of the `policies_list`."""
245-
return {policy.get(key): policy for policy in policies_list}
227+
policies_file_setting = getattr(settings, "SCANCODEIO_POLICIES_FILE", None)
228+
if not policies_file_setting:
229+
return
230+
231+
policies_file = Path(policies_file_setting).expanduser()
232+
if policies_file.exists():
233+
policies = load_policies_file(policies_file)
234+
logger.debug(style.SUCCESS(f"Loaded policies from {policies_file}"))
235+
self.license_policies_index = make_license_policy_index(policies)
236+
else:
237+
logger.debug(style.WARNING("Policies file not found."))
246238

247239
@property
248240
def policies_enabled(self):

scanpipe/policies.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
#
3+
# http://nexb.com and https://github.com/aboutcode-org/scancode.io
4+
# The ScanCode.io software is licensed under the Apache License version 2.0.
5+
# Data generated with ScanCode.io is provided as-is without warranties.
6+
# ScanCode is a trademark of nexB Inc.
7+
#
8+
# You may not use this software except in compliance with the License.
9+
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
10+
# Unless required by applicable law or agreed to in writing, software distributed
11+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
12+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
13+
# specific language governing permissions and limitations under the License.
14+
#
15+
# Data Generated with ScanCode.io is provided on an "AS IS" BASIS, WITHOUT WARRANTIES
16+
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
17+
# ScanCode.io should be considered or used as legal advice. Consult an Attorney
18+
# for any legal advice.
19+
#
20+
# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
21+
# Visit https://github.com/aboutcode-org/scancode.io for support and download.
22+
23+
from django.core.exceptions import ValidationError
24+
25+
import saneyaml
26+
27+
28+
def load_policies_yaml(policies_yaml):
29+
"""Load provided ``policies_yaml``."""
30+
try:
31+
return saneyaml.load(policies_yaml)
32+
except saneyaml.YAMLError as e:
33+
raise ValidationError(f"Policies format error: {e}")
34+
35+
36+
def load_policies_file(policies_file, validate=True):
37+
"""
38+
Load provided ``policies_file`` into a Python dictionary.
39+
The policies format is validated by default.
40+
"""
41+
policies_dict = load_policies_yaml(policies_yaml=policies_file.read_text())
42+
if validate:
43+
validate_policies(policies_dict)
44+
return policies_dict
45+
46+
47+
def validate_policies(policies_dict):
48+
"""Return True if the provided ``policies_dict`` is valid."""
49+
if not isinstance(policies_dict, dict):
50+
raise ValidationError("The `policies_dict` argument must be a dictionary.")
51+
52+
if "license_policies" not in policies_dict:
53+
raise ValidationError(
54+
"The `license_policies` key is missing from provided policies data."
55+
)
56+
57+
return True
58+
59+
60+
def make_license_policy_index(policies_dict):
61+
"""Return an inverted index by ``key`` of the ``policies_list``."""
62+
validate_policies(policies_dict)
63+
64+
license_policies = policies_dict.get("license_policies", [])
65+
return {policy.get("license_key"): policy for policy in license_policies}

scanpipe/templates/scanpipe/includes/project_settings_menu.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
<i class="fa-solid fa-forward mr-2"></i>Ignored
1616
</a>
1717
</li>
18+
<li>
19+
<a href="#policies">
20+
<i class="fa-solid fa-certificate mr-2"></i>Policies
21+
</a>
22+
</li>
1823
<li>
1924
<a href="#dejacode">
2025
<i class="fa-solid fa-laptop-code mr-2"></i>DejaCode

scanpipe/templates/scanpipe/project_settings.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,20 @@
131131
</div>
132132
</div>
133133

134+
<div class="panel">
135+
<p id="policies" class="panel-heading py-2 is-size-6">Policies</p>
136+
<div class="panel-block is-block px-4">
137+
{% if project.policies_enabled %}
138+
Policies are <strong>enabled</strong> for this project.
139+
{% else %}
140+
Policies are <strong>not enabled</strong> for this project.
141+
{% endif %}
142+
<p class="help">
143+
See <a target="_blank" href="https://scancodeio.readthedocs.io/en/latest/tutorial_license_policies.html">License Policies and Compliance Alerts documentation</a> for details.
144+
</p>
145+
</div>
146+
</div>
147+
134148
<div class="panel">
135149
<p id="dejacode" class="panel-heading py-2 is-size-6">DejaCode</p>
136150
<div class="panel-block is-block px-4">

scanpipe/tests/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,10 @@ def make_dependency(project, **extra):
251251
},
252252
]
253253

254+
global_policies = {
255+
"license_policies": license_policies,
256+
}
257+
254258
license_policies_index = {
255259
"gpl-3.0": {
256260
"color_code": "#c83025",

scanpipe/tests/test_apps.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,8 @@
3030
from django.test import override_settings
3131
from django.utils import timezone
3232

33-
from scanpipe.apps import ScanPipeConfig
3433
from scanpipe.models import Project
3534
from scanpipe.models import Run
36-
from scanpipe.tests import license_policies
3735
from scanpipe.tests import license_policies_index
3836
from scanpipe.tests.pipelines.register_from_file import RegisterFromFile
3937

@@ -44,14 +42,6 @@ class ScanPipeAppsTest(TestCase):
4442
data = Path(__file__).parent / "data"
4543
pipelines_location = Path(__file__).parent / "pipelines"
4644

47-
def test_scanpipe_apps_get_policies_index(self):
48-
self.assertEqual({}, ScanPipeConfig.get_policies_index([], "license_key"))
49-
policies_index = ScanPipeConfig.get_policies_index(
50-
policies_list=license_policies,
51-
key="license_key",
52-
)
53-
self.assertEqual(license_policies_index, policies_index)
54-
5545
def test_scanpipe_apps_set_policies(self):
5646
scanpipe_app.license_policies_index = {}
5747
policies_files = None

scanpipe/tests/test_policies.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
#
3+
# http://nexb.com and https://github.com/nexB/scancode.io
4+
# The ScanCode.io software is licensed under the Apache License version 2.0.
5+
# Data generated with ScanCode.io is provided as-is without warranties.
6+
# ScanCode is a trademark of nexB Inc.
7+
#
8+
# You may not use this software except in compliance with the License.
9+
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
10+
# Unless required by applicable law or agreed to in writing, software distributed
11+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
12+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
13+
# specific language governing permissions and limitations under the License.
14+
#
15+
# Data Generated with ScanCode.io is provided on an "AS IS" BASIS, WITHOUT WARRANTIES
16+
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
17+
# ScanCode.io should be considered or used as legal advice. Consult an Attorney
18+
# for any legal advice.
19+
#
20+
# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
21+
# Visit https://github.com/nexB/scancode.io for support and download.
22+
23+
from pathlib import Path
24+
25+
from django.apps import apps
26+
from django.core.exceptions import ValidationError
27+
from django.test import TestCase
28+
29+
from scanpipe.policies import load_policies_file
30+
from scanpipe.policies import load_policies_yaml
31+
from scanpipe.policies import make_license_policy_index
32+
from scanpipe.policies import validate_policies
33+
from scanpipe.tests import global_policies
34+
from scanpipe.tests import license_policies_index
35+
36+
scanpipe_app = apps.get_app_config("scanpipe")
37+
38+
39+
class ScanPipePoliciesTest(TestCase):
40+
data = Path(__file__).parent / "data"
41+
42+
def test_scanpipe_policies_load_policies_yaml(self):
43+
policies_yaml = "{wrong format"
44+
with self.assertRaises(ValidationError):
45+
load_policies_yaml(policies_yaml)
46+
47+
policies_files = self.data / "policy" / "policies.yml"
48+
policies_dict = load_policies_yaml(policies_files.read_text())
49+
self.assertIn("license_policies", policies_dict)
50+
51+
def test_scanpipe_policies_load_policies_file(self):
52+
policies_files = self.data / "policy" / "policies.yml"
53+
policies_dict = load_policies_file(policies_files)
54+
self.assertIn("license_policies", policies_dict)
55+
56+
def test_scanpipe_policies_validate_policies(self):
57+
error_msg = "The `policies_dict` argument must be a dictionary."
58+
policies_dict = None
59+
with self.assertRaisesMessage(ValidationError, error_msg):
60+
validate_policies(policies_dict)
61+
62+
policies_dict = []
63+
with self.assertRaisesMessage(ValidationError, error_msg):
64+
validate_policies(policies_dict)
65+
66+
error_msg = "The `license_policies` key is missing from provided policies data."
67+
policies_dict = {}
68+
with self.assertRaisesMessage(ValidationError, error_msg):
69+
validate_policies(policies_dict)
70+
71+
policies_dict = {"missing": "data"}
72+
with self.assertRaisesMessage(ValidationError, error_msg):
73+
validate_policies(policies_dict)
74+
75+
policies_dict = global_policies
76+
self.assertTrue(validate_policies(policies_dict))
77+
78+
def test_scanpipe_policies_make_license_policy_index(self):
79+
policies_dict = {"missing": "data"}
80+
with self.assertRaises(ValidationError):
81+
make_license_policy_index(policies_dict)
82+
83+
self.assertEqual(
84+
license_policies_index, make_license_policy_index(global_policies)
85+
)

0 commit comments

Comments
 (0)