schema icon indicating copy to clipboard operation
schema copied to clipboard

Defaults for subgroups

Open marcotama opened this issue 6 years ago • 3 comments

This is a feature request.

I would like it to be possible for an Optional whose value is a dictionary with Optional sub-entries to have as default a dictionary with the sub-entries and their defaults.

Maybe an example can help carry the meaning across:

from schema import Schema, Optional
schema = Schema({
        Optional('something', default={}): {
            Optional('method', default='A'): str,
            Optional('time', default=10): int,
        }
})
config1 = schema.validate({})
config2 = schema.validate({'something': {}})
config3 = schema.validate({'something': {'method': 'A', 'time': 10}})

print(config1)
>>> {'something': {}}
print(config2)
>>> {'something': {'method': 'A', 'time': 10}}
print(config3)
>>> {'something': {'method': 'A', 'time': 10}}

assert(config2 == config3) # Succeeds
assert(config1 == config2) # Fails

I would like both the asserts to succeed.

The behaviour is not currently supported (or maybe I could not find where it is documented - if this is the case, please advise). I would argue that it is a sensible behaviour to support: if I am using schema to parse a configuration file that is split in sections, I would like all the config entries to be set to their default values. The current behaviour, however, returns a structure with empty sub-sections, which means I need to specify my defaults somewhere else, and therefore have duplication (the worst kind of duplication, that of arbitrary constants).

It would be great if you could add an option for this behaviour! Please advise if I can be of help.

marcotama avatar Apr 09 '19 06:04 marcotama

This bothers me a bit too. To avoid duplicating the default values, the workaround I used was to add a bit of code before the call to schema.validate() to check for the existence of the top-level keys that are optional, and if not found simply assign empty dicts to those keys so that the validation call will always fill in the default values within them. This isn't too bad if there are just a few static sections at the top level but could get complicated if the section names are dynamic or there are several nested levels of default sections that need to be filled in.

ticalc-travis avatar Oct 01 '21 04:10 ticalc-travis

This is how today this can be achieved in Pydantic:


from pydantic import BaseModel


class Something(BaseModel):
    method: str = "A"
    time: int = 10

class Main(BaseModel):
    something: Something = Something()


config1 = Main.parse_obj({})
config2 = Main.parse_obj({'something': {}})
config3 = Main.parse_obj({'something': {'method': 'A', 'time': 10}})

print(f"{config1=}")
print(f"{config2=}")
print(f"{config3=}")

print(f"{config2 == config3}")
print(f"{config1 == config2}")

... with output:

>>> print(f"{config1=}")
config1=Main(something=Something(method='A', time=10))
>>> print(f"{config2=}")
config2=Main(something=Something(method='A', time=10))
>>> print(f"{config3=}")
config3=Main(something=Something(method='A', time=10))
>>> print(f"{config2 == config3}")
True
>>> print(f"{config1 == config2}")
True

... but I really wouldn't mind a Schema solution. @ticalc-travis, did you work out anything better? Some hacking into Schema's parser?

maciejmatczak avatar Mar 11 '22 12:03 maciejmatczak

Folks, I think I did it, Pydantic example helped, we are doing the same here:

from schema import Schema, Optional

something_schema = Schema({
    Optional('method', default='A'): str,
    Optional('time', default=10): int,
})

schema = Schema({
        Optional('something', default=something_schema.validate({})): something_schema
})


config1 = schema.validate({})
config2 = schema.validate({'something': {}})
config3 = schema.validate({'something': {'method': 'A', 'time': 10}})


print(f"{config1=}")
print(f"{config2=}")
print(f"{config3=}")

print(f"{config2 == config3}")
print(f"{config1 == config2}")

Yields:

>>> print(f"{config1=}")
config1={'something': {'method': 'A', 'time': 10}}
>>> print(f"{config2=}")
config2={'something': {'method': 'A', 'time': 10}}
>>> print(f"{config3=}")
config3={'something': {'method': 'A', 'time': 10}}
>>> print(f"{config2 == config3}")
True
>>> print(f"{config1 == config2}")
True

maciejmatczak avatar Mar 11 '22 13:03 maciejmatczak