Commits

Andy Mikhailenko committed ecc847b

Refactored code and tests; added tests for populating data with default values from a structure specification dict (no code is written yet).

Comments (0)

Files changed (6)

+# -*- coding: utf-8 -*-
+#
+#    Monk is a lightweight schema/query framework for document databases.
+#    Copyright © 2011  Andrey Mikhaylenko
+#
+#    This file is part of Monk.
+#
+#    Monk is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Lesser General Public License as published
+#    by the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    Monk is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Lesser General Public License for more details.
+#
+#    You should have received a copy of the GNU Lesser General Public License
+#    along with Monk.  If not, see <http://gnu.org/licenses/>.
+"""
+Helpers
+=======
+"""
+def walk_dict(data):
+    """ Generates pairs ``(keys, value)`` for each item in given dictionary,
+    including nested dictionaries. Each pair contains:
+
+    `keys`
+        a tuple of 1..n keys, e.g. ``('foo',)`` for a key on root level or
+        ``('foo', 'bar')`` for a key in a nested dictionary.
+    `value`
+        the value of given key or ``None`` if it is a nested dictionary and
+        therefore can be further unwrapped.
+    """
+    assert hasattr(data, '__getitem__')
+    for key, value in data.iteritems():
+        if isinstance(value, dict):
+            yield (key,), None
+            for keys, value in walk_dict(value):
+                path = (key,) + keys
+                yield path, value
+        else:
+            yield (key,), value

monk/manipulation.py

+# -*- coding: utf-8 -*-
+#
+#    Monk is a lightweight schema/query framework for document databases.
+#    Copyright © 2011  Andrey Mikhaylenko
+#
+#    This file is part of Monk.
+#
+#    Monk is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Lesser General Public License as published
+#    by the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    Monk is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Lesser General Public License for more details.
+#
+#    You should have received a copy of the GNU Lesser General Public License
+#    along with Monk.  If not, see <http://gnu.org/licenses/>.
+"""
+Data manipulation
+=================
+"""
+from monk.helpers import walk_dict
+
+
+def merged(spec, data):
+    raise NotImplementedError

monk/validation.py

 """
 from collections import deque
 
+from monk.helpers import walk_dict
+
 
 class StructureSpecificationError(Exception):
     "Raised when malformed document structure is detected."
     """
 
 
-def walk_dict(data):
-    """ Generates pairs ``(keys, value)`` for each item in given dictionary,
-    including nested dictionaries. Each pair contains:
-
-    `keys`
-        a tuple of 1..n keys, e.g. ``('foo',)`` for a key on root level or
-        ``('foo', 'bar')`` for a key in a nested dictionary.
-    `value`
-        the value of given key or ``None`` if it is a nested dictionary and
-        therefore can be further unwrapped.
-    """
-    assert hasattr(data, '__getitem__')
-    for key, value in data.iteritems():
-        if isinstance(value, dict):
-            yield (key,), None
-            for keys, value in walk_dict(value):
-                path = (key,) + keys
-                yield path, value
-        else:
-            yield (key,), value
-
-
 def validate_structure_spec(spec):
     """ Checks whether given document structure specification dictionary if
     defined correctly.

unittests/test_helpers.py

+# -*- coding: utf-8 -*-
+#
+#    Monk is a lightweight schema/query framework for document databases.
+#    Copyright © 2011  Andrey Mikhaylenko
+#
+#    This file is part of Monk.
+#
+#    Monk is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Lesser General Public License as published
+#    by the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    Monk is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Lesser General Public License for more details.
+#
+#    You should have received a copy of the GNU Lesser General Public License
+#    along with Monk.  If not, see <http://gnu.org/licenses/>.
+"""
+Helpers tests
+=============
+"""
+from monk.validation import walk_dict
+
+
+class TestDataWalking:
+
+    def test_walk_dict(self):
+        data = {
+            'a': {
+                'b': {
+                    'c': 'C',
+                },
+                'd': [
+                    { 'e': 123 },
+                ],
+            },
+            'f': ['F'],
+            'g': dict,
+            'h': list,
+            'i': None,
+        }
+        paths = [
+            # value is a dictionary, not yielded, queued for unwrapping
+            (('a',), None),
+            # nested dict unwrapped; value is a dict; queued for unwrapping
+            (('a', 'b',), None),
+            # nested dict unwrapped; value is a string; yielded as is
+            (('a', 'b', 'c'), 'C'),
+            # nested dict unwrapped; next value is a list which in opaque for
+            # this function, so yielded as is, even if there are dicts inside
+            (('a', 'd'), [{'e': 123}]),
+            # value is a list again; yielded as is
+            (('f',), ['F']),
+            # a couple of type definitions
+            (('g',), dict),
+            (('h',), list),
+            (('i',), None),
+        ]
+        assert sorted(walk_dict(data)) == sorted(paths)

unittests/test_manipulation.py

+# -*- coding: utf-8 -*-
+#
+#    Monk is a lightweight schema/query framework for document databases.
+#    Copyright © 2011  Andrey Mikhaylenko
+#
+#    This file is part of Monk.
+#
+#    Monk is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Lesser General Public License as published
+#    by the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    Monk is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Lesser General Public License for more details.
+#
+#    You should have received a copy of the GNU Lesser General Public License
+#    along with Monk.  If not, see <http://gnu.org/licenses/>.
+"""
+Data manipulation tests
+=======================
+"""
+import pytest
+
+from monk.manipulation import merged
+
+
+class TestDocumentDefaults:
+    def test_none(self):
+        assert {'a': None} == merged({'a': None}, {})
+        assert {'a': None} == merged({'a': None}, {'a': None})
+        assert {'a': 1234} == merged({'a': None}, {'a': 1234})
+
+    def test_type(self):
+        assert {'a': None} == merged({'a': unicode}, {})
+        assert {'a': None} == merged({'a': unicode}, {'a': None})
+        assert {'a': u'a'} == merged({'a': 1}, {'a': u'a'})
+
+    def test_type_in_dict(self):
+        spec = {'a': {'b': int}}
+        # value is absent
+        assert {'a': {'b': None}} == merged(spec, {})
+        assert {'a': {'b': None}} == merged(spec, {'a': None})
+        assert {'a': {'b': None}} == merged(spec, {'a': {}})
+        assert {'a': {'b': None}} == merged(spec, {'a': {'b': None}})
+        # value is present
+        assert {'a': {'b': 1234}} == merged(spec, {'a': {'b': 1234}})
+
+    def test_type_in_list(self):
+        assert {'a': [int]} == merged({}, {'a': []})
+        assert {'a': [int]} == merged({'a': []}, {'a': []})
+
+    def test_instance(self):
+        assert {'a': 1} == merged({'a': 1}, {})
+
+    def test_instance_in_dict(self):
+        assert {'a': {'b': 1}} == merged({'a': {'b': 1}}, {})
+
+    def test_instance_in_list(self):
+        assert {'a': [1]} == merged({}, {'a': [1]})
+        assert {'a': [1]} == merged({'a': []}, {'a': [1]})
+        assert {'a': [0]} == merged({'a': [0]}, {'a': [0]})
+
+    def test_instance_in_list_of_dicts(self):
+        assert {'a': {'b': 1}} == merged({'a': [{'b': 1}]}, {})
+        assert {'a': {'b': 1}} == merged({'a': [{'b': 1}]}, {'a': []})
+        assert {'a': {'b': 1}} == merged({'a': [{'b': 1}]}, {'a': [{}]})
+        assert {'a': {'b': 0}} == merged({'a': [{'b': 1}]}, {'a': [{'b': 0}]})
+
+    def test_complex_list_of_dicts(self):
+        "some items are populated, some aren't"
+        spec = {
+            'a': [
+                {'b': 1}
+            ]
+        }
+        data = {
+            'a': [
+                { },
+                {'b': 2}
+            ]
+        }
+        expected = {
+            'a': [
+                {'b': 1},
+                {'b': 2}
+            ]
+        }
+        assert merged(spec, data) == expected

unittests/test_validation.py

 import pytest
 
 from monk.validation import (
-    walk_dict, validate_structure_spec, validate_structure,
-    populate_defaults,
-    StructureSpecificationError
+    validate_structure_spec, validate_structure, StructureSpecificationError
 )
-from monk import models
 
 
 class TestStructureSpec:
 
-    def test_walk_dict(self):
-        data = {
-            'a': {
-                'b': {
-                    'c': 'C',
-                },
-                'd': [
-                    { 'e': 123 },
-                ],
-            },
-            'f': ['F'],
-            'g': dict,
-            'h': list,
-            'i': None,
-        }
-        paths = [
-            # value is a dictionary, not yielded, queued for unwrapping
-            (('a',), None),
-            # nested dict unwrapped; value is a dict; queued for unwrapping
-            (('a', 'b',), None),
-            # nested dict unwrapped; value is a string; yielded as is
-            (('a', 'b', 'c'), 'C'),
-            # nested dict unwrapped; next value is a list which in opaque for
-            # this function, so yielded as is, even if there are dicts inside
-            (('a', 'd'), [{'e': 123}]),
-            # value is a list again; yielded as is
-            (('f',), ['F']),
-            # a couple of type definitions
-            (('g',), dict),
-            (('h',), list),
-            (('i',), None),
-        ]
-        assert sorted(walk_dict(data)) == sorted(paths)
-
     def test_correct_types(self):
         '`None` stands for "any value".'
         validate_structure_spec({'foo': None})