mirror of
https://github.com/clinton-hall/nzbToMedia.git
synced 2025-08-14 18:47:09 -07:00
Move common libs to libs/common
This commit is contained in:
parent
8dbb1a2451
commit
1f4bd41bcc
1612 changed files with 962 additions and 10 deletions
24
libs/common/stevedore/__init__.py
Normal file
24
libs/common/stevedore/__init__.py
Normal 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())
|
||||
|
229
libs/common/stevedore/dispatch.py
Normal file
229
libs/common/stevedore/dispatch.py
Normal 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)
|
148
libs/common/stevedore/driver.py
Normal file
148
libs/common/stevedore/driver.py
Normal 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
|
84
libs/common/stevedore/enabled.py
Normal file
84
libs/common/stevedore/enabled.py
Normal 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
|
0
libs/common/stevedore/example/__init__.py
Normal file
0
libs/common/stevedore/example/__init__.py
Normal file
22
libs/common/stevedore/example/base.py
Normal file
22
libs/common/stevedore/example/base.py
Normal 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.
|
||||
"""
|
37
libs/common/stevedore/example/load_as_driver.py
Normal file
37
libs/common/stevedore/example/load_as_driver.py
Normal 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='')
|
39
libs/common/stevedore/example/load_as_extension.py
Normal file
39
libs/common/stevedore/example/load_as_extension.py
Normal 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('')
|
43
libs/common/stevedore/example/setup.py
Normal file
43
libs/common/stevedore/example/setup.py
Normal 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,
|
||||
)
|
20
libs/common/stevedore/example/simple.py
Normal file
20
libs/common/stevedore/example/simple.py
Normal 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
|
0
libs/common/stevedore/example2/__init__.py
Normal file
0
libs/common/stevedore/example2/__init__.py
Normal file
36
libs/common/stevedore/example2/fields.py
Normal file
36
libs/common/stevedore/example2/fields.py
Normal 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'
|
42
libs/common/stevedore/example2/setup.py
Normal file
42
libs/common/stevedore/example2/setup.py
Normal 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,
|
||||
)
|
23
libs/common/stevedore/exception.py
Normal file
23
libs/common/stevedore/exception.py
Normal 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."""
|
331
libs/common/stevedore/extension.py
Normal file
331
libs/common/stevedore/extension.py
Normal 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)
|
91
libs/common/stevedore/hook.py
Normal file
91
libs/common/stevedore/hook.py
Normal 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
|
159
libs/common/stevedore/named.py
Normal file
159
libs/common/stevedore/named.py
Normal 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,
|
||||
)
|
115
libs/common/stevedore/sphinxext.py
Normal file
115
libs/common/stevedore/sphinxext.py
Normal 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)
|
0
libs/common/stevedore/tests/__init__.py
Normal file
0
libs/common/stevedore/tests/__init__.py
Normal file
0
libs/common/stevedore/tests/extension_unimportable.py
Normal file
0
libs/common/stevedore/tests/extension_unimportable.py
Normal file
67
libs/common/stevedore/tests/manager.py
Normal file
67
libs/common/stevedore/tests/manager.py
Normal 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 []
|
55
libs/common/stevedore/tests/test_callback.py
Normal file
55
libs/common/stevedore/tests/test_callback.py
Normal 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'})
|
103
libs/common/stevedore/tests/test_dispatch.py
Normal file
103
libs/common/stevedore/tests/test_dispatch.py
Normal 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')])
|
89
libs/common/stevedore/tests/test_driver.py
Normal file
89
libs/common/stevedore/tests/test_driver.py
Normal 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')
|
42
libs/common/stevedore/tests/test_enabled.py
Normal file
42
libs/common/stevedore/tests/test_enabled.py
Normal 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'])
|
41
libs/common/stevedore/tests/test_example_fields.py
Normal file
41
libs/common/stevedore/tests/test_example_fields.py
Normal 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)
|
29
libs/common/stevedore/tests/test_example_simple.py
Normal file
29
libs/common/stevedore/tests/test_example_simple.py
Normal 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)
|
244
libs/common/stevedore/tests/test_extension.py
Normal file
244
libs/common/stevedore/tests/test_extension.py
Normal 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)
|
55
libs/common/stevedore/tests/test_hook.py
Normal file
55
libs/common/stevedore/tests/test_hook.py
Normal 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'
|
93
libs/common/stevedore/tests/test_named.py
Normal file
93
libs/common/stevedore/tests/test_named.py
Normal 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)
|
120
libs/common/stevedore/tests/test_sphinxext.py
Normal file
120
libs/common/stevedore/tests/test_sphinxext.py
Normal 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,
|
||||
)
|
216
libs/common/stevedore/tests/test_test_manager.py
Normal file
216
libs/common/stevedore/tests/test_test_manager.py
Normal 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)
|
17
libs/common/stevedore/tests/utils.py
Normal file
17
libs/common/stevedore/tests/utils.py
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue