From 80cbfa38ae4ba7add2883697ed69857c88966a73 Mon Sep 17 00:00:00 2001 From: Kobi Samoray Date: Thu, 23 Feb 2017 14:41:11 +0200 Subject: [PATCH] Recurse dictionary generation in model to_dict() Data model to_dict() methods do not recurse into object hierarchy. While trying to transfer an object via RPC to a non-octavia services. the object members are also required. Change-Id: Ic8d85447d3e20c7a0fa865a400742bf3ac294843 --- octavia/common/data_models.py | 42 +++++++++++++--- octavia/tests/unit/api/common/test_types.py | 55 +++++++++++++++++++++ 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/octavia/common/data_models.py b/octavia/common/data_models.py index a476764f3d..f8e995ed73 100644 --- a/octavia/common/data_models.py +++ b/octavia/common/data_models.py @@ -15,6 +15,7 @@ # under the License. import re +import six from sqlalchemy.orm import collections @@ -22,19 +23,44 @@ from octavia.common import constants class BaseDataModel(object): - - # NOTE(brandon-logan) This does not discover dicts for relationship - # attributes. - def to_dict(self): + def to_dict(self, calling_classes=None, recurse=False, **kwargs): """Converts a data model to a dictionary.""" + calling_classes = calling_classes or [] ret = {} for attr in self.__dict__: - if attr.startswith('_'): + if attr.startswith('_') or not kwargs.get(attr, True): continue - if isinstance(getattr(self, attr), (BaseDataModel, list)): - ret[attr] = None + value = self.__dict__[attr] + + if recurse: + if isinstance(getattr(self, attr), list): + ret[attr] = [] + for item in value: + if isinstance(item, BaseDataModel): + if type(self) not in calling_classes: + ret[attr].append( + item.to_dict(calling_classes=( + calling_classes + [type(self)]))) + else: + ret[attr] = None + else: + ret[attr] = item + elif isinstance(getattr(self, attr), BaseDataModel): + if type(self) not in calling_classes: + ret[attr] = value.to_dict( + calling_classes=calling_classes + [type(self)]) + else: + ret[attr] = None + elif six.PY2 and isinstance(value, six.text_type): + ret[attr.encode('utf8')] = value.encode('utf8') + else: + ret[attr] = value else: - ret[attr] = self.__dict__[attr] + if isinstance(getattr(self, attr), (BaseDataModel, list)): + ret[attr] = None + else: + ret[attr] = value + return ret def __eq__(self, other): diff --git a/octavia/tests/unit/api/common/test_types.py b/octavia/tests/unit/api/common/test_types.py index d70a8e279b..5e358e07bd 100644 --- a/octavia/tests/unit/api/common/test_types.py +++ b/octavia/tests/unit/api/common/test_types.py @@ -98,3 +98,58 @@ class TestTypeDataModelRenames(base.TestCase): type_dict = TestTypeTenantProject(tenant_id='1234').to_dict() self.assertEqual('1234', type_dict['project_id']) self.assertNotIn('tenant_id', type_dict) + + +class TestToDictModel(data_models.BaseDataModel): + def __init__(self, text, parent=None): + self.parent = parent + self.child = None + self.children = None + self.text = text + + def set_children(self, children): + self.children = children + + def set_child(self, child): + self.child = child + + def set_parent(self, parent): + self.parent = parent + + +class TestDataModelToDict(base.TestCase): + RECURSED_RESULT = {'parent': None, + 'text': 'parent_text', + 'child': {'parent': None, + 'text': 'child_text', + 'child': None, + 'children': None}, + 'children': [ + {'parent': None, + 'text': 'child1_text', + 'child': None, + 'children': None}, + {'parent': None, + 'text': 'child2_text', + 'child': None, + 'children': None}]} + + NO_RECURSE_RESULT = {'parent': None, + 'text': 'parent_text', + 'child': None, + 'children': None} + + def setUp(self): + super(TestDataModelToDict, self).setUp() + self.model = TestToDictModel('parent_text') + self.model.set_child(TestToDictModel('child_text', self.model)) + self.model.set_children([TestToDictModel('child1_text', self.model), + TestToDictModel('child2_text', self.model)]) + + def test_to_dict_no_recurse(self): + self.assertEqual(self.model.to_dict(), + self.NO_RECURSE_RESULT) + + def test_to_dict_recurse(self): + self.assertEqual(self.model.to_dict(recurse=True), + self.RECURSED_RESULT)