added class decorator to define composite classes
This commit is contained in:
parent
d63cb8b537
commit
dcddb19e5b
98
lib/spack/spack/util/pattern.py
Normal file
98
lib/spack/spack/util/pattern.py
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user