Skip to content

Commit 516949b

Browse files
authored
Add ability to register pipelines through a SCANCODEIO_PIPELINES_DIRS… (#244)
Signed-off-by: Thomas Druez <[email protected]>
1 parent 1285a9a commit 516949b

File tree

7 files changed

+109
-5
lines changed

7 files changed

+109
-5
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44
### unreleased
55

6+
- Add ability to register custom pipelines through a new SCANCODEIO_PIPELINES_DIRS
7+
setting.
8+
https://github.com/nexB/scancode.io/issues/237
9+
610
- Add a pipeline `scan_package.ScanPackage` to scan a single package archive with
711
ScanCode-toolkit.
812
https://github.com/nexB/scancode.io/issues/25

docs/scancodeio-settings.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ of parallel processes to 4::
7474

7575
SCANCODE_DEFAULT_OPTIONS=--processes 4,--timeout 120
7676

77+
SCANCODEIO_PIPELINES_DIRS
78+
-------------------------
79+
80+
This setting defines the additional locations ScanCode.io will search for pipelines.
81+
This should be set to a list of comma-separated strings that contain full paths to your additional
82+
pipelines directories::
83+
84+
SCANCODEIO_PIPELINES_DIRS=/var/scancodeio/pipelines/,/home/user/pipelines/
85+
7786
SCANCODEIO_POLICIES_FILE
7887
------------------------
7988

scancodeio/settings/base.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@
5353

5454
SCANCODEIO_POLICIES_FILE = env.str("SCANCODEIO_POLICIES_FILE", default="policies.yml")
5555

56+
# This setting defines the additional locations ScanCode.io will search for pipelines.
57+
# This should be set to a list of strings that contain full paths to your additional
58+
# pipelines directories.
59+
SCANCODEIO_PIPELINES_DIRS = env.list("SCANCODEIO_PIPELINES_DIRS", default=[])
60+
5661
# Application definition
5762

5863
INSTALLED_APPS = (

scanpipe/apps.py

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
2121
# Visit https://github.com/nexB/scancode.io for support and download.
2222

23+
import inspect
24+
from importlib.machinery import SourceFileLoader
2325
from pathlib import Path
2426

2527
from django.apps import AppConfig
@@ -55,17 +57,32 @@ def ready(self):
5557

5658
def load_pipelines(self):
5759
"""
58-
Load Pipelines from the "scancodeio_pipelines" entry point group.
60+
Load Pipelines from the "scancodeio_pipelines" entry point group and from the
61+
pipelines Python files found at `SCANCODEIO_PIPELINES_DIRS` locations.
5962
"""
6063
entry_points = importlib_metadata.entry_points()
6164

6265
# Ignore duplicated entries caused by duplicated paths in `sys.path`.
6366
pipeline_entry_points = set(entry_points.get("scancodeio_pipelines"))
6467

6568
for entry_point in sorted(pipeline_entry_points):
66-
pipeline_class = entry_point.load()
67-
pipeline_name = entry_point.name
68-
self.register_pipeline(pipeline_name, pipeline_class)
69+
self.register_pipeline(name=entry_point.name, cls=entry_point.load())
70+
71+
# Register user provided pipelines
72+
pipelines_dirs = getattr(settings, "SCANCODEIO_PIPELINES_DIRS", [])
73+
74+
for pipelines_dir in pipelines_dirs:
75+
pipelines_path = Path(pipelines_dir)
76+
77+
if not pipelines_path.is_dir():
78+
raise ImproperlyConfigured(
79+
f'The provided pipelines directory "{pipelines_dir}" in '
80+
f"the SCANCODEIO_PIPELINES_DIRS setting is not available."
81+
)
82+
83+
python_files = pipelines_path.rglob("*.py")
84+
for path in python_files:
85+
self.register_pipeline_from_file(path)
6986

7087
def register_pipeline(self, name, cls):
7188
"""
@@ -83,6 +100,24 @@ def register_pipeline(self, name, cls):
83100

84101
self._pipelines[name] = cls
85102

103+
def register_pipeline_from_file(self, path):
104+
"""
105+
Search for a Pipeline subclass in the provided file `path` and register it
106+
when found.
107+
"""
108+
module_name = path.stem
109+
module = SourceFileLoader(module_name, str(path)).load_module()
110+
pipeline_classes = inspect.getmembers(module, is_pipeline)
111+
112+
if len(pipeline_classes) > 1:
113+
raise ImproperlyConfigured(
114+
f"Only one Pipeline class allowed per pipeline file: {path}."
115+
)
116+
117+
elif pipeline_classes:
118+
pipeline_class = pipeline_classes[0][1]
119+
self.register_pipeline(name=module_name, cls=pipeline_class)
120+
86121
@property
87122
def pipelines(self):
88123
return dict(self._pipelines)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 scanpipe.pipelines import Pipeline
24+
25+
26+
class RegisterFromFile(Pipeline):
27+
"""
28+
A pipeline to be registered from its file path.
29+
"""
30+
31+
@classmethod
32+
def steps(cls):
33+
return (cls.step1,)
34+
35+
def step1(self):
36+
pass

scanpipe/tests/test_apps.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,21 @@
2323
from pathlib import Path
2424

2525
from django.apps import apps
26+
from django.core.exceptions import ImproperlyConfigured
2627
from django.test import TestCase
2728
from django.test import override_settings
2829

2930
from scanpipe.apps import ScanPipeConfig
3031
from scanpipe.tests import license_policies
3132
from scanpipe.tests import license_policies_index
33+
from scanpipe.tests.pipelines.register_from_file import RegisterFromFile
3234

3335
scanpipe_app = apps.get_app_config("scanpipe")
3436

3537

3638
class ScanPipeAppsTest(TestCase):
3739
data_location = Path(__file__).parent / "data"
40+
pipelines_location = Path(__file__).parent / "pipelines"
3841

3942
def test_scanpipe_apps_get_policies_index(self):
4043
self.assertEqual({}, ScanPipeConfig.get_policies_index([], "license_key"))
@@ -70,3 +73,16 @@ def test_scanpipe_apps_policies_enabled(self):
7073
self.assertFalse(scanpipe_app.policies_enabled)
7174
scanpipe_app.license_policies_index = {"key": "value"}
7275
self.assertTrue(scanpipe_app.policies_enabled)
76+
77+
def test_scanpipe_apps_register_pipeline_from_file(self):
78+
path = self.pipelines_location / "do_nothing.py"
79+
with self.assertRaises(ImproperlyConfigured):
80+
scanpipe_app.register_pipeline_from_file(path)
81+
82+
path = self.pipelines_location / "register_from_file.py"
83+
scanpipe_app.register_pipeline_from_file(path)
84+
85+
self.assertEqual(
86+
RegisterFromFile.__name__,
87+
scanpipe_app.pipelines.get("register_from_file").__name__,
88+
)

scanpipe/tests/test_pipelines.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
from scanpipe.pipelines import Pipeline
3434
from scanpipe.pipelines import is_pipeline
3535
from scanpipe.pipelines import root_filesystems
36-
from scanpipe.pipelines import scan_package
3736
from scanpipe.tests.pipelines.do_nothing import DoNothing
3837
from scanpipe.tests.pipelines.steps_as_attribute import StepsAsAttribute
3938

0 commit comments

Comments
 (0)