Skip to content
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

Implement pyright support via dataclass_transforms #796

Merged
merged 19 commits into from
May 5, 2021

Conversation

asford
Copy link
Contributor

@asford asford commented Apr 27, 2021

Implements baseline support for pyright as described in #795.

Pull Request Check List

This is just a friendly reminder about the most common mistakes. Please make sure that you tick all boxes. But please read our contribution guide at least once, it will save you unnecessary review cycles!

If an item doesn't apply to your pull request, check it anyway to make it apparent that there's nothing left to do. If your pull request is a documentation fix or a trivial typo, feel free to delete the whole thing.

  • Clarify support for frozen either in upstream spec or attrs docs.
  • Added tests for changed code.
  • New features have been added to our Hypothesis testing strategy.
  • Changes or additions to public APIs are reflected in our type stubs (files ending in .pyi).
    • ...and used in the stub test file tests/typing_example.py.
  • Updated documentation for changed code.
    • New functions/classes have to be added to docs/api.rst by hand.
    • Changes to the signature of @attr.s() have to be added by hand too.
    • Changed/added classes/methods/functions have appropriate versionadded, versionchanged, or deprecated directives. Find the appropriate next version in our __init__.py file.
  • Documentation in .rst files is written using semantic newlines.
  • Changes (and possible deprecations) have news fragments in changelog.d.

If you have any questions to any of the points above, just submit and ask! This checklist is here to help you, not to deter you from contributing!

docs/types.rst Show resolved Hide resolved
@asford
Copy link
Contributor Author

asford commented Apr 27, 2021

Please view as a starting point, I don't have super strong ownership over this feature and would happily cede the conn if this should be fast-tracked for 21.1.

This feature can be implemented via annotations on custom decorator wrappers for projects that want to use pyright and attrs now, so I don't think there's an absolute need to implement this 21.1 if attrs would like to provide more feedback upstream.

There are a number of disparities with the existing mypy types including:

  • The attr.frozen decorator does not properly read-only properties, which is supported are supported via attr.define(frozen=True).
  • Converters are explicitly not implemented.

# Static type inference support via __dataclass_transform__ implemented as per:
# https://github.com/microsoft/pyright/blob/1.1.135/specs/dataclass_transforms.md
# This annotation must be applied to all overloads of "define" and "attrs"
def __dataclass_transform__(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should probably be an implementation of this somewhere right? Otherwise this won't be useful outside of attrs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a little strange, the decorator is just being used as a marker for pyright. The upstream spec specifies that the __dataclass_transform__ decorator is defined on a per-project basis either in the a .py or .pyi source file, essentially as a compatibility layer until this is moved into typing_extensions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I'm a little concerned (ok not too much but it definitely feels weird) about having a def in the pyi file that doesn't exist in the .py file. It means that someone could try to use this function in their own code but mypy wouldn't warn them that it doesn't really exist. It would fail at runtime. Are we sure we don't want to put an implementation in _funcs.py or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 I've done a little test of this. Under the 1.1.135 pyright implementation if we:

  • Define a __dataclass_transform__ decorator in __init__.pyi file.
  • Define a __dataclass_transform__ method in __funcs.py and import in __init__.py.

Then a custom annotation of the form has types inferred properly.

import attr

@attr.__dataclass_transform__(fields=(attr.field, attr.attrib))
def custom_define(cls):
   return attr.define(cls)

@custom_define
class Custom:
    a: int

However, given that this is a very early and provisional specification and implementation in pyright, adding a "surprisingly functional" shim in attrs introduces a potentially risky coupling. We've a choice between:

  1. An immediate and loud runtime failure if you "try to use" the attrs-internal __dataclass_transform__ .pyi definition, that won't be caught static type checkers.
  2. A surprisingly functional form that "works" (a no-op a runtime, currently works at typingtime), but relies on unspecified upstream behavior.

Given how bleeding-edge this feature is I have a weakly held preference that we opt for 2 for this release to ensure that folks don't implictly rely on the import attr.__dataclass_transform__ behavior and instead wait until the spec bakes enough for the dataclass_transform to show up in typing_extensions.

Of course, happy to reconsider.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is only for pyright and doesn't currently get mypy to do the right thing (i.e. it doesn't tell mypy that this is an attrs class creator) perhaps it's best if we leave this only inside the pyi file. I might add a comment on here that says that this method doesn't actually exist at runtime.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roger, comment added.

@hynek hynek requested a review from Tinche April 28, 2021 07:47
docs/types.rst Outdated Show resolved Hide resolved
@hynek
Copy link
Member

hynek commented Apr 28, 2021

Ah @erictraut it would be great if you could give it a casual glance!

If everything goes well, we'll be able to publish 21.1 within a week. 🤞

@erictraut
Copy link
Contributor

This looks good to me.

My only word of caution is that my spec is not yet final. It's still in the review stage, so I'm a little nervous about a major library adopting it at this phase. On the other hand, the worst that would happen if I need to change the spec is that pyright/pylance users would be no worse off than they are today with respect to type support for attrs. Sounds like a reasonable risk to take.

@asford asford requested a review from Tinche April 29, 2021 05:41
@asford
Copy link
Contributor Author

asford commented Apr 29, 2021

My only word of caution is that my spec is not yet final. It's still in the review stage, so I'm a little nervous about a major library adopting it at this phase. On the other hand, the worst that would happen if I need to change the spec is that pyright/pylance users would be no worse off than they are today with respect to type support for attrs. Sounds like a reasonable risk to take.

Fully agree with this. My operating assumption is that this is highly provisional and primarily intended to allow for rapid feedback on the upstream specification.

@hynek, would you be comfortable with additional minor releases in this release series if/when the upstream spec evolves?

@asford asford requested a review from euresti April 29, 2021 21:27
@asford
Copy link
Contributor Author

asford commented Apr 29, 2021

This is ready for a "near final" review.
I've added baseline coverage for the feature in an additional tox environment that includes pyright,
the current tox environment could be reconfigured to install node and pyright into the standard test env.

Copy link
Member

@hynek hynek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lint is failing 😇

also could you please move the pyright stuff to 3.9? 3.8 is overflowing already anyways :)

@hynek
Copy link
Member

hynek commented Apr 30, 2021

Oh and some markup problems too – check out https://attrs--796.org.readthedocs.build/en/796/types.html#pyright pls. I looks like unbalanced backticks

@asford asford requested a review from hynek April 30, 2021 15:22
@hynek hynek added this to the 21.1.0 milestone May 1, 2021
@hynek
Copy link
Member

hynek commented May 5, 2021

OK thank you very much for the quick turnaround everyone; I'm very excited by this!

I'm merging it but there's one thing that's a bit weird to me and that's the hard pin of pyright's version everywhere – down to the micro. I don't think this is necessary?

@hynek hynek merged commit 7e372c5 into python-attrs:main May 5, 2021
hynek added a commit that referenced this pull request May 5, 2021
hynek added a commit that referenced this pull request May 5, 2021
@hynek
Copy link
Member

hynek commented May 5, 2021

JFTR I've unpinned in f41db43 and added some docs polish. I didn't want to pester you with more review rounds, feel free to yell at me if you disagree with anything.

I hope to publish later this week – as far as I can tell, everything has landed.

@erictraut
Copy link
Contributor

The code and documentation look good to me.

@asford
Copy link
Contributor Author

asford commented May 5, 2021 via email

@hynek
Copy link
Member

hynek commented May 5, 2021

Thanks all!

Eric quick question since I don't have a good way to test this: does this mean that attrs 21.1 will also automagically work with pylance / VS Code? (Working on the release announcement)

@jakebailey
Copy link

Yes, Pylance has had this dataclass transform support since version 2021.4.2 (released a couple of weeks ago).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants