I'm wondering if what I am trying to do is even possible or if perhaps my approach to this is just completely wrong.
Let's say we've got 3 dataclasses:
@dataclass
class MyFirstClass:
my_int: int = 1
@dataclass
class MySecondClass:
first_class: MyFirstClass = field(default_factory=MyFirstClass)
my_bool: bool = False
@dataclass
class MainClass:
second_class: MySecondClass = field(default_factory=MySecondClass)
my_str: str = 'Original String'
And now I want to update a value in any given class:
def test_this():
# make copy of dataclass
data_object = deepcopy(MainClass())
# first print as is
print(f'\n{asdict(data_object)}')
# update first class
data_object.second_class.first_class.my_int = 2
# update second class
data_object.second_class.my_bool = True
# update main class
data_object.my_str = 'Updated String'
# print updated object
print(f'\n{asdict(data_object)}')
Which would output this:
Original:
{'second_class': {'first_class': {'my_int': 1}, 'my_bool': False}, 'my_str': 'Original String'}
Updated:
{'second_class': {'first_class': {'my_int': 2}, 'my_bool': True}, 'my_str': 'Updated String'}
I really like the clean approach of doing something like this:
# make copy of dataclass
data_object = deepcopy(MainClass())
data_object.second_class.first_class.my_int = 2
as opposed to this:
data_object = replace(MainClass(), second_class=MySecondClass(first_class=MyFirstClass(my_int=2)))
And basically, I have tests that I want to run through with different data for each field of each dataclass and so I am wondering if there's some way to dynamically "get" the correct field to be updated?
I thought of initially looping through the fields of the object created, like this:
def test_this():
# make copy of dataclass
data_object = deepcopy(MainClass())
# loop through dataclass
for field in fields(data_object):
print(f'\nField: {field}')
print(f'\nField Name: {field.name}')
print(f'Field Type: {field.type}')
print(f'Field Value: {getattr(data_object, field.name)}\n')
Which outputs this:
Field: Field(name='second_class',type=<class 'tests.sample_test_four.MySecondClass'>,default=<dataclasses._MISSING_TYPE object at 0x106943c10>,default_factory=<class 'tests.sample_test_four.MySecondClass'>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD)
Field Name: second_class
Field Type: <class 'tests.sample_test_four.MySecondClass'>
Field Value: MySecondClass(first_class=MyFirstClass(my_int=1), my_bool=False)
Field: Field(name='my_str',type=<class 'str'>,default='Original String',default_factory=<dataclasses._MISSING_TYPE object at 0x106943c10>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD)
Field Name: my_str
Field Type: <class 'str'>
Field Value: Original String
But looping through the MainClass object only shows second_class and my_str as available fields and not all the fields within the nested classes.
I thought then perhaps I could do a conversion of the dataclass into a dict and from there use a deep_update and end up with something like:
Using pydantic
def test_this():
# create initial dict
data_object = asdict(deepcopy(MainClass()))
# first print as is
print(f'\nOriginal:\n{data_object}')
# update dict
updated_dict = deep_update(data_object, {'my_int': 2})
# print updated
print(f'\nUpdated:\n{updated_dict}')
Or creating my own update function:
Update value of a nested dictionary of varying depth
def update(d, u):
for k, v in u.items():
if isinstance(v, collections.abc.Mapping):
d[k] = update(d.get(k, {}), v)
else:
d[k] = v
return d
Which both outputs this:
Original:
{'second_class': {'first_class': {'my_int': 1}, 'my_bool': False}, 'my_str': 'Original String'}
Updated:
{'second_class': {'first_class': {'my_int': 1}, 'my_bool': False}, 'my_str': 'Original String', 'my_int': 2}
The problem is that it didn't update the nested field, instead it added a new one in the outer-most dict
Also, with this approach, I fear that if any of the classes have identically named fields, then which one would be updated?
So I'm not sure if what I am trying to do is possible vs the effort involved in getting the desired outcome OR if I should approach it differently? Maybe use JSON files instead of dataclasses and do something from there?