flaskext-durus / 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)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.