I've gotten into using pydantic to keep my types in order, but I've been finding that it doesn't play nicely with numpy types. The process of saving objects wraps everything in array of dtype=object and I need to manually undo it everywhere.
I've figured out how to parse this away for strings or lists. For example, for strings, the following seems to work:
from pydantic import BaseModel, validators
class str_type(str):
@classmethod
def __get_validators__(cls):
yield cls.validate
yield validators.str_validator
@classmethod
def validate(cls, value):
if issubclass(type(value), np.ndarray):
value = value.item()
return value
class MyClass(BaseModel):
my_attribute: str_type
def save_npz(self, filename):
"""Saves a *.npz file with all attributes"""
np.savez(filename, **self.dict())
@classmethod
def load_npz(cls, filename):
"""Loads a *.npz file and creates an instance of Args"""
data = np.load(filename, allow_pickle=True)
data_dict = dict(data)
return cls(**data_dict)
b = MyClass(my_attribute='hi')
b.save_npz('temp_file.npz')
c = MyClass.load_npz('temp_file.npz')
However, for this trick doesn't seem to work for dicts, and I'm at a loss for why. This is my MWE:
from pydantic import BaseModel, validators
class dict_type(dict):
@classmethod
def __get_validators__(cls):
yield cls.validate
yield validators.dict_validator
@classmethod
def validate(cls, value):
if issubclass(type(value), np.ndarray):
value = value.item()
return value
class MyClass(BaseModel):
my_attribute: dict_type[str, float]
def save_npz(self, filename):
"""Saves a *.npz file with all attributes"""
np.savez(filename, **self.dict())
@classmethod
def load_npz(cls, filename):
"""Loads a *.npz file and creates an instance of Args"""
data = np.load(filename, allow_pickle=True)
data_dict = dict(data)
return cls(**data_dict)
b = MyClass(my_attribute={'hi': 3})
b.save_npz('temp_file.npz')
c = MyClass.load_npz('temp_file.npz')
pydantic.error_wrappers.ValidationError: 1 validation error for MyClass my_attribute value is not a valid dict(type=type_error.dict)
EDIT
I ended up using a solution based on @Daniil Fajnberg's suggestions below. I created a standard BaseModel and put a numpy unwrapper within a standard validator that applies to every attribute:
def numpy_unwrap(value):
""" A common issue when loading data in an *.npz file is that numpy wraps the object in a
numpy array for safekeeping. For example, instead of saving "True" as type 'bool', it's
saved as array(True, dtype=object). In most cases, it's easy to unwrap the object from
this array by just calling the .item() method on the enclosing array, unless it's supposed
to be a list, in which case you call .tolist().
"""
if not issubclass(type(value), np.ndarray):
return value
try:
return value.tolist()
except ValueError:
return value.item()
class BaseModel_np(BaseModel):
@validator('*', pre=True, always=True)
def unwrap_numpy_array(cls, value):
return numpy_unwrap(value)
class Config:
arbitrary_types_allowed = True