Skip to content

Commit

Permalink
Check readonly before default if necessary
Browse files Browse the repository at this point in the history
  • Loading branch information
dkellner authored and nicolaiarocci committed Nov 19, 2016
1 parent 5fcb466 commit b570e78
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 2 deletions.
33 changes: 31 additions & 2 deletions cerberus/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,15 @@ def is_child(self):
Type: :class:`bool` """
return self._config.get('is_child', False)

@property
def is_normalized(self):
""" ``True`` if the document is already normalized. """
return self._config.get('is_normalized', False)

@is_normalized.setter
def is_normalized(self, value):
self._config['is_normalized'] = value

@property
def purge_unknown(self):
""" If ``True`` unknown fields will be deleted from the document
Expand Down Expand Up @@ -495,6 +504,10 @@ def __normalize_mapping(self, mapping, schema):
self.__normalize_rename_fields(mapping, schema)
if self.purge_unknown:
self._normalize_purge_unknown(mapping, schema)
# Check `readonly` fields before applying default values because
# a field's schema definition might contain both `readonly` and
# `default`.
self.__validate_readonly_fields(mapping, schema)
self.__normalize_default_fields(mapping, schema)
self._normalize_coerce(mapping, schema)
self.__normalize_containers(mapping, schema)
Expand Down Expand Up @@ -665,6 +678,12 @@ def _normalize_rename_handler(self, mapping, schema, field):
mapping[new_name] = mapping[field]
del mapping[field]

def __validate_readonly_fields(self, mapping, schema):
for field in (x for x in schema if x in mapping and
self._resolve_rules_set(schema[x]).get('readonly')):
self._validate_readonly(schema[field]['readonly'], field,
mapping[field])

def __normalize_default_fields(self, mapping, schema):
fields = [x for x in schema if x not in mapping or
mapping[x] is None and not schema[x].get('nullable', False)]
Expand Down Expand Up @@ -738,6 +757,7 @@ class instantiation.
self.__init_processing(document, schema)
if normalize:
self.__normalize_mapping(self.document, self.schema)
self.is_normalized = True

for field in self.document:
if self.ignore_none_values and self.document[field] is None:
Expand Down Expand Up @@ -1038,8 +1058,17 @@ def _validate_keyschema(self, schema, field, value):
def _validate_readonly(self, readonly, field, value):
""" {'type': 'boolean'} """
if readonly:
self._error(field, errors.READONLY_FIELD)
return True
if not self.is_normalized:
self._error(field, errors.READONLY_FIELD)
return True
# If the document was normalized (and therefore already been
# checked for readonly fields), we still have to return True
# if an error was filed.
has_error = errors.READONLY_FIELD in \
self.document_error_tree.fetch_errors_from(
self.document_path + (field,))
if self.is_normalized and has_error:
return True

def _validate_regex(self, pattern, field, value):
""" {'type': 'string'} """
Expand Down
10 changes: 10 additions & 0 deletions docs/normalization-rules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ subdocuments like ``allow_unknown`` (see :ref:`allowing-the-unknown`). The defau

.. versionadded:: 1.0

.. _default-values:

Default Values
--------------
You can set default values for missing fields in the document by using the ``default`` rule.
Expand Down Expand Up @@ -90,6 +92,14 @@ string, it points to a :doc:`custom method <customize>`.
>>> v.errors
{'a': ["default value for 'a' cannot be set: Circular dependencies of default setters."]}

You can even use both ``default`` and :ref:`readonly` on the same field. This
will create a field that cannot be assigned a value manually but it will be
automatically supplied with a default value by Cerberus. Of course the same
applies for ``default_setter``.

.. versionchanged:: 1.0.2
Can be used in conjunction with :ref:`readonly`.

.. versionadded:: 1.0

.. _type-coercion:
Expand Down
6 changes: 6 additions & 0 deletions docs/validation-rules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -420,13 +420,19 @@ Validates if *exactly one* of the provided constraints applies. See `\*of-rules`

.. versionadded:: 0.9

.. _readonly:

readonly
--------
If ``True`` the value is readonly. Validation will fail if this field is
present in the target dictionary. This is useful, for example, when receiving
a payload which is to be validated before it is sent to the datastore. The field
might be provided by the datastore, but should not writable.

.. versionchanged:: 1.0.2
Can be used in conjunction with ``default`` and ``default_setter``,
see :ref:`Default Values <default-values>`.

regex
-----
Validation will fail if field value does not match the provided regular
Expand Down

0 comments on commit b570e78

Please sign in to comment.