python-attrs / attrs

Python Classes Without Boilerplate
https://www.attrs.org/
MIT License
5.31k stars 374 forks source link

Validation happens only after the creation of all fields, which can be too late #1237

Closed n-splv closed 9 months ago

n-splv commented 9 months ago

Setup

from attrs import (
    Factory,
    field,
    frozen,
    validators,
)

Simple validation, all good:

@frozen
class Foo:

    text: str = field(validator=validators.instance_of(str))

Foo(1)
# TypeError: ("'text' must be <class 'str'> (got 1 that is a <class 'int'>).", Attribute(name='text', ...

A more nuanced case:

class Bar:

    def __init__(self, text: str):
        """
        The input type will always be validated by the client code,
        so no need to worry, right?
        """
        if not isinstance(text, str):
            raise Exception("Something terrible happens")

bar_factory = Factory(
    lambda self: Bar(self.text),
    takes_self=True
)

@frozen
class Foo:

    text: str = field(validator=validators.instance_of(str))
    _bar: Bar = field(default=bar_factory)

Foo(1)
# Exception: Something terrible happens
n-splv commented 9 months ago

Same thing with:

@frozen
class Foo:

    text: str = field(validator=validators.instance_of(str))
    _bar: Bar = field(init=False)

    @_bar.default
    def _(self):
        return Bar(self.text)

Foo(1)
# Exception: Something terrible happens
hynek commented 9 months ago

Yes, but this is on purpose. It's a trade-off between your use-cases and ppl wanting to valid the whole class once it's done.

That said, if you need more complex validation/conversion, we've got our sibling project cattrs for that: https://github.com/python-attrs/cattrs