Skip to content

Commit a43c7f1

Browse files
committed
Fix third-party package detection
1 parent f2be1cf commit a43c7f1

3 files changed

Lines changed: 65 additions & 8 deletions

File tree

pyfuse/__main__.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import logging
2121
import argparse
2222
import importlib
23+
import importlib.machinery
2324
from pathlib import Path
2425
from collections.abc import Callable
2526
from importlib.metadata import version as pkg_version
@@ -200,13 +201,24 @@ def _parse_script(script: str) -> tuple[str, ast.Module | None]:
200201

201202

202203
def _is_local_package(module_name: str, script_dir: str) -> bool:
203-
"""Return True if *module_name* resolves to a local directory or .py file."""
204-
for base in (Path(script_dir), Path.cwd()):
205-
if (base / module_name).is_dir():
206-
return True
207-
if (base / f"{module_name}.py").is_file():
208-
return True
209-
return False
204+
"""Return True if *module_name* is importable from the script's runtime path.
205+
206+
The script will be launched with ``cwd`` and its own directory on
207+
``sys.path`` (see ``_build_script_env``); we ask Python's path-based finder
208+
whether the name resolves on exactly that list. We do not mutate
209+
``sys.path`` or ``sys.modules``.
210+
211+
If the module isn't found, we leave it alone and let the script fail at
212+
runtime with the standard ``ModuleNotFoundError`` — that error names the
213+
missing module and is the clearest signal to the user that their layout or
214+
invocation directory is wrong.
215+
"""
216+
search_path = [script_dir, str(Path.cwd())]
217+
try:
218+
spec = importlib.machinery.PathFinder.find_spec(module_name, search_path)
219+
except (ImportError, ValueError):
220+
return False
221+
return spec is not None
210222

211223

212224
def _extract_top_modules(node: ast.AST) -> list[str]:

pyfuse/core/progress.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ def progress(
8181
if cb is None:
8282
return
8383

84-
if _total is not None:
84+
if isinstance(_total, (int, float)):
85+
if _total <= 0:
86+
raise ValueError("Progress total must be a positive number.")
8587
# current / total form
8688
cb(_value, _total, message)
8789
else:

tests/test_venv.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,49 @@ def test_skips_local_module_file(self, tmp_path: pytest.TempPathFactory) -> None
184184
assert "requests" in packages
185185
assert "helpers" not in packages
186186

187+
def test_skips_local_package_reachable_via_cwd(
188+
self, tmp_path: pytest.TempPathFactory, monkeypatch: pytest.MonkeyPatch,
189+
) -> None:
190+
"""A sibling package importable from cwd is treated as local.
191+
192+
Mirrors ``pyfuse run examples/foo.py`` invoked from the project root,
193+
where ``foo.py`` imports a package living at that root.
194+
"""
195+
sibling_pkg = tmp_path / "mypkg" # type: ignore[operator]
196+
sibling_pkg.mkdir()
197+
(sibling_pkg / "__init__.py").write_text("")
198+
199+
scripts_dir = tmp_path / "scripts" # type: ignore[operator]
200+
scripts_dir.mkdir()
201+
script = scripts_dir / "run.py"
202+
script.write_text("from mypkg import thing\nimport requests\n")
203+
204+
monkeypatch.chdir(tmp_path)
205+
packages = _detect_script_packages(str(script))
206+
assert "requests" in packages
207+
assert "mypkg" not in packages
208+
209+
def test_treats_unreachable_package_as_third_party(
210+
self, tmp_path: pytest.TempPathFactory, monkeypatch: pytest.MonkeyPatch,
211+
) -> None:
212+
"""If a module isn't importable from cwd or the script dir, leave it alone:
213+
the script will fail at runtime with a clear ImportError, and the user is
214+
responsible for fixing their layout (we don't silently rewrite imports)."""
215+
# ``mypkg`` lives one level up from cwd and isn't on sys.path.
216+
sibling_pkg = tmp_path / "mypkg" # type: ignore[operator]
217+
sibling_pkg.mkdir()
218+
(sibling_pkg / "__init__.py").write_text("")
219+
220+
scripts_dir = tmp_path / "scripts" # type: ignore[operator]
221+
scripts_dir.mkdir()
222+
script = scripts_dir / "run.py"
223+
script.write_text("from mypkg import thing\n")
224+
225+
monkeypatch.chdir(scripts_dir)
226+
packages = _detect_script_packages(str(script))
227+
# mypkg can't be imported from cwd or script_dir → treated as third-party.
228+
assert "mypkg" in packages
229+
187230
def test_install_package_as(self, tmp_path: pytest.TempPathFactory) -> None:
188231
script = tmp_path / "test_ipa.py" # type: ignore[operator]
189232
script.write_text(

0 commit comments

Comments
 (0)