{"id":15521350,"url":"https://github.com/andrewrosss/multicommand","last_synced_at":"2025-04-23T04:12:43.253Z","repository":{"id":42924158,"uuid":"353583013","full_name":"andrewrosss/multicommand","owner":"andrewrosss","description":"Simple subcommand CLIs with argparse","archived":false,"fork":false,"pushed_at":"2022-06-28T05:32:34.000Z","size":247,"stargazers_count":10,"open_issues_count":0,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-23T04:12:37.907Z","etag":null,"topics":["argparse","cli","nested-commands","no-dependencies","plac","python","python3","subcommands","subparsers"],"latest_commit_sha":null,"homepage":"multicommand.vercel.app","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/andrewrosss.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2021-04-01T05:20:05.000Z","updated_at":"2022-08-01T21:57:04.000Z","dependencies_parsed_at":"2022-09-10T20:22:16.738Z","dependency_job_id":null,"html_url":"https://github.com/andrewrosss/multicommand","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewrosss%2Fmulticommand","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewrosss%2Fmulticommand/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewrosss%2Fmulticommand/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewrosss%2Fmulticommand/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andrewrosss","download_url":"https://codeload.github.com/andrewrosss/multicommand/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250366718,"owners_count":21418772,"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":["argparse","cli","nested-commands","no-dependencies","plac","python","python3","subcommands","subparsers"],"created_at":"2024-10-02T10:34:02.239Z","updated_at":"2025-04-23T04:12:43.226Z","avatar_url":"https://github.com/andrewrosss.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# multicommand\n\nSimple subcommand CLIs with argparse.\n\n[![PyPI Version](https://img.shields.io/pypi/v/multicommand.svg)](https://pypi.org/project/multicommand/) [![Downloads](https://static.pepy.tech/personalized-badge/multicommand?period=month\u0026units=international_system\u0026left_color=grey\u0026right_color=brightgreen\u0026left_text=downloads%20/%20month)](https://pepy.tech/project/multicommand)\n\n`multicommand` uses only the standard library and is ~100 lines of code (modulo comments and whitespace)\n\n## Installation\n\n```bash\npip install multicommand\n```\n\n## Overview\n\nMulticommand enables you to easily write CLIs with deeply nested commands using vanilla argparse. You provide it with a package, it searches that package for parsers (`ArgumentParser` objects), and connects, names, and converts those parsers into subcommands based on the package structure.\n\n```text\n         Modules               -\u003e          CLI\n\n\ncommands/unary/__init__.py           mycli unary ...\ncommands/unary/negate.py             mycli unary negate ...\ncommands/binary/__init__.py          mycli binary ...\ncommands/binary/add.py         -\u003e    mycli binary add ...\ncommands/binary/divide.py            mycli binary divide ...\ncommands/binary/multiply.py          mycli binary multiply ...\ncommands/binary/subtract.py          mycli binary subtract ...\n```\n\nAll it needs is for each module to define a module-level `parser` variable which points to an instance of `argparse.ArgumentParser`.\n\n## Motivation\n\nI like `argparse`. It's flexible, full-featured and it's part of the standard library, so if you have Python you probably have argparse. I also like the \"subcommand\" pattern, i.e. one root command that acts as an entrypoint and subcommands to group related functionality. Of course, argparse can handle adding subcommands to parsers, but it's always felt a bit cumbersome, especially when there are many subcommands with lots of nesting.\n\nIf you've ever worked with technologies like `Next.js` or `oclif` (or even if you haven't) there's a duality between files and \"objects\". For Next.js each file under `pages/` maps to a webpage, in oclif each module under `commands/` maps to a CLI command. And that's the basic premise for multicommand: A light-weight package that lets you write one parser per file, pretty much in isolation, and it handles the wiring, exploiting the duality between command structure and file system structure.\n\n## Getting Started\n\nSee the [simple example](https://github.com/andrewrosss/multicommand/tree/master/examples/01_simple), or for the impatient:\n\nCreate a directory to work in, for example:\n\n```bash\nmkdir ~/multicommand-sample \u0026\u0026 cd ~/multicommand-sample\n```\n\nInstall `multicommand`:\n\n```bash\npython3 -m venv ./venv\nsource ./venv/bin/activate\n\npython3 -m pip install multicommand\n```\n\nCreate the subpackage to house our parsers:\n\n```bash\nmkdir -p mypkg/parsers/topic/cmd/subcmd\n```\n\nCreate the `*.py` files required for the directories to be packages\n\n```bash\ntouch mypkg/__init__.py\ntouch mypkg/parsers/__init__.py\ntouch mypkg/parsers/topic/__init__.py\ntouch mypkg/parsers/topic/cmd/__init__.py\ntouch mypkg/parsers/topic/cmd/subcmd/{__init__.py,greet.py}\n```\n\nAdd a `parser` to `greet.py`:\n\n```python\n# file: mypkg/parsers/topic/cmd/subcmd/greet.py\nimport argparse\n\n\ndef handler(args):\n    greeting = f'Hello, {args.name}!'\n    print(greeting.upper() if args.shout else greeting)\n\n\nparser = argparse.ArgumentParser(\n    description='My first CLI with multicommand',\n    formatter_class=argparse.ArgumentDefaultsHelpFormatter\n)\nparser.add_argument('name', help='Name to use in greeting')\nparser.add_argument('--shout', action='store_true', help='Yell the greeting')\nparser.set_defaults(handler=handler)\n```\n\nlastly, add an entrypoint:\n\n```bash\ntouch mypkg/cli.py\n```\n\nwith the following content:\n\n```python\n# file: mypkg/cli.py\nimport multicommand\nfrom . import parsers\n\n\ndef main():\n    parser = multicommand.create_parser(parsers)\n    args = parser.parse_args()\n    if hasattr(args, 'handler'):\n        args.handler(args)\n        return\n    parser.print_help()\n\n\nif __name__ == \"__main__\":\n    exit(main())\n```\n\nTry it out!\n\n```bash\n$ python3 -m mypkg.cli\nusage: cli.py [-h] {topic} ...\n\noptional arguments:\n  -h, --help  show this help message and exit\n\nsubcommands:\n\n  {topic}\n```\n\nTake a look at our `greet` command:\n\n```bash\n$ python3 -m mypkg.cli topic cmd subcmd greet --help\nusage: cli.py topic cmd subcmd greet [-h] [--shout] name\n\nMy first CLI with multicommand\n\npositional arguments:\n  name        Name to use in greeting\n\noptional arguments:\n  -h, --help  show this help message and exit\n  --shout     Yell the greeting (default: False)\n```\n\nFrom this we get:\n\n```bash\n$ python3 -m mypkg.cli topic cmd subcmd greet \"World\"\nHello, World!\n\n$ python3 -m mypkg.cli topic cmd subcmd greet --shout \"World\"\nHELLO, WORLD!\n```\n\n### Bonus\n\nWant to add the command `topic cmd ungreet ...` to say goodbye?\n\nAdd the module:\n\n```bash\ntouch mypkg/parsers/topic/cmd/ungreet.py\n```\n\nwith contents:\n\n```python\n# file: mypkg/parsers/topic/cmd/ungreet.py\nimport argparse\n\n\ndef handler(args):\n    print(f'Goodbye, {args.name}!')\n\n\nparser = argparse.ArgumentParser(description='Another subcommand with multicommand')\nparser.add_argument('name', help='Name to use in un-greeting')\nparser.set_defaults(handler=handler)\n```\n\nThe new command is automatically added!:\n\n```bash\n$ python3 -m mypkg.cli topic cmd --help\nusage: cli.py cmd [-h] {subcmd,ungreet} ...\n\noptional arguments:\n  -h, --help        show this help message and exit\n\nsubcommands:\n\n  {subcmd,ungreet}\n```\n\nTry it out:\n\n```bash\n$ python3 -m mypkg.cli topic cmd ungreet \"World\"\nGoodbye, World!\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrewrosss%2Fmulticommand","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandrewrosss%2Fmulticommand","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrewrosss%2Fmulticommand/lists"}