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

Typing problem: use of @with_argparser will cause mypy to issue an error #1273

Closed
MilanStaffehl opened this issue Jul 30, 2023 · 4 comments
Closed

Comments

@MilanStaffehl
Copy link

When using the @with_argparser decorator inside any class that subclasses cmd2.Cmd, mypy will flag this as an error, stating that the callable returned by the wrapper function has been given a wrong argument type.

Minimal example:

import argparse
import cmd2


class MyOwnCommandUI(cmd2.Cmd):

    parser = argparse.ArgumentParser()

    @cmd2.with_argparser(parser)
    def do_something(self, args: argparse.Namespace) -> None:
        ...

mypy 1.4.1 will issue the following error:

error: Argument 1 has incompatible type "Callable[[MyOwnCommandUI, Namespace], None]"; 
expected "Callable[[Cmd, Namespace], bool | None] | Callable[[CommandSet, Namespace], bool | None] | 
Callable[[Cmd, Namespace], bool] | Callable[[CommandSet, Namespace], bool] | Callable[[Cmd, Namespace], None] | 
Callable[[CommandSet, Namespace], None]"  [arg-type]

In fact, this behavior can also be seen when running mypy against the cmd2 decorator example and other examples using the decorator.

It appears to me that the problem here is that the ArgparseCommandFunc type alias only allows for cmd2.Cmd instances, but not subclasses thereof. This seems to be related to what is described in this StackOverflow issue that describes a roughly similar issue, I believe: How to make Mypy deal with subclasses in functions as expected

Now, I have no idea whether this is by design or if this can even be fixed with reasonable amount of effort, but I figured I'd bring it up for potential future versions. If there is something that I can do about it on my end (outside of # type: ignore of course), I would be interested in that as well!

@anselor
Copy link
Contributor

anselor commented Jul 30, 2023

Thanks for this.

I'll have to set aside some time to play around with it more but I think it's possible if we add a TypeVar declaration like this:

CommandParent = TypeVar('CommandParent', bound=Union[Cmd, CommandSet])

And replace every reference to Cmd or CommandSet in the Callable declarations that would allow subclasses to correctly pass validation.

The declarations would probably need to be changed to something like the following. It would actually simplify it a little bit.

RawCommandFuncOptionalBoolReturn = Callable[[CommandParent, Union[Statement, str]], Optional[bool]]

#: Function signature for a Command Function that uses an argparse.ArgumentParser to process user input
#: and optionally returns a boolean
ArgparseCommandFuncOptionalBoolReturn = Callable[[CommandParent, argparse.Namespace], Optional[bool]]

#: Function signature for a Command Function that uses an argparse.ArgumentParser to process user input
#: and returns a boolean
ArgparseCommandFuncBoolReturn = Callable[[CommandParent, argparse.Namespace], bool]

#: Function signature for an Command Function that uses an argparse.ArgumentParser to process user input
#: and returns nothing
ArgparseCommandFuncNoneReturn = Callable[[CommandParent, argparse.Namespace], None]

#: Aggregate of all accepted function signatures for an argparse Command Function
ArgparseCommandFunc = Union[
    ArgparseCommandFuncOptionalBoolReturn,
    ArgparseCommandFuncBoolReturn,
    ArgparseCommandFuncNoneReturn,
]

anselor added a commit that referenced this issue Aug 15, 2023
anselor added a commit that referenced this issue Aug 15, 2023
anselor added a commit that referenced this issue Aug 15, 2023
@anselor
Copy link
Contributor

anselor commented Aug 15, 2023

@MilanStaffehl
Looks like the changes to type definitions does the trick. I'd appreciate it if you could pull my branch and try it out to verify it also works for you.

There have been a number of fixes recently and I think we should be doing a new release soon.

@MilanStaffehl
Copy link
Author

I can confirm that with the changes on the branch the problem no longer occurs for me; mypy passes without an error now. Thanks!

Looking forward to the new release, that's another type: ignore I can remove!

tleonhardt pushed a commit that referenced this issue Aug 16, 2023
@tleonhardt
Copy link
Member

I'm closing this since #1276 appears to have addressed it. If I'm wrong, please feel free to re-open.

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

No branches or pull requests

3 participants