Skip to content

Commit

Permalink
__attrs_pre_init__ (#750)
Browse files Browse the repository at this point in the history
* add pre-init following post-init pattern

* add tests

* add changelog

* some typos
  • Loading branch information
indigoviolet authored Jan 25, 2021
1 parent 654aa92 commit efcbae5
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 3 deletions.
1 change: 1 addition & 0 deletions changelog.d/750.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow for a ``__attrs_pre_init__()`` method that -- if defined -- will get called at the beginning of the ``attrs``-generated ``__init__()`` method.
23 changes: 20 additions & 3 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,7 @@ class _ClassBuilder(object):
"_cls_dict",
"_delete_attribs",
"_frozen",
"_has_pre_init",
"_has_post_init",
"_is_exc",
"_on_setattr",
Expand Down Expand Up @@ -633,6 +634,7 @@ def __init__(
self._frozen = frozen
self._weakref_slot = weakref_slot
self._cache_hash = cache_hash
self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False))
self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
self._delete_attribs = not bool(these)
self._is_exc = is_exc
Expand Down Expand Up @@ -889,6 +891,7 @@ def add_init(self):
_make_init(
self._cls,
self._attrs,
self._has_pre_init,
self._has_post_init,
self._frozen,
self._slots,
Expand All @@ -908,6 +911,7 @@ def add_attrs_init(self):
_make_init(
self._cls,
self._attrs,
self._has_pre_init,
self._has_post_init,
self._frozen,
self._slots,
Expand Down Expand Up @@ -1177,9 +1181,11 @@ def attrs(
behavior <https://github.com/python-attrs/attrs/issues/136>`_ for more
details.
:param bool init: Create a ``__init__`` method that initializes the
``attrs`` attributes. Leading underscores are stripped for the
argument name. If a ``__attrs_post_init__`` method exists on the
class, it will be called after the class is fully initialized.
``attrs`` attributes. Leading underscores are stripped for the argument
name. If a ``__attrs_pre_init__`` method exists on the class, it will
be called before the class is initialized. If a ``__attrs_post_init__``
method exists on the class, it will be called after the class is fully
initialized.
If ``init`` is ``False``, an ``__attrs_init__`` method will be
injected instead. This allows you to define a custom ``__init__``
Expand Down Expand Up @@ -1326,6 +1332,8 @@ def attrs(
.. versionadded:: 20.3.0 *field_transformer*
.. versionchanged:: 21.1.0
``init=False`` injects ``__attrs_init__``
.. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__``
"""
if auto_detect and PY2:
raise PythonTooOldError(
Expand Down Expand Up @@ -1893,6 +1901,7 @@ def _is_slot_attr(a_name, base_attr_map):
def _make_init(
cls,
attrs,
pre_init,
post_init,
frozen,
slots,
Expand Down Expand Up @@ -1931,6 +1940,7 @@ def _make_init(
filtered_attrs,
frozen,
slots,
pre_init,
post_init,
cache_hash,
base_attr_map,
Expand Down Expand Up @@ -2071,6 +2081,7 @@ def _attrs_to_init_script(
attrs,
frozen,
slots,
pre_init,
post_init,
cache_hash,
base_attr_map,
Expand All @@ -2088,6 +2099,9 @@ def _attrs_to_init_script(
a cached ``object.__setattr__``.
"""
lines = []
if pre_init:
lines.append("self.__attrs_pre_init__()")

if needs_cached_setattr:
lines.append(
# Circumvent the __setattr__ descriptor to save one lookup per
Expand Down Expand Up @@ -2789,10 +2803,13 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments):
else:
raise TypeError("attrs argument must be a dict or a list.")

pre_init = cls_dict.pop("__attrs_pre_init__", None)
post_init = cls_dict.pop("__attrs_post_init__", None)
user_init = cls_dict.pop("__init__", None)

body = {}
if pre_init is not None:
body["__attrs_pre_init__"] = pre_init
if post_init is not None:
body["__attrs_post_init__"] = post_init
if user_init is not None:
Expand Down
8 changes: 8 additions & 0 deletions tests/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,17 @@ class HypClass:
attr_names = gen_attr_names()

cls_dict = dict(zip(attr_names, attrs))
pre_init_flag = draw(st.booleans())
post_init_flag = draw(st.booleans())
init_flag = draw(st.booleans())

if pre_init_flag:

def pre_init(self):
pass

cls_dict["__attrs_pre_init__"] = pre_init

if post_init_flag:

def post_init(self):
Expand Down
1 change: 1 addition & 0 deletions tests/test_dunders.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def _add_init(cls, frozen):
cls.__init__ = _make_init(
cls,
cls.__attrs_attrs__,
getattr(cls, "__attrs_pre_init__", False),
getattr(cls, "__attrs_post_init__", False),
frozen,
_is_slot_cls(cls),
Expand Down
37 changes: 37 additions & 0 deletions tests/test_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,22 @@ class D(object):
assert C.D.__name__ == "D"
assert C.D.__qualname__ == C.__qualname__ + ".D"

@pytest.mark.parametrize("with_validation", [True, False])
def test_pre_init(self, with_validation, monkeypatch):
"""
Verify that __attrs_pre_init__ gets called if defined.
"""
monkeypatch.setattr(_config, "_run_validators", with_validation)

@attr.s
class C(object):
def __attrs_pre_init__(self2):
self2.z = 30

c = C()

assert 30 == getattr(c, "z", None)

@pytest.mark.parametrize("with_validation", [True, False])
def test_post_init(self, with_validation, monkeypatch):
"""
Expand All @@ -647,6 +663,27 @@ def __attrs_post_init__(self2):

assert 30 == getattr(c, "z", None)

@pytest.mark.parametrize("with_validation", [True, False])
def test_pre_post_init_order(self, with_validation, monkeypatch):
"""
Verify that __attrs_post_init__ gets called if defined.
"""
monkeypatch.setattr(_config, "_run_validators", with_validation)

@attr.s
class C(object):
x = attr.ib()

def __attrs_pre_init__(self2):
self2.z = 30

def __attrs_post_init__(self2):
self2.z += self2.x

c = C(x=10)

assert 40 == getattr(c, "z", None)

def test_types(self):
"""
Sets the `Attribute.type` attr from type argument.
Expand Down

0 comments on commit efcbae5

Please sign in to comment.