diff --git a/deepdiff/cli.py b/deepdiff/cli.py new file mode 100644 index 00000000..fdbd3b0c --- /dev/null +++ b/deepdiff/cli.py @@ -0,0 +1,29 @@ +"""Console-script entry point for the ``deep`` command. + +The command line dependencies (``click``, and ``pyyaml`` for YAML files) are +optional and installed via the ``cli`` extra (``pip install deepdiff[cli]``). +The ``deep`` script itself is always installed, so importing +:mod:`deepdiff.commands` directly raises a bare +``ModuleNotFoundError: No module named 'click'`` on a default install +(see https://github.com/seperman/deepdiff/issues/594). This thin wrapper checks +for the dependency first and exits with an actionable message instead of a +traceback. +""" + +import sys + + +def main(): + """Entry point for the ``deep`` console script.""" + try: + import click # noqa: F401 + except ImportError: + sys.exit( + "The 'deep' command line tool requires extra dependencies that are " + "not installed.\n" + "Install them with:\n\n pip install deepdiff[cli]\n" + ) + + from deepdiff.commands import cli + + cli() diff --git a/pyproject.toml b/pyproject.toml index 980cb6f9..f9a54d7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,7 +83,7 @@ optimize = [ ] [project.scripts] -deep = "deepdiff.commands:cli" +deep = "deepdiff.cli:main" [project.urls] Homepage = "https://zepworks.com/deepdiff/" diff --git a/tests/test_command.py b/tests/test_command.py index 25427a7b..bbf5ada3 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -116,3 +116,40 @@ def test_command_extract(self): diffed = runner.invoke(extract, ['root[2][2]', path]) assert 0 == diffed.exit_code assert '0.288\n' == diffed.output + + +class TestCliEntryPoint: + """Tests for the ``deep`` console-script wrapper (deepdiff/cli.py).""" + + def test_main_runs_cli_when_click_available(self): + # click is available in the test environment; invoking the entry point + # with --help should print the group help and exit cleanly. + from click.testing import CliRunner + + from deepdiff.commands import cli + + result = CliRunner().invoke(cli, ['--help']) + assert result.exit_code == 0 + assert 'command line tool' in result.output + + def test_main_reports_missing_click_dependency(self, monkeypatch): + # Simulate a default install without the optional ``cli`` extra: the + # entry point must fail with an actionable message rather than a bare + # ModuleNotFoundError traceback. See issue #594. + import builtins + + from deepdiff import cli as cli_entry + + real_import = builtins.__import__ + + def fake_import(name, *args, **kwargs): + if name == 'click': + raise ImportError("No module named 'click'") + return real_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, '__import__', fake_import) + + with pytest.raises(SystemExit) as exc_info: + cli_entry.main() + + assert 'pip install deepdiff[cli]' in str(exc_info.value)