ZODB troubles

Nolan Darilek nolan at ethereal.dhis.org
Sun Apr 30 19:43:16 EDT 2000


For hack value, I've semi-revived some old code which I wrote and have
been attempting to rewrite it to work with ZODB.

I'm trying to create an environment in which python code is stored
alongside objects. So, you'd have a database of objects which could be
used, and you'd be able to 'dump' their code to text. But, I've been
having some ZODB issues and was hoping that someone could help.

I'm writing small test scripts to ensure that my code is behaving the
way it should. So far I have three tests:

1.  Open the ZODB and create a test object, closing it again.
2.  Re-open the ZODB, create a persistent function' and run it to
prove that it works.
3.  Open the ZODB, access the object, run the persistent function and
print the code.

Test 1 works flawlessly. Test 2 runs, but in order to save the
newly-added function, I have to delete the object from the database
and re-insert it; it can't be modified live it seems. Test 3 works
depending on which route is taken for 2; the long "delete and re-add
the object" way works, but the shorter "add an instance attribute to
the object and commit" doesn't.

I tried duplicating this problem on ints and other simple types, but I
couldn't. It only seems to appear under my circumstances. So, in order
to help you help me, I've flattened my hierarchy of scripts into a
single Python script which requires the ZODB modules. I have three
functions entitled one()/three(). Since two() determines whether this
script fails or succeeds, I've added a few instructions on how to
switch between the success and failure cases. To run this, 'python
test.py 1', or 2-3 to run the other tests. They should be run in
order, since they lay groundwork for the next test.

Another question; is it all right to embed
get_transaction().commit()'s (or commit(1)'s) deep within my code, in
places where fundamental changes are made? (See Function.compile() for
an example of what I mean.

I'm inserting the script below. If anyone could help with this, I'd
greatly appreciate it. Thanks.

--------------------------------------------------------------------------------
#!/usr/bin/env python

import Persistence
import re
import rexec
import sys
from ZODB import *
from ZODB.FileStorage import *

class Database:

    """Simple wrapper around ZODB.

    This class provides a simple wrapper around the lower-level ZODB functions.
    Some functionality is not yet implemented.
    """

    def __init__(self, file='Data.fs'):
        """ Create a database, connect to it and get the root
        object out of it.  """
        self.db = DB(FileStorage(file))
        self.connection = self.db.open()
        self.root = self.connection.root()

    def addObject(self, object):
        """Add an object to the database."""
        dbref = `len(self.root.keys())`
        while self.root.has_key(dbref):
            dbref = `int(dbref)+1`
        self.root[dbref] = object
        get_transaction().commit()
        return dbref

    def dump(self):
        """Commit all transactions."""
        get_transaction().commit()

    def __getitem__(self, item):
        return self.root[item]
    

class Function(Persistence.Persistent, rexec.RExec):

    """This is a 'persistent function.'

    A persistent function acts like a regular function, but code exists
    alongside compiled code. This also neatly handles recompiling functions,
    since the function objects don't play nice with ZODB/Pickle.
    """

    def __init__(self, code = ""):
        """Initialize a new instance.
        
        If `code' is specified, compile it.
        """
        self.code = code
        if code != "":
            self.compile(code)

    def __call__(self, *args):
        """Execute the function code.

        If specified, `args' are passed directly to the function code.
        """
        # setstate() can't seem to find self.code.
        # Since the actual function code objects don't persist, the uncompiled
        # code is kept until the function is initially called, then we compile.
        try:
            self._v_func
        except AttributeError:
            self.compile(self.code)
        apply(self._v_func, args)

    def compile(self, code):
        """Compiles the function.

        `code' is a list of strings consisting of this function's Python code.
        """
        self.code = code
        exec(code)
        counter = 0
        match = re.match("def\s+(.*?)\s*\(.*\):", self.code)
        if not match:
            return
        fname = match.string[match.regs[1][0]:match.regs[1][1]]
        exec("self._v_func = %s" % fname)
        # Will this commit the modified function?
        # Is it a good idea to nestle commits within places like this where
        # changes are made?
        get_transaction().commit()

    def __repr__(self):
        return self.code

class Object(Persistence.Persistent):

    """A persistent object with some changes.

    This acts like a normal object with a notable exception: all attributes
    are stored in 'Attribute' objects, with setattr/getattr handling access.
    This will allow certain attributes to be public, private, etc.
    """

    def __init__(self):
        self.__dict__["__attributes__"] = {}

    def __setattr__(self, name, value):
        self.__attributes__[name] = value
        get_transaction().commit()

    def __getattr__(self, name):
        return self.__attributes__[name]
    
def one():
    print "Test 1: Initializing a new database and adding test object 0."
    db = Database()
    t = Object()
    db.addObject(t)
    print "Done!"

def two():
    print "Test 2: Retrieving test object 0 and adding a function."
    db = Database()
    # t = db.root['0']
    # t.hw = Function("def hw():\n print 'Hello, world.'\n")
    # del db.root['0']
    # db.addObject(t)
    # Uncomment the previous 4 lines and comment out the next to fix this.
    db.root['0'].hw = Function("def hw():\n print 'Hello, world.'\n")
    # As you can see, the previous line is much more aesthetically pleasing.
    # How can I get it to do what the above 4 lines do?
    db.root['0'].hw()
    db.dump()
    print "Done!"

def three():
    print "Test 3: Retrieving test object and function."
    db = Database()
    db.root['0'].hw()
    print db.root['0'].hw
    print "Done!"

if sys.argv[1] == '1':
    one()
if sys.argv[1] == '2':
    two()
if sys.argv[1] == '3':
    three()
--------------------------------------------------------------------------------



More information about the Python-list mailing list