-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
fix(types): decorator typing fails #2233
Conversation
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 attemps to resolves another issue (similar to #2227, but not that one, and I don't know if it got reported).
import click
@click.group("grp")
def grp() -> None:
...
@grp.command
def cmd() -> None:
...
reveal_type(cmd)
# (before) Revealed type is "Union[def (def (*Any, **Any) -> Any) -> click.core.Command, click.core.Command]"
# (after, with suggested change) Revealed type is "click.core.Command"
To resolve #2227, I believe command()
in src/click/decorator.py
should be adjusted.
Hmm, that one already has overloads, though. Is the order important and off? |
I'd suggest: diff --git a/src/click/decorators.py b/src/click/decorators.py
index 05d1334..9923fb6 100644
--- a/src/click/decorators.py
+++ b/src/click/decorators.py
@@ -124,6 +124,13 @@ def pass_meta_key(
CmdType = t.TypeVar("CmdType", bound=Command)
+@t.overload
+def command(
+ __func: t.Callable[..., t.Any],
+) -> Command:
+ ...
+
+
@t.overload
def command(
name: t.Optional[str] = None, which leads to: import click
@click.command
def test() -> None:
...
reveal_type(test)
# (before) Revealed type is "<nothing>"
# (after) Revealed type is "click.core.Command" hence resolving #2227. |
@dlax that doesn't appear to be the correct annotation though, there is no |
I can't figure out why the overload is causing mypy to think the return value is "nothing". I tried shuffling the order around, or making it use the Simplifying the overloads by removing the two for |
A double underscore parameter in mypy is a nameless parameter (stand-in for the |
Is it really considered "valid" to pass a callable any other way other than by "leaving off" the parenthesis? You can have more limited typing that what is technically allowed at runtime. This could also be a bit more enforced at runtime, too. |
I agree this isn't intended to work with a callable and other args. Would that change what the annotation is and fix this? |
That's what I'm trying. Currently doesn't seem to, but I think it should, playing with it. |
Is there someone from mypy we can ping to help out? I'm really out of my depth when it comes to these typing issues. |
I think I got it, just a minute. |
3b52d05
to
41ae48b
Compare
FYI, you can't run diff --git a/tox.ini b/tox.ini
index 056ca0d..8afb83e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -17,7 +17,7 @@ commands = pre-commit run --all-files --show-diff-on-failure
[testenv:typing]
deps = -r requirements/typing.txt
-commands = mypy
+commands = mypy --platform=linux
[testenv:docs]
deps = -r requirements/docs.txt I can add that in another PR if you think it's useful. |
By the way, having tests for typing would be nice (it feels weird to have "typing tests", so don't have much experience with best practices there). |
Sure, I'd be fine addressing that with another PR. Maybe disable I have no idea how to write typing tests, maybe add typing to the test suite itself and run mypy against it as well? Seems like it could be good for the sprint at at PyCon. |
MyPy's unreachable is very powerful, though, and can catch things that others can't, like code after types have been fully narrowed, etc. I've mostly avoided typing tests, since tests often literally break typing constructs in order to test errors (or at least they should). Enabling pytest in typing also slows down typing a bit - I tend to use mypy via pre-commit. But maybe that would be worthwhile; it would likely expose things like this. I'd probably not make the tests "strict", though, (no need to type every test function) and I've had little success in 'turning off' aspects of strictness in specific directories. But maybe that would be a good way to do it. Typing examples might be more useful, though often people don't put types on them to make them "friendly" to beginning programmers. (Does it really? I don't know, I find types make code much easier to read). |
Yeah, that's been my experience too with typing tests. Thanks for working on this, I will merge and make a release in a bit. |
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.
Last changes seem to fix things as far as I can tell, thanks @henryiii
Type-checking the test suite would indeed help. To avoid the burden of adding annotations to all test functions, while keeping mypy strict mode, one can use: [mypy-tests.*]
check_untyped_defs = true
disallow_untyped_defs = false
disallow_untyped_calls = false Then running |
a8fe905
to
3a880a7
Compare
Let me know if you see anything that you'd like different or added! |
@@ -1809,6 +1809,16 @@ def add_command(self, cmd: Command, name: t.Optional[str] = None) -> None: | |||
_check_multicommand(self, name, cmd, register=True) | |||
self.commands[name] = cmd | |||
|
|||
@t.overload | |||
def command(self, __func: t.Callable[..., t.Any]) -> Command: |
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 cannot pass other arguments if you use it as a decorator without parenthesis. I've added assert statements requiring this be true. If you want to add other arguments, it should be used in the traditional way as a factory. And mypy certainly should not encourage that even if was supported at runtime!
Best approach is simply to type check tests and examples, as these are the places where you actually use the API.
While this is true in the case of checking that an exception is raised or similar, I find that it's not that frequent, and is easily managed with the occasional
We do this in the aio-libs projects. One thing that might catch people out, is legacy pytest behaviour discouraged having a
We usually have a quickstart in the readme or something which is not checked, but then everything under examples/ will be strictly typed. If anybody wants some hints setting any of this up, just ping me in another PR. Also feel free to look at one of our config files as an example: https://github.com/aio-libs/aiohttp-jinja2/blob/master/.mypy.ini |
That sounds good - as I've said, haven't had much experience typing tests, so didn't now how well it might work. I expect the module I'll be trying this out one of my packages soon, thanks! |
The lack of
While having
https://docs.pytest.org/en/6.2.x/pythonpath.html#import-modes If you're worried about the possible side effects from using the legacy import with
They changed the behaviour to start picking up directories which are not modules. But, the config format does not allow configuring these. I raised a bug when this happened: python/mypy#10045 |
No, that's only a requirement for importing from your own test suite (which is a bit of an antipattern in pytest, use contest.py and fixtures instead usually). Unless you need to import from your test module, you still don't need an Ahh, but that bug in mypy is probably a problem... Though it looks to me you might be able to use the test file name pattern instead, such as |
3a880a7
to
5f502ba
Compare
5f502ba
to
e234cf9
Compare
I rebased to the 8.1.x maintenance branch, added a changelog, and changed the assertion wording a bit. |
Click 8.1.1 is now available: https://pypi.org/project/click/8.1.1/ |
<nothing> not callable
error in Mypy with Click 8.1.0 #2227I've tried the fix suggested in the issue above by @Dreamsorcerer, and it seems to fix the typing issue locally.
Checklist:
CHANGES.rst
summarizing the change and linking to the issue... versionchanged::
entries in any relevant code docs.pre-commit
hooks and fix any issues.pytest
andtox
, no tests failed.