datargs


Namedatargs JSON
Version 1.1.0 PyPI version JSON
download
home_pagehttps://github.com/roee30/datargs
SummaryDeclarative, type-safe command line argument parsers from dataclasses and attrs classes
upload_time2024-05-11 13:35:17
maintainerNone
docs_urlNone
authorRoee Nizan
requires_python<4.0,>=3.7
licenseMIT
keywords argparse dataclass attrs
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # datargs

A paper-thin wrapper around `argparse` that creates type-safe parsers
from `dataclass` and `attrs` classes.

## Quickstart


Install `datargs`:

```bash
pip install datargs
```

Create a `dataclass` (or an `attrs` class) describing your command line interface, and call
`datargs.parse()` with the class:

```python
# script.py
from dataclasses import dataclass
from pathlib import Path
from datargs import parse

@dataclass  # or @attr.s(auto_attribs=True)
class Args:
    url: str
    output_path: Path
    verbose: bool
    retries: int = 3

def main():
    args = parse(Args)
    print(args)

if __name__ == "__main__":
    main()
```

***(experimental)*** Alternatively: convert an existing parser to a dataclass:
```python
# script.py
parser = ArgumentParser()
parser.add_argument(...)
from datargs import convert
convert(parser)
```

`convert()` prints a class definition to the console.
Copy it to your script.

Mypy and pycharm correctly infer the type of `args` as `Args`, and your script is good to go!
```bash
$ python script.py -h
usage: test.py [-h] --url URL --output-path OUTPUT_PATH [--retries RETRIES]
               [--verbose]

optional arguments:
  -h, --help            show this help message and exit
  --url URL
  --output-path OUTPUT_PATH
  --retries RETRIES
  --verbose
$ python script.py --url "https://..." --output-path out --retries 4 --verbose
Args(url="https://...", output_path=Path("out"), retries=4, verbose=True)
```

## Table of Contents

<!-- toc -->

- [Features](#features)
  * [Static verification](#static-verification)
  * [`dataclass`/`attr.s` agnostic](#dataclassattrs-agnostic)
  * [Aliases](#aliases)
  * [`ArgumentParser` options](#argumentparser-options)
  * [Enums](#enums)
  * [Sequences, Optionals, and Literals](#sequences-optionals-and-literals)
  * [Sub Commands](#sub-commands)
- ["Why not"s and design choices](#why-nots-and-design-choices)
  * [Just use argparse?](#just-use-argparse)
  * [Use `click`](#use-clickhttpsclickpalletsprojectscomen7x)?
  * [Use `clout`](#use-clouthttpscloutreadthedocsioenlatestindexhtml)?
  * [Use `simple-parsing`](#use-simple-parsinghttpspypiorgprojectsimple-parsing)?
  * [Use `argparse-dataclass`](#use-argparse-dataclasshttpspypiorgprojectargparse-dataclass)?
  * [Use `argparse-dataclasses`](#use-argparse-dataclasseshttpspypiorgprojectargparse-dataclasses)?
- [FAQs](#faqs)
  * [Is this cross-platform?](#is-this-cross-platform)
  * [Why are mutually exclusive options not supported?](#why-are-mutually-exclusive-options-not-supported)

<!-- tocstop -->

## Features

### Static verification
Mypy/Pycharm have your back when you when you make a mistake:
```python
...
def main():
    args = parse(Args)
    args.urll  # typo
...
```
Pycharm says: `Unresolved attribute reference 'urll' for class 'Args'`.

Mypy says: `script.py:15: error: "Args" has no attribute "urll"; maybe "url"?`


### `dataclass`/`attr.s` agnostic
```pycon
>>> import attr, datargs
>>> @attr.s
... class Args:
...     flag: bool = attr.ib()
>>> datargs.parse(Args, [])
Args(flag=False)
```

### Aliases
Aliases and `ArgumentParser.add_argument()` parameters are taken from `metadata`:

```pycon
>>> from dataclasses import dataclass, field
>>> from datargs import parse
>>> @dataclass
... class Args:
...     retries: int = field(default=3, metadata=dict(help="number of retries", aliases=["-r"], metavar="RETRIES"))
>>> parse(Args, ["-h"])
usage: ...
optional arguments:
  -h, --help            show this help message and exit
  --retries RETRIES, -r RETRIES
>>> parse(Args, ["-r", "4"])
Args(retries=4)
```

`arg` is a replacement for `field` that puts `add_argument()` parameters in `metadata`
and makes `aliases` behaves like in the original method. Use it to save precious keystrokes:
```pycon
>>> from dataclasses import dataclass
>>> from datargs import parse, arg
>>> @dataclass
... class Args:
...     retries: int = arg("-r", default=3, help="number of retries", metavar="RETRIES")
>>> parse(Args, ["-h"])
# exactly the same as before
```

**NOTE**: `arg()` does not currently work with `attr.s`.

`arg()` also supports all `field`/`attr.ib()` keyword arguments.


### `ArgumentParser` options
You can pass `ArgumnetParser` keyword arguments to `argsclass`.
Description is its own parameter - the rest are passed as the `parser_params` parameter as a `dict`.

When a class is used as a subcommand (see below), `parser_params` are passed to `add_parser`, including `aliases`.
```pycon
>>> from datargs import parse, argsclass
>>> @argsclass(description="Romans go home!", parser_params=dict(prog="messiah.py"))
... class Args:
...     flag: bool
>>> parse(Args, ["-h"], parser=parser)
usage: messiah.py [-h] [--flag]
Romans go home!
...
```

or you can pass your own parser:
```pycon
>>> from argparse import ArgumentParser
>>> from datargs import parse, argsclass
>>> @argsclass
... class Args:
...     flag: bool
>>> parser = ArgumentParser(description="Romans go home!", prog="messiah.py")
>>> parse(Args, ["-h"], parser=parser)
usage: messiah.py [-h] [--flag]
Romans go home!
...
```

Use `make_parser()` to create a parser and save it for later:
```pycon
>>> from datargs import make_parser
>>> @dataclass
... class Args:
...     ...
>>> parser = make_parser(Args)  # pass `parser=...` to modify an existing parser
```
**NOTE**: passing your own parser ignores `ArgumentParser` params passed to `argsclass()`.

### Enums
With `datargs`, enums Just Work™:

```pycon
>>> import enum, attr, datargs
>>> class FoodEnum(enum.Enum):
...     ham = 0
...     spam = 1
>>> @attr.dataclass
... class Args:
...     food: FoodEnum
>>> datargs.parse(Args, ["--food", "ham"])
Args(food=<FoodEnum.ham: 0>)
>>> datargs.parse(Args, ["--food", "eggs"])
usage: enum_test.py [-h] --food {ham,spam}
enum_test.py: error: argument --food: invalid choice: 'eggs' (choose from ['ham', 'spam'])
```

**NOTE**: enums are passed by name on the command line and not by value.

## Sequences, Optionals, and Literals
Have a `Sequence` or a `List` of something to
automatically use `nargs`:


```python
from pathlib import Path
from dataclasses import dataclass
from typing import Sequence
from datargs import parse

@dataclass
class Args:
    # same as nargs='*'
    files: Sequence[Path] = ()

args = parse(Args, ["--files", "foo.txt", "bar.txt"])
assert args.files == [Path("foo.txt"), Path("bar.txt")]
```

Specify a list of positional parameters like so:

```python
from datargs import argsclass, arg
@argsclass
class Args:
    arg: Sequence[int] = arg(default=(), positional=True)
```

`Optional` arguments default to `None`:

```python
from pathlib import Path
from dataclasses import dataclass
from typing import Optional
from datargs import parse

@dataclass
class Args:
    path: Optional[Path] = None

args = parse(Args, ["--path", "foo.txt"])
assert args.path == Path("foo.txt")

args = parse(Args, [])
assert args.path is None
```

And `Literal` can be used to specify choices:

```python
from pathlib import Path
from dataclasses import dataclass
from typing import Literal
from datargs import parse

@dataclass
class Args:
    path: Literal[Path("foo.txt"), Path("bar.txt")]

args = parse(Args, ["--path", "foo.txt"])
assert args.path == Path("foo.txt")

# Throws an error!
args = parse(Args, ["--path", "bad-option.txt"])
```

### Sub Commands

No need to specify a useless `dest` to dispatch on different commands.
A `Union` of dataclasses/attrs classes automatically becomes a group of subparsers.
The attribute holding the `Union` holds the appropriate instance
upon parsing, making your code type-safe:

```python
import typing, logging
from datargs import argsclass, arg, parse

@argsclass(description="install package")
class Install:
    package: str = arg(positional=True, help="package to install")

@argsclass(description="show all packages")
class Show:
    verbose: bool = arg(help="show extra info")

@argsclass(description="Pip Install Packages!")
class Pip:
    action: typing.Union[Install, Show]
    log: str = None

args = parse(Pip, ["--log", "debug.log", "install", "my_package"])
print(args)
# prints: Pip(action=Install(package='my_package'), log='debug.log')

# Consume arguments:
if args.log:
    logging.basicConfig(filename=args.log)
if isinstance(args.action, Install):
    install_package(args.action.package)
    # static type error: args.action.verbose
elif isinstance(args.action, Show):
    list_all_packages(verbose=args.action.verbose)
else:
    assert False, "Unreachable code"
```
Command name is derived from class name. To change this, use the `name` parameter to `@argsclass`.

As with all other parameters to `add_parser`,
`aliases` can be passed as a key in `parser_params` to add subcommand aliases.

**NOTE**: if the commented-out line above does not issue a type error, try adding an `@dataclass/@attr.s`
before or instead of `@argsclass()`:

```python
@argsclass(description="Pip Install Packages!")  # optional
@dataclass
class Pip:
    action: typing.Union[Install, Show]
    log: str = None
...
if isinstance(args.action, Install):
    install_package(args.action.package)
    # this should now produce a type error: args.action.verbose
```

## "Why not"s and design choices
Many libraries out there do similar things. This list serves as documentation for existing solutions and differences.

So, why not...

### Just use argparse?
That's easy. The interface is clumsy and repetitive, a.k.a boilerplate. Additionally, `ArgumentParser.parse_args()` returns a `Namespace`, which is
equivalent to `Any`, meaning that it any attribute access is legal when type checking. Alas, invalid attribute access will fail at runtime. For example:
```python
def parse_args():
    parser = ArgumentParser()
    parser.add_argument("--url")
    return parser.parse_args()

def main():
    args = parse_args()
    print(args.url)
```

Let's say for some reason `--url` is changed to `--uri`:

```python
parser.add_argument("--uri")
...
print(args.url)  # oops
```
You won't discover you made a mistake until you run the code. With `datargs`, a static type checker will issue an error.
Also, why use a carriage when you have a spaceship?

### Use [`click`](https://click.palletsprojects.com/en/7.x/)?
`click` is a great library. It provides many utilities for command line programs.

Use `datargs` if you believe user interface should not be coupled with implementation, or if you
want to use `argparse` without boilerplate.
Use `click` if you don't care.


### Use [`clout`](https://clout.readthedocs.io/en/latest/index.html)?
It seems that `clout` aims to be an end-to-end solution for command line programs à la click.

Use it if you need a broader solution. Use `datargs` if you want to use `argparse` without boilerplate.

### Use [`simple-parsing`](https://pypi.org/project/simple-parsing/)?
This is another impressive library.

Use it if you have deeply-nested options, or if the following points don't apply
to you.

Use `datargs` if you:
* need `attrs` support
* want as little magic as possible
* don't have many options or they're not nested
* prefer dashes (`--like-this`) over underscores (`--like_this`)

### Use [`argparse-dataclass`](https://pypi.org/project/argparse-dataclass/)?
It's similar to this library. The main differences I found are:
* no `attrs` support
* not on github, so who you gonna call?

### Use [`argparse-dataclasses`](https://pypi.org/project/argparse-dataclasses/)?
Same points `argparse-dataclass` but also [Uses inheritance](https://refactoring.guru/replace-inheritance-with-delegation).

## FAQs
### Is this cross-platform?
Yes, just like `argparse`.
If you find a bug on a certain platform (or any other bug), please report it.

### Why are mutually exclusive options not supported?

This library is based on the idea of a one-to-one correspondence between most parsers
and simple classes. Conceptually, mutually exclusive options are analogous to
[sum types](https://en.wikipedia.org/wiki/Tagged_union), just like [subparsers](#sub-commands) are,
but writing a class for each flag is not ergonomic enough.
Contact me if you want this feature or if you come up with a better solution.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/roee30/datargs",
    "name": "datargs",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.7",
    "maintainer_email": null,
    "keywords": "argparse, dataclass, attrs",
    "author": "Roee Nizan",
    "author_email": "roeen30@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/b4/45/774220466879035eda65c7a4c0f62ce805b5df32e5e3c93dbf8a543a15ab/datargs-1.1.0.tar.gz",
    "platform": null,
    "description": "# datargs\n\nA paper-thin wrapper around `argparse` that creates type-safe parsers\nfrom `dataclass` and `attrs` classes.\n\n## Quickstart\n\n\nInstall `datargs`:\n\n```bash\npip install datargs\n```\n\nCreate a `dataclass` (or an `attrs` class) describing your command line interface, and call\n`datargs.parse()` with the class:\n\n```python\n# script.py\nfrom dataclasses import dataclass\nfrom pathlib import Path\nfrom datargs import parse\n\n@dataclass  # or @attr.s(auto_attribs=True)\nclass Args:\n    url: str\n    output_path: Path\n    verbose: bool\n    retries: int = 3\n\ndef main():\n    args = parse(Args)\n    print(args)\n\nif __name__ == \"__main__\":\n    main()\n```\n\n***(experimental)*** Alternatively: convert an existing parser to a dataclass:\n```python\n# script.py\nparser = ArgumentParser()\nparser.add_argument(...)\nfrom datargs import convert\nconvert(parser)\n```\n\n`convert()` prints a class definition to the console.\nCopy it to your script.\n\nMypy and pycharm correctly infer the type of `args` as `Args`, and your script is good to go!\n```bash\n$ python script.py -h\nusage: test.py [-h] --url URL --output-path OUTPUT_PATH [--retries RETRIES]\n               [--verbose]\n\noptional arguments:\n  -h, --help            show this help message and exit\n  --url URL\n  --output-path OUTPUT_PATH\n  --retries RETRIES\n  --verbose\n$ python script.py --url \"https://...\" --output-path out --retries 4 --verbose\nArgs(url=\"https://...\", output_path=Path(\"out\"), retries=4, verbose=True)\n```\n\n## Table of Contents\n\n<!-- toc -->\n\n- [Features](#features)\n  * [Static verification](#static-verification)\n  * [`dataclass`/`attr.s` agnostic](#dataclassattrs-agnostic)\n  * [Aliases](#aliases)\n  * [`ArgumentParser` options](#argumentparser-options)\n  * [Enums](#enums)\n  * [Sequences, Optionals, and Literals](#sequences-optionals-and-literals)\n  * [Sub Commands](#sub-commands)\n- [\"Why not\"s and design choices](#why-nots-and-design-choices)\n  * [Just use argparse?](#just-use-argparse)\n  * [Use `click`](#use-clickhttpsclickpalletsprojectscomen7x)?\n  * [Use `clout`](#use-clouthttpscloutreadthedocsioenlatestindexhtml)?\n  * [Use `simple-parsing`](#use-simple-parsinghttpspypiorgprojectsimple-parsing)?\n  * [Use `argparse-dataclass`](#use-argparse-dataclasshttpspypiorgprojectargparse-dataclass)?\n  * [Use `argparse-dataclasses`](#use-argparse-dataclasseshttpspypiorgprojectargparse-dataclasses)?\n- [FAQs](#faqs)\n  * [Is this cross-platform?](#is-this-cross-platform)\n  * [Why are mutually exclusive options not supported?](#why-are-mutually-exclusive-options-not-supported)\n\n<!-- tocstop -->\n\n## Features\n\n### Static verification\nMypy/Pycharm have your back when you when you make a mistake:\n```python\n...\ndef main():\n    args = parse(Args)\n    args.urll  # typo\n...\n```\nPycharm says: `Unresolved attribute reference 'urll' for class 'Args'`.\n\nMypy says: `script.py:15: error: \"Args\" has no attribute \"urll\"; maybe \"url\"?`\n\n\n### `dataclass`/`attr.s` agnostic\n```pycon\n>>> import attr, datargs\n>>> @attr.s\n... class Args:\n...     flag: bool = attr.ib()\n>>> datargs.parse(Args, [])\nArgs(flag=False)\n```\n\n### Aliases\nAliases and `ArgumentParser.add_argument()` parameters are taken from `metadata`:\n\n```pycon\n>>> from dataclasses import dataclass, field\n>>> from datargs import parse\n>>> @dataclass\n... class Args:\n...     retries: int = field(default=3, metadata=dict(help=\"number of retries\", aliases=[\"-r\"], metavar=\"RETRIES\"))\n>>> parse(Args, [\"-h\"])\nusage: ...\noptional arguments:\n  -h, --help            show this help message and exit\n  --retries RETRIES, -r RETRIES\n>>> parse(Args, [\"-r\", \"4\"])\nArgs(retries=4)\n```\n\n`arg` is a replacement for `field` that puts `add_argument()` parameters in `metadata`\nand makes `aliases` behaves like in the original method. Use it to save precious keystrokes:\n```pycon\n>>> from dataclasses import dataclass\n>>> from datargs import parse, arg\n>>> @dataclass\n... class Args:\n...     retries: int = arg(\"-r\", default=3, help=\"number of retries\", metavar=\"RETRIES\")\n>>> parse(Args, [\"-h\"])\n# exactly the same as before\n```\n\n**NOTE**: `arg()` does not currently work with `attr.s`.\n\n`arg()` also supports all `field`/`attr.ib()` keyword arguments.\n\n\n### `ArgumentParser` options\nYou can pass `ArgumnetParser` keyword arguments to `argsclass`.\nDescription is its own parameter - the rest are passed as the `parser_params` parameter as a `dict`.\n\nWhen a class is used as a subcommand (see below), `parser_params` are passed to `add_parser`, including `aliases`.\n```pycon\n>>> from datargs import parse, argsclass\n>>> @argsclass(description=\"Romans go home!\", parser_params=dict(prog=\"messiah.py\"))\n... class Args:\n...     flag: bool\n>>> parse(Args, [\"-h\"], parser=parser)\nusage: messiah.py [-h] [--flag]\nRomans go home!\n...\n```\n\nor you can pass your own parser:\n```pycon\n>>> from argparse import ArgumentParser\n>>> from datargs import parse, argsclass\n>>> @argsclass\n... class Args:\n...     flag: bool\n>>> parser = ArgumentParser(description=\"Romans go home!\", prog=\"messiah.py\")\n>>> parse(Args, [\"-h\"], parser=parser)\nusage: messiah.py [-h] [--flag]\nRomans go home!\n...\n```\n\nUse `make_parser()` to create a parser and save it for later:\n```pycon\n>>> from datargs import make_parser\n>>> @dataclass\n... class Args:\n...     ...\n>>> parser = make_parser(Args)  # pass `parser=...` to modify an existing parser\n```\n**NOTE**: passing your own parser ignores `ArgumentParser` params passed to `argsclass()`.\n\n### Enums\nWith `datargs`, enums Just Work\u2122:\n\n```pycon\n>>> import enum, attr, datargs\n>>> class FoodEnum(enum.Enum):\n...     ham = 0\n...     spam = 1\n>>> @attr.dataclass\n... class Args:\n...     food: FoodEnum\n>>> datargs.parse(Args, [\"--food\", \"ham\"])\nArgs(food=<FoodEnum.ham: 0>)\n>>> datargs.parse(Args, [\"--food\", \"eggs\"])\nusage: enum_test.py [-h] --food {ham,spam}\nenum_test.py: error: argument --food: invalid choice: 'eggs' (choose from ['ham', 'spam'])\n```\n\n**NOTE**: enums are passed by name on the command line and not by value.\n\n## Sequences, Optionals, and Literals\nHave a `Sequence` or a `List` of something to\nautomatically use `nargs`:\n\n\n```python\nfrom pathlib import Path\nfrom dataclasses import dataclass\nfrom typing import Sequence\nfrom datargs import parse\n\n@dataclass\nclass Args:\n    # same as nargs='*'\n    files: Sequence[Path] = ()\n\nargs = parse(Args, [\"--files\", \"foo.txt\", \"bar.txt\"])\nassert args.files == [Path(\"foo.txt\"), Path(\"bar.txt\")]\n```\n\nSpecify a list of positional parameters like so:\n\n```python\nfrom datargs import argsclass, arg\n@argsclass\nclass Args:\n    arg: Sequence[int] = arg(default=(), positional=True)\n```\n\n`Optional` arguments default to `None`:\n\n```python\nfrom pathlib import Path\nfrom dataclasses import dataclass\nfrom typing import Optional\nfrom datargs import parse\n\n@dataclass\nclass Args:\n    path: Optional[Path] = None\n\nargs = parse(Args, [\"--path\", \"foo.txt\"])\nassert args.path == Path(\"foo.txt\")\n\nargs = parse(Args, [])\nassert args.path is None\n```\n\nAnd `Literal` can be used to specify choices:\n\n```python\nfrom pathlib import Path\nfrom dataclasses import dataclass\nfrom typing import Literal\nfrom datargs import parse\n\n@dataclass\nclass Args:\n    path: Literal[Path(\"foo.txt\"), Path(\"bar.txt\")]\n\nargs = parse(Args, [\"--path\", \"foo.txt\"])\nassert args.path == Path(\"foo.txt\")\n\n# Throws an error!\nargs = parse(Args, [\"--path\", \"bad-option.txt\"])\n```\n\n### Sub Commands\n\nNo need to specify a useless `dest` to dispatch on different commands.\nA `Union` of dataclasses/attrs classes automatically becomes a group of subparsers.\nThe attribute holding the `Union` holds the appropriate instance\nupon parsing, making your code type-safe:\n\n```python\nimport typing, logging\nfrom datargs import argsclass, arg, parse\n\n@argsclass(description=\"install package\")\nclass Install:\n    package: str = arg(positional=True, help=\"package to install\")\n\n@argsclass(description=\"show all packages\")\nclass Show:\n    verbose: bool = arg(help=\"show extra info\")\n\n@argsclass(description=\"Pip Install Packages!\")\nclass Pip:\n    action: typing.Union[Install, Show]\n    log: str = None\n\nargs = parse(Pip, [\"--log\", \"debug.log\", \"install\", \"my_package\"])\nprint(args)\n# prints: Pip(action=Install(package='my_package'), log='debug.log')\n\n# Consume arguments:\nif args.log:\n    logging.basicConfig(filename=args.log)\nif isinstance(args.action, Install):\n    install_package(args.action.package)\n    # static type error: args.action.verbose\nelif isinstance(args.action, Show):\n    list_all_packages(verbose=args.action.verbose)\nelse:\n    assert False, \"Unreachable code\"\n```\nCommand name is derived from class name. To change this, use the `name` parameter to `@argsclass`.\n\nAs with all other parameters to `add_parser`,\n`aliases` can be passed as a key in `parser_params` to add subcommand aliases.\n\n**NOTE**: if the commented-out line above does not issue a type error, try adding an `@dataclass/@attr.s`\nbefore or instead of `@argsclass()`:\n\n```python\n@argsclass(description=\"Pip Install Packages!\")  # optional\n@dataclass\nclass Pip:\n    action: typing.Union[Install, Show]\n    log: str = None\n...\nif isinstance(args.action, Install):\n    install_package(args.action.package)\n    # this should now produce a type error: args.action.verbose\n```\n\n## \"Why not\"s and design choices\nMany libraries out there do similar things. This list serves as documentation for existing solutions and differences.\n\nSo, why not...\n\n### Just use argparse?\nThat's easy. The interface is clumsy and repetitive, a.k.a boilerplate. Additionally, `ArgumentParser.parse_args()` returns a `Namespace`, which is\nequivalent to `Any`, meaning that it any attribute access is legal when type checking. Alas, invalid attribute access will fail at runtime. For example:\n```python\ndef parse_args():\n    parser = ArgumentParser()\n    parser.add_argument(\"--url\")\n    return parser.parse_args()\n\ndef main():\n    args = parse_args()\n    print(args.url)\n```\n\nLet's say for some reason `--url` is changed to `--uri`:\n\n```python\nparser.add_argument(\"--uri\")\n...\nprint(args.url)  # oops\n```\nYou won't discover you made a mistake until you run the code. With `datargs`, a static type checker will issue an error.\nAlso, why use a carriage when you have a spaceship?\n\n### Use [`click`](https://click.palletsprojects.com/en/7.x/)?\n`click` is a great library. It provides many utilities for command line programs.\n\nUse `datargs` if you believe user interface should not be coupled with implementation, or if you\nwant to use `argparse` without boilerplate.\nUse `click` if you don't care.\n\n\n### Use [`clout`](https://clout.readthedocs.io/en/latest/index.html)?\nIt seems that `clout` aims to be an end-to-end solution for command line programs \u00e0 la click.\n\nUse it if you need a broader solution. Use `datargs` if you want to use `argparse` without boilerplate.\n\n### Use [`simple-parsing`](https://pypi.org/project/simple-parsing/)?\nThis is another impressive library.\n\nUse it if you have deeply-nested options, or if the following points don't apply\nto you.\n\nUse `datargs` if you:\n* need `attrs` support\n* want as little magic as possible\n* don't have many options or they're not nested\n* prefer dashes (`--like-this`) over underscores (`--like_this`)\n\n### Use [`argparse-dataclass`](https://pypi.org/project/argparse-dataclass/)?\nIt's similar to this library. The main differences I found are:\n* no `attrs` support\n* not on github, so who you gonna call?\n\n### Use [`argparse-dataclasses`](https://pypi.org/project/argparse-dataclasses/)?\nSame points `argparse-dataclass` but also [Uses inheritance](https://refactoring.guru/replace-inheritance-with-delegation).\n\n## FAQs\n### Is this cross-platform?\nYes, just like `argparse`.\nIf you find a bug on a certain platform (or any other bug), please report it.\n\n### Why are mutually exclusive options not supported?\n\nThis library is based on the idea of a one-to-one correspondence between most parsers\nand simple classes. Conceptually, mutually exclusive options are analogous to\n[sum types](https://en.wikipedia.org/wiki/Tagged_union), just like [subparsers](#sub-commands) are,\nbut writing a class for each flag is not ergonomic enough.\nContact me if you want this feature or if you come up with a better solution.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Declarative, type-safe command line argument parsers from dataclasses and attrs classes",
    "version": "1.1.0",
    "project_urls": {
        "Homepage": "https://github.com/roee30/datargs",
        "Repository": "https://github.com/roee30/datargs"
    },
    "split_keywords": [
        "argparse",
        " dataclass",
        " attrs"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "47c77726bb43d5230a9ed8c812614aef7bbbc3527b803b92134925d761621e8f",
                "md5": "42181dc30f3b3acca71cbbb12e377d06",
                "sha256": "1bf3c3f52e02cd93990abcfb4afe6b182557eb3375b116d7a83d158ec00e18b3"
            },
            "downloads": -1,
            "filename": "datargs-1.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "42181dc30f3b3acca71cbbb12e377d06",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.7",
            "size": 16490,
            "upload_time": "2024-05-11T13:35:15",
            "upload_time_iso_8601": "2024-05-11T13:35:15.605216Z",
            "url": "https://files.pythonhosted.org/packages/47/c7/7726bb43d5230a9ed8c812614aef7bbbc3527b803b92134925d761621e8f/datargs-1.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b445774220466879035eda65c7a4c0f62ce805b5df32e5e3c93dbf8a543a15ab",
                "md5": "09a8492173912dbde29db8491ae6f231",
                "sha256": "f0918b3a4fc2a903cfd0d221dfb09d1be2febc3c6bbe4ae2e196405b7120bf13"
            },
            "downloads": -1,
            "filename": "datargs-1.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "09a8492173912dbde29db8491ae6f231",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.7",
            "size": 19266,
            "upload_time": "2024-05-11T13:35:17",
            "upload_time_iso_8601": "2024-05-11T13:35:17.540277Z",
            "url": "https://files.pythonhosted.org/packages/b4/45/774220466879035eda65c7a4c0f62ce805b5df32e5e3c93dbf8a543a15ab/datargs-1.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-05-11 13:35:17",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "roee30",
    "github_project": "datargs",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "tox": true,
    "lcname": "datargs"
}
        
Elapsed time: 3.11895s