Source

rpy2 / r.py

Full commit
class RPythonTypeError(Exception):
    pass

NO_DEFAULT = object()

class attribute(object):
    def __init__(self, typ, default_value=NO_DEFAULT):
        self.attribute_type = get_rpy_type(typ)
        if default_value is NO_DEFAULT:
            self.default_value = self.attribute_type.default_value
        else:
            self.default_value = default_value

    def _rpy_bind(self, cls, name):
        self.rpy_class = cls
        self.attribute_name = name

    def __get__(self, inst, cls):
        try:
            return inst.__dict__[self.attribute_name]
        except KeyError:
            return self.default_value

    def __set__(self, inst, value):
        self.attribute_type.type_check(value)
        inst.__dict__[self.attribute_name] = value

class immutable_attribute(attribute):
    def __set__(self, inst, value):
        if self.attribute_name in inst.__dict__:
            raise RPythonTypeError("cannot change immutable attribute %s of %s" % (self.attribute_name, inst))
        return attribute.__set__(self, inst, value)

class RPyType(type):
    def type_check(self, value):
        raise NotImplementedError("abstract base class")

class RPyPrimitiveType(RPyType):
    def __init__(self, name, bases, dct):
        assert len(bases) == 1
        self.typ = self.__base__
        self.default_value = self.typ()

    def type_check(self, value):
        if not isinstance(value, self.typ):
            raise RPythonTypeError

class RPyInt(int):
    __metaclass__ = RPyPrimitiveType

class RPyFloat(float):
    __metaclass__ = RPyPrimitiveType

class RPythonClass(RPyType):
    def __new__(cls, name, bases, dct):
        offending_bases = cls.check_bases(name, bases)
        if len(offending_bases) > 0:
            raise RPythonTypeError(
                "Class %s inherits from non-RPython bases clases: %r"
                    % (name, offending_bases,))
        offending_bases = cls.check_mixin_bases(name, bases)
        if len(offending_bases) > 0:
            raise RPythonTypeError(
                "Class %s inherits from more than one class (non mixin): %r"
                    % (name, offending_bases,))
        result = type.__new__(cls, name, bases, dct)
        for key, value in dct.iteritems():
            if isinstance(value, attribute):
                value._rpy_bind(result, key)
        result.default_value = None
        return result

    def type_check(self, value):
        if value is not None and not isinstance(value, self):
            raise RPythonTypeError

    @staticmethod
    def check_bases(name, bases):
        f = lambda x: not isinstance(x, RPyType) and name != 'RPyObject'
        return filter(f, bases)

    @staticmethod
    def check_mixin_bases(name, bases):
        def f(x):
            if x is bases[0] or name == 'RPyObject':
                return False
            return not (hasattr(x, '_mixin_') and x._mixin_)
        return filter(f, bases)


class RPyObject(object):
    __metaclass__ = RPythonClass


class RPyTuple(RPyType):
    _cache = {}

    def type_check(self, value):
        if not isinstance(value, tuple) or len(value) != len(self.element_types):
            raise RPythonTypeError
        for val, typ in zip(value, self.element_types):
            typ.type_check(val)

    @staticmethod
    def make_tuple_type(types):
        key = tuple(types)
        res = RPyTuple._cache.get(key)
        if not res:
            res = RPyTuple._cache[key] = RPyTuple(str(key), (tuple, ), {"element_types": types})
        return res


def get_rpy_type(obj):
    if isinstance(obj, RPyType):
        return obj
    if obj is int:
        return RPyInt
    if obj is float:
        return RPyFloat
    if isinstance(obj, tuple):
        return RPyTuple.make_tuple_type([get_rpy_type(t) for t in obj])
    assert 0, "unknown type %s" % obj