Highlights
The main reason for this release (and why it's published today) is that it ships the first pieces of work for Python 3.14 and PEP 749. There will be more work required and there's going to be a lot more churn once everyone starts testing 3.14 earnestly. We hope to receive more feedback before spending more time on this.
Full changelog below!
Special Thanks
This release would not be possible without my generous sponsors! Thank you to all of you making sustainable maintenance possible! If you would like to join them, go to https://github.com/sponsors/hynek and check out the sweet perks!
Above and Beyond
Variomedia AG (@variomedia), Tidelift (@tidelift), Privacy Solutions GmbH (@privacy-solutions), Quesma (@QuesmaOrg), FilePreviews (@filepreviews), Doist (@Doist), Daniel Fortunov (@asqui), and Kevin P. Fleming (@kpfleming).
Maintenance Sustainers
Buttondown (@buttondown), Christopher Dignam (@chdsbd), Magnus Watn (@magnuswatn), David Cramer (@dcramer), Jesse Snyder (@jessesnyder), Rivo Laks (@rivol), Polar (@polarsource), Mike Fiedler (@miketheman), Duncan Hill (@cricalix), Colin Marquardt (@cmarqu), Pieter Swinkels (@swinkels), Nick Libertini (@libertininick), Brian M. Dennis (@crossjam), Celebrity News AG (@celebritynewsag), The Westervelt Company (@westerveltco), Sławomir Ehlert (@slafs), Mostafa Khalil (@khadrawy), Filip Mularczyk (@mukiblejlok), Thomas Klinger (@thmsklngr), Andreas Poehlmann (@ap--), August Trapper Bigelow (@atbigelow), Carlton Gibson (@carltongibson), and Roboflow (@roboflow).
Full Changelog
Backwards-incompatible Changes
-
Class-level
kw_only=True
behavior is now consistent withdataclasses
.Previously, a class that sets
kw_only=True
makes all attributes keyword-only, including those from base classes. If an attribute setskw_only=False
, that setting is ignored, and it is still made keyword-only.Now, only the attributes defined in that class that doesn't explicitly set
kw_only=False
are made keyword-only.This shouldn't be a problem for most users, unless you have a pattern like this:
@attrs.define(kw_only=True) class Base: a: int b: int = attrs.field(default=1, kw_only=False) @attrs.define class Subclass(Base): c: int
Here, we have a
kw_only=True
attrs class (Base
) with an attribute that setskw_only=False
and has a default (Base.b
), and then create a subclass (Subclass
) with required arguments (Subclass.c
). Previously this would work, since it would makeBase.b
keyword-only, but now this fails sinceBase.b
is positional, and we have a required positional argument (Subclass.c
) following another argument with defaults. #1457
Changes
-
Values passed to the
__init__()
method ofattrs
classes are now correctly passed to__attrs_pre_init__()
instead of their default values (in cases where kw_only was not specified). #1427 -
attrs.validators.deep_mapping()
now allows to leave out either key_validator xor value_validator. #1448 -
attrs.validators.deep_iterator()
andattrs.validators.deep_mapping()
now accept lists and tuples for all validators and wrap them into aattrs.validators.and_()
. #1449 -
Added a new experimental way to inspect classes:
attrs.inspect(cls)
returns the effective class-wide parameters that were used by attrs to construct the class.The returned class is the same data structure that attrs uses internally to decide how to construct the final class. #1454
-
Fixed annotations for
attrs.field(converter=...)
. Previously, atuple
of converters was only accepted if it had exactly one element. #1461 -
The performance of
attrs.asdict()
has been improved by 45–260%. #1463 -
The performance of
attrs.astuple()
has been improved by 49–270%. #1469 -
The type annotation for
attrs.validators.or_()
now allows for different types of validators.This was only an issue on Pyright. #1474
This release contains contributions from @A5rocks, @carltongibson, @eendebakpt, @finite-state-machine, @huzecong, @hynek, @JelleZijlstra, @ProfDoof, @redruin1, @Tinche, and @zed.
Artifact Attestations
You can verify this release's artifact attestions using GitHub's CLI tool by downloading the sdist and wheel from PyPI and running:
$ gh attestation verify --owner python-attrs attrs-25.4.0.tar.gz
and
$ gh attestation verify --owner python-attrs attrs-25.4.0-py3-none-any.whl