Commits

Petar Marić committed 03e6379

Decoupled the plugin framework to https://bitbucket.org/petar/simple_plugins for reusability

Comments (0)

Files changed (8)

beam_integrals/__init__.py

 
 if 'MPMATH_NOGMPY' not in os.environ and mpmath.libmp.BACKEND == 'python': #@UndefinedVariable, pragma: no cover
     import warnings
-    from .exceptions import PerformanceWarning
+    from simple_plugins import PerformanceWarning
     
     warnings.warn(
         'gmpy is not available for speedups (code will still run correctly, '\

beam_integrals/beam_types.py

 from friendly_name_mixin import FriendlyNameFromClassMixin
+from simple_plugins import PluginMount
 from sympy import cos, cosh, diff, Float, pi, sin, sinh, tan, tanh
 from . import a, y, mu_m
-from .utils import PluginMount
 
 
 class BaseBeamType(FriendlyNameFromClassMixin):

beam_integrals/characteristic_equation_solvers.py

 import os
 import cPickle as pickle
 from friendly_name_mixin import FriendlyNameFromClassMixin
+from simple_plugins import AttrDict, PluginMount
 from sympy import Abs, Float, nan, mpmath, sign
 from . import PROJECT_SETTINGS_DIR, DEFAULT_MAX_MODE, DEFAULT_DECIMAL_PRECISION
 from . import exceptions as exc
 from .beam_types import BaseBeamType
-from .utils import AttrDict, PluginMount
 
 
 class BaseRootfinder(FriendlyNameFromClassMixin):

beam_integrals/exceptions.py

     """A generic exception for all others to extend""" 
 
 
-class PerformanceWarning(UserWarning):
-    """Warning issued when something causes a performance degradation"""
-
-
-class CoercionError(BeamTypesException):
-    """The given value can't be coerced to a valid instance"""
-
-
 class InvalidModeError(BeamTypesException):
     """The given value isn't a valid mode"""
 

beam_integrals/integrals.py

 import itertools
 import re
 from friendly_name_mixin import FriendlyNameFromClassMixin
+from simple_plugins import PluginMount
 from sympy import Float, mpmath
 from . import DEFAULT_DECIMAL_PRECISION
 from .characteristic_equation_solvers import find_best_root
-from .utils import PluginMount
 
 
 class BaseIntegral(FriendlyNameFromClassMixin):

beam_integrals/utils.py

-from warnings import warn
-from .exceptions import CoercionError, PerformanceWarning
-
-
-# Based on `django.db.models`, http://djangosnippets.org/snippets/542/ and
-# http://stackoverflow.com/questions/224026/javascript-style-dot-notation-for-dictionary-keys-unpythonic
-class AttrDict(dict):
-    __getattr__= dict.__getitem__
-    __setattr__= dict.__setitem__
-    __delattr__= dict.__delitem__
-
-
-class PluginMount(type):
-    def __init__(cls, name, bases, attrs): #@UnusedVariable
-        if not hasattr(cls, '_plugin_registry'):
-            # This branch only executes when processing the mount point itself.
-            # So, since this is a new plugin type, not an implementation, this
-            # class shouldn't be registered as a plugin. Instead, it sets up a
-            # list where plugins can be registered later.
-            cls._plugin_registry = []
-            cls._plugins = None
-            
-            meta = getattr(cls, 'Meta', object())
-            def override_options(options):
-                return dict(
-                    (name, getattr(meta, name, default_value))
-                    for name, default_value in options.items()
-                )
-            
-            cls._meta = AttrDict(_base_class=cls)
-            cls._meta.update(override_options({
-                'id_field': 'id',
-                'id_field_coerce': int,
-            }))
-        else:
-            # Don't register 'Base*' classes as plugin implementations
-            if not name.startswith('Base'):
-                # This must be a plugin implementation, which should be registered.
-                # Simply appending it to the list is all that's needed to keep
-                # track of it later.
-                cls._plugin_registry.append(cls)
-    
-    def _unregister_plugin(self):
-        self._plugin_registry.remove(self)
-        self._plugins = None # Clear out the old plugin cache
-    
-    @property
-    def plugins(self):
-        base_cls = self._meta._base_class
-        if base_cls._plugins is None:
-            x = AttrDict()
-            x.classes = set(self._plugin_registry)
-            x.instances = set(cls() for cls in x.classes)
-            x.id_to_instance = dict((getattr(obj, self._meta.id_field), obj) for obj in x.instances)
-            x.id_to_class = dict((k, type(v)) for k, v in x.id_to_instance.items())
-            x.class_to_id = dict((v, k) for k, v in x.id_to_class.items())
-            x.instances_sorted_by_id = [v for _, v in sorted(x.id_to_instance.items())]
-            x.valid_ids = set(x.id_to_instance)
-            
-            if hasattr(base_cls, '_contribute_to_plugins'):
-                base_cls._contribute_to_plugins(_plugins=x)
-            
-            base_cls._plugins = x
-        
-        return base_cls._plugins
-    
-    def coerce(self, value):
-        """Coerce the passed value into the right instance"""
-        perf_warn_msg = "Creating too many %s instances may be expensive, passing "\
-                        "the objects id is generally preferred" % self._meta._base_class
-        
-        # Check if the passed value is already a `_base_class` instance
-        if isinstance(value, self._meta._base_class):
-            warn(perf_warn_msg, category=PerformanceWarning)
-            return value # No coercion needed
-        
-        # Check if the passed value is a `_base_class` subclass
-        try:
-            if issubclass(value, self._meta._base_class):
-                warn(perf_warn_msg, category=PerformanceWarning)
-                return value()
-        except TypeError:
-            pass # Passed value is not a class
-        
-        # Check if the passed value is a valid object id
-        try:
-            object_id = self._meta.id_field_coerce(value)
-            try:
-                return self.plugins.id_to_instance[object_id]
-            except KeyError:
-                raise CoercionError("%d is not a valid object id" % object_id)
-        except (TypeError, ValueError):
-            pass # Passed value can't be coerced to the object id type
-        
-        # Can't coerce an unknown type
-        raise CoercionError("Can't coerce %r to a valid %s instance" % (
-            value, self._meta._base_class)
-        )
 friendly_name_mixin>=1.0
+simple_plugins>=1.0
 sympy==0.7.1

tests/test_utils.py

-from friendly_name_mixin import FriendlyNameFromClassMixin
-from nose.tools import raises
-from nose_extra_tools import assert_equal, assert_is, issues_warnings #@UnresolvedImport
-from beam_integrals.exceptions import CoercionError, PerformanceWarning
-from beam_integrals.utils import AttrDict, PluginMount
-
-
-class TestPluginMount(object):
-    def setup_http_method_plugins(self):
-        class HTTPMethod(FriendlyNameFromClassMixin):
-            """Mount point should not be registered as a plugin"""
-            __metaclass__ = PluginMount
-            class Meta:
-                id_field = 'name'
-                id_field_coerce = str
-        class BaseIdempotentHTTPMethod(HTTPMethod):
-            """'Base*' classes should not be registered as plugins"""
-            pass
-        class GET(BaseIdempotentHTTPMethod):
-            pass
-        class POST(HTTPMethod):
-            pass
-        class FakePOST(HTTPMethod):
-            """Both parent and its child should be registered as plugins"""
-            pass
-        class PUT(BaseIdempotentHTTPMethod):
-            pass
-        class DELETE(BaseIdempotentHTTPMethod):
-            pass
-        
-        self.HTTPMethod = HTTPMethod
-        self.GET = GET
-        self.POST = POST
-        self.FakePOST = FakePOST
-        
-        self.actual_http_method_id = 'POST'
-        self.desired_http_method_instance = HTTPMethod.plugins.id_to_instance[
-            self.actual_http_method_id
-        ]
-        
-        id_to_class = {
-            'GET': GET, 'POST': POST, 'Fake POST': FakePOST, 'PUT': PUT,
-            'DELETE': DELETE
-        }
-        self.desired_http_method_plugins = AttrDict(
-            classes=set(id_to_class.values()),
-            id_to_class=id_to_class,
-            class_to_id=dict((v, k) for k, v in id_to_class.items()),
-            classes_sorted_by_id=[v for _, v in sorted(id_to_class.items())],
-            valid_ids=set(id_to_class.keys()),
-        )
-    
-    def setup_http_response_plugins(self):
-        class HttpResponse(object):
-            """Mount point should not be registered as a plugin"""
-            status_code = None
-            __metaclass__ = PluginMount
-            class Meta:
-                id_field = 'status_code'
-            @classmethod
-            def _contribute_to_plugins(cls, _plugins):
-                _plugins.contribute_to_plugins_works = True
-        class OK(HttpResponse):
-            status_code = 200
-        class BaseRedirection(HttpResponse):
-            """Both parent and its child should be registered as plugins"""
-            pass
-        class MovedPermanently(BaseRedirection):
-            status_code = 301
-        class NotModified(BaseRedirection):
-            status_code = 304
-        class BadRequest(HttpResponse):
-            status_code = 400
-        class NotFound(HttpResponse):
-            status_code = 404
-        
-        self.HttpResponse = HttpResponse
-        
-        self.actual_http_response_class = MovedPermanently
-        self.desired_http_response_instance = HttpResponse.plugins.id_to_instance[
-            self.actual_http_response_class.status_code
-        ]
-        self.desired_http_response_class = type(self.desired_http_response_instance)
-    
-    def setup(self):
-        self.setup_http_method_plugins()
-        self.setup_http_response_plugins()
-    
-    def test_plugin_registration(self):
-        # Not comparing as sets to be able to confirm there are no duplicates
-        # in the plugin registry
-        assert_equal(
-            sorted(self.HTTPMethod._plugin_registry),
-            sorted(self.desired_http_method_plugins.classes)
-        )
-    
-    def test_plugin_info(self):
-        actual_plugins = self.HTTPMethod.plugins.copy()
-        
-        # Special case for any instances related information
-        assert_equal(
-            set(type(obj) for obj in actual_plugins.pop('instances')),
-            self.desired_http_method_plugins.classes
-        )
-        assert_equal(
-            dict((k, type(v)) for k, v in actual_plugins.pop('id_to_instance').items()),
-            self.desired_http_method_plugins.id_to_class
-        )
-        assert_equal(
-            [type(obj) for obj in actual_plugins.pop('instances_sorted_by_id')],
-            self.desired_http_method_plugins.pop('classes_sorted_by_id')
-        )
-        
-        # Compare the remaining plugin information directly
-        assert_equal(actual_plugins, self.desired_http_method_plugins)
-    
-    def test_contribute_to_plugins(self):
-        assert_is(self.HttpResponse.plugins.contribute_to_plugins_works, True)
-    
-    def test_distant_relatives_use_same_plugin_info_cache(self):
-        self.GET._plugins = None
-        self.POST._plugins = None
-        assert_is(self.GET.plugins, self.POST.plugins)
-    
-    def test_child_uses_same_plugin_info_cache_as_parent(self):
-        self.FakePOST._plugins = None
-        self.POST._plugins = None
-        assert_is(self.FakePOST.plugins, self.POST.plugins)
-    
-    def test_child_uses_same_plugin_info_cache_as_base_class(self):
-        self.FakePOST._plugins = None
-        self.HTTPMethod._plugins = None
-        assert_is(self.FakePOST.plugins, self.HTTPMethod.plugins)
-    
-    def test_parent_uses_same_plugin_info_cache_as_base_class(self):
-        self.POST._plugins = None
-        self.HTTPMethod._plugins = None
-        assert_is(self.POST.plugins, self.HTTPMethod.plugins)
-    
-    def test_plugins_not_shared_between_mount_points(self):
-        assert self.HTTPMethod.plugins.classes.isdisjoint(
-            self.HttpResponse.plugins.classes
-        )
-    
-    @issues_warnings(PerformanceWarning)
-    def test_coerce_valid_http_response_instance(self):
-        assert_is(
-            type(self.HttpResponse.coerce(self.actual_http_response_class())),
-            self.desired_http_response_class
-        )
-    
-    @issues_warnings(PerformanceWarning)
-    def test_coerce_valid_http_response_class(self):
-        assert_is(
-            type(self.HttpResponse.coerce(self.actual_http_response_class)),
-            self.desired_http_response_class
-        )
-    
-    def test_coerce_works_with_empty_plugin_info_cache(self):
-        self.HttpResponse._plugins = None
-        self.HTTPMethod.coerce(self.actual_http_method_id)
-    
-    def test_coerce_nonnumeric_id(self):
-        assert_is(
-            self.HTTPMethod.coerce(self.actual_http_method_id),
-            self.desired_http_method_instance
-        )
-    
-    def test_coerce_valid_http_response_id(self):
-        assert_is(
-            self.HttpResponse.coerce(self.actual_http_response_class.status_code),
-            self.desired_http_response_instance
-        )
-    
-    @raises(CoercionError)
-    def test_coerce_invalid_http_response_id(self):
-        self.HttpResponse.coerce(-1)
-    
-    @raises(CoercionError)
-    def test_coerce_unknown_type(self):
-        self.HttpResponse.coerce(object())