diff --git a/fawltydeps/main.py b/fawltydeps/main.py index 8c34f1e5..a6489d29 100644 --- a/fawltydeps/main.py +++ b/fawltydeps/main.py @@ -106,7 +106,8 @@ def create(cls, settings: Settings) -> "Analysis": assert ret.imports is not None # convince Mypy that these cannot assert ret.declared_deps is not None # be None at this time. ret.resolved_deps = resolve_dependencies( - dep.name for dep in ret.declared_deps + (dep.name for dep in ret.declared_deps), + venv_path=settings.venv, ) if ret.is_enabled(Action.REPORT_UNDECLARED): diff --git a/fawltydeps/settings.py b/fawltydeps/settings.py index cba03681..8e5627a5 100644 --- a/fawltydeps/settings.py +++ b/fawltydeps/settings.py @@ -120,6 +120,7 @@ class Settings(BaseSettings): # type: ignore actions: Set[Action] = {Action.REPORT_UNDECLARED, Action.REPORT_UNUSED} code: Set[PathOrSpecial] = {Path(".")} deps: Set[Path] = {Path(".")} + venv: Optional[Path] = None output_format: OutputFormat = OutputFormat.HUMAN_SUMMARY ignore_undeclared: Set[str] = set() ignore_unused: Set[str] = set() @@ -325,6 +326,16 @@ def populate_parser_options(parser: argparse._ActionsContainer) -> None: " to looking for supported files in the current directory)" ), ) + parser.add_argument( + "--venv", + type=Path, + metavar="VENV_DIR", + help=( + "Where to find a virtualenv that has the project dependencies" + " installed, defaults to the Python environment where FawltyDeps is" + " installed." + ), + ) parser.add_argument( "--ignore-undeclared", nargs="+", diff --git a/noxfile.py b/noxfile.py index 890ff0b2..8e0d3dfe 100644 --- a/noxfile.py +++ b/noxfile.py @@ -81,7 +81,7 @@ def lint(session): session.run("pylint", "fawltydeps") session.run( "pylint", - "--disable=missing-function-docstring,invalid-name,redefined-outer-name", + "--disable=missing-function-docstring,invalid-name,redefined-outer-name,too-many-lines", "tests", ) diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 88ac4c7f..a7c5f3f7 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -116,6 +116,7 @@ def test_list_imports_json__from_py_file__prints_imports_from_file(write_tmp_fil "actions": ["list_imports"], "code": [f"{tmp_path}/myfile.py"], "deps": ["."], + "venv": None, "output_format": "json", "ignore_undeclared": [], "ignore_unused": [], @@ -302,6 +303,7 @@ def test_list_deps_json__dir__prints_deps_from_requirements_txt( "actions": ["list_deps"], "code": ["."], "deps": [f"{tmp_path}"], + "venv": None, "output_format": "json", "ignore_undeclared": [], "ignore_unused": [], @@ -565,6 +567,7 @@ def test_check_json__simple_project__can_report_both_undeclared_and_unused( "actions": ["check_undeclared", "check_unused"], "code": [f"{tmp_path}"], "deps": [f"{tmp_path}"], + "venv": None, "output_format": "json", "ignore_undeclared": [], "ignore_unused": [], @@ -753,6 +756,25 @@ def test__quiet_check__writes_only_names_of_unused_and_undeclared( assert returncode == 3 +def test_check__simple_project_in_fake_venv__resolves_imports_vs_deps( + fake_venv, project_with_code_and_requirements_txt +): + tmp_path = project_with_code_and_requirements_txt( + imports=["requests"], + declares=["pandas"], + ) + # A venv where the "pandas" package provides a "requests" import name + # should satisfy our comparison + venv_dir = fake_venv({"pandas": {"requests"}}) + + output, errors, returncode = run_fawltydeps( + "--detailed", f"--code={tmp_path}", f"--deps={tmp_path}", f"--venv={venv_dir}" + ) + assert output.splitlines() == [] + assert errors == "" + assert returncode == 0 + + @pytest.mark.parametrize( "args,imports,dependencies,expected", [ @@ -892,6 +914,7 @@ def test_cmdline_on_ignored_undeclared_option( actions = ['list_imports'] # code = ['.'] deps = ['foobar'] + # venv = None output_format = 'human_detailed' # ignore_undeclared = [] # ignore_unused = [] diff --git a/tests/test_settings.py b/tests/test_settings.py index ae309b27..b4f5ae33 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -23,6 +23,7 @@ actions={Action.REPORT_UNDECLARED, Action.REPORT_UNUSED}, code={Path(".")}, deps={Path(".")}, + venv=None, output_format=OutputFormat.HUMAN_SUMMARY, ignore_undeclared=set(), ignore_unused=set(),