{"id":19798614,"url":"https://github.com/eriknyquist/versionedobj","last_synced_at":"2025-05-01T05:30:26.235Z","repository":{"id":58112051,"uuid":"530041124","full_name":"eriknyquist/versionedobj","owner":"eriknyquist","description":"Easy object serialization and versioning framework for python","archived":false,"fork":false,"pushed_at":"2023-04-08T04:16:06.000Z","size":3646,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-06T08:34:16.971Z","etag":null,"topics":["config-file","config-file-generator","config-files","configuration-files","deserialization","json","marshal","marshaling","marshall","marshalling","object-deserialization","object-serialization","python","python3","serialization","tools","unmarshal","unmarshalling","utility","versioning"],"latest_commit_sha":null,"homepage":"https://eriknyquist.github.io/versionedobj/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eriknyquist.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":"CONTRIBUTING.rst","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-29T02:36:38.000Z","updated_at":"2024-06-02T17:13:31.000Z","dependencies_parsed_at":"2024-11-12T07:32:45.855Z","dependency_job_id":"c63c902a-c710-4084-ba1e-93c70313e955","html_url":"https://github.com/eriknyquist/versionedobj","commit_stats":{"total_commits":164,"total_committers":1,"mean_commits":164.0,"dds":0.0,"last_synced_commit":"7b66afa8fea8e49c23870ae08237346e9fde7117"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eriknyquist%2Fversionedobj","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eriknyquist%2Fversionedobj/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eriknyquist%2Fversionedobj/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eriknyquist%2Fversionedobj/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eriknyquist","download_url":"https://codeload.github.com/eriknyquist/versionedobj/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251830449,"owners_count":21650802,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["config-file","config-file-generator","config-files","configuration-files","deserialization","json","marshal","marshaling","marshall","marshalling","object-deserialization","object-serialization","python","python3","serialization","tools","unmarshal","unmarshalling","utility","versioning"],"created_at":"2024-11-12T07:30:47.538Z","updated_at":"2025-05-01T05:30:24.431Z","avatar_url":"https://github.com/eriknyquist.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Object serialization \u0026 versioning framework for python 3x\n=========================================================\n\n.. |tests_badge| image:: https://github.com/eriknyquist/versionedobj/actions/workflows/tests.yml/badge.svg\n.. |cov_badge| image:: https://github.com/eriknyquist/versionedobj/actions/workflows/coverage.yml/badge.svg\n.. |version_badge| image:: https://badgen.net/pypi/v/versionedobj\n.. |license_badge| image:: https://badgen.net/pypi/license/versionedobj\n.. |codeclimate_badge| image:: https://api.codeclimate.com/v1/badges/77e77f051600a584019a/maintainability\n\n|tests_badge| |cov_badge| |version_badge| |license_badge| |codeclimate_badge|\n\n.. contents:: Table of Contents\n\n**versionedobj** is an object serialization framework that allows you to create\ncomplex python objects that can be serialized/deserialized to and from strings,\nor dicts, or JSON files.\n\n**versionedobj** also provides a versioning mechanism, to track changes in object\nstructure across time, and to migrate between different object versions.\n\nSee `API documentation \u003chttps://eriknyquist.github.io/versionedobj/versionedobj.html\u003e`_\n\n\nInstalling\n----------\n\nInstall ``versionedobj`` using pip:\n\n::\n\n    pip install versionedobj\n\nGetting started\n---------------\n\nObject definition\n*****************\n\nDefine objects by creating a new class that inherits from ``VersionedObject``,\nand set class attributes to define your object attributes:\n\n.. code:: python\n\n    from versionedobj import VersionedObjbect\n\n    class UserConfig(VersionedObject):\n        version = \"v1.0.0\"\n        username = \"john smith\"\n        friend_list = [\"user1\", \"user2\", \"user3\"]\n\nYou can also nest VersionedObjects by simply assigning another ``VersionedObject``\nclass or instance object to a class attribute:\n\n.. code:: python\n\n    from versionedobj import VersionedObject\n\n    class DisplayConfig(VersionedObject):\n        display_mode = \"windowed\"\n        resolution = \"1920x1080\"\n        volume = 0.66\n\n    # Populate class attributes to build your object\n    class UserConfig(VersionedObject):\n        version = \"v1.0.0\"\n        username = \"john smith\"\n        friend_list = [\"user1\", \"user2\", \"user3\"]\n        display_config = DisplayConfig() # VersionedObjects can be nested\n\n        # Nested VersionedObjects can be a class object, or an instance of the\n        # class, either way will behave the same\n\n        # display_config = DisplayConfig\n\nCreating object instances and accessing object attributes\n*********************************************************\n\nThe values you set on the class attributes of a ``VersionedObject`` serve as the default\nvalues for that object. When you create an instance of your ``VersionedObject`` class,\ninstance attributes will automatically be created to match the class attributes, and\nthe values of the class attributes will be copied over to the instance attributes:\n\n.. code:: python\n\n    obj = UserConfig()\n\n    print(obj.friend_list)\n    # Output looks like this: [\"user1\", \"user2\", \"user3\"]\n\n    print(obj.display_config.display_mode)\n    # Output looks like this: \"windowed\"\n\nAs well as regular dot notation, you can also treat an object instance like a dict,\nand access individual attributes using their full dot name as the key:\n\n.. code:: python\n\n    print(obj['friend_list'])\n    # Output looks like this: [\"user1\", \"user2\", \"user3\"]\n\n    print(obj['display_config.display_mode'])\n    # Output looks like this: \"windowed\"\n\n    # Change the value of an instance attribute\n    obj['display_config.display_mode'] = \"fullscreen\"\n\n    print(obj['display_config.display_mode'])\n    # Output looks like this: \"fullscreen\"\n\nYou can also treat a ``VersionedObjbect`` instance as an iterable, to iterate\nover all object attribute names, as you would with keys in a dict:\n\n.. code:: python\n\n    for attr_name in obj:\n        print(f\"{attr_name}: {obj[attr_name]}\")\n\n    # Output looks like this:\n    #\n    # version: v1.0.0\n    # username: john smith\n    # friend_list: [\"user1\", \"user2\", \"user3\"]\n    # display_config.display_mode: windowed\n    # display_config.resolution: 1920x1080\n    # display_config.volume: 0.66\n\nSerializing and de-serializing\n******************************\n\nCreate an instance of the ``versionedobj.Serializer`` class, and use the ``to_file``\nand ``from_file`` methods to serialize/deserialize data to/from a JSON file:\n\n.. code:: python\n\n    from versionedobj import VersionedObject, Serializer\n\n    class DisplayConfig(VersionedObject):\n        display_mode = \"windowed\"\n        resolution = \"1920x1080\"\n        volume = 0.66\n\n    class UserConfig(VersionedObject):\n        version = \"v1.0.0\"\n        username = \"john smith\"\n        friend_list = [\"user1\", \"user2\", \"user3\"]\n        display_config = DisplayConfig() # VersionedObjects can be nested\n\n    # Create an instance of our VersionedObject\n    obj = UserConfig()\n\n    # Create a serializer instance\n    serializer = Serializer(obj)\n\n    # Save object instance to JSON file\n    serializer.to_file('user_config.json', indent=4)\n\n    # Load JSON file and populate the same object instance\n    serializer.from_file('user_config.json')\n\nYou can also save/load object data as a JSON string:\n\n.. code:: python\n\n    # Save object instance to JSON string\n    obj_as_json = serializer.to_json(indent=4)\n\n    # Load object instance from JSON string\n    serializer.from_json(obj_as_json)\n\nOr, as a dict:\n\n.. code:: python\n\n    # Save object instance to dict\n    obj_as_dict = serializer.to_dict()\n\n    # Load object instance from dict\n    serializer.from_dict(obj_as_dict)\n\nUsing one Serializer instance with multiple object types\n--------------------------------------------------------\n\nFor convenience, you can pass an object instance when you create a ``versionedobj.Serializer``,\nand this object will be used for all future serialization/deserialization operations,\nso that you don't have to pass in the object instance every time (as shown in previous\nexamples).\n\nHowever, this is not required, and you can optionally provide an object instance\nfor all serialization/deserialization methods, if you want to (for example) use\na single ``versionedobj.Serializer`` instance for multiple object types:\n\n.. code:: python\n\n    from versionedobj import VersionedObject, Serializer\n\n    class ObjectA(VersionedObject):\n        name = \"john\"\n        age = 44\n\n    class ObjectB(VersionedObject):\n        last_login_time = 12345678\n        enabled = False\n\n    # Create an instance of each object\n    a = ObjectA()\n    b = ObjectB()\n    serializer = Serializer()\n\n    # Serialize both objects using the same serializer\n    a_jsonstr = serializer.to_json(a)\n    b_jsonstr = serializer.to_json(b)\n\n    # De-serialize both objects using the same serializer\n    serializer.from_json(a_jsonstr, a)\n    serializer.from_json(b_jsonstr, b)\n\nFiltering serialization/deserialization output\n----------------------------------------------\n\nWhitelisting by field name\n**************************\n\nWhen serializing, if you only want to output certain fields, you can use the 'only'\nparameter to specify which fields should be output (effectively a whitelist by field name):\n\n.. code:: python\n\n    serializer.to_file('user_config.json', only=['version', 'username', 'display_config.resolution'])\n\n    # Output looks like this:\n    #\n    # {\n    #     \"version\": \"v1.0.0\",\n    #     \"username\": \"jane doe\",\n    #     \"display_config\": {\n    #         \"resolution\": \"1920x1080\",\n    #     }\n    # }\n\nThe same parameter can be used for de-serializing:\n\n.. code:: python\n\n    serializer.from_file('user_config.json', only=['display_config.display_mode'])\n\n    # Only the 'display_config.display_mode' field is loaded from the file\n\nBlacklisting by field name\n**************************\n\nWhen serializing, if you *don't* want to output certain fields, you can use the 'ignore'\nparameter to specify which fields should be excluded from output (effectively a blacklist\nby field name):\n\n.. code:: python\n\n    serializer.to_file('user_config.json', ignore=['friend_list', 'display_config.volume'])\n\n    # Output looks like this:\n    #\n    # {\n    #     \"version\": \"v1.0.0\",\n    #     \"username\": \"jane doe\",\n    #     \"display_config\": {\n    #         \"display_mode\": \"windowed\",\n    #         \"resolution\": \"1920x1080\"\n    #     }\n    # }\n\nThe same parameter can be used for de-serializing:\n\n.. code:: python\n\n    serializer.from_file('user_config.json', ignore=['friend_list'])\n\n    # Every field except for the 'friend_list' field is loaded from the file\n\nversionedobj.ListField: store a sequence of objects in a single field\n---------------------------------------------------------------------\n\n``versionedobj.ListField`` is a list class that behaves exactly like a regular python list,\nexcept for the following 2 differences:\n\n* Only instances of a class which is a subclass of the ``VersionedObject`` may be added to lists\n  (ValueError is raised otherwise)\n* Only instances of the same class may be added to a single list (ValueError is raised otherwise)\n\nYou can assign a ``versionedobj.ListField`` instance as the value for a field in your versioned object\nclass definition, and that field can then hold a sequence of multiple versioned objects. This\nis useful if you need to store a variably-sized collection of objects that are created a runtime.\n\n.. code:: python\n\n    from versionedobj import VersionedObject, Serializer, ListField\n\n    # The list will contain objects of this type only\n    class UserData(VersionedObject):\n        name = \"john\"\n        age = 30\n\n    # This object will contain a list of multiple users\n    class AllUserData(VersionedObject):\n        # a List may only contain instances of the same class\n        users = ListField(UserData)\n\n    all_user_data = AllUserData()\n\n    # Add some users to the list\n    all_user_data.users.append(UserData(initial_values={'name': 'sam', 'age': 66}))\n    all_user_data.users.append(UserData(initial_values={'name': 'sally', 'age': 28}))\n\n    # Serialize object and print out JSON data\n    print(Serializer(all_user_data).to_json(indent=4))\n\n    # Output looks like this:\n    #\n    # {\n    #     \"users\": [\n    #         {\n    #             \"name\": \"sam\",\n    #             \"age\": 66\n    #         },\n    #         {\n    #             \"name\": \"sally\",\n    #             \"age\": 28\n    #         }\n    #     ]\n    # }\n\nContext manager for loading \u0026 editing saved object data\n-------------------------------------------------------\n\nIf you want to load object data from a JSON file, make some changes to the data,\nand save it back to the same JSON file, then you can use the ``FileLoader`` context\nmanager, which will load/create the file for you on entry, return a deserialized\nobject for you to modify, and then serializes your modified object back to the same\nfile on exit. This may be useful if you are worried about forgetting to re-serialize\nthe object when you are done.\n\n.. code:: python\n\n    from versionedobj import VersionedObject, FileLoader\n\n    class Recipe(VersionedObject):\n        ingredient_1 = \"onions\"\n        ingredient_2 = \"tomatoes\"\n        ingredient_3 = \"garlic\"\n\n    # Creates a new instance of the object, and loads data from\n    # \"recipe.json\" if the file already exists\n    with FileLoader(Recipe, \"recipe.json\") as obj:\n        # Change something\n        obj.ingredient_3 = \"celery\"\n\n    # recipe.json now looks like this:\n    #\n    # {\n    #     \"ingredient_1\": \"onions\",\n    #     \"ingredient_2\": \"tomatoes\",\n    #     \"ingredient_3\": \"celery\",\n    # }\n\nMigrations: making use of the version number\n--------------------------------------------\n\nA VersionedObject object can have a ``version`` attribute, which can be any object,\nalthough it is typically a string (e.g. ``\"v1.2.3\"``). This version attribute can be\nused to support migrations for older objects, in the event that you need to\nchange the format of your object.\n\nExample scenario, part 1: you have created a beautiful versioned object\n***********************************************************************\n\nLet's take the same config file definition from the previous example:\n\n.. code:: python\n\n    from versionedobj import VersionedObject\n\n    # Nested config object\n    class DisplayConfig(VersionedObject):\n        display_mode = \"windowed\"\n        resolution = \"1920x1080\"\n        volume = 0.66\n\n    # Top-level config object with another nested config object\n    class UserConfig(VersionedObject):\n        version = \"v1.0.0\"\n        username = \"john smith\"\n        friend_list = [\"user1\", \"user2\", \"user3\"]\n        display_config = DisplayConfig()\n\nImagine you've already released this code out into the world. People are already\nusing it, and they have JSON files generated by your ``UserConfig`` class sitting\non their computers.\n\nExample scenario, part 2: you update your software, modifying the versioned object\n**********************************************************************************\n\nNow, imagine you are making a new release of your software, and some new features\nrequire you to make the following changes to your versioned object:\n\n* remove the the ``DisplayConfig.resolution`` field entirely\n* change the name of ``DisplayConfig.volume`` to ``DisplayConfig.volumes``\n* change the value of ``DisplayConfig.volumes`` from a float to a list\n\n.. code:: python\n\n    from versionedobj import VersionedObject\n\n    # Nested config object\n    class DisplayConfig(VersionedObject):\n        display_mode = \"windowed\"\n        # 'resolution' field is deleted\n        volumes = [0.66, 0.1] # 'volume' is now called 'volumes', and is a list\n\n    # Top-level config object with another nested config object\n    class UserConfig(VersionedObject):\n        version = \"v1.0.0\"\n        username = \"john smith\"\n        friend_list = [\"user1\", \"user2\", \"user3\"]\n        display_config = DisplayConfig()\n\nUh-oh, you have a problem...\n****************************\n\nRight now, if you send this updated UserConfig class to your existing users, it will fail\nto load their existing JSON files with version ``v1.0.0``, since those files will contain\nthe ``DisplayConfig.resolution`` field that we deleted in ``v1.0.1``, and\n``DisplayConfig.volume`` will similarly be gone, having been replaced with\n``DisplayConfig.volumes``. This situation is what migrations are for.\n\nSolution-- migrations!\n**********************\n\nThe solution is to:\n\n#. Change the version number to something new, e.g. ``v1.0.0`` becomes ``v1.0.1``\n#. Write a migration function to transform ``v1.0.0`` object data into ``v1.0.1`` object data\n#. Use the ``versionedobj.migration`` decorator to register your migration function\n\n.. code:: python\n\n    from versionedobj import VersionedObject, migration\n\n    # Nested config object\n    class DisplayConfig(VersionedObject):\n        display_mode = \"windowed\"\n        # 'resolution' field is deleted\n        volumes = [0.66, 0.1] # 'volume' is now called 'volumes', and is a list\n\n    # Top-level config object with another nested config object\n    class UserConfig(VersionedObject):\n        version = \"v1.0.1\" # Version has been updated to 1.0.1\n        username = \"john smith\"\n        friend_list = [\"user1\", \"user2\", \"user3\"]\n        display_config = DisplayConfig()\n\n    # Create the migration function for v1.0.0 to v1.0.1\n    @migration(UserConfig, \"v1.0.0\", \"v1.0.1\")\n    def migrate_100_to_101(attrs):\n        del attrs['display_config']['resolution']        # Delete resolution field\n        del attrs['display_config']['volume']            # Delete volume field\n        attrs['display_config']['volumes'] = [0.66, 0.1] # Add defaults for new volume values\n        return attrs                                     # Return modified data (important!)\n\nafter you add the migration function and update the version to ``v1.0.1``, JSON files\nthat are loaded and contain the version ``v1.0.0`` will be automatically migrated to version\n``v1.0.1`` using the migration function you added.\n\nThe downside to this approach, is that you have to manually udpate the version number,\nand write a new migration function, anytime the structure of your config data changes.\n\nThe upside, of course, is that you can relatively easily support migrating any older\nversion of your config file to the current version.\n\nIf you don't need the versioning/migration functionality, just never change your version\nnumber, or don't create a ``version`` attribute on your ``VersionedObject`` classes.\n\nMigrations: migrating an unversioned object\n-------------------------------------------\n\nYou may run into a situation where you release an unversioned object, but then\nlater you need to make changes, and migrate an unversioned object to a versioned object.\n\nThis can be handled simply by passing \"None\" to the \"add_migration()\" method, for the\n\"from_version\" parameter. For example:\n\n.. code:: python\n\n    from versionedobj import VersionedObj, migration\n\n    class UserConfig(VersionedObject):\n        version = \"v1.0.0\"\n        username = \"\"\n        friend_list = []\n\n    @migration(UserConfig, None, \"v1.0.0\")\n    def migrate_none_to_100(attrs);\n        attrs['friend_list'] = [] # Add new 'friend_list' field\n        return attrs\n\n\nValidating input data without deserializing\n-------------------------------------------\n\nYou may want to validate some serialized object data without actually deserializing\nand loading the object values. You can use the ``Serializer.validate_dict`` method for this.\n\n.. code:: python\n\n    from versionedobj import VersionedObject, Serializer\n\n    class Recipe(VersionedObject):\n        ingredient_1 = \"onions\"\n        ingredient_2 = \"tomatoes\"\n        ingredient_3 = \"garlic\"\n\n    rcp = Recipe()\n    serializer = Serializer(rcp)\n\n    serializer.validate_dict({\"ingredient_1\": \"celery\", \"ingredient_2\": \"carrots\"})\n    # Raises versionedobj.exceptions.InputValidationError because 'ingredient_3' is missing\n\n    serializer.validate_dict({\"ingredient_1\": \"celery\", \"ingredient_2\": \"carrots\", \"ingredient_12\": \"cumin\"})\n    # Raises versionedobj.exceptions.InputValidationError because 'ingredient_12' is not a valid attribute\n\nResetting object instance to default values\n-------------------------------------------\n\nYou can use the ``Serializer.reset_to_defaults`` method to set all instance attributes to\nthe default values defined in the matching class attributes.\n\n.. code:: python\n\n    from versionedobj import VersionedObject, Serializer\n\n    class Recipe(VersionedObject):\n        ingredient_1 = \"onions\"\n        ingredient_2 = \"tomatoes\"\n        ingredient_3 = \"garlic\"\n\n    rcp = Recipe()\n    serializer = Serializer(rcp)\n\n    # Change a value\n    rcp.ingredient_1 = \"celery\"\n\n    print(serializer.to_dict())\n    # {\"ingredient_1\": \"celery\", \"ingredient_2\": \"tomatoes\", \"ingredient_3\": \"garlic\"}\n\n    # Reset object instance to defaults\n    serializer.reset_to_defaults()\n\n    print(serializer.to_dict())\n    # {\"ingredient_1\": \"onions\", \"ingredient_2\": \"tomatoes\", \"ingredient_3\": \"garlic\"}\n\nTesting object instance equality\n--------------------------------\n\nYou can test whether two ``VersionedObject`` instances are equal in both structure and\nvalues, the same way in which you would check equality of any other two objects:\n\n.. code:: python\n\n    from versionedobj import VersionedObject\n\n    class Recipe(VersionedObject):\n        ingredient_1 = \"onions\"\n        ingredient_2 = \"tomatoes\"\n        ingredient_3 = \"garlic\"\n\n    rcp1 = Recipe()\n    rcp2 = Recipe()\n\n    print(rcp1 == rcp2)\n    # True\n\n    rcp1.ingredient_3 = \"ginger\"\n\n    print(rcp1 == rcp2)\n    # False\n\nIn order for two ``VersionedObject`` instances to be considered equal, the following\nconditions must be true:\n\n* Both objects are instances of the same class\n* Both objects contain matching attribute names and values\n\nObject instance hashing\n-----------------------\n\nObjects can be uniquely hashed based on their instance attribute values, using the builtin\n``hash()`` function. This means, for example, that you can use object instances as dict keys:\n\n.. code:: python\n\n    from versionedobj import VersionedObject\n\n    class Person(VersionedObject):\n        name = \"sam\"\n        age = 31\n\n    p1 = Person()\n    p2 = Person()\n\n    # Change 1 value on p2 so the hash value is different\n    p2.age = 32\n\n    d = {p1: \"a\", p2: \"b\"}\n    print(d)\n    # { Person({\"name\": \"sam\", \"age\": 31}): \"a\", Person({\"name\": \"sam\", \"age\": 32}): \"b\" }\n\nTesting whether object instances contain specific values\n--------------------------------------------------------\n\nYou can check whether an object instance contains a particular attribute value using the ``in``\nkeyword:\n\n.. code:: python\n\n    from versionedobj import VersionedObject\n\n    class Person(VersionedObject):\n        name = \"sam\"\n        age = 31\n\n    p = Person()\n\n    print(\"sam\" in p)\n    # True\n\n    p.name = \"sally\"\n\n    print(\"sam\" in p)\n    # False\n\n    print(\"sally\" in p)\n    # True\n\nPerformance/stress test visualization\n-------------------------------------\n\nThe following image is generated by the ``tests/performance_tests/big_class_performance_test.py`` script,\nwhich creates and serializes/deserializes multiple versioned objects of an incrementally increasing size,\nand simultaneously having an increasing depth of contained nested objects.\n\nEach data point in the graph represents measurements taken for an object of a particular size.\nThe time taken to serialize the object to a dict, and also to deserialize the object data\nfrom a dict, and also to create an instance of the object, is measured for each object size. It is\nworth mentioning that measuring the ``from/to_json`` and ``from/to_file`` methods is not very\nuseful in this case, since that would only be measuring ``to/from_dict`` with additional JSON\nparser or file I/O overhead. That is why this graph only measures ``to/from_dict``.\n\nThis test was executed on a system with an Intel Core-i7 running Debian GNU/Linux 10 (buster)\nwith Linux debian 4.19.0-21-amd64.\n\n.. image:: https://github.com/eriknyquist/versionedobj/raw/master/images/performance_graph.png\n\nContributions\n-------------\n\nContributions are welcome, please open a pull request at `\u003chttps://github.com/eriknyquist/versionedobj\u003e`_ and ensure that:\n\n#. All existing unit tests pass (run tests via ``python setup.py test``)\n#. New unit tests are added to cover any modified/new functionality (run ``python code_coverage.py``\n   to ensure that coverage is above 98%)\n\nYou will need to install packages required for development, these are listed in ``dev_requirements.txt``:\n\n::\n\n    pip install -r dev_requirements.txt\n\nIf you have any questions about / need help with contributions or unit tests, please\ncontact Erik at eknyquist@gmail.com.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feriknyquist%2Fversionedobj","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feriknyquist%2Fversionedobj","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feriknyquist%2Fversionedobj/lists"}