funparse


Namefunparse JSON
Version 0.3.1 PyPI version JSON
download
home_pagehttps://github.com/brunofauth/funparse
Summary`funparse` allows you to 'derive' an argument parser from type annotations of a function's signature, cutting down on the boilerplate code.
upload_time2023-11-17 23:26:22
maintainer
docs_urlNone
authorBruno Fauth
requires_python>=3.11,<4.0
license
keywords cli command-line type hint derive decorator
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Introduction

`funparse` allows you to "derive" an argument parser (such as those from 
[argparse](https://docs.python.org/3/library/argparse.html)) from type 
annotations of a function's signature, cutting down on the boilerplate code. 
It's similar to [fire](https://github.com/google/python-fire) in this way, but 
it's more lightweight and designed in a way to give its user more control over 
what is going on.


**Disclaimer:** your user experience may be much richer if you're using a 
static type checker, such as [mypy](https://mypy-lang.org/)


# Installation

With pip:

    pip install funparse[docstring]

With poetry:
    
    poetry install funparse[docstring]

If you don't need to generate per-argument help strings, you can omit the 
`[docstring]` extra when installing this package.


# Examples

## Basic Usage

```python
import sys
import funparse.api as fp


@fp.as_arg_parser
def some_parser_name(
    your_name: str,
    your_age: int,
    pets: list[str] | None = None,
    loves_python: bool = False,
) -> None:
    print("Hi", your_name)

    if pets is not None:
        for pet in pets:
            print("send greetings to", pet, "for me")

    if loves_python:
        print("Cool! I love python too!")


# Run the parser on this set of arguments
some_parser_name.run([
    "Johnny",
    "33",
    *("--pets", "Goofy"),
    *("--pets", "Larry"),
    *("--pets", "Yes"),
    "--loves-python",
])

# You can also use args from the command line
some_parser_name.run(sys.argv)
```

## Printing Help

```python
import funparse.api as fp


@fp.as_arg_parser
def some_parser_name(
    your_name: str,
    your_age: int,
) -> None:
    print("Hi", your_name)
    if your_age > 325:
        print("getting elderly, eh")


# You can print help and usage information like this:
some_parser_name.print_usage()
some_parser_name.print_help()
# These work just like they do on 'argparse.ArgumentParser'

# You can also format this information into strings
usage = some_parser_name.format_usage()
help_str = some_parser_name.format_help()
```

[See more about it 
here](https://docs.python.org/3/library/argparse.html#printing-help)


## Behavior on Booleans

```python
import funparse.api as fp


@fp.as_arg_parser
def booler(
    # This is a positional argument
    aaa: bool,

    # This is a flag which, if present, will set 'bbb' to False
    bbb: bool = True,

    # This is a flag which, if set, will set 'ccc' to True
    ccc: bool = False,
) -> None:
    print(aaa, bbb, ccc)


# This will print: True, False, False
booler.run([
    "yes",  # 'y', 'true', 'True' and '1' will also work
    "--bbb",
])

# This will print: False, True, False
booler.run([
    "false",  # 'n', 'no', 'False' and '0' will also work
])
```


## Behavior on Enums

```python
import funparse.api as fp
import enum


# This Enum functionality will work better if you use SCREAMING_SNAKE_CASE for
# the names of your enum members (if you don't, your CLI will work in a
# case-sensitive way :P)
class CommandModes(fp.Enum):  # You can use enum.Enum and similar classes too
    CREATE_USER = enum.auto()
    LIST_USERS = enum.auto()
    DELETE_USER = enum.auto()


@fp.as_arg_parser
def some_parser(mode: CommandModes) -> None:
    print(f"you picked {mode.name!r} mode!")


some_parser.run(["CREATE_USER"])  # This is valid...
some_parser.run(["create_user"])  # ...so is this...
some_parser.run(["crEatE_usEr"])  # ...and this too...

# This raises an error
some_parser.run(["NON EXISTING FUNCTIONALITY EXAMPLE"])
```

## Bypassing the command-line

If you want to pass extra data to the function which you're using as your 
parser generator, but without having to supply this data through the CLI, you 
can use the `ignore` parameter on `as_arg_parser`, like this:

```python
import funparse.api as fp


@fp.as_arg_parser(ignore=["user_count", "user_name"])
def some_parser(
    user_count: int,
    user_name: str,
    user_address: str,
    is_foreigner: bool = False,
) -> None:
    print(f"you're the {user_count}th user today! welcome, {user_name}")
    print("They say", user_address, "is lovely this time of the year...")


# These 'state-variables' must be passed as keyword args (or through **kwargs)
some_parser.with_state(
    user_count=33,
    user_name="Josh",
).run(["some address..."])

# If you want, you can cache these parser-with-state objects. It sort of
# reminds me of 'functools.partial'
saving_for_later = some_parser.with_state(
    user_count=33,
    user_name="Josh",
)

# Later:
saving_for_later.run([
    "some address...",
    "--is-foreigner",
])
```


## Using custom argument parsers

```python
import argparse
import funparse.api as fp


# First, subclass 'argparse.ArgumentParser'
class MyParser(argparse.ArgumentParser):
    """Just like argparse's, but better!"""


# Then, pass your parser as an argument to 'as_arg_parser'
@fp.as_arg_parser(parser_type=MyParser)
def some_parser(
    user_name: str,
    is_foreigner: bool = False,
) -> None:
    print("Welcome", user_name)
    if is_foreigner:
        print("Nice to have you here")


# Finally, run your parser. It all works as expected!
some_parser.run([
    "johnny",
    "--is-foreigner",
])
```

## Generating per-argument help strings from docstrings

Thanks to [this package](https://github.com/rr-/docstring_parser), `funparse` 
can generate `help` strings for arguments, from the docstring of the function 
in question, like this:

```python
import funparse.api as fp


@fp.as_arg_parser(parse_docstring=fp.DocstringStyle.GOOGLE)
def some_parser(
    name: str,
    is_foreigner: bool = False,
) -> None:
    """My awesome command.

    Long description... Aut reiciendis voluptatem aperiam rerum voluptatem non. 
    Aut sit temporibus in ex ut mollitia. Omnis velit asperiores voluptatem ut 
    molestiae quis et qui.

    Args:
        name: some help information about this arg
        is_foreigner: some other help information
    """
    print("Welcome", name)
    if is_foreigner:
        print("Nice to have you here")


some_parser.print_help()
```

The generated command help should look like this:

```
usage: - [-h] [--is-foreigner] name

Long description... Aut reiciendis voluptatem aperiam rerum voluptatem non.
Aut sit temporibus in ex ut mollitia. Omnis velit asperiores voluptatem ut
molestiae quis et qui.

positional arguments:
  name            some help information about this arg

options:
  -h, --help      show this help message and exit
  --is-foreigner  bool (default=False): some other help information
```


## Variadic Positional Arguments

You can use the star notation in a function's signature to specify that the
argument in question should take in one or more parameters. If you want your
function's parameter to allow zero or more items, consider defining, in your
functions signature, a parameter of type `list[T] | None` with a default value
of `None`, as shown in './examples/01_basic_usage.py' or in the above section
titled "Basic Usage".

```python
import funparse.api as fp


@fp.as_arg_parser
def some_parser_name(
    *pet_names: str,  # Here's the aforementioned star notation
    your_name: str = "John",
) -> None:
    print("Hi", your_name)
    for pet_name in pet_names:
        print("send greetings to", pet_name, "for me")


# Run the parser on this set of arguments
some_parser_name.run([
    "Goofy",
    "Larry",
    "Yes",
    "--your-name",
    "Johnny",
])
```


# Extras

Beyond `as_arg_parser`, this module also ships:

- `funparse.Enum`, which is a subclass of `enum.Enum`, but with a `__str__` 
  that better fits your CLI apps
- `funparse.ArgumentParser`, which is a subclass of `argparse.ArgumentParser` 
  that, unlike the latter, does not terminate your app on (most) exceptions

Have fun!



            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/brunofauth/funparse",
    "name": "funparse",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.11,<4.0",
    "maintainer_email": "",
    "keywords": "cli,command-line,type,hint,derive,decorator",
    "author": "Bruno Fauth",
    "author_email": "149593@upf.br",
    "download_url": "https://files.pythonhosted.org/packages/bf/4c/374cbc468ea57d198824a512697e0e16f545ed023f89666156b675eda6aa/funparse-0.3.1.tar.gz",
    "platform": null,
    "description": "# Introduction\n\n`funparse` allows you to \"derive\" an argument parser (such as those from \n[argparse](https://docs.python.org/3/library/argparse.html)) from type \nannotations of a function's signature, cutting down on the boilerplate code. \nIt's similar to [fire](https://github.com/google/python-fire) in this way, but \nit's more lightweight and designed in a way to give its user more control over \nwhat is going on.\n\n\n**Disclaimer:** your user experience may be much richer if you're using a \nstatic type checker, such as [mypy](https://mypy-lang.org/)\n\n\n# Installation\n\nWith pip:\n\n    pip install funparse[docstring]\n\nWith poetry:\n    \n    poetry install funparse[docstring]\n\nIf you don't need to generate per-argument help strings, you can omit the \n`[docstring]` extra when installing this package.\n\n\n# Examples\n\n## Basic Usage\n\n```python\nimport sys\nimport funparse.api as fp\n\n\n@fp.as_arg_parser\ndef some_parser_name(\n    your_name: str,\n    your_age: int,\n    pets: list[str] | None = None,\n    loves_python: bool = False,\n) -> None:\n    print(\"Hi\", your_name)\n\n    if pets is not None:\n        for pet in pets:\n            print(\"send greetings to\", pet, \"for me\")\n\n    if loves_python:\n        print(\"Cool! I love python too!\")\n\n\n# Run the parser on this set of arguments\nsome_parser_name.run([\n    \"Johnny\",\n    \"33\",\n    *(\"--pets\", \"Goofy\"),\n    *(\"--pets\", \"Larry\"),\n    *(\"--pets\", \"Yes\"),\n    \"--loves-python\",\n])\n\n# You can also use args from the command line\nsome_parser_name.run(sys.argv)\n```\n\n## Printing Help\n\n```python\nimport funparse.api as fp\n\n\n@fp.as_arg_parser\ndef some_parser_name(\n    your_name: str,\n    your_age: int,\n) -> None:\n    print(\"Hi\", your_name)\n    if your_age > 325:\n        print(\"getting elderly, eh\")\n\n\n# You can print help and usage information like this:\nsome_parser_name.print_usage()\nsome_parser_name.print_help()\n# These work just like they do on 'argparse.ArgumentParser'\n\n# You can also format this information into strings\nusage = some_parser_name.format_usage()\nhelp_str = some_parser_name.format_help()\n```\n\n[See more about it \nhere](https://docs.python.org/3/library/argparse.html#printing-help)\n\n\n## Behavior on Booleans\n\n```python\nimport funparse.api as fp\n\n\n@fp.as_arg_parser\ndef booler(\n    # This is a positional argument\n    aaa: bool,\n\n    # This is a flag which, if present, will set 'bbb' to False\n    bbb: bool = True,\n\n    # This is a flag which, if set, will set 'ccc' to True\n    ccc: bool = False,\n) -> None:\n    print(aaa, bbb, ccc)\n\n\n# This will print: True, False, False\nbooler.run([\n    \"yes\",  # 'y', 'true', 'True' and '1' will also work\n    \"--bbb\",\n])\n\n# This will print: False, True, False\nbooler.run([\n    \"false\",  # 'n', 'no', 'False' and '0' will also work\n])\n```\n\n\n## Behavior on Enums\n\n```python\nimport funparse.api as fp\nimport enum\n\n\n# This Enum functionality will work better if you use SCREAMING_SNAKE_CASE for\n# the names of your enum members (if you don't, your CLI will work in a\n# case-sensitive way :P)\nclass CommandModes(fp.Enum):  # You can use enum.Enum and similar classes too\n    CREATE_USER = enum.auto()\n    LIST_USERS = enum.auto()\n    DELETE_USER = enum.auto()\n\n\n@fp.as_arg_parser\ndef some_parser(mode: CommandModes) -> None:\n    print(f\"you picked {mode.name!r} mode!\")\n\n\nsome_parser.run([\"CREATE_USER\"])  # This is valid...\nsome_parser.run([\"create_user\"])  # ...so is this...\nsome_parser.run([\"crEatE_usEr\"])  # ...and this too...\n\n# This raises an error\nsome_parser.run([\"NON EXISTING FUNCTIONALITY EXAMPLE\"])\n```\n\n## Bypassing the command-line\n\nIf you want to pass extra data to the function which you're using as your \nparser generator, but without having to supply this data through the CLI, you \ncan use the `ignore` parameter on `as_arg_parser`, like this:\n\n```python\nimport funparse.api as fp\n\n\n@fp.as_arg_parser(ignore=[\"user_count\", \"user_name\"])\ndef some_parser(\n    user_count: int,\n    user_name: str,\n    user_address: str,\n    is_foreigner: bool = False,\n) -> None:\n    print(f\"you're the {user_count}th user today! welcome, {user_name}\")\n    print(\"They say\", user_address, \"is lovely this time of the year...\")\n\n\n# These 'state-variables' must be passed as keyword args (or through **kwargs)\nsome_parser.with_state(\n    user_count=33,\n    user_name=\"Josh\",\n).run([\"some address...\"])\n\n# If you want, you can cache these parser-with-state objects. It sort of\n# reminds me of 'functools.partial'\nsaving_for_later = some_parser.with_state(\n    user_count=33,\n    user_name=\"Josh\",\n)\n\n# Later:\nsaving_for_later.run([\n    \"some address...\",\n    \"--is-foreigner\",\n])\n```\n\n\n## Using custom argument parsers\n\n```python\nimport argparse\nimport funparse.api as fp\n\n\n# First, subclass 'argparse.ArgumentParser'\nclass MyParser(argparse.ArgumentParser):\n    \"\"\"Just like argparse's, but better!\"\"\"\n\n\n# Then, pass your parser as an argument to 'as_arg_parser'\n@fp.as_arg_parser(parser_type=MyParser)\ndef some_parser(\n    user_name: str,\n    is_foreigner: bool = False,\n) -> None:\n    print(\"Welcome\", user_name)\n    if is_foreigner:\n        print(\"Nice to have you here\")\n\n\n# Finally, run your parser. It all works as expected!\nsome_parser.run([\n    \"johnny\",\n    \"--is-foreigner\",\n])\n```\n\n## Generating per-argument help strings from docstrings\n\nThanks to [this package](https://github.com/rr-/docstring_parser), `funparse` \ncan generate `help` strings for arguments, from the docstring of the function \nin question, like this:\n\n```python\nimport funparse.api as fp\n\n\n@fp.as_arg_parser(parse_docstring=fp.DocstringStyle.GOOGLE)\ndef some_parser(\n    name: str,\n    is_foreigner: bool = False,\n) -> None:\n    \"\"\"My awesome command.\n\n    Long description... Aut reiciendis voluptatem aperiam rerum voluptatem non. \n    Aut sit temporibus in ex ut mollitia. Omnis velit asperiores voluptatem ut \n    molestiae quis et qui.\n\n    Args:\n        name: some help information about this arg\n        is_foreigner: some other help information\n    \"\"\"\n    print(\"Welcome\", name)\n    if is_foreigner:\n        print(\"Nice to have you here\")\n\n\nsome_parser.print_help()\n```\n\nThe generated command help should look like this:\n\n```\nusage: - [-h] [--is-foreigner] name\n\nLong description... Aut reiciendis voluptatem aperiam rerum voluptatem non.\nAut sit temporibus in ex ut mollitia. Omnis velit asperiores voluptatem ut\nmolestiae quis et qui.\n\npositional arguments:\n  name            some help information about this arg\n\noptions:\n  -h, --help      show this help message and exit\n  --is-foreigner  bool (default=False): some other help information\n```\n\n\n## Variadic Positional Arguments\n\nYou can use the star notation in a function's signature to specify that the\nargument in question should take in one or more parameters. If you want your\nfunction's parameter to allow zero or more items, consider defining, in your\nfunctions signature, a parameter of type `list[T] | None` with a default value\nof `None`, as shown in './examples/01_basic_usage.py' or in the above section\ntitled \"Basic Usage\".\n\n```python\nimport funparse.api as fp\n\n\n@fp.as_arg_parser\ndef some_parser_name(\n    *pet_names: str,  # Here's the aforementioned star notation\n    your_name: str = \"John\",\n) -> None:\n    print(\"Hi\", your_name)\n    for pet_name in pet_names:\n        print(\"send greetings to\", pet_name, \"for me\")\n\n\n# Run the parser on this set of arguments\nsome_parser_name.run([\n    \"Goofy\",\n    \"Larry\",\n    \"Yes\",\n    \"--your-name\",\n    \"Johnny\",\n])\n```\n\n\n# Extras\n\nBeyond `as_arg_parser`, this module also ships:\n\n- `funparse.Enum`, which is a subclass of `enum.Enum`, but with a `__str__` \n  that better fits your CLI apps\n- `funparse.ArgumentParser`, which is a subclass of `argparse.ArgumentParser` \n  that, unlike the latter, does not terminate your app on (most) exceptions\n\nHave fun!\n\n\n",
    "bugtrack_url": null,
    "license": "",
    "summary": "`funparse` allows you to 'derive' an argument parser from type annotations of a function's signature, cutting down on the boilerplate code.",
    "version": "0.3.1",
    "project_urls": {
        "Bug Tracker": "https://github.com/brunofauth/funparse/issues",
        "Homepage": "https://github.com/brunofauth/funparse",
        "Repository": "https://github.com/brunofauth/funparse"
    },
    "split_keywords": [
        "cli",
        "command-line",
        "type",
        "hint",
        "derive",
        "decorator"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "bf4c374cbc468ea57d198824a512697e0e16f545ed023f89666156b675eda6aa",
                "md5": "e6e8023ba98157b706cdfda2652ade46",
                "sha256": "2d4c0e8194043435feb1d927a5235a94236420d65095b12d6b80812fe4649625"
            },
            "downloads": -1,
            "filename": "funparse-0.3.1.tar.gz",
            "has_sig": false,
            "md5_digest": "e6e8023ba98157b706cdfda2652ade46",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.11,<4.0",
            "size": 7711,
            "upload_time": "2023-11-17T23:26:22",
            "upload_time_iso_8601": "2023-11-17T23:26:22.627735Z",
            "url": "https://files.pythonhosted.org/packages/bf/4c/374cbc468ea57d198824a512697e0e16f545ed023f89666156b675eda6aa/funparse-0.3.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-11-17 23:26:22",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "brunofauth",
    "github_project": "funparse",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "funparse"
}
        
Elapsed time: 2.28647s