Skip to content

Commit

Permalink
Merge branch 'main' into add-has-typeguard
Browse files Browse the repository at this point in the history
  • Loading branch information
hynek authored Aug 11, 2022
2 parents 06ee0a8 + 536ddc5 commit d824f72
Show file tree
Hide file tree
Showing 13 changed files with 34 additions and 85 deletions.
8 changes: 4 additions & 4 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,20 @@ $ make html

The built documentation can then be found in `docs/_build/html/`.

To avoid committing code that violates our style guide, we strongly advise you to install [*pre-commit*] [^dev] hooks:
To avoid committing code that violates our style guide, we strongly advise you to install [*pre-commit*] and its hooks:

```console
$ pre-commit install
```

You can also run them anytime (as our *tox* does) using:
This is not strictly necessary, because our [*tox*] file contains an environment that runs:

```console
$ pre-commit run --all-files
```

[^dev]: *pre-commit* should have been installed into your virtualenv automatically when you ran `pip install -e '.[dev]'` above.
If *pre-commit* is missing, your probably need to run `pip install -e '.[dev]'` again.
and our CI has integration with `pre-commit.ci <https://pre-commit.ci>`_.
But it's way more comfortable to run it locally and *git* catching avoidable errors.


## Code
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ Backward-incompatible Changes
Changes
^^^^^^^

- ``attr.define()`` et al now correct detect ``__eq__`` and ``__ne__``.
- ``attr.define()`` et al now correctly detect ``__eq__`` and ``__ne__``.
`#671 <https://github.com/python-attrs/attrs/issues/671>`_
- ``attr.define()`` et al's hybrid behavior now also works correctly when arguments are passed.
`#675 <https://github.com/python-attrs/attrs/issues/675>`_
Expand Down
12 changes: 12 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
# SPDX-License-Identifier: MIT

import pytest

from hypothesis import HealthCheck, settings

from attr._compat import PY310


@pytest.fixture(name="slots", params=(True, False))
def _slots(request):
return request.param


@pytest.fixture(name="frozen", params=(True, False))
def _frozen(request):
return request.param


def pytest_configure(config):
# HealthCheck.too_slow causes more trouble than good -- especially in CIs.
settings.register_profile(
Expand Down
10 changes: 7 additions & 3 deletions docs/init.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,13 @@ Try to design your classes in a way that is clean and convenient to use -- not b
The database format can change anytime and you're stuck with a bad class design that is hard to change.
Embrace functions and classmethods as a filter between reality and what's best for you to work with.

If you look for object serialization, there's a bunch of projects listed on our ``attrs`` extensions `Wiki page`_.
Some of them even support nested schemas.
.. warning::

While ``attrs``'s initialization concepts (including the following sections about validators and converters) are powerful, they are **not** intended to replace a fully-featured serialization or validation system.

We want to help you to write a ``__init__`` that you'd write by hand, but with less boilerplate.

If you look for powerful-yet-unintrusive serialization and validation for your ``attrs`` classes, have a look at our sibling project `cattrs <https://cattrs.readthedocs.io/>`_ or our `third-party extensions <https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs>`_.


Private Attributes
Expand Down Expand Up @@ -485,6 +490,5 @@ That said, and as pointed out in the beginning of the chapter, a better approach
This makes the class more testable.


.. _`Wiki page`: https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs
.. _`get confused`: https://github.com/python-attrs/attrs/issues/289
.. _`there is no such thing as a private argument`: https://github.com/hynek/characteristic/issues/6
8 changes: 4 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
NAME = "attrs"
PACKAGES = find_packages(where="src")
META_PATH = os.path.join("src", "attr", "__init__.py")
KEYWORDS = ["class", "attribute", "boilerplate"]
KEYWORDS = ["class", "attribute", "boilerplate", "dataclass"]
PROJECT_URLS = {
"Documentation": "https://www.attrs.org/",
"Changelog": "https://www.attrs.org/en/stable/changelog.html",
Expand Down Expand Up @@ -56,11 +56,11 @@
"mypy>=0.971; python_implementation == 'CPython'",
"pytest-mypy-plugins; python_implementation == 'CPython'",
],
"tests": {
"tests": [
"attrs[tests-no-zope]",
"zope.interface",
},
"dev": {"attrs[tests,docs]": ["pre-commit"]},
],
"dev": ["attrs[tests,docs]"],
}
# Don't break Paul unnecessarily just yet. C.f. #685
EXTRAS_REQUIRE["tests_no_zope"] = EXTRAS_REQUIRE["tests-no-zope"]
Expand Down
9 changes: 0 additions & 9 deletions tests/test_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ class C:
sys.version_info[:2] < (3, 11),
reason="Incompatible behavior on older Pythons",
)
@pytest.mark.parametrize("slots", [True, False])
def test_auto_attribs(self, slots):
"""
If *auto_attribs* is True, bare annotations are collected too.
Expand Down Expand Up @@ -154,7 +153,6 @@ class C:
foo=typing.Any,
)

@pytest.mark.parametrize("slots", [True, False])
def test_auto_attribs_unannotated(self, slots):
"""
Unannotated `attr.ib`s raise an error.
Expand All @@ -172,7 +170,6 @@ class C:
"The following `attr.ib`s lack a type annotation: v, y.",
) == e.value.args

@pytest.mark.parametrize("slots", [True, False])
def test_auto_attribs_subclassing(self, slots):
"""
Attributes from base classes are inherited, it doesn't matter if the
Expand Down Expand Up @@ -390,7 +387,6 @@ def noop():
sys.version_info[:2] < (3, 11),
reason="Incompatible behavior on older Pythons",
)
@pytest.mark.parametrize("slots", [True, False])
def test_annotations_strings(self, slots):
"""
String annotations are passed into __init__ as is.
Expand Down Expand Up @@ -423,7 +419,6 @@ class C:
foo=typing.Any,
)

@pytest.mark.parametrize("slots", [True, False])
def test_typing_extensions_classvar(self, slots):
"""
If ClassVar is coming from typing_extensions, it is recognized too.
Expand Down Expand Up @@ -521,7 +516,6 @@ class C:
assert str is attr.fields(C).y.type
assert None is attr.fields(C).z.type

@pytest.mark.parametrize("slots", [True, False])
def test_resolve_types_auto_attrib(self, slots):
"""
Types can be resolved even when strings are involved.
Expand All @@ -541,7 +535,6 @@ class A:
assert typing.List[int] == attr.fields(A).b.type
assert typing.List[int] == attr.fields(A).c.type

@pytest.mark.parametrize("slots", [True, False])
def test_resolve_types_decorator(self, slots):
"""
Types can be resolved using it as a decorator.
Expand All @@ -558,7 +551,6 @@ class A:
assert typing.List[int] == attr.fields(A).b.type
assert typing.List[int] == attr.fields(A).c.type

@pytest.mark.parametrize("slots", [True, False])
def test_self_reference(self, slots):
"""
References to self class using quotes can be resolved.
Expand All @@ -574,7 +566,6 @@ class A:
assert A == attr.fields(A).a.type
assert typing.Optional[A] == attr.fields(A).b.type

@pytest.mark.parametrize("slots", [True, False])
def test_forward_reference(self, slots):
"""
Forward references can be resolved.
Expand Down
4 changes: 0 additions & 4 deletions tests/test_dunders.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,6 @@ class TestAddRepr:
Tests for `_add_repr`.
"""

@pytest.mark.parametrize("slots", [True, False])
def test_repr(self, slots):
"""
If `repr` is False, ignore that attribute.
Expand Down Expand Up @@ -646,8 +645,6 @@ def __hash__(self):
assert 1 == cached_instance.hash_counter.times_hash_called

@pytest.mark.parametrize("cache_hash", [True, False])
@pytest.mark.parametrize("frozen", [True, False])
@pytest.mark.parametrize("slots", [True, False])
def test_copy_hash_cleared(self, cache_hash, frozen, slots):
"""
Test that the default hash is recalculated after a copy operation.
Expand Down Expand Up @@ -701,7 +698,6 @@ def test_cache_hash_serialization_hash_cleared(self, klass, cached):

assert original_hash != hash(obj_rt)

@pytest.mark.parametrize("frozen", [True, False])
def test_copy_two_arg_reduce(self, frozen):
"""
If __getstate__ returns None, the tuple returned by object.__reduce__
Expand Down
26 changes: 6 additions & 20 deletions tests/test_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,6 @@ def compute(self):

assert C(1, 2) == C()

@pytest.mark.parametrize("slots", [True, False])
@pytest.mark.parametrize("frozen", [True, False])
@pytest.mark.parametrize("weakref_slot", [True, False])
def test_attrib_overwrite(self, slots, frozen, weakref_slot):
"""
Expand Down Expand Up @@ -423,7 +421,6 @@ class C:
class D(C):
pass

@pytest.mark.parametrize("slots", [True, False])
def test_hash_false_eq_false(self, slots):
"""
hash=False and eq=False make a class hashable by ID.
Expand All @@ -435,7 +432,6 @@ class C:

assert hash(C()) != hash(C())

@pytest.mark.parametrize("slots", [True, False])
def test_eq_false(self, slots):
"""
eq=False makes a class hashable by ID.
Expand Down Expand Up @@ -543,8 +539,6 @@ class C:
assert "itemgetter" == attr.fields(C).itemgetter.name
assert "x" == attr.fields(C).x.name

@pytest.mark.parametrize("slots", [True, False])
@pytest.mark.parametrize("frozen", [True, False])
def test_auto_exc(self, slots, frozen):
"""
Classes with auto_exc=True have a Exception-style __str__, compare and
Expand Down Expand Up @@ -599,8 +593,6 @@ class FooError(Exception):
deepcopy(e1)
deepcopy(e2)

@pytest.mark.parametrize("slots", [True, False])
@pytest.mark.parametrize("frozen", [True, False])
def test_auto_exc_one_attrib(self, slots, frozen):
"""
Having one attribute works with auto_exc=True.
Expand All @@ -614,8 +606,6 @@ class FooError(Exception):

FooError(1)

@pytest.mark.parametrize("slots", [True, False])
@pytest.mark.parametrize("frozen", [True, False])
def test_eq_only(self, slots, frozen):
"""
Classes with order=False cannot be ordered.
Expand All @@ -639,7 +629,6 @@ class C:

assert ei.value.args[0] in possible_errors

@pytest.mark.parametrize("slots", [True, False])
@pytest.mark.parametrize("cmp", [True, False])
def test_attrib_cmp_shortcut(self, slots, cmp):
"""
Expand All @@ -653,7 +642,6 @@ class C:
assert cmp is attr.fields(C).x.eq
assert cmp is attr.fields(C).x.order

@pytest.mark.parametrize("slots", [True, False])
def test_no_setattr_if_validate_without_validators(self, slots):
"""
If a class has on_setattr=attr.setters.validate (former default in NG
Expand All @@ -663,11 +651,11 @@ def test_no_setattr_if_validate_without_validators(self, slots):
Regression test for #816.
"""

@attr.s(on_setattr=attr.setters.validate)
@attr.s(on_setattr=attr.setters.validate, slots=slots)
class C:
x = attr.ib()

@attr.s(on_setattr=attr.setters.validate)
@attr.s(on_setattr=attr.setters.validate, slots=slots)
class D(C):
y = attr.ib()

Expand All @@ -678,18 +666,17 @@ class D(C):
assert "self.y = y" in src
assert object.__setattr__ == D.__setattr__

@pytest.mark.parametrize("slots", [True, False])
def test_no_setattr_if_convert_without_converters(self, slots):
"""
If a class has on_setattr=attr.setters.convert but sets no validators,
don't use the (slower) setattr in __init__.
"""

@attr.s(on_setattr=attr.setters.convert)
@attr.s(on_setattr=attr.setters.convert, slots=slots)
class C:
x = attr.ib()

@attr.s(on_setattr=attr.setters.convert)
@attr.s(on_setattr=attr.setters.convert, slots=slots)
class D(C):
y = attr.ib()

Expand All @@ -700,15 +687,14 @@ class D(C):
assert "self.y = y" in src
assert object.__setattr__ == D.__setattr__

@pytest.mark.parametrize("slots", [True, False])
def test_no_setattr_with_ng_defaults(self, slots):
"""
If a class has the NG default on_setattr=[convert, validate] but sets
no validators or converters, don't use the (slower) setattr in
__init__.
"""

@attr.define
@attr.define(slots=slots)
class C:
x = attr.ib()

Expand All @@ -718,7 +704,7 @@ class C:
assert "self.x = x" in src
assert object.__setattr__ == C.__setattr__

@attr.define
@attr.define(slots=slots)
class D(C):
y = attr.ib()

Expand Down
3 changes: 0 additions & 3 deletions tests/test_init_subclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@
Tests for `__init_subclass__` related tests.
"""

import pytest

import attr


@pytest.mark.parametrize("slots", [True, False])
def test_init_subclass_vanilla(slots):
"""
`super().__init_subclass__` can be used if the subclass is not an attrs
Expand Down
Loading

0 comments on commit d824f72

Please sign in to comment.