.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "gallery/fundamentals/example0_serializables.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note Click :ref:`here ` to download the full example code .. rst-class:: sphx-glr-example-title .. _sphx_glr_gallery_fundamentals_example0_serializables.py: Defining Serializables ======================== This example describes the various ways in which one can construct new serializable objects. .. GENERATED FROM PYTHON SOURCE LINES 10-13 Introduction ----------------- To run the example you will need the following imports: .. GENERATED FROM PYTHON SOURCE LINES 13-20 .. code-block:: default import dman import numpy as np from dataclasses import dataclass, asdict from dman.core.serializables import serializable .. GENERATED FROM PYTHON SOURCE LINES 21-46 The base objects in ``dman`` are ``serializables``. Any ``serializable`` instance is defined implicitly such that the following operations return the original object. .. code-block:: python ser: dict = dman.serialize(obj) res: str = dman.sjson.dumps(ser) ser: dict = dman.sjson.loads(res) assert(obj == dman.deserialize(res)) The goal is that the string ``res`` can be stored in a human readable file. .. note:: We used ``sjson`` instead of ``json`` for dumping the dictionary to a string. This replaces any unserializable objects with a placeholder string. This corresponds with the ideals behind ``dman``, one of which is that (some) serialization should always be produced. By default, several types are serializable. Specifically: ``str``, ``int``, ``float``, ``bool``, ``NoneType``, ``list``, ``dict``, ``tuple``. Collections can be nested. Note that ``tuple`` is somewhat of an exception since it is deserialized as a list. We are however able to extend upon these basic types, which is the topic of this example. .. GENERATED FROM PYTHON SOURCE LINES 48-58 Creating Serializables ------------------------- There are several ways of creating a ``serializable`` class from scratch. You can either do it manually or use some code generation functionality build into ``dman``. Manual Definition ^^^^^^^^^^^^^^^^^^^ The standard way of defining a serializable is as follows: .. GENERATED FROM PYTHON SOURCE LINES 58-74 .. code-block:: default @dman.serializable(name='manual') class Manual: def __init__(self, value: str): self.value = value def __repr__(self): return f'Manual(value={self.value})' def __serialize__(self): return {'value': self.value} @classmethod def __deserialize__(cls, ser: dict): return cls(**ser) .. GENERATED FROM PYTHON SOURCE LINES 75-76 We can serialize the object .. GENERATED FROM PYTHON SOURCE LINES 76-81 .. code-block:: default test = Manual(value='hello world!') ser = dman.serialize(test) res = dman.sjson.dumps(ser, indent=4) print(res) .. rst-class:: sphx-glr-script-out .. code-block:: none { "_ser__type": "manual", "_ser__content": { "value": "hello world!" } } .. GENERATED FROM PYTHON SOURCE LINES 82-85 Note how the dictionary under ``_ser__content`` is the output of our ``__serialize__`` method. The type name is also added such that the dictionary can be interpreted correctly. We can ``deserialize`` a dictionary created like this as follows: .. GENERATED FROM PYTHON SOURCE LINES 85-90 .. code-block:: default ser = dman.sjson.loads(res) test = dman.deserialize(ser) print(test) .. rst-class:: sphx-glr-script-out .. code-block:: none Manual(value=hello world!) .. GENERATED FROM PYTHON SOURCE LINES 91-99 .. note:: It is possible to not include the serializable type and deserialize by specifying the type manually using the following syntax .. code-block:: python ser = dman.serialize(test, content_only=True) reconstructed: Manual = dman.deserialize(ser, ser_type=Manual) .. GENERATED FROM PYTHON SOURCE LINES 101-105 .. warning:: The name provided to ``@serializable`` should be unique within your library. It is used as the identifier of the class by ``dman`` when deserializing. .. GENERATED FROM PYTHON SOURCE LINES 108-117 Automatic Generation ^^^^^^^^^^^^^^^^^^^^^^ Of course it would not be convenient to manually specify the ``__serialize__`` and ``__deserialize__`` methods. Hence, the ``serializable`` decorator has been implemented to automatically generate them whenever the class is an instance of ``Enum`` or a ``dataclass`` (and when no prior ``__serialize__`` and ``__deserialize__`` methods are specified). So in the case of enums: .. GENERATED FROM PYTHON SOURCE LINES 117-128 .. code-block:: default from enum import Enum @dman.serializable(name='mode') class Mode(Enum): RED = 1 BLUE = 2 ser = dman.serialize(Mode.RED) print(dman.sjson.dumps(ser, indent=4)) .. rst-class:: sphx-glr-script-out .. code-block:: none { "_ser__type": "mode", "_ser__content": "Mode.RED" } .. GENERATED FROM PYTHON SOURCE LINES 129-130 In the case of ``dataclasses`` we get the following: .. GENERATED FROM PYTHON SOURCE LINES 130-143 .. code-block:: default from dataclasses import dataclass @dman.serializable(name='dcl_basic') @dataclass class DCLBasic: value: str test = DCLBasic(value='hello world!') ser = dman.serialize(test) print(dman.sjson.dumps(ser, indent=4)) .. rst-class:: sphx-glr-script-out .. code-block:: none { "_ser__type": "dcl_basic", "_ser__content": { "value": "hello world!" } } .. GENERATED FROM PYTHON SOURCE LINES 144-182 As long as all of the fields in the dataclass are serializable, the whole will be as well. .. warning:: Be careful when specifying the name that it is unique. It is used to reconstruct an instance of a class based on the ``_ser__type`` string. If a name is left unspecified, the value under ``__name__`` in the class will be used. .. warning:: In almost all cases it will be better to use ``@dman.modelclass`` when converting a ``dataclass`` into a ``serializable``. This is mostly important when some fields are ``storable``, in which case they will be handled automatically. See :ref:`sphx_glr_gallery_fundamentals_example3_models.py` for an overview of the ``modelclass`` decorator. .. note:: It is possible to have fields in your dataclass that you don't want serialized. '' .. code-block:: python from dataclasses import dataclass @serializable(name='dcl_basic') @dataclass class DCLBasic: __no_serialize__ = ['hidden'] value: str hidden: int = 0 The field names in ``__no_serialize__`` will not be included in the serialized ``dict``. Note that this means that you should specify a default value for these fields to support deserialization. .. GENERATED FROM PYTHON SOURCE LINES 184-199 Serializing Existing Types ----------------------------- Often you will already have some objects in a library that should also be made serializable. In ``dman`` we provide some functionality that makes this process simpler. Registered Definition ^^^^^^^^^^^^^^^^^^^^^^^^ The most flexible way of making a class serializable is by registering it manually. This is especially useful when the original class definition cannot be manipulated (for example for ``numpy.ndarray``). Say we have some frozen class definition: .. GENERATED FROM PYTHON SOURCE LINES 199-206 .. code-block:: default class Frozen: def __init__(self, data: int): self.data = data def __repr__(self): return f'{self.__class__.__name__}(data={self.data})' .. GENERATED FROM PYTHON SOURCE LINES 207-209 We can make it serializable without touching the original class definition as follows: .. GENERATED FROM PYTHON SOURCE LINES 209-216 .. code-block:: default dman.register_serializable( 'frozen', Frozen, serialize=lambda frozen: frozen.data, deserialize=lambda data: Frozen(data) ) .. GENERATED FROM PYTHON SOURCE LINES 217-218 Now we can serialize frozen itself: .. GENERATED FROM PYTHON SOURCE LINES 218-223 .. code-block:: default frozen = Frozen(data=42) ser = dman.serialize(frozen) res = dman.sjson.dumps(ser, indent=4) print(res) .. rst-class:: sphx-glr-script-out .. code-block:: none { "_ser__type": "frozen", "_ser__content": 42 } .. GENERATED FROM PYTHON SOURCE LINES 224-225 And deserialize it .. GENERATED FROM PYTHON SOURCE LINES 225-229 .. code-block:: default ser = dman.sjson.loads(res) frozen = dman.deserialize(ser) print(frozen) .. rst-class:: sphx-glr-script-out .. code-block:: none Frozen(data=42) .. GENERATED FROM PYTHON SOURCE LINES 230-232 You can take a look at ``dman.numerics`` to see an example of this in practice. .. GENERATED FROM PYTHON SOURCE LINES 234-242 Templates ^^^^^^^^^^^^^^^^^^^ In many cases however it will be possible to alter the original class. So say we have some user class that is used all throughout your library: .. GENERATED FROM PYTHON SOURCE LINES 242-249 .. code-block:: default class User: def __init__(self, name: int): self.name = name def __repr__(self): return f'{self.__class__.__name__}(id={self.name})' .. GENERATED FROM PYTHON SOURCE LINES 250-253 We would like to make ``User`` serializable without defining ``__serialize__`` and ``__deserialize__`` manually. We can do so using a template: .. GENERATED FROM PYTHON SOURCE LINES 253-265 .. code-block:: default @dman.serializable @dataclass class UserTemplate: name: str @classmethod def __convert__(cls, other: 'User'): return cls(other.name) def __de_convert__(self): return User(self.name) .. GENERATED FROM PYTHON SOURCE LINES 266-269 A template has a method that allows conversion from the original class to the template and a method to undo that conversion. .. GENERATED FROM PYTHON SOURCE LINES 271-272 Using a template we can then make ``User`` itself serializable like this: .. GENERATED FROM PYTHON SOURCE LINES 272-274 .. code-block:: default serializable(User, name='user', template=UserTemplate) .. GENERATED FROM PYTHON SOURCE LINES 275-276 Now we can serialize a user: .. GENERATED FROM PYTHON SOURCE LINES 276-281 .. code-block:: default user = User(name='Thomas Anderson') ser = dman.serialize(user) res = dman.sjson.dumps(ser, indent=4) print(res) .. rst-class:: sphx-glr-script-out .. code-block:: none { "_ser__type": "user", "_ser__content": { "name": "Thomas Anderson" } } .. GENERATED FROM PYTHON SOURCE LINES 282-284 However this does make an adjustment to the class. Specifically a field ``_ser__type`` is added: .. GENERATED FROM PYTHON SOURCE LINES 284-286 .. code-block:: default print(getattr(User, '_ser__type')) .. rst-class:: sphx-glr-script-out .. code-block:: none user .. GENERATED FROM PYTHON SOURCE LINES 287-292 Using templates can also be useful when you are able to work with subclasses of some ``Base`` class instead. So say you start with some ``Base`` class: .. GENERATED FROM PYTHON SOURCE LINES 292-303 .. code-block:: default class Base: def __init__(self, data: int, computation: int = None): self.data = data self.computation = computation def compute(self): self.computation = self.data**2 def __repr__(self): return f'{self.__class__.__name__}(data={self.data}, computation={self.computation})' .. GENERATED FROM PYTHON SOURCE LINES 304-306 We want to create a subtype of this class that is serializable without defining the ``__serialize__`` method manually. .. GENERATED FROM PYTHON SOURCE LINES 306-321 .. code-block:: default @dman.serializable @dataclass class Template: data: int computation: int @classmethod def __convert__(cls, other: 'SBase'): return cls(other.data, other.computation) @dman.serializable(name='base', template=Template) class SBase(Base): ... .. GENERATED FROM PYTHON SOURCE LINES 322-326 So we defined a template class with a convert method from ``Base`` and similarly we defined a serializable subclass of ``Base`` that can be converted from ``Template``. Now we can serialize an instance of ``SBase`` as follows: .. GENERATED FROM PYTHON SOURCE LINES 326-333 .. code-block:: default base = SBase(data=25) base.compute() ser = dman.serialize(base) res = dman.sjson.dumps(ser, indent=4) print(res) .. rst-class:: sphx-glr-script-out .. code-block:: none { "_ser__type": "base", "_ser__content": { "data": 25, "computation": 625 } } .. GENERATED FROM PYTHON SOURCE LINES 334-335 And we can deserialize it too .. GENERATED FROM PYTHON SOURCE LINES 335-340 .. code-block:: default ser = dman.sjson.loads(res) base = dman.deserialize(ser) print(base) .. rst-class:: sphx-glr-script-out .. code-block:: none SBase(data=25, computation=625) .. GENERATED FROM PYTHON SOURCE LINES 341-345 Note how we did not specify in the above example how to go from an instance of ``Template`` to one of ``SBase``. Such a ``__convert__`` method was actually generated automatically. We could have instead specified the same behavior manually as follows: .. GENERATED FROM PYTHON SOURCE LINES 345-352 .. code-block:: default @dman.serializable(name='base', template=Template) class SBase(Base): @classmethod def __convert__(cls, other: Template): return cls(**asdict(other)) .. GENERATED FROM PYTHON SOURCE LINES 353-357 Specifying this conversion manually could be relevant if the fields of the ``Template`` dataclass do not match the ones for the ``__init__`` method of ``Base``. For example we could have had: .. GENERATED FROM PYTHON SOURCE LINES 357-369 .. code-block:: default class Base: def __init__(self, data: int): self.data = data self.computation = None def compute(self): self.computation = self.data**2 def __repr__(self): return f'{self.__class__.__name__}(data={self.data}, computation={self.computation})' .. GENERATED FROM PYTHON SOURCE LINES 370-372 So the value of ``computation`` cannot be passed to the constructor. We can however compensate for this in the ``__convert__`` method: .. GENERATED FROM PYTHON SOURCE LINES 372-381 .. code-block:: default @dman.serializable(name='base', template=Template) class SBase(Base): @classmethod def __convert__(cls, other: Template): res = cls(other.data) res.computation = other.computation return res .. GENERATED FROM PYTHON SOURCE LINES 382-387 Serializing Instances -------------------------------- In some settings it is useful to serialize instances directly. One common example is methods. .. GENERATED FROM PYTHON SOURCE LINES 388-396 .. code-block:: default from math import sqrt @dman.register_instance(name='ell1') def ell1(x, y): return abs(x) + abs(y) @dman.register_instance(name='ell2') def ell2(x, y): return sqrt(x**2 + y**2) .. GENERATED FROM PYTHON SOURCE LINES 397-398 When serializing the result looks as follows: .. GENERATED FROM PYTHON SOURCE LINES 398-401 .. code-block:: default ser = dman.serialize([ell1, ell2]) dman.tui.print_serialized(ser) .. rst-class:: sphx-glr-script-out .. code-block:: none [ { "_ser__type": "__instance", "_ser__content": "ell1" }, { "_ser__type": "__instance", "_ser__content": "ell2" } ] .. GENERATED FROM PYTHON SOURCE LINES 402-403 Deserialization then works as expected. .. GENERATED FROM PYTHON SOURCE LINES 403-406 .. code-block:: default dser = dman.deserialize(ser) print(dser) .. rst-class:: sphx-glr-script-out .. code-block:: none [, ] .. GENERATED FROM PYTHON SOURCE LINES 407-408 For specific instances we can also call `register_instance` inline. .. GENERATED FROM PYTHON SOURCE LINES 408-416 .. code-block:: default class Auto: ... AUTO = Auto() dman.register_instance(AUTO, name='auto') dman.tui.print_serialized(dman.serialize(AUTO)) .. rst-class:: sphx-glr-script-out .. code-block:: none { "_ser__type": "__instance", "_ser__content": "auto" } .. rst-class:: sphx-glr-timing **Total running time of the script:** ( 0 minutes 0.026 seconds) .. _sphx_glr_download_gallery_fundamentals_example0_serializables.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: example0_serializables.py ` .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: example0_serializables.ipynb ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_