Skip to content

msrest 0.4.12 Serialization change

Laurent Mazuel edited this page Aug 11, 2017 · 23 revisions

This page to describe all the new features related to serialization in msrest 0.4.12. This is already tested with Autorest / SDK / CLI.

Improved automatic Model parsing

Given a Model like this one:

        class TestKeyTypeObj(Model):

            _validation = {}
            _attribute_map = {
                'attr_d': {'key':'properties.KeyD', 'type': 'int'},
            }

Assuming the create_or_update operation takes this object as parameter, all these syntax are equivalent:

client.operations.create_or_update({'attr_d': 42})
client.operations.create_or_update({'ATTR_D': 42})
client.operations.create_or_update({'keyd': 42})
client.operations.create_or_update({'KEYD': 42})
client.operations.create_or_update({'properties': {'keyd': 42}})
client.operations.create_or_update({'PROPERTIES': {'KEYD': 42}})

But since "explicit is better than implicit", the recommended way is now:

client.operations.create_or_update(TestKeyTypeObj.from_dict({'attr_d': 42}))

Validation

All models have now a validate method that return a list with the validation that fails:

From this class:

        class TestObj(Model):

            _validation = {
                'name': {'min_length': 3},
            }                
            _attribute_map = {
                'name': {'key':'RestName', 'type':'str'},
            }
            
            def __init__(self, name):
                self.name = name

We can call validate directly:

In [5]: obj = TestObj("ab")

In [7]: obj.validate()
Out[7]: [msrest.exceptions.ValidationError("Parameter 'name' must have length greater than 3.")]

This will recursively validate the entire model, and return the complete list.

Serialization

All model now have two new methods: serialize and to_dict:

In [11]: obj.serialize()
Out[11]: {'RestName': 'ab'}

In [12]: obj.as_dict()
Out[12]: {'name': 'ab'}

serialize will return the JSON for the Azure RestAPI. Which means this also trim readonly values. as_dict can be configured to:

  • Change the key used using a callback that receive attribute name, attribute meta and value. A list can be used to imply hierarchy
  • Keep or not the read only values

Examples:

In [13]: obj.as_dict(lambda attr, attr_desc, value: "prefix_"+attr)
Out[13]: {'prefix_name': 'ab'}

In [15]: obj.as_dict(lambda attr, attr_desc, value: ["prefix", attr])
Out[15]: {'prefix': {'name': 'ab'}}

Three callbacks are available by default:

  • attribute_transformer : just use the attribute name
  • full_restapi_key_transformer : use RestAPI complete syntax and hierarchy (like serialize)
  • last_restapi_key_transformer : use RestAPI syntax, but not hierarchy (flatten object, but RestAPI case, close to CLI to_dict)

Deserialization

All model now have two new class methods: deserialize and from_dict:

In [19]: a = TestObj.deserialize({'RestName': 'ab'}) ; print(type(a), a)
<class '__main__.TestObj'> {'name': 'ab'}

In [20]: a = TestObj.from_dict({'name': 'ab'}) ; print(type(a), a)
<class '__main__.TestObj'> {'name': 'ab'}

from_dict takes a key extraction callback list. By default, this is the case insensitive RestAPI extractor, the case insensitive attribute extractor and the case insensitive last part of RestAPI key extractor.

This can be used to tweak the deserialisation process if necessary:

# Scheduler returns duration as "00:00:10"
        class TestDurationObj(Model):
            _attribute_map = {
                'attr_a': {'key':'attr_a', 'type':'duration'},
            }

        with self.assertRaises(DeserializationError):
            obj = TestDurationObj.from_dict({
                "attr_a": "00:00:10"
            })

        def duration_rest_key_extractor(attr, attr_desc, data):
            value = rest_key_extractor(attr, attr_desc, data)
            if attr == "attr_a":
                # Stupid parsing, this is just a test
                return "PT"+value[-2:]+"S"

        obj = TestDurationObj.from_dict(
            {"attr_a": "00:00:10"},
            key_extractors=[duration_rest_key_extractor]
        )
        self.assertEqual(timedelta(seconds=10), obj.attr_a)

Misc

Roundtrip

  • serialize / deserialize : No roundtrip in most cases, since serialize removes the read-only attributes. But you can override it if necessary:
In [7]: a = TestObj.deserialize(TestObj('ab').serialize(keep_readonly=True)) ; print(type(a), a)
<class '__main__.TestObj'> {'name': 'ab'}
  • from_dict / to_dict : Should support roundtrip, or it's a bug
In [6]: TestObj.from_dict({'name': 'ab'}).as_dict()
Out[6]: {'name': 'ab'}

In [7]: a = TestObj.from_dict(TestObj('ab').as_dict()) ; print(type(a), a)
<class '__main__.TestObj'> {'name': 'ab'}
Clone this wiki locally