Skip to content
This repository has been archived by the owner on Nov 15, 2024. It is now read-only.

Commit

Permalink
add on_setattr (#14)
Browse files Browse the repository at this point in the history
* allow for custom repr function + bump version

* remove '_get_descriptors'

* make private classmethods functions

* improve readability

* add 'on_setattr'
  • Loading branch information
PythonFZ authored Dec 14, 2022
1 parent b8f1a00 commit 8a058cc
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 0 deletions.
20 changes: 20 additions & 0 deletions tests/test_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,23 @@ class WithMetadata:
def test_descriptor_metadata():
"""Test a descriptor with metadata."""
assert WithMetadata.value.metadata["foo"] == "bar"


class WithSetAttr:
"""Modify the value before saving it."""

value = Descriptor(on_setattr=lambda self, value: value + 1)
value_frozen = Descriptor(frozen=True, on_setattr=lambda self, value: value + 1)


def test_with_setattr():
"""Test a descriptor with on_setattr."""
example = WithSetAttr()
example.value = 1
assert example.value == 2

example.value_frozen = 1
assert example.value_frozen == 2

with pytest.raises(TypeError):
example.value_frozen = 10
7 changes: 7 additions & 0 deletions zninit/descriptor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def __init__(
check_types: bool = False,
metadata: dict = None,
frozen: bool = False,
on_setattr: typing.Callable = None,
): # pylint: disable=too-many-arguments
"""Define a Descriptor object.
Expand All @@ -81,6 +82,9 @@ def __init__(
additional metadata for the descriptor.
repr_func: Callable, default=repr
A callable that will be used to compute the _repr_ of the descriptor.
on_setattr: Callable, default=None
A callable that is run whenever an attribute is set via 'class.myattr = value'
or 'setattr(class, "mattr", value)'.
"""
self._default = default
self._owner = owner
Expand All @@ -91,6 +95,7 @@ def __init__(
self.metadata = metadata or {}
self.frozen = frozen
self.get_repr = repr_func
self.on_setattr = on_setattr
self._frozen = weakref.WeakKeyDictionary()
if check_types and ("typeguard" not in sys.modules):
raise ImportError(
Expand Down Expand Up @@ -164,6 +169,8 @@ def __set__(self, instance, value):
"""Save value to instance.__dict__."""
if self._frozen.get(instance, False):
raise TypeError(f"Frozen attribute '{self.name}' can not be changed.")
if self.on_setattr is not None:
value = self.on_setattr(instance, value)
if self.check_types:
typeguard.check_type(
argname=self.name, value=value, expected_type=self.annotation
Expand Down

0 comments on commit 8a058cc

Please sign in to comment.