Skip to content

What's the exact logic of on_setattr, validators and converters? #1148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
beelze opened this issue Jun 14, 2023 · 1 comment
Closed

What's the exact logic of on_setattr, validators and converters? #1148

beelze opened this issue Jun 14, 2023 · 1 comment

Comments

@beelze
Copy link

beelze commented Jun 14, 2023

At first, thank for the great and inspiring package!

But let me explain my question:

def before(instance, attrib, new_value):
    print(f'before: {attrib.name}={new_value!r}')
    return new_value

def after(instance, attrib, new_value):
    print(f'after: {attrib.name}={new_value!r}')
    return new_value

@define(on_setattr=[before, setters.convert, setters.validate, after])
class A:
    b: Optional[int] = field(default=1, validator=validators.instance_of(int))

In [45]: A()
Out[45]: A(b=1)  # before/after NOT called
In [46]: A(b=2)
Out[46]: A(b=2)  # again there is no before/after
In [47]: A().b=3  # they are work **only** when attrib is assigned explicitly
before: b=3
after: b=3
  1. Why on_setattr is ignored on initialization stage? Please explain the logic behind it. It was unexpected for me.
  2. What are before and after here? converters or validators? At least before is called as validator, receiving instance, attrib, new_value, but return value is used further in chain like in converter.
  3. I realize that attrs is not a validator class, but:
    • converters can't access attributes values (including «current» one). This is pretty common when one attribute depends on another.
    • validators can't access «original» or «previous in chain» value, leading to incorrect exceptions (because new_value may be converted at this stage)
  4. Is there any way to skip setters chain for default attribute value? Sometimes initial value can be «invalid» from the point of subsequent assignment, i.e. we can have None as initial attribute value, but assigning None to an attribute is invalid
  5. It can be convenient writing/using simple converters/validators that throws the same class-bound Exception in the end, something like:
@define(setter_exception=ErrorA)
class A:
    b: int = field(converter=lambda v: int(v), validator=[validators.ge(1), validators(le(3))], setter_error_msg='expected to be ⩾1 and ⩽3')

A().b = 'invalid' throws ErrorA(assigned_value, Attribute(), exception_caught)
class B can be defined with setter_exception=ErrorB. So we can use one error handler for assignments for both classes while having all info to produce useful error message, using «original» invalid value, underlying exception and all Attribute() attributes, including predefined error_msg
I've tried to extend attrs in such a way, but failed.

@hynek
Copy link
Member

hynek commented Dec 15, 2024

Hi, sorry for the delay, your question came in a time when I was traveling for a prolonged time and it's rather lot. :)

I'll try to give you the best possible answer, but keep in mind that attrs is going to be 10 years old next year and it's impossible to keep a project 100% coherent over such a long time.

Why on_setattr is ignored on initialization stage? Please explain the logic behind it. It was unexpected for me.

Because it's on_setattr and not on_init or something. attrs had __attrs_post_init__ rather early on, but people repeatedly asked for a way to react to setattr too. Conceptually, a user setting an attribute and a class setting an attribute are two different use-cases that need different behavior.

What are before and after here? converters or validators?

They are on_setattr hooks. ¯\_(ツ)_/¯ Trying to unify validators and converters is one of the longest-running issues on the bug tracker. Given that converters now also can receive additional arguments, it's probably easier to settle on calling them converters now.

converters can't access attributes values (including «current» one). This is pretty common when one attribute depends on another.

This shouldn't be an issue as of #1267 anymore! Yay, procrastination on responding to issues.

Is there any way to skip setters chain for default attribute value? Sometimes initial value can be «invalid» from the point of subsequent assignment, i.e. we can have None as initial attribute value, but assigning None to an attribute is invalid

not 100% sure what you mean by that

It can be convenient writing/using simple converters/validators that throws the same class-bound Exception in the end

have you tried to write a wrapper around validators? it would be probably more work than you'd like, but also your wish is a bit special. this is solid validation framework territory.

@hynek hynek closed this as not planned Won't fix, can't repro, duplicate, stale Dec 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants