Skip to content

Commit

Permalink
Add dict_option() and dict_positional() helpers
Browse files Browse the repository at this point in the history
Kuba314 committed Dec 30, 2023
1 parent d3b0be5 commit 95f7d58
Showing 4 changed files with 130 additions and 0 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -124,6 +124,21 @@ class Args:
results: list[Result] = option(converter=itemwise(Result.from_int))
```

### dict helpers
Sometimes creating an argument able to choose a value from a dict by its key is desired. `dict_option` and `dict_positional` do exactly that. In the following example passing `--foo yes` will result in `.foo` being `True`.
```py
from arcparse import dict_option

values = {
"yes": True,
"no": False,
}

@arcparser
class Args:
foo: bool = dict_option(values)
```

### Mutually exclusive groups
Use `mx_group` to group multiple arguments together in a mutually exclusive group. Each argument has to have a default defined either implicitly through the type (being `bool` or a union with `None`) or explicitly with `default`.
```py
4 changes: 4 additions & 0 deletions arcparse/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from ._argument_helpers import (
dict_option,
dict_positional,
flag,
mx_group,
no_flag,
@@ -18,6 +20,8 @@
"flag",
"no_flag",
"tri_flag",
"dict_positional",
"dict_option",
"mx_group",
"subparsers",
"itemwise",
59 changes: 59 additions & 0 deletions arcparse/_argument_helpers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from collections.abc import Callable, Collection
from typing import Any

from arcparse.errors import InvalidArgument

from ._arguments import Void, void
from ._partial_arguments import (
PartialFlag,
@@ -98,3 +100,60 @@ def mx_group(*, required: bool = False) -> PartialMxGroup:

def subparsers(*args: str) -> Any:
return PartialSubparsers(names=list(args))


def dict_positional[T](
dict_: dict[str, T],
*,
default: T | Void = void,
name_override: str | None = None,
at_least_one: bool = False,
mx_group: PartialMxGroup | None = None,
help: str | None = None,
) -> T:
"""Creates positional() from dict by pre-filling choices and converter"""

if default is not void and default not in dict_.values():
raise InvalidArgument("dict_positional default must be a value in dict")

return positional(
default=default,
choices=list(dict_.keys()),
converter=dict_.__getitem__,
name_override=name_override,
at_least_one=at_least_one,
mx_group=mx_group,
help=help,
)



def dict_option[T](
dict_: dict[str, T],
*,
short: str | None = None,
short_only: bool = False,
default: T | Void = void,
name_override: str | None = None,
append: bool = False,
at_least_one: bool = False,
mx_group: PartialMxGroup | None = None,
help: str | None = None,
) -> T:
"""Creates option() from dict by pre-filling choices and converter"""

if default is not void and default not in dict_.values():
raise InvalidArgument("dict_positional default must be a value in dict")

return option(
short=short,
short_only=short_only,
default=default,
choices=list(dict_.keys()),
converter=dict_.__getitem__,
name_override=name_override,
append=append,
at_least_one=at_least_one,
mx_group=mx_group,
help=help,
)
52 changes: 52 additions & 0 deletions tests/test_dict_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import pytest

from arcparse import arcparser, dict_option, dict_positional
from arcparse.errors import InvalidArgument


dict_ = {
"foo": 1,
"bar": 0,
}


def test_dict_positional_default_in_dict() -> None:
with pytest.raises(InvalidArgument):
dict_positional(dict_, default=2)


@pytest.mark.parametrize(
"arg_string,value",
[
("foo", 1),
("bar", 0),
]
)
def test_dict_positional(arg_string: str, value: int) -> None:
@arcparser
class Args:
foo_bar: int = dict_positional(dict_)

parsed = Args.parse(arg_string.split())
assert parsed.foo_bar == value


def test_dict_option_default_in_dict() -> None:
with pytest.raises(InvalidArgument):
dict_option(dict_, default=2)


@pytest.mark.parametrize(
"arg_string,value",
[
("--foo-bar foo", 1),
("--foo-bar bar", 0),
]
)
def test_dict_option(arg_string: str, value: int) -> None:
@arcparser
class Args:
foo_bar: int = dict_option(dict_)

parsed = Args.parse(arg_string.split())
assert parsed.foo_bar == value

0 comments on commit 95f7d58

Please sign in to comment.