Skip to content

Instantly share code, notes, and snippets.

@hynek
Last active September 18, 2020 22:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hynek/1e3844d0c99e479e716169034b5fa963 to your computer and use it in GitHub Desktop.
Save hynek/1e3844d0c99e479e716169034b5fa963 to your computer and use it in GitHub Desktop.

attrs 20.1.0 released!

I’m thrilled to finally present attrs 20.1.0 with the following changes:

  • bug fixes
  • performance improvements

JUST KIDDING!

This release is HUGE and I'm stoked we can finally get it to you! It’s the result of more than a year of development and not only does it come with many great features that users desired for a long time, it also lays the foundation for the future evolution of this package.


The final push has only been possible thanks to attrs’s Tidelift subscribers and my generous GitHub Sponsors. If you want to speed up the development for the forthcoming 20.2.0 release (You do! There’s a lot of great stuff coming up!), please consider supporting my work by either sponsoring me, or convincing your boss to subscribe to my projects on Tidelift.

Hooks on Setting Attributes

You could always use validators for when a class is instantiated and you could always make classes immutable.

Now you can also freeze specific attributes and validate attribute values on assignment using the new on_setattr argument.

It takes a callable and runs it whenever the user tries to assign a new value to the attribute.

See #660.

Automatic Detection of Own Methods

If you want to write your own dunder method – say __init__ – you have to remember to tell attrs to not generate an own version (e.g. @attr.s(init=False)) otherwise it will happily overwrite it.

Not anymore! If you pass @attr.s(auto_detect=True), attrs will look for your own methods automatically – but only in the current class. In other words: inherited methods don’t count. It’s still a bit of hassle to set it to True, but that will be remedied in the future (see below).

See #607.

Attribute Collection and the MRO

attrs’s collection of attributes was slightly wrong in very specific situations involving multiple inheritance (editor’s note: don’t use multiple inheritance). Most people won't notice, but some do we like to do things the right way.

See #635.

The Road Ahead

attrs started out in February 2015. The Python landscape was very different back then and lots of things changed since. We also got some things subtly wrong that need to be fixed by passing arguments. And finally the cute attr.s and attr.ib function names didn't age as attrs gained more and more features over the years.

Therefore, we intend to add a new attrs namespace in 20.2.0 later this year, allowing you to import attrs.

But there’s more than an extra “s” planned. We want to free people from having to pass all kinds of arguments to get the “good behavior” of attrs and therefore we want to introduce new APIs that have better defaults. Thanks to the new namespace, we don’t have to break backward compatibility and the old APIs aren’t going anywhere: in fact, the new ones build on them.


To get the new APIs just right, we’ve added them to attrs 20.1.0 provisionally for you to test and give us with feedback.

The APIs are attr.define() which is supposed to become the default way to decorate classes in the future, attr.frozen() that’s the same thing as attr.define(frozen=True) and finally attr.mutable() that is an alias for define() and was wished for by immutability fans.

There’s also a new alias for attr.ib() called attr.field() since that seems to be the nomenclature the Python community has agreed on. It carries no changes except that it’s keyword-only.

To give you a taste, this is an example for a class defined using the new APIs:

import attr

@attr.define
class C:
    x: int
    y: str = attr.field(default="foo")

    def __repr__(self) -> str:
        return "attrs will not overwrite this method."

If everything goes according to plan, in 20.2.0 you’ll be able to substitute attr with attrs.

N.B.:

  • All these APIs require at least Python 3.6. While attrs will be Python 2-compatible as long as we can, the new APIs are not.
  • mypy's attrs plugin doesn't know about these APIs yet and has to be updated first! Please check out the next file on how to work around this limitation for now!

Check out the API documentation for provisional APIs and please use issue #668 for feedback! Relevant discussions happened in #408, #487, and finally #666 (yep).

Full Changelog

The full (looong) changelog with many more features and fixes can be found at https://www.attrs.org/en/stable/changelog.html.

"""
How to use the new-style APIs until mypy adds support for it:
1. Create a mypy.ini in the root of your project with the following contents:
```
[mypy]
plugins=attrs_ng_plugin.py
```
If you have one, just add the `plugins=` line.
2. Add this file next to the mypy.ini.
3. There is no step 3!
N.B. mypy will treat your classes as `@attr.s(auto_attribs=True)` which means
you MUST use type annotations. This is not how `@attr.define()` et al behave,
they are hybrid.
If you use attrs without type annotions (IOW `attr.ib()`s all the way), you
have to use `attr_class_makers` instead of `attr_dataclass_makers`.
See https://www.attrs.org/en/stable/extending.html#wrapping-the-decorator for
more details.
"""
from mypy.plugin import Plugin
from mypy.plugins.attrs import attr_attrib_makers, attr_dataclass_makers
attr_dataclass_makers.add("attr.define")
attr_dataclass_makers.add("attr.mutable")
attr_dataclass_makers.add("attr.frozen")
attr_attrib_makers.add("attr.field")
class AttrsNGPlugin(Plugin):
pass
def plugin(version):
return AttrsNGPlugin
@euresti
Copy link

euresti commented Aug 21, 2020

you MUST use type annotations.

I just tried this and it failed:

>>> @attr.define()
... class A:
...    x = attr.ib()
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/Users/david/src/pilot/zapgram/.ve/lib/python3.8/site-packages/attr/_make.py", line 1247, in wrap
    builder = _ClassBuilder(
  File "/Users/david/src/pilot/zapgram/.ve/lib/python3.8/site-packages/attr/_make.py", line 577, in __init__
    attrs, base_attrs, base_map = _transform_attrs(
  File "/Users/david/src/pilot/zapgram/.ve/lib/python3.8/site-packages/attr/_make.py", line 463, in _transform_attrs
    raise UnannotatedAttributeError(
attr.exceptions.UnannotatedAttributeError: The following `attr.ib`s lack a type annotation: x.

@hynek
Copy link
Author

hynek commented Aug 21, 2020

@euresti Yeah, that’s what I wrote? 😅 If you need both, you can make mutable behave like attr.s I guess?

@euresti
Copy link

euresti commented Aug 21, 2020

I'm not sure. You said

This is not how @attr.define() et al behave, they are hybrid.

Which makes me think that the above code should work at runtime, but it doesn't.

>>> import attr
>>> attr.__version__
'20.1.0'

Oh it should work according to the code. Weird.

Oh this is a bug. I'll file it.

@hynek
Copy link
Author

hynek commented Aug 21, 2020

Ah sorry I misunderstood. Yeah I figured it was too good to be true. 😅

Edit: fixed in python-attrs/attrs#675

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment