Skip to content

Commit 63cf46f

Browse files
committed
feat(cli): add --no-venv to skip virtualenv creation and use the ambient env
Closes #46 Add a --no-venv flag (AnalysisOptions.no_venv) that skips virtualenv creation and dependency installation and resolves imports against the ambient interpreter (self.virtualenv stays None, so Jedi uses the default environment). Useful in CI / containers where the project's dependencies are already installed, for sandboxed runs where network installs are disallowed, and for speed. Tradeoff: import / call-resolution quality then depends on what is installed in the ambient env. Regenerates the README --help block; adds a CLI regression test (no virtualenv is created and analysis.json is still produced).
1 parent de41937 commit 63cf46f

5 files changed

Lines changed: 46 additions & 3 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,11 @@ $ canpy --help
185185
│ [default: lazy] │
186186
│ --skip-tests --include-tests Skip test files in analysis. │
187187
│ [default: skip-tests] │
188+
│ --no-venv --venv Skip virtualenv creation and │
189+
│ dependency installation; resolve │
190+
│ imports against the ambient Python │
191+
│ environment instead. │
192+
│ [default: venv] │
188193
│ --file-name PATH Analyze only the specified file │
189194
│ (relative to input directory). │
190195
│ --cache-dir -c PATH Directory to store analysis cache. │

codeanalyzer/__main__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ def main(
104104
help="Skip test files in analysis.",
105105
),
106106
] = True,
107+
no_venv: Annotated[
108+
bool,
109+
typer.Option(
110+
"--no-venv/--venv",
111+
help="Skip virtualenv creation and dependency installation; resolve "
112+
"imports against the ambient Python environment instead.",
113+
),
114+
] = False,
107115
file_name: Annotated[
108116
Optional[Path],
109117
typer.Option(
@@ -144,6 +152,7 @@ def main(
144152
using_ray=using_ray,
145153
rebuild_analysis=rebuild_analysis,
146154
skip_tests=skip_tests,
155+
no_venv=no_venv,
147156
file_name=file_name,
148157
cache_dir=cache_dir,
149158
clear_cache=clear_cache,

codeanalyzer/core.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def __init__(self, options: AnalysisOptions) -> None:
6666
self.skip_tests = options.skip_tests
6767
self.using_codeql = options.using_codeql
6868
self.rebuild_analysis = options.rebuild_analysis
69+
self.no_venv = options.no_venv
6970
self.cache_dir = (
7071
options.cache_dir.resolve() if options.cache_dir is not None else self.project_dir
7172
) / ".codeanalyzer"
@@ -260,8 +261,13 @@ def __enter__(self) -> "Codeanalyzer":
260261
venv_path = self.cache_dir / self.project_dir.name / "virtualenv"
261262
# Ensure the cache directory exists for this project
262263
venv_path.parent.mkdir(parents=True, exist_ok=True)
264+
if self.no_venv:
265+
logger.info(
266+
"--no-venv: using the ambient Python environment "
267+
"(skipping virtualenv creation and dependency installation)"
268+
)
263269
# Create the virtual environment if it does not exist
264-
if not venv_path.exists() or self.rebuild_analysis:
270+
if not self.no_venv and (not venv_path.exists() or self.rebuild_analysis):
265271
logger.info(f"(Re-)creating virtual environment at {venv_path}")
266272
self._cmd_exec_helper(
267273
[str(self._get_base_interpreter()), "-m", "venv", str(venv_path)],
@@ -320,8 +326,9 @@ def __enter__(self) -> "Codeanalyzer":
320326
# Point Jedi at the analysis venv so it resolves the project's third-party
321327
# imports. This runs on both a fresh build and a lazy reuse of an existing
322328
# venv -- previously self.virtualenv stayed None, so the install above was
323-
# never actually used by the symbol-table builder.
324-
if venv_path.exists():
329+
# never actually used by the symbol-table builder. With --no-venv we leave
330+
# it None so Jedi resolves against the ambient interpreter instead.
331+
if not self.no_venv and venv_path.exists():
325332
self.virtualenv = venv_path
326333

327334
if self.using_codeql:

codeanalyzer/options/options.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class AnalysisOptions:
3838
using_ray: bool = False
3939
rebuild_analysis: bool = False
4040
skip_tests: bool = True
41+
no_venv: bool = False
4142
file_name: Optional[Path] = None
4243
cache_dir: Optional[Path] = None
4344
clear_cache: bool = False

test/test_cli.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,27 @@ def test_cli_call_symbol_table_with_json(cli_runner, whole_applications__xarray)
3838
assert len(json_obj["symbol_table"]) > 0, "Symbol table should not be empty"
3939

4040

41+
def test_no_venv_skips_virtualenv(
42+
cli_runner, single_functionalities__stuff_nested_in_functions, tmp_path
43+
):
44+
"""#46: --no-venv must skip virtualenv creation/installation and still analyze."""
45+
out = tmp_path / "out"
46+
cache = tmp_path / "cache"
47+
result = cli_runner.invoke(
48+
app,
49+
[
50+
"--input", str(single_functionalities__stuff_nested_in_functions),
51+
"--output", str(out),
52+
"--cache-dir", str(cache),
53+
"--no-venv", "--no-codeql", "--no-ray",
54+
],
55+
env={"NO_COLOR": "1", "TERM": "dumb"},
56+
)
57+
assert result.exit_code == 0, result.output
58+
assert (out / "analysis.json").exists(), "analysis.json should still be produced with --no-venv"
59+
assert not list(cache.rglob("virtualenv")), "--no-venv must not create a virtualenv"
60+
61+
4162
def test_single_file(cli_runner, single_functionalities__stuff_nested_in_functions):
4263
"""Must be able to run the CLI with single file analysis using --file-name flag."""
4364
output_dir = single_functionalities__stuff_nested_in_functions.joinpath(".output")

0 commit comments

Comments
 (0)