Skip to content

Commit

Permalink
Extend resource_files for entry-points
Browse files Browse the repository at this point in the history
Signed-off-by: Cristian Le <[email protected]>
  • Loading branch information
LecrisUT committed May 20, 2024
1 parent 43ce77f commit 5c79195
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 10 deletions.
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ repos:
#
# For now, we simply copy & paste from pyproject.toml :(
additional_dependencies:
- "importlib-metadata>=3.6.0; python_version < '3.10'"
- "click>=8.0.3,!=8.1.4" # 8.1.3 / 8.1.6 TODO type annotations tmt.cli.Context -> click.core.Context click/issues/2558
- "docutils>=0.16" # 0.16 is the current one available for RHEL9
- "fmf>=1.3.0"
Expand Down Expand Up @@ -75,6 +76,7 @@ repos:
#
# For now, we simply copy & paste from pyproject.toml :(
additional_dependencies:
- "importlib-metadata>=3.6.0; python_version < '3.10'"
- "click>=8.0.3,!=8.1.4" # 8.1.3 / 8.1.6 TODO type annotations tmt.cli.Context -> click.core.Context click/issues/2558
- "docutils>=0.16" # 0.16 is the current one available for RHEL9
- "fmf>=1.3.0"
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ classifiers = [
"Operating System :: POSIX :: Linux",
]
dependencies = [ # F39 / PyPI
"importlib-metadata>=3.6.0; python_version < '3.10'",
"click>=8.0.3,!=8.1.4", # 8.1.3 / 8.1.6 TODO type annotations tmt.cli.Context -> click.core.Context click/issues/2558
"docutils>=0.16", # 0.16 is the current one available for RHEL9
"fmf>=1.3.0",
Expand Down
67 changes: 57 additions & 10 deletions tmt/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
from collections import Counter, OrderedDict
from collections.abc import Iterable, Iterator, Sequence
from contextlib import suppress

if sys.version_info < (3, 10):
from importlib_metadata import entry_points
else:
from importlib.metadata import entry_points
from importlib.readers import MultiplexedPath
from re import Match, Pattern
from threading import Thread
from types import ModuleType
Expand Down Expand Up @@ -7058,16 +7064,57 @@ def resource_files(
Helper function to get path of package file or directory.
A thin wrapper for :py:func:`importlib.resources.files`:
``files()`` returns ``Traversable`` object, though in our use-case
it should always produce a :py:class:`pathlib.PosixPath` object.
Converting it to :py:class:`tmt.utils.Path` instance should be
safe and stick to the "``Path`` only!" rule in tmt's code base.
:param path: file or directory path to retrieve, relative to the ``package`` root.
:param package: package in which to search for the file/directory.
:returns: an absolute path to the requested file or directory.
"""
return importlib.resources.files(package) / path
``files()`` returns ``Traversable`` object that can be either a
:py:class:`pathlib.PosixPath` or a :py:class:`importlib.reader.MultiplexedPath`.
If the final path is a file, go ahead and convert it to a regular ``Path``, otherwise
keep it as a traversable in order to properly support MultiplexedPaths.
Additional search paths are introduced from entry-point definitions
:param path: file or directory path to retrieve, relative to the ``package``
or entry-point's root.
:param package: primary package in which to search for the file/directory.
:returns: a traversable path to the requested file or directory.
"""
def accumulate_path(paths: list[pathlib.Path],
pkg_path: Union[pathlib.Path,
MultiplexedPath]) -> None:
if isinstance(pkg_path, MultiplexedPath):
# The root resources.files can be a MultiplexedPath if it is a namespace
paths.extend(pkg_path._paths)
elif isinstance(pkg_path, pathlib.Path):
# Otherwise it should be a normal Path, just add it as-is
paths.append(pkg_path)
else:
# This should not happen
raise TypeError(f"Unexpected type improtlib.resources for package: {package}")

# Accumulate the base path of the package and entry-points
base_paths: list[pathlib.Path] = []

main_path = importlib.resources.files(package)
accumulate_path(base_paths, main_path)

# Additional resource files can be imported from entry-point
entry_point_name = 'tmt.resources'

entry_point_group = entry_points().select(group=entry_point_name)

for found in entry_point_group:
ep_module = found.load()
with suppress(Exception):
# TODO: Log errors if entry-point was defined that failed to retrieve base path
ep_path = importlib.resources.files(ep_module)
accumulate_path(base_paths, ep_path)

# Extract the Path if only one was found
if len(base_paths) == 1:
return base_paths[0] / path
# Otherwise construct a MultiplexedPath
assert (len(base_paths) > 1)
search_path = MultiplexedPath(*base_paths)
return search_path / path


class Stopwatch(contextlib.AbstractContextManager['Stopwatch']):
Expand Down

0 comments on commit 5c79195

Please sign in to comment.