{"id":16406875,"url":"https://github.com/terror/arrg","last_synced_at":"2025-07-10T17:04:35.393Z","repository":{"id":107011376,"uuid":"454092291","full_name":"terror/arrg","owner":"terror","description":"A Python library for building modular command-line applications","archived":false,"fork":false,"pushed_at":"2025-04-20T06:17:52.000Z","size":90,"stargazers_count":16,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-06T23:52:12.152Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc0-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/terror.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-01-31T16:56:40.000Z","updated_at":"2025-04-21T15:01:37.000Z","dependencies_parsed_at":null,"dependency_job_id":"9685750a-3a6f-49c1-bce3-7efddd034c0c","html_url":"https://github.com/terror/arrg","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/terror/arrg","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/terror%2Farrg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/terror%2Farrg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/terror%2Farrg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/terror%2Farrg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/terror","download_url":"https://codeload.github.com/terror/arrg/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/terror%2Farrg/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264614134,"owners_count":23637521,"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":[],"created_at":"2024-10-11T06:11:12.825Z","updated_at":"2025-07-10T17:04:35.368Z","avatar_url":"https://github.com/terror.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"## arrg\n\n[![pypi](https://img.shields.io/pypi/v/arrg.svg)](https://pypi.org/project/arrg/)\n[![ci](https://github.com/terror/arrg/actions/workflows/ci.yml/badge.svg)](https://github.com/terror/arrg/actions/workflows/ci.yml)\n[![downloads](https://img.shields.io/pypi/dm/arrg.svg)](https://pypi.org/project/arrg/)\n\n\u003cdiv align='left' style='margin: 20px 0 20px 0'\u003e\n \u003cimg width='10%' src='https://oldschool.runescape.wiki/images/Arrg.png?2e0cb'/\u003e\n\u003c/div\u003e\n\n**arrg** is a Python library for building modular command-line applications using\na declarative, class-based approach. It leverages Python type hints and decorators\nto simplify the creation of complex command-line interfaces with arguments and\nsubcommands, while maintaining compatibility with the standard [argparse](https://docs.python.org/3/library/argparse.html)\nlibrary.\n\n## Installation\n\nInstall the package via the Python package manager [pip](https://pip.pypa.io/en/stable/installation/):\n\n```bash\npip install arrg\n```\n\nAlternatively, if you use [uv](https://docs.astral.sh/uv/), add it to your\nproject:\n\n```bash\nuv add arrg\n```\n\n## Quick Start\n\nHere's a simple example demonstrating the `app` decorator:\n\n```python\nfrom arrg import app, argument\n\n@app(description=\"A wonderful command-line interface\")\nclass Arguments:\n  input: str = argument()\n\n  def run(self):\n    print(self.input)\n\nif __name__ == '__main__':\n  Arguments.from_args().run()\n```\n\nThe `input` field defaults to a positional argument with the name `input` (the\nfield name). Assuming this code lives in a file called `main.py`, running it\nwith `python3 main.py hello` will print `hello`.\n\n## Features\n\n### Arguments\n\nIn **arrg**, arguments are defined using the `argument` function on class fields\nwithin a class decorated with `@app` or `@subcommand`. This function mirrors the\n[add_argument](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument)\nmethod of a [argparse.ArgumentParser](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser),\nsupporting all the familiar parameters like `action`, `nargs`, `type`,\n`choices`, `default`, `help`, and more.\n\nArguments can be `positional` or `optional`:\n\n```python\nfrom arrg import app, argument\n\n@app\nclass Arguments:\n  input: str = argument()\n```\n\nThe argument `input` here will default as a positional argument with the name\n`input` (the field name). Since we're using argparse under the hood, positional\nand optional arguments are differentiated by name.\n\nHere is another example defining an argument `input` as an option with a type\nand a default value.\n\n```python\nfrom arrg import app, argument\n\n@app\nclass Arguments:\n  input: str = argument('--input', type=str, default='foo')\n\nif __name__ == '__main__':\n  arguments = Argument.from_args()\n  ...\n```\n\nNow you can pass in a `--input` option to your program and have substituted on\nyour app instance.\n\n### Subcommands\n\nSubcommands enable hierarchical command-line interface structures (e.g. `git add`,\n`git commit`). They are defined using the `@subcommand` decorator and integrated\nas fields in an `@app` class.\n\nHere is a basic example:\n\n```python\nfrom arrg import subcommand\n\n@subcommand\nclass Add:\n  numbers: list[float] = argument('--numbers', help='Numbers to add together')\n\n  def run(self):\n    print(sum(self.numbers))\n```\n\nIncorporating them into an existing app by adding them as a field looks like:\n\n```python\nfrom arrg import app, argument, subcommand\n\n@subcommand\nclass Add:\n  numbers: list[float] = argument('--numbers', help='Numbers to add together')\n\n  def run(self):\n    print(sum(self.numbers))\n\n@app(description='Simple calculator')\nclass Calculator:\n  add: Add\n\n  def run(self):\n    if self.add is not None:\n      self.add.run()\n\nif __name__ == '__main__':\n  Calculator.from_args().run()\n```\n\nYour program will now accept arguments like `add --numbers 1 2 3`.\n\nThis example is present in [examples/simple_subcommand.py](https://github.com/terror/arrg/blob/master/examples/simple_subcommand.py),\ntry it out!\n\n### App inheritance\n\nApps can inherit from other apps, combining their arguments and subcommands:\n\n```python\n@app\nclass A:\n  a: str = argument('--a')\n\n@app\nclass B(A):\n  b: str = argument('--b')\n\nif __name__ == '__main__':\n  arguments = B.from_args()\n  print(arguments.a + arguments.b)\n```\n\nThe fields `a` and `b` are accessible from `B`, so passing in `--a foo --b bar`\nwill yield `foobar`.\n\nSubcommands are also inherited:\n\n```python\n@subcommand\nclass C:\n  c: str = argument('--c')\n\n@app\nclass A:\n  a: str = argument('--a')\n  c: C\n\n@app\nclass B(A):\n  b: str = argument('--b')\n\nif __name__ == '__main__':\n  arguments = B.from_args()\n  print(arguments.a + arguments.b + arguments.c.c)\n```\n\nPassing in `--a foo --b bar c --c baz` will yield `foobarbaz`.\n\n### Subcommand inheritance\n\nLike apps, subcommands can also inherit from subcommands. This enables a more\nmodular design for subcommand structures, letting you easily share arguments and\nbehaviours:\n\n```python\n@subcommand\nclass Base:\n  quiet: bool = argument('-q', '--quiet', help='Suppress output')\n  verbose: bool = argument('-v', '--verbose', help='Enable verbose output')\n\n@subcommand\nclass Push(Base):\n  force: bool = argument('-f', '--force', help='Force push')\n\n@subcommand\nclass Status(Base):\n  all: bool = argument('-a', '--all', help='Show all statuses')\n```\n\nThe subcommands `Push` and `Status` inherit the options `--quiet` and `--verbose`\nfrom `Base`.\n\nNested subcommands can also benefit from inheritance:\n\n```python\n@subcommand\nclass Base:\n  quiet: bool = argument('-q', '--quiet', help='Suppress output')\n  verbose: bool = argument('-v', '--verbose', help='Enable verbose output')\n\n@subcommand\nclass Remote(Base):\n  name: str = argument('--name', default='origin')\n\n@app\nclass Git:\n  remote: Remote\n\nif __name__ == '__main__':\n  print(Git.from_args())\n```\n\nPassing in `remote origin --verbose` will yield `Git(remote=Remote(quiet=False, verbose=True, name='origin'))`.\n\n### Smart type conversion\n\n**arrg** automatically converts argument inputs to their annotated types,\nreducing the need to specify types manually. Supported types include:\n\n- Primitives: `int`, `float`, `str`, `bool`\n- Collections: `list`, `dict`, `tuple`, `set`\n- Optional/Union: `Optional[T]`, `Union[T1, T2, ...]`\n- Custom Types: `datetime.date`, `datetime.time`, `uuid.UUID`, `pathlib.Path`, `ipaddress.IPv4Address`, `ipaddress.IPv6Address`, `re.Pattern`\n- Enums and Literals: Custom `Enum` classes, `Literal['a', 'b']`\n\nFor instance, **arrg** will automatically resolve your union types:\n\n```python\n@app\nclass Arguments:\n  input: t.Union[int, str] = argument('--input')\n\n  def run(self):\n    print(f\"{self.input} ({type(self.input).__name__})\")\n\nif __name__ == '__main__':\n  Arguments.from_args().run()\n```\n\n- `--input 42` =\u003e `42 (int)`\n- `--input hello` =\u003e `hello (str)`\n\nIt will also handle your list types:\n\n```python\n@app\nclass Arguments:\n  numbers: list[int] = argument('--numbers')\n\nif __name__ == '__main__':\n  print(Arguments.from_args())\n```\n\nPassing in `--numbers 1 2 3` will yield `Arguments(numbers=[1, 2, 3])`.\n\nOf course, you can opt out of these smart type conversion features by specifying\nthe `type` for arguments yourself.\n\n### Argparse API compatibility\n\n**arrg** aligns with the [argparse](https://docs.python.org/3/library/argparse.html)\nAPI for familiarity and interoperability.\n\nAs mentioned before, the `argument` accepts `add_argument` parameters on an\n`argparse.ArgumentParser` instance:\n\n```python\n@app\nclass Arguments:\n  verbose: bool = argument('--verbose', action='store_true', help='Verbose output')\n```\n\nMoreover, the `@app` decorator accepts `argparse.ArgumentParser` parameters:\n\n```python\n@app(description='My app', epilog='More info', prog='mycli')\nclass Arguments:\n  pass\n```\n\nRunning `--help` will display the custom description and epilog.\n\nThe `@subcommand` decorator supports similar options:\n\n```python\n@subcommand(name='pr', help='Create pull request', description='Detailed PR creation')\nclass PullRequest:\n  title: str = argument('--title')\n```\n\nThese get added to their respective subparser instances.\n\n## Prior Art\n\nThis library is heavily indebted to the rust crate [structopt](https://docs.rs/structopt/latest/structopt/),\nfor which heavy inspiration was drawn from.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fterror%2Farrg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fterror%2Farrg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fterror%2Farrg/lists"}