Skip to main content

handofcats Python package PyPi version Code style: black

A 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 application

    • If you want to treat python function as single-command, as_command() is helpful
    • If you want to treat python function as sub-commands, as_subcommand() is helpful
  • ◀️Escape from dependencies, if dislike this library

  • If you want something like create-react-app's eject, use --expose option

Installing

You can install via pip command, as you know.

$ pip install handofcats

Using plain python function as Console application

as_command()

If you want plain python function to treat as single command, you can attach with as_command decorator. Then it acts as executable command.

greeting.py

from handofcats import as_command

@as_command
def 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))

🚀 It acts as single-command.

$ python greeting.py hello
foo: hello
$ python greeting.py --is-surprised hello
foo: hello!
$ python greeting.py --is-surprised --name=bar bye
bar: bye!

Then, help message is here.

$ python greeting.py -h
usage: greeting [-h] [--is-surprised] [--name NAME] [--expose] [--inplace]
                [--simple]
                [--logging {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}]
                message

greeting message

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}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() and as_subcommand.run()

Sub-command support is also included, so handofcats can be useful, when using plain python functions as sub-commands.

Using as_subcommand decorator, and calling as_subcommand.run(). There is no need to write if __name__ == "__main__".

cli.py

from handofcats import as_subcommand


@as_subcommand
def hello(*, name: str = "world") -> None:
    print(f"hello {name}")


@as_subcommand
def byebye(name: str) -> None:
    print(f"byebye {name}")


# :warning: don't forget this
as_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 foo

Then, 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 dependencies

If you dislike handofcats, you can drop it.

--expose

No 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 like create-react-app'seject , runing with --expose option, generationg the code that dropping dependencies of handofcats module.

If you want to eject from the code described above, --expose is helpful, maybe.

$ python greeting.py --expose
import typing as t

def 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 argparse

    parser = argparse.ArgumentParser(prog=greeting.__name__, description=greeting.__doc__, formatter_class=type('_HelpFormatter', (argparse.ArgumentDefaultsHelpFormatter, argparse.RawTextHelpFormatter), {}))
    parser.print_usage = parser.print_help  # type: ignore
    parser.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()

--expose with --inplace

In addition, running with inplace option, when --expose, overwrite target source code.

For handofcats, eject action is --inplace --exepose.

If you're lazy, you can even skip using decorators

If you're lazy, passing file to handofcats command. After installing this package, you can use the handofcats command.

For example, pass the following file to handofcats command:

sum.py

def sum(x: int, y: int) -> None:
    print(f"{x} + {y} = {x + y}")

It acts as single-command, even not decorated by the decorators introduced earlier.

$ handofcats sum.py:sum 10 20
10 + 20 = 30

$ handofcats sum.py:sum -h
handofcats sum.py:sum -h
usage: sum [-h] [--expose] [--inplace] [--simple]
           [--logging {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}]
           x y

positional arguments:
  x
  y

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}

--expose with handofcats command

Passed in the form <filename>.py, it will be interpreted as a sub-commands. Of course, the --expose option 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.py

def hello(*, name: str = "world"):
    print(f"hello {name}")


# FIXME: default arguments (positional arguments)
def byebye(name: str):
    print(f"byebye {name}")


# ignored
def _ignore(name: str):
    print("ignored")
# treated as sub-commands
$ handofcats cli.py --expose
import typing as t

def hello(*, name: str = "world"):
    print(f"hello {name}")


# FIXME: default arguments (positional arguments)
def byebye(name: str):
    print(f"byebye {name}")


# ignored
def 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 argparse

    parser = argparse.ArgumentParser(formatter_class=type('_HelpFormatter', (argparse.ArgumentDefaultsHelpFormatter, argparse.RawTextHelpFormatter), {}))
    subparsers = parser.add_subparsers(title='subcommands', dest='subcommand')
    subparsers.required = True

    fn = hello
    sub_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: ignore
    sub_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()


# treated as single-command
$ handofcats cli.py:hello --expose
...

experimental

sequences

from typing import List, Optional

def psum(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)
    ..

choices

from typing_extensions import Literal


DumpFormat = Literal["json", "csv"]   # this: (experimental)


def run(*, format: DumpFormat = "json"):
    # treated as
    # parser.add_argument("--format", defaul="json", choices=("json", "csv"), required=False)
    ...

3.3.0

  • run with FAKE_CALL=1 envvar, skipping actual command execution.
  • show help instead of usage in also sub-commands

3.2.0

  • add print() function at handofcats module

3.1.2

  • fix falsy default value is treated as None

3.1.1

  • fix --cont option support completely, in handofcats command

3.1.0

  • add --cont option
  • add --simple option, for --expose
  • remove --untyped option, for --expose

3.0.1

  • (README uploading is failed)

3.0.0

  • add sub-commands support
  • some refactoring
  • on --expose, default format is typed code, changes to more mypy friendly output

2.5.0

  • logging option for lazy person
  • run with DEBUG=1 envvar, then logging feature is actuvated, automatically

2.4.3

  • catch up magicalimport 0.8.1

2.4.2

  • Injector with callback option

2.4.1

  • Injector support ignore_arguments and ignore_flags

2.4.0

  • some refactoring
  • use magicalimport for reducing code
  • use fastentrypoint for fast bootstrap time editable installed

2.3.2

  • fix, with Literal Types, detect type is failed

2.3.1

  • fix, choices's type is list, not dict

2.3.0

  • support Literal types
  • fix, error is occured, running with from __future__ import annotations
  • fix, generated code is invalid when positional arguments with "_"
  • fix, unable to use the function named "main"
  • --typed option, with --expose

2.2.0

  • --inplace option, with --expose

2.1.0

  • choices function
  • fix bug that it is not working, importing with physical filepath

Metadata

Release files for handofcats 3.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for handofcats 3.3.0
File Size Uploaded
handofcats-3.3.0.tar.gz 20.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for handofcats 3.3.0
File Interpreter ABI Platform
handofcats-3.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 41.4 kB

Release files / handofcats-3.3.0.tar.gz

Download URL handofcats-3.3.0.tar.gz
Size 20.0 kB
Tags Source
SHA-256 checksum
How to use checksums
f96ce6bb1025fc46901bbcf49f8e8c68585983130b40e431fa7ae907bffc567d
BLAKE2b-256 checksum
How to use checksums
91c5ff3308de76d22eb3ab527768eda9ae8946a401a4cf1b25842ed1b1289e79
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.4.1 importlib_metadata/3.10.1 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.60.0 CPython/3.10.0a6

Release files / handofcats-3.3.0-py3-none-any.whl

Download URL handofcats-3.3.0-py3-none-any.whl
Size 21.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ac3f6365c042cbf8380d937b74bb4289b85c10415670f70aba056c562f62ad1b
BLAKE2b-256 checksum
How to use checksums
1029bde9e469a08b35715b425534ccda076e1b91c71d447ab4fbd82d4e45b073
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.4.1 importlib_metadata/3.10.1 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.60.0 CPython/3.10.0a6

Release history Release notifications | RSS feed

This release

3.3.0 This release

2 release files

3.2.0

2 release files

3.1.2

2 release files

3.1.1

2 release files

3.1.0

2 release files

3.0.1

2 release files

3.0.0

2 release files

2.5.0

1 release file

2.4.3

1 release file

2.4.2

1 release file

2.4.1

1 release file

2.4.0

1 release file

2.3.2

1 release file

2.3.1

1 release file

2.3.0

1 release file

2.2.0

2 release files

2.1.0

2 release files

2.0.0

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.4

1 release file

0.1.3

1 release file

0.1.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page