Search code examples
pythoncerberus

Cerberus: Can the schema copy a value to multiple fields?


I want to take an input document like the below and copy the 'foo' key to multiple fields with different coercions and validations to get something like this:

>>> input_doc = {'foo': 10}
>>> coercions = {'foo': {'copy_to': 'bar', 'coerce': str}, 'bar': {'coerce': add_five}}
>>> v = Validator(coercions)
>>> v.normalized(input_doc)
{'foo': '10', 'bar': 15}

I know I could copy the values to other keys before feeding into Cerberus, but if I could do it all in the schema that would be ideal.

Cerberus has the 'rename' normalization rule, which runs before other coercions and validations, but if you pass it a container it just renames the key to that container rather than copying to each.

I think a custom rule could handle it except that it runs too late in the process. I need to copy pre-validation and even pre-coercion, ideally.

Maybe I'm asking too much of Cerberus, but it's so close to being a one-stop solution for my data munging needs.


Solution

  • The below works but it's a bit hacky. Still wondering if there's a better way to handle this.

    Basically, I made a custom Validator class and give it a custom rename_handler method so that it has direct access to the document. The KEY_MAP is a dict that tells the Validator which field names to copy where, since you can't pass arguments to a rename_handler.

    from cerberus import Validator
    
    class CustomValidator(Validator):
        KEY_MAP = {'foo': 'bar'}
    
        def _normalize_coerce_copy_field(self, field):
            copy = self.KEY_MAP[field]
            self.document[copy] = self.document[field]
    
            return field
    
    
    >>> input_doc = {'foo': 10}
    >>> coercions = {'foo': {'rename_handler': 'copy_field', 'coerce': str}, 'bar': {'coerce': lambda x: x + 5}}
    >>> v = CustomValidator(coercions)
    >>> v.normalized(input_doc)
    {'foo': '10', 'bar': 15}