Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelog.d/1264.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
`attrs.evolve()` doesn't accept the *inst* argument as a keyword argument anymore.
Pass it as the first positional argument instead.
33 changes: 10 additions & 23 deletions src/attr/_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,8 @@ def evolve(*args, **changes):
Create a new instance, based on the first positional argument with
*changes* applied.

:param inst: Instance of a class with *attrs* attributes.
:param inst: Instance of a class with *attrs* attributes. *inst* must be
passed as a positional argument.
:param changes: Keyword changes in the new copy.

:return: A copy of inst with *changes* incorporated.
Expand All @@ -387,30 +388,16 @@ def evolve(*args, **changes):
*inst*. It will raise a warning until at least April 2024, after which
it will become an error. Always pass the instance as a positional
argument.
.. versionchanged:: 24.1.0
*inst* can't be passed as a keyword argument anymore.
"""
# Try to get instance by positional argument first.
# Use changes otherwise and warn it'll break.
if args:
try:
(inst,) = args
except ValueError:
msg = f"evolve() takes 1 positional argument, but {len(args)} were given"
raise TypeError(msg) from None
else:
try:
inst = changes.pop("inst")
except KeyError:
msg = "evolve() missing 1 required positional argument: 'inst'"
raise TypeError(msg) from None

import warnings

warnings.warn(
"Passing the instance per keyword argument is deprecated and "
"will stop working in, or after, April 2024.",
DeprecationWarning,
stacklevel=2,
try:
(inst,) = args
except ValueError:
msg = (
f"evolve() takes 1 positional argument, but {len(args)} were given"
)
raise TypeError(msg) from None

cls = inst.__class__
attrs = fields(cls)
Expand Down
19 changes: 3 additions & 16 deletions tests/test_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -781,26 +781,13 @@ class Cls2:
obj1a, param1=obj2b
)

def test_inst_kw(self):
"""
If `inst` is passed per kw argument, a warning is raised.
See #1109
"""

@attr.s
class C:
pass

with pytest.warns(DeprecationWarning) as wi:
evolve(inst=C())

assert __file__ == wi.list[0].filename

def test_no_inst(self):
"""
Missing inst argument raises a TypeError like Python would.
"""
with pytest.raises(TypeError, match=r"evolve\(\) missing 1"):
with pytest.raises(
TypeError, match=r"evolve\(\) takes 1 positional argument"
):
evolve(x=1)

def test_too_many_pos_args(self):
Expand Down