view roundup/volatiledb.py @ 905:502a5ae11cc5

Very close now. The cgi and mailgw now use the new security API. The two templates have been migrated to that setup. Lots of unit tests. Still some issue in the web form for editing Roles assigned to users.
author Richard Jones <richard@users.sourceforge.net>
date Fri, 26 Jul 2002 08:27:00 +0000
parents b0d3d3535998
children
line wrap: on
line source

import weakref, re

from roundup import hyperdb
from roundup.hyperdb import String, Password, Date, Interval, Link, \
    Multilink, DatabaseError, Boolean, Number

class VolatileClass(hyperdb.Class):
    ''' This is a class that just sits in memory, no saving to disk.
        It has no journal.
    '''
    def __init__(self, db, classname, **properties):
        ''' Set up an in-memory store for the nodes of this class
        '''
        self.db = weakref.proxy(db)       # use a weak ref to avoid circularity
        self.classname = classname
        self.properties = properties
        self.id_counter = 1
        self.store = {}
        self.by_key = {}
        self.key = ''
        db.addclass(self)

    def setkey(self, propname):
        prop = self.getprops()[propname]
        if not isinstance(prop, String):
            raise TypeError, 'key properties must be String'
        self.key = propname

    def getprops(self, protected=1):
        d = self.properties.copy()
        if protected:
            d['id'] = String()
        return d

    def create(self, **propvalues):
        ''' Create a new node in the in-memory store
        '''
        if propvalues.has_key('id'):
            raise KeyError, '"id" is reserved'
        newid = str(self.id_counter)
        self.id_counter += 1

        # get the key value, validate it
        if self.key:
            keyvalue = propvalues[self.key]
            try:
                self.lookup(keyvalue)
            except KeyError:
                pass
            else:
                raise ValueError, 'node with key "%s" exists'%keyvalue
            self.by_key[keyvalue] = newid

        # validate propvalues
        num_re = re.compile('^\d+$')

        for key, value in propvalues.items():

            # try to handle this property
            try:
                prop = self.properties[key]
            except KeyError:
                raise KeyError, '"%s" has no property "%s"'%(self.classname,
                    key)

            if isinstance(prop, Link):
                if type(value) != type(''):
                    raise ValueError, 'link value must be String'
                link_class = self.properties[key].classname
                # if it isn't a number, it's a key
                if not num_re.match(value):
                    try:
                        value = self.db.classes[link_class].lookup(value)
                    except (TypeError, KeyError):
                        raise IndexError, 'new property "%s": %s not a %s'%(
                            key, value, link_class)
                elif not self.db.hasnode(link_class, value):
                    raise IndexError, '%s has no node %s'%(link_class, value)

                # save off the value
                propvalues[key] = value

            elif isinstance(prop, Multilink):
                if type(value) != type([]):
                    raise TypeError, 'new property "%s" not a list of ids'%key

                # clean up and validate the list of links
                link_class = self.properties[key].classname
                l = []
                for entry in value:
                    if type(entry) != type(''):
                        raise ValueError, '"%s" link value (%s) must be '\
                            'String'%(key, value)
                    # if it isn't a number, it's a key
                    if not num_re.match(entry):
                        try:
                            entry = self.db.classes[link_class].lookup(entry)
                        except (TypeError, KeyError):
                            raise IndexError, 'new property "%s": %s not a %s'%(
                                key, entry, self.properties[key].classname)
                    l.append(entry)
                value = l
                propvalues[key] = value

                # handle additions
                for id in value:
                    if not self.db.hasnode(link_class, id):
                        raise IndexError, '%s has no node %s'%(link_class, id)

            elif isinstance(prop, String):
                if type(value) != type(''):
                    raise TypeError, 'new property "%s" not a string'%key

            elif isinstance(prop, Password):
                if not isinstance(value, password.Password):
                    raise TypeError, 'new property "%s" not a Password'%key

            elif isinstance(prop, Date):
                if value is not None and not isinstance(value, date.Date):
                    raise TypeError, 'new property "%s" not a Date'%key

            elif isinstance(prop, Interval):
                if value is not None and not isinstance(value, date.Interval):
                    raise TypeError, 'new property "%s" not an Interval'%key

        # make sure there's data where there needs to be
        for key, prop in self.properties.items():
            if propvalues.has_key(key):
                continue
            if key == self.key:
                raise ValueError, 'key property "%s" is required'%key
            if isinstance(prop, Multilink):
                propvalues[key] = []
            else:
                propvalues[key] = None

        # done
        self.store[newid] = propvalues

        return newid

    _marker = []
    def get(self, nodeid, propname, default=_marker, cache=1):
        ''' Get the node from the in-memory store
        '''
        if propname == 'id':
            return nodeid
        return self.store[nodeid][propname]

    def set(self, nodeid, **propvalues):
        ''' Set properties on the node in the in-memory store
        '''
        if not propvalues:
            return

        if propvalues.has_key('id'):
            raise KeyError, '"id" is reserved'

        node = self.store[nodeid]
        num_re = re.compile('^\d+$')

        for propname, value in propvalues.items():
            # check to make sure we're not duplicating an existing key
            if propname == self.key and node[propname] != value:
                try:
                    self.lookup(value)
                except KeyError:
                    pass
                else:
                    raise ValueError, 'node with key "%s" exists'%value

            # this will raise the KeyError if the property isn't valid
            # ... we don't use getprops() here because we only care about
            # the writeable properties.
            prop = self.properties[propname]

            # if the value's the same as the existing value, no sense in
            # doing anything
            if node.has_key(propname) and value == node[propname]:
                del propvalues[propname]
                continue

            # do stuff based on the prop type
            if isinstance(prop, Link):
                link_class = self.properties[propname].classname
                # if it isn't a number, it's a key
                if type(value) != type(''):
                    raise ValueError, 'link value must be String'
                if not num_re.match(value):
                    try:
                        value = self.db.classes[link_class].lookup(value)
                    except (TypeError, KeyError):
                        raise IndexError, 'new property "%s": %s not a %s'%(
                            propname, value, self.properties[propname].classname)

                if not self.db.hasnode(link_class, value):
                    raise IndexError, '%s has no node %s'%(link_class, value)

            elif isinstance(prop, Multilink):
                if type(value) != type([]):
                    raise TypeError, 'new property "%s" not a list of'\
                        ' ids'%propname
                link_class = self.properties[propname].classname
                l = []
                for entry in value:
                    # if it isn't a number, it's a key
                    if type(entry) != type(''):
                        raise ValueError, 'new property "%s" link value ' \
                            'must be a string'%propname
                    if not num_re.match(entry):
                        try:
                            entry = self.db.classes[link_class].lookup(entry)
                        except (TypeError, KeyError):
                            raise IndexError, 'new property "%s": %s not a %s'%(
                                propname, entry,
                                self.properties[propname].classname)
                    l.append(entry)
                value = l
                propvalues[propname] = value

            elif isinstance(prop, String):
                if value is not None and type(value) != type(''):
                    raise TypeError, 'new property "%s" not a string'%propname

            elif isinstance(prop, Password):
                if not isinstance(value, password.Password):
                    raise TypeError, 'new property "%s" not a Password'%propname
                propvalues[propname] = value

            elif value is not None and isinstance(prop, Date):
                if not isinstance(value, date.Date):
                    raise TypeError, 'new property "%s" not a Date'% propname
                propvalues[propname] = value

            elif value is not None and isinstance(prop, Interval):
                if not isinstance(value, date.Interval):
                    raise TypeError, 'new property "%s" not an '\
                        'Interval'%propname
                propvalues[propname] = value

            elif value is not None and isinstance(prop, Number):
                try:
                    float(value)
                except ValueError:
                    raise TypeError, 'new property "%s" not numeric'%propname

            elif value is not None and isinstance(prop, Boolean):
                try:
                    int(value)
                except ValueError:
                    raise TypeError, 'new property "%s" not boolean'%propname

            node[propname] = value

        # do the set
        self.store[nodeid] = node

    def lookup(self, keyvalue):
        ''' look up the key node in the store
        '''
        return self.by_key[keyvalue]

    def hasnode(self, nodeid):
        nodeid = str(nodeid)
        return self.store.has_key(nodeid)

    def list(self):
        l = self.store.keys()
        l.sort()
        return l

    def index(self, nodeid):
        pass

    def stringFind(self, **requirements):
        """Locate a particular node by matching a set of its String
           properties in a caseless search.

           If the property is not a String property, a TypeError is raised.
        
           The return is a list of the id of all nodes that match.
        """
        for propname in requirements.keys():
            prop = self.properties[propname]
            if isinstance(not prop, String):
                raise TypeError, "'%s' not a String property"%propname
            requirements[propname] = requirements[propname].lower()
        l = []
        for nodeid, node in self.store.items():
            for key, value in requirements.items():
                if node[key] and node[key].lower() != value:
                    break
            else:
                l.append(nodeid)
        return l

    def getkey(self):
        """Return the name of the key property for this class or None."""
        return self.key

    def labelprop(self, default_to_id=0):
        ''' Return the property name for a label for the given node.

        This method attempts to generate a consistent label for the node.
        It tries the following in order:
            1. key property
            2. "name" property
            3. "title" property
            4. first property from the sorted property name list
        '''
        k = self.getkey()
        if  k:
            return k
        props = self.getprops()
        if props.has_key('name'):
            return 'name'
        elif props.has_key('title'):
            return 'title'
        if default_to_id:
            return 'id'
        props = props.keys()
        props.sort()
        return props[0]


Roundup Issue Tracker: http://roundup-tracker.org/