Package: handofcats
handofcatsA tiny magically Converter that making executable command from
plain python function. If the function is type annotated, it is used.Feature✨Using plain python function as Console applicationIf you want to treat python function as single-command,as_command()is helpfulIf you want to treat python function as sub-commands,as_subcommand()is helpful◀️Escape from dependencies, if dislike this libraryIf you want something likecreate-react-app's eject, use--exposeoptionInstallingYou can install via pip command, as you know.$pipinstallhandofcatsUsing plain python function as Console applicationas_command()If you want plain python function to treat as single command, you can attach withas_commanddecorator. Then it acts as executable command.greeting.pyfromhandofcatsimportas_command@as_commanddefgreeting(message:str,is_surprised:bool=False,name:str="foo")->None:"""greeting message"""suffix="!"ifis_surprisedelse""print("{name}:{message}{suffix}".format(name=name,message=message,suffix=suffix))🚀 It acts as single-command.$pythongreeting.pyhellofoo: hello$pythongreeting.py--is-surprisedhellofoo: hello!$pythongreeting.py--is-surprised--name=barbyebar: bye!Then, help message is here.$pythongreeting.py-husage: greeting [-h] [--is-surprised] [--name NAME] [--expose] [--inplace][--simple][--logging {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}]messagegreeting messagepositional arguments:message               -optional arguments:-h, --help            show this help message and exit--is-surprised        - (default: False)--name NAME           - (default: foo)--expose              dump generated code. with --inplace, eject from handofcats dependency (default: False)--inplace             overwrite file (default: False)--simple              use minimum expression (default: False)--logging {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}positional arguments:message               -optional arguments:-h, --help            show this help message and exit--is-surprised        - (default: False)--name NAME           - (default: foo)--expose              dump generated code. with --inplace, eject from handofcats dependency (default: False)--inplace             overwrite file (default: False)--simple              use minimum expression (default: False)--logging {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}( :warning: TODO: detail description )as_subcommand()andas_subcommand.run()Sub-command support is also included, so handofcats can be useful, when using plain python functions as sub-commands.Usingas_subcommanddecorator, and callingas_subcommand.run(). There is no need to writeif __name__ == "__main__".cli.pyfromhandofcatsimportas_subcommand@as_subcommanddefhello(*,name:str="world")->None:print(f"hello{name}")@as_subcommanddefbyebye(name:str)->None:print(f"byebye{name}")# :warning: don't forget thisas_subcommand.run()🚀 It acts as sub-commands.$ python cli.py hello
hello world

$ python cli.py hello --name foo
hello foo

$ python cli.py byebye foo
byebye fooThen, help message is here.$ python cli.py -h
usage: cli.py [-h] [--expose] [--inplace] [--simple]
              [--logging {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}]
              {hello,byebye} ...

optional arguments:
  -h, --help            show this help message and exit
  --expose              dump generated code. with --inplace, eject from handofcats dependency (default: False)
  --inplace             overwrite file (default: False)
  --simple              use minimum expression (default: False)
  --logging {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}

subcommands:
  {hello,byebye}
    hello
    byebye


$ python cli.py hello -h
usage: cli.py hello [-h] [--name NAME]

optional arguments:
  -h, --help   show this help message and exit
  --name NAME  (default: 'world')Dropping dependenciesIf you dislike handofcats, you can drop it.--exposeNo Lock-In: You can “eject” to a custom setup at any time. Run a single-command, and all the configuration and build dependencies will be moved directly into your project, so you can pick up right where you left off.Something likecreate-react-app'seject, runing with--exposeoption, generationg the code that dropping dependencies of handofcats module.If you want to eject fromthe code described above,--exposeis helpful, maybe.$pythongreeting.py--exposeimport typing as tdef greeting(message: str, is_surprised: bool = False, name: str = "foo") -> None:"""greeting message"""suffix = "!" if is_surprised else ""print("{name}: {message}{suffix}".format(name=name, message=message, suffix=suffix))def main(argv: t.Optional[t.List[str]] = None) -> t.Any:import argparseparser = argparse.ArgumentParser(prog=greeting.__name__, description=greeting.__doc__, formatter_class=type('_HelpFormatter', (argparse.ArgumentDefaultsHelpFormatter, argparse.RawTextHelpFormatter), {}))parser.print_usage = parser.print_help  # type: ignoreparser.add_argument('message', help='-')parser.add_argument('--is-surprised', action='store_true', help='-')parser.add_argument('--name', required=False, default='foo', help='-')args = parser.parse_args(argv)params = vars(args).copy()return greeting(**params)if __name__ == '__main__':main()--exposewith--inplaceIn addition, running withinplaceoption, when--expose, overwrite
target source code.For handofcats, eject action is--inplace --exepose.If you're lazy, you can even skip using decoratorsIf you're lazy, passing file tohandofcatscommand. After installing this package, you can use thehandofcatscommand.For example, pass the following file tohandofcatscommand:sum.pydefsum(x:int,y:int)->None:print(f"{x}+{y}={x+y}")It acts as single-command, even not decorated by the decorators introduced earlier.$handofcatssum.py:sum102010 + 20 = 30$handofcatssum.py:sum-hhandofcats sum.py:sum -husage: sum [-h] [--expose] [--inplace] [--simple][--logging {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}]x ypositional arguments:xyoptional arguments:-h, --help            show this help message and exit--expose              dump generated code. with --inplace, eject from handofcats dependency (default: False)--inplace             overwrite file (default: False)--simple              use minimum expression (default: False)--logging {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}--exposewith handofcats commandPassed in the form<filename>.py, it will be interpreted as a
sub-commands. Of course, the--exposeoption also works.And passed in the form<filename>.py:<function name>, it will be
interpreted as a single-command.So, plain python function only needed.cli.pydefhello(*,name:str="world"):print(f"hello{name}")# FIXME: default arguments (positional arguments)defbyebye(name:str):print(f"byebye{name}")# ignoreddef_ignore(name:str):print("ignored")#treatedassub-commands$handofcatscli.py--exposeimport typing as tdef hello(*, name: str = "world"):print(f"hello {name}")#FIXME:defaultarguments(positionalarguments)def byebye(name: str):print(f"byebye {name}")#ignoreddef ignore(name: str):print(f"ignored {name}")def _ignore(name: str):print("of cource, ignored")def main(argv: t.Optional[t.List[str]] = None) -> t.Any:import argparseparser = argparse.ArgumentParser(formatter_class=type('_HelpFormatter', (argparse.ArgumentDefaultsHelpFormatter, argparse.RawTextHelpFormatter), {}))subparsers = parser.add_subparsers(title='subcommands', dest='subcommand')subparsers.required = Truefn = hellosub_parser = subparsers.add_parser(fn.__name__, help=fn.__doc__, formatter_class=parser.formatter_class)sub_parser.add_argument('--name', required=False, default='world', help='-')sub_parser.set_defaults(subcommand=fn)fn = byebye  # type: ignoresub_parser = subparsers.add_parser(fn.__name__, help=fn.__doc__, formatter_class=parser.formatter_class)sub_parser.add_argument('name', help='-')sub_parser.set_defaults(subcommand=fn)args = parser.parse_args(argv)params = vars(args).copy()subcommand = params.pop('subcommand')return subcommand(**params)if __name__ == '__main__':main()#treatedassingle-command$handofcatscli.py:hello--expose...experimentalsequencesfromtypingimportList,Optionaldefpsum(xs:List[int],*,ys:Optional[List[int]]=None):# treated as# parser.add_argument('xs', nargs='*', type=int)# parser.add_argument('--ys', action='append', required=False, type=int)..choicesfromtyping_extensionsimportLiteralDumpFormat=Literal["json","csv"]# this: (experimental)defrun(*,format:DumpFormat="json"):# treated as# parser.add_argument("--format", defaul="json", choices=("json", "csv"), required=False)...3.3.0run withFAKE_CALL=1envvar, skipping actual command execution.show help instead of usage in also sub-commands3.2.0add print() function at handofcats module3.1.2fix falsy default value is treated as None3.1.1fix--contoption support completely, in handofcats command3.1.0add--contoptionadd--simpleoption, for--exposeremove--untypedoption, for--expose3.0.1(README uploading is failed)3.0.0add sub-commands supportsome refactoringon--expose, default format is typed code, changes to more mypy friendly output2.5.0logging option for lazy personrun with DEBUG=1 envvar, then logging feature is actuvated, automatically2.4.3catch up magicalimport 0.8.12.4.2Injector with callback option2.4.1Injector support ignore_arguments and ignore_flags2.4.0some refactoringuse magicalimport for reducing codeuse fastentrypoint for fast bootstrap time editable installed2.3.2fix, with Literal Types, detect type is failed2.3.1fix, choices's type is list, not dict2.3.0support Literal typesfix, error is occured, running withfrom __future__ import annotationsfix, generated code is invalid when positional arguments with "_"fix, unable to use the function named "main"--typedoption, with--expose2.2.0--inplaceoption, with--expose2.1.0choicesfunctionfix bug that it is not working, importing with physical filepath