Oslo Versioned Objects
Oslo Versioned Objects is an OpenStack library that provides a framework for defining data objects with built-in versioning capabilities. It allows for seamless evolution of object schemas, particularly important in RPC (Remote Procedure Call) contexts where different service versions might communicate. It's currently at version 3.9.0 and is part of the OpenStack Oslo libraries, receiving updates in sync with OpenStack releases.
Common errors
-
TypeError: Object of type MyExampleObject is not JSON serializable
cause Attempting to serialize a VersionedObject instance directly using `json.dumps()` or similar standard JSON encoders.fixUse the provided serializer: `from oslo_versionedobjects import base; serializer = base.VersionedObjectSerializer(); primitive = serializer.serialize_entity(None, my_obj)` -
VersionedObjectNotFound: Object MyExampleObject with version 1.0 could not be found.
cause The object class (or its specific version) was not properly registered with the VersionedObjectRegistry before deserialization or RPC calls, or there's a mismatch between `obj_name`/`obj_version` and the registered objects.fixEnsure your VersionedObject classes are imported and accessible where deserialization occurs. In OpenStack services, this is often handled by a central manager; for standalone use, ensure all object definitions are loaded. -
AttributeError: 'MyExampleObject' object has no attribute 'some_field'
cause Attempting to access a field that was not defined in the `fields` dictionary of the VersionedObject class, or the field was not loaded (if using lazy-loading from `obj_load_attr`).fixVerify that 'some_field' is correctly defined in the `fields` dictionary of `MyExampleObject`. If the object is loaded from a primitive, ensure the primitive contains the field data. For lazy-loaded fields, call `obj_load_attr('some_field')` before access.
Warnings
- breaking Version 3.0.0 and above of `oslo-versionedobjects` explicitly require Python 3.10 or newer. Previous versions (2.x) supported Python 3.6-3.9.
- breaking The `VersionedObject.obj_from_db_object` method was removed in version 3.0.0. This change affects how objects are loaded from database records, requiring direct initialization or alternative loading mechanisms.
- gotcha Directly using `json.dumps()` on a `VersionedObject` instance will often fail with `TypeError: Object of type <YourObject> is not JSON serializable` because VOO instances are complex objects.
Install
-
pip install oslo-versionedobjects
Imports
- VersionedObject
from oslo.versionedobjects import base
from oslo_versionedobjects import base
- Field
from oslo.versionedobjects import fields
from oslo_versionedobjects import fields
- VersionedObjectSerializer
from oslo.versionedobjects import base
from oslo_versionedobjects import base
Quickstart
import uuid
from oslo_versionedobjects import base, fields
# 1. Define your VersionedObject
class MyExampleObject(base.VersionedObject):
# The current version of this object schema.
# Increment this when making backwards-incompatible changes.
VERSION = '1.0'
# Define the fields for your object
fields = {
'id': fields.UUIDField(),
'name': fields.StringField(nullable=False),
'status': fields.StringField(default='active'),
'value': fields.IntegerField(nullable=True, default=0),
}
# Optional: Override __init__ to set defaults or perform custom logic
def __init__(self, context=None, **kwargs):
super().__init__(context, **kwargs)
# Call obj_set_defaults() to ensure default values from fields are applied.
self.obj_set_defaults()
# Optional: Add methods to your object
def activate(self):
if self.status != 'active':
self.status = 'active'
self.obj_make_compatible() # Mark object as changed for serialization
print(f"Object {self.name} activated.")
else:
print(f"Object {self.name} is already active.")
# 2. Instantiate and use your object
# Create an instance with some data
obj_id = str(uuid.uuid4())
my_obj = MyExampleObject(id=obj_id, name="First Item", value=100)
print(f"Initial object: {my_obj.obj_name} v{my_obj.obj_version}")
print(f"ID: {my_obj.id}")
print(f"Name: {my_obj.name}")
print(f"Status: {my_obj.status}")
print(f"Value: {my_obj.value}")
print(f"Is changed? {my_obj.obj_what_changed()}")
my_obj.activate()
print(f"Status after activation: {my_obj.status}")
print(f"Is changed? {my_obj.obj_what_changed()}")
# 3. Serialize and Deserialize (demonstrates versioning capability)
serializer = base.VersionedObjectSerializer()
# Convert the object to a primitive (dictionary) for serialization
primitive = serializer.serialize_entity(None, my_obj)
print("\nSerialized primitive:")
print(primitive)
# Simulate deserialization (e.g., after receiving over RPC)
deserialized_obj = serializer.deserialize_entity(None, MyExampleObject, primitive)
print("\nDeserialized object:")
print(f"Name: {deserialized_obj.name}")
print(f"Status: {deserialized_obj.status}")
print(f"Value: {deserialized_obj.value}")
print(f"Are objects equal? {my_obj == deserialized_obj}")
print(f"Has deserialized object changed? {deserialized_obj.obj_what_changed()}")