konradhalas / dacite

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

When using from dict with strict config it collides with InitVar #81

Closed KasperskyZiv closed 4 years ago

KasperskyZiv commented 4 years ago

I user InitVar to initiate some enum from number but it's not in fields() so when i user strict it throws an UnexpectedDataError.

possible solution is to add a new field function:

def fields_w_init_var(class_or_instance):
    """Return a tuple describing the fields of this dataclass.

    Accepts a dataclass or an instance of one. Tuple elements are of
    type Field.
    """

    # Might it be worth caching this, per class?
    try:
        fields = getattr(class_or_instance, _FIELDS)
    except AttributeError:
        raise TypeError('must be called with a dataclass type or instance')

    # Exclude pseudo-fields.  Note that fields is sorted by insertion
    # order, so the order of the tuple is as the fields were defined.
    return tuple(f for f in fields.values() if f._field_type is _FIELD or f._field_type is _FIELD_INITVAR)

and then edit the from_dict code:

    if config.strict:
        accepted_fields = fields_w_init_var(data_class)
        extra_fields = set(data.keys()) - {f.name for f in accepted_fields}
KasperskyZiv commented 4 years ago

also:

            if config.check_types and not is_instance(value, field.type) and not field.type is InitVar:
                raise WrongTypeError(field_path=field.name, field_type=field.type, value=value)

it means disabling type check for init var but it's better then rewriting dataclass Init var to actually store value or something

konradhalas commented 4 years ago

Hi @KasperskyZiv - thank you for reporting this issue.

You have right, it's a bug or maybe I should say - missing feature. Currently dacite doesn't support InitVar at all - no matter if you use strict mode or not.

Minimal example:

@dataclass
class X:
    a: int
    b: InitVar[int]

    def __post_init__(self, b: int) -> None:
        pass

result = dacite.from_dict(X, {"a": 1, "b": 2})

It will rise: TypeError: __init__() missing 1 required positional argument: 'b'

It was an attempt to tackle this problem here but it was long time ago and probably it will be easier to start from the scratch.

I will try to provide InitVar support in the next release.

KasperskyZiv commented 4 years ago

Yea, i solved it locally with my code, but it's a bit patchy haha.. Anyway thanks!