Commits

pfw  committed c328538

add spec and keep files from qp

  • Participants
  • Parent commits f3f7759

Comments (0)

Files changed (3)

File flaskext/durus.py

 
 from UserDict import IterableUserDict
 
+from .spec import either, spec, nspec, init, length, add_getters_and_setters
+from .spec import require, get_spec_problems, specify, identifier_pattern, both
+from .spec import datetime_with_tz, sequence, instance, mapping, match, get_specs
+from .keep import Keyed, Stamped, Keep, Counter
+
 from flask import _request_ctx_stack, redirect, url_for
 
+from datetime import datetime
+import pytz
+epoch = datetime(1970,1,1, tzinfo=pytz.utc)
+
+def with_getters_and_setters(klass):
+    """
+    Add getters and setters to the persistent class
+    """
+    add_getters_and_setters(klass)
+    return klass
+    
+
 class Durus(IterableUserDict):
     connections = []
     
     def commit(self):
         ctx = _request_ctx_stack.top
         if ctx is not None:
-            return self._get_db(ctx).commit()
+            # walk list of changed items
+            db = self._get_db(ctx) 
+            for changed in db.changed.itervalues():
+                print "changed:", changed
+            return db.commit()
 
     @property
     def connection(self):
                 start, end, closed_start=closed_start, closed_end=closed_end, 
                 limit=limit)
 
-
 def browsedb_on_connection(db, app, url_prefix):
     """(db:Durus, app:Flask, url_prefix:str)
     Add a route for a db browser to the app at url_prefix.
         return """<html><head><title>%s</title></head><body><div>%s</div><div>%s</div></body></html>""" % (repr(value).replace('<', '&lt;').replace('>','&gt;'), repr(value).replace('<', '&lt;').replace('>','&gt;'), format(persistent_vars(value)))
 
 
+class IndexedKeep(Keep):
+    """
+    A Keep that maintains indexes for quick access to the values.
+    """
+    
+    def __init__(self, value_spec=Keyed, mapping_class=BTree, counter_class=Counter):
+        Keep.__init__(self, value_spec, mapping_class, counter_class)
+        self._indexes = PersistentDict()
+        self.create_indexes()
 
+    def add(self, value):
+        Keep.add(self, value)
+        # update indexes if defined.
+        value._indexes = PeristentDict()
+
+    def create_indexes(self):
+        """()
+        Create all indexes bases on callables on the value_spec class that start with 'key_for'
+        """
+        for attr in self.value_spec.__dict__.keys():
+            if attr.startswith('key_for_') and callable(getattr(self.value_spec, attr)):
+                # index key
+                print attr
+                self._indexes.setdefault(attr.split('key_for_')[1], BTree())
+
+    def update_indexes(self):
+        """()
+        Rebuild all indexes defined on this Keep.
+        """
+        for name, index in self._indexes.items():
+            print "update index:", name
+            index.clear()
+            for item in self.mapping.itervalues():
+                key = getattr(item, "key_for_%s" % name)()
+                index[(key, item.key)] = item
+                if not hasattr(item, '_indexes'):
+                    item._indexes = PersistentDict()
+                item._indexes[name] = (key, item.key)
+
+
+
+
+

File flaskext/keep.py

+"""
+$URL: svn+ssh://sting.mems-exchange.org/repos/trunk/DurusWorks/qp/lib/keep.py $
+$Id: keep.py 32474 2010-06-09 15:09:26Z dbinger $
+"""
+from __future__ import absolute_import
+
+from durus.btree import BTree
+from durus.persistent import PersistentObject
+from durus.persistent_dict import PersistentDict
+
+from .spec import either, datetime_with_tz, mapping, spec, integer
+from .spec import require, get_spec_problems, anything, specify
+
+import pytz
+from datetime import datetime
+
+def rand_int(bits):
+    bytes, remainder = divmod(bits, 8)
+    if remainder == 0:
+        return int(randbytes(bytes), 16)
+    else:
+        return int(randbytes(bytes + 1), 16) >> (8 - remainder)
+
+class Keyed (object):
+    """
+    An item with an int key used as a key in an Keep.
+    Note that the key attribute is set when the item is put in the Keep,
+    so there is no set_key() method.
+    This is a mixin for PersistentObject classes.
+    """
+    key_is = integer
+
+    __slots__ = []
+
+    def __init__(self):
+        assert isinstance(self, PersistentObject)
+        self.key = None
+
+    def get_key(self):
+        return self.key
+
+
+class KeyCounter (PersistentObject):
+    """
+    The base class.  Generates keys using random 64 bit ints.
+    """
+    def next(self):
+        """() -> integer
+        This base implementation just uses a big random number.
+        """
+        return rand_int(64)
+
+
+class Counter (KeyCounter):
+    """
+    Generates keys by incrementing them.
+    """
+    next_available_is = integer
+
+    __slots__ = ['next_available']
+
+    def __init__(self):
+        self.next_available = 1
+
+    def next(self):
+        result = self.next_available
+        self.next_available = self.next_available + 1
+        return result
+
+
+class GrowingRandomCounter (KeyCounter):
+    """
+    Keep a key counter that generates keys randomly, and gradually
+    expands the numbers of bits in the random numbers.
+    The idea is to take advantage of randomly distributed keys, but
+    to try to keep the numbers relatively small.
+    """
+    bits_is = integer
+    used_is = integer
+
+    __slots__ = ['bits', 'used']
+
+    def __init__(self):
+        specify(self, bits=10, used=0)
+
+    def next(self):
+        self.used = self.used + 1
+        # If more than 1% of the possible values have been used,
+        # double the space of possible values.
+        if (self.used / (2 ** self.bits)) > 0.01:
+            self.bits += 1
+        return rand_int(self.bits)
+
+
+class Keep (object):
+    """
+    A simple database that stores a mapping of objects by key.
+    This is a mixin for PersistentObject classes.
+    """
+    value_spec_is = spec(
+        anything,
+        "Specifies the type of values allowed in mapping")
+    mapping_is = mapping({integer:Keyed}, either(PersistentDict, BTree))
+    key_counter_is = KeyCounter
+    __slots__ = []
+
+    def __init__(self, value_spec=Keyed, mapping_class=BTree, counter_class=Counter):
+        assert isinstance(self, PersistentObject)
+        self.mapping = mapping_class()
+        self.value_spec = value_spec
+        self.set_counter(counter_class())
+
+    def set_counter(self, counter):
+        specify(self, key_counter=counter)
+
+    def __len__(self):
+        return len(self.mapping)
+
+    def get_mapping(self):
+        return self.mapping
+
+    def get(self, key, default=None):
+        return self.mapping.get(key, default)
+
+    def itervalues(self):
+        for value in self.mapping.itervalues():
+            yield value
+
+    def iterkeys(self):
+        for key in self.mapping.iterkeys():
+            yield key
+
+    def iteritems(self):
+        for item in self.mapping.iteritems():
+            yield item
+
+    def add(self, value):
+        require(value, self.value_spec)
+        assert value.key is None
+        for attempt in range(1000):
+            value.key = self.key_counter.next()
+            if value.key not in self.mapping:
+                break
+        else:
+            raise KeyError('Unable to find unused key for %r.' % value)
+        if get_spec_problems(value):
+            raise TypeError(''.join(get_spec_problems(value)))
+        self.mapping[value.key] = value
+
+    def remove(self, value):
+        require(value, self.value_spec)
+        assert value.key is self.mapping[value.key].key
+        del self.mapping[value.key]
+
+
+class Stamped (object):
+    """
+    Provides a timestamp.
+    This is a mixin for PersistentObject classes.  
+    """
+    stamp_is = datetime_with_tz
+
+    __slots__ = []
+
+    def __init__(self):
+        assert isinstance(self, PersistentObject)
+        self.set_stamp()
+
+    def get_stamp(self):
+        return self.stamp
+
+    def set_stamp(self):
+        self.stamp = datetime.now(pytz.utc)
+
+
+def stamp_sorted(stamped_sequence):
+    items = [(x.stamp, x) for x in stamped_sequence]
+    return [x for (stamp, x) in sorted(items)]
+
+def reverse_stamp_sorted(stamped_sequence):
+    result = stamp_sorted(stamped_sequence)
+    result.reverse()
+    return result
+

File flaskext/spec.py

+"""
+$URL: svn+ssh://sting.mems-exchange.org/repos/trunk/DurusWorks/qp/lib/spec.py $
+$Id: spec.py 33984 2011-06-22 18:51:03Z rmasse $
+
+This module provides tools for matching values to specs.
+
+In particular, it provides the functions
+  match(),
+  require()
+
+This module also provides tools for constructing specs,
+using them to specify and validate instance attributes.
+"""
+
+import re
+from datetime import datetime
+from types import FunctionType, MethodType
+import sys
+
+if sys.version < "3":
+    from __builtin__ import unicode, long
+    unicode_string = unicode
+    byte_string = str
+    number_classes = (int, long, float)
+else:
+    from builtins import bytes
+    unicode_string = str
+    byte_string = bytes
+    number_classes = (int, float)
+
+string_classes = (unicode_string, byte_string)
+
+def format_spec(spec):
+    """
+    Returns the canonical string representation of the spec.
+    """
+    if spec is None:
+        return 'None'
+    if type(spec) is tuple:
+        return '(%s)' % ', '.join(map(format_spec, spec))
+    if type(spec) is list:
+        return '[%s]' % ', '.join(map(format_spec, spec))
+    if type(spec) is dict:
+        return ('{%s}' %
+                ', '.join(["%s: %s" % (format_spec(key_type),
+                                       format_spec(val_type)) #nocover
+                           for key_type, val_type in spec.items()]))
+    if hasattr(spec, '__name__'):
+        return spec.__name__
+    if isinstance(spec, string_classes):
+        return repr(spec)
+    return str(spec)
+
+class SpecOperator (object):
+    """
+    The superclass of all of the spec operators.
+    Constructors of subclasses should assign to self.args
+    a tuple of the arguments to the constructor.
+    """
+
+    def get_args(self):
+        return self.args
+
+    def format_args(self):
+        return ', '.join(map(format_spec, self.get_args()))
+
+    def __str__(self):
+        return "%s(%s)" % (self.__class__.__name__, self.format_args())
+
+    def explain_difference(self, value):
+        return format_expected_got(self, value)
+
+def match(value, spec):
+    """
+    Return True or False depending on whether or not value matches
+    the given type specification.  Here are the available type
+    specifications:
+
+    Type Spec                Matches
+
+    a type or class         any instance of the type
+
+    a callable              anything for which <spec>(value) is true,
+                            so you can do arbitrary checking.
+
+    a list (or tuple)       any list (or tuple) whose elements all match the
+    with length 1           enclosed type specification.
+
+    a list (or tuple)       any list (tuple) whose sequence of elements match
+    with length > 1,        the sequence of corresponding type specifications,
+    not containing None,
+    or any string or numeric
+    or bool literal.
+
+    a tuple with            any value that matches any of the items in the tuple.
+    length > 1
+    containing None,
+    or any string or
+    numeric
+    literal.
+
+    a set                   any set whose elements match any of the elements in
+                            the type specification.
+
+    a dict                  any dict for which every item matches some item in
+                            this dictionary.
+
+    other                   any value for which value == other.
+
+    """
+    if isinstance(spec, (FunctionType, SpecOperator, Specification)):
+        return spec(value)
+    spec_type = type(spec)
+    if spec_type is tuple and len(spec) > 1:
+        # Possible disjunction.
+        for element in spec:
+            if (element is None or
+                type(element) in (float, int, bool) or
+                isinstance(element, string_classes)):
+                # Definite disjunction.
+                for element in spec:
+                    if match(value, element):
+                        return True
+                return False
+    if spec_type in (list, tuple):
+        if type(value) is not spec_type:
+            return False
+        if len(spec) == 1:
+            element_type = spec[0]
+            for element in value:
+                if not match(element, element_type):
+                    return False
+            return True
+        else:
+            if len(value) != len(spec):
+                return False
+            for element, element_type in zip(value, spec):
+                if not match(element, element_type):
+                    return False
+            return True
+    if spec_type is dict:
+        if type(value) is not dict:
+            return False
+        for key, val in value.items():
+            for key_type, value_type in spec.items():
+                if (match(key, key_type) and
+                    match(val, value_type)):
+                    break
+            else:
+                return False
+        return True
+    if spec_type is set:
+        for element in value:
+            for element_type in spec:
+                if match(element, element_type):
+                    break
+            else:
+                return False
+        return True
+    try:
+        return isinstance(value, spec)
+    except TypeError:
+        pass
+    if spec_type is type(value):
+        return value == spec
+    if hasattr(spec, '__call__'):
+        return spec(value)
+    try:
+        return value == spec
+    except (TypeError, ValueError, AssertionError):
+        pass
+    return False
+
+def format_expected_got(spec, value):
+    return ('\n  Expected: %s\n'
+            '  Got: %r\n') % (format_spec(spec), value)
+
+def require(value, spec, message=None):
+    if not match(value, spec):
+        if isinstance(spec, SpecOperator):
+            error = spec.explain_difference(value)
+        else:
+            error = format_expected_got(spec, value)
+        if message:
+            error = '(%s)%s' % (message, error)
+        raise TypeError(error)
+
+class Anything (object):
+    """
+    The universal spec.
+    """
+    def __str__(self):
+        return "anything"
+
+    def __call__(self, value):
+        return True
+
+anything = Anything()
+
+class sequence (SpecOperator):
+
+    """
+    Use this to specify a sequence.
+    """
+
+    def __init__(self, element_spec=anything, container_spec=anything):
+        self.container_spec = container_spec
+        self.element_spec = element_spec
+        self.args = (element_spec, container_spec)
+
+    def __call__(self, value):
+        if not match(value, self.container_spec):
+            return False
+        element_spec = self.element_spec
+        for element in value:
+            if not match(element, element_spec):
+                return False
+        return True
+
+
+class mapping (SpecOperator):
+
+    """
+    Use this to specify a mapping.
+    """
+
+    def __init__(self, dict_spec=anything, container_spec=anything):
+        self.dict_spec = dict_spec
+        self.container_spec = container_spec
+        self.args =  (self.dict_spec, container_spec)
+
+    def __call__(self, value):
+        if not match(value, self.container_spec):
+            return False
+        try:
+            dict_value = dict(value.items())
+        except AttributeError:
+            return False
+        if not match(dict_value, self.dict_spec):
+            return False
+        return True
+
+class subclass (SpecOperator):
+
+    """
+    Use this to specify a subclass of a given class.
+    """
+
+    def __init__(self, klass):
+        self.klass = klass
+        self.args = (klass,)
+
+    def __call__(self, value):
+        try:
+            return issubclass(value, self.klass)
+        except TypeError:
+            return False
+
+
+class instance (SpecOperator):
+
+    """
+    Use this to specify an instance of a class with a given name
+    when the class itself is not available.
+    """
+
+    def __init__(self, klass_name):
+        self.klass_name = klass_name
+        self.args = (klass_name,)
+
+    def __call__(self, value):
+        if hasattr(value, '__class__'):
+            classes = [value.__class__]
+            while classes:
+                if classes[0].__name__ == self.klass_name:
+                    return True
+                classes = classes[1:] + list(classes[0].__bases__)
+        return False
+
+
+class ConnectiveSpecOperator (SpecOperator):
+
+    def __init__(self, *specs):
+        self.specs = specs
+        self.args = self.specs
+
+
+class no (ConnectiveSpecOperator):
+
+    """
+    Use this to specify a negation of specs.
+    Example: no(None)
+    """
+
+    def __call__(self, value):
+        for spec in self.specs:
+            if match(value, spec):
+                return False
+        return True
+
+
+class both (ConnectiveSpecOperator):
+
+    """
+    Use this to specify a conjunction of specs.
+    Example:
+      both([int], length(3)) specifies values that are lists of 3 ints.
+    """
+
+    def __call__(self, value):
+        for spec in self.specs:
+            if not match(value, spec):
+                return False
+        return True
+
+    def explain_difference(self, value):
+        error = format_expected_got(self, value)
+        for spec in self.specs:
+            if not match(value, spec):
+                error += "  (which does not match %s)" % format_spec(spec)
+                break
+        return error
+
+class either (ConnectiveSpecOperator):
+
+    """
+    Use this to specify a disjunction of specs.
+    Examples:
+      either(int, str) specifies values that are ints or strs.
+    """
+
+    def __call__(self, value):
+        for spec in self.specs:
+            if match(value, spec):
+                return True
+        return False
+
+
+class proper (both):
+
+    """
+    Use this to specify an instance with attributes that agree with the
+    attribute specifications for this class.
+    If arguments are provided, they are additional specs which the instance
+    must satisfy.
+    """
+
+    def __call__(self, value):
+        return both.__call__(self, value) and get_spec_problems(value) == []
+
+
+class interval (SpecOperator):
+
+    def __init__(self, min=anything, max=anything):
+        self.min = min
+        self.max = max
+
+    def format_args(self):
+        if self.max is anything:
+            if self.min is anything:
+                return ''
+            return format_spec(self.min)
+        else:
+            return '%s, %s' % (format_spec(self.min), format_spec(self.max))
+
+    def __call__(self, value):
+        if self.min not  in (None, anything):
+            if self.max is anything:
+                return value >= self.min
+            elif value < self.min:
+                return False
+        if self.max not  in (None, anything):
+            if self.min is anything:
+                return value <= self.max
+            elif value > self.max:
+                return False
+        return True
+
+
+class length (interval):
+
+    """
+    Use this to specify specs by ranges of length.
+    Examples:
+      length(3, None) specifies values of length 3 or more.
+      length(3) specifies values of length 3.
+    """
+
+    def __call__(self, value):
+        return interval.__call__(self, len(value))
+
+
+class eq (SpecOperator):
+
+    """
+    Use this to specify specs exact value.
+    Examples:
+      eq(SpecOperator) matches the SpecOperator class, but nothing else.
+
+    """
+
+    def __init__(self, value):
+        self.value = value
+
+    def format_args(self):
+        return repr(self.value)
+
+    def __call__(self, value):
+        return value is self.value
+
+
+class equal (SpecOperator):
+
+    """
+    Use this to specify specs exact value.
+    Examples:
+      eq([2,3]) matches any list [2,3]
+
+    """
+
+    def __init__(self, value):
+        self.value = value
+
+    def format_args(self):
+        return repr(self.value)
+
+    def __call__(self, value):
+        return value == self.value
+
+boolean = either(True, False, 0, 1)
+
+if sys.version < "3":
+    integer = either(int, long)
+else:
+    integer = int
+
+class charset (SpecOperator):
+
+    def __init__(self, charset):
+        self.charset = charset
+
+    def __str__(self):
+        return self.charset
+
+    def __call__(self, value):
+        if isinstance(value, unicode_string):
+            try:
+                value.encode(self.charset)
+            except UnicodeEncodeError:
+                return False
+        elif isinstance(value, byte_string):
+            try:
+                unicode_string(value, self.charset)
+            except UnicodeDecodeError:
+                return False
+        else:
+            return False
+        return True
+
+
+ascii = charset('ascii')
+
+
+class String (SpecOperator):
+
+    """
+    This is the common string class, including ascii byte sequences and
+    all unicode instances.
+    """
+
+    def __str__(self):
+        return 'string'
+
+    def __call__(self, value):
+        if isinstance(value, unicode_string):
+            return True
+        elif isinstance(value, byte_string):
+            try:
+                unicode_string(value, 'ascii')
+                return True
+            except UnicodeDecodeError:
+                return False
+        return False
+
+string = String()
+
+
+class pattern (SpecOperator):
+    """
+    Matches strings that match the regular expression.
+    """
+    def __init__(self, pattern):
+        self.pattern = re.compile(pattern)
+        self.args = (pattern,)
+
+    def __call__(self, value):
+        try:
+            return bool(self.pattern.match(value))
+        except TypeError:
+            return False
+
+identifier_pattern = pattern('[a-zA-Z_][a-zA-Z0-9_]*$')
+
+class with_attribute (SpecOperator):
+    """
+    Matches values with attributes matching given specs.
+    A keyword argument gives the spec for each attribute.
+    """
+    def __init__(self, **attribute_specs):
+        self.attribute_specs = attribute_specs
+
+    def __call__(self, value):
+        for attribute, spec in self.attribute_specs.items():
+            if (not hasattr(value, attribute) or
+                not match(getattr(value, attribute), spec)):
+                return False
+        return True
+
+    def format_args(self):
+        return ', '.join(['%s=%s' % (name, format_spec(spec))
+                          for name, spec in sorted(self.attribute_specs.items())])
+
+
+datetime_without_tz = both(datetime, with_attribute(tzinfo=None))
+datetime_with_tz = both(datetime, with_attribute(tzinfo=no(None)))
+
+class Callable (object):
+
+    def __str__(self):
+        return 'callable'
+
+    def __call__(self, value):
+        return hasattr(value, '__call__')
+
+callable = Callable()
+
+class Specification:
+    """
+    Instance attributes:
+      doc : string
+      spec : anything
+    """
+
+    def __init__(self, spec, doc=''):
+        self.doc = doc
+        self.spec = spec
+
+    def __call__(self, *value):
+        """
+        if there are no arguments call the spec to instantiate a conforming value
+        otherwise verify that the argument conforms.
+        """
+        if len(value) == 0:
+            return self.spec()
+        assert len(value) == 1, len(value)
+        return match(value[0], self.spec)
+
+    def __str__(self):
+        """-> str
+        Return a formatted string describing this spec and corresponding doc.
+        """
+        doc = '\n  '.join([line.strip()
+                           for line in self.doc.split('\n')])
+        if doc:
+            return self.format_spec() + '\n  ' + doc
+        else:
+            return self.format_spec()
+
+    def valid(self, value):
+        """(value : anything) -> bool
+        Does the value match this spec?
+        """
+        return match(value, self.spec)
+
+    def format_spec(self):
+        """() -> str
+        Return a string version of the spec.
+        """
+        return format_spec(self.spec)
+
+class Optional(Specification):
+    """
+    This specifies a value that is not required to be present.
+    """
+    def format_spec(self):
+        """() -> str
+        Return a string version of the spec.
+        """
+        return "%s (optional)" % format_spec(self.spec)
+
+def get_spec(klass, name):
+    """(klass:anything, name:str) -> anything
+    """
+    value = getattr(klass, name)
+    # functions as specifiers must be unwrapped, since
+    # assignment to class variables changes them.
+    if type(value) is MethodType: # normal classes
+        spec = value.im_func
+    else:
+        spec = value
+    return spec
+
+def spec(spec, *docs):
+    """(anything, *str) -> Specification
+    An alternative Specification constructor.
+    """
+    doc = '\n'.join(docs)
+    return Specification(spec, doc)
+
+def optional(spec, *docs):
+    """(anything, *str) -> Specification
+    An alternative Specification constructor.
+    """
+    doc = '\n'.join(docs)
+    return Optional(spec, doc)
+
+def nspec(spec, *docs):
+    """(anything, *str) -> Specification
+    Like spec, but this uses a modified Specification (if needed) to make
+    sure that None is a valid value.
+    For applications that always want None as a valid value, this
+    allows us to avoid using the either(None,*) pattern around every spec.
+    """
+    doc = '\n'.join(docs)
+    s = Specification(spec, doc)
+    if s.valid(None):
+        return s
+    if isinstance(spec, either):
+        return Specification(either(None, *spec.get_args()), doc)
+    return Specification(either(None, spec), doc)
+
+def get_specs(klass):
+    """(klass:anything) -> { str : anything }
+    Returns a dictionary mapping names to specs that apply to instances of
+    klass.  Specs are distinguished by the following naming convention:
+    A class attribute whose name ends in '_is' specifies an instance
+    attribute without the suffix.
+    Example:
+    class A:
+        color_is = either(str, None)
+        def __init__(self, x):
+            self.color = x
+    """
+    specs = {}
+    for name in dir(klass):
+        if name.endswith('_is'):
+            specs[name[:-3]] = get_spec(klass, name)
+    return specs
+
+def persistent_vars(obj):
+    """() -> dict
+    This is like the built-in vars() function, except that it also works
+    for PersistentObject instances or other instances that use slots for
+    data attributes.
+    """
+    if hasattr(obj, '__getstate__'):
+        return obj.__getstate__() or {}
+    elif hasattr(obj, '__dict__'):
+        return vars(obj)
+    else:
+        result = {}
+        for name in obj.__slots__:
+            if hasattr(obj, name):
+                result[name] = getattr(obj, name)
+        return result
+
+def get_spec_problems(x, specs=None):
+    """(x:anything, specs:{str:Spec}) -> [str]
+    Return a list of reasons why the attributes of this
+    instance do not match the specs.
+    Use provided specs for speed if you already
+    have the specs of the class of x.
+    """
+    reasons = []
+    if specs is None:
+        specs = get_specs(x.__class__)
+    for name, spec in specs.items():
+        if not hasattr(x, name):
+            if not isinstance(spec, Optional):
+                reasons.append(
+                    ('%r.%s: Missing\n'
+                    '  Expected: %s\n') % (x, name, format_spec(spec)))
+        else:
+            value = getattr(x, name)
+            if not match(value, spec):
+                reasons.append(
+                    ('%r.%s:\n' #nocover
+                     '  Expected: %s\n'
+                     '  Got: %r\n') % ( #nocover
+                    x, name, format_spec(spec), value))
+    for name, value in persistent_vars(x).items():
+        if not name in specs:
+            reasons.append('Found non-specified attribute: %r.%s (=%r)' %
+                           (x, name, value))
+    return reasons
+
+def get_spec_doc(klass):
+    """(klass:Specified) -> str
+    Return a string containing the docs of all specs of klass.
+    """
+    spec_items = sorted(get_specs(klass).items())
+    return '\n'.join(["%s: %s" % (name, format_spec(spec))
+                      for name, spec in spec_items])
+
+def get_spec_report(instances):
+    """(instances: [InstanceType]) -> str|None
+    Study the instances with respect to their attribute specs.
+    The instances must all be of the same class.
+    If any instance does not satisfy the specs, return a
+    string explaining the problem.
+    If all instances satisfy the specs, return a message reporting
+    the number of instances verified.
+    If the class of the instances has no specifications,
+    return a message that says so.
+    If there are no instances, return None.
+    """
+    count = 0
+    klass = specs = None
+    for instance in instances:
+        if klass is None:
+            klass = instance.__class__
+            specs = get_specs(klass)
+            if not specs:
+                return "\nunspecified " + klass.__name__
+        assert instance.__class__ is klass
+        count += 1
+        instance_problems = get_spec_problems(instance, specs=specs)
+        if instance_problems:
+            return "\n%s:\n" % klass.__name__ + "\n".join(instance_problems)
+    return klass and "\n%s: %s ok\n" % (klass.__name__ , count)
+
+def specify(obj, **attributes_values):
+    """(obj:object, **attributes_values:{str:anything})
+    Set object attributes given as keywords.
+    Raise a TypeError if any value does match a spec,
+    and AttributeError if no corresponding spec is found.
+    """
+    for attribute, value in attributes_values.items():
+        require(value, getattr(obj.__class__, attribute + '_is'))
+        setattr(obj, attribute, value)
+
+def init(obj, **attributes_values):
+    """(obj:object, **attributes_values:{str:anything})
+    Set object attributes given as keywords.
+    Raise a TypeError if any value does match a spec,
+    and AttributeError if no corresponding spec is found.
+    In addition, all other specified attributes are set to None,
+    without any type checking.
+    """
+    for name in get_specs(obj.__class__):
+        if not hasattr(obj, name):
+            setattr(obj, name, None)
+    specify(obj, **attributes_values)
+
+def add_getters(klass):
+    """
+    Add trivial getter method for each specified attribute
+    for which no such method currently exists.
+    """
+    def add_getter(klass, name):
+        accessor_name = 'get_' + name
+        if not hasattr(klass, accessor_name):
+            def get_specified_attribute(self):
+                return getattr(self, name)
+            get_specified_attribute.__name__ = accessor_name
+            setattr(klass, accessor_name, get_specified_attribute)
+    for name in get_specs(klass):
+        add_getter(klass, name)
+
+def add_setters(klass):
+    """
+    Add trivial setter method for each specified attribute
+    for which no such method currently exists.
+    """
+    def add_setter(klass, name):
+        setter_name = 'set_' + name
+        if not hasattr(klass, setter_name):
+            def f(self, value):
+                require(value, getattr(klass, name + '_is'))
+                setattr(self, name, value)
+            f.__name__ = setter_name
+            setattr(klass, setter_name, f)
+    for name in get_specs(klass):
+        add_setter(klass, name)
+
+def add_getters_and_setters(klass):
+    add_getters(klass)
+    add_setters(klass)
+
+
+class SlottedType (type):
+    """
+    Slotted classes have a __slots__ attribute.
+    The default, provided here, is to have __slots__ = [].
+    """
+    def __new__(self, klass_name, bases, namespace):
+        if '__slots__' not in namespace:
+            namespace['__slots__'] = []
+        return type.__new__(self, klass_name, bases, namespace)
+
+
+class SlotsFromSpecs (SlottedType):
+    """
+    Classes with this metaclass get a __slots__ value that is constructed
+    from the attributes (including inherited ones) whose names end with "_is".
+    """
+    def __new__(self, klass_name, bases, namespace):
+        if '__slots__' in namespace:
+            raise TypeError(
+                "Specified class %s already has __slots__: %s" %
+                (klass_name, namespace['__slots__']))
+        else:
+            names = set()
+            for name in namespace:
+                if name.endswith('_is'):
+                    names.add(name[:-3])
+            for base in bases:
+                for name in dir(base):
+                    if name.endswith('_is'):
+                        names.add(name[:-3])
+            namespace['__slots__'] = sorted(names)
+        return type.__new__(self, klass_name, bases, namespace)
+
+Specified = SlotsFromSpecs('Specified', (object,), dict(__doc__=""""
+    This mixin class has a metaclass that uses attribute specs to build
+    a __slots__ list for the class.
+    """))
+
+Mixin = SlottedType('Mixin', (object,), dict(__doc__=""""
+    This mixin class has a metaclass that sets __slots__ = [].
+    This allows subclasses to use slots for attributes.
+    """))