biqqles / dataclassy

A fast and flexible reimplementation of data classes
https://pypi.org/project/dataclassy
Mozilla Public License 2.0
81 stars 9 forks source link

Mypy support #19

Closed giannitedesco closed 3 years ago

giannitedesco commented 3 years ago

First of all, love dataclassy, it solves all of my problems with dataclasses/namedtuples and the like wonderfully :)

Only problem is lack of checking with mypy! Following demonstrates the issue:

from dataclassy import dataclass

@dataclass
class Thing:
    prop: int

x = Thing(1)
$ mypy --strict foo.py
foo.py:1: error: Skipping analyzing 'dataclassy': found module but no type hints or library stubs
foo.py:1: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
foo.py:7: error: Too many arguments for "Thing"
Found 1 error in 1 file (checked 1 source file)

Am not sure what the solution is here. Can this be solved by simply adding a dataclassy.pyi stub file and setting py.typed to the module ?

I tried stubgen, which at least got the arguments for the decorator...

giannitedesco commented 3 years ago

Ah, it looks like mypy has a plugin to handle these types of classes, that makes sense: https://github.com/python/mypy/blob/master/mypy/plugins/dataclasses.py

What I did was add type-stubs based on the default dataclass type stubs in to my typeshed. Then I added dataclassy.dataclass to dataclass_makers in mypy/plugin/dataclasses.py then it works well in simple cases.

Of course, that breaks things for the new inheritance features of dataclassy.

So it looks like the solution is in two parts:

  1. Add a type stub and py.typed to dataclassy
  2. In mpyp, first register dataclassy in to the existing plugin, and then extend that pluggin to support dataclassy's inheritance (and any other?) features.
giannitedesco commented 3 years ago

For typeshed:

--- dataclasses.pyi 2020-10-31 02:10:14.585767164 +0900
+++ dataclassy.pyi  2021-02-03 11:30:54.065126147 +0900
@@ -19,7 +19,7 @@
 def dataclass(_cls: None) -> Callable[[Type[_T]], Type[_T]]: ...
 @overload
 def dataclass(
-    *, init: bool = ..., repr: bool = ..., eq: bool = ..., order: bool = ..., unsafe_hash: bool = ..., frozen: bool = ...
+    *, init: bool = ..., slots: bool = ..., repr: bool = ..., eq: bool = ..., order: bool = ..., unsafe_hash: bool = ..., frozen: bool = ...
 ) -> Callable[[Type[_T]], Type[_T]]: ...

 class Field(Generic[_T]):

For mypy:

--- dataclasses.py.orig 2021-02-03 11:39:08.509598332 +0900
+++ dataclasses.py  2021-02-03 11:33:04.036571514 +0900
@@ -19,6 +19,7 @@
 dataclass_makers = {
     'dataclass',
     'dataclasses.dataclass',
+    'dataclassy.dataclass',
 }  # type: Final

 SELF_TVAR_NAME = '_DT'  # type: Final
biqqles commented 3 years ago

Interesting, thanks for this. Tool incompatibility has been raised before (#5) and is something I'd love to improve if possible. I have never used either of these tools, so please excuse my ignorance, but are there files that I can include in dataclassy's wheel package that would help with this? Or would it require an upstream revision of those rules? I'm guessing from your diffs that it's the latter, in which case my only concern is whether they would accept PRs for a relatively unknown library like this...

giannitedesco commented 3 years ago

Partly. There are things which can be added to the dataclassy wheel which will help. That is to add "py.typed" file and to add a "dataclassy.pyi" file. That would achieve the following: the decorators themselves would be type-checked and you wouldn't get the "error: unknown module" thing from mypy. The procedure for this is described in PEP-561. I can help with that at the weekend, if you like. I actually did it in one of my own projects recently.

Next step would be a one-liner PR for mypy, I can't see a downside for them but who knows :) That would make type-checking work for most (but not all) dataclassy use-cases. Most significantly, the inheritance features would still cause type-check failures.

If that goes well, maybe we (again I would be interested in doing this provided I can find time at weekends), can add full support to mypy by editing the current dataclasses plugin or by creating a new plugin. I've been trying to grok the code and not yet sure which approach is best, probably the latter.

PS. Surprised that this library is relatively unknown, I've run in to the need for it in, like, half a dozen projects and the workarounds I've gone to before discovering dataclassy are kind of nuts. I heard about it on a stackoverflow question and just assumed it was something that "everyone knew" :)

biqqles commented 3 years ago

Those PRs would be appreciated! I just tried adding (ReST) parameter specifications to the docstring for @dataclass and they seem to be recognised and used for autocompletion by both PyCharm and VSCode! I don't know if mypy is smart enough to read them too, obviating the need for an extra file, but no matter if it doesn't.


Funnily enough, the Stack Overflow answer you saw was probably by me - I ran into the same issues with dataclasses that everyone does eventually, stared aghast at the workarounds required, then wrote dataclassy and came back to a couple of the questions I'd found with it as a solution.

So yes, I think it is relatively unknown in terms of the number of stars etc., but I hope once discovered it's the sort of thing people find useful again and again. I'm glad that was the case for you!

giannitedesco commented 3 years ago

Added #20 to enable type-checked installations. It seems to work pretty well.

I will look at forking mypy to figure out the issues around inheritance and so on, and post any updates here.

giannitedesco commented 3 years ago

OK, I've gotten a proof-of-concept mypy plugin. I just need to make it put default-assigned attributes at the end of the parameter list and support all the various permutations of decorator options and so on. I think I'll be ready to share something next weekend.

The other thing I've discovered is that we could distribute the plugin as part of dataclassy (or as a separate module) if we can't merge it with mypy (or until that happens). That way, users can just add a mypy.ini with plugins=dataclassy.mypy if they want type-checking to work for a dataclassy project.

biqqles commented 3 years ago

Great news on both fronts, thanks for your continued work on this.

ZdenekM commented 3 years ago

I'm really looking forward to trying dataclassy once full mypy support will be there!

biqqles commented 3 years ago

Closing this issue now that basic mypy support has been released. Feel free to open new issues for enhancements!