{"id":16561785,"url":"https://github.com/jamesob/clii","last_synced_at":"2025-07-30T23:36:56.525Z","repository":{"id":62562662,"uuid":"230980573","full_name":"jamesob/clii","owner":"jamesob","description":"Python 3.7+ function annotations -\u003e CLI","archived":false,"fork":false,"pushed_at":"2022-09-20T16:08:43.000Z","size":51,"stargazers_count":45,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-27T12:46:02.642Z","etag":null,"topics":["cli","command-line-interface","command-line-parser","minimal","no-dependencies","python-cli","python3"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesob.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-12-30T21:12:31.000Z","updated_at":"2024-12-09T00:48:05.000Z","dependencies_parsed_at":"2022-11-03T15:17:53.713Z","dependency_job_id":null,"html_url":"https://github.com/jamesob/clii","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesob%2Fclii","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesob%2Fclii/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesob%2Fclii/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesob%2Fclii/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesob","download_url":"https://codeload.github.com/jamesob/clii/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243826798,"owners_count":20354221,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","command-line-interface","command-line-parser","minimal","no-dependencies","python-cli","python3"],"created_at":"2024-10-11T20:34:19.616Z","updated_at":"2025-03-16T20:30:39.734Z","avatar_url":"https://github.com/jamesob.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# clii\n\nGenerate argument parsers from Python 3 function annotations with minimal\nboilerplate.\n\n\n```python\n#!/usr/bin/env python3.8\nfrom clii import App\nfrom pathlib import Path\nfrom subprocess import run\n\ncli = App()\n\n\n@cli.cmd\ndef add(a: int, b: int = 3):\n    print(a + b)\n\n\n@cli.cmd\n@cli.arg('path', '-p', help='Destroy where?')\ndef subtract(path: Path):\n    run(f'rm -rf {path}')\n\n\n@cli.main\ndef main():\n    print(f\"{add(1, 2)} and {subtract('/uhoh')}\")\n\n\nif __name__ == '__main__':\n    cli.run() \n```\n\n- **No dependencies.** This library has no dependencies and is a single file.\n  You wanna vendor it? Vendor it.\n\n- **Short implementation.** Take 10 minutes, skim the implementation, convince\n  yourself I'm not exfiltrating your `id_rsa`, then vendor this puppy and never\n  think about anything again.\n\n- **Nothing to learn.** if you know how to use Python function annotations, you\n  already know 98% of this library. \n\n- **Optimized for the common case.** Check out `test_bad_git.py`. I know what\n  you want to do (create a subpar reproduction of git), and I've made it\n  concise. \n\n- **Functions can still be used as plain old functions.** You can still call\n  the clii-decorated functions regularly with no special ceremony. The\n  decorators are only used to construct a parser.\n\n---\n\nOkay, you and I both know the last thing that anyone needs is another way to\ngenerate command line interfaces. The idea of adding an additional dependency\nto your project just so you can learn yet another\nonly-slightly-more-ergonomic-than-stdlib interface for parsing args is right up\nthere with rewriting all your Makefiles in whatever flavor-of-the-week\nJavascript-based build system. I get it.\n\nYes, instead of writing this library I should probably do something actually\nuseful like try to find a life partner or see how much grain alcohol I can\ndrink within the span of an X-Files episode, but each time I'm typing out some\noverly verbose `argparse` incantation that I had to look up on docs.python.org\nfor the sixteenth time in a year, one of the few remaining shreds of childlike\nwonder for computing left in my over-caffeinated heart gets crosslegged and\nsets itself on fire.\n\n[Click](https://click.palletsprojects.com/en/7.x/) is the equivalent of calling\nin an architect to fix your kitchen sink. It's a lot of code and the interface\nis wordy and unintuitive. [Docopt](https://github.com/docopt/docopt) is neat\nbut it's slow, a novelty, also a ton of code, and I have to read 3 examples\neach time before I use it.\n[Argparse](https://docs.python.org/3/library/argparse.html) is an alright\nbuiltin, and the noble progenitor of this library, but it's overly verbose and\nthe common task of wiring up subparsers that call functions is a pain.\n\n**Don't immolate your childlike wonder. Use function annotations. Use this\nstupid library.**\n\n---\n\n## Installation\n\n```sh\n# Requires Python \u003e=3.7\npython3 -m pip install --user clii\n```\n\n## Substantial usage example\n\n```python\n#!/usr/bin/env python3.8\n\"\"\"\nA really lame version of git.\n\"\"\"\n\nfrom pathlib import Path\nimport typing as t\n\nfrom clii import App\n\n\ncli = App(description=__doc__)\ncli.add_arg('--verbose', '-v', action='store_true', default=False)\n\n\n@cli.cmd\ndef clone(url: str, target: Path, branch: t.Optional[str] = None):\n    \"\"\"Clone the branch so you can melt your computer.\"\"\"\n    branch = f' -b {branch}' if branch else ''\n\n    # We can reference global args since all parsed arguments are attached\n    # to `cli.args` after parsing.\n    if cli.args.verbose:\n        print(f'git clone{branch} {url} {target}')\n\n\n@cli.cmd\ndef push(remote: str, branch: str, force: bool = False):\n    force_flag = ' -f' if force else ''\n\n    if cli.args.verbose:\n        print(f'git push{force_flag} {remote} {branch}')\n\n\n@cli.cmd\n@cli.arg('all', '-a')  # add a short flag, or any other argparse setting\n@cli.arg('message', '-m')  \ndef commit(all: bool = False, message: str = ''):\n    # Arguments are --all, -a and --message, -m\n    print(all)\n    print(message)\n\n\n@cli.cmd\n@cli.arg('updated', '-u')  \ndef add(*files, updated: bool = False):\n    # `files` will be a variadic positional arg, while --updated/-u is a bool\n    # flag.\n    if cli.args.verbose:\n        print(f\"adding files: {files}\")\n\n\n\nif __name__ == '__main__':\n    cli.run() \n```\n\nwhich then gets you\n\n```\n% ./test_bad_git.py --help\nusage: test_bad_git.py [-h] [--verbose] {clone,push,commit,add} ...\n\nA really lame version of git.\n\npositional arguments:\n  {clone,push,commit,add}\n\noptional arguments:\n  -h, --help            show this help message and exit\n  --verbose, -v\n\n% ./test_bad_git.py clone --help\nusage: test_bad_git.py clone [-h] [--branch BRANCH] url target\n\nClone the branch so you can melt your computer.\n\npositional arguments:\n  url\n  target\n\noptional arguments:\n  -h, --help       show this help message and exit\n  --branch BRANCH  default: None\n```\n\n## Usage notes\n\n### Add a main command with `@App.main`\n\nDecorating any function with the `@cli.main` decorator means that function\nis the default subparser chosen if no function argument is given.\n\n\n### Add `add_argument` arguments with `@cli.arg(...)`\n\nYou can add arbitrary `ArgumentParser.add_argument(...)` args for a given\nparameter by using the `@cli.arg('paramname', \u003cadditional args\u003e...)` \ndecorator.\n\nThis decorator must be applied on lines *after* the `@cli.cmd` decorator.\n\n### Help text from docstrings\n\nclii will pull argument help text from docstrings that are formatted like so:\n```python\nimport clii\ncli = clii.App()\n\n@cli.cmd\ndef foo(bar: str):\n    \"\"\"\n    Args:\n      bar: some kind of helpful docstring.\n    \"\"\"\n\ncli.run()\n```\n\nSpecifically, the docstring is searched for \"  [parameter name]:\" - if that \npattern is found, the contents after the colon are used as help text.\n\n\n### `store_true` and `store_false` inference\n\nArguments that are declared type bool and given a default value are inferred\nas being `store_true` or `store_false` depending upon their default value;\nit is inferred that if the flag is given on the commandline, the reverse of\nthe default is desired.\n\nFor example, in\n\n```python\n@cli.cmd\ndef commit(force: bool = False):\n    ...\n```\n\nif `--force` is given, the function will be called with `force=True`, but\notherwise `force` will stay False.\n\n---\n\nIf you like using this library, consider sending me some magic internet money:\n\n```\nbc1qlj36t63qlgkywg83gwslcu3ehl76h2k2d6hcv2\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesob%2Fclii","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesob%2Fclii","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesob%2Fclii/lists"}