-
-
Notifications
You must be signed in to change notification settings - Fork 631
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
Context and instantiated Netsted schemas. #1791
Comments
Thanks for reporting and sorry about the delay. Indeed there is an inconsistency. See related discussion in #1617 and an implementation proposal for marshmallow 4 in #1826. What would you expect?
The former brings thread-safety issues, so I'm willing to chose the latter. This is what I do in #1833. The update case is something I didn't think about when proposing #1826. |
I'm not sure to fully understand your proposal and its implications but here is what I understand/expect. I don't know what the original intent was when designing the "context" attribute but I use it a lot to provide different contexts to nested schemas: import marshmallow as mm
DO_SOMETHING = "DO_SOMETHING"
class SchemaA(mm.Schema):
a = mm.fields.Integer(required=True)
@mm.post_load
def _post_load(self, data, **_):
do_smt = self.context.get(DO_SOMETHING, False)
if do_smt:
data["a"] = data["a"] * 4
return data
class SchemaB(mm.Schema):
ac1 = mm.fields.Nested(SchemaA(context={DO_SOMETHING: True}))
ac2 = mm.fields.Nested(SchemaA(context={DO_SOMETHING: False})) sch = SchemaB()
data = {"ac1": {"a": 1}, "ac2": {"a": 1}}
sch.load(data)
>> {'ac1': {'a': 4}, 'ac2': {'a': 1}} sch = SchemaB(context={DO_SOMETHING: True})
data = {"ac1": {"a": 1}, "ac2": {"a": 1}}
sch.load(data)
>> {'ac1': {'a': 4}, 'ac2': {'a': 4}} Both of these results seem consistent to me and are what I would expect. In my initial example, it was not about parents updating nested schema's context but nested schema updating their parents' context. The way I see it, each nested schema's context should inherit from its parent's context by making a copy of it (but not sharing it nor having the possibility to update it) and add its own context elements the way it's done today. |
I don't know the original intent for this feature either. I've never used it. To me, the point is to allow modifying the behaviour at runtime. Your sample code uses it to modify the schema at init (import) time. This could easily be achieved by just subclassing class SchemaA(mm.Schema):
def __init__(self, do_smt=False, *args, **kwargs):
self.do_smt = do_smt
a = mm.fields.Integer(required=True)
@mm.post_load
def _post_load(self, data, **_):
if self.do_smt:
data["a"] = data["a"] * 4
return data
class SchemaB(mm.Schema):
ac1 = mm.fields.Nested(SchemaA(do_smt=True}))
ac2 = mm.fields.Nested(SchemaA()) Is this correct? Or was your example too contrived and it wouldn't work in your real use case? The rework I propose in #1826 indeed breaks your use case but I believe this is fine as you should be able to achieve what you want like I'm showing above and context would be used only to modify things at runtime, not init. The question that remains is what to do about marshmallow 3. The fix in #1833 also breaks your use case. But it fixes a thread-safety issue that can catch users by surprise. We could argue that the way you're using it is not the way it is documented in the docs but to be fair, the doc for this feature is a bit light. We could also argue that the breaking change is likely to be caught in unit tests while the thread-safety issue is silent. We could merge the fix raise a warning when initiating a schema with a context in a Or we go the other way, don't merge the fix but raise a warning to alert about the thread-safety issue. No breaking change in marshmallow 3.x. Users will have to use a schema class rather than an instance to avoid the thread-safety issue, which means they won't be able to use schema modifiers while passing a context. We could live with this corner case limitation until 4.0. |
This is correct and looks cleaner than the way I'm doing it. I think (not 100% sure) this would allow me to not use contexts this way anymore and not be too much affected by the #1833 fix. Changing your code to this import marshmallow as mm
DO_SOMETHING = "DO_SOMETHING"
class SchemaA(mm.Schema):
def __init__(self, do_smt=False, *args, **kwargs):
super().__init__(*args, **kwargs)
self.do_smt = do_smt
a = mm.fields.Integer(required=True)
@mm.post_load
def _post_load(self, data, **_):
do_smt = self.context.get(DO_SOMETHING, self.do_smt)
if do_smt:
data["a"] = data["a"] * 4
return data
class SchemaB(mm.Schema):
ac1 = mm.fields.Nested(SchemaA(do_smt=True))
ac2 = mm.fields.Nested(SchemaA()) Would still allow the following usage: sch = SchemaB(context={DO_SOMETHING: True})
data = {"ac1": {"a": 1}, "ac2": {"a": 1}}
sch.load(data)
>> {'ac1': {'a': 4}, 'ac2': {'a': 4}} #1833 would also fix the inconsistency I mentioned in my initial message. Like you, I'm not sure a breaking change in 3.x would be a good thing (except if I'm the only one using contexts like this, I wouldn't mind). |
I like to use schema context to transmit information from one schema to another (child or parent) and the following behavior seems a little strange to me.
The only difference between SchemaB1 and SchemaB2 is that I provide the SchemaA class to the first one and an instance of SchemaA to the other.
This is the kind of things I usually do to provide a specific context to SchemaA when generated from SchemaB.
Something like that :
ac = mrsh.fields.Nested(SchemaA(context={"x": 1))
Using an instance of SchemaA result in the context not being fully shared between the Nested schema and its parent.
Same object if using a class.
Different objects if using an instance.
SchemaA correctly inherited from its parent context.
I'm not sure if that's a bug or not but that behavior somehow feels inconsistent to me (or at least hard to predict).
Is this the expected behavior ?
Thanks for your great library!
The text was updated successfully, but these errors were encountered: