Repository: python-attrs/attrs (python)

Issue:
attr.ib cleanup + auto_attribs + class vars from superclasses
Consider the following:

```python
import attr

class Base:
    x = 0

@attr.s(auto_attribs=True)
class C(Base):
    x: int
```

This raises an:

```
Traceback (most recent call last):
  File "t.py", line 9, in <module>
    class C(Base):
  File "/Users/hynek/Projects/attrs/src/attr/_make.py", line 858, in wrap
    return builder.build_class()
  File "/Users/hynek/Projects/attrs/src/attr/_make.py", line 496, in build_class
    return self._patch_original_class()
  File "/Users/hynek/Projects/attrs/src/attr/_make.py", line 514, in _patch_original_class
    delattr(cls, name)
AttributeError: x
```

(interestingly `getattr(cls, name)` works but `delattr` does not)

This is actually quite common if you implement an ABC. The current code is:

```python
                if (
                    name not in super_names
                    and getattr(cls, name, None) is not None
                ):
                    delattr(cls, name)
```

Maybe we should change the check to `isinstance(getattr(cls, name, None), _CountingAttr)`?

Pull request:
Allow to override class vars of super classes w/ annotations
This is the best I could come up with for #427. Am I missing some weird edge case here? I’m afraid to silence some legit error but can’t think of a better way.

Fixes #427 


Patch:
diff --git a/changelog.d/431.change.rst b/changelog.d/431.change.rst
new file mode 100644
index 0000000..c96bb7b
--- /dev/null
+++ b/changelog.d/431.change.rst
@@ -0,0 +1,1 @@
+It is now possible to override a superclass' class variable using only class annotations.
diff --git a/src/attr/_make.py b/src/attr/_make.py
index 3eef8f9..ecd7c09 100644
--- a/src/attr/_make.py
+++ b/src/attr/_make.py
@@ -522,7 +522,13 @@ class _ClassBuilder(object):
                     name not in super_names
                     and getattr(cls, name, None) is not None
                 ):
-                    delattr(cls, name)
+                    try:
+                        delattr(cls, name)
+                    except AttributeError:
+                        # This can happen if a superclass defines a class
+                        # variable and we want to set an attribute with the
+                        # same name by using only a type annotation.
+                        pass
 
         # Attach our dunder methods.
         for name, value in self._cls_dict.items():


Test patch:
diff --git a/tests/test_annotations.py b/tests/test_annotations.py
index fee45d1..4be1ea6 100644
--- a/tests/test_annotations.py
+++ b/tests/test_annotations.py
@@ -165,7 +165,7 @@ class TestAnnotations:
     @pytest.mark.parametrize("slots", [True, False])
     def test_auto_attribs_subclassing(self, slots):
         """
-        Attributes from super classes are inherited, it doesn't matter if the
+        Attributes from superclasses are inherited, it doesn't matter if the
         subclass has annotations or not.
 
         Ref #291
@@ -250,3 +250,18 @@ class TestAnnotations:
 
         assert c.x == 0
         assert c.y == 1
+
+    def test_super_class_variable(self):
+        """
+        Superclass class variables can be overridden with an attribute
+        without resorting to using an explicit `attr.ib()`.
+        """
+
+        class Base:
+            x: int = 42
+
+        @attr.s(auto_attribs=True)
+        class C(Base):
+            x: int
+
+        assert 1 == C(1).x
diff --git a/tests/test_dark_magic.py b/tests/test_dark_magic.py
index d3f8ea9..e033627 100644
--- a/tests/test_dark_magic.py
+++ b/tests/test_dark_magic.py
@@ -421,7 +421,7 @@ class TestDarkMagic(object):
 
     def test_overwrite_super(self):
         """
-        Super classes can overwrite each other and the attributes are added
+        Superclasses can overwrite each other and the attributes are added
         in the order they are defined.
         """
 
diff --git a/tests/test_make.py b/tests/test_make.py
index fe6d233..884aee0 100644
--- a/tests/test_make.py
+++ b/tests/test_make.py
@@ -323,7 +323,7 @@ class TestTransformAttrs(object):
 
     def test_these(self):
         """
-        If these is passed, use it and ignore body and super classes.
+        If these is passed, use it and ignore body and superclasses.
         """
 
         class Base(object):