# argParseFromDoc
A simple python package for creating/updating [argparse](https://docs.python.org/3/library/argparse.html)
ArgumentParser(s) given a type hinted and documented function.
## Content
- [Installation](#Installation)
- [Quick overview](#Quick overview)
- [Supported features](#Supported-features)
- [Assumptions](#Assumptions)
- [Usage](#Usage)
### Installation
- Option 1. Cloning this repository:
```
git clone https://github.com/rsanchezgarc/argParseFromDoc.git
cd argParseFromDoc
pip install .
```
- Option 2. Installing with pip
```
pip install argParseFromDoc
```
Or if you want the latest update
```
pip install git+https://github.com/rsanchezgarc/argParseFromDoc
```
### Quick overview
argParseFromDoc allows you to create argument parsers directly from the type hints and the docstring of a function.
The most common use case of creating a parser for a main function and calling it can be implemented in 2 lines if you
have previously documented your function
```
def add(a: int, b: int):
'''
@param a: first number.
@param b: second number.
'''
return a + b
if __name__ == "__main__":
from argParseFromDoc import parse_function_and_call
out = parse_function_and_call(add)
print(out)
```
argParseFromDoc also support more advanced features via the class `AutoArgumentParser` and the function `get_parser_from_function`.
See below for more details
### Supported features
The following features are currently supported
- Function argument types:
- `int`, `str`, `float` and `bool`
- (Homogeneous) Lists of any of the previous types (defined as`typing.List[primitive_type]`)
- Files (defined as`typing.TextIO` and `typing.BinaryIO`)
- Ignoring/selecting a subset of the arguments of the function
- Use `myarg:typing.Optional[VALID_TYPE]=None` to set it as not required parameter or `args_optional=["myarg"]`
- Creating a new parser or adding new arguments to it. You can also use parser groups
- Several docsctring formats (see [docstring_parser](https://github.com/rr-/docstring_parser) )
- Support for methods assuming first argument in definition is `self`
### Assumptions
- Positional arguments. Functions can have positional arguments, but the parser will consider all them as
if they were keyword/optional (always `--argname VALUE`)
- If no default value is provided for an argument in the function signature, argument will be considered as
required (`parser.add_argument(..., required=True)`). The same applies to `default=None` except if the
name of the argument is included in `args_optional` or it is declared as `typing.Optional`.
<br>E.g `get_parser_from_function(..., args_optional=[name1, name2...])`
- Boolean arguments:
- Boolean arguments must be provided with default value.
- If a boolean argument defaults to False (`name:bool=False`), the parser sets
the argument `name=True` if `--name` flag provided.
- If a boolean argument defaults to True (`name:bool=True`), the parser sets
the argument `name=False` if `--NOT_name` flag provided. Please notice that the name of
the argument in the argument parser has been changed from `name` to `--NOT_name` to reflect that
but the argument is stored using the original name, so no further changes in the code are required
- Multiple arguments can be provided if using `typing.List`. For example:
`def fun(several_strings: List[str]):`
- Setting deafult values for `typing.TextIO` and `typing.BinaryIO` is not advisable, as they should be opened files.
If you are only employing the function for the argument parser, you could default it to
string values pointing files, but again, this is not encouraged. Instead, if you want to set a default filename,
use type `str`. The main purpose of `typing.TextIO` and `typing.BinaryIO` in the parser is to allow pipes. For example:
```
#These two commands are equivalent
python count_lines --inputFile /etc/passwd
cat /etc/passwd | python count_lines --inputFile -
```
- Methods, including `__init__`, are supported providing `self` is always used as the first
argument in the definition
- When defining functions, `*arg` and `**kwargs` are ignored for the parser. No other `*` or `**` argument
is supported.
### Usage
You only need to document the type and possible default values for the arguments of your functions
with [typing](https://docs.python.org/3/library/typing.html) and add the description of each within the docstring.
Examples of documented functions are:
```
def add(a: int, b: int):
'''
@param a: first number. Mandatory
@param b: second number. Mandatory
'''
return a + b
def printYourAge(age: int, name: str = "Unknown"):
'''
@param age: your age
@param name: your name. This is optional
'''
return str(a) + " "+ b
def addList(several_nums: List[int], b: int=1):
'''
@param several_nums: first number
@param b: second number
'''
return [a + b for a in several_nums]
```
Then, obtaining an ArgumentParser for any of these functions (say `add`) is as easy as:
```
if __name__ == "__main__":
from argParseFromDoc import get_parser_from_function
parser = get_parser_from_function(add)
args = parser.parse_args()
print(add(**vars(args)))
```
Or you can directly use the AutoArgumentParser class
```
if __name__ == "__main__":
from argParseFromDoc import AutoArgumentParser
parser = AutoArgumentParser()
parser.add_args_from_function(add)
```
Finally, for convenience, you can create the parser, parse the argument and call the function
in one line using
```
if __name__ == "__main__":
from argParseFromDoc import parse_function_and_call
out = parse_function_and_call(add)
```
If you want to add to a previously instantiated parser the arguements of the function,
you just need to provide the original parser (or group) to the `get_parser_from_function` function.
```
if __name__ == "__main__":
from argParseFromDoc import get_parser_from_function
#standard ArgumentParser
from argparse import ArgumentParser
parser = ArgumentParser(prog="Add_example")
parser.add_argument("--other_type_of_argument", type=str, default="Not provided")
#####################################################
# ### If you prefer a group instead of a whole parser
# group = parser.add_argument_group()
# get_parser_from_function(add, parser=group)
#####################################################
#provide the original parser to get_parser_from_function that will add the new options to the parser
get_parser_from_function(add, parser=parser)
args = parser.parse_args()
print(add(**vars(args)))
```
Finally, if your function has some arguments that you do not want to include
to the parser, you can use the `args_to_ignore` option. If you want to use only a subset,
use the `args_to_include` option.
```
def manyArgsFun(a: int, b: int, c: int = 1, d: int = 2, e: str = "oneStr"):
'''
:param a: a
:param b: b
:param c: c
:param d: d
:param e: e
:return:
'''
print(e)
return sum([a, b, c, d])
if __name__ == "__main__":
from argParseFromDoc import get_parser_from_function
# parser = get_parser_from_function(manyArgsFun, args_to_ignore=["c", "d", "e"])
parser = get_parser_from_function(manyArgsFun, args_to_include=["a", "b"])
args = parser.parse_args()
print(manyArgsFun(**vars(args)))
```
You can use argParseFromDoc with subparsers easily. For instance:
```
if __name__ == "__main__":
from argParseFromDoc import AutoArgumentParser, get_parser_from_function
parser = AutoArgumentParser("programName")
subparsers = parser.add_subparsers(help='command: this is to select a command', required=True, dest='command')
parser1 = subparsers.add_parser('command1_name', help='')
get_parser_from_function(function1, parser=parser1)
parser2 = subparsers.add_parser('command2_name', help='')
get_parser_from_function(function2, parser=parser2)
arguments = parser.parse_args()
if arguments.command == "command1_name":
del arguments.command
function1(**vars(arguments))
elif arguments.command == "command2_name":
del arguments.command
function2(**vars(arguments))
else:
raise ValueError(f"Command not valid {arguments.command}")
```
Some additional examples can be found in [examples folder](examples) or in [test_argParseFromDoc.py](tests/test_argParseFromDoc.py)
Raw data
{
"_id": null,
"home_page": "https://github.com/rsanchezgarc/argParseFromDoc",
"name": "argParseFromDoc",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "argument parser ArgParser docstring argparse",
"author": "Ruben Sanchez-Garcia",
"author_email": "ruben.sanchez-garcia@stats.ox.ac.uk",
"download_url": "https://files.pythonhosted.org/packages/1a/f1/5f677c7274673df698041f71ea75c70a906102db96d913a51a8ce3d97738/argParseFromDoc-0.1.2.tar.gz",
"platform": null,
"description": "# argParseFromDoc\n\nA simple python package for creating/updating [argparse](https://docs.python.org/3/library/argparse.html)\nArgumentParser(s) given a type hinted and documented function.\n\n## Content\n\n- [Installation](#Installation)\n- [Quick overview](#Quick overview)\n- [Supported features](#Supported-features)\n- [Assumptions](#Assumptions)\n- [Usage](#Usage)\n\n### Installation\n\n- Option 1. Cloning this repository:\n```\ngit clone https://github.com/rsanchezgarc/argParseFromDoc.git\ncd argParseFromDoc\npip install .\n```\n- Option 2. Installing with pip\n```\npip install argParseFromDoc\n```\nOr if you want the latest update\n```\npip install git+https://github.com/rsanchezgarc/argParseFromDoc\n```\n\n### Quick overview\n\nargParseFromDoc allows you to create argument parsers directly from the type hints and the docstring of a function.\nThe most common use case of creating a parser for a main function and calling it can be implemented in 2 lines if you\nhave previously documented your function\n\n```\ndef add(a: int, b: int):\n '''\n @param a: first number. \n @param b: second number.\n '''\n return a + b\n \nif __name__ == \"__main__\":\n from argParseFromDoc import parse_function_and_call\n out = parse_function_and_call(add)\n print(out)\n```\n\nargParseFromDoc also support more advanced features via the class `AutoArgumentParser` and the function `get_parser_from_function`.\nSee below for more details\n\n### Supported features\nThe following features are currently supported\n- Function argument types:\n - `int`, `str`, `float` and `bool`\n - (Homogeneous) Lists of any of the previous types (defined as`typing.List[primitive_type]`)\n - Files (defined as`typing.TextIO` and `typing.BinaryIO`)\n- Ignoring/selecting a subset of the arguments of the function\n - Use `myarg:typing.Optional[VALID_TYPE]=None` to set it as not required parameter or `args_optional=[\"myarg\"]` \n- Creating a new parser or adding new arguments to it. You can also use parser groups\n- Several docsctring formats (see [docstring_parser](https://github.com/rr-/docstring_parser) )\n- Support for methods assuming first argument in definition is `self`\n\n### Assumptions\n - Positional arguments. Functions can have positional arguments, but the parser will consider all them as \n if they were keyword/optional (always `--argname VALUE`)\n - If no default value is provided for an argument in the function signature, argument will be considered as\n required (`parser.add_argument(..., required=True)`). The same applies to `default=None` except if the\n name of the argument is included in `args_optional` or it is declared as `typing.Optional`. \n <br>E.g `get_parser_from_function(..., args_optional=[name1, name2...])` \n - Boolean arguments:\n - Boolean arguments must be provided with default value.\n - If a boolean argument defaults to False (`name:bool=False`), the parser sets\n the argument `name=True` if `--name` flag provided.\n - If a boolean argument defaults to True (`name:bool=True`), the parser sets\n the argument `name=False` if `--NOT_name` flag provided. Please notice that the name of\n the argument in the argument parser has been changed from `name` to `--NOT_name` to reflect that\n but the argument is stored using the original name, so no further changes in the code are required\n - Multiple arguments can be provided if using `typing.List`. For example:\n `def fun(several_strings: List[str]):`\n - Setting deafult values for `typing.TextIO` and `typing.BinaryIO` is not advisable, as they should be opened files. \n If you are only employing the function for the argument parser, you could default it to\n string values pointing files, but again, this is not encouraged. Instead, if you want to set a default filename,\n use type `str`. The main purpose of `typing.TextIO` and `typing.BinaryIO` in the parser is to allow pipes. For example:\n ```\n #These two commands are equivalent\n python count_lines --inputFile /etc/passwd \n cat /etc/passwd | python count_lines --inputFile -\n \n ```\n - Methods, including `__init__`, are supported providing `self` is always used as the first \n argument in the definition\n - When defining functions, `*arg` and `**kwargs` are ignored for the parser. No other `*` or `**` argument\n is supported.\n\n### Usage\n\nYou only need to document the type and possible default values for the arguments of your functions\nwith [typing](https://docs.python.org/3/library/typing.html) and add the description of each within the docstring.\nExamples of documented functions are:\n\n```\ndef add(a: int, b: int):\n '''\n @param a: first number. Mandatory\n @param b: second number. Mandatory\n '''\n return a + b\n \ndef printYourAge(age: int, name: str = \"Unknown\"):\n '''\n @param age: your age\n @param name: your name. This is optional\n '''\n return str(a) + \" \"+ b\n \ndef addList(several_nums: List[int], b: int=1):\n '''\n @param several_nums: first number\n @param b: second number\n '''\n return [a + b for a in several_nums]\n\n```\n\nThen, obtaining an ArgumentParser for any of these functions (say `add`) is as easy as:\n\n```\nif __name__ == \"__main__\":\n from argParseFromDoc import get_parser_from_function\n parser = get_parser_from_function(add)\n args = parser.parse_args()\n print(add(**vars(args)))\n```\nOr you can directly use the AutoArgumentParser class\n\n```\nif __name__ == \"__main__\":\n from argParseFromDoc import AutoArgumentParser\n parser = AutoArgumentParser()\n parser.add_args_from_function(add)\n```\n\nFinally, for convenience, you can create the parser, parse the argument and call the function\nin one line using\n\n``` \nif __name__ == \"__main__\":\n from argParseFromDoc import parse_function_and_call\n out = parse_function_and_call(add)\n\n```\nIf you want to add to a previously instantiated parser the arguements of the function,\nyou just need to provide the original parser (or group) to the `get_parser_from_function` function.\n\n```\nif __name__ == \"__main__\":\n from argParseFromDoc import get_parser_from_function\n #standard ArgumentParser\n from argparse import ArgumentParser\n parser = ArgumentParser(prog=\"Add_example\")\n parser.add_argument(\"--other_type_of_argument\", type=str, default=\"Not provided\")\n #####################################################\n # ### If you prefer a group instead of a whole parser\n # group = parser.add_argument_group()\n # get_parser_from_function(add, parser=group)\n #####################################################\n #provide the original parser to get_parser_from_function that will add the new options to the parser\n get_parser_from_function(add, parser=parser)\n args = parser.parse_args()\n print(add(**vars(args)))\n```\nFinally, if your function has some arguments that you do not want to include\nto the parser, you can use the `args_to_ignore` option. If you want to use only a subset,\nuse the `args_to_include` option. \n\n```\ndef manyArgsFun(a: int, b: int, c: int = 1, d: int = 2, e: str = \"oneStr\"):\n '''\n\n :param a: a\n :param b: b\n :param c: c\n :param d: d\n :param e: e\n :return:\n '''\n print(e)\n return sum([a, b, c, d])\n\n\nif __name__ == \"__main__\":\n from argParseFromDoc import get_parser_from_function\n # parser = get_parser_from_function(manyArgsFun, args_to_ignore=[\"c\", \"d\", \"e\"])\n parser = get_parser_from_function(manyArgsFun, args_to_include=[\"a\", \"b\"])\n args = parser.parse_args()\n print(manyArgsFun(**vars(args)))\n\n```\n\nYou can use argParseFromDoc with subparsers easily. For instance:\n\n```\nif __name__ == \"__main__\":\n from argParseFromDoc import AutoArgumentParser, get_parser_from_function\n parser = AutoArgumentParser(\"programName\")\n\n subparsers = parser.add_subparsers(help='command: this is to select a command', required=True, dest='command')\n \n parser1 = subparsers.add_parser('command1_name', help='')\n get_parser_from_function(function1, parser=parser1)\n\n parser2 = subparsers.add_parser('command2_name', help='')\n get_parser_from_function(function2, parser=parser2)\n\n arguments = parser.parse_args()\n if arguments.command == \"command1_name\":\n del arguments.command\n function1(**vars(arguments))\n elif arguments.command == \"command2_name\":\n del arguments.command\n function2(**vars(arguments))\n else:\n raise ValueError(f\"Command not valid {arguments.command}\")\n```\n\nSome additional examples can be found in [examples folder](examples) or in [test_argParseFromDoc.py](tests/test_argParseFromDoc.py)\n",
"bugtrack_url": null,
"license": "Apache 2.0",
"summary": "Simple argument parser for documented functions",
"version": "0.1.2",
"project_urls": {
"Homepage": "https://github.com/rsanchezgarc/argParseFromDoc"
},
"split_keywords": [
"argument",
"parser",
"argparser",
"docstring",
"argparse"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "740a5db62e9482551c0fb66fdb4bc4c7614f52581e0cb7d8e65a32944c97e13f",
"md5": "6db120b25bb6808b679bfc13662423cc",
"sha256": "ca263970dd9f7a4283ee2daa923ec1b370451064bbcc20ebee63e8b4b6ffc633"
},
"downloads": -1,
"filename": "argParseFromDoc-0.1.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "6db120b25bb6808b679bfc13662423cc",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 20155,
"upload_time": "2023-09-13T15:59:17",
"upload_time_iso_8601": "2023-09-13T15:59:17.247181Z",
"url": "https://files.pythonhosted.org/packages/74/0a/5db62e9482551c0fb66fdb4bc4c7614f52581e0cb7d8e65a32944c97e13f/argParseFromDoc-0.1.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "1af15f677c7274673df698041f71ea75c70a906102db96d913a51a8ce3d97738",
"md5": "e7fa5df01dd9b5610cc09ba024af6dde",
"sha256": "386adc080000ba8c39aa6c8417e2308946dca9566cc98b0a64c607c62f3a0c80"
},
"downloads": -1,
"filename": "argParseFromDoc-0.1.2.tar.gz",
"has_sig": false,
"md5_digest": "e7fa5df01dd9b5610cc09ba024af6dde",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 17716,
"upload_time": "2023-09-13T15:59:18",
"upload_time_iso_8601": "2023-09-13T15:59:18.276283Z",
"url": "https://files.pythonhosted.org/packages/1a/f1/5f677c7274673df698041f71ea75c70a906102db96d913a51a8ce3d97738/argParseFromDoc-0.1.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-09-13 15:59:18",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "rsanchezgarc",
"github_project": "argParseFromDoc",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [],
"lcname": "argparsefromdoc"
}