9b25718
# function_class.py -- Library for making python classes from a set
9b25718
#                      of functions. 
9b25718
#
9b25718
# Copyright (C) 2008 ParIT Worker Co-operative <paritinfo@parit.ca>
9b25718
# This program is free software; you can redistribute it and/or
9b25718
# modify it under the terms of the GNU General Public License as
9b25718
# published by the Free Software Foundation; either version 2 of
9b25718
# the License, or (at your option) any later version.
9b25718
#
9b25718
# This program is distributed in the hope that it will be useful,
9b25718
# but WITHOUT ANY WARRANTY; without even the implied warranty of
9b25718
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9b25718
# GNU General Public License for more details.
9b25718
#
9b25718
# You should have received a copy of the GNU General Public License
9b25718
# along with this program; if not, contact:
9b25718
# Free Software Foundation           Voice:  +1-617-542-5942
9b25718
# 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652
9b25718
# Boston, MA  02110-1301,  USA       gnu@gnu.org
9b25718
#
9b25718
# @author Mark Jenkins, ParIT Worker Co-operative <mark@parit.ca>
9b25718
9b25718
##  @file
9b25718
#   @brief Library for making python classes from a set of functions.
9b25718
#   @author Mark Jenkins, ParIT Worker Co-operative <mark@parit.ca>
9b25718
#   @author Jeff Green,   ParIT Worker Co-operative <jeff@parit.ca>
9b25718
#   @ingroup python_bindings
9b25718
9b25718
INSTANCE_ARGUMENT = "instance"
9b25718
9b25718
class ClassFromFunctions(object):
9b25718
    """Inherit this class to give yourself a python class that wraps a set of
9b25718
    functions that together constitute the methods of the class.
9b25718
9b25718
    The method functions must all have as a first argument an object
9b25718
    holding the instance data. There must also be a function that
9b25718
    returns a new instance of the class, the constructor.
9b25718
9b25718
    Your subclass must define
9b25718
    _module - The module where the method functions, including the
9b25718
    constructor can be found
9b25718
    _new_instance - The name of a function that serves as a constructor,
9b25718
    returning the instance data.
9b25718
9b25718
    To access the instance data, use the read-only property instance.
9b25718
9b25718
    To add some functions from _module as methods, call classmethods like
9b25718
    add_method and add_methods_with_prefix.
9b25718
    """
9b25718
    def __new__(cls, *args, **kargs):
9b25718
        # why reimpliment __new__? Because later on we're going to
9b25718
        # use new to avoid creating new instances when existing instances
9b25718
        # already exist with the same __instance value, or equivalent __instance
9b25718
        # values, where this is desirable...
9b25718
        return super(ClassFromFunctions, cls).__new__(cls)
9b25718
    
9b25718
    def __init__(self, *args, **kargs):
9b25718
        """Construct a new instance, using either the function
9b25718
        self._module[self._new_instance] or using existing instance
9b25718
        data. (specified with the keyword argument, instance)
9b25718
9b25718
        Pass the arguments that should be passed on to
9b25718
        self._module[self._new_instance] . Any arguments of that
9b25718
        are instances of ClassFromFunctions will be switched with the instance
9b25718
        data. (by calling the .instance property)
9b25718
        """
9b25718
        if INSTANCE_ARGUMENT in kargs:
9b25718
            self.__instance = kargs[INSTANCE_ARGUMENT]
9b25718
        else:
9b25718
            self.__instance = getattr(self._module, self._new_instance)(
9b25718
                *process_list_convert_to_instance(args) )
9b25718
9b25718
    def get_instance(self):
9b25718
        """Get the instance data.
9b25718
9b25718
        You can also call the instance property
9b25718
        """
9b25718
        return self.__instance
9b25718
9b25718
    instance = property(get_instance)
9b25718
9b25718
    # CLASS METHODS
9b25718
9b25718
    @classmethod
9b25718
    def add_method(cls, function_name, method_name):
9b25718
        """Add the function, method_name to this class as a method named name
9b25718
        """
9b25718
        def method_function(self, *meth_func_args):
9b25718
            return getattr(self._module, function_name)(
9b25718
                self.instance,
9b25718
                *process_list_convert_to_instance(meth_func_args) )
9b25718
        
9b25718
        setattr(cls, method_name, method_function)
9b25718
        setattr(method_function, "__name__", method_name)
9b25718
        return method_function
9b25718
9b25718
    @classmethod
9b25718
    def ya_add_classmethod(cls, function_name, method_name):
9b25718
        """Add the function, method_name to this class as a classmethod named name
9b25718
        
9b25718
        Taken from function_class and slightly modified.
9b25718
        """
9b25718
        def method_function(self, *meth_func_args):
9b25718
            return getattr(self._module, function_name)(
9b25718
                self,
9b25718
                *process_list_convert_to_instance(meth_func_args) )
9b25718
        
9b25718
        setattr(cls, method_name, classmethod(method_function))
9b25718
        setattr(method_function, "__name__", method_name)
9b25718
        return method_function    
9b25718
9b25718
    @classmethod
9b25718
    def ya_add_method(cls, function_name, method_name):
9b25718
        """Add the function, method_name to this class as a method named name
9b25718
        
9b25718
        Taken from function_class and slightly modified.
9b25718
        """
9b25718
        def method_function(self, *meth_func_args):
9b25718
            return getattr(self._module, function_name)(
9b25718
                self,
9b25718
                *process_list_convert_to_instance(meth_func_args) )
9b25718
        
9b25718
        setattr(cls, method_name, method_function)
9b25718
        setattr(method_function, "__name__", method_name)
9b25718
        return method_function
9b25718
9b25718
    @classmethod
9b25718
    def add_methods_with_prefix(cls, prefix):
9b25718
        """Add a group of functions with the same prefix 
9b25718
        """
9b25718
        for function_name, function_value, after_prefix in \
9b25718
            extract_attributes_with_prefix(cls._module, prefix):
9b25718
                cls.add_method(function_name, after_prefix)
9b25718
    
9b25718
    @classmethod
9b25718
    def add_constructor_and_methods_with_prefix(cls, prefix, constructor):
9b25718
        """Add a group of functions with the same prefix, and set the
9b25718
        _new_instance attribute to prefix + constructor
9b25718
        """
9b25718
        cls.add_methods_with_prefix(prefix)
9b25718
        cls._new_instance = prefix + constructor
9b25718
9b25718
    @classmethod
9b25718
    def decorate_functions(cls, decorator, *args):
9b25718
        for function_name in args:
9b25718
            setattr( cls, function_name,
9b25718
                     decorator( getattr(cls, function_name) ) )
9b25718
9b25718
def method_function_returns_instance(method_function, cls):
9b25718
    """A function decorator that is used to decorate method functions that
9b25718
    return instance data, to return instances instead.
9b25718
9b25718
    You can't use this decorator with @, because this function has a second
9b25718
    argument.
9b25718
    """
9b25718
    assert( 'instance' == INSTANCE_ARGUMENT )
9b25718
    def new_function(*args):
9b25718
        kargs = { INSTANCE_ARGUMENT : method_function(*args) }
9b25718
        if kargs['instance'] == None:
9b25718
            return None
9b25718
        else:
9b25718
            return cls( **kargs )
9b25718
    
9b25718
    return new_function
9b25718
9b25718
def method_function_returns_instance_list(method_function, cls):
9b25718
    def new_function(*args):
9b25718
        return [ cls( **{INSTANCE_ARGUMENT: item} )
9b25718
                 for item in method_function(*args) ]
9b25718
    return new_function
9b25718
9b25718
def methods_return_instance_lists(cls, function_dict):
9b25718
    for func_name, instance_name in function_dict.iteritems():
9b25718
        setattr(cls, func_name,
9b25718
                method_function_returns_instance_list(
9b25718
                getattr(cls, func_name), instance_name))
9b25718
9b25718
def default_arguments_decorator(function, *args):
9b25718
    """Decorates a function to give it default, positional arguments
9b25718
9b25718
    You can't use this decorator with @, because this function has more
9b25718
    than one argument.
9b25718
    """
9b25718
    def new_function(*function_args):
9b25718
        new_argset = list(function_args)
9b25718
        new_argset.extend( args[ len(function_args): ] )
9b25718
        return function( *new_argset )
9b25718
    return new_function
9b25718
    
9b25718
def return_instance_if_value_has_it(value):
9b25718
    """Return value.instance if value is an instance of ClassFromFunctions,
9b25718
    else return value
9b25718
    """
9b25718
    if isinstance(value, ClassFromFunctions):
9b25718
        return value.instance
9b25718
    else:
9b25718
        return value
9b25718
9b25718
def process_list_convert_to_instance( value_list ):
9b25718
    """Return a list built from value_list, where if a value is in an instance
9b25718
    of ClassFromFunctions, we put value.instance in the list instead.
9b25718
9b25718
    Things that are not instances of ClassFromFunctions are returned to
9b25718
    the new list unchanged.
9b25718
    """
9b25718
    return [ return_instance_if_value_has_it(value)
9b25718
             for value in value_list ]
9b25718
9b25718
def extract_attributes_with_prefix(obj, prefix):
9b25718
    """Generator that iterates through the attributes of an object and
9b25718
    for any attribute that matches a prefix, this yields
9b25718
    the attribute name, the attribute value, and the text that appears
9b25718
    after the prefix in the name
9b25718
    """
9b25718
    for attr_name, attr_value in obj.__dict__.iteritems():
9b25718
        if attr_name.startswith(prefix):
9b25718
            after_prefix = attr_name[ len(prefix): ]
9b25718
            yield attr_name, attr_value, after_prefix
9b25718
9b25718
def methods_return_instance(cls, function_dict):
9b25718
    """Iterates through a dictionary of function name strings and instance names
9b25718
    and sets the function to return the associated instance 
9b25718
    """
9b25718
    for func_name, instance_name in function_dict.iteritems():
9b25718
        setattr(cls, func_name, 
9b25718
            method_function_returns_instance( getattr(cls, func_name), instance_name))
9b25718