-
- Notifications
You must be signed in to change notification settings - Fork 408
Make kw_only=True behavior consistent with dataclasses #1457
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| Additionally, changes also need to be made for type-checkers / IDEs to support this new behavior, although I'm not too concerned because most of them aren't currently fully correct anyway. And also, the behavior we're changing to is the same as Here's an example to illustrate: The correct signatures, before this change, are: and after: Here's what So
Here's what PyCharm shows for autocompletion: This feels like the old Here's what VSCode shows for autocompletion: which interestingly is our desired after behavior. My guess is that the VSCode LSP has support |
kw_only=True apply only to attributes in the current class without an explicit kw_only=False settingkw_only=True behavior consistent with dataclasses | The reason why docs are failing is because you're using which is is short for Please use asterisks when referring to arguments. Sadly, there's no good way to have Markdown docstrings for now. |
hynek left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, this is an absolute amazing PR, including the supplied comments.
Any reason why it is a Draft? The changelog is gonna be important, but other than that it seems fine, I think?
As you write and as far as I can tell, the only important case we will be breaking here is:
@attrs.define(kw_only=True) class A: a: int b: int = attrs.field(default=1, kw_only=False) @attrs.define class B(A): c: intEverything else would only get weakened and keep working.
The use-case is very much up Hyrum's wheelhouse, but also an accidental thing that works that shouldn't and that can be fixed on both ends: either making A force_kw_only or adding kw_only to B, so there is an easy way forward.
I think the biggest question here is how fast @Tinche (presumably) will be able to get Mypy fixed. Pyright does support attrs btw, but also only via dataclass transform, so it makes sense to move our modern APIs there.
The biggest attrs users I know of are Airflow and Hypothesis so I guess it would make sense to ping @ashb and @Zac-HD whether they think this is something problematic.
| 👋 happy to chime in - as always, thanks @hynek for a great library, and thanks @huzecong for a great patch! Hypothesis turns out not to use
My main concern is actually that using non-kw-only arguments with inheritance sounds super confusing to me, but it also seems reasonable to make that users' problem. (I'd just make kw-only=True the default 😉) |
| Thanks for the reviews and suggestions! @hynek Your analysis of the breaking case is correct and I agree that the workaround is simple. I also agree that fixing I left the PR as a draft because I haven't had time to write new docs and the changelog etc. I'll find time for it later today or tomorrow, and then take it out of draft. Code should be ready! @Zac-HD Thanks for the pointer! I changed to regex to
Also I agree that mixing
|
Co-authored-by: Hynek Schlawack <hs@ox.cx>
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
| Turns out there's not much docs & changelog changes :) |
changelog.d/1457.change.md Outdated
| @@ -0,0 +1 @@ | |||
| Class-level `kw_only=True` behavior is now consistent with `dataclasses`. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to expand this a bit, and the file name must be changed from change to breaking, such that it jumps out at people.
The first line is OK, but we should add the one example that we know that's gonna break and how to work around it. I absolutely hate it, when a package says they broke something and won't tell me how to fix it. 😅
Maybe lead with something like "This shouldn't be a problem for most users, unless …"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a description of what exactly has changed and the breaking example above. Do you think this is too verbose, or is it good?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's a tiny bit verbose and doesn't do semantic new lines 🤓 but it's 100% fine – I'll merge and fumble around until I'm happy myself.
thanks for your endless patience! 💛
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha happy to contribute to the library! And thanks for the discussions and reviews. I'll read up on this semantic new line practice too 😁
for more information, see https://pre-commit.ci
| Yeah I'm with @Zac-HD here, and these changes as described fine for Airflow, and in the very very unlikely event it breaks we'll fix our code |
| JFTR, I've looked at another notable attrs user https://github.com/python-trio/trio and couldn't find any dangerous/relevant use of kw_only. The tests pass with current main too, so I hope they should be fine, too. aiohttp uses only old-school |
Summary
This PR resolve #980.
Before this change
When
kw_only=Trueis set on a class, all attributes are made keyword-only, including those from base classes. If an attribute setskw_only=False, that setting is ignored and it is still made keyword-only.After this change
When
kw_only=Trueis set on a class, only the attributes defined in that class that doesn't explicitly setkw_only=Falseare made keyword-only. Notably this is also the behavior fordataclasses.See
TestKeywordOnlyAttributes.{test_kw_only_inheritance,test_kw_only_inheritance_force_kw_only}for an example of the old and new behaviors.Implementation
The default for
kw_onlyinattr.ib/attrs.fieldis changed from False to None. When set to None, the attribute'skw_onlymirrors the value set on the class.An additional back-compat
force_kw_onlyargument is added toattr.s/attrs.define. When set to True, the old behavior is restored for the class. The default is False (new behavior) forattrs.defineand True (old behavior) forattr.s.Backwards compatibility
This change makes certain attributes that were previously kw-only no longer kw-only. As such, all call sites where we construct an instance of an attrs class should be backwards compatible.
If no attribute in an attrs class and all of its base classes explicitly sets
kw_only=False, then that class is also backwards compatible.If there are
kw_only=Falseattributes, then it is possible for the class to fail on import due to attribute ordering. For example:Previously,
B.__init__would have the signature(self, c, *, a, b=...), but with the new behavior this is wrong becausecfollowsb, a non-kw-only attribute with a default.My hope is that this is rare enough --- since
kw_only=Falsewas the default onattr.ib/attrs.fieldand doesn't actually do anything when the class setskw_only=True, very few users should've set it. A quick scan in my work code base, which extensively usesattrswith thousands of attrs-decorated classes, shows only one usage of field-levelkw_only=False.Pull Request Check List
mainbranch – use a separate branch!Our CI fails if coverage is not 100%.
.pyi).tests/typing_example.py.attr/__init__.pyi, they've also been re-imported inattrs/__init__.pyi.docs/api.rstby hand.@attr.s()and@attrs.define()have to be added by hand too.versionadded,versionchanged, ordeprecateddirectives.The next version is the second number in the current release + 1.
The first number represents the current year.
So if the current version on PyPI is 22.2.0, the next version is gonna be 22.3.0.
If the next version is the first in the new year, it'll be 23.1.0.
attrs.define()andattr.s(), you have to add version directives to both..rstand.mdfiles is written using semantic newlines.changelog.d.