tomerfiliba / plumbum

Plumbum: Shell Combinators
https://plumbum.readthedocs.io
MIT License
2.78k stars 182 forks source link

Optional typing compatibility #336

Open ASMfreaK opened 6 years ago

ASMfreaK commented 6 years ago

mypy complains about functions wrapped with plumbum.cli.Predicate: error: Invalid type "..." An example:

from plumbum import cli
@cli.Predicate
def GummyBear(n):
    if not n.startswith('gummybear'):
        raise ValueError('{} is not a gummybear!'.format(n))
    return n

class GummyConsumer(cli.Application):
    def main(self, bear: GummyBear):
        print('MUNCH MUNCH MUNCH')
$ mypy gummy.py
gummy.py:1: error: Cannot find module named 'plumbum'
gummy.py:1: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-imports" flag would help)
gummy.py:9: error: Invalid type "gummy.GummyBear"

This should be made consistent

ASMfreaK commented 6 years ago

A quick hack that I'm using for now:

from plumbum import cli
class GummyBear(str):
    def __new__(cls, tids: str):
        if not n.startswith('gummybear'):
            raise ValueError('{} is not a gummybear!'.format(n))
        return n

class GummyConsumer(cli.Application):
    def main(self, bear: GummyBear):
        print('MUNCH MUNCH MUNCH')
$ python gummy_new.py gummybear
MUNCH MUNCH MUNCH
$ python gummy_new.py gummybe
Error: Argument of bear expected to be <class '__main__.GummyBear'>, not 'gummybe':
    ValueError('gummybe is not a gummybear!',)
------
Usage:
    gummy_new.py [SWITCHES] bear

Meta-switches
    -h, --help         Prints this help message and quits
    --help-all         Print help messages of all subcommands and quit
    -v, --version      Prints the program's version and quits

$ mypy gummy_new.py
gummy_new.py:2: error: Cannot find module named 'plumbum'
gummy_new.py:2: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-imports" flag would help)

you can see it in my repo

ASMfreaK commented 6 years ago
Traceback (most recent call last):
  File "gummy.py", line 12, in <module>
    GummyConsumer.run()
  File "plumbum/cli/application.py", line 480, in run
    ordered, tailargs = inst._validate_args(swfuncs, tailargs)
  File "plumbum/cli/application.py", line 431, in _validate_args
    positional[args_names.index(item)] = m.annotations[item]
ValueError: 'return' is not in list

This traceback is triggered if main() of cli.Application has a typed return i.e.:

class GummyConsumer(cli.Application):
    def main(self, bear: GummyBear) -> None:
        print('MUNCH MUNCH MUNCH')

This should be sorted out somehow for #334 for full typing information.

henryiii commented 6 years ago

I'm planning on looking at this eventually, but feel free to investigate, you might be faster!

I'm not sure what the fix would be; a function is not a type, so it is invalid for MyPy. Your workaround is of course reasonable, but don't know of a simpler way.

henryiii commented 6 years ago

Correction: the most recent problem should be an easy fix. Try the mypy branch.

henryiii commented 6 years ago

It might be possible for Predicate to transform:

@cli.Predicate
def GummyBear(n) -> str:
    if not n.startswith('gummybear'):
        raise ValueError('{} is not a gummybear!'.format(n))
    return n

to

GummyBear = type('GummyBear', str, '__new__':...)
ASMfreaK commented 6 years ago

No, I don't think this will work. Mypy is very static and won't understand (at least for now) the runtime type creation. For now, we should use the class-__new__ workaround as mypy developers are yet to agree on decorators annotations (python/mypy#3157), so I guess this issue is blocked until they are done

henryiii commented 6 years ago

@ASMfreaK, yes, I played around with MyPy and realized that wouldn't work.