Repository: python-attrs/attrs
Pull request #1264 (2024-03-16 16:18:16)

evolve: remove support for passing instances per kw
As per our deprecation policy.



Patch:
diff --git a/changelog.d/1264.breaking.md b/changelog.d/1264.breaking.md
new file mode 100644
index 0000000..f268e2d
--- /dev/null
+++ b/changelog.d/1264.breaking.md
@@ -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.
diff --git a/src/attr/_funcs.py b/src/attr/_funcs.py
index 5be1adb..0912a45 100644
--- a/src/attr/_funcs.py
+++ b/src/attr/_funcs.py
@@ -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.
@@ -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)


Test patch:
diff --git a/tests/test_funcs.py b/tests/test_funcs.py
index 398ba35..67c9bc9 100644
--- a/tests/test_funcs.py
+++ b/tests/test_funcs.py
@@ -781,26 +781,13 @@ class TestEvolve:
             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):