Commits

Andy Mikhailenko  committed 32ffa34

Added support for nested dot-expanded dictionaries.

  • Participants
  • Parent commits 52f28f1

Comments (0)

Files changed (2)

File monk/modeling.py

 from monk import validation
 
 
-class DotExpandedDictMixin(object):
+def make_dot_expanded(data):
+    if isinstance(data, dict):
+        pairs = []
+        for key, value in data.iteritems():
+            pairs.append((key, make_dot_expanded(value)))
+        return DotExpandedDict(pairs)
+    elif isinstance(data, list):
+        return [make_dot_expanded(x) for x in data]
+    return data
+
+
+class DotExpandedDict(dict):
+    def __init__(self, *args, **kwargs):
+        super(DotExpandedDict, self).__init__(*args, **kwargs)
+        for key, value in self.iteritems():
+            self[key] = make_dot_expanded(value)
+
     def __getattr__(self, attr):
         if not attr.startswith('_') and attr in self:
             return self[attr]
         raise AttributeError('Attribute or key {0.__class__.__name__}.{1} '
                              'does not exist'.format(self, attr))
 
+    def __setattr__(self, attr, value):
+        if not attr.startswith('_') and attr in self:
+            self[attr] = value
+
 
 class TypedDictReprMixin(object):
     def __repr__(self):
         db[self.collection].save(outgoing)
 
 
-class StructuredDict(dict):
+class StructuredDictMixin(object):
     """ A dictionary with structure specification and validation.
     """
     structure = {}
         validation.validate_structure(self.structure, self)
 
 
-class Document(TypedDictReprMixin, MongoBoundDictMixin, DotExpandedDictMixin,
-               StructuredDict):
+class Document(TypedDictReprMixin, MongoBoundDictMixin,
+               StructuredDictMixin, DotExpandedDict):
     """ A structured dictionary that is bound to MongoDB and supports dot
     notation for access to items.
     """

File unittests/test_modeling.py

             'comments': [
                 {
                     'text': unicode,
-                    'is_spam': bool,
+                    'is_spam': False,
                 },
             ]
         }
-        defaults = {
-            'comments.is_spam': False,
-        }
     data = {
         'title': u'Hello',
         'author': {
         with pytest.raises(KeyError):
             entry['nonexistent_key']
 
-    @pytest.mark.xfail(reason='nested dictionaries are dumb')
     def test_dot_expanded(self):
         entry = self.Entry(self.data)
-        assert entry.title == entry['title']
+
+        # getattr -> getitem
+        assert entry['title'] == self.data['title']
+        assert entry['title'] == entry.title
         with pytest.raises(AttributeError):
             entry.nonexistent_key
+        assert entry['author']['first_name'] == entry.author.first_name
+
+        # setattr -> setitem
+        entry.title = u'Bye!'
+        assert entry.title == u'Bye!'
+        assert entry.title == entry['title']
+
+        entry.author.first_name = u'Joan'
+        assert entry.author.first_name == u'Joan'
         assert entry.author.first_name == entry['author']['first_name']
 
-    '''
+        assert entry.comments[0].text == entry['comments'][0]['text']
 
-    def test_dotted_dict(self):
-        self.assertEqual(
-            monk.get_dotkeys(self.Entry.structure),
-            ['author', 'author.first_name', 'author.last_name',
-             'comments', 'comments.is_spam', 'comments.text', 'title']
-        )
-        d = self.Entry()
-        #self.assertEqual(d['comments'], [])
+    def test_defaults(self):
+        entry = self.Entry(self.data)
+        assert entry.comments[0].is_spam == False
 
-        d = self.Entry(self.data)
-        assert d['title'] == self.data['title']
-        assert d['author']['first_name'] == self.data['author']['first_name']
-
-        #assert d.author.first_name == data['author']['first_name']
-        with self.assertRaises(KeyError):
-            d['comments'][0]['is_spam']
-        #d.populate_skeleton()
-        #d.populate_defaults()
-        #assert d['comments'][0]['is_spam'] == False
-
-    '''
 
     '''
     TEST_DATABASE_NAME = 'test_monk'