Move common libs to libs/common

This commit is contained in:
Labrys of Knossos 2018-12-16 13:30:24 -05:00
commit 1f4bd41bcc
1612 changed files with 962 additions and 10 deletions

View file

@ -0,0 +1,24 @@
# flake8: noqa
__all__ = [
'ExtensionManager',
'EnabledExtensionManager',
'NamedExtensionManager',
'HookManager',
'DriverManager',
]
from .extension import ExtensionManager
from .enabled import EnabledExtensionManager
from .named import NamedExtensionManager
from .hook import HookManager
from .driver import DriverManager
import logging
# Configure a NullHandler for our log messages in case
# the app we're used from does not set up logging.
LOG = logging.getLogger('stevedore')
LOG.addHandler(logging.NullHandler())

View file

@ -0,0 +1,229 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from .enabled import EnabledExtensionManager
from .exception import NoMatches
LOG = logging.getLogger(__name__)
class DispatchExtensionManager(EnabledExtensionManager):
"""Loads all plugins and filters on execution.
This is useful for long-running processes that need to pass
different inputs to different extensions.
:param namespace: The namespace for the entry points.
:type namespace: str
:param check_func: Function to determine which extensions to load.
:type check_func: callable
:param invoke_on_load: Boolean controlling whether to invoke the
object returned by the entry point after the driver is loaded.
:type invoke_on_load: bool
:param invoke_args: Positional arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_args: tuple
:param invoke_kwds: Named arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_kwds: dict
:param propagate_map_exceptions: Boolean controlling whether exceptions
are propagated up through the map call or whether they are logged and
then ignored
:type invoke_on_load: bool
"""
def map(self, filter_func, func, *args, **kwds):
"""Iterate over the extensions invoking func() for any where
filter_func() returns True.
The signature of filter_func() should be::
def filter_func(ext, *args, **kwds):
pass
The first argument to filter_func(), 'ext', is the
:class:`~stevedore.extension.Extension`
instance. filter_func() should return True if the extension
should be invoked for the input arguments.
The signature for func() should be::
def func(ext, *args, **kwds):
pass
The first argument to func(), 'ext', is the
:class:`~stevedore.extension.Extension` instance.
Exceptions raised from within func() are propagated up and
processing stopped if self.propagate_map_exceptions is True,
otherwise they are logged and ignored.
:param filter_func: Callable to test each extension.
:param func: Callable to invoke for each extension.
:param args: Variable arguments to pass to func()
:param kwds: Keyword arguments to pass to func()
:returns: List of values returned from func()
"""
if not self.extensions:
# FIXME: Use a more specific exception class here.
raise NoMatches('No %s extensions found' % self.namespace)
response = []
for e in self.extensions:
if filter_func(e, *args, **kwds):
self._invoke_one_plugin(response.append, func, e, args, kwds)
return response
def map_method(self, filter_func, method_name, *args, **kwds):
"""Iterate over the extensions invoking each one's object method called
`method_name` for any where filter_func() returns True.
This is equivalent of using :meth:`map` with func set to
`lambda x: x.obj.method_name()`
while being more convenient.
Exceptions raised from within the called method are propagated up
and processing stopped if self.propagate_map_exceptions is True,
otherwise they are logged and ignored.
.. versionadded:: 0.12
:param filter_func: Callable to test each extension.
:param method_name: The extension method name to call
for each extension.
:param args: Variable arguments to pass to method
:param kwds: Keyword arguments to pass to method
:returns: List of values returned from methods
"""
return self.map(filter_func, self._call_extension_method,
method_name, *args, **kwds)
class NameDispatchExtensionManager(DispatchExtensionManager):
"""Loads all plugins and filters on execution.
This is useful for long-running processes that need to pass
different inputs to different extensions and can predict the name
of the extensions before calling them.
The check_func argument should return a boolean, with ``True``
indicating that the extension should be loaded and made available
and ``False`` indicating that the extension should be ignored.
:param namespace: The namespace for the entry points.
:type namespace: str
:param check_func: Function to determine which extensions to load.
:type check_func: callable
:param invoke_on_load: Boolean controlling whether to invoke the
object returned by the entry point after the driver is loaded.
:type invoke_on_load: bool
:param invoke_args: Positional arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_args: tuple
:param invoke_kwds: Named arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_kwds: dict
:param propagate_map_exceptions: Boolean controlling whether exceptions
are propagated up through the map call or whether they are logged and
then ignored
:type invoke_on_load: bool
:param on_load_failure_callback: Callback function that will be called when
a entrypoint can not be loaded. The arguments that will be provided
when this is called (when an entrypoint fails to load) are
(manager, entrypoint, exception)
:type on_load_failure_callback: function
:param verify_requirements: Use setuptools to enforce the
dependencies of the plugin(s) being loaded. Defaults to False.
:type verify_requirements: bool
"""
def __init__(self, namespace, check_func, invoke_on_load=False,
invoke_args=(), invoke_kwds={},
propagate_map_exceptions=False,
on_load_failure_callback=None,
verify_requirements=False):
super(NameDispatchExtensionManager, self).__init__(
namespace=namespace,
check_func=check_func,
invoke_on_load=invoke_on_load,
invoke_args=invoke_args,
invoke_kwds=invoke_kwds,
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback,
verify_requirements=verify_requirements,
)
def _init_plugins(self, extensions):
super(NameDispatchExtensionManager, self)._init_plugins(extensions)
self.by_name = dict((e.name, e) for e in self.extensions)
def map(self, names, func, *args, **kwds):
"""Iterate over the extensions invoking func() for any where
the name is in the given list of names.
The signature for func() should be::
def func(ext, *args, **kwds):
pass
The first argument to func(), 'ext', is the
:class:`~stevedore.extension.Extension` instance.
Exceptions raised from within func() are propagated up and
processing stopped if self.propagate_map_exceptions is True,
otherwise they are logged and ignored.
:param names: List or set of name(s) of extension(s) to invoke.
:param func: Callable to invoke for each extension.
:param args: Variable arguments to pass to func()
:param kwds: Keyword arguments to pass to func()
:returns: List of values returned from func()
"""
response = []
for name in names:
try:
e = self.by_name[name]
except KeyError:
LOG.debug('Missing extension %r being ignored', name)
else:
self._invoke_one_plugin(response.append, func, e, args, kwds)
return response
def map_method(self, names, method_name, *args, **kwds):
"""Iterate over the extensions invoking each one's object method called
`method_name` for any where the name is in the given list of names.
This is equivalent of using :meth:`map` with func set to
`lambda x: x.obj.method_name()`
while being more convenient.
Exceptions raised from within the called method are propagated up
and processing stopped if self.propagate_map_exceptions is True,
otherwise they are logged and ignored.
.. versionadded:: 0.12
:param names: List or set of name(s) of extension(s) to invoke.
:param method_name: The extension method name
to call for each extension.
:param args: Variable arguments to pass to method
:param kwds: Keyword arguments to pass to method
:returns: List of values returned from methods
"""
return self.map(names, self._call_extension_method,
method_name, *args, **kwds)

View file

@ -0,0 +1,148 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from .exception import NoMatches, MultipleMatches
from .named import NamedExtensionManager
class DriverManager(NamedExtensionManager):
"""Load a single plugin with a given name from the namespace.
:param namespace: The namespace for the entry points.
:type namespace: str
:param name: The name of the driver to load.
:type name: str
:param invoke_on_load: Boolean controlling whether to invoke the
object returned by the entry point after the driver is loaded.
:type invoke_on_load: bool
:param invoke_args: Positional arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_args: tuple
:param invoke_kwds: Named arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_kwds: dict
:param on_load_failure_callback: Callback function that will be called when
a entrypoint can not be loaded. The arguments that will be provided
when this is called (when an entrypoint fails to load) are
(manager, entrypoint, exception)
:type on_load_failure_callback: function
:param verify_requirements: Use setuptools to enforce the
dependencies of the plugin(s) being loaded. Defaults to False.
:type verify_requirements: bool
:type warn_on_missing_entrypoint: bool
"""
def __init__(self, namespace, name,
invoke_on_load=False, invoke_args=(), invoke_kwds={},
on_load_failure_callback=None,
verify_requirements=False,
warn_on_missing_entrypoint=True):
on_load_failure_callback = on_load_failure_callback \
or self._default_on_load_failure
super(DriverManager, self).__init__(
namespace=namespace,
names=[name],
invoke_on_load=invoke_on_load,
invoke_args=invoke_args,
invoke_kwds=invoke_kwds,
on_load_failure_callback=on_load_failure_callback,
verify_requirements=verify_requirements,
warn_on_missing_entrypoint=warn_on_missing_entrypoint
)
@staticmethod
def _default_on_load_failure(drivermanager, ep, err):
raise
@classmethod
def make_test_instance(cls, extension, namespace='TESTING',
propagate_map_exceptions=False,
on_load_failure_callback=None,
verify_requirements=False):
"""Construct a test DriverManager
Test instances are passed a list of extensions to work from rather
than loading them from entry points.
:param extension: Pre-configured Extension instance
:type extension: :class:`~stevedore.extension.Extension`
:param namespace: The namespace for the manager; used only for
identification since the extensions are passed in.
:type namespace: str
:param propagate_map_exceptions: Boolean controlling whether exceptions
are propagated up through the map call or whether they are logged
and then ignored
:type propagate_map_exceptions: bool
:param on_load_failure_callback: Callback function that will
be called when a entrypoint can not be loaded. The
arguments that will be provided when this is called (when
an entrypoint fails to load) are (manager, entrypoint,
exception)
:type on_load_failure_callback: function
:param verify_requirements: Use setuptools to enforce the
dependencies of the plugin(s) being loaded. Defaults to False.
:type verify_requirements: bool
:return: The manager instance, initialized for testing
"""
o = super(DriverManager, cls).make_test_instance(
[extension], namespace=namespace,
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback,
verify_requirements=verify_requirements)
return o
def _init_plugins(self, extensions):
super(DriverManager, self)._init_plugins(extensions)
if not self.extensions:
name = self._names[0]
raise NoMatches('No %r driver found, looking for %r' %
(self.namespace, name))
if len(self.extensions) > 1:
discovered_drivers = ','.join(e.entry_point_target
for e in self.extensions)
raise MultipleMatches('Multiple %r drivers found: %s' %
(self.namespace, discovered_drivers))
def __call__(self, func, *args, **kwds):
"""Invokes func() for the single loaded extension.
The signature for func() should be::
def func(ext, *args, **kwds):
pass
The first argument to func(), 'ext', is the
:class:`~stevedore.extension.Extension` instance.
Exceptions raised from within func() are logged and ignored.
:param func: Callable to invoke for each extension.
:param args: Variable arguments to pass to func()
:param kwds: Keyword arguments to pass to func()
:returns: List of values returned from func()
"""
results = self.map(func, *args, **kwds)
if results:
return results[0]
@property
def driver(self):
"""Returns the driver being used by this manager.
"""
ext = self.extensions[0]
return ext.obj if ext.obj else ext.plugin

View file

@ -0,0 +1,84 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from .extension import ExtensionManager
LOG = logging.getLogger(__name__)
class EnabledExtensionManager(ExtensionManager):
"""Loads only plugins that pass a check function.
The check_func argument should return a boolean, with ``True``
indicating that the extension should be loaded and made available
and ``False`` indicating that the extension should be ignored.
:param namespace: The namespace for the entry points.
:type namespace: str
:param check_func: Function to determine which extensions to load.
:type check_func: callable, taking an :class:`Extension`
instance as argument
:param invoke_on_load: Boolean controlling whether to invoke the
object returned by the entry point after the driver is loaded.
:type invoke_on_load: bool
:param invoke_args: Positional arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_args: tuple
:param invoke_kwds: Named arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_kwds: dict
:param propagate_map_exceptions: Boolean controlling whether exceptions
are propagated up through the map call or whether they are logged and
then ignored
:type propagate_map_exceptions: bool
:param on_load_failure_callback: Callback function that will be called when
a entrypoint can not be loaded. The arguments that will be provided
when this is called (when an entrypoint fails to load) are
(manager, entrypoint, exception)
:type on_load_failure_callback: function
:param verify_requirements: Use setuptools to enforce the
dependencies of the plugin(s) being loaded. Defaults to False.
:type verify_requirements: bool
"""
def __init__(self, namespace, check_func, invoke_on_load=False,
invoke_args=(), invoke_kwds={},
propagate_map_exceptions=False,
on_load_failure_callback=None,
verify_requirements=False,):
self.check_func = check_func
super(EnabledExtensionManager, self).__init__(
namespace,
invoke_on_load=invoke_on_load,
invoke_args=invoke_args,
invoke_kwds=invoke_kwds,
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback,
verify_requirements=verify_requirements,
)
def _load_one_plugin(self, ep, invoke_on_load, invoke_args, invoke_kwds,
verify_requirements):
ext = super(EnabledExtensionManager, self)._load_one_plugin(
ep, invoke_on_load, invoke_args, invoke_kwds,
verify_requirements,
)
if ext and not self.check_func(ext):
LOG.debug('ignoring extension %r', ep.name)
return None
return ext

View file

@ -0,0 +1,22 @@
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class FormatterBase(object):
"""Base class for example plugin used in the tutorial.
"""
def __init__(self, max_width=60):
self.max_width = max_width
@abc.abstractmethod
def format(self, data):
"""Format the data and return unicode text.
:param data: A dictionary with string keys and simple types as
values.
:type data: dict(str:?)
:returns: Iterable producing the formatted text.
"""

View file

@ -0,0 +1,37 @@
from __future__ import print_function
import argparse
from stevedore import driver
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument(
'format',
nargs='?',
default='simple',
help='the output format',
)
parser.add_argument(
'--width',
default=60,
type=int,
help='maximum output width for text',
)
parsed_args = parser.parse_args()
data = {
'a': 'A',
'b': 'B',
'long': 'word ' * 80,
}
mgr = driver.DriverManager(
namespace='stevedore.example.formatter',
name=parsed_args.format,
invoke_on_load=True,
invoke_args=(parsed_args.width,),
)
for chunk in mgr.driver.format(data):
print(chunk, end='')

View file

@ -0,0 +1,39 @@
from __future__ import print_function
import argparse
from stevedore import extension
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument(
'--width',
default=60,
type=int,
help='maximum output width for text',
)
parsed_args = parser.parse_args()
data = {
'a': 'A',
'b': 'B',
'long': 'word ' * 80,
}
mgr = extension.ExtensionManager(
namespace='stevedore.example.formatter',
invoke_on_load=True,
invoke_args=(parsed_args.width,),
)
def format_data(ext, data):
return (ext.name, ext.obj.format(data))
results = mgr.map(format_data, data)
for name, result in results:
print('Formatter: {0}'.format(name))
for chunk in result:
print(chunk, end='')
print('')

View file

@ -0,0 +1,43 @@
from setuptools import setup, find_packages
setup(
name='stevedore-examples',
version='1.0',
description='Demonstration package for stevedore',
author='Doug Hellmann',
author_email='doug@doughellmann.com',
url='http://git.openstack.org/cgit/openstack/stevedore',
classifiers=['Development Status :: 3 - Alpha',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Intended Audience :: Developers',
'Environment :: Console',
],
platforms=['Any'],
scripts=[],
provides=['stevedore.examples',
],
packages=find_packages(),
include_package_data=True,
entry_points={
'stevedore.example.formatter': [
'simple = stevedore.example.simple:Simple',
'plain = stevedore.example.simple:Simple',
],
},
zip_safe=False,
)

View file

@ -0,0 +1,20 @@
from stevedore.example import base
class Simple(base.FormatterBase):
"""A very basic formatter.
"""
def format(self, data):
"""Format the data and return unicode text.
:param data: A dictionary with string keys and simple types as
values.
:type data: dict(str:?)
"""
for name, value in sorted(data.items()):
line = '{name} = {value}\n'.format(
name=name,
value=value,
)
yield line

View file

@ -0,0 +1,36 @@
import textwrap
from stevedore.example import base
class FieldList(base.FormatterBase):
"""Format values as a reStructuredText field list.
For example::
: name1 : value
: name2 : value
: name3 : a long value
will be wrapped with
a hanging indent
"""
def format(self, data):
"""Format the data and return unicode text.
:param data: A dictionary with string keys and simple types as
values.
:type data: dict(str:?)
"""
for name, value in sorted(data.items()):
full_text = ': {name} : {value}'.format(
name=name,
value=value,
)
wrapped_text = textwrap.fill(
full_text,
initial_indent='',
subsequent_indent=' ',
width=self.max_width,
)
yield wrapped_text + '\n'

View file

@ -0,0 +1,42 @@
from setuptools import setup, find_packages
setup(
name='stevedore-examples2',
version='1.0',
description='Demonstration package for stevedore',
author='Doug Hellmann',
author_email='doug@doughellmann.com',
url='http://git.openstack.org/cgit/openstack/stevedore',
classifiers=['Development Status :: 3 - Alpha',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Intended Audience :: Developers',
'Environment :: Console',
],
platforms=['Any'],
scripts=[],
provides=['stevedore.examples2',
],
packages=find_packages(),
include_package_data=True,
entry_points={
'stevedore.example.formatter': [
'field = stevedore.example2.fields:FieldList',
],
},
zip_safe=False,
)

View file

@ -0,0 +1,23 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
class NoUniqueMatch(RuntimeError):
"""There was more than one extension, or none, that matched the query."""
class NoMatches(NoUniqueMatch):
"""There were no extensions with the driver name found."""
class MultipleMatches(NoUniqueMatch):
"""There were multiple matches for the given name."""

View file

@ -0,0 +1,331 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""ExtensionManager
"""
import operator
import pkg_resources
import logging
from .exception import NoMatches
LOG = logging.getLogger(__name__)
class Extension(object):
"""Book-keeping object for tracking extensions.
The arguments passed to the constructor are saved as attributes of
the instance using the same names, and can be accessed by the
callables passed to :meth:`map` or when iterating over an
:class:`ExtensionManager` directly.
:param name: The entry point name.
:type name: str
:param entry_point: The EntryPoint instance returned by
:mod:`pkg_resources`.
:type entry_point: EntryPoint
:param plugin: The value returned by entry_point.load()
:param obj: The object returned by ``plugin(*args, **kwds)`` if the
manager invoked the extension on load.
"""
def __init__(self, name, entry_point, plugin, obj):
self.name = name
self.entry_point = entry_point
self.plugin = plugin
self.obj = obj
@property
def entry_point_target(self):
"""The module and attribute referenced by this extension's entry_point.
:return: A string representation of the target of the entry point in
'dotted.module:object' format.
"""
return '%s:%s' % (self.entry_point.module_name,
self.entry_point.attrs[0])
class ExtensionManager(object):
"""Base class for all of the other managers.
:param namespace: The namespace for the entry points.
:type namespace: str
:param invoke_on_load: Boolean controlling whether to invoke the
object returned by the entry point after the driver is loaded.
:type invoke_on_load: bool
:param invoke_args: Positional arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_args: tuple
:param invoke_kwds: Named arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_kwds: dict
:param propagate_map_exceptions: Boolean controlling whether exceptions
are propagated up through the map call or whether they are logged and
then ignored
:type propagate_map_exceptions: bool
:param on_load_failure_callback: Callback function that will be called when
a entrypoint can not be loaded. The arguments that will be provided
when this is called (when an entrypoint fails to load) are
(manager, entrypoint, exception)
:type on_load_failure_callback: function
:param verify_requirements: Use setuptools to enforce the
dependencies of the plugin(s) being loaded. Defaults to False.
:type verify_requirements: bool
"""
def __init__(self, namespace,
invoke_on_load=False,
invoke_args=(),
invoke_kwds={},
propagate_map_exceptions=False,
on_load_failure_callback=None,
verify_requirements=False):
self._init_attributes(
namespace,
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback)
extensions = self._load_plugins(invoke_on_load,
invoke_args,
invoke_kwds,
verify_requirements)
self._init_plugins(extensions)
@classmethod
def make_test_instance(cls, extensions, namespace='TESTING',
propagate_map_exceptions=False,
on_load_failure_callback=None,
verify_requirements=False):
"""Construct a test ExtensionManager
Test instances are passed a list of extensions to work from rather
than loading them from entry points.
:param extensions: Pre-configured Extension instances to use
:type extensions: list of :class:`~stevedore.extension.Extension`
:param namespace: The namespace for the manager; used only for
identification since the extensions are passed in.
:type namespace: str
:param propagate_map_exceptions: When calling map, controls whether
exceptions are propagated up through the map call or whether they
are logged and then ignored
:type propagate_map_exceptions: bool
:param on_load_failure_callback: Callback function that will
be called when a entrypoint can not be loaded. The
arguments that will be provided when this is called (when
an entrypoint fails to load) are (manager, entrypoint,
exception)
:type on_load_failure_callback: function
:param verify_requirements: Use setuptools to enforce the
dependencies of the plugin(s) being loaded. Defaults to False.
:type verify_requirements: bool
:return: The manager instance, initialized for testing
"""
o = cls.__new__(cls)
o._init_attributes(namespace,
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback)
o._init_plugins(extensions)
return o
def _init_attributes(self, namespace, propagate_map_exceptions=False,
on_load_failure_callback=None):
self.namespace = namespace
self.propagate_map_exceptions = propagate_map_exceptions
self._on_load_failure_callback = on_load_failure_callback
def _init_plugins(self, extensions):
self.extensions = extensions
self._extensions_by_name_cache = None
@property
def _extensions_by_name(self):
if self._extensions_by_name_cache is None:
d = {}
for e in self.extensions:
d[e.name] = e
self._extensions_by_name_cache = d
return self._extensions_by_name_cache
ENTRY_POINT_CACHE = {}
def list_entry_points(self):
"""Return the list of entry points for this namespace.
The entry points are not actually loaded, their list is just read and
returned.
"""
if self.namespace not in self.ENTRY_POINT_CACHE:
eps = list(pkg_resources.iter_entry_points(self.namespace))
self.ENTRY_POINT_CACHE[self.namespace] = eps
return self.ENTRY_POINT_CACHE[self.namespace]
def entry_points_names(self):
"""Return the list of entry points names for this namespace."""
return list(map(operator.attrgetter("name"), self.list_entry_points()))
def _load_plugins(self, invoke_on_load, invoke_args, invoke_kwds,
verify_requirements):
extensions = []
for ep in self.list_entry_points():
LOG.debug('found extension %r', ep)
try:
ext = self._load_one_plugin(ep,
invoke_on_load,
invoke_args,
invoke_kwds,
verify_requirements,
)
if ext:
extensions.append(ext)
except (KeyboardInterrupt, AssertionError):
raise
except Exception as err:
if self._on_load_failure_callback is not None:
self._on_load_failure_callback(self, ep, err)
else:
# Log the reason we couldn't import the module,
# usually without a traceback. The most common
# reason is an ImportError due to a missing
# dependency, and the error message should be
# enough to debug that. If debug logging is
# enabled for our logger, provide the full
# traceback.
LOG.error('Could not load %r: %s', ep.name, err,
exc_info=LOG.isEnabledFor(logging.DEBUG))
return extensions
def _load_one_plugin(self, ep, invoke_on_load, invoke_args, invoke_kwds,
verify_requirements):
# NOTE(dhellmann): Using require=False is deprecated in
# setuptools 11.3.
if hasattr(ep, 'resolve') and hasattr(ep, 'require'):
if verify_requirements:
ep.require()
plugin = ep.resolve()
else:
plugin = ep.load(require=verify_requirements)
if invoke_on_load:
obj = plugin(*invoke_args, **invoke_kwds)
else:
obj = None
return Extension(ep.name, ep, plugin, obj)
def names(self):
"Returns the names of the discovered extensions"
# We want to return the names of the extensions in the order
# they would be used by map(), since some subclasses change
# that order.
return [e.name for e in self.extensions]
def map(self, func, *args, **kwds):
"""Iterate over the extensions invoking func() for each.
The signature for func() should be::
def func(ext, *args, **kwds):
pass
The first argument to func(), 'ext', is the
:class:`~stevedore.extension.Extension` instance.
Exceptions raised from within func() are propagated up and
processing stopped if self.propagate_map_exceptions is True,
otherwise they are logged and ignored.
:param func: Callable to invoke for each extension.
:param args: Variable arguments to pass to func()
:param kwds: Keyword arguments to pass to func()
:returns: List of values returned from func()
"""
if not self.extensions:
# FIXME: Use a more specific exception class here.
raise NoMatches('No %s extensions found' % self.namespace)
response = []
for e in self.extensions:
self._invoke_one_plugin(response.append, func, e, args, kwds)
return response
@staticmethod
def _call_extension_method(extension, method_name, *args, **kwds):
return getattr(extension.obj, method_name)(*args, **kwds)
def map_method(self, method_name, *args, **kwds):
"""Iterate over the extensions invoking a method by name.
This is equivalent of using :meth:`map` with func set to
`lambda x: x.obj.method_name()`
while being more convenient.
Exceptions raised from within the called method are propagated up
and processing stopped if self.propagate_map_exceptions is True,
otherwise they are logged and ignored.
.. versionadded:: 0.12
:param method_name: The extension method name
to call for each extension.
:param args: Variable arguments to pass to method
:param kwds: Keyword arguments to pass to method
:returns: List of values returned from methods
"""
return self.map(self._call_extension_method,
method_name, *args, **kwds)
def _invoke_one_plugin(self, response_callback, func, e, args, kwds):
try:
response_callback(func(e, *args, **kwds))
except Exception as err:
if self.propagate_map_exceptions:
raise
else:
LOG.error('error calling %r: %s', e.name, err)
LOG.exception(err)
def items(self):
"""
Return an iterator of tuples of the form (name, extension).
This is analogous to the Mapping.items() method.
"""
return self._extensions_by_name.items()
def __iter__(self):
"""Produce iterator for the manager.
Iterating over an ExtensionManager produces the :class:`Extension`
instances in the order they would be invoked.
"""
return iter(self.extensions)
def __getitem__(self, name):
"""Return the named extension.
Accessing an ExtensionManager as a dictionary (``em['name']``)
produces the :class:`Extension` instance with the
specified name.
"""
return self._extensions_by_name[name]
def __contains__(self, name):
"""Return true if name is in list of enabled extensions.
"""
return any(extension.name == name for extension in self.extensions)

View file

@ -0,0 +1,91 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from .named import NamedExtensionManager
class HookManager(NamedExtensionManager):
"""Coordinate execution of multiple extensions using a common name.
:param namespace: The namespace for the entry points.
:type namespace: str
:param name: The name of the hooks to load.
:type name: str
:param invoke_on_load: Boolean controlling whether to invoke the
object returned by the entry point after the driver is loaded.
:type invoke_on_load: bool
:param invoke_args: Positional arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_args: tuple
:param invoke_kwds: Named arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_kwds: dict
:param on_load_failure_callback: Callback function that will be called when
a entrypoint can not be loaded. The arguments that will be provided
when this is called (when an entrypoint fails to load) are
(manager, entrypoint, exception)
:type on_load_failure_callback: function
:param verify_requirements: Use setuptools to enforce the
dependencies of the plugin(s) being loaded. Defaults to False.
:type verify_requirements: bool
:type on_missing_entrypoints_callback: function
:param verify_requirements: Use setuptools to enforce the
dependencies of the plugin(s) being loaded. Defaults to False.
:param warn_on_missing_entrypoint: Flag to control whether failing
to load a plugin is reported via a log mess. Only applies if
on_missing_entrypoints_callback is None.
:type warn_on_missing_entrypoint: bool
"""
def __init__(self, namespace, name,
invoke_on_load=False, invoke_args=(), invoke_kwds={},
on_load_failure_callback=None,
verify_requirements=False,
on_missing_entrypoints_callback=None,
# NOTE(dhellmann): This default is different from the
# base class because for hooks it is less likely to
# be an error to have no entry points present.
warn_on_missing_entrypoint=False):
super(HookManager, self).__init__(
namespace,
[name],
invoke_on_load=invoke_on_load,
invoke_args=invoke_args,
invoke_kwds=invoke_kwds,
on_load_failure_callback=on_load_failure_callback,
on_missing_entrypoints_callback=on_missing_entrypoints_callback,
verify_requirements=verify_requirements,
warn_on_missing_entrypoint=warn_on_missing_entrypoint,
)
def _init_attributes(self, namespace, names, name_order=False,
propagate_map_exceptions=False,
on_load_failure_callback=None):
super(HookManager, self)._init_attributes(
namespace, names,
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback)
self._name = names[0]
def __getitem__(self, name):
"""Return the named extensions.
Accessing a HookManager as a dictionary (``em['name']``)
produces a list of the :class:`Extension` instance(s) with the
specified name, in the order they would be invoked by map().
"""
if name != self._name:
raise KeyError(name)
return self.extensions

View file

@ -0,0 +1,159 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from .extension import ExtensionManager
LOG = logging.getLogger(__name__)
class NamedExtensionManager(ExtensionManager):
"""Loads only the named extensions.
This is useful for explicitly enabling extensions in a
configuration file, for example.
:param namespace: The namespace for the entry points.
:type namespace: str
:param names: The names of the extensions to load.
:type names: list(str)
:param invoke_on_load: Boolean controlling whether to invoke the
object returned by the entry point after the driver is loaded.
:type invoke_on_load: bool
:param invoke_args: Positional arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_args: tuple
:param invoke_kwds: Named arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_kwds: dict
:param name_order: If true, sort the loaded extensions to match the
order used in ``names``.
:type name_order: bool
:param propagate_map_exceptions: Boolean controlling whether exceptions
are propagated up through the map call or whether they are logged and
then ignored
:type propagate_map_exceptions: bool
:param on_load_failure_callback: Callback function that will be called when
a entrypoint can not be loaded. The arguments that will be provided
when this is called (when an entrypoint fails to load) are
(manager, entrypoint, exception)
:type on_load_failure_callback: function
:param on_missing_entrypoints_callback: Callback function that will be
called when one or more names cannot be found. The provided argument
will be a subset of the 'names' parameter.
:type on_missing_entrypoints_callback: function
:param verify_requirements: Use setuptools to enforce the
dependencies of the plugin(s) being loaded. Defaults to False.
:type verify_requirements: bool
:param warn_on_missing_entrypoint: Flag to control whether failing
to load a plugin is reported via a log mess. Only applies if
on_missing_entrypoints_callback is None.
:type warn_on_missing_entrypoint: bool
"""
def __init__(self, namespace, names,
invoke_on_load=False, invoke_args=(), invoke_kwds={},
name_order=False, propagate_map_exceptions=False,
on_load_failure_callback=None,
on_missing_entrypoints_callback=None,
verify_requirements=False,
warn_on_missing_entrypoint=True):
self._init_attributes(
namespace, names, name_order=name_order,
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback)
extensions = self._load_plugins(invoke_on_load,
invoke_args,
invoke_kwds,
verify_requirements)
self._missing_names = set(names) - set([e.name for e in extensions])
if self._missing_names:
if on_missing_entrypoints_callback:
on_missing_entrypoints_callback(self._missing_names)
elif warn_on_missing_entrypoint:
LOG.warning('Could not load %s' %
', '.join(self._missing_names))
self._init_plugins(extensions)
@classmethod
def make_test_instance(cls, extensions, namespace='TESTING',
propagate_map_exceptions=False,
on_load_failure_callback=None,
verify_requirements=False):
"""Construct a test NamedExtensionManager
Test instances are passed a list of extensions to use rather than
loading them from entry points.
:param extensions: Pre-configured Extension instances
:type extensions: list of :class:`~stevedore.extension.Extension`
:param namespace: The namespace for the manager; used only for
identification since the extensions are passed in.
:type namespace: str
:param propagate_map_exceptions: Boolean controlling whether exceptions
are propagated up through the map call or whether they are logged
and then ignored
:type propagate_map_exceptions: bool
:param on_load_failure_callback: Callback function that will
be called when a entrypoint can not be loaded. The
arguments that will be provided when this is called (when
an entrypoint fails to load) are (manager, entrypoint,
exception)
:type on_load_failure_callback: function
:param verify_requirements: Use setuptools to enforce the
dependencies of the plugin(s) being loaded. Defaults to False.
:type verify_requirements: bool
:return: The manager instance, initialized for testing
"""
o = cls.__new__(cls)
names = [e.name for e in extensions]
o._init_attributes(namespace, names,
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback)
o._init_plugins(extensions)
return o
def _init_attributes(self, namespace, names, name_order=False,
propagate_map_exceptions=False,
on_load_failure_callback=None):
super(NamedExtensionManager, self)._init_attributes(
namespace, propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback)
self._names = names
self._missing_names = set()
self._name_order = name_order
def _init_plugins(self, extensions):
super(NamedExtensionManager, self)._init_plugins(extensions)
if self._name_order:
self.extensions = [self[n] for n in self._names
if n not in self._missing_names]
def _load_one_plugin(self, ep, invoke_on_load, invoke_args, invoke_kwds,
verify_requirements):
# Check the name before going any further to prevent
# undesirable code from being loaded at all if we are not
# going to use it.
if ep.name not in self._names:
return None
return super(NamedExtensionManager, self)._load_one_plugin(
ep, invoke_on_load, invoke_args, invoke_kwds,
verify_requirements,
)

View file

@ -0,0 +1,115 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import unicode_literals
import inspect
from docutils import nodes
from docutils.parsers import rst
from docutils.parsers.rst import directives
from docutils.statemachine import ViewList
from sphinx.util import logging
from sphinx.util.nodes import nested_parse_with_titles
from stevedore import extension
LOG = logging.getLogger(__name__)
def _get_docstring(plugin):
return inspect.getdoc(plugin) or ''
def _simple_list(mgr):
for name in sorted(mgr.names()):
ext = mgr[name]
doc = _get_docstring(ext.plugin) or '\n'
summary = doc.splitlines()[0].strip()
yield('* %s -- %s' % (ext.name, summary),
ext.entry_point.module_name)
def _detailed_list(mgr, over='', under='-', titlecase=False):
for name in sorted(mgr.names()):
ext = mgr[name]
if over:
yield (over * len(ext.name), ext.entry_point.module_name)
if titlecase:
yield (ext.name.title(), ext.entry_point.module_name)
else:
yield (ext.name, ext.entry_point.module_name)
if under:
yield (under * len(ext.name), ext.entry_point.module_name)
yield ('\n', ext.entry_point.module_name)
doc = _get_docstring(ext.plugin)
if doc:
yield (doc, ext.entry_point.module_name)
else:
yield ('.. warning:: No documentation found in %s'
% ext.entry_point,
ext.entry_point.module_name)
yield ('\n', ext.entry_point.module_name)
class ListPluginsDirective(rst.Directive):
"""Present a simple list of the plugins in a namespace."""
option_spec = {
'class': directives.class_option,
'detailed': directives.flag,
'titlecase': directives.flag,
'overline-style': directives.single_char_or_unicode,
'underline-style': directives.single_char_or_unicode,
}
has_content = True
def run(self):
namespace = ' '.join(self.content).strip()
LOG.info('documenting plugins from %r' % namespace)
overline_style = self.options.get('overline-style', '')
underline_style = self.options.get('underline-style', '=')
def report_load_failure(mgr, ep, err):
LOG.warning(u'Failed to load %s: %s' % (ep.module_name, err))
mgr = extension.ExtensionManager(
namespace,
on_load_failure_callback=report_load_failure,
)
result = ViewList()
titlecase = 'titlecase' in self.options
if 'detailed' in self.options:
data = _detailed_list(
mgr, over=overline_style, under=underline_style,
titlecase=titlecase)
else:
data = _simple_list(mgr)
for text, source in data:
for line in text.splitlines():
result.append(line, source)
# Parse what we have into a new section.
node = nodes.section()
node.document = self.state.document
nested_parse_with_titles(self.state, result, node)
return node.children
def setup(app):
LOG.info('loading stevedore.sphinxext')
app.add_directive('list-plugins', ListPluginsDirective)

View file

View file

@ -0,0 +1,67 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""TestExtensionManager
Extension manager used only for testing.
"""
import warnings
from stevedore import extension
class TestExtensionManager(extension.ExtensionManager):
"""ExtensionManager that is explicitly initialized for tests.
.. deprecated:: 0.13
Use the :func:`make_test_instance` class method of the class
being replaced by the test instance instead of using this class
directly.
:param extensions: Pre-configured Extension instances to use
instead of loading them from entry points.
:type extensions: list of :class:`~stevedore.extension.Extension`
:param namespace: The namespace for the entry points.
:type namespace: str
:param invoke_on_load: Boolean controlling whether to invoke the
object returned by the entry point after the driver is loaded.
:type invoke_on_load: bool
:param invoke_args: Positional arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_args: tuple
:param invoke_kwds: Named arguments to pass when invoking
the object returned by the entry point. Only used if invoke_on_load
is True.
:type invoke_kwds: dict
"""
def __init__(self, extensions,
namespace='test',
invoke_on_load=False,
invoke_args=(),
invoke_kwds={}):
super(TestExtensionManager, self).__init__(namespace,
invoke_on_load,
invoke_args,
invoke_kwds,
)
self.extensions = extensions
warnings.warn(
'TestExtesionManager has been replaced by make_test_instance()',
DeprecationWarning)
def _load_plugins(self, *args, **kwds):
return []

View file

@ -0,0 +1,55 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Tests for failure loading callback
"""
from testtools.matchers import GreaterThan
import mock
from stevedore import extension
from stevedore import named
from stevedore.tests import utils
class TestCallback(utils.TestCase):
def test_extension_failure_custom_callback(self):
errors = []
def failure_callback(manager, entrypoint, error):
errors.append((manager, entrypoint, error))
em = extension.ExtensionManager('stevedore.test.extension',
invoke_on_load=True,
on_load_failure_callback=
failure_callback)
extensions = list(em.extensions)
self.assertTrue(len(extensions), GreaterThan(0))
self.assertEqual(len(errors), 2)
for manager, entrypoint, error in errors:
self.assertIs(manager, em)
self.assertIsInstance(error, (IOError, ImportError))
@mock.patch('stevedore.named.NamedExtensionManager._load_plugins')
def test_missing_entrypoints_callback(self, load_fn):
errors = set()
def callback(names):
errors.update(names)
load_fn.return_value = [
extension.Extension('foo', None, None, None)
]
named.NamedExtensionManager('stevedore.test.extension',
names=['foo', 'bar'],
invoke_on_load=True,
on_missing_entrypoints_callback=callback)
self.assertEqual(errors, {'bar'})

View file

@ -0,0 +1,103 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from stevedore.tests import utils
from stevedore import dispatch
def check_dispatch(ep, *args, **kwds):
return ep.name == 't2'
class TestDispatch(utils.TestCase):
def check_dispatch(ep, *args, **kwds):
return ep.name == 't2'
def test_dispatch(self):
def invoke(ep, *args, **kwds):
return (ep.name, args, kwds)
em = dispatch.DispatchExtensionManager('stevedore.test.extension',
lambda *args, **kwds: True,
invoke_on_load=True,
invoke_args=('a',),
invoke_kwds={'b': 'B'},
)
self.assertEqual(len(em.extensions), 2)
self.assertEqual(set(em.names()), set(['t1', 't2']))
results = em.map(check_dispatch,
invoke,
'first',
named='named value',
)
expected = [('t2', ('first',), {'named': 'named value'})]
self.assertEqual(results, expected)
def test_dispatch_map_method(self):
em = dispatch.DispatchExtensionManager('stevedore.test.extension',
lambda *args, **kwds: True,
invoke_on_load=True,
invoke_args=('a',),
invoke_kwds={'b': 'B'},
)
results = em.map_method(check_dispatch, 'get_args_and_data', 'first')
self.assertEqual(results, [(('a',), {'b': 'B'}, 'first')])
def test_name_dispatch(self):
def invoke(ep, *args, **kwds):
return (ep.name, args, kwds)
em = dispatch.NameDispatchExtensionManager('stevedore.test.extension',
lambda *args, **kwds: True,
invoke_on_load=True,
invoke_args=('a',),
invoke_kwds={'b': 'B'},
)
self.assertEqual(len(em.extensions), 2)
self.assertEqual(set(em.names()), set(['t1', 't2']))
results = em.map(['t2'], invoke, 'first', named='named value',)
expected = [('t2', ('first',), {'named': 'named value'})]
self.assertEqual(results, expected)
def test_name_dispatch_ignore_missing(self):
def invoke(ep, *args, **kwds):
return (ep.name, args, kwds)
em = dispatch.NameDispatchExtensionManager(
'stevedore.test.extension',
lambda *args, **kwds: True,
invoke_on_load=True,
invoke_args=('a',),
invoke_kwds={'b': 'B'},
)
results = em.map(['t3', 't1'], invoke, 'first', named='named value',)
expected = [('t1', ('first',), {'named': 'named value'})]
self.assertEqual(results, expected)
def test_name_dispatch_map_method(self):
em = dispatch.NameDispatchExtensionManager(
'stevedore.test.extension',
lambda *args, **kwds: True,
invoke_on_load=True,
invoke_args=('a',),
invoke_kwds={'b': 'B'},
)
results = em.map_method(['t3', 't1'], 'get_args_and_data', 'first')
self.assertEqual(results, [(('a',), {'b': 'B'}, 'first')])

View file

@ -0,0 +1,89 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Tests for stevedore.extension
"""
import pkg_resources
from stevedore import driver
from stevedore import exception
from stevedore import extension
from stevedore.tests import test_extension
from stevedore.tests import utils
class TestCallback(utils.TestCase):
def test_detect_plugins(self):
em = driver.DriverManager('stevedore.test.extension', 't1')
names = sorted(em.names())
self.assertEqual(names, ['t1'])
def test_call(self):
def invoke(ext, *args, **kwds):
return (ext.name, args, kwds)
em = driver.DriverManager('stevedore.test.extension', 't1')
result = em(invoke, 'a', b='C')
self.assertEqual(result, ('t1', ('a',), {'b': 'C'}))
def test_driver_property_not_invoked_on_load(self):
em = driver.DriverManager('stevedore.test.extension', 't1',
invoke_on_load=False)
d = em.driver
self.assertIs(d, test_extension.FauxExtension)
def test_driver_property_invoked_on_load(self):
em = driver.DriverManager('stevedore.test.extension', 't1',
invoke_on_load=True)
d = em.driver
self.assertIsInstance(d, test_extension.FauxExtension)
def test_no_drivers(self):
try:
driver.DriverManager('stevedore.test.extension.none', 't1')
except exception.NoMatches as err:
self.assertIn("No 'stevedore.test.extension.none' driver found",
str(err))
def test_bad_driver(self):
try:
driver.DriverManager('stevedore.test.extension', 'e2')
except ImportError:
pass
else:
self.assertEqual(False, "No error raised")
def test_multiple_drivers(self):
# The idea for this test was contributed by clayg:
# https://gist.github.com/clayg/6311348
extensions = [
extension.Extension(
'backend',
pkg_resources.EntryPoint.parse('backend = pkg1:driver'),
'pkg backend',
None,
),
extension.Extension(
'backend',
pkg_resources.EntryPoint.parse('backend = pkg2:driver'),
'pkg backend',
None,
),
]
try:
dm = driver.DriverManager.make_test_instance(extensions[0])
# Call the initialization code that verifies the extension
dm._init_plugins(extensions)
except exception.MultipleMatches as err:
self.assertIn("Multiple", str(err))
else:
self.fail('Should have had an error')

View file

@ -0,0 +1,42 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from stevedore import enabled
from stevedore.tests import utils
class TestEnabled(utils.TestCase):
def test_enabled(self):
def check_enabled(ep):
return ep.name == 't2'
em = enabled.EnabledExtensionManager(
'stevedore.test.extension',
check_enabled,
invoke_on_load=True,
invoke_args=('a',),
invoke_kwds={'b': 'B'},
)
self.assertEqual(len(em.extensions), 1)
self.assertEqual(em.names(), ['t2'])
def test_enabled_after_load(self):
def check_enabled(ext):
return ext.obj and ext.name == 't2'
em = enabled.EnabledExtensionManager(
'stevedore.test.extension',
check_enabled,
invoke_on_load=True,
invoke_args=('a',),
invoke_kwds={'b': 'B'},
)
self.assertEqual(len(em.extensions), 1)
self.assertEqual(em.names(), ['t2'])

View file

@ -0,0 +1,41 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Tests for stevedore.example2.fields
"""
from stevedore.example2 import fields
from stevedore.tests import utils
class TestExampleFields(utils.TestCase):
def test_simple_items(self):
f = fields.FieldList(100)
text = ''.join(f.format({'a': 'A', 'b': 'B'}))
expected = '\n'.join([
': a : A',
': b : B',
'',
])
self.assertEqual(text, expected)
def test_long_item(self):
f = fields.FieldList(25)
text = ''.join(f.format({'name':
'a value longer than the allowed width'}))
expected = '\n'.join([
': name : a value longer',
' than the allowed',
' width',
'',
])
self.assertEqual(text, expected)

View file

@ -0,0 +1,29 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Tests for stevedore.example.simple
"""
from stevedore.example import simple
from stevedore.tests import utils
class TestExampleSimple(utils.TestCase):
def test_simple_items(self):
f = simple.Simple(100)
text = ''.join(f.format({'a': 'A', 'b': 'B'}))
expected = '\n'.join([
'a = A',
'b = B',
'',
])
self.assertEqual(text, expected)

View file

@ -0,0 +1,244 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Tests for stevedore.extension
"""
import operator
import mock
from stevedore import exception
from stevedore import extension
from stevedore.tests import utils
ALL_NAMES = ['e1', 't1', 't2']
WORKING_NAMES = ['t1', 't2']
class FauxExtension(object):
def __init__(self, *args, **kwds):
self.args = args
self.kwds = kwds
def get_args_and_data(self, data):
return self.args, self.kwds, data
class BrokenExtension(object):
def __init__(self, *args, **kwds):
raise IOError("Did not create")
class TestCallback(utils.TestCase):
def test_detect_plugins(self):
em = extension.ExtensionManager('stevedore.test.extension')
names = sorted(em.names())
self.assertEqual(names, ALL_NAMES)
def test_get_by_name(self):
em = extension.ExtensionManager('stevedore.test.extension')
e = em['t1']
self.assertEqual(e.name, 't1')
def test_list_entry_points(self):
em = extension.ExtensionManager('stevedore.test.extension')
n = em.list_entry_points()
self.assertEqual(set(['e1', 'e2', 't1', 't2']),
set(map(operator.attrgetter("name"), n)))
self.assertEqual(4, len(n))
def test_list_entry_points_names(self):
em = extension.ExtensionManager('stevedore.test.extension')
names = em.entry_points_names()
self.assertEqual(set(['e1', 'e2', 't1', 't2']), set(names))
self.assertEqual(4, len(names))
def test_contains_by_name(self):
em = extension.ExtensionManager('stevedore.test.extension')
self.assertEqual('t1' in em, True)
def test_get_by_name_missing(self):
em = extension.ExtensionManager('stevedore.test.extension')
try:
em['t3']
except KeyError:
pass
else:
assert False, 'Failed to raise KeyError'
def test_load_multiple_times_entry_points(self):
# We expect to get the same EntryPoint object because we save them
# in the cache.
em1 = extension.ExtensionManager('stevedore.test.extension')
eps1 = [ext.entry_point for ext in em1]
em2 = extension.ExtensionManager('stevedore.test.extension')
eps2 = [ext.entry_point for ext in em2]
self.assertIs(eps1[0], eps2[0])
def test_load_multiple_times_plugins(self):
# We expect to get the same plugin object (module or class)
# because the underlying import machinery will cache the values.
em1 = extension.ExtensionManager('stevedore.test.extension')
plugins1 = [ext.plugin for ext in em1]
em2 = extension.ExtensionManager('stevedore.test.extension')
plugins2 = [ext.plugin for ext in em2]
self.assertIs(plugins1[0], plugins2[0])
def test_use_cache(self):
# If we insert something into the cache of entry points,
# the manager should not have to call into pkg_resources
# to find the plugins.
cache = extension.ExtensionManager.ENTRY_POINT_CACHE
cache['stevedore.test.faux'] = []
with mock.patch('pkg_resources.iter_entry_points',
side_effect=
AssertionError('called iter_entry_points')):
em = extension.ExtensionManager('stevedore.test.faux')
names = em.names()
self.assertEqual(names, [])
def test_iterable(self):
em = extension.ExtensionManager('stevedore.test.extension')
names = sorted(e.name for e in em)
self.assertEqual(names, ALL_NAMES)
def test_invoke_on_load(self):
em = extension.ExtensionManager('stevedore.test.extension',
invoke_on_load=True,
invoke_args=('a',),
invoke_kwds={'b': 'B'},
)
self.assertEqual(len(em.extensions), 2)
for e in em.extensions:
self.assertEqual(e.obj.args, ('a',))
self.assertEqual(e.obj.kwds, {'b': 'B'})
def test_map_return_values(self):
def mapped(ext, *args, **kwds):
return ext.name
em = extension.ExtensionManager('stevedore.test.extension',
invoke_on_load=True,
)
results = em.map(mapped)
self.assertEqual(sorted(results), WORKING_NAMES)
def test_map_arguments(self):
objs = []
def mapped(ext, *args, **kwds):
objs.append((ext, args, kwds))
em = extension.ExtensionManager('stevedore.test.extension',
invoke_on_load=True,
)
em.map(mapped, 1, 2, a='A', b='B')
self.assertEqual(len(objs), 2)
names = sorted([o[0].name for o in objs])
self.assertEqual(names, WORKING_NAMES)
for o in objs:
self.assertEqual(o[1], (1, 2))
self.assertEqual(o[2], {'a': 'A', 'b': 'B'})
def test_map_eats_errors(self):
def mapped(ext, *args, **kwds):
raise RuntimeError('hard coded error')
em = extension.ExtensionManager('stevedore.test.extension',
invoke_on_load=True,
)
results = em.map(mapped, 1, 2, a='A', b='B')
self.assertEqual(results, [])
def test_map_propagate_exceptions(self):
def mapped(ext, *args, **kwds):
raise RuntimeError('hard coded error')
em = extension.ExtensionManager('stevedore.test.extension',
invoke_on_load=True,
propagate_map_exceptions=True
)
try:
em.map(mapped, 1, 2, a='A', b='B')
assert False
except RuntimeError:
pass
def test_map_errors_when_no_plugins(self):
expected_str = 'No stevedore.test.extension.none extensions found'
def mapped(ext, *args, **kwds):
pass
em = extension.ExtensionManager('stevedore.test.extension.none',
invoke_on_load=True,
)
try:
em.map(mapped, 1, 2, a='A', b='B')
except exception.NoMatches as err:
self.assertEqual(expected_str, str(err))
def test_map_method(self):
em = extension.ExtensionManager('stevedore.test.extension',
invoke_on_load=True,
)
result = em.map_method('get_args_and_data', 42)
self.assertEqual(set(r[2] for r in result), set([42]))
def test_items(self):
em = extension.ExtensionManager('stevedore.test.extension')
expected_output = set([(name, em[name]) for name in ALL_NAMES])
self.assertEqual(expected_output, set(em.items()))
class TestLoadRequirementsNewSetuptools(utils.TestCase):
# setuptools 11.3 and later
def setUp(self):
super(TestLoadRequirementsNewSetuptools, self).setUp()
self.mock_ep = mock.Mock(spec=['require', 'resolve', 'load', 'name'])
self.em = extension.ExtensionManager.make_test_instance([])
def test_verify_requirements(self):
self.em._load_one_plugin(self.mock_ep, False, (), {},
verify_requirements=True)
self.mock_ep.require.assert_called_once_with()
self.mock_ep.resolve.assert_called_once_with()
def test_no_verify_requirements(self):
self.em._load_one_plugin(self.mock_ep, False, (), {},
verify_requirements=False)
self.assertEqual(0, self.mock_ep.require.call_count)
self.mock_ep.resolve.assert_called_once_with()
class TestLoadRequirementsOldSetuptools(utils.TestCase):
# Before setuptools 11.3
def setUp(self):
super(TestLoadRequirementsOldSetuptools, self).setUp()
self.mock_ep = mock.Mock(spec=['load', 'name'])
self.em = extension.ExtensionManager.make_test_instance([])
def test_verify_requirements(self):
self.em._load_one_plugin(self.mock_ep, False, (), {},
verify_requirements=True)
self.mock_ep.load.assert_called_once_with(require=True)
def test_no_verify_requirements(self):
self.em._load_one_plugin(self.mock_ep, False, (), {},
verify_requirements=False)
self.mock_ep.load.assert_called_once_with(require=False)

View file

@ -0,0 +1,55 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from stevedore import hook
from stevedore.tests import utils
class TestHook(utils.TestCase):
def test_hook(self):
em = hook.HookManager(
'stevedore.test.extension',
't1',
invoke_on_load=True,
invoke_args=('a',),
invoke_kwds={'b': 'B'},
)
self.assertEqual(len(em.extensions), 1)
self.assertEqual(em.names(), ['t1'])
def test_get_by_name(self):
em = hook.HookManager(
'stevedore.test.extension',
't1',
invoke_on_load=True,
invoke_args=('a',),
invoke_kwds={'b': 'B'},
)
e_list = em['t1']
self.assertEqual(len(e_list), 1)
e = e_list[0]
self.assertEqual(e.name, 't1')
def test_get_by_name_missing(self):
em = hook.HookManager(
'stevedore.test.extension',
't1',
invoke_on_load=True,
invoke_args=('a',),
invoke_kwds={'b': 'B'},
)
try:
em['t2']
except KeyError:
pass
else:
assert False, 'Failed to raise KeyError'

View file

@ -0,0 +1,93 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from stevedore import named
from stevedore.tests import utils
import mock
class TestNamed(utils.TestCase):
def test_named(self):
em = named.NamedExtensionManager(
'stevedore.test.extension',
names=['t1'],
invoke_on_load=True,
invoke_args=('a',),
invoke_kwds={'b': 'B'},
)
actual = em.names()
self.assertEqual(actual, ['t1'])
def test_enabled_before_load(self):
# Set up the constructor for the FauxExtension to cause an
# AssertionError so the test fails if the class is instantiated,
# which should only happen if it is loaded before the name of the
# extension is compared against the names that should be loaded by
# the manager.
init_name = 'stevedore.tests.test_extension.FauxExtension.__init__'
with mock.patch(init_name) as m:
m.side_effect = AssertionError
em = named.NamedExtensionManager(
'stevedore.test.extension',
# Look for an extension that does not exist so the
# __init__ we mocked should never be invoked.
names=['no-such-extension'],
invoke_on_load=True,
invoke_args=('a',),
invoke_kwds={'b': 'B'},
)
actual = em.names()
self.assertEqual(actual, [])
def test_extensions_listed_in_name_order(self):
# Since we don't know the "natural" order of the extensions, run
# the test both ways: if the sorting is broken, one of them will
# fail
em = named.NamedExtensionManager(
'stevedore.test.extension',
names=['t1', 't2'],
name_order=True
)
actual = em.names()
self.assertEqual(actual, ['t1', 't2'])
em = named.NamedExtensionManager(
'stevedore.test.extension',
names=['t2', 't1'],
name_order=True
)
actual = em.names()
self.assertEqual(actual, ['t2', 't1'])
def test_load_fail_ignored_when_sorted(self):
em = named.NamedExtensionManager(
'stevedore.test.extension',
names=['e1', 't2', 'e2', 't1'],
name_order=True,
invoke_on_load=True,
invoke_args=('a',),
invoke_kwds={'b': 'B'},
)
actual = em.names()
self.assertEqual(['t2', 't1'], actual)
em = named.NamedExtensionManager(
'stevedore.test.extension',
names=['e1', 't1'],
name_order=False,
invoke_on_load=True,
invoke_args=('a',),
invoke_kwds={'b': 'B'},
)
actual = em.names()
self.assertEqual(['t1'], actual)

View file

@ -0,0 +1,120 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Tests for the sphinx extension
"""
from __future__ import unicode_literals
from stevedore import extension
from stevedore import sphinxext
from stevedore.tests import utils
import mock
import pkg_resources
def _make_ext(name, docstring):
def inner():
pass
inner.__doc__ = docstring
m1 = mock.Mock(spec=pkg_resources.EntryPoint)
m1.module_name = '%s_module' % name
s = mock.Mock(return_value='ENTRY_POINT(%s)' % name)
m1.__str__ = s
return extension.Extension(name, m1, inner, None)
class TestSphinxExt(utils.TestCase):
def setUp(self):
super(TestSphinxExt, self).setUp()
self.exts = [
_make_ext('test1', 'One-line docstring'),
_make_ext('test2', 'Multi-line docstring\n\nAnother para'),
]
self.em = extension.ExtensionManager.make_test_instance(self.exts)
def test_simple_list(self):
results = list(sphinxext._simple_list(self.em))
self.assertEqual(
[
('* test1 -- One-line docstring', 'test1_module'),
('* test2 -- Multi-line docstring', 'test2_module'),
],
results,
)
def test_simple_list_no_docstring(self):
ext = [_make_ext('nodoc', None)]
em = extension.ExtensionManager.make_test_instance(ext)
results = list(sphinxext._simple_list(em))
self.assertEqual(
[
('* nodoc -- ', 'nodoc_module'),
],
results,
)
def test_detailed_list(self):
results = list(sphinxext._detailed_list(self.em))
self.assertEqual(
[
('test1', 'test1_module'),
('-----', 'test1_module'),
('\n', 'test1_module'),
('One-line docstring', 'test1_module'),
('\n', 'test1_module'),
('test2', 'test2_module'),
('-----', 'test2_module'),
('\n', 'test2_module'),
('Multi-line docstring\n\nAnother para', 'test2_module'),
('\n', 'test2_module'),
],
results,
)
def test_detailed_list_format(self):
results = list(sphinxext._detailed_list(self.em, over='+', under='+'))
self.assertEqual(
[
('+++++', 'test1_module'),
('test1', 'test1_module'),
('+++++', 'test1_module'),
('\n', 'test1_module'),
('One-line docstring', 'test1_module'),
('\n', 'test1_module'),
('+++++', 'test2_module'),
('test2', 'test2_module'),
('+++++', 'test2_module'),
('\n', 'test2_module'),
('Multi-line docstring\n\nAnother para', 'test2_module'),
('\n', 'test2_module'),
],
results,
)
def test_detailed_list_no_docstring(self):
ext = [_make_ext('nodoc', None)]
em = extension.ExtensionManager.make_test_instance(ext)
results = list(sphinxext._detailed_list(em))
self.assertEqual(
[
('nodoc', 'nodoc_module'),
('-----', 'nodoc_module'),
('\n', 'nodoc_module'),
('.. warning:: No documentation found in ENTRY_POINT(nodoc)',
'nodoc_module'),
('\n', 'nodoc_module'),
],
results,
)

View file

@ -0,0 +1,216 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from mock import Mock, sentinel
from stevedore import (ExtensionManager, NamedExtensionManager, HookManager,
DriverManager, EnabledExtensionManager)
from stevedore.dispatch import (DispatchExtensionManager,
NameDispatchExtensionManager)
from stevedore.extension import Extension
from stevedore.tests import utils
test_extension = Extension('test_extension', None, None, None)
test_extension2 = Extension('another_one', None, None, None)
mock_entry_point = Mock(module_name='test.extension', attrs=['obj'])
a_driver = Extension('test_driver', mock_entry_point, sentinel.driver_plugin,
sentinel.driver_obj)
# base ExtensionManager
class TestTestManager(utils.TestCase):
def test_instance_should_use_supplied_extensions(self):
extensions = [test_extension, test_extension2]
em = ExtensionManager.make_test_instance(extensions)
self.assertEqual(extensions, em.extensions)
def test_instance_should_have_default_namespace(self):
em = ExtensionManager.make_test_instance([])
self.assertEqual(em.namespace, 'TESTING')
def test_instance_should_use_supplied_namespace(self):
namespace = 'testing.1.2.3'
em = ExtensionManager.make_test_instance([], namespace=namespace)
self.assertEqual(namespace, em.namespace)
def test_extension_name_should_be_listed(self):
em = ExtensionManager.make_test_instance([test_extension])
self.assertIn(test_extension.name, em.names())
def test_iterator_should_yield_extension(self):
em = ExtensionManager.make_test_instance([test_extension])
self.assertEqual(test_extension, next(iter(em)))
def test_manager_should_allow_name_access(self):
em = ExtensionManager.make_test_instance([test_extension])
self.assertEqual(test_extension, em[test_extension.name])
def test_manager_should_call(self):
em = ExtensionManager.make_test_instance([test_extension])
func = Mock()
em.map(func)
func.assert_called_once_with(test_extension)
def test_manager_should_call_all(self):
em = ExtensionManager.make_test_instance([test_extension2,
test_extension])
func = Mock()
em.map(func)
func.assert_any_call(test_extension2)
func.assert_any_call(test_extension)
def test_manager_return_values(self):
def mapped(ext, *args, **kwds):
return ext.name
em = ExtensionManager.make_test_instance([test_extension2,
test_extension])
results = em.map(mapped)
self.assertEqual(sorted(results), ['another_one', 'test_extension'])
def test_manager_should_eat_exceptions(self):
em = ExtensionManager.make_test_instance([test_extension])
func = Mock(side_effect=RuntimeError('hard coded error'))
results = em.map(func, 1, 2, a='A', b='B')
self.assertEqual(results, [])
def test_manager_should_propagate_exceptions(self):
em = ExtensionManager.make_test_instance([test_extension],
propagate_map_exceptions=True)
self.skipTest('Skipping temporarily')
func = Mock(side_effect=RuntimeError('hard coded error'))
em.map(func, 1, 2, a='A', b='B')
# NamedExtensionManager
def test_named_manager_should_use_supplied_extensions(self):
extensions = [test_extension, test_extension2]
em = NamedExtensionManager.make_test_instance(extensions)
self.assertEqual(extensions, em.extensions)
def test_named_manager_should_have_default_namespace(self):
em = NamedExtensionManager.make_test_instance([])
self.assertEqual(em.namespace, 'TESTING')
def test_named_manager_should_use_supplied_namespace(self):
namespace = 'testing.1.2.3'
em = NamedExtensionManager.make_test_instance([], namespace=namespace)
self.assertEqual(namespace, em.namespace)
def test_named_manager_should_populate_names(self):
extensions = [test_extension, test_extension2]
em = NamedExtensionManager.make_test_instance(extensions)
self.assertEqual(em.names(), ['test_extension', 'another_one'])
# HookManager
def test_hook_manager_should_use_supplied_extensions(self):
extensions = [test_extension, test_extension2]
em = HookManager.make_test_instance(extensions)
self.assertEqual(extensions, em.extensions)
def test_hook_manager_should_be_first_extension_name(self):
extensions = [test_extension, test_extension2]
em = HookManager.make_test_instance(extensions)
# This will raise KeyError if the names don't match
assert(em[test_extension.name])
def test_hook_manager_should_have_default_namespace(self):
em = HookManager.make_test_instance([test_extension])
self.assertEqual(em.namespace, 'TESTING')
def test_hook_manager_should_use_supplied_namespace(self):
namespace = 'testing.1.2.3'
em = HookManager.make_test_instance([test_extension],
namespace=namespace)
self.assertEqual(namespace, em.namespace)
def test_hook_manager_should_return_named_extensions(self):
hook1 = Extension('captain', None, None, None)
hook2 = Extension('captain', None, None, None)
em = HookManager.make_test_instance([hook1, hook2])
self.assertEqual([hook1, hook2], em['captain'])
# DriverManager
def test_driver_manager_should_use_supplied_extension(self):
em = DriverManager.make_test_instance(a_driver)
self.assertEqual([a_driver], em.extensions)
def test_driver_manager_should_have_default_namespace(self):
em = DriverManager.make_test_instance(a_driver)
self.assertEqual(em.namespace, 'TESTING')
def test_driver_manager_should_use_supplied_namespace(self):
namespace = 'testing.1.2.3'
em = DriverManager.make_test_instance(a_driver, namespace=namespace)
self.assertEqual(namespace, em.namespace)
def test_instance_should_use_driver_name(self):
em = DriverManager.make_test_instance(a_driver)
self.assertEqual(['test_driver'], em.names())
def test_instance_call(self):
def invoke(ext, *args, **kwds):
return ext.name, args, kwds
em = DriverManager.make_test_instance(a_driver)
result = em(invoke, 'a', b='C')
self.assertEqual(result, ('test_driver', ('a',), {'b': 'C'}))
def test_instance_driver_property(self):
em = DriverManager.make_test_instance(a_driver)
self.assertEqual(sentinel.driver_obj, em.driver)
# EnabledExtensionManager
def test_enabled_instance_should_use_supplied_extensions(self):
extensions = [test_extension, test_extension2]
em = EnabledExtensionManager.make_test_instance(extensions)
self.assertEqual(extensions, em.extensions)
# DispatchExtensionManager
def test_dispatch_instance_should_use_supplied_extensions(self):
extensions = [test_extension, test_extension2]
em = DispatchExtensionManager.make_test_instance(extensions)
self.assertEqual(extensions, em.extensions)
def test_dispatch_map_should_invoke_filter_for_extensions(self):
em = DispatchExtensionManager.make_test_instance([test_extension,
test_extension2])
filter_func = Mock(return_value=False)
args = ('A',)
kw = {'big': 'Cheese'}
em.map(filter_func, None, *args, **kw)
filter_func.assert_any_call(test_extension, *args, **kw)
filter_func.assert_any_call(test_extension2, *args, **kw)
# NameDispatchExtensionManager
def test_name_dispatch_instance_should_use_supplied_extensions(self):
extensions = [test_extension, test_extension2]
em = NameDispatchExtensionManager.make_test_instance(extensions)
self.assertEqual(extensions, em.extensions)
def test_name_dispatch_instance_should_build_extension_name_map(self):
extensions = [test_extension, test_extension2]
em = NameDispatchExtensionManager.make_test_instance(extensions)
self.assertEqual(test_extension, em.by_name[test_extension.name])
self.assertEqual(test_extension2, em.by_name[test_extension2.name])
def test_named_dispatch_map_should_invoke_filter_for_extensions(self):
em = NameDispatchExtensionManager.make_test_instance([test_extension,
test_extension2])
func = Mock()
args = ('A',)
kw = {'BIGGER': 'Cheese'}
em.map(['test_extension'], func, *args, **kw)
func.assert_called_once_with(test_extension, *args, **kw)

View file

@ -0,0 +1,17 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import unittest
class TestCase(unittest.TestCase):
pass