From 26963d65b45229db50444665a3160bd0f1358421 Mon Sep 17 00:00:00 2001 From: Ross MacArthur Date: Sat, 8 Feb 2020 16:37:26 +0200 Subject: [PATCH] Make `Tag.lookup_tag` default to module name + qualname --- README.rst | 12 +++++----- RELEASES.rst | 7 ++++++ src/serde/tags.py | 5 +++-- tests/test_model.py | 43 +++++++++++++++++++++++++----------- tests/test_tags.py | 54 +++++++++++++++++++++++++++++++++------------ 5 files changed, 86 insertions(+), 35 deletions(-) diff --git a/README.rst b/README.rst index 2e336da..18481d6 100644 --- a/README.rst +++ b/README.rst @@ -285,7 +285,7 @@ serialization. .. code-block:: python >>> Cat(name='Fluffy', hates_dogs=True).to_dict() - OrderedDict([('name', 'Fluffy'), ('hates_dogs', True), ('species', 'Cat')]) + OrderedDict([('name', 'Fluffy'), ('hates_dogs', True), ('species', '__main__.Cat')]) When deserializing, tag deserialization is done first to determine which model to use for the deserialization. @@ -295,7 +295,7 @@ to use for the deserialization. >>> milo = Pet.from_dict({ ... 'name': 'Milo', ... 'hates_cats': False, - ... 'species': 'Dog' + ... 'species': '__main__.Dog' ... }) >>> milo.__class__ @@ -313,10 +313,10 @@ An invalid or missing tag will raise a ``DeserializationError``. ... serde.exceptions.DeserializationError: expected tag 'species' >>> - >>> Pet.from_dict({'name': 'Duke', 'species': 'Horse'}) + >>> Pet.from_dict({'name': 'Duke', 'species': '__main__.Horse'}) Traceback (most recent call last): ... - serde.exceptions.DeserializationError: no variant found for tag 'Horse' + serde.exceptions.DeserializationError: no variant found for tag '__main__.Horse' Externally tagged ^^^^^^^^^^^^^^^^^ @@ -337,7 +337,7 @@ tagged example above. ... hates_cats: fields.Bool() ... >>> Dog(name='Max', hates_cats=True).to_dict() - OrderedDict([('Dog', OrderedDict([('name', 'Max'), ('hates_cats', True)]))]) + OrderedDict([('__main__.Dog', OrderedDict([('name', 'Max'), ('hates_cats', True)]))]) Adjacently tagged ^^^^^^^^^^^^^^^^^ @@ -358,7 +358,7 @@ example. ... hates_cats: fields.Bool() ... >>> Dog(name='Max', hates_cats=True).to_dict() - OrderedDict([('species', 'Dog'), ('data', OrderedDict([('name', 'Max'), ('hates_cats', True)]))]) + OrderedDict([('species', '__main__.Dog'), ('data', OrderedDict([('name', 'Max'), ('hates_cats', True)]))]) Abstract models ^^^^^^^^^^^^^^^ diff --git a/RELEASES.rst b/RELEASES.rst index 2c08c3d..e04dce8 100644 --- a/RELEASES.rst +++ b/RELEASES.rst @@ -1,6 +1,13 @@ Releases ======== +0.8.0 +----- + +*Unreleased* + +- Make ``Tag.lookup_tag`` default to module + qualname. + 0.7.3 ----- diff --git a/src/serde/tags.py b/src/serde/tags.py index ce3c677..7c5c3b4 100644 --- a/src/serde/tags.py +++ b/src/serde/tags.py @@ -56,9 +56,10 @@ def lookup_tag(self, variant): variant (Model): the model class. Returns: - tag: the corresponding tag value. + str: the corresponding tag value. """ - return variant.__name__ + name = getattr(variant, '__qualname__', variant.__name__) + return '{}.{}'.format(variant.__module__, name) def lookup_variant(self, tag): """ diff --git a/tests/test_model.py b/tests/test_model.py index f3b045d..54fa69c 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -15,6 +15,23 @@ from tests import py3 +class LookupTagMixin(object): + def lookup_tag(self, variant): + return variant.__name__ + + +class External(LookupTagMixin, tags.External): + pass + + +class Internal(LookupTagMixin, tags.Internal): + pass + + +class Adjacent(LookupTagMixin, tags.Adjacent): + pass + + class TestModel: def test___new___empty(self): # Check that a Model with no Fields can be created. There should still @@ -122,9 +139,9 @@ def test___new___meta_class(self): class Example(Model): class Meta: - tag = tags.Internal(tag='kind') + tag = Internal(tag='kind') - assert Example.__tags__ == [tags.Internal(tag='kind')] + assert Example.__tags__ == [Internal(tag='kind')] def test___init___empty(self): # An empty Model with no Fields should work just fine. @@ -483,7 +500,7 @@ class NestedExample(Model): class Example(Model): class Meta: - tag = tags.External() + tag = External() nested = fields.Nested(NestedExample) @@ -500,7 +517,7 @@ class NestedExample(Model): class Example(Model): class Meta: - tag = tags.Internal(tag='kind') + tag = Internal(tag='kind') nested = fields.Nested(NestedExample) @@ -517,7 +534,7 @@ class NestedExample(Model): class Example(Model): class Meta: - tag = tags.Adjacent(tag='kind', content='data') + tag = Adjacent(tag='kind', content='data') nested = fields.Nested(NestedExample) @@ -629,7 +646,7 @@ def test_from_dict_externally_tagged(self): class Example(Model): class Meta: - tag = tags.External() + tag = External() a = fields.Int() @@ -667,7 +684,7 @@ def test_from_dict_internally_tagged(self): class Example(Model): class Meta: - tag = tags.Internal(tag='kind') + tag = Internal(tag='kind') a = fields.Int() @@ -697,7 +714,7 @@ def test_from_dict_adjacently_tagged(self): class Example(Model): class Meta: - tag = tags.Adjacent(tag='kind', content='data') + tag = Adjacent(tag='kind', content='data') a = fields.Int() @@ -733,7 +750,7 @@ class SubExample(Example): def test_from_dict_override_tag_for(self): # Check that from_dict() works when you modify the Meta.tag_for() method - class ExampleTag(tags.Internal): + class ExampleTag(Internal): def lookup_tag(self, variant): return variant.__name__.lower() @@ -867,7 +884,7 @@ def test_to_dict_externally_tagged(self): class Example(Model): class Meta: - tag = tags.External() + tag = External() a = fields.Int() @@ -885,7 +902,7 @@ def test_to_dict_internally_tagged(self): class Example(Model): class Meta: - tag = tags.Internal(tag='kind') + tag = Internal(tag='kind') a = fields.Int() @@ -907,7 +924,7 @@ def test_to_dict_adjacently_tagged(self): class Example(Model): class Meta: - tag = tags.Adjacent(tag='kind', content='data') + tag = Adjacent(tag='kind', content='data') a = fields.Int() @@ -923,7 +940,7 @@ class SubExample(Example): def test_to_dict_override_tag_for(self): # Check that to_dict() works when you modify the Meta.tag_for() method - class ExampleTag(tags.Internal): + class ExampleTag(Internal): def lookup_tag(self, variant): return variant.__name__.lower() diff --git a/tests/test_tags.py b/tests/test_tags.py index 59fc146..5e51345 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -1,3 +1,5 @@ +import mock +import six from pytest import raises from serde import Model @@ -70,7 +72,11 @@ def test_lookup_tag(self): class Example(Model): pass - assert Tag().lookup_tag(Example) == 'Example' + if six.PY2: + prefix = 'tests.test_tags' + else: + prefix = 'tests.test_tags.TestTag.test_lookup_tag.' + assert Tag().lookup_tag(Example) == prefix + '.Example' def test_lookup_variant(self): class Example(Model): @@ -84,16 +90,24 @@ class Example3(Example): tag = Tag(recurse=True) tag._bind(Example) - assert tag.lookup_variant('Example') is Example - assert tag.lookup_variant('Example2') is Example2 - assert tag.lookup_variant('Example3') is Example3 - assert tag.lookup_variant('Example4') is None + if six.PY2: + prefix = 'tests.test_tags' + else: + prefix = 'tests.test_tags.TestTag.test_lookup_variant.' + assert tag.lookup_variant(prefix + '.Example') is Example + assert tag.lookup_variant(prefix + '.Example2') is Example2 + assert tag.lookup_variant(prefix + '.Example3') is Example3 + assert tag.lookup_variant(prefix + '.Example4') is None def test_serialize(self): class Example(Model): pass - assert Tag().serialize(Example) == 'Example' + if six.PY2: + prefix = 'tests.test_tags' + else: + prefix = 'tests.test_tags.TestTag.test_serialize.' + assert Tag().serialize(Example) == prefix + '.Example' def test_deserialize(self): class Example(Model): @@ -107,22 +121,32 @@ class Example3(Example): tag = Tag(recurse=True) tag._bind(Example) - - assert tag.deserialize('Example') is Example - assert tag.deserialize('Example2') is Example2 - assert tag.deserialize('Example3') is Example3 - + if six.PY2: + prefix = 'tests.test_tags' + else: + prefix = 'tests.test_tags.TestTag.test_deserialize.' + assert tag.deserialize(prefix + '.Example') is Example + assert tag.deserialize(prefix + '.Example2') is Example2 + assert tag.deserialize(prefix + '.Example3') is Example3 + + tag_value = prefix + '.Example4' with raises(DeserializationError) as e: - tag.deserialize('Example4') + tag.deserialize(tag_value) assert ( e.value.pretty() == """\ -DeserializationError: no variant found for tag 'Example4' - Due to => value 'Example4' for tag 'Tag' on model 'Example'""" +DeserializationError: no variant found for tag '{}' + Due to => value {} for tag 'Tag' on model 'Example'""".format( + tag_value, + "'tests.test_tags.Example4'" + if six.PY2 + else "'tests.test_tags.TestTag.t... ", + ) ) +@mock.patch('serde.tags.Tag.lookup_tag', lambda _, variant: variant.__name__) class TestExternal: def test__serialize_with(self): class Example(Model): @@ -161,6 +185,7 @@ def test__deserialize_with_untagged(self): ) +@mock.patch('serde.tags.Tag.lookup_tag', lambda _, variant: variant.__name__) class TestInternal: def test___init___basic(self): tag = Internal() @@ -222,6 +247,7 @@ class Example2(Example): ) +@mock.patch('serde.tags.Tag.lookup_tag', lambda _, variant: variant.__name__) class TestAdjacent: def test___init___basic(self): tag = Adjacent()