-
-
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
Inherit options #1432
base: dev
Are you sure you want to change the base?
Inherit options #1432
Conversation
This is really cool! Interested to see where it goes. |
Should we also "auto-inherit" class MyBaseSchema(Schema):
class Meta:
render_module = ujson
class ArtistSchema(MyBaseSchema):
class Meta: # implicitly inherits MyBaseSchema.Meta
ordered = True |
I'm don't have a good sense whether this implicit inheritance matches user expectations. Do you have time to provide an evaluation of what other libraries do (e.g. Django ORM, wtforms, etc)? |
The question of whether I don't hate the idea of us making them inherit. But it should possibly be also locked behind a flag, because doing this by default might confuse a lot of people. Django REST Framework doesn't seem to make Basically, I don't see any harm in doing slightly unusual things here, because they'll be hidden behind a constructor flag. So it will help people who need these settings but not affect everyone else. |
One motivating example I can think of for allowing Metas to inherit is setting the |
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.
I like the Meta auto-inheritance. It makes using a base Schema more practical.
Maybe it can be achieved in another PR.
Also, we could make those new features default (opt-out) behaviour in MA4.
# Set klass.opts in __new__ rather than __init__ so that it is accessible in | ||
# get_declared_fields | ||
klass.opts = klass.OPTIONS_CLASS(meta, ordered=ordered) | ||
|
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.
Rewrite proposal:
if combine_opts and len(bases) > 1:
# Create a new options class that combines all the base classes
opts = type(
name + "Opts", tuple({base.OPTIONS_CLASS for base in bases}), {}
)
else:
opts = klass.OPTIONS_CLASS
# Set klass.opts in __new__ rather than __init__ so that it is accessible in
# get_declared_fields
klass.opts = opts(meta, ordered=ordered)
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.
True, that is admittedly a lot less redundant. I'll fix it at some point
Ensure this behaviour still works if the two parent schemas have the same opts | ||
class | ||
""" | ||
|
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.
I don't get the purpose of this test.
In fact, I don't understand the purpose of the Meta
class attribute in both tests.
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.
The class Meta
sets the values of fields on the options class. It's just normal Marshmallow behaviour.
This particular test basically just checks that using combine_opts=True
works for two schemas with the same options class. Basically just checking backwards compatibility.
This seems like it has some risk of unpredictable behavior in the case where multiple schema classes define options classes, though. It seems like you can still end up with cases where an "earlier" base defines a parent class as the options class, or whatever, just by accident. Perhaps the same possible issues arise when doing this explicitly, though. In practice this may be fine for now, but the limitations of doing this sort of inheritance suggest that it might make more sense to move to something more like a class method or something that could leverage normal inheritance semantics, but probably not without a major bump. |
Can you give an example of a class hierarchy where you think this might happen? The entire use case for this PR is where multiple schema classes define different options classes. In addition, if there is a specific issue caused by this behaviour, you can just revert back to not using You can already leverage normal inheritance semantics in Marshmallow, whereby you have an options class inherit from another options class. All my PR does is give you an option to do this automatically, but if you don't want this behaviour you can instead do it manually if you want. |
I'm thinking something like: class Opts1(SchemaOpts):
pass
class Opts2(Opts1):
pass
class Schema1(Schema):
OPTIONS_CLASS = Opts1
class Schema2(Schema1):
OPTIONS_CLASS = Opts2
class Schema3(Schema1):
pass
class Schema4(Schema3, Schema2, Schema1):
pass For But, unless I'm missing something, we'd have This is of course somewhat contrived |
I suppose there might be a case where two options classes use the same keywords and thus won't play nicely together, but in general they shouldn't do that. The options classes are just bags of fields, so it's not like they have methods where it matters which one goes first. That said, if the order matters you can always a) rearrange the parent classes in the child class definition, and if that isn't satisfactory b) Manually define I guess my point is that this is just a utility keyword that won't break backwards compatibility and it only adds utility for a very common use-case, but doesn't take away any power over inheritance. |
I just opened a related RFC: #1969. |
Solves the issue documented here, where combining two schemas, each with different
SCHEMA_OPTS
, need to be combined: marshmallow-code/marshmallow-jsonapi#3My solution automatically creates a new
SchemaOpts
subclass that inherits from both parents whenever you subclass aSchema
with two parents. I could have implemented the metaclass to always do this, but this would break downstream code that might depend on theSchemaOpts
not properly inheriting. Thus, I've added a new class constructor parameter calledcombine_opts=True
.An example usage for the issue above is this:
I've also added a test for this parameter