konradhalas / dacite

Simple creation of data classes from dictionaries.
MIT License
1.72k stars 106 forks source link

Question regarding Optional/Default values of dataclasses #177

Closed arossi07 closed 1 year ago

arossi07 commented 2 years ago

Hi, I have the following problem. I need classes with Optional/Default attributes and values like in the following example.

from dataclasses import dataclass
from typing import Optional, Union

import dacite
from dacite import Config

@dataclass
class A:
    x: Optional[int] = None

@dataclass
class B(A):
    y: Optional[int] = None

@dataclass
class C(A):
    z: Optional[int] = None

@dataclass
class Collection:
    my_class: Optional[Union[B, C]] = None

data_b = {
    "x": 1,
    "y": 2,
}

data_c = {
    "x": 1,
    "z": 3
}

collection_b = {"my_class": data_b}
collection_c = {"my_class": data_c}

result_1 = dacite.from_dict(data_class=Collection, data=collection_b, config=Config(strict_unions_match=True))
result_2 = dacite.from_dict(data_class=Collection, data=collection_c, config=Config(strict_unions_match=True))

When running the code it will fail with the following exception: dacite.exceptions.StrictUnionMatchError: can not choose between possible Union matches for field "my_class": B, C Which is at this point intended behaviour as far as I understand.

However, I would propose to create another config parameter which can be used to enable or disable the use of the default value in the dataclass. Example implementation could look like this in core.py:

try:
    if config.use_default:
        value = get_default_value_for_field(field)
    else:
        raise MissingValueError(field.name)
except DefaultValueNotFoundError:
    if not field.init:
        continue
    raise MissingValueError(field.name)

Or is there maybe another workaround for this issue which I am missing?

konradhalas commented 1 year ago

@arossi07 you need to use strict=True, check this example:

result_1 = dacite.from_dict(data_class=Collection, data=collection_b, config=Config(strict_unions_match=True, strict=True))
result_2 = dacite.from_dict(data_class=Collection, data=collection_c, config=Config(strict_unions_match=True, strict=True))

It returns correct result:

Collection(my_class=B(x=1, y=2))
Collection(my_class=C(x=1, z=3))