Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,16 @@ jobs:
- name: "Run tox targets for ${{ matrix.python-version }}"
run: "python -m tox"

# We always use a modern Python version for combining coverage to prevent
# parsing errors in older versions for modern code.
- uses: "actions/setup-python@v2"
with:
python-version: "3.8"

- name: "Combine coverage"
run: |
set -xe
python -m pip install coverage[toml]
python -m coverage combine
python -m coverage xml
if: "contains(env.USING_COVERAGE, matrix.python-version)"
Expand Down
3 changes: 3 additions & 0 deletions changelog.d/408.deprecation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ If this is a problem for you for some reason, please report it to our bug tracke

The old ``attr`` namespace isn't going anywhere and its defaults are not changing – this is a purely additive measure.
Please check out the linked issue for more details.

These new APIs have been added *provisionally* as part of `#666 <https://github.com/python-attrs/attrs/pull/666>`_ so you can try them out today and provide feedback.
Learn more in the `API docs <https://www.attrs.org/en/stable/api.html#provisional-apis>`_.
8 changes: 8 additions & 0 deletions changelog.d/666.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
**Provisional** APIs called ``attr.define()``, ``attr.mutable()``, and ``attr.frozen()`` have been added.

They are only available on Python 3.6 and later, and call ``attr.s()`` with different default values.

If nothing comes up, they will become the official way for creating classes in 20.2.0 (see above).

**Please note** that it may take some time until mypy – and other tools that have dedicated support for ``attrs`` – recognize these new APIs.
Please **do not** open issues on our bug tracker, there is nothing we can do about it.
6 changes: 5 additions & 1 deletion conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@ def pytest_configure(config):
collect_ignore = []
if sys.version_info[:2] < (3, 6):
collect_ignore.extend(
["tests/test_annotations.py", "tests/test_init_subclass.py"]
[
"tests/test_annotations.py",
"tests/test_init_subclass.py",
"tests/test_next_gen.py",
]
)
47 changes: 47 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ What follows is the API explanation, if you'd like a more hands-on introduction,
Core
----


.. warning::
As of ``attrs`` 20.1.0, it also ships with a bunch of provisional APIs that are intended to become the main way of defining classes in the future.

Please have a look at :ref:`prov`.

.. autodata:: attr.NOTHING

.. autofunction:: attr.s(these=None, repr_ns=None, repr=None, cmp=None, hash=None, init=None, slots=False, frozen=False, weakref_slot=True, str=False, auto_attribs=False, kw_only=False, cache_hash=False, auto_exc=False, eq=None, order=None, auto_detect=False, collect_by_mro=False, getstate_setstate=None, on_setattr=None)
Expand Down Expand Up @@ -580,6 +586,47 @@ These are helpers that you can use together with `attr.s`'s and `attr.ib`'s ``on
N.B. Please use `attr.s`'s *frozen* argument to freeze whole classes; it is more efficient.


.. _prov:

Provisional APIs
----------------

These are Python 3.6 and later-only, keyword-only, and **provisional** APIs that call `attr.s` with different default values.

The most notable differences are:

- automatically detect whether or not *auto_attribs* should be `True`
- *slots=True* (see :term:`slotted classes`)
- *auto_exc=True*
- *auto_detect=True*
- *eq=True*, but *order=False*
- Validators run when you set an attribute (*on_setattr=attr.setters.validate*).
- Some options that aren't relevant to Python 3 have been dropped.

Please note that these are *defaults* and you're free to override them, just like before.

----

Their behavior is scheduled to become part of the upcoming ``import attrs`` that will introduce a new namespace with nicer names and nicer defaults (see `#408 <https://github.com/python-attrs/attrs/issues/408>`_ and `#487 <https://github.com/python-attrs/attrs/issues/487>`_).

Therefore your constructive feedback in the linked issues above is strongly encouraged!

.. note::
Provisional doesn't mean we will remove it (although it will be deprecated once the final form is released), but that it might change if we receive relevant feedback.

`attr.s` and `attr.ib` (and their serious business cousins) aren't going anywhere.
The new APIs build on top of them.

.. autofunction:: attr.define
.. function:: attr.mutable(same_as_define)

Alias for `attr.define`.

.. function:: attr.frozen(same_as_define)

Behaves the same as `attr.define` but sets *frozen=True* and *on_setattr=None*.


Deprecated APIs
---------------

Expand Down
8 changes: 7 additions & 1 deletion src/attr/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import absolute_import, division, print_function

import sys

from functools import partial

from . import converters, exceptions, filters, setters, validators
Expand Down Expand Up @@ -39,7 +41,6 @@
ib = attr = attrib
dataclass = partial(attrs, auto_attribs=True) # happy Easter ;)


__all__ = [
"Attribute",
"Factory",
Expand Down Expand Up @@ -68,3 +69,8 @@
"validate",
"validators",
]

if sys.version_info[:2] >= (3, 6):
from ._next_gen import define, frozen, mutable

__all__.extend((define, frozen, mutable))
47 changes: 47 additions & 0 deletions src/attr/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,53 @@ def attrs(
getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
) -> Callable[[_C], _C]: ...
@overload
def define(
maybe_cls: _C,
*,
these: Optional[Dict[str, Any]] = ...,
repr: bool = ...,
hash: Optional[bool] = ...,
init: bool = ...,
slots: bool = ...,
frozen: bool = ...,
weakref_slot: bool = ...,
str: bool = ...,
auto_attribs: bool = ...,
kw_only: bool = ...,
cache_hash: bool = ...,
auto_exc: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
auto_detect: bool = ...,
getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
) -> _C: ...
@overload
def define(
maybe_cls: None = ...,
*,
these: Optional[Dict[str, Any]] = ...,
repr: bool = ...,
hash: Optional[bool] = ...,
init: bool = ...,
slots: bool = ...,
frozen: bool = ...,
weakref_slot: bool = ...,
str: bool = ...,
auto_attribs: bool = ...,
kw_only: bool = ...,
cache_hash: bool = ...,
auto_exc: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
auto_detect: bool = ...,
getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
) -> Callable[[_C], _C]: ...

mutable = define
frozen = define # they differ only in their defaults

# TODO: add support for returning NamedTuple from the mypy plugin
class _Fields(Tuple[Attribute[Any], ...]):
Expand Down
86 changes: 86 additions & 0 deletions src/attr/_next_gen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""
This is a Python 3.6 and later-only, keyword-only, and **provisional** API that
calls `attr.s` with different default values.

Provisional APIs that shall become "import attrs" one glorious day.
"""

from functools import partial

from attr.exceptions import UnannotatedAttributeError

from . import setters
from ._make import attrs


def define(
maybe_cls=None,
*,
these=None,
repr=None,
hash=None,
init=None,
slots=True,
frozen=False,
weakref_slot=True,
str=False,
auto_attribs=None,
kw_only=False,
cache_hash=False,
auto_exc=True,
eq=True,
order=False,
auto_detect=True,
getstate_setstate=None,
on_setattr=setters.validate
):
r"""
The only behavioral difference is the handling of the *auto_attribs*
option:

: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*.
2. Otherwise it assumes *auto_attribs=False* and tries to collect
`attr.ib`\ s.


.. versionadded:: 20.1.0
"""

def do_it(auto_attribs):
return attrs(
maybe_cls=maybe_cls,
these=these,
repr=repr,
hash=hash,
init=init,
slots=slots,
frozen=frozen,
weakref_slot=weakref_slot,
str=str,
auto_attribs=auto_attribs,
kw_only=kw_only,
cache_hash=cache_hash,
auto_exc=auto_exc,
eq=eq,
order=order,
auto_detect=auto_detect,
collect_by_mro=True,
getstate_setstate=getstate_setstate,
on_setattr=on_setattr,
)

if auto_attribs is not None:
return do_it(auto_attribs)

try:
return do_it(True)
except UnannotatedAttributeError:
return do_it(False)


mutable = define
frozen = partial(define, frozen=True, on_setattr=None)
Loading