• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

abravalheri / validate-pyproject / 5226968556240896

pending completion
5226968556240896

push

cirrus-ci

GitHub
Typo: validate-project => validate-pyproject (#78)

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.53
/src/validate_pyproject/plugins/__init__.py
1
# The code in this module is mostly borrowed/adapted from PyScaffold and was originally
2
# published under the MIT license
3
# The original PyScaffold license can be found in 'NOTICE.txt'
4
"""
8✔
5
.. _entry point: https://setuptools.readthedocs.io/en/latest/userguide/entry_point.html
6
"""
7

8
import sys
8✔
9
from string import Template
8✔
10
from textwrap import dedent
8✔
11
from typing import Any, Callable, Iterable, List, Optional, cast
8✔
12

13
from .. import __version__
8✔
14
from ..types import Plugin
8✔
15

16
if sys.version_info[:2] >= (3, 8):  # pragma: no cover
17
    # TODO: Import directly (no need for conditional) when `python_requires = >= 3.8`
18
    from importlib.metadata import EntryPoint, entry_points
19
else:  # pragma: no cover
20
    from importlib_metadata import EntryPoint, entry_points
21

22

23
ENTRYPOINT_GROUP = "validate_pyproject.tool_schema"
8✔
24

25

26
class PluginWrapper:
8✔
27
    def __init__(self, tool: str, load_fn: Plugin):
8✔
28
        self._tool = tool
8✔
29
        self._load_fn = load_fn
8✔
30

31
    @property
8✔
32
    def id(self):
6 all except 5226968556240896.2 and 5226968556240896.4 ✔
33
        return f"{self._load_fn.__module__}.{self._load_fn.__name__}"
8✔
34

35
    @property
8✔
36
    def tool(self):
6 all except 5226968556240896.2 and 5226968556240896.4 ✔
37
        return self._tool
8✔
38

39
    @property
8✔
40
    def schema(self):
6 all except 5226968556240896.2 and 5226968556240896.4 ✔
41
        return self._load_fn(self.tool)
8✔
42

43
    @property
8✔
44
    def help_text(self) -> str:
8✔
45
        tpl = self._load_fn.__doc__
8✔
46
        if not tpl:
8✔
47
            return ""
8✔
48
        return Template(tpl).safe_substitute(tool=self.tool, id=self.id)
8✔
49

50

51
def iterate_entry_points(group=ENTRYPOINT_GROUP) -> Iterable[EntryPoint]:
8✔
52
    """Produces a generator yielding an EntryPoint object for each plugin registered
53
    via ``setuptools`` `entry point`_ mechanism.
54

55
    This method can be used in conjunction with :obj:`load_from_entry_point` to filter
56
    the plugins before actually loading them.
57
    """  # noqa
58
    entries = entry_points()
8✔
59
    if hasattr(entries, "select"):  # pragma: no cover
60
        # The select method was introduced in importlib_metadata 3.9 (and Python 3.10)
61
        # and the previous dict interface was declared deprecated
62
        select = cast(Any, getattr(entries, "select"))  # typecheck gymnastics # noqa
63
        entries_: Iterable[EntryPoint] = select(group=group)
64
    else:  # pragma: no cover
65
        # TODO: Once Python 3.10 becomes the oldest version supported, this fallback and
66
        #       conditional statement can be removed.
67
        entries_ = (plugin for plugin in entries.get(group, []))
68
    deduplicated = {e.name: e for e in sorted(entries_, key=lambda e: e.name)}
8✔
69
    return list(deduplicated.values())
8✔
70

71

72
def load_from_entry_point(entry_point: EntryPoint) -> PluginWrapper:
8✔
73
    """Carefully load the plugin, raising a meaningful message in case of errors"""
74
    try:
8✔
75
        fn = entry_point.load()
8✔
76
        return PluginWrapper(entry_point.name, fn)
8✔
77
    except Exception as ex:
8✔
78
        raise ErrorLoadingPlugin(entry_point=entry_point) from ex
8✔
79

80

81
def list_from_entry_points(
8✔
82
    group: str = ENTRYPOINT_GROUP,
83
    filtering: Callable[[EntryPoint], bool] = lambda _: True,
84
) -> List[PluginWrapper]:
85
    """Produces a list of plugin objects for each plugin registered
86
    via ``setuptools`` `entry point`_ mechanism.
87

88
    Args:
89
        group: name of the setuptools' entry point group where plugins is being
90
            registered
91
        filtering: function returning a boolean deciding if the entry point should be
92
            loaded and included (or not) in the final list. A ``True`` return means the
93
            plugin should be included.
94
    """  # noqa
95
    return [
8✔
96
        load_from_entry_point(e) for e in iterate_entry_points(group) if filtering(e)
97
    ]
98

99

100
class ErrorLoadingPlugin(RuntimeError):
8✔
101
    """There was an error loading '{plugin}'.
102
    Please make sure you have installed a version of the plugin that is compatible
103
    with {package} {version}. You can also try uninstalling it.
104
    """
105

106
    def __init__(self, plugin: str = "", entry_point: Optional[EntryPoint] = None):
8✔
107
        if entry_point and not plugin:
8!
108
            plugin = getattr(entry_point, "module", entry_point.name)
8✔
109

110
        sub = dict(package=__package__, version=__version__, plugin=plugin)
8✔
111
        msg = dedent(self.__doc__ or "").format(**sub).splitlines()
8✔
112
        super().__init__(f"{msg[0]}\n{' '.join(msg[1:])}")
8✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc