{"id":13493495,"url":"https://github.com/swansonk14/typed-argument-parser","last_synced_at":"2026-02-21T06:33:12.915Z","repository":{"id":35076014,"uuid":"202804431","full_name":"swansonk14/typed-argument-parser","owner":"swansonk14","description":"Typed argument parser for Python","archived":false,"fork":false,"pushed_at":"2026-01-25T22:36:58.000Z","size":814,"stargazers_count":617,"open_issues_count":33,"forks_count":47,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-01-26T13:59:05.882Z","etag":null,"topics":["argument-parser","argument-parsing","python","python3","types","typing"],"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/swansonk14.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-08-16T22:01:11.000Z","updated_at":"2026-01-25T22:37:01.000Z","dependencies_parsed_at":"2022-09-25T03:24:57.318Z","dependency_job_id":"5e717526-bce1-42f2-8893-e8dc299e6a51","html_url":"https://github.com/swansonk14/typed-argument-parser","commit_stats":{"total_commits":305,"total_committers":21,"mean_commits":"14.523809523809524","dds":0.6,"last_synced_commit":"eb55a9bf91b4675a0d8588b2a8f4947e48426336"},"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"purl":"pkg:github/swansonk14/typed-argument-parser","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swansonk14%2Ftyped-argument-parser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swansonk14%2Ftyped-argument-parser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swansonk14%2Ftyped-argument-parser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swansonk14%2Ftyped-argument-parser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/swansonk14","download_url":"https://codeload.github.com/swansonk14/typed-argument-parser/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swansonk14%2Ftyped-argument-parser/sbom","scorecard":{"id":861596,"data":{"date":"2025-08-11","repo":{"name":"github.com/swansonk14/typed-argument-parser","commit":"043490c2845f0aa1c6f0fba0c4964a32cef8b03c"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.1,"checks":[{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":4,"reason":"5 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 4","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":3,"reason":"Found 6/19 approved changesets -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/tests.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/swansonk14/typed-argument-parser/tests.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/swansonk14/typed-argument-parser/tests.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/tests.yml:58: update your workflow using https://app.stepsecurity.io/secureworkflow/swansonk14/typed-argument-parser/tests.yml/main?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/tests.yml:37","Warn: pipCommand not pinned by hash: .github/workflows/tests.yml:38","Warn: pipCommand not pinned by hash: .github/workflows/tests.yml:50","Warn: pipCommand not pinned by hash: .github/workflows/tests.yml:54","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   0 out of   4 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 17 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-24T01:27:39.086Z","repository_id":35076014,"created_at":"2025-08-24T01:27:39.086Z","updated_at":"2025-08-24T01:27:39.086Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29675471,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T06:23:40.028Z","status":"ssl_error","status_checked_at":"2026-02-21T06:23:39.222Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["argument-parser","argument-parsing","python","python3","types","typing"],"created_at":"2024-07-31T19:01:15.867Z","updated_at":"2026-02-21T06:33:12.899Z","avatar_url":"https://github.com/swansonk14.png","language":"Python","funding_links":[],"categories":["Python","[Python](https://www.python.org/)"],"sub_categories":["Useful awesome list for Go cli"],"readme":"\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://raw.githubusercontent.com/swansonk14/typed-argument-parser/refs/tags/v_1.11.0/images/logo.png\" width=\"40%\" align=\"middle\"\u003e\n\u003c/p\u003e\n\n# Typed Argument Parser (Tap)\n\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/typed-argument-parser)](https://badge.fury.io/py/typed-argument-parser)\n[![PyPI version](https://badge.fury.io/py/typed-argument-parser.svg)](https://badge.fury.io/py/typed-argument-parser)\n[![Downloads](https://pepy.tech/badge/typed-argument-parser)](https://pepy.tech/project/typed-argument-parser)\n[![Build Status](https://github.com/swansonk14/typed-argument-parser/workflows/tests/badge.svg)](https://github.com/swansonk14/typed-argument-parser)\n[![codecov](https://codecov.io/gh/swansonk14/typed-argument-parser/branch/main/graph/badge.svg)](https://codecov.io/gh/swansonk14/typed-argument-parser)\n[![license](https://img.shields.io/github/license/swansonk14/typed-argument-parser.svg)](https://github.com/swansonk14/typed-argument-parser/blob/main/LICENSE.txt)\n\nTap is a typed modernization of Python's [argparse](https://docs.python.org/3/library/argparse.html) library.\n\nTap provides the following benefits:\n- Static type checking\n- Code completion\n- Source code navigation (e.g. go to definition and go to implementation)\n\n![Tap](https://github.com/swansonk14/typed-argument-parser/raw/main/images/tap.png)\n\nSee [this poster](https://docs.google.com/presentation/d/1AirN6gpiq4P1L8K003EsXmobVxP3A4AVEIR2KOEQN7Y/edit?usp=sharing), which we presented at [PyCon 2020](https://us.pycon.org/2020/), for a presentation of some of the relevant concepts we used to guide the development of Tap. \n\nAs of version 1.8.0, Tap includes `tapify`, which runs functions or initializes classes with arguments parsed from the command line. We show an example below.\n\n```python\n# square.py\nfrom tap import tapify\n\ndef square(num: float) -\u003e float:\n    return num ** 2\n\nif __name__ == '__main__':\n    print(f'The square of your number is {tapify(square)}.')\n```\n\nRunning `python square.py --num 2` will print `The square of your number is 4.0.`. Please see [tapify](#tapify) for more details.\n\n## Installation\n\nTap requires Python 3.10+\n\nTo install Tap from PyPI run: \n\n```\npip install typed-argument-parser\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eTo install Tap from source, run the following commands:\u003c/summary\u003e\n\n```\ngit clone https://github.com/swansonk14/typed-argument-parser.git\ncd typed-argument-parser\npip install -e .\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eTo develop this package, install development requirements (in a virtual environment):\u003c/summary\u003e\n\n```\npython -m pip install -e \".[dev]\"\n```\n\nUse [`flake8`](https://github.com/PyCQA/flake8) linting.\n\nTo run tests, run:\n\n```\npytest\n```\n\n\u003c/details\u003e\n\n## Table of Contents\n\n* [Installation](#installation)\n* [Table of Contents](#table-of-contents)\n* [Tap is Python-native](#tap-is-python-native)\n* [Tap features](#tap-features)\n  + [Arguments](#arguments)\n  + [Tap help](#tap-help)\n  + [Configuring arguments](#configuring-arguments)\n    - [Adding special argument behavior](#adding-special-argument-behavior)\n    - [Adding subparsers](#adding-subparsers)\n  + [Types](#types)\n  + [Argument processing](#argument-processing)\n  + [Processing known args](#processing-known-args)\n  + [Subclassing](#subclassing)\n  + [Printing](#printing)\n  + [Reproducibility](#reproducibility)\n  + [Saving and loading arguments](#saving-and-loading-arguments)\n  + [Loading from configuration files](#loading-from-configuration-files)\n* [tapify](#tapify)\n  + [Examples](#examples)\n    - [Function](#function)\n    - [Class](#class)\n    - [Dataclass](#dataclass)\n  + [tapify help](#tapify-help)\n  + [Command line vs explicit arguments](#command-line-vs-explicit-arguments)\n  + [Known args](#known-args)\n* [Convert to a `Tap` class](#convert-to-a-tap-class)\n  + [`to_tap_class` examples](#to_tap_class-examples)\n    - [Simple](#simple)\n    - [Complex](#complex)\n\n## Tap is Python-native\n\nTo see this, let's look at an example:\n\n```python\n\"\"\"main.py\"\"\"\n\nfrom tap import Tap\n\nclass SimpleArgumentParser(Tap):\n    name: str  # Your name\n    language: str = 'Python'  # Programming language\n    package: str = 'Tap'  # Package name\n    stars: int  # Number of stars\n    max_stars: int = 5  # Maximum stars\n\nargs = SimpleArgumentParser().parse_args()\n\nprint(f'My name is {args.name} and I give the {args.language} package '\n      f'{args.package} {args.stars}/{args.max_stars} stars!')\n```\n\nYou use Tap the same way you use standard argparse.\n\n```\n\u003e\u003e\u003e python main.py --name Jesse --stars 5\nMy name is Jesse and I give the Python package Tap 5/5 stars!\n```\n\nThe equivalent argparse code is:\n```python\n\"\"\"main.py\"\"\"\n\nfrom argparse import ArgumentParser\n\nparser = ArgumentParser()\nparser.add_argument('--name', type=str, required=True,\n                    help='Your name')\nparser.add_argument('--language', type=str, default='Python',\n                    help='Programming language')\nparser.add_argument('--package', type=str, default='Tap',\n                    help='Package name')\nparser.add_argument('--stars', type=int, required=True,\n                    help='Number of stars')\nparser.add_argument('--max_stars', type=int, default=5,\n                    help='Maximum stars')\nargs = parser.parse_args()\n\nprint(f'My name is {args.name} and I give the {args.language} package '\n      f'{args.package} {args.stars}/{args.max_stars} stars!')\n```\n\nThe advantages of being Python-native include being able to:\n- Overwrite convenient built-in methods (e.g. `process_args` ensures consistency among arguments)\n- Add custom methods\n- Inherit from your own template classes\n\n## Tap features\n\nNow we are going to highlight some of our favorite features and give examples of how they work in practice.\n\n### Arguments\n\nArguments are specified as class variables defined in a subclass of `Tap`. Variables defined as `name: type` are required arguments while variables defined as `name: type = value` are not required and default to the provided value.\n\n```python\nclass MyTap(Tap):\n    required_arg: str\n    default_arg: str = 'default value'\n```\n\n### Tap help\n\nSingle line and/or multiline comments which appear after the argument are automatically parsed into the help string provided when running `python main.py -h`. The type and default values of arguments are also provided in the help string.\n\n```python\n\"\"\"main.py\"\"\"\n\nfrom tap import Tap\n\nclass MyTap(Tap):\n    x: float  # What am I?\n    pi: float = 3.14  # I'm pi!\n    \"\"\"Pi is my favorite number!\"\"\"\n\nargs = MyTap().parse_args()\n```\n\nRunning `python main.py -h` results in the following:\n\n```\n\u003e\u003e\u003e python main.py -h\nusage: main.py --x X [--pi PI] [-h]\n\noptional arguments:\n  --x X       (float, required) What am I?\n  --pi PI     (float, default=3.14) I'm pi! Pi is my favorite number.\n  -h, --help  show this help message and exit\n```\n\n### Configuring arguments\nTo specify behavior beyond what can be specified using arguments as class variables, override the `configure` method.\n`configure` provides access to advanced argument parsing features such as `add_argument` and `add_subparser`.\nSince Tap is a wrapper around argparse, Tap provides all of the same functionality.\nWe detail these two functions below.\n\n#### Adding special argument behavior\nIn the `configure` method, call `self.add_argument` just as you would use argparse's `add_argument`. For example,\n\n```python\nfrom tap import Tap\n\nclass MyTap(Tap):\n    positional_argument: str\n    list_of_three_things: List[str]\n    argument_with_really_long_name: int\n\n    def configure(self):\n        self.add_argument('positional_argument')\n        self.add_argument('--list_of_three_things', nargs=3)\n        self.add_argument('-arg', '--argument_with_really_long_name')\n```\n\n#### Adding subparsers\nTo add a subparser, override the `configure` method and call `self.add_subparser`. Optionally, to specify keyword arguments (e.g., `help`) to the subparser collection, call `self.add_subparsers`. For example,\n\n```python\nclass SubparserA(Tap):\n    bar: int  # bar help\n\nclass SubparserB(Tap):\n    baz: Literal['X', 'Y', 'Z']  # baz help\n\nclass Args(Tap):\n    foo: bool = False  # foo help\n\n    def configure(self):\n        self.add_subparsers(help='sub-command help')\n        self.add_subparser('a', SubparserA, help='a help')\n        self.add_subparser('b', SubparserB, help='b help')\n```\n\n### Types\n\nTap automatically handles all the following types:\n\n```python\nstr, int, float, bool\nOptional, Optional[str], Optional[int], Optional[float], Optional[bool]\nlist, list[str], list[int], list[float], list[bool]\nset, set[str], set[int], set[float], set[bool]\ntuple, tuple[Type1, Type2, etc.], tuple[Type, ...]\nLiteral\n```\n\nTap also supports `Union`, but this requires additional specification (see [Union](#-union-) section below).\n\nAdditionally, any type that can be instantiated with a string argument can be used. For example, in\n```python\nfrom pathlib import Path\nfrom tap import Tap\n\nclass Args(Tap):\n   path: Path\n\nargs = Args().parse_args()\n```\n`args.path` is a `Path` instance containing the string passed in through the command line.\n\n#### `str`, `int`, and `float`\n\nEach is automatically parsed to their respective types, just like argparse.\n\n#### `bool`\n\nIf an argument `arg` is specified as `arg: bool` or `arg: bool = False`, then adding the `--arg` flag to the command line will set `arg` to `True`. If `arg` is specified as `arg: bool = True`, then adding `--arg` sets `arg` to `False`.\n\nNote that if the `Tap` instance is created with `explicit_bool=True`, then booleans can be specified on the command line as `--arg True` or `--arg False` rather than `--arg`. Additionally, booleans can be specified by prefixes of `True` and `False` with any capitalization as well as `1` or `0` (e.g. for True, `--arg tRu`, `--arg T`, `--arg 1` all suffice). \n\n#### `Optional`\n\nThese arguments are parsed in exactly the same way as `str`, `int`, `float`, and `bool`. Note bools can be specified using the same rules as above and that `Optional` is equivalent to `Optional[str]`.\n\n#### `List`\n\nIf an argument `arg` is a `List`, simply specify the values separated by spaces just as you would with regular argparse. For example, `--arg 1 2 3` parses to `arg = [1, 2, 3]`.\n\n#### `Set`\n\nIdentical to `List` but parsed into a set rather than a list.\n\n#### `Tuple`\n\nTuples can be used to specify a fixed number of arguments with specified types using the syntax `Tuple[Type1, Type2, etc.]` (e.g. `Tuple[str, int, bool, str]`). Tuples with a variable number of arguments are specified by `Tuple[Type, ...]` (e.g. `Tuple[int, ...]`). Note `Tuple` defaults to `Tuple[str, ...]`.\n\n#### `Literal`\n\nLiteral is analagous to argparse's [choices](https://docs.python.org/3/library/argparse.html#choices), which specifies the values that an argument can take. For example, if arg can only be one of 'H', 1, False, or 1.0078 then you would specify that `arg: Literal['H', 1, False, 1.0078]`. For instance, `--arg False` assigns arg to False and `--arg True` throws error.\n\n#### `Union`\n\nUnion types must include the `type` keyword argument in `add_argument` in order to specify which type to use, as in the example below.\n\n```python\ndef to_number(string: str) -\u003e Union[float, int]:\n    return float(string) if '.' in string else int(string)\n\nclass MyTap(Tap):\n    number: Union[float, int]\n\n    def configure(self):\n        self.add_argument('--number', type=to_number)\n```\n\nIn Python 3.10+, `Union[Type1, Type2, etc.]` can be replaced with `Type1 | Type2 | etc.`, but the `type` keyword argument must still be provided in `add_argument`.\n\n#### Complex Types\n\nTap can also support more complex types than the ones specified above. If the desired type is constructed with a single string as input, then the type can be specified directly without additional modifications. For example,\n\n```python\nclass Person:\n    def __init__(self, name: str) -\u003e None:\n        self.name = name\n\nclass Args(Tap):\n    person: Person\n\nargs = Args().parse_args('--person Tapper'.split())\nprint(args.person.name)  # Tapper\n```\n\nIf the desired type has a more complex constructor, then the `type` keyword argument must be provided in `add_argument`. For example,\n\n```python\nclass AgedPerson:\n    def __init__(self, name: str, age: int) -\u003e None:\n        self.name = name\n        self.age = age\n\ndef to_aged_person(string: str) -\u003e AgedPerson:\n    name, age = string.split(',')\n    return AgedPerson(name=name, age=int(age))\n\nclass Args(Tap):\n    aged_person: AgedPerson\n\n    def configure(self) -\u003e None:\n        self.add_argument('--aged_person', type=to_aged_person)\n\nargs = Args().parse_args('--aged_person Tapper,27'.split())\nprint(f'{args.aged_person.name} is {args.aged_person.age}')  # Tapper is 27\n```\n\n\n### Argument processing\n\nWith complex argument parsing, arguments often end up having interdependencies. This means that it may be necessary to disallow certain combinations of arguments or to modify some arguments based on other arguments.\n\nTo handle such cases, simply override `process_args` and add the required logic. `process_args` is automatically called when `parse_args` is called.\n\n```python\nclass MyTap(Tap):\n    package: str\n    is_cool: bool\n    stars: int\n\n    def process_args(self):\n        # Validate arguments\n        if self.is_cool and self.stars \u003c 4:\n            raise ValueError('Cool packages cannot have fewer than 4 stars')\n\n        # Modify arguments\n        if self.package == 'Tap':\n            self.is_cool = True\n            self.stars = 5\n```\n\n### Processing known args\n\nSimilar to argparse's `parse_known_args`, Tap is capable of parsing only arguments that it is aware of without raising an error due to additional arguments. This can be done by calling `parse_args` with `known_only=True`. The remaining un-parsed arguments are then available by accessing the `extra_args` field of the Tap object.\n\n```python\nclass MyTap(Tap):\n    package: str\n\nargs = MyTap().parse_args(['--package', 'Tap', '--other_arg', 'value'], known_only=True)\nprint(args.extra_args)  # ['--other_arg', 'value']\n```\n\n### Subclassing\n\nIt is sometimes useful to define a template Tap and then subclass it for different use cases. Since Tap is a native Python class, inheritance is built-in, making it easy to customize from a template Tap.\n\nIn the example below, `StarsTap` and `AwardsTap` inherit the arguments (`package` and `is_cool`) and the methods (`process_args`) from `BaseTap`.\n\n```python\nclass BaseTap(Tap):\n    package: str\n    is_cool: bool\n\n    def process_args(self):\n        if self.package == 'Tap':\n            self.is_cool = True\n\n\nclass StarsTap(BaseTap):\n    stars: int\n\n\nclass AwardsTap(BaseTap):\n    awards: List[str]\n```\n\n### Printing\n\nTap uses Python's [pretty printer](https://docs.python.org/3/library/pprint.html) to print out arguments in an easy-to-read format.\n\n```python\n\"\"\"main.py\"\"\"\n\nfrom tap import Tap\nfrom typing import List\n\nclass MyTap(Tap):\n    package: str\n    is_cool: bool = True\n    awards: List[str] = ['amazing', 'wow', 'incredible', 'awesome']\n\nargs = MyTap().parse_args()\nprint(args)\n```\n\nRunning `python main.py --package Tap` results in:\n\n```\n\u003e\u003e\u003e python main.py\n{'awards': ['amazing', 'wow', 'incredible', 'awesome'],\n 'is_cool': True,\n 'package': 'Tap'}\n```\n\n### Reproducibility\n\nTap makes reproducibility easy, especially when running code in a git repo.\n\n#### Reproducibility info\n\nSpecifically, Tap has a method called `get_reproducibility_info` that returns a dictionary containing all the information necessary to replicate the settings under which the code was run. This dictionary includes:\n- Python command\n    - The Python command that was used to run the program\n    - Ex. `python main.py --package Tap`\n- Time\n    - The time when the command was run\n    - Ex. `Thu Aug 15 00:09:13 2019`\n- Git root\n    - The root of the git repo containing the code that was run\n    - Ex. `/Users/swansonk14/typed-argument-parser`\n- Git url\n    - The url to the git repo, specifically pointing to the current git hash (i.e. the hash of HEAD in the local repo)\n    - Ex. [https://github.com/swansonk14/typed-argument-parser/tree/446cf046631d6bdf7cab6daec93bf7a02ac00998](https://github.com/swansonk14/typed-argument-parser/tree/446cf046631d6bdf7cab6daec93bf7a02ac00998)\n- Uncommitted changes\n    - Whether there are any uncommitted changes in the git repo (i.e. whether the code is different from the code at the above git hash)\n    - Ex. `True` or `False`\n\n### Conversion Tap to and from dictionaries\n\nTap has methods `as_dict` and `from_dict` that convert Tap objects to and from dictionaries.\nFor example,\n\n```python\n\"\"\"main.py\"\"\"\nfrom tap import Tap\n\nclass Args(Tap):\n    package: str\n    is_cool: bool = True\n    stars: int = 5\n\nargs = Args().parse_args([\"--package\", \"Tap\"])\n\nargs_data = args.as_dict()\nprint(args_data)  # {'package': 'Tap', 'is_cool': True, 'stars': 5}\n\nargs_data['stars'] = 2000\nargs = args.from_dict(args_data)\nprint(args.stars)  # 2000 \n```\n\nNote that `as_dict` does not include attributes set directly on an instance (e.g., `arg` is not included even after setting `args.arg = \"hi\"` in the code above because `arg` is not an attribute of the `Args` class).\nAlso note that `from_dict` ensures that all required arguments are set.\n\n### Saving and loading arguments\n\n#### Save\n\nTap has a method called `save` which saves all arguments, along with the reproducibility info, to a JSON file.\n\n```python\n\"\"\"main.py\"\"\"\n\nfrom tap import Tap\n\nclass MyTap(Tap):\n    package: str\n    is_cool: bool = True\n    stars: int = 5\n\nargs = MyTap().parse_args()\nargs.save('args.json')\n```\n\nAfter running `python main.py --package Tap`, the file `args.json` will contain:\n\n```\n{\n    \"is_cool\": true,\n    \"package\": \"Tap\",\n    \"reproducibility\": {\n        \"command_line\": \"python main.py --package Tap\",\n        \"git_has_uncommitted_changes\": false,\n        \"git_root\": \"/Users/swansonk14/typed-argument-parser\",\n        \"git_url\": \"https://github.com/swansonk14/typed-argument-parser/tree/446cf046631d6bdf7cab6daec93bf7a02ac00998\",\n        \"time\": \"Thu Aug 15 00:18:31 2019\"\n    },\n    \"stars\": 5\n}\n```\n\nNote: More complex types will be encoded in JSON as a pickle string.\n\n#### Load\n\u003e :exclamation: :warning:\u003cbr/\u003e\n\u003e Never call `args.load('args.json')` on untrusted files. Argument loading uses the `pickle` module to decode complex types automatically. Unpickling of untrusted data is a security risk and can lead to arbitrary code execution. See [the warning in the pickle docs](https://docs.python.org/3/library/pickle.html).\u003cbr/\u003e\n\u003e :exclamation: :warning:\n\nArguments can be loaded from a JSON file rather than parsed from the command line.\n\n```python\n\"\"\"main.py\"\"\"\n\nfrom tap import Tap\n\nclass MyTap(Tap):\n    package: str\n    is_cool: bool = True\n    stars: int = 5\n\nargs = MyTap()\nargs.load('args.json')\n```\n\nNote: All required arguments (in this case `package`) must be present in the JSON file if not already set in the Tap object.\n\n#### Load from dict\n\nArguments can be loaded from a Python dictionary rather than parsed from the command line.\n\n```python\n\"\"\"main.py\"\"\"\n\nfrom tap import Tap\n\nclass MyTap(Tap):\n    package: str\n    is_cool: bool = True\n    stars: int = 5\n\nargs = MyTap()\nargs.from_dict({\n    'package': 'Tap',\n    'stars': 20\n})\n```\n\nNote: As with `load`, all required arguments must be present in the dictionary if not already set in the Tap object. All values in the provided dictionary will overwrite values currently in the Tap object.\n\n### Loading from configuration files\nConfiguration files can be loaded along with arguments with the optional flag `config_files: List[str]`. Arguments passed in from the command line overwrite arguments from the configuration files. Arguments in configuration files that appear later in the list overwrite the arguments in previous configuration files.\n\nFor example, if you have the config file `my_config.txt`\n```\n--arg1 1\n--arg2 two\n```\nthen you can write\n```python\nfrom tap import Tap\n\nclass Args(Tap):\n    arg1: int\n    arg2: str\n\nargs = Args(config_files=['my_config.txt']).parse_args()\n```\n\nConfig files are parsed using `shlex.split` from the python standard library, which supports shell-style string quoting, as well as line-end comments starting with `#`.\n\nFor example, if you have the config file `my_config_shlex.txt`\n```\n--arg1 21 # Important arg value\n\n# Multi-word quoted string\n--arg2 \"two three four\"\n```\nthen you can write\n```python\nfrom tap import Tap\n\nclass Args(Tap):\n    arg1: int\n    arg2: str\n\nargs = Args(config_files=['my_config_shlex.txt']).parse_args()\n```\nto get the resulting `args = {'arg1': 21, 'arg2': 'two three four'}`\n\nThe legacy parsing behavior of using standard string split can be re-enabled by passing `legacy_config_parsing=True` to `parse_args`.\n\n## tapify\n\n`tapify` makes it possible to run functions or initialize objects via command line arguments. This is inspired by Google's [Python Fire](https://github.com/google/python-fire), but `tapify` also automatically casts command line arguments to the appropriate types based on the type hints. Under the hood, `tapify` implicitly creates a Tap object and uses it to parse the command line arguments, which it then uses to run the function or initialize the class. We show a few examples below.\n\n### Examples\n\n#### Function\n\n```python\n# square_function.py\nfrom tap import tapify\n\ndef square(num: float) -\u003e float:\n    \"\"\"Square a number.\n\n    :param num: The number to square.\n    \"\"\"\n    return num ** 2\n\nif __name__ == '__main__':\n    squared = tapify(square)\n    print(f'The square of your number is {squared}.')\n```\n\nRunning `python square_function.py --num 5` prints `The square of your number is 25.0.`.\n\n#### Class\n\n```python\n# square_class.py\nfrom tap import tapify\n\nclass Squarer:\n    def __init__(self, num: float) -\u003e None:\n        \"\"\"Initialize the Squarer with a number to square.\n\n        :param  num: The number to square.\n        \"\"\"\n        self.num = num\n\n    def get_square(self) -\u003e float:\n        \"\"\"Get the square of the number.\"\"\"\n        return self.num ** 2\n\nif __name__ == '__main__':\n    squarer = tapify(Squarer)\n    print(f'The square of your number is {squarer.get_square()}.')\n```\n\nRunning `python square_class.py --num 2` prints `The square of your number is 4.0.`.\n\n#### Dataclass\n\n```python\n# square_dataclass.py\nfrom dataclasses import dataclass\n\nfrom tap import tapify\n\n@dataclass\nclass Squarer:\n    \"\"\"Squarer with a number to square.\n\n    :param num: The number to square.\n    \"\"\"\n    num: float\n\n    def get_square(self) -\u003e float:\n        \"\"\"Get the square of the number.\"\"\"\n        return self.num ** 2\n\nif __name__ == '__main__':\n    squarer = tapify(Squarer)\n    print(f'The square of your number is {squarer.get_square()}.')\n```\n\nRunning `python square_dataclass.py --num -1` prints `The square of your number is 1.0.`.\n\n\u003cdetails\u003e\n\u003csummary\u003eArgument descriptions\u003c/summary\u003e\n\nFor dataclasses, the argument's description (which is displayed in the `-h` help message) can either be specified in the\nclass docstring or the field's description in `metadata`. If both are specified, the description from the docstring is\nused. In the example below, the description is provided in `metadata`.\n\n```python\n# square_dataclass.py\nfrom dataclasses import dataclass, field\n\nfrom tap import tapify\n\n@dataclass\nclass Squarer:\n    \"\"\"Squarer with a number to square.\n    \"\"\"\n    num: float = field(metadata={\"description\": \"The number to square.\"})\n\n    def get_square(self) -\u003e float:\n        \"\"\"Get the square of the number.\"\"\"\n        return self.num ** 2\n\nif __name__ == '__main__':\n    squarer = tapify(Squarer)\n    print(f'The square of your number is {squarer.get_square()}.')\n```\n\n\u003c/details\u003e\n\n#### Pydantic\n\nPydantic [Models](https://docs.pydantic.dev/latest/concepts/models/) and\n[dataclasses](https://docs.pydantic.dev/latest/concepts/dataclasses/) can be `tapify`d.\n\n```python\n# square_pydantic.py\nfrom pydantic import BaseModel, Field\n\nfrom tap import tapify\n\nclass Squarer(BaseModel):\n    \"\"\"Squarer with a number to square.\n    \"\"\"\n    num: float = Field(description=\"The number to square.\")\n\n    def get_square(self) -\u003e float:\n        \"\"\"Get the square of the number.\"\"\"\n        return self.num ** 2\n\nif __name__ == '__main__':\n    squarer = tapify(Squarer)\n    print(f'The square of your number is {squarer.get_square()}.')\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eArgument descriptions\u003c/summary\u003e\n\nFor Pydantic v2 models and dataclasses, the argument's description (which is displayed in the `-h` help message) can\neither be specified in the class docstring or the field's `description`. If both are specified, the description from the\ndocstring is used. In the example below, the description is provided in the docstring.\n\nFor Pydantic v1 models and dataclasses, the argument's description must be provided in the class docstring:\n\n```python\n# square_pydantic.py\nfrom pydantic import BaseModel\n\nfrom tap import tapify\n\nclass Squarer(BaseModel):\n    \"\"\"Squarer with a number to square.\n\n    :param num: The number to square.\n    \"\"\"\n    num: float\n\n    def get_square(self) -\u003e float:\n        \"\"\"Get the square of the number.\"\"\"\n        return self.num ** 2\n\nif __name__ == '__main__':\n    squarer = tapify(Squarer)\n    print(f'The square of your number is {squarer.get_square()}.')\n```\n\n\u003c/details\u003e\n\n### tapify help\n\nThe help string on the command line is set based on the docstring for the function or class. For example, running `python square_function.py -h` will print:\n\n```\nusage: square_function.py [-h] --num NUM\n\nSquare a number.\n\noptions:\n  -h, --help  show this help message and exit\n  --num NUM   (float, required) The number to square.\n```\n\nNote that for classes, if there is a docstring in the `__init__` method, then `tapify` sets the help string description to that docstring. Otherwise, it uses the docstring from the top of the class.\n\n### Command line vs explicit arguments\n\n`tapify` can simultaneously use both arguments passed from the command line and arguments passed in explicitly in the `tapify` call. Arguments provided in the `tapify` call override function defaults, and arguments provided via the command line override both arguments provided in the `tapify` call and function defaults. We show an example below.\n\n```python\n# add.py\nfrom tap import tapify\n\ndef add(num_1: float, num_2: float = 0.0, num_3: float = 0.0) -\u003e float:\n    \"\"\"Add numbers.\n\n    :param num_1: The first number.\n    :param num_2: The second number.\n    :param num_3: The third number.\n    \"\"\"\n    return num_1 + num_2 + num_3\n\nif __name__ == '__main__':\n    added = tapify(add, num_2=2.2, num_3=4.1)\n    print(f'The sum of your numbers is {added}.')\n```\n\nRunning `python add.py --num_1 1.0 --num_2 0.9` prints `The sum of your numbers is 6.0.`. (Note that `add` took `num_1 = 1.0` and `num_2 = 0.9` from the command line and `num_3=4.1` from the `tapify` call due to the order of precedence.)\n\n### Known args\n\nCalling `tapify` with `known_only=True` allows `tapify` to ignore additional arguments from the command line that are not needed for the function or class. If `known_only=False` (the default), then `tapify` will raise an error when additional arguments are provided. We show an example below where `known_only=True` might be useful for running multiple `tapify` calls.\n\n```python\n# person.py\nfrom tap import tapify\n\ndef print_name(name: str) -\u003e None:\n    \"\"\"Print a person's name.\n\n    :param name: A person's name.\n    \"\"\"\n    print(f'My name is {name}.')\n\ndef print_age(age: int) -\u003e None:\n    \"\"\"Print a person's age.\n\n    :param name: A person's age.\n    \"\"\"\n    print(f'My age is {age}.')\n\nif __name__ == '__main__':\n    tapify(print_name, known_only=True)\n    tapify(print_age, known_only=True)\n```\n\nRunning `python person.py --name Jesse --age 1` prints `My name is Jesse.` followed by `My age is 1.`. Without `known_only=True`, the `tapify` calls would raise an error due to the extra argument.\n\n### Explicit boolean arguments\n\nTapify supports explicit specification of boolean arguments (see [bool](#bool) for more details). By default, `explicit_bool=False` and it can be set with `tapify(..., explicit_bool=True)`. \n\n## Convert to a `Tap` class\n\n`to_tap_class` turns a function or class into a `Tap` class. The returned class can be [subclassed](#subclassing) to add\nspecial argument behavior. For example, you can override [`configure`](#configuring-arguments) and\n[`process_args`](#argument-processing).\n\nIf the object can be `tapify`d, then it can be `to_tap_class`d, and vice-versa. `to_tap_class` provides full control\nover argument parsing. Just as in `tapify`, the data extracted from standard Python classes (not `pydantic` models or `dataclass`es) only comes from the `__init__`, not from class variables.\n\n### `to_tap_class` examples\n\n#### Simple\n\n```python\n# main.py\n\"\"\"\nMy script description\n\"\"\"\n\nfrom pydantic import BaseModel\n\nfrom tap import to_tap_class\n\nclass Project(BaseModel):\n    package: str\n    is_cool: bool = True\n    stars: int = 5\n\nif __name__ == \"__main__\":\n    ProjectTap = to_tap_class(Project)\n    tap = ProjectTap(description=__doc__)  # from the top of this script\n    args = tap.parse_args()\n    project = Project(**args.as_dict())\n    print(f\"Project instance: {project}\")\n```\n\nRunning `python main.py --package tap` will print `Project instance: package='tap' is_cool=True stars=5`.\n\n### Complex\n\nThe general pattern is:\n\n```python\nfrom tap import to_tap_class\n\nclass MyCustomTap(to_tap_class(my_class_or_function)):\n    # Special argument behavior, e.g., override configure and/or process_args\n```\n\nPlease see [`demos/demo_data_model.py`](./demos/demo_data_model.py) for an example of overriding\n[`configure`](#configuring-arguments) and [`process_args`](#argument-processing).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswansonk14%2Ftyped-argument-parser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fswansonk14%2Ftyped-argument-parser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswansonk14%2Ftyped-argument-parser/lists"}