I have data with a nested dict containing IDs as keys and data objects as values. I am trying to convert this into a pydantic model.
Sometimes there is a need to access a nested value if it exists. However, making an if check every time when accessing a nested object is very verbose. E.g.:
from typing import Dict
from pydantic import BaseModel
class EventModel(BaseModel):
eventId: str
eventName: str
class UserModel(BaseModel):
userId: str
events: Dict[str, EventModel]
user_dict = {
'userId': 'abc',
'events': {
'xyz': {'eventId': 'xyz', 'eventName': 'User log in'},
'123': {'eventId': '123', 'eventName': 'User log out'},
},
}
user = UserModel(**user_dict)
unknown_id = '987'
# Inconvenient access pattern if there are multiple nested ID maps on the way
if user.events.get(unknown_id):
print(user.events[unknown_id].eventName)
# Compare to the more convenient dict access pattern
print(user_dict['events'].get(unknown_id, {}).get('eventName'))
If the object is deep, the access pattern with if statements become very ugly.
Is there a more convenient way to access nested values with ID mappings on the way using pydantic? Or is there a way to improve this pydantic model to be more scalable without changing the underlying data structure?
For a simple translation of your "more convenient dict access pattern", you could try this, which uses the optional default parameter of the built-in getattr function:
getattr(user.events.get(unknown_id), 'eventName', None)
A more efficient solution, however, might be to add a method to UserModel that will return as soon as it is known that there is no event with that ID. The following suggested solution follows the "Easier to ask for forgiveness than permission" pattern.
from typing import Dict
from pydantic import BaseModel
class EventModel(BaseModel):
eventId: str
eventName: str
class UserModel(BaseModel):
userId: str
events: Dict[str, EventModel]
def name_of_event_with_id(event_id, default=None):
try:
event = self.events[event_id]
except KeyError:
return default
else:
return event.eventName
I ended up augmenting the library itself to include many features from the python dict interface, which helps quite a bit.
from pydantic import BaseModel as PydanticBasemodel
class BaseModel(PydanticBasemodel):
# Enable instance[field] syntax
def __getitem__(self, field):
return getattr(self, field)
def __setitem__(self, field, new_value):
return setattr(self, field, new_value)
# Enable spreading like a dict: func(**model)
def keys(self):
return self.__fields__.keys()
# Enable dict like get
def get(self, field, default=None):
return self[field] if field in self.keys() else default
# Change default config
class Config:
validate_assignment = True
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With