Best way to specify nested dict with pydantic?
Asked Answered
A

2

20

Context

I'm trying to validate/parse some data with pydantic.

I want to specify that the dict can have a key daytime, or not. If it does, I want the value of daytime to include both sunrise and sunset.

e.g. These should be allowed:

{
   'type': 'solar',
   'daytime': {
      'sunrise': 4, # 4am
      'sunset': 18 # 6pm
   }
}

And

{
   'type': 'wind'
   # daytime key is omitted
}

And

{
   'type': 'wind',
   'daytime': None
}

But I want to fail validation for

{
   'type': 'solar',
   'daytime': {
      'sunrise': 4
   }
}

Because this has a daytime value, but no sunset value.

MWE

I've got some code that does this. If I run this script, it executes successfully.

from pydantic import BaseModel, ValidationError
from typing import List, Optional, Dict

class DayTime(BaseModel):
    sunrise: int
    sunset: int
    
class Plant(BaseModel):
    daytime: Optional[DayTime] = None
    type: str

p = Plant.parse_obj({'type': 'wind'})
p = Plant.parse_obj({'type': 'wind', 'daytime': None})
p = Plant.parse_obj({
    'type': 'solar', 
    'daytime': {
        'sunrise': 5, 
        'sunset': 18
    }})
    
try:
    p = Plant.parse_obj({
        'type': 'solar', 
        'daytime': {
            'sunrise': 5
        }})
except ValidationError:
    pass
else:
    raise AssertionError("Should have failed")

Question

What I'm wondering is, is this how you're supposed to use pydantic for nested data?

I have lots of layers of nesting, and this seems a bit verbose.

Is there any way to do something more concise, like:

class Plant(BaseModel):
    daytime: Optional[Dict[('sunrise', 'sunset'), int]] = None
    type: str
Antibody answered 5/8, 2020 at 3:5 Comment(0)
C
23

Pydantic create_model function is what you need:

from pydantic import BaseModel, create_model

class Plant(BaseModel):
    daytime: Optional[create_model('DayTime', sunrise=(int, ...), sunset=(int, ...))] = None
    type: str
Cynth answered 5/8, 2020 at 6:55 Comment(4)
is there any way to leave it untyped? Just say dict of dict?Ashely
@Ashely You can simply declare dict as the type for daytime if you didn't want further typing, like so: daytime: dictDevelop
How is this different from the questioner's MWE? I can't see the advantage of create_model('DayTime', ...) over class DayTime(BaseModel): .... The docs only say that create_model() is for when the shape of a model is not known until runtime.Floranceflore
I'd rather avoid this solution at least for OP's case, it's harder to understand, and still 'flat is better than nested'Alible
W
2

Like this?

class DayTime(BaseModel):
    sunrise: int
    sunset: int
    
class Plant(BaseModel):
    daytime: Optional[DayTime] = None
    type: str

yo_data  = {
    'type': 'solar', 
    'daytime': {
        'sunrise': 5, 
        'sunset': 18
    }}

# then simply

a_plant = Plant(**yo_data)

see

Waist answered 4/10, 2023 at 22:51 Comment(1)
This seems to be the best solution imho.Hammers

© 2022 - 2024 — McMap. All rights reserved.