How to prevent LockError on ZODB FileStorage when accessing ZODB objects through function calls (single process)

225 Views Asked by At

I am having problems with the ZODB LockError but I believe I am only operating on the db with a single process.

My goal for my project is to have just two functions, load_project() and save_project() that allows the user to load the Project object, work on it, then save it intermittently with save_project() without the user having to interact with ZODB directly. Kind of like they are just working on a document and saving as they go.

However, I noticed that I am consistently getting the LockError: I can load the object, save the object once, but then I get the LockError whenever I try to save it subsequently. Obviously, there is something that I have done wrong or am not understanding about how to the ZODB works. How can I prevent this LockError?

All of the other LockError problems on SO seem to deal with multiple processes. I am interacting with the code through a Jupyter Notebook, which I believe, is just a single process. When I run into the LockError, I can restart the kernel (thereby starting a new process), load the project and then save it again. But then, I can't save it a second time.

Here is the code from the two functions that I am importing from my functions module:

def save_project(project: BIMProject):
    """
    Commits the project to the ZODB; including opening and closing the connection
    """
    if not hasattr(project, "filename"):
        project.filename = input("Enter a filename (without extension):")
    if not hasattr(project, "filepath"):
        tk_root = Tk()
        tk_root.withdraw()
        project.filepath = filedialog.askdirectory(title="Please select a directory")

    filename = project.filename
    full_path = project.filepath + "/" + filename

    storage=FileStorage(full_path)
    db=ZODB.DB(storage)
    connection=db.open()
    root=connection.root()
    if 'project' in root:
        root.update({'project': project})
    else:
        root['project'] = project # reassign to change
    transaction.commit()
    connection.close()

def load_project():
    """
    Loads a project from a database file
    """
    tk_root = Tk()
    tk_root.withdraw()
    filepath = filedialog.askopenfilename(title="Please select a database file")

    storage = FileStorage(filepath)
    db = ZODB.DB(storage)
    connection = db.open()
    root = connection.root()
    project = copy.deepcopy(root["project"])
    connection.close()
    db.close()
    return project

Any help would be greatly appreciated.

1

There are 1 best solutions below

0
Connor Ferster On

So, after beating my head against a wall for a day, I took a walk and decided that instead of accessing the database through functions alone, an object would be better suited for maintaining the connection to the file storage and database. Not quite what I was picturing but works perfectly.

class LoaderSaver:
    """
    A 'hang around' object to use in conjunction with Project objects to save them
    in a ZODB FileStorage. Carries the ZODB storage, db, and connection objects once 
    they are opened.

    Only one Project file can be open at one time.
    """
    def __init__(self):
        self.storage = None
        self.db = None
        self.connection = None
        self.root = None

    def load(self):
        """
        Returns a Project object stored in a ZODB file storage. File selected by 
        user in a graphical 'file dialog' interface.
        """
        tk_root = Tk()
        tk_root.withdraw()
        filepath = filedialog.askopenfilename(title="Please select a database file")

        self.storage = FileStorage(filepath)
        self.db = ZODB.DB(self.storage)
        self.connection = self.db.open()
        self.root = self.connection.root()
        project = copy.deepcopy(self.root["project"])
        self.connection.close()
        return project

    def save(self, project: Project):
        """
        Commits the project to the ZODB
        """
        if not hasattr(project, "filename"):
            project.filename = input("Enter a filename (without extension):")
        if not hasattr(project, "filepath"):
            tk_root = Tk()
            tk_root.withdraw()
            project.filepath = filedialog.askdirectory(title="Please select a directory")

        filename = project.filename
        full_path = project.filepath + "/" + filename

        if not (self.storage and self.db):
            self.storage=FileStorage(full_path)
            self.db=ZODB.DB(self.storage)
        else:
            self.connection=self.db.open()
            self.root=self.connection.root()

        self.root['project'] = project
        transaction.commit()
        self.connection.close()