-
-
Notifications
You must be signed in to change notification settings - Fork 626
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Add Field#validator for registering field validation methods #368
Conversation
marshmallow/fields.py
Outdated
# Bind registered method validators so that the `self` argument | ||
# is the Schema | ||
for validator in self._method_validators: | ||
self.validators.append(functools.partial(validator, schema)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not that I forsee a huge problem, but won't this bind the validator to the first schema it's on, rather than to the inheritance chain, unless I missed some Juju in SchemaMeta. I can't test my hunch as I'm mobile, but I think this shouldn't produce the expected results...
class MySchema(Schema):
bar = DecimalField()
@bar.validator
def error(self, *a, **k):
raise ValidationError(self.__class__.__name__)
class SubSchema(MySchema):
pass
SubSchema.dump(dotteddict(bar=4)).errors
But I could be wrong. Another sticky part could be child schemas that have stricter validation needs. How would this react to:
class SubSchema(MySchema):
@MySchema.bar.validates
def stricter a validation(...):
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretend that last bit is a valid python name. Stupid autocorrect.
See #368 (comment) 1. Ensure that validators get bound to the correct Schema instance (working) 2. Ensure that validators can be overriden by subclasses (not working) Currently, overriding a validator method in a Schema subclass does not prevent its parent class's validator method from being invoked. This is not the desired behavior. Also, 2. requires that Schema classes retain fields as class attributes, e.g. MySchema.my_field. This was achieved by passing pop=False to _get_fields in SchemaMeta. This is a **breaking change**.
@justanr You raise valid concerns in #368 (comment) (which I'm moving out here so this discussion doesn't get lost if the code changes). So we have 2 cases to address:
Since marshmallow/tests/test_decorators.py Line 376 in b30b705
Case 2 is currently broken. Overriding a validator method in a concrete Schema class will not prevent its parent validator method from being invoked. Also, I had to change |
I missed that sneaky assert in that test case. I have always found it odd that fields aren't accessible as properties - though, I can see the logic about that if it's possible to access them in a broken state that way. I guess my last question would be since one instance is shared through the inheritance chain. Would validators registered on child schemas be run when the parent runs the validate method? Going back to my second example, would MySchema().dump(...) Run the Sorry to ask a bunch of questions, just trying to stem potential edge cases before they're actual issues. |
This patch is a proposed fix for the failing `test_method_validator_override` in `new-method-validators`. Instead of storing validators in a list, use an `OrderedDict` keyed by method name, so that overrides by subclasses clobber parent methods with the same name.
Partialling validator methods means users must re-decorate validator overrides in subclasses, which I don't think should be necessary.
I submitted some test fixes in #400, but now that I'm thinking about this proposal, I have a few reservations. Or maybe I'm just confused--it's hard to tell the difference.
Assuming I'm not off-base so far, I'm wondering if we should take field objects back off the schema and not allow schema subclasses to add new validators to fields defined in their parents, since both behaviors lead to edge cases and possibly counter-intuitive behavior. Would that make this interface too hard to use? |
New method validators
@jmcarp You make a good case for keeping fields off of schema classes. I am still unsure how to answer the question about validators defined on subclasses. I'll have to give this more thought, but we definitely should prevent subclasses from adding validators to parents. |
@sloria Not sure how willing you are to do this, but instance variables defined in metaclasses are available to classes but not to instances of those classes. It's an interesting effect of the type system and potentially useful for this, but could cause a lot of confusion. Classes can have things defined as one name and instances can overwrite those and provide their own values for them, not affecting the class at all. For an example: https://gist.github.com/justanr/52301d3af774f714a80d The Something to mull over at least. I'll also note, I've not verified that this is intended, documented or compatible between Python 2 and 3. |
@sloria Some thoughts after mulling this over for a while. By default, child schemas inherit their parents' validators. So, what do the following semantics mean: class MySchema(Schema):
bar = DecimalField()
@bar.validator
def error(self, *a, **k):
raise ValidationError(self.__class__.__name__)
class SubSchema(MySchema):
@MySchema.bar.validator
def stricter(self, *a, **k):
pass Additionally, is it possible to have multiple validators bound to a single field: class MySchema(Schema):
bar = DecimalField()
@bar.validator
def error(self, *a, **k):
raise ValidationError
@bar.validator
def other(self, *a, **k):
pass I know in Python 2 this would always be non-deterministic registering, but it's possible to avoid in Python 3 by specifying If the behavior is that, no you should register one and one validator callable per field, and child validators should override their parents for their own instances, I think a solution is possible using some metaclass magic. A very basic (and potentially wrong) example could be: class Field(object):
def __init__(self, *args, **kwargs):
self.validators = {}
def validator(self, func):
func.__validator_for__ = self
return func
def _run_validators(self, schema, value):
# wrong for finding parent validators, we should look to py3 functools._find_impl for inspiration
registered = self.validators.get(type(schema), lambda schema, value: value)
registered(schema, value)
class SchemaMeta(type):
def __init__(cls, name, bases, attrs):
super(cls, cls).__init__(cls, name, bases, attrs)
for possible in attrs.values():
field = getattr(possible, '__validator_for__', None)
if not field:
continue
field.validators[cls] = possible If the behavior is that, yes you can register multiple validation methods for a single field, then the solution is still possible but trickier because now we also need to walk the MRO of the Schema in question and call all parent validators in MRO. |
Implements #367
TODO
@validates
for PendingDeprecation