From 4993b06cce52c573e615003b8d3744b74c508eb0 Mon Sep 17 00:00:00 2001 From: Kaushik Kumar Date: Mon, 21 Sep 2026 03:18:31 +0530 Subject: [PATCH] Add required phrase model command wrapper Signed-off-by: Kaushik Kumar --- etc/scripts/licenses/README.rst | 15 ++- .../licenses/add_model_required_phrases.py | 29 +++++ .../test_add_model_required_phrases.py | 110 ++++++++++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 etc/scripts/licenses/add_model_required_phrases.py create mode 100644 etc/scripts/licenses/test_add_model_required_phrases.py diff --git a/etc/scripts/licenses/README.rst b/etc/scripts/licenses/README.rst index 422a3ede404..ef2c28a0b2d 100644 --- a/etc/scripts/licenses/README.rst +++ b/etc/scripts/licenses/README.rst @@ -1 +1,14 @@ -A set of scripts to update and generate licenses and license rules. \ No newline at end of file +A set of scripts to update and generate licenses and license rules. + + +Model-assisted required phrases +=============================== + +``add_model_required_phrases.py`` delegates to the +`scancode-required-phrases `_ +package. Install or update that package with its ``inference`` extra in the +same environment before running the script. + +.. code-block:: console + + python etc/scripts/licenses/add_model_required_phrases.py --help diff --git a/etc/scripts/licenses/add_model_required_phrases.py b/etc/scripts/licenses/add_model_required_phrases.py new file mode 100644 index 00000000000..42769409a25 --- /dev/null +++ b/etc/scripts/licenses/add_model_required_phrases.py @@ -0,0 +1,29 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# ScanCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/scancode-toolkit for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + + +def main(): + try: + from scancode_required_phrases.model_cli import add_model_required_phrases + except ModuleNotFoundError as error: + if error.name not in { + "scancode_required_phrases", + "scancode_required_phrases.model_cli", + }: + raise + raise SystemExit( + "The scancode-required-phrases model command is required. " + "Install or update the package with its inference extra." + ) from None + + add_model_required_phrases() + + +if __name__ == "__main__": + main() diff --git a/etc/scripts/licenses/test_add_model_required_phrases.py b/etc/scripts/licenses/test_add_model_required_phrases.py new file mode 100644 index 00000000000..6ab1029a512 --- /dev/null +++ b/etc/scripts/licenses/test_add_model_required_phrases.py @@ -0,0 +1,110 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# ScanCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/scancode-toolkit for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +import builtins +from pathlib import Path +import runpy +import sys +from types import ModuleType + +import pytest + + +SCRIPT = Path(__file__).with_name("add_model_required_phrases.py") +MISSING_COMMAND = ( + "The scancode-required-phrases model command is required. " + "Install or update the package with its inference extra." +) + + +def load_wrapper(): + return runpy.run_path(str(SCRIPT)) + + +def test_import_does_not_load_package_or_ml_libraries(monkeypatch): + original_import = builtins.__import__ + + def guarded_import(name, *args, **kwargs): + if name.split(".", 1)[0] in { + "scancode_required_phrases", + "torch", + "transformers", + }: + pytest.fail(f"unexpected import: {name}") + return original_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", guarded_import) + + assert callable(load_wrapper()["main"]) + + +def test_main_delegates_arguments_and_exit(monkeypatch): + calls = [] + + def command(): + calls.append(tuple(sys.argv)) + raise SystemExit(7) + + package = ModuleType("scancode_required_phrases") + package.__path__ = [] + model_cli = ModuleType("scancode_required_phrases.model_cli") + model_cli.add_model_required_phrases = command + monkeypatch.setitem(sys.modules, "scancode_required_phrases", package) + monkeypatch.setitem(sys.modules, "scancode_required_phrases.model_cli", model_cli) + monkeypatch.setattr(sys, "argv", [str(SCRIPT), "--predict-only"]) + + with pytest.raises(SystemExit) as error: + load_wrapper()["main"]() + + assert error.value.code == 7 + assert calls == [(str(SCRIPT), "--predict-only")] + + +@pytest.mark.parametrize( + "missing_name", + [ + "scancode_required_phrases", + "scancode_required_phrases.model_cli", + ], +) +def test_main_reports_a_missing_package_command(monkeypatch, missing_name): + original_import = builtins.__import__ + + def missing_import(name, *args, **kwargs): + if name == "scancode_required_phrases.model_cli": + raise ModuleNotFoundError( + f"No module named {missing_name!r}", + name=missing_name, + ) + return original_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", missing_import) + + with pytest.raises(SystemExit) as error: + load_wrapper()["main"]() + + assert error.value.code == MISSING_COMMAND + assert error.value.__cause__ is None + + +def test_main_does_not_hide_an_unrelated_import_error(monkeypatch): + original_import = builtins.__import__ + missing_dependency = ModuleNotFoundError("No module named 'dependency'", name="dependency") + + def missing_import(name, *args, **kwargs): + if name == "scancode_required_phrases.model_cli": + raise missing_dependency + return original_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", missing_import) + + with pytest.raises(ModuleNotFoundError) as error: + load_wrapper()["main"]() + + assert error.value is missing_dependency