{ "info": { "author": "Jesse Michel and Kyle Swanson", "author_email": "swansonk.14@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7" ], "description": "# 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\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/blob/master/images/tap.png)\n \n## Table of Contents\n\n* [Installation](#installation)\n* [Tap is Python-native](#tap-is-python-native)\n* [Tap features](#tap-features)\n + [Arguments](#arguments)\n + [Help string](#help-string)\n + [Flexibility of `add_arguments`](#flexibility-of-add_arguments)\n + [Types](#types)\n + [Argument processing with `process_args`](#argument-processing-with-process_args)\n + [Processing known args](#processing-known-args)\n + [Subclassing](#subclassing)\n + [Printing](#printing)\n + [Reproducibility](#reproducibility)\n - [Reproducibility info](#reproducibility-info)\n - [Saving arguments](#saving-arguments)\n \n## Installation\n\nTap requires Python 3.6+\n\nTo install Tap, run the following commands:\n\n```\ngit clone https://github.com/swansonk14/typed-argument-parser.git\ncd typed-argument-parser\npip install -e .\n```\n\n## Tap is Python-native\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>>> 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,\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### Help string\n\nSingle line comments 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\nargs = MyTap().parse_args()\n```\n\nRunning `python main.py -h` results in the following:\n\n```\n>>> python main.py -h\nusage: demo.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!\n -h, --help show this help message and exit\n```\n\n### Flexibility of `add_arguments`\n\nPython's argparse provides a number of advanced argument parsing features with the `add_argument` method. Since Tap is a wrapper around argparse, Tap provides all of the same functionality.\n\nTo make use of this functionality, first define arguments as class variables as usual, then override Tap's `add_arguments` and use `self.add_argument` just as you would use argparse's `add_argument`.\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 add_arguments(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### Types\n\nTap automatically handles all of the following types:\n\n```python\nstr, int, float, bool\nOptional[str], Optional[int], Optional[float]\nList[str], List[int], List[float]\nSet[str], Set[int], Set[float]\n```\n\nEach of these arguments is parsed as follows:\n\n`str`, `int`, and `float`: Each is automatically parsed to their respective types, just like argparse.\n\n`bool`: If 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\n`Optional`: These arguments are parsed in exactly the same way as `str`, `int`, and `float`.\n\n`List`: If 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`: Identical to `List` but parsed into a set rather than a list.\n\nMore complex types _must_ be specified with the `type` keyword argument in `add_argument`, as in the example below.\n\n```python\ndef to_number(string: str):\n return float(string) if '.' in string else int(string)\n\nclass MyTap(Tap):\n number: Union[int, float]\n\n def add_arguments(self):\n self.add_argument('--number', type=to_number)\n```\n\n### Argument processing with `process_args`\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 < 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>>> 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\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- Uncommited 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#### Saving arguments\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```", "description_content_type": "text/markdown", "docs_url": null, "download_url": "https://github.com/swansonk14/typed-argument-parser/v_1.3.tar.gz", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/swansonk14/typed-argument-parser", "keywords": "typing,argument parser,python", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "typed-argument-parser", "package_url": "https://pypi.org/project/typed-argument-parser/", "platform": "", "project_url": "https://pypi.org/project/typed-argument-parser/", "project_urls": { "Download": "https://github.com/swansonk14/typed-argument-parser/v_1.3.tar.gz", "Homepage": "https://github.com/swansonk14/typed-argument-parser" }, "release_url": "https://pypi.org/project/typed-argument-parser/1.3/", "requires_dist": null, "requires_python": "", "summary": "Typed Argument Parser", "version": "1.3" }, "last_serial": 5798045, "releases": { "1.2": [ { "comment_text": "", "digests": { "md5": "7377c835671401da0997cb6bcfabb689", "sha256": "a8cea9fd206093c74437596131612dcb3044c36c2010f3e21576a3b35c0383bf" }, "downloads": -1, "filename": "typed-argument-parser-1.2.tar.gz", "has_sig": false, "md5_digest": "7377c835671401da0997cb6bcfabb689", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 14589, "upload_time": "2019-08-23T05:31:02", "url": "https://files.pythonhosted.org/packages/16/33/8f6a4d242ac77d388e32d372b04a7312c3e84408f97306fb4c5dec119019/typed-argument-parser-1.2.tar.gz" } ], "1.3": [ { "comment_text": "", "digests": { "md5": "64c1cd87a07405303b5eaa39dc16c14a", "sha256": "62058aff3deccee9b6ffcb3acfe46a2878fb65dcf411945a9ea948f07340c041" }, "downloads": -1, "filename": "typed-argument-parser-1.3.tar.gz", "has_sig": false, "md5_digest": "64c1cd87a07405303b5eaa39dc16c14a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 14669, "upload_time": "2019-09-08T02:21:53", "url": "https://files.pythonhosted.org/packages/a9/85/4474242da08b1121ab2a05e529e640bdf7e5936c34de7417f154347004d0/typed-argument-parser-1.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "64c1cd87a07405303b5eaa39dc16c14a", "sha256": "62058aff3deccee9b6ffcb3acfe46a2878fb65dcf411945a9ea948f07340c041" }, "downloads": -1, "filename": "typed-argument-parser-1.3.tar.gz", "has_sig": false, "md5_digest": "64c1cd87a07405303b5eaa39dc16c14a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 14669, "upload_time": "2019-09-08T02:21:53", "url": "https://files.pythonhosted.org/packages/a9/85/4474242da08b1121ab2a05e529e640bdf7e5936c34de7417f154347004d0/typed-argument-parser-1.3.tar.gz" } ] }