Skip to content

Commit e58cff7

Browse files
committed
Values given to `__init__` should now always be propogated to `__attrs_pre_init__`, regardless of default configuration.
1 parent 3d42a69 commit e58cff7

File tree

3 files changed

+52
-4
lines changed

3 files changed

+52
-4
lines changed

changelog.d/1427.change.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Values passed to the `__init__()` method of `attrs` classes are now correctly passed to `__attrs_pre_init__()` instead of their default values (in cases where *kw_only* was not specified).

src/attr/_make.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -2126,8 +2126,9 @@ def _attrs_to_init_script(
21262126
)
21272127
lines.extend(extra_lines)
21282128

2129-
args = []
2130-
kw_only_args = []
2129+
args = [] # Parameters in the definition of __init__
2130+
pre_init_args = [] # Parameters in the call to __attrs_pre_init__
2131+
kw_only_args = [] # Used for both 'args' and 'pre_init_args' above
21312132
attrs_to_validate = []
21322133

21332134
# This is a dictionary of names to validator and converter callables.
@@ -2205,6 +2206,7 @@ def _attrs_to_init_script(
22052206
kw_only_args.append(arg)
22062207
else:
22072208
args.append(arg)
2209+
pre_init_args.append(arg_name)
22082210

22092211
if converter is not None:
22102212
lines.append(
@@ -2224,6 +2226,7 @@ def _attrs_to_init_script(
22242226
kw_only_args.append(arg)
22252227
else:
22262228
args.append(arg)
2229+
pre_init_args.append(arg_name)
22272230
lines.append(f"if {arg_name} is not NOTHING:")
22282231

22292232
init_factory_name = _INIT_FACTORY_PAT % (a.name,)
@@ -2266,6 +2269,7 @@ def _attrs_to_init_script(
22662269
kw_only_args.append(arg_name)
22672270
else:
22682271
args.append(arg_name)
2272+
pre_init_args.append(arg_name)
22692273

22702274
if converter is not None:
22712275
lines.append(
@@ -2322,7 +2326,7 @@ def _attrs_to_init_script(
23222326
lines.append(f"BaseException.__init__(self, {vals})")
23232327

23242328
args = ", ".join(args)
2325-
pre_init_args = args
2329+
pre_init_args = ", ".join(pre_init_args)
23262330
if kw_only_args:
23272331
# leading comma & kw_only args
23282332
args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}"
@@ -2337,7 +2341,7 @@ def _attrs_to_init_script(
23372341
pre_init_args += pre_init_kw_only_args
23382342

23392343
if call_pre_init and pre_init_has_args:
2340-
# If pre init method has arguments, pass same arguments as `__init__`.
2344+
# If pre init method has arguments, pass the values given to __init__.
23412345
lines[0] = f"self.__attrs_pre_init__({pre_init_args})"
23422346

23432347
# Python <3.12 doesn't allow backslashes in f-strings.

tests/test_make.py

+43
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,49 @@ def __attrs_pre_init__(self, *, kw_and_default):
715715

716716
assert 3 == val == inst.kw_and_default
717717

718+
@pytest.mark.usefixtures("with_and_without_validation")
719+
def test_pre_init_with_mixture_of_defaults_and_kw_only(self):
720+
"""
721+
Attrs should properly handle a mixture of positional, positional with
722+
default, keyword-only, and keyword-only with default attributes when
723+
passing values to __attrs_pre_init__.
724+
"""
725+
g_val1 = None
726+
g_val2 = None
727+
g_val3 = None
728+
g_val4 = None
729+
g_val5 = None
730+
g_val6 = None
731+
732+
@attr.define
733+
class MixtureClass:
734+
val1: int
735+
val2: int = 100
736+
val3: int = attr.field(factory=int)
737+
val4: int = attr.field(kw_only=True)
738+
val5: int = attr.field(default=100, kw_only=True)
739+
val6: int = attr.field(factory=int, kw_only=True)
740+
741+
def __attrs_pre_init__(self, val1, val2, val3, val4, val5, val6):
742+
nonlocal g_val1, g_val2, g_val3, g_val4, g_val5, g_val6
743+
g_val1 = val1
744+
g_val2 = val2
745+
g_val3 = val3
746+
g_val4 = val4
747+
g_val5 = val5
748+
g_val6 = val6
749+
750+
inst = MixtureClass(
751+
val1=200, val2=200, val3=200, val4=200, val5=200, val6=200
752+
)
753+
754+
assert 200 == g_val1 == inst.val1
755+
assert 200 == g_val2 == inst.val2
756+
assert 200 == g_val3 == inst.val3
757+
assert 200 == g_val4 == inst.val4
758+
assert 200 == g_val5 == inst.val5
759+
assert 200 == g_val6 == inst.val6
760+
718761
@pytest.mark.usefixtures("with_and_without_validation")
719762
def test_post_init(self):
720763
"""

0 commit comments

Comments
 (0)