marshmallow-code / marshmallow

A lightweight library for converting complex objects to and from simple Python datatypes.
https://marshmallow.readthedocs.io/
MIT License
7.06k stars 629 forks source link

Polymorphic schema usage #973

Closed nikoladsp closed 6 years ago

nikoladsp commented 6 years ago

Hi, I have a question regarding usage of polymorphic schema objects.

Say, I have a base Config class object, with only id, name and type fields:

class Config(Schema):
    id = base_fields.Integer(description='Configuration ID', required=True)
    name = base_fields.String(description='Configuration name', required=True)
    type = base_fields.Integer(description='Configuration type', required=True)

Now, assuming that there will be many schema types that derives from Config but each with specific field(s), like:

class ConfigFile(Schema):
    path = base_fields.String(description='Config file path', required=True)

class ConfigDB(Schema):
    user = base_fields.String(description='DB username', required=True)
    password = base_fields.String(description='DB password', required=True)

When e.g. I pull data from the DB, I will get list of SQLAlchemy objects that are polymorphic, and type is a discriminator:

__mapper_args__ = {'polymorphic_identity': 'ConfigFile'} 
...
__mapper_args__ = {'polymorphic_identity': 'ConfigDB'} 

Is it possible to use ConfigFile schema when for example, creating a response which can hold list of objects that are sub-classes of it like:

class ConfigResponse(Schema):
    response = base_fields.Nested(ConfigFile, description='', many=True, required=True)

Is usage of marshmallow-oneofschema safe or some custom fix is recommended? Additional reason to ask is because we also use flasgger and I am not sure how it will 'dance' with it.

One additional question, not related to this topic: what are performance penalties comparing to usage of custom serialize method/class based on plain dictionaries? I like the library, but one of my coworkers keeps undermine usage of it based on suspicions regarding speed and not only that but also of ease of usage. I think second can not be further from the truth, but for speed I am not sure how much is the overhead. Are there some figures already, or to make my own test?

Thank you in advance

lafrech commented 6 years ago

Polymorphism is something that regularly comes out. There is no support in marshmallow itself, but there are extensions out there that can help.

You already found oneofschema, which is the one I would have suggested. Unfortunately, I don't think any lib generating OpenAPI documentation from marshmallow will be able to understand oneofschema without modification. (I didn't try, I'm just guessing.) This is not likely to improve as long as marshmallow doesn't have any official polymorphic field.

nikoladsp commented 6 years ago

Thank you kindly.

I would not mind to help implementing polymorphic field. Is there any documentation I should read in order to do so, or just reading the code? I've also found this one: marshmallow-polyfield

Do you maybe have a performance hint(s)?

deckar01 commented 6 years ago

https://marshmallow.readthedocs.io/en/3.0/custom_fields.html#creating-a-field-class

What kinds of operations do you need to do to this non-homogeneous list? If you just need to dump the fields that are inherited from the base schema you should be able to do that.

schema = Config(many=True, unknown=EXCLUDE)
schema.dump(data)
nikoladsp commented 6 years ago

Well, in general, there will be many types of subclasses. What I want is to dump all subclass schema fields, so to have an resulting array similar to:

[
    {'id': 1, 'name': 'file_config_1,' 'type': 'file', 'path': '/tmp/main.cfg'}, 
    {'id': 2, 'name': 'db_config_1,' 'type': 'db', 'user': 'my_db_user', 'password': 'my_db_password'}
]

And more to that, it would be great if I can display possible schemes for frontend guys in swagger page.

deckar01 commented 6 years ago

https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/

Apispec uses a "discriminator" to solve the problem of determining which schema an object belongs to. It's not a perfect solution, but I think it is something that marshmallow/apispec could implement.

lafrech commented 6 years ago

Indeed, polymorphism support was improved in OpenAPIv3.

It is hard to support it out-of-the-box in apispec because there is no "official" polymorphic field in marshmallow. It would be different if marshmallow included e.g. marshmallow-oneofschema.

See discussion here: https://github.com/marshmallow-code/apispec/pull/182.

nikoladsp commented 6 years ago

Thank you all!