Skip to content

Commit

Permalink
Improve literal metavar ordering
Browse files Browse the repository at this point in the history
  • Loading branch information
brentyi committed Jan 24, 2022
1 parent 36ab615 commit 8a29b36
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 7 deletions.
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ Returns:

**Example usage**

If you're familiar with dataclasses, writing a script with `dcargs.parse()` is
simple!

```python
# examples/simple.py
import dataclasses

import dcargs
Expand All @@ -81,7 +85,7 @@ if __name__ == "__main__":
print(args)
```

Which we can run to get:
We can run this to get:

```
$ python simple.py --help
Expand All @@ -101,7 +105,7 @@ $ python simple.py --field1 string --field2 4
Args(field1='string', field2=4, flag=False)
```

Note that we support significantly more complex structures and annotations,
Note that we also support significantly more complex structures and annotations,
including nested dataclasses, container types, generics, optional and default
arguments, enums, and more. Examples of additional features can be found in the
[examples](./examples/) and [unit tests](./tests/); a
Expand All @@ -111,9 +115,9 @@ arguments, enums, and more. Examples of additional features can be found in the

Compared to other options, using dataclasses for configuration is:

- **Low-effort.** Type annotations, docstrings, and default values for dataclass
- **Low effort.** Type annotations, docstrings, and default values for dataclass
fields can be used to automatically generate argument parsers.
- **Non-invasive.** Dataclasses themselves are part of the standard Python
- **Noninvasive.** Dataclasses themselves are part of the standard Python
library; defining them requires no external dependencies and they can be
easily instantiated without `dcargs` (for example, within quick experiments in
Jupyter notebooks).
Expand All @@ -123,7 +127,7 @@ Compared to other options, using dataclasses for configuration is:
of configurable fields across modules or source files. A model configuration
dataclass, for example, can be co-located in its entirety with the model
implementation and dropped into any experiment configuration dataclass with an
import --- this eliminates the redundancy you typically see with the argparse
import this eliminates the redundancy you typically see with the argparse
equivalent and makes the entire module easy to port across codebases.
- **Strongly typed.** Unlike dynamic configuration namespaces produced by
libraries like `argparse`, `YACS`, `abseil`, or `ml_collections`, dataclasses
Expand All @@ -142,7 +146,7 @@ dataclass serialization:
<code><strong>dcargs.to_yaml</strong>(instance: T) -> str</code> convert
between YAML-style strings and dataclass instances.

The functions attempt to strike a balance between flexibility and robustness ---
The functions attempt to strike a balance between flexibility and robustness
in contrast to naively dumping or loading dataclass instances (via pickle,
PyYAML, etc), explicit type references enable custom tags that are robust
against code reorganization and refactor, while a PyYAML backend enables
Expand Down
3 changes: 2 additions & 1 deletion dcargs/_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,8 @@ def transform_choices_from_literals(arg: ArgumentDefinition) -> ArgumentDefiniti
return dataclasses.replace(
arg,
type=type(next(iter(choices))),
choices=set(map(str, choices)),
# We use a list and not a set to preserve ordering.
choices=list(map(str, choices)),
)
else:
return arg
Expand Down
29 changes: 29 additions & 0 deletions tests/test_docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Generic, List, Optional, Tuple, TypeVar

import pytest
from typing_extensions import Literal

import dcargs

Expand Down Expand Up @@ -204,3 +205,31 @@ class GenericTupleHelptext(Generic[T]):
dcargs.parse(GenericTupleHelptext[int], args=["--help"])
helptext = f.getvalue()
assert "--x INT [INT ...]\n" in helptext


def test_literal_helptext():
@dataclasses.dataclass
class LiteralHelptext:
x: Literal[1, 2, 3]
"""A number."""

f = io.StringIO()
with pytest.raises(SystemExit):
with contextlib.redirect_stdout(f):
dcargs.parse(LiteralHelptext, args=["--help"])
helptext = f.getvalue()
assert "--x {1,2,3} A number.\n" in helptext


def test_optional_literal_helptext():
@dataclasses.dataclass
class OptionalLiteralHelptext:
x: Optional[Literal[1, 2, 3]]
"""A number."""

f = io.StringIO()
with pytest.raises(SystemExit):
with contextlib.redirect_stdout(f):
dcargs.parse(OptionalLiteralHelptext, args=["--help"])
helptext = f.getvalue()
assert "--x {1,2,3} A number. (default: None)\n" in helptext

0 comments on commit 8a29b36

Please sign in to comment.