-
Notifications
You must be signed in to change notification settings - Fork 0
Add Exclude option when building entrypoints #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4e56112
79222e4
7613128
4ce9f5d
354b912
f3e5cc6
94c694e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ | |
| import shutil | ||
| import sys | ||
| import typing as t | ||
| from fnmatch import fnmatchcase | ||
| from importlib.util import find_spec | ||
| from pathlib import Path | ||
|
|
||
| import setuptools | ||
|
|
@@ -39,20 +41,33 @@ def _ensure_dist_info(self, *args, **kwargs): | |
| class plugins(InfoCommon, setuptools.Command): | ||
| description = "Discover plux plugins and store them in .egg_info" | ||
|
|
||
| user_options = [ | ||
| # TODO | ||
| user_options: t.ClassVar[t.List[t.Tuple[str, str, str]]] = [ | ||
| ('exclude=', 'e', "a sequence of paths to exclude; '*' can be used as a wildcard in the names. 'foo.*' will exclude all subpackages of 'foo' (but not 'foo' itself)."), | ||
| # TODO: add more | ||
| ] | ||
|
|
||
| egg_info: str | ||
|
|
||
| def initialize_options(self) -> None: | ||
| self.plux_json_path = None | ||
| self.exclude = None | ||
|
|
||
| def finalize_options(self) -> None: | ||
| self.plux_json_path = get_plux_json_path(self.distribution) | ||
| self.ensure_string_list('exclude') | ||
| if self.exclude is None: | ||
| self.exclude = [] | ||
|
|
||
| # we merge the configuration from the CLI arguments with the configuration read from the `pyproject.toml` | ||
| # [tool.plux] section | ||
| project_config = read_plux_configuration(self.distribution) | ||
| file_exclude = project_config.get("exclude") | ||
| if file_exclude: | ||
| self.exclude = set(self.exclude) | set(file_exclude) | ||
| self.exclude = [_path_to_module(item) for item in self.exclude] | ||
|
|
||
| def run(self) -> None: | ||
| plugin_finder = PluginFromPackageFinder(DistributionPackageFinder(self.distribution)) | ||
| plugin_finder = PluginFromPackageFinder(DistributionPackageFinder(self.distribution, exclude=self.exclude)) | ||
| ep = discover_entry_points(plugin_finder) | ||
|
|
||
| self.debug_print(f"writing discovered plugins into {self.plux_json_path}") | ||
|
|
@@ -191,6 +206,34 @@ def get_plux_json_path(distribution): | |
| return os.path.join(egg_info_dir, "plux.json") | ||
|
|
||
|
|
||
| def read_plux_configuration(distribution) -> dict: | ||
| """ | ||
| Try reading the [tool.plux] section of the `pyproject.toml` TOML file of the Distribution, and returns it as a | ||
| dictionary. | ||
| """ | ||
| if find_spec("tomllib"): | ||
| # the tomllib library is part of the standard library since 3.11 | ||
| from tomllib import load as load_toml | ||
| elif find_spec("tomli"): | ||
| # setuptools vendors the tomli library in 3.10 | ||
| from tomli import load as load_toml | ||
| else: | ||
| # if we cannot find a TOML lib, we do not return any configuration | ||
| return {} | ||
|
|
||
| dirs = distribution.package_dir | ||
| pyproject_base = (dirs or {}).get("", os.curdir) | ||
| pyproject_file = os.path.join(pyproject_base, "pyproject.toml") | ||
| if not os.path.exists(pyproject_file): | ||
| return {} | ||
|
|
||
| with open(pyproject_file, "rb") as file: | ||
| pyproject_config = load_toml(file) | ||
|
|
||
| tool_table = pyproject_config.get("tool", {}) | ||
| return tool_table.get("plux", {}) | ||
|
|
||
|
|
||
| def update_entrypoints(distribution, ep: EntryPointDict): | ||
| if distribution.entry_points is None: | ||
| distribution.entry_points = {} | ||
|
|
@@ -223,8 +266,8 @@ def load_entry_points( | |
| instead, acting as sort of a cache. | ||
|
|
||
| :param where: the file path to look for plugins (default, the current working dir) | ||
| :param exclude: the glob patterns to exclude | ||
| :param include: the glob patterns to include | ||
| :param exclude: the shell style wildcard patterns to exclude | ||
| :param include: the shell style wildcard patterns to include | ||
|
Comment on lines
+269
to
+270
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as I copied the code for So I've used the same wording |
||
| :param merge: a map of entry points that are always added | ||
| """ | ||
| should_read, meta_dir = _should_read_existing_egg_info() | ||
|
|
@@ -375,6 +418,27 @@ def _to_filename(name): | |
| return name.replace("-", "_") | ||
|
|
||
|
|
||
| def _path_to_module(path): | ||
| """ | ||
| Convert a path to a Python module to its module representation | ||
| Example: plux/core/test -> plux.core.test | ||
| """ | ||
| return ".".join(Path(path).with_suffix("").parts) | ||
|
|
||
|
|
||
| class _Filter: | ||
| """ | ||
| Given a list of patterns, create a callable that will be true only if | ||
| the input matches at least one of the patterns. | ||
| This is from `setuptools.discovery._Filter` | ||
| """ | ||
| def __init__(self, patterns: t.Iterable[str]): | ||
| self._patterns = patterns | ||
|
|
||
| def __call__(self, item: str): | ||
| return any(fnmatchcase(item, pat) for pat in self._patterns) | ||
|
|
||
|
|
||
| class _PackageFinder: | ||
| """ | ||
| Generate a list of Python packages. How these are generated depends on the implementation. | ||
|
|
@@ -395,13 +459,17 @@ class DistributionPackageFinder(_PackageFinder): | |
| then the ``[tool.setuptools.package.find]`` config will be interpreted, resolved, and then | ||
| ``distribution.packages`` will contain the resolved packages. This already contains namespace packages | ||
| correctly if configured. | ||
| You can additionally pass a sequence of values to the `exclude` parameters to provide a list of Unix shell style | ||
| patterns that will be matched against the Python packages to exclude them from the resolved packages. | ||
| Wildcards are allowed in the patterns with '*'. 'foo.*' will exclude all subpackages of 'foo' (but not 'foo' itself) | ||
| """ | ||
|
|
||
| def __init__(self, distribution: Distribution): | ||
| def __init__(self, distribution: Distribution, exclude: t.Optional[t.Iterable[str]] = None): | ||
|
Comment on lines
-400
to
+467
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could we update the pydoc of _PackageFinder, to add a couple of sentences + example how the exclude works?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do! However, beware, I've only updated the I've found that we are using the |
||
| self.distribution = distribution | ||
| self.exclude = _Filter(exclude or []) | ||
|
|
||
| def find_packages(self) -> t.Iterable[str]: | ||
| return self.distribution.packages | ||
| return self.filter_packages(self.distribution.packages) | ||
|
|
||
| @property | ||
| def path(self) -> str: | ||
|
|
@@ -415,6 +483,9 @@ def path(self) -> str: | |
| where = "." | ||
| return where | ||
|
|
||
| def filter_packages(self, packages: t.Iterable[str]) -> t.Iterable[str]: | ||
| return [item for item in packages if not self.exclude(item)] | ||
|
|
||
|
|
||
| class DefaultPackageFinder(_PackageFinder): | ||
| def __init__(self, where=".", exclude=(), include=("*",), namespace=True) -> None: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| from plugin import Plugin | ||
|
|
||
|
|
||
| class MyNestedPlugin(Plugin): | ||
| namespace = "plux.test.plugins" | ||
| name = "mynestedplugin" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| from plugin import Plugin | ||
|
|
||
|
|
||
| class MyNestedPlugin(Plugin): | ||
| namespace = "plux.test.plugins" | ||
| name = "mynestedplugin" | ||
bentsku marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually not too sure if we should filter at the
Packagelevel or theModulelevel. Module level might be a bit more flexible, right now we have to ignore the full package and specifying a module name won't work.It is currently more in line with
setuptools.find_packagesand the other PluginFinder, but doesn't exactly serve the same purpose.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't thought very long about, but I think either can work. This looks good!