-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Pluggable system for generating types from docstrings (revisited) #3225
Conversation
7f655a2
to
254f3d2
Compare
376cd58
to
724c484
Compare
Added tests and improved the structure a bit.
Thus accessing a Questions:
Thanks! |
mypy/hooks.py
Outdated
|
||
from typing import Callable, Dict, Generic, Optional, Tuple, TypeVar, TYPE_CHECKING | ||
|
||
if TYPE_CHECKING: |
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.
You should use MYPY = False
and then if MYPY: ...
. TYPE_CHECKING
is not present in older versions of Python.
@gvanrossum You reviewed the previous attempt, maybe you could review this PR too? |
I see that @JukkaL just pushed up a general-plugins branch for #1240. There's a lot of overlap with what I've done so it seems like a good starting place for this PR. The mechanism for registering and loading custom plugins looks like it has not been added yet, so I'm interested to see how that will work: via mypy.ini or something else? |
@chadrik Plugins will be registered through mypy.ini. Once the general plugin framework lands, hopefully it isn't hard to extend it to support plugins that parse docstrings. |
I'm not sure how to proceed. After #3501 lands maybe we can look at how to add hooks for parsing docstrings using the new Plugin class, but a bunch more machinery needs to be invented first. But I expect that a thorough review of the current PR would be a waste of time, since the plan is to let you define a plugin for this purpose outside the mypy source code. |
Maybe wait until #3501 has landed and there is support for user-defined plugins (in a follow-up PR)? Then we can look at adding docstring parse hooks to the Plugin class. The latter would hopefully be pretty straightforward once the rest of the plugin system is in place. |
Maybe Chad can help design/write the code for user-defined plugins?
|
I'd be glad to help out with the user-defined plugins. I have prior experience designing a handful of similar systems. I'll drop some thoughts over in the other PR. |
In the meantime I hope you don't mind if I close this one (again). Thanks for your suggestions on the dsign of a plugin system, your insights are useful! |
@gvanrossum If you don't mind I'd like to keep this open specifically for adding the docstring support once we wrap up the user-plugin feature (#3517). I don't want to complicate that PR by mixing it up with docstrings. Based on the progress in #3517 this won't be lingering for long. |
OK, np! Though I was under the impression that with a powerful enough plugin system we could have plugins installed from PyPI, so the docstring plugin wouldn't need to be a mypy PR? |
The closest existing hook to what I would need is the Docstrings are an alternative to type comments, so it makes sense for a docstring hook to run earlier, at the time that type annotations are extracted from code and turned into Types. That's the approach that I've taken in this PR. When I port it to the plugin system, I plan to add a new hook type and call it in fastparse. That is, unless you see a way to make it work using |
Agreed, I had always understood that you need a hook in the parsing stage for this purpose. It's just that the code of the hook (i.e. the actual docstring parsing) doesn't have to be part of mypy, right? |
Correct. This PR will be to add the new hook type to the Plugin class and call it at the appropriate place in fastparse. |
Honestly that sounds like a new PR, but you are free to reuse this one if it's easier for you. |
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.
Waiting for you.
here you go! |
Heads up: once #3534 lands (specifically 3283cbf) I believe this will require more work, since Jukka is changing the signatures for hooks. Instead of having a separate signature for each hook type, the hooks get passed a specific object which has various useful attributes -- it's easier to add new attributes that way, without having to update all hooks of that type. |
mypy/fastparse.py
Outdated
|
||
|
||
def parse(source: Union[str, bytes], fnam: str = None, errors: Errors = None, | ||
options: Options = Options()) -> MypyFile: | ||
options: Options = Options(), plugin: Plugin = Plugin()) -> MypyFile: |
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.
There ought to be no reason to provide a default. And the default definitely should not create a Plugin instance that's usually not used.
mypy/fastparse.py
Outdated
@@ -58,10 +59,12 @@ | |||
|
|||
TYPE_COMMENT_SYNTAX_ERROR = 'syntax error in type comment' | |||
TYPE_COMMENT_AST_ERROR = 'invalid type comment or annotation' | |||
TYPE_COMMENT_DOCSTRING_ERROR = ('Arguments parsed from docstring are not ' |
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.
Maybe 'One or more arguments specified in docstring are not '.
mypy/fastparse.py
Outdated
return_type = type_map.pop('return', AnyType()) | ||
if type_map: | ||
errors.report(line, 0, | ||
TYPE_COMMENT_DOCSTRING_ERROR.format(list(type_map), arg_names)) |
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.
Maybe there also ought to be an error for arguments in the arg list but not in the docstring (given that there's a type map at all).
Also, I think the error message here needn't list the expected arg names (those are easily recovered from the source) but only the list of args in the docstring but not in the arg list.
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.
Maybe there also ought to be an error for arguments in the arg list but not in the docstring (given that there's a type map at all).
I think it's important that docstring annotations can be sparse, as with multi-line python 2 function annotations.
mypy/fastparse.py
Outdated
@@ -345,6 +369,14 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef], | |||
return_type = TypeConverter(self.errors, line=n.returns.lineno | |||
if n.returns else n.lineno).visit(n.returns) | |||
|
|||
docstring_hook = self.plugin.get_docstring_parser_hook() | |||
if docstring_hook is not None and not any(arg_types) and return_type is None: |
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 wonder if there'd be value in parsing the docstring (if present) even if there are types in the signature, and flagging discrepancies (only when both are in direct conflict) and maybe even merging the info (if some args have a type only in one of the two sources)? (Admittedly we don't do that for PEP-3107-style annotations and signature type comments either, but there we at least reject the presence of both. Here you seem to completely disregard the docstring if any types are present in the signature.)
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.
Rejecting the presence of both seems like the right choice.
mypy/plugin.py
Outdated
# The function's return type, if specified, is stored in the mapping with the special | ||
# key 'return'. Other than 'return', each key of the mapping must be one of the | ||
# arguments of the documented function; otherwise, an error will be raised. | ||
DocstringParserHook = Callable[ |
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.
This is the part that will need updating after #3534 lands.
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.
Thanks for the feedback. I'll get these fixed up and pushed, then do an optimistic rebase against #3534 so that I'm ready when that lands.
@gvanrossum rebased against master and addressed review notes |
Hi @gvanrossum & @ilevkivskyi, I'm ready for the next round of notes. thanks! |
Hey guys, I know that this issue isn't very high on your priority list, but I feel it's very close, so it would be great to get it completed! |
@JukkaL Maybe you can look at this? |
I'm going to be on vacation soon for a little over a week and I unfortunately may not have time for this until after my vacation. I can get back to this in 2 weeks or so. |
No worries. Enjoy your vacation and I'll talk to you in 2 weeks.
…On Tue, Aug 15, 2017 at 10:09 AM Jukka Lehtosalo ***@***.***> wrote:
I'm going to be on vacation soon for a little over a week and I
unfortunately may not have time for this until after my vacation. I can get
back to this in 2 weeks or so.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#3225 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAD3E72dyu7Lf9ui8yc8OKa3tlnR8jiNks5sYdCzgaJpZM4NFQ6Y>
.
|
The merges don't look very hard. I used to be very skeptical of this approach but I've mellowed a bit and I think this is fine to go in. I might even find time to review it over my holiday break (which starts now). |
I merged and pushed. There are a few remaining issues, but that's all I have time for at this moment. |
Strangely, the conflicts are still there. Perhaps you didn't fetch before merging? |
Yeah, I noticed that, but thought there might be a delay. I squashed the branch and force pushed it, which cleared up the issue. |
What's the status of this? Is this abandoned or got it replaced by a different PR or project? I'd really like to have mypy checking for pybind11 C extensions (which do add type info to the doc strings). |
It's a complicated patch, and we haven't found the time to review it. I
don't know what pybind11 is but if it's a code generator, I would suggest
that a more effective approach would be to generate separate .pyi files --
we're seeing this approach adopted in other code generators (e.g. for
protocol buffers, see https://github.com/dropbox/mypy-protobuf/).
|
Thanks. In short, pybind11 is a python extension that allows C++ functions to be called from python. When accessing it from python, they have .__doc__ strings with PEP484 type hints. Would be cool if mypy could use these. https://pybind11.readthedocs.io/en/stable/ |
@smessmer While it would still be cool to get this merged, I don't think it's going to work for your case. This plugin runs within mypy and so does not import any code and thus only has access to statically defined docstrings. Unless pybind11 is generating .py files containing entrypoint/wrapper functions and classes with statically inspectable docstrings -- which I'm guessing it's not -- this plugin isn't going to "see" the doc attributes generated by the bindings. And if it is then it should be simple to make those into .pyi files. |
I see, makes sense. Thanks for the explanation. I had misunderstood the PR description to have plugins import python modules into a python runtime and read the doc strings there. |
I propose to (once again) abandon this idea. |
I've finally released my solution to this: https://pypi.org/project/doc484/ It's command-line tool that inserts type comments into your code based on docstrings. It has the advantage of working with PyCharm and other PEP484-compatible tools and not just mypy. Feel free to close this. edit: closed it myself, because it's my pull request :) |
That's a cool tool, and in retrospect a much better solution! Thanks for
your persistence.
|
Hi,
I'm picking up where I left off on #2240.
Here's a bit of a review:
This PR would allow third-party tools to extend mypy with callbacks to parse PEP484 type annotations within function docstrings. To do so, this PR introduces a basic "hook" system, as a pre-cursor to a complete plugin system (issue #1240).
A hook can be registered, along with options specific to that hook, using the mypy configuration file:
Hook options specified in the config file are passed to the hook as a dictionary.
One big new change from where I left off with #2240 is that the
docstring_parser
hook is now passed the activemypy.errors.Errors
instance so that it can use the full range of error reporting, especially specification of blocker and severity level.I have concerns about exposing the
mypy.errors.Errors
class as part of a public API so I was considering writing a simple wrapper that exposes only the bare minimum interface, perhaps something like this:What do you think?
Sorry about getting stalled out before (I blamed it on the birth of my second daughter!). Looking forward to getting this wrapped up.