Andy Mikhailenko avatar Andy Mikhailenko committed e75a75e

Added support for callables in the structure spec: both value type and default value are obtained by calling the function supplied for given key.

Comments (0)

Files changed (4)

monk/manipulation.py

 Data manipulation
 =================
 """
-def merged(spec, data):
+def merged(spec, data, value_processor=lambda x:x):
     """ Returns a dictionary based on `spec` + `data`.
 
     Does not validate values. If `data` overrides a default value, it is
             # there's no default value for this key, just a restriction on type
             value = None
 
+        # call additional value processor, if any
+        #
+        # TODO: move most of logic above to such pluggable processors
+        #       because similar logic is also used in validation
+        value = value_processor(value)
+
         result[key] = value
 
     return result
 ======
 """
 from functools import partial
+import types
 
 from pymongo import dbref
 
 
 class StructuredDictMixin(object):
     """ A dictionary with structure specification and validation.
+
+    .. attribute:: structure
+
+        The document structure specification. For details see
+        :func:`monk.validation.validate_structure_spec` and
+        :func:`monk.validation.validate_structure`.
+
     """
     structure = {}
     #defaults = {}
     #with_skeleton = True
 
     def _insert_defaults(self):
-        with_defaults = manipulation.merged(self.structure, self)
+        """ Inserts default values from :attr:`StructuredDictMixin.structure`
+        to `self` by merging the two structures
+        (see :func:`monk.manipulation.merged`).
+        """
+        def process_value(value):
+            func_types = types.FunctionType,types.BuiltinFunctionType
+            if isinstance(value, func_types):
+                return value()
+            else:
+                return value
+
+        with_defaults = manipulation.merged(self.structure, self,
+                                            value_processor=process_value)
+
         for key, value in with_defaults.iteritems():
             self[key] = value
 
     ):
     """ A structured dictionary that is bound to MongoDB and supports dot
     notation for access to items.
+
+    Inherits features from:
+
+    * `dict` (builtin),
+    * class:`~monk.modeling.TypedDictReprMixin`,
+    * class:`~monk.modeling.DotExpandedDictMixin`,
+    * class:`~monk.modeling.StructuredDictMixin` and
+    * class:`~monk.modeling.MongoBoundDictMixin`.
+
     """
     def __init__(self, *args, **kwargs):
         super(Document, self).__init__(*args, **kwargs)

monk/validation.py

 ==========
 """
 from collections import deque
+import types
 
 from monk.helpers import walk_dict
 
     if typespec is None:
         # any value is allowed
         return
+
+    if isinstance(typespec, (types.FunctionType, types.BuiltinFunctionType)):
+        # default value is obtained from a function with no arguments;
+        # then check type against  what the callable returns. (It is expected
+        # that the callable does not have side effects.)
+        typespec = typespec()
+
     if not isinstance(typespec, type):
         # default value is provided
         typespec = type(typespec)
+
     if not isinstance(value, typespec):
         key = '.'.join(keys_tuple)
         raise TypeError('{key}: expected {typespec.__name__}, got '

unittests/test_modeling.py

 Modeling tests
 ==============
 """
+import datetime
 import pymongo
 from pymongo.objectid import ObjectId
 import pytest
         entry = self.Entry(self.data)
         assert entry.comments[0].is_spam == False
 
+    def test_callable_defaults(self):
+        class Event(modeling.Document):
+            structure = {
+                'text': unicode,
+                'time': datetime.datetime.utcnow,
+            }
+
+        event = Event(time=datetime.datetime.utcnow())
+        event.validate()
+
+        event = Event()
+        event.validate()
+
+        with pytest.raises(TypeError):
+            event = Event(time=datetime.date.today())
+            event.validate()
+
 
 class TestMongo:
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.