You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This has many advantages, like eq/hash behavior working, reprs working, easy to add runtime validators, &c. However, it's not obvious to me how I can typecheck creating instances of this, because there's no __init__. (I'm using Python 2.7, so comment syntax, if it matters :))
A bit more detail, as the problem here isn't just that you can't validate attrs class initializer usage using mypy (and, in fact, you can validate types at runtime using attrs), but also that mypy is emitting false positives, which makes mypy harder to use if you use attrs.
Which runs thusly (note the runtime type validation, if that's a thing you like):
wsanchez$ python thing.pything1 exists: Truething2 exists: TrueTraceback (most recent call last): File "thing.py", line 21, in <module> thing3 = ThingWithPath(".") File "<attrs generated init 72ca13b0f10a00b12fb26accfd84d196fc2af0c6>", line 5, in __init__ File "/Users/wsanchez/Dropbox/Developer/PIE/wsanchez/ConfigService/.tox/py35-coverage/lib/python3.5/site-packages/attr/validators.py", line 24, in __call__ attr, self.type, value,TypeError: ("'_path' must be <class 'pathlib.Path'> (got '.' that is a <class 'str'>).", Attribute(name='_path', default=NOTHING, validator=<instance_of validator for type <class 'pathlib.Path'>>, repr=True, cmp=True, hash=True, init=True, convert=None, metadata=mappingproxy({})), <class 'pathlib.Path'>, '.')
Running mypy on this script emits:
thing.py:15: error: Too many arguments for "ThingWithPath"thing.py:18: error: Unexpected keyword argument "path" for "ThingWithPath"thing.py:21: error: Too many arguments for "ThingWithPath"
It's unfortunate, but not unique to attrs -- this is a general problem when you have a metaclass or a class decorator that affects the API of a class. I suppose we could teach mypy to special-case attrs, but that just moves the problem to every other library engaging in such tricks.
A more principled approach would be to add some new magic primitive to PEP 484 (and support in mypy of course) that would allow one to write a stub for attrs that explains to mypy what the effect of the class decorator is. This might be easier now than when this issue was created given that we already have some support for metaclasses now (#2475), so some of the necessary scaffolding in mypy already exists.
You could also imagine that mypy could just parse the full source code for the attrs package and understand what it does without needing any help. But I think that's unlikely to be successful, due to the difficulty for mypy to understand the dynamic tricks such an implementation undoubtedly uses. (I haven't looked.)
In general, supporting code that does runtime manipulation of interfaces is not high on the list of things that mypy and PEP 484 are likely to support soon. It's generally a bit antithetic to the idea of static types, after all. :-)
Yup, I was just trying to be more specific about the issue here.
I'm not sure I agree that attrs (or at least its functionality) is "antithetic to the idea of static types". I understand why it creates challenges here, but the degree to which it does dynamic things is limited to one time when the class is created, not some random manipulations as the program is running.
That said, I'm fairly new to mypy and the typing system.
I agree that being able to write a stub would be useful absent more robust but considerably harder to implement solutions.
I'm not sure I agree that attrs (or at least its functionality) is "antithetic to the idea of static types".
I agree with this assessment. attrs itself is not antithetical, though it's current implementation obviously is. It's really about supporting better use of types, by making them simpler to declare and be useful. I'm sure that if there were a way to support type systems at the same time, that would definitely fall in line with the goals of attrs.
Whether the real solution is pluggability for the type system to allow for this kind of compile-time transformations, or whether the only real solution is to have something that fulfills the goals of attrs built into the language, I'm not sure, but they are two approaches that are both trying to encourage the proper use of types. The dynamic implementation is just that, an implementation detail.
Well, mypy doesn't execute the program. It just reads the source code. So in order to validate the ThingWithPath() calls it would have to know the signature of the __init__() method that is added by the @attrs decorator, which in turn depends on going over the class attributes looking for expressions of a whatever type is returned by attrib(). Figuring that out presumably requires understanding the attrs package thoroughly, which is not possible given the state of the art and mypy's limited understanding of source code.
If it helps anyone, we solved this problem recently (specifically with attrs) where we define the init ourselves for attrs. Maybe it could help someone else too! Reusing the example from above:
There's discussion on an API in attrs (here: python-attrs/attrs#151) that would help bridging this. I'm following along since I like both typing and attrs for removed (and always correct!) boilerplate.
@fxfitz: As @ambv notes, the problem there is that your solution removes one of the big benefits of attrs. And presumably that prevents attrs from doing the validation it would have done in the generated __init__, no?
I suppose that if attrs nuked and replaced your __init__, that may work, and then you'd instead write:
@attrs()classThingWithPath(object):
_path=attrib(validator=instance_of(Path))
def__init__(self, _path: Path): # Note added type annotation herepass# This implementation isn't used, so passdefexists(self):
returnself._path.exists()
It would be confusing for someone who may put code into __init__ not knowing it's ignored… so it's not 100% great, but maybe better.
@wsanchez Ah right, I forget that if you init=False you would lose your validation; my bad! We used a similar approach to what I suggested earlier, but our goals were to (1) get mypy working and (2) still use attrs for the working equality/hash checks.
I think it's fair to say mypy will have a really hard time ever working out of the box with attrs since we do some really filthy things under the hood.
I also think making attrs work with mypy would be a very worthwhile thing to try to do, since the point of attrs is to make people use structured data more and this is very complimentary to the idea of static typing checks.
I'd be interested in maybe being involved with the effort to develop a plugin API, if that's the direction we're going in, and ultimately developing a plugin for attrs.
Reacted by Leynos, Fran Fitzpatrick, Wilfredo Sánchez Vega, Hynek Schlawack, Łukasz Langa, Steven Seguin, Max, Persephone, Bryan Culbertson, Joar Wandborg and 1 more
Great plan! I wonder if you could approach this from the other direction: first write a mypy patch that supports attrs, hard-coding recognition of the special constructs it supports; and then, learning from that experience, figure out how a plugin API should be designed.
Reacted by Hynek Schlawack, Łukasz Langa, Steven Seguin and Sebastian Hillig
Concerning the plugins, I already mentioned this, I would prefer not to allow plugins to replace TypeInfos (because of problems with forward references), but only allow to modify TypeInfos in-place (for example update names symbol table, or similar).
You mentioned that we should only be modifying TypeInfo but none of the plugins take TypeInfos they all take Type or Expression.
Yes. You will need to add a new plugin kind. If you have time, I think it makes sense (to be future proof) to ensure that plugins can be triggered not only by a specific decorator full name, but also by a specific metaclass or base class full name.
Oh wait. Just re-read the thread:
Yes, this looks like a reasonable API for the plugin. The plugin should modify TypeInfo in place and return None. I think however it is better to pass the whole ClassDef AST node (again, to be future proof) then the decorator expressions can be easily extracted by the plugin when necessary.
import attr
import typing
@attr.s(auto_attribs=True)
class Auto:
normal: int
_private: int
def_arg: int = 18
_def_kwarg: int = attr.ib(validator=None, default=18)
FOO: typing.ClassVar[int] = 18
reveal_type(Auto)
Auto(1, 2, 3)
@attr.s
class Typed:
normal: int = attr.ib()
_private: int = attr.ib()
def_arg: int = attr.ib(18)
_def_kwarg: int = attr.ib(validator=None, default=18)
FOO: int = 18
reveal_type(Typed)
Typed(1, 2, 3)
@attr.s
class UnTyped:
normal = attr.ib()
_private = attr.ib()
def_arg = attr.ib(18)
_def_kwarg = attr.ib(validator=None, default=18)
FOO = 18
reveal_type(UnTyped)
UnTyped(1, 2, 3)
Which now gives the following output from mypy:
foo.py:14: error: Revealed type is 'def (normal: builtins.int, private: builtins.int, def_arg: builtins.int =, def_kwarg: builtins.int =) -> foo.Auto'
foo.py:27: error: Revealed type is 'def (normal: builtins.int, private: builtins.int, def_arg: builtins.int =, def_kwarg: builtins.int =) -> foo.Typed'
foo.py:33: error: Need type annotation for variable
foo.py:34: error: Need type annotation for variable
foo.py:35: error: Need type annotation for variable
foo.py:36: error: Need type annotation for variable
foo.py:40: error: Revealed type is 'def (normal: Any, private: Any, def_arg: Any =, def_kwarg: Any =) -> foo.UnTyped'
But I'm not sure if this code belongs in mypy or in attrs or maybe in a completely different project altogether? Also the pyi file has to go somewhere but I'm not sure if it goes in the typeshed or somewhere else. Please advise.
Also I imagine dataclasses will look pretty similar to the auto_attrib case.
Now might be a good time to consider where this code might actually live. Does mypy support this plugin living outside and somehow picking it up after it being pip installed? We will want to have this plugin versioned according to which attrs version it can support.
Great work David, I'm super excited that someone found the time to pull this thru!
I think part of the question where to put it also kind of depends on how the plugin APIs will evolve? It would be unfortunate if certain versions of attrs only worked with certain versions of mypy.
So I think it might make sense to to put it either into mypy or start a new package in the python-attrs orga? attrs proper seems like the worst possible place at this point.
@euresti Stub files should go to typeshed. After PEP 561 is accepted and implemented, you can host the stub wherever you want and distribute them via PyPI/pip install.
As for the plugin it is less clear. Currently, mypy doesn't support plugins installed via pip install, but I think that the long term goal is to support this. Therefore, there are three options:
First add the attrs plugin for mypy as a PR to mypy, then implement the installable plugin infrastructure.
First implement the full infrastructure, then publish the attrs plugin on PyPI.
Work on two projects in parallel.
I think option 1 is the fastest way to get the attrs support. I am leaning towards this option, but I don't have a strong preference.
Activity
gvanrossum commentedon Sep 2, 2016
Hi Laurens, this looks like something that should be doable with the (as yet hypothetical) plugin architecture: #1240
wsanchez commentedon Mar 8, 2017
A bit more detail, as the problem here isn't just that you can't validate
attrsclass initializer usage usingmypy(and, in fact, you can validate types at runtime usingattrs), but also thatmypyis emitting false positives, which makesmypyharder to use if you useattrs.Here's a script
thing.py:Which runs thusly (note the runtime type validation, if that's a thing you like):
Running
mypyon this script emits:And
mypyof course exists with a non-zero status.gvanrossum commentedon Mar 8, 2017
It's unfortunate, but not unique to attrs -- this is a general problem when you have a metaclass or a class decorator that affects the API of a class. I suppose we could teach mypy to special-case attrs, but that just moves the problem to every other library engaging in such tricks.
A more principled approach would be to add some new magic primitive to PEP 484 (and support in mypy of course) that would allow one to write a stub for attrs that explains to mypy what the effect of the class decorator is. This might be easier now than when this issue was created given that we already have some support for metaclasses now (#2475), so some of the necessary scaffolding in mypy already exists.
You could also imagine that mypy could just parse the full source code for the attrs package and understand what it does without needing any help. But I think that's unlikely to be successful, due to the difficulty for mypy to understand the dynamic tricks such an implementation undoubtedly uses. (I haven't looked.)
In general, supporting code that does runtime manipulation of interfaces is not high on the list of things that mypy and PEP 484 are likely to support soon. It's generally a bit antithetic to the idea of static types, after all. :-)
wsanchez commentedon Mar 8, 2017
Yup, I was just trying to be more specific about the issue here.
I'm not sure I agree that
attrs(or at least its functionality) is "antithetic to the idea of static types". I understand why it creates challenges here, but the degree to which it does dynamic things is limited to one time when the class is created, not some random manipulations as the program is running.That said, I'm fairly new to mypy and the typing system.
I agree that being able to write a stub would be useful absent more robust but considerably harder to implement solutions.
ryanhiebert commentedon Mar 8, 2017
I agree with this assessment. attrs itself is not antithetical, though it's current implementation obviously is. It's really about supporting better use of types, by making them simpler to declare and be useful. I'm sure that if there were a way to support type systems at the same time, that would definitely fall in line with the goals of attrs.
Whether the real solution is pluggability for the type system to allow for this kind of compile-time transformations, or whether the only real solution is to have something that fulfills the goals of attrs built into the language, I'm not sure, but they are two approaches that are both trying to encourage the proper use of types. The dynamic implementation is just that, an implementation detail.
gvanrossum commentedon Mar 8, 2017
Well, mypy doesn't execute the program. It just reads the source code. So in order to validate the
ThingWithPath()calls it would have to know the signature of the__init__()method that is added by the@attrsdecorator, which in turn depends on going over the class attributes looking for expressions of a whatever type is returned byattrib(). Figuring that out presumably requires understanding the attrs package thoroughly, which is not possible given the state of the art and mypy's limited understanding of source code.fxfitz commentedon Mar 10, 2017
If it helps anyone, we solved this problem recently (specifically with attrs) where we define the init ourselves for attrs. Maybe it could help someone else too! Reusing the example from above:
ambv commentedon Mar 10, 2017
There's discussion on an API in attrs (here: python-attrs/attrs#151) that would help bridging this. I'm following along since I like both typing and attrs for removed (and always correct!) boilerplate.
wsanchez commentedon Mar 10, 2017
@fxfitz: As @ambv notes, the problem there is that your solution removes one of the big benefits of
attrs. And presumably that preventsattrsfrom doing the validation it would have done in the generated__init__, no?I suppose that if
attrsnuked and replaced your__init__, that may work, and then you'd instead write:It would be confusing for someone who may put code into
__init__not knowing it's ignored… so it's not 100% great, but maybe better.fxfitz commentedon Mar 10, 2017
@wsanchez Ah right, I forget that if you
init=Falseyou would lose your validation; my bad! We used a similar approach to what I suggested earlier, but our goals were to (1) get mypy working and (2) still use attrs for the working equality/hash checks.Tinche commentedon Mar 23, 2017
Hello, I'm one of the attrs maintainers.
I think it's fair to say mypy will have a really hard time ever working out of the box with attrs since we do some really filthy things under the hood.
I also think making attrs work with mypy would be a very worthwhile thing to try to do, since the point of attrs is to make people use structured data more and this is very complimentary to the idea of static typing checks.
I'd be interested in maybe being involved with the effort to develop a plugin API, if that's the direction we're going in, and ultimately developing a plugin for attrs.
gvanrossum commentedon Mar 23, 2017
Great plan! I wonder if you could approach this from the other direction: first write a mypy patch that supports attrs, hard-coding recognition of the special constructs it supports; and then, learning from that experience, figure out how a plugin API should be designed.
Tinche commentedon Mar 27, 2017
I'll give it a shot. No promises on the timeline :)
23 remaining items
euresti commentedon Dec 5, 2017
Oh wait. Just re-read the thread:
And the function will return
NoneI guess.ilevkivskyi commentedon Dec 5, 2017
@euresti
Yes. You will need to add a new plugin kind. If you have time, I think it makes sense (to be future proof) to ensure that plugins can be triggered not only by a specific decorator full name, but also by a specific metaclass or base class full name.
Yes, this looks like a reasonable API for the plugin. The plugin should modify
TypeInfoin place and returnNone. I think however it is better to pass the wholeClassDefAST node (again, to be future proof) then the decorator expressions can be easily extracted by the plugin when necessary.RFC: Add Class Decorator/Metaclass/Base Class plugin
euresti commentedon Dec 6, 2017
@ilevkivskyi Just added an RFC PR. (Sorry for pinging, I don't know if github notifies when PRs are added)
euresti commentedon Dec 14, 2017
@Tinche @hynek Now that the plugin scaffolding is nearly finished I started working on the actual plugin. You can see the code here: https://github.com/euresti/mypy/blob/attrs_plugin/mypy/plugin.py#L421
It actually works!
Which now gives the following output from mypy:
But I'm not sure if this code belongs in
mypyor inattrsor maybe in a completely different project altogether? Also the pyi file has to go somewhere but I'm not sure if it goes in the typeshed or somewhere else. Please advise.Also I imagine
dataclasseswill look pretty similar to theauto_attribcase.Tinche commentedon Dec 14, 2017
Cool! I can take a deeper look when I have time.
Now might be a good time to consider where this code might actually live. Does mypy support this plugin living outside and somehow picking it up after it being pip installed? We will want to have this plugin versioned according to which attrs version it can support.
hynek commentedon Dec 14, 2017
Great work David, I'm super excited that someone found the time to pull this thru!
I think part of the question where to put it also kind of depends on how the plugin APIs will evolve? It would be unfortunate if certain versions of attrs only worked with certain versions of mypy.
So I think it might make sense to to put it either into mypy or start a new package in the python-attrs orga? attrs proper seems like the worst possible place at this point.
ilevkivskyi commentedon Dec 14, 2017
@euresti Stub files should go to typeshed. After PEP 561 is accepted and implemented, you can host the stub wherever you want and distribute them via PyPI/
pip install.As for the plugin it is less clear. Currently, mypy doesn't support plugins installed via
pip install, but I think that the long term goal is to support this. Therefore, there are three options:attrsplugin for mypy as a PR to mypy, then implement the installable plugin infrastructure.attrsplugin on PyPI.I think option 1 is the fastest way to get the
attrssupport. I am leaning towards this option, but I don't have a strong preference.hynek commentedon Dec 14, 2017
I think option 1 is the best for both projects until your plugin API stabilises. In the end you’ll get failing tests if you break something. :)
Once the plugin API is solid, we can talk about extracting it into a separate project.
attrsproper seems like a terrible option in every way.JukkaL commentedon Dec 14, 2017
I agree that including the plugin with mypy is the right course of action. It may be a while before mypy supports installing plugins through pip.
ilevkivskyi commentedon Dec 14, 2017
@euresti OK, it looks like everyone likes the first option, so go ahead and make a PR to mypy.
Add Class Decorator/Metaclass/Base Class plugin (#4328)
Plugin to typecheck attrs-generated classes (#4397)