Skip to content

Commit

Permalink
Merge branch 'main' into add-mypy-const
Browse files Browse the repository at this point in the history
  • Loading branch information
hynek committed Aug 16, 2022
2 parents 8ed30cf + 70b7f88 commit 96c1473
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 15 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

Expand All @@ -54,7 +54,7 @@ jobs:

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
- uses: actions/setup-python@v4
with:
# Use latest Python, so it understands all syntax.
python-version: ${{env.PYTHON_LATEST}}
Expand Down Expand Up @@ -86,7 +86,7 @@ jobs:

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
- uses: actions/setup-python@v4
with:
python-version: ${{env.PYTHON_LATEST}}

Expand All @@ -107,7 +107,7 @@ jobs:

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
- uses: actions/setup-python@v4
with:
python-version: ${{env.PYTHON_LATEST}}
- run: python -m pip install -e .[dev]
Expand Down
3 changes: 3 additions & 0 deletions changelog.d/1001.change.rst
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions changelog.d/997.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
``attrs.has()`` is now a ``TypeGuard`` for ``AttrsInstance``.
That means that type checkers know a class is an instance of an ``attrs`` class if you check it using ``attrs.has()`` (or ``attr.has()``) first.
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,5 +143,4 @@ def find_meta(meta):
install_requires=INSTALL_REQUIRES,
extras_require=EXTRAS_REQUIRE,
include_package_data=True,
options={"bdist_wheel": {"universal": "1"}},
)
7 changes: 6 additions & 1 deletion src/attr/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ from ._typing_compat import AttrsInstance_

AttrsInstance = AttrsInstance_

if sys.version_info >= (3, 10):
from typing import TypeGuard
else:
from typing_extensions import TypeGuard

__version__: str
__version_info__: VersionInfo
__title__: str
Expand Down Expand Up @@ -467,7 +472,7 @@ def astuple(
tuple_factory: Type[Sequence[Any]] = ...,
retain_collection_types: bool = ...,
) -> Tuple[Any, ...]: ...
def has(cls: type) -> bool: ...
def has(cls: type) -> TypeGuard[Type[AttrsInstance]]: ...
def assoc(inst: _T, **changes: Any) -> _T: ...
def evolve(inst: _T, **changes: Any) -> _T: ...

Expand Down
34 changes: 26 additions & 8 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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()
12 changes: 12 additions & 0 deletions tests/test_mypy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1371,3 +1371,15 @@
b: str
fields(A) # E: Argument 1 to "fields" has incompatible type "Type[A]"; expected "Type[AttrsInstance]"
- case: testHasTypeGuard
main: |
from attrs import define, has
@define
class A:
pass
reveal_type(A) # N: Revealed type is "def () -> main.A"
if has(A):
reveal_type(A) # N: Revealed type is "Type[attr.AttrsInstance]"
5 changes: 5 additions & 0 deletions tests/typing_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,3 +416,8 @@ def accessing_from_attrs() -> None:
attrs.setters.frozen
attrs.validators.and_
attrs.cmp_using


foo = object
if attrs.has(foo) or attr.has(foo):
foo.__attrs_attrs__
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ commands =
skip_install = true
deps = pre-commit
passenv = HOMEPATH # needed on Windows
commands = pre-commit run --all-files
commands = pre-commit run --all-files --show-diff-on-failure


[testenv:manifest]
Expand Down

0 comments on commit 96c1473

Please sign in to comment.