Skip to content

Commit

Permalink
Clarify next-gen auto_attribs inference rules (#742)
Browse files Browse the repository at this point in the history
* Clarify next-gen auto_attribs inference rules

The next-gen auto_attribs api documentation does not clearly describe
cases where (a) only a subset of attributes are annotated and (b)
`field` definitions are provided for a subset of fields. Update
docstring to reflect current, desirable, behavior.

Add tests to clarify `.define` behavior focused on fully-annotated
classes with partially-defined fields, which is commonly used to add
non-default behavior to a subset of a classes fields. For example:

```python

@attr.define
class NewSchool:
    x: int
    y: list = attr.field()

    @y.validator
    def _validate_y(self, attribute, value):
        if value < 0:
            raise ValueError("y must be positive")

```

The previous docstring *could* be read to imply that:

* The new-school API will not infer auto_attribs if there are any
  unannotated attributes.
* The new-school API will not infer auto_attribs if *any* attr.ib are
  defined, even if those attr.ibs are type annotated.

* Update test to match PR example

* Fix lint error

Co-authored-by: Hynek Schlawack <hs@ox.cx>
  • Loading branch information
asford and hynek committed Dec 30, 2020
1 parent 345fb0b commit 2fdf929
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 2 deletions.
4 changes: 2 additions & 2 deletions src/attr/_next_gen.py
Expand Up @@ -42,8 +42,8 @@ def define(
:param Optional[bool] auto_attribs: If set to `True` or `False`, it behaves
exactly like `attr.s`. If left `None`, `attr.s` will try to guess:
1. If all attributes are annotated and no `attr.ib` is found, it assumes
*auto_attribs=True*.
1. If any attributes are annotated and no unannotated `attr.ib`\ s
are found, it assumes *auto_attribs=True*.
2. Otherwise it assumes *auto_attribs=False* and tries to collect
`attr.ib`\ s.
Expand Down
39 changes: 39 additions & 0 deletions tests/test_next_gen.py
Expand Up @@ -112,6 +112,45 @@ class OldSchool2:

assert OldSchool2(1) == OldSchool2(1)

def test_auto_attribs_detect_fields_and_annotations(self):
"""
define infers auto_attribs=True if fields have type annotations
"""

@attr.define
class NewSchool:
x: int
y: list = attr.field()

@y.validator
def _validate_y(self, attribute, value):
if value < 0:
raise ValueError("y must be positive")

assert NewSchool(1, 1) == NewSchool(1, 1)
with pytest.raises(ValueError):
NewSchool(1, -1)
assert list(attr.fields_dict(NewSchool).keys()) == ["x", "y"]

def test_auto_attribs_partially_annotated(self):
"""
define infers auto_attribs=True if any type annotations are found
"""

@attr.define
class NewSchool:
x: int
y: list
z = 10

# fields are defined for any annotated attributes
assert NewSchool(1, []) == NewSchool(1, [])
assert list(attr.fields_dict(NewSchool).keys()) == ["x", "y"]

# while the unannotated attributes are left as class vars
assert NewSchool.z == 10
assert "z" in NewSchool.__dict__

def test_auto_attribs_detect_annotations(self):
"""
define correctly detects if a class has type annotations.
Expand Down

0 comments on commit 2fdf929

Please sign in to comment.