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

Can/should marshmallow include pre-built validates_schema methods? #1988

Open
sirosen opened this issue May 23, 2022 · 3 comments
Open

Can/should marshmallow include pre-built validates_schema methods? #1988

sirosen opened this issue May 23, 2022 · 3 comments

Comments

@sirosen
Copy link
Contributor

sirosen commented May 23, 2022

I have a case in which I'm looking at adding a mutually exclusive set of fields to a schema.
It would be awesome if I could define this behavior with marshmallow.validate.MutuallyExclusive(["foo", "bar"]), and I'd be happy to work on an implementation of that. But where would it go and how would users apply it?

Unless I've missed/forgotten something, there's no way to provide pre-packaged schema validators for users to apply.

The following could work but is ugly:

class MySchema(Schema):
    foo = fields.String()
    bar = fields.String()

    _foo_bar_mutex = validates_schema(MutuallyExclusive(["foo", "bar"]))

What about a class decorator which adds a validator?

@with_validates_schema(MutuallyExclusive(["foo", "bar"]))
class MySchema(Schema):
    foo = fields.String()
    bar = fields.String()

Is this worth pursuing? And if so, what is the correct interface to build?

@deckar01
Copy link
Member

I would consider mutually exclusive fields to be discriminators for polymorphism. As far as I can tell none of the community polymorphism libraries are quite as concise as your examples, so they are probably not going to save you any lines over just rolling schema validators as needed.

Another syntax you might consider is a custom base schema with a field registry.

class MySchema(MutexSchema):
    foo = Mutex(fields.String())
    bar = Mutex(fields.String())

A more complicated use case you might consider is supporting mutex groups:

alpha = Mutex()
beta = Mutex()

# (foo XOR (bar AND baz)) AND (fizz XOR buzz)
class MySchema(MutexSchema):
    foo = alpha(fields.String())
    bar = alpha(fields.String())
    baz = alpha(fields.String(), group='bar')
    fizz = beta(fields.String())
    buzz = beta(fields.String())

I generally try to build extensions as modules in my project, work out any kinks after using it a few times, then publish it as a community module once it has stabilized. That actually reminds me, I have a community module for a schema meta decorator I need to publish soon. 😄

@sirosen
Copy link
Contributor Author

sirosen commented May 24, 2022

Yep, I agree that it's a kind of polymorphism! It can get tricky to model with mutex, since the number of variants is equal to the product of sizes of mutex groups, which can get a bit big.

Another syntax you might consider is a custom base schema with a field registry.

This looks very cool. I'll look into that for my own purposes for sure; thanks for sharing the idea! It could also possibly be pulled from field metadata, e.g. fields.String(metadata={"mutex_group": alpha}).

I don't want to focus overly much of mutex groups -- that's a motivating example, but not my only area of interest.

I realized that it isn't very smooth, with the current interfaces, to share a schema-level validator as something reusable, even within my own project. To me, that looks like a gap in the API, but it might not be worth filling.

I just thought of this way of adding it to the core:

class MySchema(Schema, validate=MutuallyExclusive(["foo", "bar"])):
    foo = fields.String()[
    bar = fields.String()

That has really nice symmetry with the validate arg to Field classes, but it requires work in SchemaMeta so I'm a bit wary of it.

@deckar01
Copy link
Member

That's not a pattern I have seen in other libraries, so I would probably avoid it. The schema decorator syntax would work, but you still have to make the referenced fields optional. Communicating that behavior explicitly by wrapping field attributes has the benefit of avoiding rebuilding the schema with instrumented fields.

You could make a decorator for injecting schema validation methods using the same pattern I used for my meta decorator.

https://github.com/deckar01/marshmallow-meta/blob/master/src/marshmallow_meta/__init__.py

def schema_validator(**validators):
    def wrapper(schema):
        return type(schema.__name__, (schema,), validators)
    return wrapper

@schema_validator(mutex=MutuallyExclusive(["foo", "bar"])
class MySchema(Schema):
    ...

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

No branches or pull requests

3 participants