Skip to content

Commit 1edc252

Browse files
committed
warn users if both dir and module exist with the same name, refs #233
1 parent faa1d67 commit 1edc252

3 files changed

Lines changed: 50 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
- Private function decorators (those starting with "\_")
66
are now hidden by default. (@zmoon)
7+
- If pdoc is invoked with a name that is both an installed Python module
8+
and a local directory, notify the user that the installed module will be documented.
79

810
# 2021-03-10: pdoc 6.4.0
911

pdoc/extract.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,36 @@ def parse_spec(spec: Union[Path, str]) -> str:
159159
*This function has side-effects:* `sys.path` will be amended if the specification is a path.
160160
If this side-effect is undesired, pass a module name instead.
161161
"""
162-
# isinstance check is required as Path is not iterable.
163-
if not isinstance(spec, Path) and (
162+
pspec = Path(spec)
163+
if isinstance(spec, str) and (
164164
os.sep in spec or (os.altsep and os.altsep in spec)
165165
):
166-
spec = Path(spec)
166+
# We have a path separator, so it's definitely a filepath.
167+
spec = pspec
168+
169+
if isinstance(spec, str) and (pspec.is_file() or (pspec / "__init__.py").is_file()):
170+
# We have a local file with this name, but is there also a module with the same name?
171+
try:
172+
with mock_some_common_side_effects():
173+
modspec = importlib.util.find_spec(spec)
174+
if modspec is None:
175+
raise ModuleNotFoundError
176+
except AnyException:
177+
# Module does not exist, use local file.
178+
spec = pspec
179+
else:
180+
# Module does exist. We now check if the local file/directory is the same (e.g. after pip install -e),
181+
# and emit a warning if that's not the case.
182+
origin = Path(modspec.origin).absolute() if modspec.origin else Path("unknown")
183+
local_dir = Path(spec).absolute()
184+
if local_dir not in (origin, origin.parent):
185+
print(
186+
f"Warning: {spec!r} may refer to either the installed Python module or the local file/directory "
187+
f"with the same name. pdoc will document the installed module, prepend './' to force "
188+
f"documentation of the local file/directory.\n"
189+
f" - Module location: {origin}\n"
190+
f" - Local file/directory: {local_dir}"
191+
)
167192

168193
if isinstance(spec, Path):
169194
if (spec.parent / "__init__.py").exists():
@@ -191,7 +216,8 @@ def module_mtime(modulename: str) -> Optional[float]:
191216
"""Returns the time the specified module file was last modified, or `None` if this cannot be determined.
192217
The primary use of this is live-reloading modules on modification."""
193218
try:
194-
spec = importlib.util.find_spec(modulename)
219+
with mock_some_common_side_effects():
220+
spec = importlib.util.find_spec(modulename)
195221
except AnyException:
196222
pass
197223
else:

test/test_extract.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,24 @@ def test_parse_spec():
4444
sys.path = p
4545

4646

47+
def test_parse_spec_mod_and_dir(capsys, tmp_path, monkeypatch):
48+
"""Test that we display a warning when both a module and a local directory exist with the provided name."""
49+
(tmp_path / "dataclasses").mkdir()
50+
(tmp_path / "dataclasses" / "__init__.py").touch()
51+
monkeypatch.chdir(tmp_path)
52+
53+
assert parse_spec("dataclasses") == "dataclasses"
54+
captured = capsys.readouterr()
55+
assert captured.out.startswith(
56+
"Warning: 'dataclasses' may refer to either the installed Python module or the local file/directory"
57+
)
58+
59+
monkeypatch.chdir(here / "testdata")
60+
assert parse_spec("demo.py") == "demo"
61+
captured = capsys.readouterr()
62+
assert not captured.out
63+
64+
4765
def test_module_mtime():
4866
assert module_mtime("dataclasses")
4967
assert module_mtime("unknown") is None

0 commit comments

Comments
 (0)