Commits

Anonymous committed 4d62ae8

Re-org for Flask 0.8 handling of extensions.

  • Participants
  • Parent commits 0ba9ca0

Comments (0)

Files changed (9)

flask_durus/__init__.py

Empty file added.

flask_durus/durus.py

+from __future__ import absolute_import
+
+from durus.client_storage import ClientStorage
+from durus.connection import Connection
+from durus.persistent import Persistent, PersistentObject
+from durus.persistent_list import PersistentList
+from durus.persistent_dict import PersistentDict
+import durus.btree 
+
+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
+
+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 __init__(self, app=None):
+        if app is not None:
+            self.app = app
+            self.setup(self.app)
+        else:
+            self.app = None
+
+    @property
+    def data(self):
+        ctx = _request_ctx_stack.top
+        if ctx is not None:
+            return self._get_db(ctx).get_root()
+    
+    def _get_db(self, ctx):
+        if not hasattr(ctx, 'durus_db'):
+            ctx.durus_db = self.connect()
+        return ctx.durus_db
+
+
+    def setup_app(self, app, browser=None):
+        self.app = app
+        self.app.config.setdefault('DURUS_PORT', 2972)
+        self.app.config.setdefault('DURUS_HOST', 'localhost')
+        self.app.teardown_request(self.teardown_request)
+        self.app.before_request(self.before_request)
+        if browser:
+            browsedb_on_connection(self, app, browser)
+
+    def connect(self):
+        ##print "connect, try to get from pool"
+        try:
+            conn = self.connections.pop()
+            ##print "existing connection"
+        except Exception, e:
+            #print "new conection [%s]" % e
+            conn = Connection(ClientStorage(port=self.app.config['DURUS_PORT']))
+        #print "connection [%s]" % id(conn)
+        return conn
+
+    def before_request(self):
+        #print "before request"
+        ctx = _request_ctx_stack.top
+        ctx.durus_db = self.connect()
+
+    def teardown_request(self, exception):
+        #print "teardown request, put connection back in the pool"
+        ctx = _request_ctx_stack.top
+        if hasattr(ctx, 'durus_db'):
+            ctx.durus_db.abort()
+            self.connections.append(ctx.durus_db)
+            #print "back in pool, size [%d]" % len(self.connections)
+            del(ctx.durus_db)
+
+    def abort(self):
+        ctx = _request_ctx_stack.top
+        if ctx is not None:
+            return self._get_db(ctx).abort()
+
+    def commit(self):
+        ctx = _request_ctx_stack.top
+        if ctx is not None:
+            # walk list of changed items
+            db = self._get_db(ctx) 
+            changed = db.changed.values()
+            for changed in changed:
+                # call commit if definied on item
+                print "changed:", changed
+                on_commit = getattr(changed, 'on_commit', None)
+                if on_commit:
+                    print "changed:", changed, "calling on_commit"
+                    on_commit()
+
+            return db.commit()
+
+    @property
+    def connection(self):
+        ctx = _request_ctx_stack.top
+        if ctx is not None:
+            return self._get_db(ctx)
+
+class BTree(durus.btree.BTree):
+    def _with_limit(self, method, *args,  **kws):
+        limit = kws.pop('limit', None)
+        for idx, item in enumerate(method(self, *args, **kws)):
+            if limit == None or idx < limit:
+                yield item
+            else:
+                raise StopIteration()
+
+    def items_backward(self, limit=None):
+        """(limit=None) -> generator
+        Generate all items in reverse order, getting max limit items.
+        """
+        return self._with_limit(durus.btree.BTree.items_backward, limit=limit)
+
+    def items_from(self, key, closed=True, limit=None):
+        """(key, closed=True, limit=None) -> generator
+        If closed is true, generate all items with keys greater than or equal to
+        the given key.
+        If closed is false, generate all items with keys greater than the
+        given key.
+        """
+        return self._with_limit(durus.btree.BTree.items_from, 
+                key, closed=closed, limit=limit)
+
+
+    def items_backward_from(self, key, closed=False, limit=None):
+        """(key, closed=False, limit=None) -> generator
+        If closed is true, generate in reverse order all items with keys
+        less than or equal to the given key.
+        If closed is true, generate in reverse order all items with keys
+        less than the given key.
+        """
+        return self._with_limit(durus.btree.BTree.items_backward_from, 
+                key, closed=closed, limit=limit)
+
+    def items_range(self, start, end, closed_start=True, closed_end=False, limit=None):
+        """(start, end, closed_start=True, closed_end=False, limit=None) -> generator
+        Generate items with keys in the given range, from the start to the end.
+        If closed_start is true, include the item with the start key,
+        if it is present.
+        If closed_end is true, include the item with the end key,
+        if it is present.
+        """
+        return self._with_limit(durus.btree.BTree.items_range, 
+                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.
+    """
+    @app.route("/%s" % url_prefix)
+    def browseroot():
+        """
+        Redirect to the root object
+        """
+        return redirect(url_for('browsedb', oid=0))
+
+    @app.route("/%s/<int:oid>" % url_prefix)
+    def browsedb(oid):
+        from flask import url_for
+
+        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
+
+        value = db.connection.get(oid)
+
+        def format(value):
+            if isinstance(value, PersistentObject):
+                ret = """<a href="%s">%s</a>""" % (url_for("browsedb", oid=value._p_format_oid()), 
+                        repr(value).replace('<', '&lt;').replace('>', '&gt;'))
+            elif isinstance(value, list):
+                ret = '[' + ', '.join([format(v) for v in value]) + ']'
+            elif isinstance(value, tuple):
+                ret = '(' + ', '.join([format(v) for v in value]) + ')'
+            elif isinstance(value, dict):
+                ret = '<div style="margin-left:2em">{'
+                for k, v in value.items():
+                    ret += '<div>' + format(k) + ' : ' + format(v) + '</div>'
+                ret += '}</div>'
+            else:
+                ret = repr(value).replace('<', '&lt;').replace('>','&gt;') 
+
+            return ret
+
+        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 IndexedKeyed(Keyed):
+    """
+    A keyed item that maintains indexes
+    """
+    KEY_PREFIX = 'key_for_'
+    KEY_PREFIX_LEN = len(KEY_PREFIX)
+    
+    _keep_is = spec(either(None, "IndexedKeep"), "Pointer to the keep this item is in")
+    _indexes_is = spec(PersistentDict, "Pointers to the indexes this item exists in")
+
+    def __init__(self):
+        print "in init"
+        Keyed.__init__(self)
+        self._indexes = PersistentDict()
+        self._keep = None
+    
+    def _index(self):
+        """()
+        Return index keys for this item based on key functions
+        """
+        for name in dir(self):
+            attr = getattr(self, name)
+            if name.startswith(self.KEY_PREFIX) and callable(attr):
+                for key in attr():
+                    if key:
+                        # only return a index and key if the key isn't None, ie. no valid value for that key
+                        yield name[self.KEY_PREFIX_LEN:], key
+
+    def save(self):
+        """()
+        Get all index keys and update in the keep
+        """
+        print "update indexes"
+        if self._keep:
+            # remove existing index keys
+            for name, existing_key in self._indexes.items():
+                try:
+                    del(self._keep._indexes[name][existing_key])
+                    # remove index if empty
+                    if not self._keep._indexes[name]:
+                        del(self._keep._indexes[name])
+                except KeyError:
+                    """ no index """
+                    # TODO - clean up, should check for index
+            
+            # add current values
+            for name, key in self._index():
+                if type(key) == tuple:
+                    key = list(key)
+                if type(key) != list:
+                    key = [key]
+                # add item to end of key to ensure uniqueness
+                key.append(self)
+
+                key=tuple(key)
+                print "key [%s]" % str(key)
+                self._keep._indexes.setdefault(name, BTree())[key] = None
+
+                # store pointer back into index
+                self._indexes[name] = key
+
+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()
+
+    def add(self, value):
+        Keep.add(self, value)
+        value._keep = self
+
+
+    def by(self, name):
+        """(name:str) -> BTree
+        Get an index into the keep
+        """
+        return self._indexes.get(name, None)
+
+
+    # def on_commit(self):
+    #     """
+    #     When commiting this keep, run through all changed items in it and update the indexes.
+    #     """
+    #     for item in self.mapping.itervalues():
+    #         if item._p_is_unsaved():
+    #             item.update_indexes(value)
+
+    # 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, value=None):
+    #     """(value:Persistent|None)
+    #     Update the indexes for this keep, if value is specified, only update indexes for it.
+    #     """
+    #     items = [value] if value else self.mapping.itervalues()
+    #     for name, index in self._indexes.items():
+    #         for item in items:
+    #             if not item._p_oid or item._p_is_unsaved():
+    #                 print "update index:", name, item
+                    
+    #                 # remove existing entries in index
+    #                 existing_key = item._indexes.get(name, None)
+    #                 if existing_key:
+    #                     del(index[existing_key])
+    #                     del(item._indexes[name])
+
+    #                 # add new entries to index
+    #                 key = getattr(item, "key_for_%s" % name)()
+    #                 index[(key, item.key)] = item
+    #                 item._indexes[name] = (key, item.key)
+
+
+
+
+

flask_durus/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_without_tz, mapping, spec, integer
+from .spec import require, get_spec_problems, anything, specify
+
+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_without_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.utcnow()
+
+
+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
+

flask_durus/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
+
+epoch = datetime(1970,1,1)
+
+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.
+    """))

flaskext/__init__.py

-__import__('pkg_resources').declare_namespace(__name__)

flaskext/durus.py

-from __future__ import absolute_import
-
-from durus.client_storage import ClientStorage
-from durus.connection import Connection
-from durus.persistent import Persistent, PersistentObject
-from durus.persistent_list import PersistentList
-from durus.persistent_dict import PersistentDict
-import durus.btree 
-
-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
-
-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 __init__(self, app=None):
-        if app is not None:
-            self.app = app
-            self.setup(self.app)
-        else:
-            self.app = None
-
-    @property
-    def data(self):
-        ctx = _request_ctx_stack.top
-        if ctx is not None:
-            return self._get_db(ctx).get_root()
-    
-    def _get_db(self, ctx):
-        if not hasattr(ctx, 'durus_db'):
-            ctx.durus_db = self.connect()
-        return ctx.durus_db
-
-
-    def setup_app(self, app, browser=None):
-        self.app = app
-        self.app.config.setdefault('DURUS_PORT', 2972)
-        self.app.config.setdefault('DURUS_HOST', 'localhost')
-        self.app.teardown_request(self.teardown_request)
-        self.app.before_request(self.before_request)
-        if browser:
-            browsedb_on_connection(self, app, browser)
-
-    def connect(self):
-        ##print "connect, try to get from pool"
-        try:
-            conn = self.connections.pop()
-            ##print "existing connection"
-        except Exception, e:
-            #print "new conection [%s]" % e
-            conn = Connection(ClientStorage(port=self.app.config['DURUS_PORT']))
-        #print "connection [%s]" % id(conn)
-        return conn
-
-    def before_request(self):
-        #print "before request"
-        ctx = _request_ctx_stack.top
-        ctx.durus_db = self.connect()
-
-    def teardown_request(self, exception):
-        #print "teardown request, put connection back in the pool"
-        ctx = _request_ctx_stack.top
-        if hasattr(ctx, 'durus_db'):
-            ctx.durus_db.abort()
-            self.connections.append(ctx.durus_db)
-            #print "back in pool, size [%d]" % len(self.connections)
-            del(ctx.durus_db)
-
-    def abort(self):
-        ctx = _request_ctx_stack.top
-        if ctx is not None:
-            return self._get_db(ctx).abort()
-
-    def commit(self):
-        ctx = _request_ctx_stack.top
-        if ctx is not None:
-            # walk list of changed items
-            db = self._get_db(ctx) 
-            changed = db.changed.values()
-            for changed in changed:
-                # call commit if definied on item
-                print "changed:", changed
-                on_commit = getattr(changed, 'on_commit', None)
-                if on_commit:
-                    print "changed:", changed, "calling on_commit"
-                    on_commit()
-
-            return db.commit()
-
-    @property
-    def connection(self):
-        ctx = _request_ctx_stack.top
-        if ctx is not None:
-            return self._get_db(ctx)
-
-class BTree(durus.btree.BTree):
-    def _with_limit(self, method, *args,  **kws):
-        limit = kws.pop('limit', None)
-        for idx, item in enumerate(method(self, *args, **kws)):
-            if limit == None or idx < limit:
-                yield item
-            else:
-                raise StopIteration()
-
-    def items_backward(self, limit=None):
-        """(limit=None) -> generator
-        Generate all items in reverse order, getting max limit items.
-        """
-        return self._with_limit(durus.btree.BTree.items_backward, limit=limit)
-
-    def items_from(self, key, closed=True, limit=None):
-        """(key, closed=True, limit=None) -> generator
-        If closed is true, generate all items with keys greater than or equal to
-        the given key.
-        If closed is false, generate all items with keys greater than the
-        given key.
-        """
-        return self._with_limit(durus.btree.BTree.items_from, 
-                key, closed=closed, limit=limit)
-
-
-    def items_backward_from(self, key, closed=False, limit=None):
-        """(key, closed=False, limit=None) -> generator
-        If closed is true, generate in reverse order all items with keys
-        less than or equal to the given key.
-        If closed is true, generate in reverse order all items with keys
-        less than the given key.
-        """
-        return self._with_limit(durus.btree.BTree.items_backward_from, 
-                key, closed=closed, limit=limit)
-
-    def items_range(self, start, end, closed_start=True, closed_end=False, limit=None):
-        """(start, end, closed_start=True, closed_end=False, limit=None) -> generator
-        Generate items with keys in the given range, from the start to the end.
-        If closed_start is true, include the item with the start key,
-        if it is present.
-        If closed_end is true, include the item with the end key,
-        if it is present.
-        """
-        return self._with_limit(durus.btree.BTree.items_range, 
-                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.
-    """
-    @app.route("/%s" % url_prefix)
-    def browseroot():
-        """
-        Redirect to the root object
-        """
-        return redirect(url_for('browsedb', oid=0))
-
-    @app.route("/%s/<int:oid>" % url_prefix)
-    def browsedb(oid):
-        from flask import url_for
-
-        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
-
-        value = db.connection.get(oid)
-
-        def format(value):
-            if isinstance(value, PersistentObject):
-                ret = """<a href="%s">%s</a>""" % (url_for("browsedb", oid=value._p_format_oid()), 
-                        repr(value).replace('<', '&lt;').replace('>', '&gt;'))
-            elif isinstance(value, list):
-                ret = '[' + ', '.join([format(v) for v in value]) + ']'
-            elif isinstance(value, tuple):
-                ret = '(' + ', '.join([format(v) for v in value]) + ')'
-            elif isinstance(value, dict):
-                ret = '<div style="margin-left:2em">{'
-                for k, v in value.items():
-                    ret += '<div>' + format(k) + ' : ' + format(v) + '</div>'
-                ret += '}</div>'
-            else:
-                ret = repr(value).replace('<', '&lt;').replace('>','&gt;') 
-
-            return ret
-
-        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 IndexedKeyed(Keyed):
-    """
-    A keyed item that maintains indexes
-    """
-    KEY_PREFIX = 'key_for_'
-    KEY_PREFIX_LEN = len(KEY_PREFIX)
-    
-    _keep_is = spec(either(None, "IndexedKeep"), "Pointer to the keep this item is in")
-    _indexes_is = spec(PersistentDict, "Pointers to the indexes this item exists in")
-
-    def __init__(self):
-        print "in init"
-        Keyed.__init__(self)
-        self._indexes = PersistentDict()
-        self._keep = None
-    
-    def _index(self):
-        """()
-        Return index keys for this item based on key functions
-        """
-        for name in dir(self):
-            attr = getattr(self, name)
-            if name.startswith(self.KEY_PREFIX) and callable(attr):
-                for key in attr():
-                    if key:
-                        # only return a index and key if the key isn't None, ie. no valid value for that key
-                        yield name[self.KEY_PREFIX_LEN:], key
-
-    def save(self):
-        """()
-        Get all index keys and update in the keep
-        """
-        print "update indexes"
-        if self._keep:
-            # remove existing index keys
-            for name, existing_key in self._indexes.items():
-                try:
-                    del(self._keep._indexes[name][existing_key])
-                    # remove index if empty
-                    if not self._keep._indexes[name]:
-                        del(self._keep._indexes[name])
-                except KeyError:
-                    """ no index """
-                    # TODO - clean up, should check for index
-            
-            # add current values
-            for name, key in self._index():
-                if type(key) == tuple:
-                    key = list(key)
-                if type(key) != list:
-                    key = [key]
-                # add item to end of key to ensure uniqueness
-                key.append(self)
-
-                key=tuple(key)
-                print "key [%s]" % str(key)
-                self._keep._indexes.setdefault(name, BTree())[key] = None
-
-                # store pointer back into index
-                self._indexes[name] = key
-
-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()
-
-    def add(self, value):
-        Keep.add(self, value)
-        value._keep = self
-
-
-    def by(self, name):
-        """(name:str) -> BTree
-        Get an index into the keep
-        """
-        return self._indexes.get(name, None)
-
-
-    # def on_commit(self):
-    #     """
-    #     When commiting this keep, run through all changed items in it and update the indexes.
-    #     """
-    #     for item in self.mapping.itervalues():
-    #         if item._p_is_unsaved():
-    #             item.update_indexes(value)
-
-    # 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, value=None):
-    #     """(value:Persistent|None)
-    #     Update the indexes for this keep, if value is specified, only update indexes for it.
-    #     """
-    #     items = [value] if value else self.mapping.itervalues()
-    #     for name, index in self._indexes.items():
-    #         for item in items:
-    #             if not item._p_oid or item._p_is_unsaved():
-    #                 print "update index:", name, item
-                    
-    #                 # remove existing entries in index
-    #                 existing_key = item._indexes.get(name, None)
-    #                 if existing_key:
-    #                     del(index[existing_key])
-    #                     del(item._indexes[name])
-
-    #                 # add new entries to index
-    #                 key = getattr(item, "key_for_%s" % name)()
-    #                 index[(key, item.key)] = item
-    #                 item._indexes[name] = (key, item.key)
-
-
-
-
-

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_without_tz, mapping, spec, integer
-from .spec import require, get_spec_problems, anything, specify
-
-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_without_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.utcnow()
-
-
-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
-

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
-
-epoch = datetime(1970,1,1)
-
-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