Skip to content
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

Do not persist the context in validators #6172

Closed
wants to merge 1 commit into from

Conversation

michael-k
Copy link
Contributor

This fixes #5760 and supersedes #5762. It does however not include the failing test from #5761 as this makes no longer sense.

Description

Please see #5760 for a detailed description of the race condition. In short: When two serializers of the same type run their validators in parallel, the context might leak from one validator to the other (because the validator instances are identical).

Before validators could have a set_context method that persisted things like the instance of a django model on the validator. This PR deprecates this feature.

Instead it adds a new base class ContextBasedValidator for class based validators. Instances of this class will get the context (serializer or serialzer_field) as an additional argument. This context can be used during validation without the need to persist it on the instance.

Background of the implementation

This was basically proposed by @rpkilby in #5762 (comment) in a similar form:

  • Refactor unique validators to inherit a base InstanceValidator
    • I decided to do this for all validators for consistency. And it should make it easier to prevent similar issues for custom validators.
  • Change the InstanceValidator call signature to expect an instance in addition to the value.
    • I've implemented this with the modification that __call__ accepts the serializer[_field] instead. This ensures that the full context is still available if needed.
  • Delete the set_context method handling.
    • Deprecated for now.
  • Handle regular validators first, then instance validators second.
    • I've skipped this since I'm not convinced that this provides any advantages.

This approach also prevents deepcopy as preferred by @tomchristie:

If set_context means that we need a separate instance, perhaps we should be reconsidering the interface (e.g. set_context returns a new instance).

deepcopy is black magic, we should avoid it if we can.

Source: #5762 (comment)

Backwards compatibility

  • set_context is still called by fields/serializers to give authors of custom validators time to adjust to the new behavior. (The concrete release for the removal might need adjustment.)
  • Existing validators in rest_framework no longer provide some fields on their instances (self.instance, self.field_name, …). Custom validators based on those classes need to be adjusted immediately without any deprecation warning.

@rpkilby rpkilby added this to the 3.9 Release milestone Sep 10, 2018
@rpkilby rpkilby self-requested a review September 10, 2018 16:31
@tomchristie tomchristie modified the milestones: 3.9 Release, 3.10 Release Oct 16, 2018
@bluetech bluetech mentioned this pull request May 17, 2019
6 tasks
errors = []
for validator in self.validators:
if hasattr(validator, 'set_context'):
warnings.warn(
"Method `set_context` on validators is deprecated and will "
"no longer be called starting with 3.10. Instead derive the "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be 3.11 now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw - we've formalized our deprecation policy. This should use RemovedInDRF312Warning, which is currently a subclass of PendingDeprecationWarning. Deprecation classes can be found in rest_framework/__init__.py

rest_framework/fields.py Outdated Show resolved Hide resolved
rest_framework/validators.py Outdated Show resolved Hide resolved
"""Base class for validators that need a context during evaluation.

In extension to regular validators their `__call__` method must not only
accept a value, but also an instance of a serializer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should say "serializer field" here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. Depends on the validator, I guess. I think it's mostly relevant for validators used on serializers since fields are deepcopied.

@@ -47,7 +47,7 @@ If set, this gives the default value that will be used for the field if no input

The `default` is not applied during partial update operations. In the partial update case only fields that are provided in the incoming data will have a validated value returned.

May be set to a function or other callable, in which case the value will be evaluated each time it is used. When called, it will receive no arguments. If the callable has a `set_context` method, that will be called each time before getting the value with the field instance as only argument. This works the same way as for [validators](validators.md#using-set_context).
May be set to a function or other callable, in which case the value will be evaluated each time it is used. When called, it will receive no arguments. If the callable has a `set_context` method, that will be called each time before getting the value with the field instance as only argument.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like changing default to use the same mechanism would be a good idea as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be not relevant due to the deepcloning. But I'm not sure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think even if it's not strictly required for defaults, we should still adopt it for consistency.

@michael-k
Copy link
Contributor Author

@bluetech Thanks for the review! I've rebased my branch to fix the merge conflicts and included some of your suggestions.

Copy link
Member

@rpkilby rpkilby left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still need to review this further (when it's not the end of the day), but one thing that pops out at me is testing. There isn't much change to external API surface, but the following would be great:

field it is being used with as additional context. You can do so by using
`rest_framework.validators.ContextBasedValidator` as a base class for the
validator. The `__call__` method will then be called with the `serializer_field`
or `serializer` as an additional argument.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ContextBasedValidator is potentially confusing, as the serializer context is an entirely different concept. Possibly FieldContextValidator or FieldBasedValidator?

@rpkilby
Copy link
Member

rpkilby commented Jul 11, 2019

@tomchristie I'm inclined to punt this to the 3.11 release. The short version is that I think the deprecation path could be improved, or it should be removed. Right now, the warning is sufficient for users who write their own validators that have a set_context method, but this will break for users that have subclassed these validators, due to the various method signature changes. Is it worth it to go through a proper deprecation process, or should we just hard assert the breaking change?

Also, there are a few outstanding changes, like using the proper deprecation exception classes.

@michael-k
Copy link
Contributor Author

I'm ok with this not landing in 3.10. Haven't had the time to update this PR and incorporate the suggested changes.

@rpkilby rpkilby modified the milestones: 3.10 Release, 3.11 Release Jul 13, 2019
@rpkilby
Copy link
Member

rpkilby commented Jul 13, 2019

Thanks @michael-k. I think this is a great start, but I also think there's some non-obvious things we'll need to consider. Hoping we can get this in for 3.11!

@tomchristie
Copy link
Member

This is looking great! I think rather than a different class we could either...

  • Check for an attribute on the class, eg... requires_context = True?
  • Automatically determine if we should pass the context, based on the signature of __call__?

@tomchristie tomchristie mentioned this pull request Nov 21, 2019
@michael-k
Copy link
Contributor Author

Fixed in #7062

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Race condition in validators
4 participants