Skip to content

Commit

Permalink
fix: support editable install with multiple roots
Browse files Browse the repository at this point in the history
Python packages may have multiple root modules where
each is installed to its own folder under `site-packages`.

When using new setuptools editable install in such cases
griffe, when used by mkdocstrings fails to find the
correct paths.

The root cause it that griffe assumed a variable named
`MAPPING` in the `.py` file created by the editable install,
and assumed that this variable is a dict with single entry.

When a package has multiple roots - then this dictionary
contain multiple entries.

This PR aims to handle such cases.

I have no knowledge about `editables` so I could not fix
that case.

The unit test added simulates the case, and this has been tested
against a real complex package with multiple roots using setuptools
65 editable install.
  • Loading branch information
gilfree committed Jan 17, 2023
1 parent 34bc108 commit a1b0390
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 13 deletions.
26 changes: 15 additions & 11 deletions src/griffe/finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import ast
import os
import re
import sys
Expand Down Expand Up @@ -268,8 +269,12 @@ def _extend_from_editable_modules(self):
for path in self.search_paths: # noqa: WPS440
for item in self._contents(path):
if item.stem.startswith(("__editables_", "__editable__")) and item.suffix == ".py":
with suppress(UnhandledEditableModuleError):
self._append_search_path(_handle_editable_module(item))
self._extend_from_an_editable_module(item)

def _extend_from_an_editable_module(self, item: Path):
with suppress(UnhandledEditableModuleError):
for editable_path in _handle_editable_module(item):
self._append_search_path(editable_path)

def _filter_py_modules(self, path: Path) -> Iterator[Path]:
for root, dirs, files in os.walk(path, topdown=True):
Expand Down Expand Up @@ -338,22 +343,21 @@ def _handle_editable_module(path: Path): # noqa: WPS231
editable_lines = path.read_text(encoding="utf8").splitlines(keepends=False)
except FileNotFoundError:
raise UnhandledEditableModuleError(path)

if path.name.startswith("__editables_"):
# support for how 'editables' writes these files:
# example line: F.map_module('griffe', '/media/data/dev/griffe/src/griffe/__init__.py')
new_path = Path(editable_lines[-1].split("'")[3])
if new_path.exists(): # TODO: could remove existence check
if new_path.name.startswith("__init__"):
return new_path.parent.parent
return new_path
return [new_path.parent.parent]
return [new_path]
elif path.name.startswith("__editable__"):
# support for how 'setuptools' writes these files:
# example line: MAPPING = {'griffe': '/media/data/dev/griffe/src/griffe'}
# example line: MAPPING = {'griffe': '/media/data/dev/griffe/src/griffe', 'briffe': '/media/data/dev/griffe/src/briffe'}
for line in editable_lines:
match = _re_mapping_line.match(line)
if match:
new_path = Path(match.group(1))
if new_path.exists(): # TODO: could remove existence check
return new_path.parent
break
if line.startswith("MAPPING"):
paths = ast.literal_eval(line.split(" = ", 1)[1])
assert isinstance(paths, dict)
return [Path(pt).parent for pt in paths.values()]
raise UnhandledEditableModuleError(path)
15 changes: 13 additions & 2 deletions tests/test_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def test_editables_file_handling(tmp_path):
"""
pth_file = tmp_path / "__editables_whatever.py"
pth_file.write_text("hello\nF.map_module('griffe', 'src/griffe/__init__.py')")
assert _handle_editable_module(pth_file) == Path("src")
assert list(_handle_editable_module(pth_file)) == [Path("src")]


def test_setuptools_file_handling(tmp_path):
Expand All @@ -105,4 +105,15 @@ def test_setuptools_file_handling(tmp_path):
"""
pth_file = tmp_path / "__editable__whatever.py"
pth_file.write_text("hello\nMAPPING = {'griffe': 'src/griffe'}")
assert _handle_editable_module(pth_file) == Path("src")
assert list(_handle_editable_module(pth_file)) == [Path("src")]


def test_setuptools_file_handling_multiple_paths(tmp_path):
"""Assert editable modules by `setuptools` are handled when multiple packages are installed in the same editable.
Parameters:
tmp_path: Pytest fixture.
"""
pth_file = tmp_path / "__editable__whatever.py"
pth_file.write_text("hello\nMAPPING = {'griffe': 'src1/griffe', 'briffe':'src2/briffe'}")
assert list(_handle_editable_module(pth_file)) == [Path("src1"), Path("src2")]

0 comments on commit a1b0390

Please sign in to comment.