3

Now that I have finally dropped support for Python 2, I am migrating from attrs to Python 3 dataclasses and there is one issue I am particularly struggling with.

Let's say I have a frozen and hashable class MyClass with one field my_field of type tuple.

Thanks to attrs converters, I was able to provide a flexible API, with clients able to instantiate my_field with a variety of types like list, set or dict_keys. They would all be automatically converted to a tuple before the class creation.

Can I preserve this API with dataclasses?

As requested, a small code sample:

@attr.s(frozen=True, hash=True)
class MyClass:
    my_field = attr.ib(default=tuple(), converter=tuple)


print(MyClass([1, 2, 3]))
j08691
  • 204,283
  • 31
  • 260
  • 272
Campi
  • 1,932
  • 1
  • 16
  • 21
  • 1
    There is likely a solution to your problem. Only your question does not contain [MCVE](https://stackoverflow.com/help/mcve) and it is difficult to see how exactly you perform your conversion. Please provide a complete example. – sophros Jan 19 '19 at 07:44
  • Hi sophros, I thought it would be self explanatory in the context of my question as attrs provides a converter argument. I have added a snippet nonetheless. – Campi Jan 19 '19 at 08:48

2 Answers2

3

Setting the attribute value through the base class in post_init step seems to work:

@dataclass(frozen=True)
class MyClass:
    my_field: Sequence[str]

    def __post_init__(self):
        super().__setattr__('my_field', tuple(getattr(self, 'my_field')))

Mixin implementation:

class ValidationError(AttributeError):
    pass


class ConversionValidationField(Field):

    def __init__(self, default=MISSING, default_factory=MISSING, init=True, repr=True,
                 hash=None, compare=True, metadata=None, converter=None, validator=None):
        self.converter = converter
        self.validator = validator
        super().__init__(default, default_factory, init, repr, hash, compare, metadata)


class ConversionValidationMixin:

    def __post_init__(self):
        for field in fields(self):
            if isinstance(field, ConversionValidationField):
                if field.converter:
                    super().__setattr__(field.name, field.converter(getattr(self, field.name)))

                if field.validator:
                    if not field.validator(getattr(self, field.name)):
                        raise ValidationError('Validation failed for {}.'.format(field.name))
Campi
  • 1,932
  • 1
  • 16
  • 21
1

No, converters are one of the things the dataclass PEP chose not to implement to keep is simple. http://www.attrs.org/en/stable/why.html#data-classes mentions a few more.

DCs are strictly a subset of attrs and it's unlikely that it'll ever change.

hynek
  • 3,647
  • 1
  • 18
  • 26
  • Thanks for the information. I was hoping there may be a trick to emulate the conversion step before the class is frozen, maybe through some metaclass. It's a shame that the conversion feature was not included. – Campi Jan 20 '19 at 13:25