Skip to content

Commit 956e5b2

Browse files
authored
Simplify and improve setuptools.config.expand._find_module (#4405)
2 parents e6c3f1f + 6c8d66c commit 956e5b2

File tree

3 files changed

+27
-36
lines changed

3 files changed

+27
-36
lines changed

newsfragments/4405.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improvement for ``attr:`` directives in configuration to handle
2+
more edge cases related to complex ``package_dir``.

setuptools/config/expand.py

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import sys
2626
from glob import iglob
2727
from configparser import ConfigParser
28-
from importlib.machinery import ModuleSpec
28+
from importlib.machinery import ModuleSpec, all_suffixes
2929
from itertools import chain
3030
from typing import (
3131
TYPE_CHECKING,
@@ -47,14 +47,12 @@
4747
from distutils.errors import DistutilsOptionError
4848

4949
from .._path import same_path as _same_path, StrPath
50+
from ..discovery import find_package_path
5051
from ..warnings import SetuptoolsWarning
5152

5253
if TYPE_CHECKING:
5354
from setuptools.dist import Distribution # noqa
54-
from setuptools.discovery import ConfigDiscovery # noqa
55-
from distutils.dist import DistributionMetadata # noqa
5655

57-
chain_iter = chain.from_iterable
5856
_K = TypeVar("_K")
5957
_V = TypeVar("_V", covariant=True)
6058

@@ -187,7 +185,7 @@ def read_attr(
187185
attr_name = attrs_path.pop()
188186
module_name = '.'.join(attrs_path)
189187
module_name = module_name or '__init__'
190-
_parent_path, path, module_name = _find_module(module_name, package_dir, root_dir)
188+
path = _find_module(module_name, package_dir, root_dir)
191189
spec = _find_spec(module_name, path)
192190

193191
try:
@@ -220,36 +218,25 @@ def _load_spec(spec: ModuleSpec, module_name: str) -> ModuleType:
220218

221219
def _find_module(
222220
module_name: str, package_dir: Optional[Mapping[str, str]], root_dir: StrPath
223-
) -> Tuple[StrPath, Optional[str], str]:
224-
"""Given a module (that could normally be imported by ``module_name``
225-
after the build is complete), find the path to the parent directory where
226-
it is contained and the canonical name that could be used to import it
227-
considering the ``package_dir`` in the build configuration and ``root_dir``
221+
) -> Optional[str]:
222+
"""Find the path to the module named ``module_name``,
223+
considering the ``package_dir`` in the build configuration and ``root_dir``.
224+
225+
>>> tmp = getfixture('tmpdir')
226+
>>> _ = tmp.ensure("a/b/c.py")
227+
>>> _ = tmp.ensure("a/b/d/__init__.py")
228+
>>> r = lambda x: x.replace(str(tmp), "tmp").replace(os.sep, "/")
229+
>>> r(_find_module("a.b.c", None, tmp))
230+
'tmp/a/b/c.py'
231+
>>> r(_find_module("f.g.h", {"": "1", "f": "2", "f.g": "3", "f.g.h": "a/b/d"}, tmp))
232+
'tmp/a/b/d/__init__.py'
228233
"""
229-
parent_path = root_dir
230-
module_parts = module_name.split('.')
231-
if package_dir:
232-
if module_parts[0] in package_dir:
233-
# A custom path was specified for the module we want to import
234-
custom_path = package_dir[module_parts[0]]
235-
parts = custom_path.rsplit('/', 1)
236-
if len(parts) > 1:
237-
parent_path = os.path.join(root_dir, parts[0])
238-
parent_module = parts[1]
239-
else:
240-
parent_module = custom_path
241-
module_name = ".".join([parent_module, *module_parts[1:]])
242-
elif '' in package_dir:
243-
# A custom parent directory was specified for all root modules
244-
parent_path = os.path.join(root_dir, package_dir[''])
245-
246-
path_start = os.path.join(parent_path, *module_name.split("."))
247-
candidates = chain(
248-
(f"{path_start}.py", os.path.join(path_start, "__init__.py")),
249-
iglob(f"{path_start}.*"),
234+
path_start = find_package_path(module_name, package_dir or {}, root_dir)
235+
candidates = chain.from_iterable(
236+
(f"{path_start}{ext}", os.path.join(path_start, f"__init__{ext}"))
237+
for ext in all_suffixes()
250238
)
251-
module_path = next((x for x in candidates if os.path.isfile(x)), None)
252-
return parent_path, module_path, module_name
239+
return next((x for x in candidates if os.path.isfile(x)), None)
253240

254241

255242
def resolve_class(
@@ -263,8 +250,8 @@ def resolve_class(
263250
class_name = qualified_class_name[idx + 1 :]
264251
pkg_name = qualified_class_name[:idx]
265252

266-
_parent_path, path, module_name = _find_module(pkg_name, package_dir, root_dir)
267-
module = _load_spec(_find_spec(module_name, path), module_name)
253+
path = _find_module(pkg_name, package_dir, root_dir)
254+
module = _load_spec(_find_spec(pkg_name, path), pkg_name)
268255
return getattr(module, class_name)
269256

270257

setuptools/tests/config/test_expand.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import sys
23
from pathlib import Path
34

45
import pytest
@@ -147,7 +148,8 @@ def test_import_order(self, tmp_path):
147148
({}, "flat_layout/pkg.py", "flat_layout.pkg", 836),
148149
],
149150
)
150-
def test_resolve_class(tmp_path, package_dir, file, module, return_value):
151+
def test_resolve_class(monkeypatch, tmp_path, package_dir, file, module, return_value):
152+
monkeypatch.setattr(sys, "modules", {}) # reproducibility
151153
files = {file: f"class Custom:\n def testing(self): return {return_value}"}
152154
write_files(files, tmp_path)
153155
cls = expand.resolve_class(f"{module}.Custom", package_dir, tmp_path)

0 commit comments

Comments
 (0)