pyeve / cerberus

Lightweight, extensible data validation library for Python
http://python-cerberus.org
ISC License
3.15k stars 238 forks source link

Proposal: custom coerce-methods #102

Closed funkyfuture closed 8 years ago

funkyfuture commented 9 years ago

i think it's better to include this feature before 0.9 is released. and as there is a reliable pattern for types, it wouldn't be much effort i guess.

one could also add an example to the docs and tests that illustrates a subclassed Validator that makes use of coercing in conjunction with contextual instance-properties.

nicolaiarocci commented 9 years ago

Design/usage example? Thanks!

funkyfuture commented 9 years ago
class MyValidator(Validator):
    def _corce_none_to_string(self, field, value):
        if value is None:
            self.document[field] = "N/A"

    def _coerce_expand_avatar_path(self, field, value):
        self.document[field] = os.path.join(self.avatar_dir, value)

schema = {'avatar': {'coerce': 'expand_avatar_path', 'type': 'accessible_image_file'},
          'phone': {'coerce': 'none_to_string'}}
nicolaiarocci commented 9 years ago

:+1:

nicolaiarocci commented 9 years ago

Type coercion allows this already, as you can pass any callable to the coerce method. You can also confine coercion within a custom validator:

class MyValidator(Validator):
    def __init__(self, *args, **kwargs):
        super(MyValidator, self).__init__(*args, **kwargs)
        self.avatar_dir = '~/avatars'

   def _validate_expand_avatar(self, expand_avatar, field, value):
       self.document[field] = os.path.join(self.avatar_dir, value)

s = {'a': {'type': 'string', 'expand_avatar': True}

Alternatively you could create a new avatar type:

class MyValidator(Validator):
    def __init__(self, *args, **kwargs):
        super(MyValidator, self).__init__(*args, **kwargs)
        self.avatar_dir = '~/avatars'

   def _validate_type_avatar(self, field, value):
       self.document[field] = os.path.join(self.avatar_dir, value)

s = {'a': {'type': 'avatar'}}

Do we really need to add yet another way to achieve this?

funkyfuture commented 9 years ago

imo yes, as i think that normalization and validation should be clearer segregated in the code. you're proposal is more a workaround where normalization is applied while validation, right?

nicolaiarocci commented 9 years ago

Yes, I think you are right.

manugarri commented 9 years ago

:+1:

One example would be for those fields in which you want a default value. Since there is no default keyword this could be easily implemented with coerce.

Also, i agree that makes sense the separation of validation (no data changes) versus coerce (data is modified).

funkyfuture commented 9 years ago

i wouldn't see why a default-normalization-rule shouldn't be contributed with the vanilla Validator.

manugarri commented 9 years ago

it could, but it is more aligned with coerce in which it would silently alter the original data.

funkyfuture commented 9 years ago

i don't understand. i thought you mean:

>>> schema = {'amount': {'default': 1}}
>>> document = {'amount': None}
>>> v.validated(document, schema)
{'amount': 1}
>>> document = {'model': 'The Robots'}
>>> v.normalized(document, schema)
{'model': 'The Robots', 'amount': 1}

yes, it alters values like coerce, but in a different way.

manugarri commented 9 years ago

yeah that's what i meant. I guess both coerce and default are same level citizens.

lelit commented 8 years ago

I'd take advantage of this: I'm loading the schema from an external file (actually, from a schema in a RAML document), and there's no easy way to express a callable (although I could use a YAML tag like {a: !!python/name:__builtin__.int}, that's quite ugly, difficult to understand to non-Python-introduced people who read the schema, and most Python RAML loaders use yaml.SafeLoader so that's not a generic option even).

Ideally I'd like to have a way to inject custom coercer functions on Validator, that _coerce_values() could lookup by name.

funkyfuture commented 8 years ago

closing this in favor of #188 where i argue that this is a 1.0-must-have.