summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authoralalazo <massimiliano.culpo@googlemail.com>2016-01-01 17:35:01 +0100
committeralalazo <massimiliano.culpo@googlemail.com>2016-01-01 17:35:01 +0100
commitdcddb19e5b55c8c61279a084bdbaa95047236ccb (patch)
tree21139e3296650e1c11512d566d55294a1cda94aa /lib
parentd63cb8b537a9fc12a2a1ee5b22f2b20e19d90dc1 (diff)
downloadspack-dcddb19e5b55c8c61279a084bdbaa95047236ccb.tar.gz
spack-dcddb19e5b55c8c61279a084bdbaa95047236ccb.tar.bz2
spack-dcddb19e5b55c8c61279a084bdbaa95047236ccb.tar.xz
spack-dcddb19e5b55c8c61279a084bdbaa95047236ccb.zip
added class decorator to define composite classes
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/util/pattern.py98
1 files changed, 98 insertions, 0 deletions
diff --git a/lib/spack/spack/util/pattern.py b/lib/spack/spack/util/pattern.py
new file mode 100644
index 0000000000..8d1584b4c2
--- /dev/null
+++ b/lib/spack/spack/util/pattern.py
@@ -0,0 +1,98 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+import inspect
+import collections
+import functools
+
+
+def composite(interface=None, method_list=None, container=list):
+ """
+ Returns a class decorator that patches a class adding all the methods it needs to be a composite for a given
+ interface.
+
+ :param interface: class exposing the interface to which the composite object must conform. Only non-private and
+ non-special methods will be taken into account
+
+ :param method_list: names of methods that should be part of the composite
+
+ :param container: container for the composite object (default = list). Must fulfill the MutableSequence contract.
+ The composite class will expose the container API to manage object composition
+
+ :return: class decorator
+ """
+ # Check if container fulfills the MutableSequence contract and raise an exception if it doesn't
+ # The patched class returned by the decorator will inherit from the container class to expose the
+ # interface needed to manage objects composition
+ if not issubclass(container, collections.MutableSequence):
+ raise TypeError("Container must fulfill the MutableSequence contract")
+
+ # Check if at least one of the 'interface' or the 'method_list' arguments are defined
+ if interface is None and method_list is None:
+ raise TypeError("Either 'interface' or 'method_list' must be defined on a call to composite")
+
+ def cls_decorator(cls):
+ # Retrieve the base class of the composite. Inspect its the methods and decide which ones will be overridden
+ def no_special_no_private(x):
+ return inspect.ismethod(x) and not x.__name__.startswith('_')
+
+ # Patch the behavior of each of the methods in the previous list. This is done associating an instance of the
+ # descriptor below to any method that needs to be patched.
+ class IterateOver(object):
+ """
+ Decorator used to patch methods in a composite. It iterates over all the items in the instance containing the
+ associated attribute and calls for each of them an attribute with the same name
+ """
+ def __init__(self, name, func=None):
+ self.name = name
+ self.func = func
+
+ def __get__(self, instance, owner):
+ def getter(*args, **kwargs):
+ for item in instance:
+ getattr(item, self.name)(*args, **kwargs)
+ # If we are using this descriptor to wrap a method from an interface, then we must conditionally
+ # use the `functools.wraps` decorator to set the appropriate fields.
+ if self.func is not None:
+ getter = functools.wraps(self.func)(getter)
+ return getter
+
+ dictionary_for_type_call = {}
+ # Construct a dictionary with the methods explicitly passed as name
+ if method_list is not None:
+ method_list_dict = {name: IterateOver(name) for name in method_list}
+ dictionary_for_type_call.update(method_list_dict)
+ # Construct a dictionary with the methods inspected from the interface
+ if interface is not None:
+ interface_methods = {name: method for name, method in inspect.getmembers(interface, predicate=no_special_no_private)}
+ interface_methods_dict = {name: IterateOver(name, method) for name, method in interface_methods.iteritems()}
+ dictionary_for_type_call.update(interface_methods_dict)
+ # Get the methods that are defined in the scope of the composite class and override any previous definition
+ cls_method = {name: method for name, method in inspect.getmembers(cls, predicate=inspect.ismethod)}
+ dictionary_for_type_call.update(cls_method)
+ # Generate the new class on the fly and return it
+ wrapper_class = type(cls.__name__, (cls, container), dictionary_for_type_call)
+ return wrapper_class
+
+ return cls_decorator