#!/usr/bin/env python
# ###########################################################################
#
# This file is part of Taurus
#
# http://taurus-scada.org
#
# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
#
# Taurus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Taurus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Taurus. If not, see <http://www.gnu.org/licenses/>.
#
# ###########################################################################
"""
Utilities for plugin loading and selection
"""
__all__ = ["selectEntryPoints", "EntryPointAlike"]
import re
import pkg_resources
from re import fullmatch
[docs]
class EntryPointAlike(object):
"""
A dummy EntryPoint substitute to be used with :func:`selectEntryPoints`
for wrapping arbitrary objects and imitate the `name` and `load()` API of
a :class:`pkg_resources.EntryPoint` instance.
Pass an object and optionally a name to the constructor to get the wrapped
`EntryPointAlike` instance. The `repr` of the object is used as the name
if `name` arg is not provided.
"""
def __init__(self, obj, name=None):
self._obj = obj
if name is None:
name = repr(obj)
self.name = name
[docs]
def load(self):
"""Returns the wrapped object"""
return self._obj
[docs]
def selectEntryPoints(group=None, include=(".*",), exclude=()):
"""
Selects and prioritizes entry points from an entry point group.
The entry points are selected using their name.
The selection is done by regex matching on the names: first the entry
points whose name matches any of the patterns in `excluded` are discarded;
then each pattern in `included` is used to select from the names of the
remaining entry points (from highest to lowest priority).
In this way, the entry points are selected in the order dictated by
the `included` pattern list. If a pattern matches several names, these
will be sorted alphabetically. If a name is matched by several patterns,
it is only selected by the first pattern.
For example, if there are the following registered entry point names:
['foo1', 'foo2', 'baz1', 'baz2', 'bar1', 'bar2']
And we use `exclude=("foo2",)` and `include=("bar2", "b.*1", "f.*")` ,
then the selection will be: ["bar2","bar1","baz1","foo1"]
Note: apart from regex patterns (strings or compiled) the `include` list
can also contain :class:`pkg_resources`EntryPoint`-like instances
(more specifically, an object having `.name` and `.load()` members), in
which case they are added directly to the selected list. If a member is
something other than a pattern or an EntryPoint-like object, it will be
wrapped in an :class:`EntryPointAlike` instance and also included in the
selection.
:param group: entry point group name from which the entry points are
obtained.
:type group: str
:param include: The members of the tuple can either be patterns (both in
the form of strings or of :class:`re.Pattern` objects), which will be
matched against registered names in group; or EntryPoint-like objects
which will be included as they are; or an arbitrary object which will
be wrapped as an EntryPoint-like object before being included. Default
is `(".*",)`, which matches all registered names in group and the sort
is purely alphabetical.
:type include: `tuple`
:param exclude: Regexp patterns (either `str` or :class:`re.Pattern`
objects) matching names to be excluded. Default is `()`, so no entry
point is excluded.
:type exclude: `tuple`
:return: the selected entry points.
:rtype: list of :class:`pkg_resources.EntryPoint`
"""
ret = []
remaining = list(pkg_resources.iter_entry_points(group))
# filter out the entry points whose name matches a exclude pattern
for p in exclude:
remaining = [e for e in remaining if not fullmatch(p, e.name)]
# sort the remaining entry points alphabetically
remaining.sort(key=lambda e: e.name)
# fill the ret list with the entry points whose name matches a pattern in
# the `include` tuple. The inclusion order follows the order of the
# patterns in `include` (and alphabetically for a pattern that produces
# multiple matches)
for p in include:
try:
# check if it is a pattern string or pattern object
p = re.compile(p)
except TypeError:
# if p is not an entry point -like object, create one
if not hasattr(p, "name") or not hasattr(p, "load"):
p = EntryPointAlike(p)
# and add it directly
ret.append(p)
continue
# if it is a pattern, match it against remaining entry points
tmp = remaining
remaining = []
for e in tmp:
if fullmatch(p, e.name):
ret.append(e)
else:
remaining.append(e)
return ret