1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.
  2. Anuncie Aqui
    Anuncie aqui você Também: fdantas@4each.com.br

[Python] How to trigger multiple validators when an attribute is changed in an existing...

Discussão em 'Python' iniciado por Stack, Setembro 15, 2024.

  1. Stack

    Stack Membro Participativo

    I am using attrs (https://www.attrs.org) with custom initialization and setter validations.

    When I update an attribute, I want to validate that attribute and other attributes that depend on it.

    Below is a simple example. The actual application is for a comparative genomics analysis package, so this is a significant simplification. I want to use attrs instead of pydantic and I want the instance attributes to be mutable.

    I tried another approach where on_setattr=_some_custom_func is in field(), but I wasn't able to get it to work; it set the attribute to None instead of the new parameter, and I didn't pursue it further.

    Code:

    import warnings

    from attrs import define, field, setters, validators
    from collections import namedtuple

    StoreParams = namedtuple("StoreParams", ["hours"])
    HourParams = namedtuple("HourParams", ["opening", "closing"])

    @define(slots=True, kw_only=True, on_setattr=[setters.convert, setters.validate])
    class Foo:

    # For this parent class Foo
    ALLOWED_STORES = ("grocer_a", "grocer_b", "grocer_c", "grocer_d", "grocer_e", "grocer_f")

    # Will be in subclass of Foo, but in Foo for troubleshooting
    PARAMS = {
    "store": {
    "grocer_a": StoreParams(hours=HourParams(5, 18)),
    "grocer_b": StoreParams(hours=HourParams(5, 20)),
    "grocer_c": StoreParams(hours=HourParams(6, 18)),
    }
    }

    # Initializations
    store: str = field(
    default="blah",
    converter=lambda v: v.lower() if v is not None and isinstance(v, str) else v,
    validator=[
    validators.optional(validators.instance_of(str)),
    validators.in_(ALLOWED_STORES),
    ],
    )

    # Notes on store init:
    # use isinstance() because converters run before validators; .lower error issued before TypeError
    # optional allows None type
    # raise ValueError if `store` nonsense (e.g., `store=meow`)
    # issue UserWarning `store` is not nonsense but not recommended in subclass

    hours: float = field(
    default=None,
    validator=validators.optional(validators.instance_of(int)),
    )

    @store.validator
    def _validate_stores(self, attribute, value):
    """Ensure the store parameter is permitted in the subclass instance."""
    print("Debug 1", value)
    if value is not None and value not in self.PARAMS["store"]:
    msg = (
    f"The recommended options for `{attribute.name}` are "
    f"({list(self.PARAMS['store'].keys())}), but '{value}' was entered."
    )
    warnings.warn(msg, UserWarning)

    self._validate_hours(self.__class__.hours, self.hours) # Doesn't seem to be executing?


    @hours.validator
    def _validate_hours(self, attribute, value):
    """Ensure the hours parameter is valid for the store parameter in the subclass instance."""
    print("Debug 2")

    if value is None:
    return

    # Determine valid hours range based on the store
    if self.store in self.PARAMS["store"]:
    hours_range = self.PARAMS["store"][self.store].hours
    else:
    hours_range = self.ALLOWED_STORES

    min_val, max_val = hours_range.opening, hours_range.closing

    # Check if hours is within range
    if not (min_val <= value <= max_val):
    warnings.warn(
    f"Provided hours {value} is out of range for store '{self.store}' "
    f"(valid range: {min_val} - {max_val})",
    UserWarning
    )

    # Other logic


    Testing:

    my_instance = Foo(store="grocer_A", hours=3)
    # Warning issued as expected
    # Debug 1 grocer_a
    # Debug 2
    # Debug 2

    my_instance = Foo(store="grocer_A", hours=5)
    # Debug 1 grocer_a
    # Debug 2
    # Debug 2

    print(f"*The store is set as {my_instance.store} with hours {my_instance.hours}.")
    # *The store is set as grocer_a with hours 5.

    my_instance.store = "grocer_c"
    # Debug 1 grocer_c
    # Debug 2
    # A Warning should have been issued! 5 is not allowed for grocer_c

    print(f"*The store is set as {my_instance.store} with hours {my_instance.hours}.")
    # *The store is set as grocer_c with hours 5.

    Continue reading...

Compartilhe esta Página