{"id":8797,"library":"zodb","title":"ZODB","description":"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.","status":"active","version":"6.3","language":"en","source_language":"en","source_url":"https://github.com/zopefoundation/ZODB","tags":["object-database","persistence","zope","oodb","nosql","python","acid","mvcc"],"install":[{"cmd":"pip install ZODB","lang":"bash","label":"Install latest version"}],"dependencies":[{"reason":"Core library for persistent objects, required by ZODB.","package":"persistent","optional":false},{"reason":"Provides highly optimized persistent B-Tree data structures, often used for indexing in ZODB.","package":"BTrees","optional":false},{"reason":"Used for configuration management, especially for complex ZODB setups.","package":"ZConfig","optional":false},{"reason":"Manages ZODB's ACID transactions (commit, abort, begin).","package":"transaction","optional":false},{"reason":"Provides file-based locking mechanisms for concurrent access control.","package":"zc.lockfile","optional":false},{"reason":"Provides Zope's interface definition and implementation system.","package":"zope.interface","optional":false},{"reason":"Optimized pickling (serialization) for ZODB objects.","package":"zodbpickle","optional":false}],"imports":[{"symbol":"FileStorage","correct":"from ZODB.FileStorage import FileStorage"},{"symbol":"DB","correct":"from ZODB.DB import DB"},{"note":"Used for explicit transaction management (commit, abort).","symbol":"transaction","correct":"import transaction"}],"quickstart":{"code":"import transaction\nfrom ZODB.FileStorage import FileStorage\nfrom ZODB.DB import DB\nfrom persistent import Persistent\n\n# Define a simple persistent class\nclass MyPersistentObject(Persistent):\n    def __init__(self, value):\n        self.value = value\n        self.data = {}\n\n# Create a FileStorage (e.g., 'mydata.fs')\n# For in-memory, use `DB(None)`\nstorage = FileStorage('mydata.fs')\ndb = DB(storage)\nconnection = db.open()\nroot = connection.root()\n\n# Perform a transaction\nwith transaction.manager:\n    if 'my_app_root' not in root:\n        root['my_app_root'] = MyPersistentObject('initial value')\n        root['my_app_root'].data['first'] = 'Hello ZODB'\n\n    # Modify an object\n    app_root = root['my_app_root']\n    app_root.value = 'updated value'\n    app_root.data['second'] = 'Another entry'\n    # Manually mark as changed for mutable nested objects like dicts/lists\n    app_root.data._p_changed = True \n    print(f\"Stored value: {app_root.value}\")\n    print(f\"Stored data: {app_root.data}\")\n    # The 'with' statement automatically commits if no exception, aborts otherwise\n\n# Re-open the database to verify persistence\ndb.close()\nstorage = FileStorage('mydata.fs') # Re-open storage\ndb = DB(storage)\nconnection = db.open()\nroot = connection.root()\n\napp_root = root['my_app_root']\nprint(f\"Retrieved value: {app_root.value}\")\nprint(f\"Retrieved data: {app_root.data}\")\n\ndb.close()\nstorage.close()","lang":"python","description":"This quickstart demonstrates how to create a basic ZODB `FileStorage`, store a `Persistent` object, modify it within a transaction, and then retrieve the persisted data. It highlights the use of `FileStorage`, `DB`, `Connection`, the `root` object (a dictionary-like container), and `transaction.manager` for managing commits and rollbacks. It also shows a common gotcha: marking mutable nested objects as changed."},"warnings":[{"fix":"Use the `zodbupdate` tool and follow the Python 3 migration guide. This involves converting `str` objects that contain binary data into `zodbpickle.binary` and decoding text `str` objects.","message":"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.","severity":"breaking","affected_versions":"All versions when migrating from Python 2.x created databases to Python 3.x."},{"fix":"Ensure your environment uses Python 3.10 or newer (ZODB 6.3 supports Python 3.10-3.14). Refer to the ZODB `CHANGES.rst` for exact Python compatibility per release.","message":"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.","severity":"breaking","affected_versions":"ZODB >= 6.0 (specific Python versions vary by minor release)."},{"fix":"After modifying a mutable attribute (e.g., `my_obj.my_list.append(item)` or `my_obj.my_dict['key'] = value`), explicitly mark the parent persistent object as changed: `my_obj.my_list._p_changed = True` or `my_obj._p_changed = True`.","message":"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.","severity":"gotcha","affected_versions":"All versions."},{"fix":"Plan for schema evolution. For significant changes, write explicit migration scripts to transform existing objects in the database to the new schema. This is similar to database migrations in relational databases but needs to be custom-implemented.","message":"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.","severity":"gotcha","affected_versions":"All versions."},{"fix":"Review applications using `RelStorage` or similar `IMVCCStorage` implementations, and update code that relied on `ConnectionPool.map()` (e.g., iterate `ConnectionPool` directly).","message":"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.","severity":"breaking","affected_versions":"ZODB >= 5.0."}],"env_vars":null,"last_verified":"2026-04-16T00:00:00.000Z","next_check":"2026-07-15T00:00:00.000Z","problems":[{"fix":"Implement 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.","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.","error":"ConflictError: database conflict error"},{"fix":"Run 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.","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.","error":"TypeError: can't concat str to bytes"},{"fix":"Ensure 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.","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.","error":"AttributeError: 'module' object has no attribute 'ClassName' or 'cannot unpickle object'"}]}