
from types import FunctionType
import os

def automethod(func):
    """Return func unchanged, wrapped in a staticmethod or in a classmethod,
    depending on what it seems to be (from parsing argument names)"""
    if not func.func_code.co_argcount:
        return staticmethod(func)
    if func.func_code.co_varnames[0] == 'cls':
        return classmethod(func)
    if func.func_code.co_varnames[0] == 'self':
        return func
    return staticmethod(func)

class cachingprop(object):
    __slots__ = ["_name", "_fget"]
    """
    A read-only property() that caches the value on the instance
    Only supports the 'getter' function, not setter/delete and __doc__,
    and doesn't work on objects without a __dict__.
    """
    def __init__(self, name, fget):
        self._name = name
        self._fget = fget
    def __get__(self, inst, type=None):
        if inst is None:
            return self
        v = self._fget(inst)
        inst.__dict__[self._name] = v
        return v

class conveniencyTypeType(type):
    """
    Conveniency metaclass: save typing by automatically creating classmethods
    and properties, triggered by the naming scheme.

    For each method (Python function) in the newly created class:
    - if it has no arguments, convert it to a staticmethod
    - if the first argument is named 'cls', convert it to a classmethod
    - otherwise, if the first argument is not 'self', it's a staticmethod

    - if the name matches _cacheget_*, * is the name of a
      'cachingproperty', and the result of the _cacheget_ function is
       automatically placed in the instance's __dict__.
    - if the name matches _get_*, _set_*, _del_* or _doc_*, * is the
      name of a regular property. Inheritance is obeyed for these
      attributes; you can meaningfully 'extend' an existing property by
      overriding some of these functions.

    Also, create an 'unbound super object' as class.__super.
    
    """
    def __init__(self, name, bases, attrs):
        super(conveniencyTypeType, self).__init__(name, bases, attrs)

        # Create an 'unbound super object'
        supername = "_%s__super" % name
        if hasattr(self, supername):
            raise TypeError, "Conflicting classname " + supername
        setattr(self, supername, super(self))

        props = {}
        # Process all attributes
        for attr, value in attrs.items():
            if attr.startswith("__") and attr.endswith("__"):
                continue
            if isinstance(value, FunctionType):
                # Auto-convert methods
                setattr(self, attr, automethod(value))

            # Find and wrap "caching properties"
            if attr.startswith("_cacheget_"):
                if not hasattr(self, "__dict__"):
                    raise TypeError, "Can't use _cacheget_ on " \
                          "objects without __dict__"
                propname = attr[10:]
                setattr(self, propname, cachingprop(propname, value))
            if attr[:10] in ("_cacheset_", "_cachedelete_", "_cachedoc_"):
                raise TypeError, "_cacheset_, _cachedelete_ and _cachedoc_" \
                      " are not supported"

            # Collect normal properties...
            if attr[:5] in ("_get_", "_set_", "_del_", "_doc_"):
                props[attr[5:]] = 1

        # ... and fetch them using getattr() (for inheritance)
        for name in props.keys():
            fget = getattr(self, "_get_" + name, None)
            fset = getattr(self, "_set_" + name, None)
            fdel = getattr(self, "_del_" + name, None)
            fdoc = getattr(self, "_doc_" + name, None)
            setattr(self, name, property(fget, fset, fdel, fdoc))

        
class conveniencyType(object):
    __slots__ = []
    __metaclass__ = conveniencyTypeType

class automethodTest(conveniencyType):
    def spam(self, *args):
        return self, args
    def ham(cls, *args):
        return cls, args
    def eggs(*args):
        return args

class Bag(conveniencyType):
    def __init__(self, dirname):
        self._dirname = dirname
    def _cacheget_spam(self):
        return file(os.path.join(self._dirname, "spam.txt")).read()
    def _cacheget_ham(self):
        print "Reaching in bag for ham"
        return file(os.path.join(self._dirname, "ham.txt")).read()

class mydict(dict):
    __metaclass__ = conveniencyTypeType
    def __setitem__(self, item, value):
        print "Setting item", "to", value
        return self.__super.__setitem__(item, value)

class tristate(int):
    # Specify an (empty) __slots__ so instances don't get a __dict__
    # -- just like regular ints, they won't have arbitrary attributes.
    __slots__ = []
    __metaclass__ = conveniencyTypeType
    nstates = 3

    # __new__ is the method to override for immutable values.
    # It is a static method. The first argument is the class that is being
    # created (which may be a subclass), and there is no actual instance
    # yet.
    def __new__(cls, state=0):
        # We want to limit the actual value of the type to [0, nstates >
        state %= cls.nstates

        # To actually create the new instance, we call the parent's __new__. 
        # Since __new__ is a static method, we have to pass the first
        # argument explicitly.

        # To call the right parent method, we use super(). What super()
        # does, is call the method that _would have been called_ if this
        # method wasn't present. In single-inheritance situations this just
        # means 'int.__new__' in our case, but if a subclass of our class
        # actually inherits from more than just us, it may end up being
        # another method altogether. Using super() in all places assures
        # consistent method-call order.
        
        # super() can be used in several ways, and the actual return value
        # is a magic object, a proxy object that does the right thing with
        # bound and unbound instance methods, and class methods.
        # Since we only have a class, we 

        return super(tristate, cls).__new__(cls, state)

    def __add__(self, other):
        return tristate(self.__super.__add__(other))
    def __sub__(self, other):
        return self.__add__(-other)

class mylist(list):
    __metaclass__ = conveniencyTypeType
    def __getitem__(self, i):
      try:
        return self.__super.__getitem__(i)
      except IndexError:
        return None

