#!/usr/bin/env python3.0

"""Define a structure using a class.

Structures have some properties that normal classes don't:

(a) instance attributes are typed
(b) only pre-defined instance attributes are allowed
(c) instance variables have a well-defined order (i.e., declaration order)

Example:

    class C(Structure):

        '''A class with two fields, an int and a string.'''

        a = Field(int)
        b = Field(str)

    x = C()
    x.a = 42
    x.b = "hello"
    print(x.a)  # 42
    print(x.b)  # hello

The following assignments are now all disallowed:

    x.a = "hello"
    x.b = 42
    x.c = 0

"""

class _FieldDict(dict):

    __slots__ = ["_order"]

    def __init__(self, initial=()):
        if hasattr(initial, "keys"):
            initial = [(key, initial[key]) for key in initial.keys()]
        self._order = list(initial)
        dict.__init__(self, initial)

    def __setitem__(self, key, value):
        self._order.append((key, value))
        dict.__setitem__(self, key, value)

class Field:

    def __init__(self, type=object):
        self._type = type
        self._get = None
        self._set = None
        self._delete = None
        self._name = "?"

    def __get__(self, obj, cls=None):
        return self._get(obj, cls)

    def __set__(self, obj, value):
        if not isinstance(value, self._type):
            raise TypeError("can't set %s to %.100r; %s required" %
                            (self._name, value, self._type.__name__))
        self._set(obj, value)

    def __delete__(self, obj):
        self._delete(obj)

class _StructureClass(type):

    @classmethod
    def __prepare__(mcls, *args):
        return _FieldDict()

    def __new__(mcls, classname, bases, namespace):
        if "__slots__" in namespace:
            raise TypeError("You can't define __slots__ in a Structure")
        slots = []
        fields = {}
        for name, value in namespace._order:
            if isinstance(value, Field):
                if namespace.get(name) is value and name not in fields:
                    value._name = name
                    slots.append(name)
                    fields[name] = value
                    del namespace[name]
        namespace = dict(namespace)
        namespace["__slots__"] = slots
        cls = type.__new__(mcls, classname, bases, namespace)
        for name in slots:
            field = fields[name]
            descr = getattr(cls, name)
            field._get = descr.__get__
            field._set = descr.__set__
            field._delete = descr.__delete__
            setattr(cls, name, field)
        return cls

class Structure(metaclass=_StructureClass):

    pass

# Example

class C(Structure):

    a = Field(int)
    b = Field(str)

    def f(self):
        print(self.a, self.b)

x = C()
x.a = 42
x.b = "hello"
assert not hasattr(x, "__dict__")
print(x.__slots__)
x.f()
x.a = "hello"
