-
-
Notifications
You must be signed in to change notification settings - Fork 632
Extra documentation for new context implementation? #2824
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
Comments
Ah, I think that I just misread the upgrade notes then. I took 'Use contextvars.ContextVar for passing context to fields, pre-/post-processing methods, and validators instead.' to mean that there was also a non-experimental way of doing it, that I just didn't understood because I never did anything with contextvars. |
Context variables is a neat feature.
It is really worth learning, especially if you need something
context-related in marshmallow.
The experimental code in marshmallow provides the use of a context
variable in a context manager. You may use it in your code or just as
an example.
|
One way that I use the context is to pass information from a parent schema into a nested one for conditional validation. This change has made things quite a bit more complex, was this use case considered? |
@juledwar can you post an example of the use case with the legacy API that you're trying to achieve with the new API? maybe we can assist with the migration |
I would appreciate that, thanks. I'll try and pare it down here, I am not permitted to post the code as-is, so this is not something I've run but it should give you the rough idea: class Foo(Schema):
name = String(required=True)
reason = String(required=True)
@pre_load
def set_required(self, data, **kwargs):
if self.context.get('reason_not_required') is False:
self.fields['reason'].required = False
return data
class Bar(Schema):
id = Integer()
details = Nested(Foo)
@pre_load
def needs_reason(self, data, **kwargs):
if data.get('some_thing'): # This could be any property of the data
self.context['reason_not_required'] = False
return data Previously I could encapsulate all of this decision making in the schema here, but now I need to set context vars outside of the schema. There's no way to enforce their presence as well, as far as I can tell, other than to just blow up at runtime. It would be nice to pass something via the field constructor but this is not something that can be controlled in the schema definition. The hacky way would be to do something like This change also has some ramifications for |
Sounds like a polymorphism issue, indeed. Indeed not really the case I had in mind for the context feature. |
This is absolutely not using polymorphism here, it's just one example of setting properties to affect changes in validation on the nested schema, which is one of the use cases described in the documentation. The OneOfSchema thing is possibly a polymorphism issue but I've written more there. |
We could think of it as But maybe that's not the most natural way to see it. And it would only work on this possibly contrived example where the check on I don't see what part of the doc you're pointing to but such a use case may have been used to illustrate the old context feature. What I'm saying is that I didn't have this in mind when proposing the changes in v4. I don't have time to try this now but doesn't it work if you just replace |
I think you're confusing OneOfSchema with this one? The doc I'm referring to is here https://marshmallow.readthedocs.io/en/latest/custom_fields.html#using-context where it talks about using external data to affect the serialization. My use case is to encapsulate this into the schema itself because forcing callers to provide the context when it's already all available in the parent schema seems quite awkward. I'll see if |
I'm not confusing with OneOfSchema. I somehow missed the page you're pointing to and only saw this one. Anyway, you're right, it won't work if the context is not opened by the A different approach to this would be to do the validation in the parent: in Also, I understand it sounds better to use schema/fields API rather than duplicate validation code, but modifying schema such as in I hope I'm being helpful. Sorry about the wrong hint in previous comment. |
You're right in that it was a super-contrived example for the sake of brevity. But it's a general pattern I feel should be easier to do as I have quite a few nested schemas that need to change behaviour based on some external data that is a property of the original parent schema's data. I would rather just inject the data from the call to Perhaps if I rename the context variable like this it's less of a weak-looking abstraction: class Foo(Schema):
name = String(required=True)
reason = String(required=True)
@pre_load
def set_required(self, data, **kwargs):
if self.context.get('totem') is False:
self.fields['reason'].required = False # or could do anything else
return data
class Bar(Schema):
id = Integer()
details = Nested(Foo)
@pre_load
def set_totem(self, data, **kwargs):
if data.get('some_thing'): # This could be any property of the data
self.context['totem'] = False
return data This is why Regarding changing
You have been nothing but very helpful every time I have a question on this project! |
just brainstorming: would adding a setter to from marshmallow.experimental.context import Context
class Foo(Schema):
name = String(required=True)
reason = String(required=True)
@pre_load
def set_required(self, data, **kwargs):
if Context.get()["totem"] is False:
self.fields['reason'].required = False # or could do anything else
return data
class Bar(Schema):
id = Integer()
details = Nested(Foo)
@pre_load
def set_totem(self, data, **kwargs):
if data.get('some_thing'): # This could be any property of the data
Context.set({"totem": False}) # <-- doesn't yet exist
return data
with Context({"totem": True}):
Bar().load({...}) we held off on adding a setter to avoid pathological cases where the context isn't reset. we could avoid that by raising an error if the setter is called without entering the context manager. |
I'm not sure it would help as you've still got the context data being set outside the I think a decent way to manage this might be to have a context hook on the base schema class, a bit like class Bar(Schema):
id = Integer()
details = Nested(Foo)
@load_context
def set_totem(self, data, **kwargs):
if data.get('some_thing'): # This could be any property of the data
return {'totem': False}
return None It's not returning data like the other hooks, it purely returns something to set in a contextvar, which is all managed for you by the base class. Similarly, a This would effectively remove the need for users to know anything about contextvars, but would still use them "under the hood" by using a setter and a reset when needed. Thoughts? |
I was a bit surprised that in the documentation there is only an example for how to use the experimental Context class, and not an example of how to do it the none experimental way?
This seems a lot more complex than the old way of doing it, is there anyway to just say the context is a dict and leave it at that?
The text was updated successfully, but these errors were encountered: