Skip to content

Commit

Permalink
Call abc.update_abstractmethods on 3.10+ (#1001)
Browse files Browse the repository at this point in the history
  • Loading branch information
hynek committed Aug 11, 2022
1 parent 536ddc5 commit c58ffd4
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 8 deletions.
3 changes: 3 additions & 0 deletions changelog.d/1001.change.rst
@@ -0,0 +1,3 @@
On Python 3.10 and later, call `abc.update_abstractmethods() <https://docs.pytho.
org/3.10/library/abc.html#abc.update_abstractmethods>`_ on dict classes after creation.
This improves the detection of abstractness.
34 changes: 26 additions & 8 deletions src/attr/_make.py
Expand Up @@ -717,15 +717,33 @@ def __init__(
def __repr__(self):
return f"<_ClassBuilder(cls={self._cls.__name__})>"

def build_class(self):
"""
Finalize class based on the accumulated configuration.
if PY310:
import abc

def build_class(self):
"""
Finalize class based on the accumulated configuration.
Builder cannot be used after calling this method.
"""
if self._slots is True:
return self._create_slots_class()

return self.abc.update_abstractmethods(
self._patch_original_class()
)

else:

def build_class(self):
"""
Finalize class based on the accumulated configuration.
Builder cannot be used after calling this method.
"""
if self._slots is True:
return self._create_slots_class()

Builder cannot be used after calling this method.
"""
if self._slots is True:
return self._create_slots_class()
else:
return self._patch_original_class()

def _patch_original_class(self):
Expand Down
56 changes: 56 additions & 0 deletions tests/test_abc.py
@@ -0,0 +1,56 @@
# SPDX-License-Identifier: MIT

import abc
import inspect

import pytest

import attrs

from attr._compat import PY310


@pytest.mark.skipif(not PY310, reason="abc.update_abstractmethods is 3.10+")
class TestUpdateAbstractMethods:
def test_abc_implementation(self, slots):
"""
If an attrs class implements an abstract method, it stops being
abstract.
"""

class Ordered(abc.ABC):
@abc.abstractmethod
def __lt__(self, other):
pass

@abc.abstractmethod
def __le__(self, other):
pass

@attrs.define(order=True, slots=slots)
class Concrete(Ordered):
x: int

assert not inspect.isabstract(Concrete)
assert Concrete(2) > Concrete(1)

def test_remain_abstract(self, slots):
"""
If an attrs class inherits from an abstract class but doesn't implement
abstract methods, it remains abstract.
"""

class A(abc.ABC):
@abc.abstractmethod
def foo(self):
pass

@attrs.define(slots=slots)
class StillAbstract(A):
pass

assert inspect.isabstract(StillAbstract)
with pytest.raises(
TypeError, match="class StillAbstract with abstract method foo"
):
StillAbstract()

0 comments on commit c58ffd4

Please sign in to comment.