ZODB
ZODB (Zope Object Database) is a Python native, object-oriented database designed for transparently and persistently storing Python objects without the need for object-relational mapping (ORM). It provides ACID transactions, multi-version concurrency control (MVCC), and a pluggable storage framework (e.g., FileStorage, ZEO for client-server, RelStorage for RDBMS backends). ZODB is actively maintained, with version 6.3 being the latest, and receives regular updates to support new Python versions and address bugs.
Common errors
-
ConflictError: database conflict error
cause Multiple concurrent transactions attempt to write to the same object in the database, leading to a conflict that ZODB's MVCC cannot resolve without a retry.fixImplement retry logic for transactions (ZODB often retries automatically up to three times but manual handling might be needed for complex scenarios) or redesign your application to reduce contention on heavily accessed objects, possibly by partitioning data or using specialized storages like ZEO for distributed access. -
TypeError: can't concat str to bytes
cause Occurs during Python 3 migration, typically when a ZODB database created under Python 2 is opened with Python 3, and `str` objects (which were bytes in Python 2) are encountered where unicode strings are expected.fixRun the `zodbupdate` migration tool on your Python 2 ZODB database *before* attempting to open it with Python 3. This tool correctly converts pickled `str` objects to either `bytes` or `str` based on their content. -
AttributeError: 'module' object has no attribute 'ClassName' or 'cannot unpickle object'
cause A persistent object was stored in ZODB, but the Python class definition or its module path has since changed, moved, or been removed. The pickling machinery cannot find or load the necessary class to deserialize the object.fixEnsure that all code (modules and classes) referenced by objects in the ZODB is present and importable in the Python environment. If a class or module was renamed or moved, provide 'migration hooks' or 'redirects' in your code to allow the old pickle path to map to the new class location. This often involves creating dummy classes or using `sys.modules` manipulation to intercept old import paths during deserialization.
Warnings
- breaking Migrating ZODB databases from Python 2 to Python 3 requires a specific migration process, primarily due to changes in how Python handles `str` (bytes in Python 2, unicode in Python 3). Direct opening of a Python 2 `.fs` file with Python 3 ZODB is prevented by a different magic code in the file header.
- breaking ZODB 6.0 dropped support for Python 2.7, 3.5, and 3.6. Earlier versions of Python 3 (like 3.7, 3.8, 3.9) have also been dropped in subsequent ZODB 6.x releases.
- gotcha When modifying mutable Python objects (like lists or dictionaries) that are attributes of a `persistent.Persistent` object, ZODB does not automatically detect the change. The parent object needs to be explicitly marked as modified.
- gotcha Changes to the schema (attributes) of persistent classes are not automatically handled by ZODB. While it offers flexibility, evolving object schemas can lead to deserialization errors or unexpected behavior if not explicitly managed.
- breaking ZODB 5.0 introduced significant internal changes to Multi-Version Concurrency Control (MVCC) implementation. Specifically, for storages implementing `IMVCCStorage` (like RelStorage), MVCC is no longer implemented directly within ZODB, simplifying client-server storage implementations. Additionally, `ConnectionPool.map()` was removed.
Install
-
pip install ZODB
Imports
- FileStorage
from ZODB.FileStorage import FileStorage
- DB
from ZODB.DB import DB
- transaction
import transaction
Quickstart
import transaction
from ZODB.FileStorage import FileStorage
from ZODB.DB import DB
from persistent import Persistent
# Define a simple persistent class
class MyPersistentObject(Persistent):
def __init__(self, value):
self.value = value
self.data = {}
# Create a FileStorage (e.g., 'mydata.fs')
# For in-memory, use `DB(None)`
storage = FileStorage('mydata.fs')
db = DB(storage)
connection = db.open()
root = connection.root()
# Perform a transaction
with transaction.manager:
if 'my_app_root' not in root:
root['my_app_root'] = MyPersistentObject('initial value')
root['my_app_root'].data['first'] = 'Hello ZODB'
# Modify an object
app_root = root['my_app_root']
app_root.value = 'updated value'
app_root.data['second'] = 'Another entry'
# Manually mark as changed for mutable nested objects like dicts/lists
app_root.data._p_changed = True
print(f"Stored value: {app_root.value}")
print(f"Stored data: {app_root.data}")
# The 'with' statement automatically commits if no exception, aborts otherwise
# Re-open the database to verify persistence
db.close()
storage = FileStorage('mydata.fs') # Re-open storage
db = DB(storage)
connection = db.open()
root = connection.root()
app_root = root['my_app_root']
print(f"Retrieved value: {app_root.value}")
print(f"Retrieved data: {app_root.data}")
db.close()
storage.close()