Skip to content

Commit

Permalink
py: resolve module paths without executing modules
Browse files Browse the repository at this point in the history
Signed-off-by: Filipe Laíns <[email protected]
  • Loading branch information
FFY00 committed Jan 9, 2025
1 parent 2bb5a75 commit 79e4a4c
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 3 deletions.
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ extend-select = [
lines-after-imports = 2
lines-between-types = 1

[tool.ruff.lint.per-file-ignores]
'tests/packages/error-on-import/**' = ['TRY002', 'TRY003', 'EM101']

[tool.coverage.run]
source = [
'pkgconf',
Expand Down
35 changes: 32 additions & 3 deletions src/pkgconf/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import importlib
import importlib.util
import itertools
import logging
import operator
Expand All @@ -8,6 +8,8 @@
import subprocess
import sys
import sysconfig
import types
import unittest.mock
import warnings

from typing import Any, Optional
Expand Down Expand Up @@ -67,9 +69,36 @@ def get_executable() -> pathlib.Path:
raise RuntimeError(msg)


def _import_module_no_exec(name: str) -> types.ModuleType:
"""Return a module object with all the module attributes set, but without executing it."""
if name in sys.modules:
return sys.modules[name]
# Import parent
if parent := name.rpartition('.')[0]:
_import_module_no_exec(parent)
# Find spec
spec = importlib.util.find_spec(name)
if not spec:
msg = f'No module named {name!r}'
raise ModuleNotFoundError(msg)
# Create module object without executing
module = importlib.util.module_from_spec(spec)
# Save to sys/modules
sys.modules[name] = module
return module


def _get_module_paths(name: str) -> Sequence[str]:
module = importlib.import_module(name)
return list(module.__path__)
try:
with unittest.mock.patch.dict('sys.modules', sys.modules.copy()):
module = _import_module_no_exec(name)
if not hasattr(module, '__path__'):
warnings.warn(f"{module} isn't a package, it won't be added to PKG_CONFIG_PATH", stacklevel=2)
return []
return list(module.__path__)
except Exception:
_LOGGER.exception(f'Failed to find paths for module {name!r}')
return []


def get_pkg_config_path() -> Sequence[str]:
Expand Down
1 change: 1 addition & 0 deletions tests/packages/error-on-import/foo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
raise Exception('Error during import :(')
1 change: 1 addition & 0 deletions tests/packages/error-on-import/foo/bar/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
raise Exception('Error during import :(')
5 changes: 5 additions & 0 deletions tests/packages/error-on-import/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
project('error-on-import', 'c', version: '1.0.0')

py = import('python').find_installation()
py.install_sources('foo/__init__.py', subdir: 'foo')
py.install_sources('foo/bar/__init__.py', subdir: 'foo/bar')
11 changes: 11 additions & 0 deletions tests/packages/error-on-import/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[build-system]
build-backend = 'mesonpy'
requires = ['meson-python']

[project]
name = 'error-on-import'
version = '1.0.0'

[project.entry-points.pkg_config]
error-on-import-foo = 'foo'
error-on-import-bar = 'foo.bar'
11 changes: 11 additions & 0 deletions tests/test_python_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ def test_pkg_config_path_namespace(env, packages):
assert path == [pathlib.Path(env.scheme['purelib'], 'namespace')]


def test_pkg_config_path_error_on_import(env, packages):
path = list(env.introspectable.call('pkgconf.get_pkg_config_path'))
assert len(path) == 0

env.install_from_path(packages / 'error-on-import', from_sdist=False)
env_site_dir = pathlib.Path(env.scheme['purelib'])

path = set(map(pathlib.Path, env.introspectable.call('pkgconf.get_pkg_config_path')))
assert path == {env_site_dir / 'foo', env_site_dir / 'foo' / 'bar'}


def test_run_pkgconfig(env):
output = env.introspectable.call('pkgconf.run_pkgconf', '--help', capture_output=True)
assert output.stdout.decode().startswith('usage: pkgconf')
Expand Down

0 comments on commit 79e4a4c

Please sign in to comment.