Serializable#

class jangada.serialization.Serializable(*args, **kwargs)#

Bases: object

Base class for objects that can be serialized to/from dictionaries.

Serializable provides a framework for converting Python objects to dictionary structures suitable for persistence in HDF5 files or other storage backends. It handles nested objects, collections, primitive types, and special dataset types like NumPy arrays and Pandas timestamps.

Parameters:
*argstuple

Variable positional arguments. Supports: - No args: Initialize from kwargs - One Serializable instance: Copy constructor

**kwargsdict

Keyword arguments mapping property names to values.

Raises:
ValueError

If args don’t match any supported initialization signature, or if kwargs contain unknown property names.

See also

SerializableProperty

Descriptor for serializable properties

SerializableMetatype

Metaclass that enables serialization

Notes

All Serializable properties must be SerializableProperty descriptors. Regular attributes are not included in serialization.

The serialization format uses dictionaries with a special ‘__class__’ key to store the fully qualified class name. This allows accurate reconstruction of the original object type.

When using is_copy=True in serialize(), only properties with copiable=True are included. This is useful for persistence - you can mark cached or temporary properties as non-copiable.

Examples

Define a Serializable class:

>>> class Experiment(Serializable):
...     name = SerializableProperty(default="")
...     temperature = SerializableProperty(default=293.15)
...     data = SerializableProperty(default=None)
...
>>> exp = Experiment(name="Test", temperature=373.15)
>>> exp.name
'Test'

Serialize to a dictionary:

>>> data = Serializable.serialize(exp)
>>> data['__class__']
'__main__.Experiment'
>>> data['name']
'Test'

Deserialize back to an object:

>>> restored = Serializable.deserialize(data)
>>> restored.name
'Test'
>>> restored.temperature
373.15

Copy an object:

>>> copy = exp.copy()
>>> copy is exp
False
>>> copy == exp
True

Nested objects work automatically:

>>> class Trial(Serializable):
...     trial_num = SerializableProperty(default=0)
...     experiment = SerializableProperty(default=None)
...
>>> trial = Trial(trial_num=1, experiment=exp)
>>> data = Serializable.serialize(trial)
>>> restored = Serializable.deserialize(data)
>>> restored.experiment.name
'Test'

Collections are handled recursively:

>>> exp_list = [Experiment(name=f"Exp{i}") for i in range(3)]
>>> serialized = Serializable.serialize(exp_list)
>>> restored = Serializable.deserialize(serialized)
>>> len(restored)
3

Construction and Initialization#

Methods for creating and initializing Serializable objects.

Serializable.__init__(*args, **kwargs)

Initialize a Serializable object.

Serializable.copy()

Create an independent copy of this object.

Serialization Methods#

Static methods for converting objects to/from dictionary structures.

Serializable.serialize(obj[, is_copy])

Recursively serialize an object to a dictionary structure.

Serializable.deserialize(data)

Recursively deserialize data to reconstruct objects.

Comparison and Copying#

Special methods for object comparison and copying protocol support.

Serializable.__eq__(other)

Compare equality based on copiable properties.

Serializable.__copy__()

Support for the copy module.

Type Registration#

Class methods for managing the type registry system.

Serializable.register_primitive_type(...)

Register a type as a primitive that can be serialized as-is.

Serializable.remove_primitive_type(...)

Remove a type from the primitive types registry.

Serializable.is_primitive_type(type_)

Check if a type is registered as a primitive.

Serializable.register_dataset_type(...)

Register a type that requires special handling for serialization.

Serializable.remove_dataset_type(dataset_type)

Remove a dataset type registration.

Serializable.is_dataset_type(type_)

Check if a type is registered as a dataset type.

Read-Only Properties#

Properties that provide information about registered types and properties.

Examples#

Basic Usage#

Define a simple Serializable class:

from jangada.serialization import Serializable, SerializableProperty

class Experiment(Serializable):
    name = SerializableProperty(default="")
    temperature = SerializableProperty(default=293.15)
    data = SerializableProperty(default=None)

Create and serialize an object:

exp = Experiment(name="Test1", temperature=373.15)
data = Serializable.serialize(exp)

# data is now a dictionary:
# {
#     '__class__': 'mymodule.Experiment',
#     'name': 'Test1',
#     'temperature': 373.15,
#     'data': None
# }

Deserialize back to an object:

restored = Serializable.deserialize(data)
print(restored.name)  # 'Test1'
print(restored.temperature)  # 373.15

Nested Objects#

Serialization handles nested Serializable objects automatically:

class Trial(Serializable):
    trial_num = SerializableProperty(default=0)
    experiment = SerializableProperty(default=None)

trial = Trial(trial_num=1, experiment=exp)
data = Serializable.serialize(trial)
restored = Serializable.deserialize(data)

# Nested object is preserved
print(restored.experiment.name)  # 'Test1'

Collections#

Lists, dicts, tuples, and sets are handled recursively:

experiments = [
    Experiment(name="Exp1", temperature=300.0),
    Experiment(name="Exp2", temperature=350.0),
    Experiment(name="Exp3", temperature=400.0)
]

data = Serializable.serialize(experiments)
restored = Serializable.deserialize(data)

print(len(restored))  # 3
print(restored[0].name)  # 'Exp1'

Copying Objects#

Create independent copies with the copy() method:

original = Experiment(name="Original", temperature=300.0)
copied = original.copy()

copied.name = "Modified"
print(original.name)  # 'Original' (unchanged)
print(copied.name)    # 'Modified'

Selective Copying#

Use the copiable flag to control what gets copied:

class DataProcessor(Serializable):
    # This will be copied/serialized
    input_data = SerializableProperty(default=None, copiable=True)

    # This won't be copied (cache, temporary state)
    cached_result = SerializableProperty(default=None, copiable=False)

proc = DataProcessor(input_data=[1, 2, 3], cached_result=[4, 5, 6])

# Copy only includes copiable properties
copied = proc.copy()
print(copied.input_data)      # [1, 2, 3]
print(copied.cached_result)   # None (default)

Custom Primitive Types#

Register custom types that serialize as-is:

from decimal import Decimal

Serializable.register_primitive_type(Decimal)

class FinancialData(Serializable):
    amount = SerializableProperty(default=Decimal('0.00'))

data = FinancialData(amount=Decimal('123.45'))
serialized = Serializable.serialize(data)
# Decimal values pass through unchanged

Custom Dataset Types#

Register types that need conversion to/from arrays:

import numpy as np

class CustomMatrix:
    def __init__(self, data):
        self.data = np.array(data)

def disassemble(matrix):
    """Convert to array and metadata."""
    return matrix.data, {'shape': matrix.data.shape}

def assemble(arr, attrs):
    """Reconstruct from array and metadata."""
    return CustomMatrix(arr)

Serializable.register_dataset_type(
    CustomMatrix,
    disassemble=disassemble,
    assemble=assemble
)

NumPy and Pandas Integration#

NumPy arrays and Pandas timestamps work automatically:

import numpy as np
import pandas as pd

class Measurement(Serializable):
    timestamp = SerializableProperty(default=None)
    values = SerializableProperty(default=None)

measurement = Measurement(
    timestamp=pd.Timestamp('2024-01-15 12:30:00', tz='UTC'),
    values=np.array([1.2, 3.4, 5.6])
)

data = Serializable.serialize(measurement)
restored = Serializable.deserialize(data)

# Timezone and array are preserved
print(restored.timestamp.tz)  # <UTC>
print(restored.values.shape)   # (3,)

Equality Comparison#

Objects compare based on their copiable properties:

exp1 = Experiment(name="Test", temperature=300.0)
exp2 = Experiment(name="Test", temperature=300.0)
exp3 = Experiment(name="Other", temperature=300.0)

print(exp1 == exp2)  # True (same values)
print(exp1 == exp3)  # False (different name)

See Also#

  • serializable_property - Property descriptor for serializable attributes

  • serializable_metatype - Metaclass documentation

  • Type Aliases - Type aliases used in the API

  • hdf5_persistence - Using Serializable with HDF5 storage (if available)