Skip to content

Commit bee2f04

Browse files
authored
Merge pull request #28 from mosquito/skip-inaccesible-configs
Skip unreadable config files
2 parents 09c63a5 + 61de228 commit bee2f04

7 files changed

Lines changed: 399 additions & 211 deletions

File tree

.github/rich_example.png

126 KB
Loading

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ jobs:
5555

5656
matrix:
5757
python:
58-
- '3.8'
5958
- '3.9'
6059
- '3.10'
6160
- '3.11'
6261
- '3.12'
62+
- '3.13'
6363
steps:
6464
- uses: actions/checkout@v2
6565
- name: Setup python${{ matrix.python }}

README.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,35 @@ parser.parse_args(["--numbers", "1", "2", "3"])
114114
assert parser.numbers == frozenset([1, 2, 3])
115115
```
116116

117+
## Boolean arguments
118+
119+
Boolean arguments can be specified using like this:
120+
121+
<!--- name: test_bools --->
122+
```python
123+
import argclass
124+
125+
126+
class ArgumentParser(argclass.Parser):
127+
# Complete form you have to set default and action
128+
stored_true: bool = argclass.Argument(
129+
action=argclass.Actions.STORE_TRUE,
130+
default=False
131+
)
132+
# Short form with default value
133+
# This is the alias for: argclass.Argument(action=argclass.Actions.STORE_TRUE, default=False)
134+
stored_true_short: bool = False
135+
# This is the alias for: argclass.Argument(action=argclass.Actions.STORE_FALSE, default=True)
136+
stored_false: bool = True
137+
138+
139+
parser = ArgumentParser(auto_env_var_prefix='APP_')
140+
arguments = parser.parse_args(["--stored-true-short"])
141+
assert arguments.stored_true is False
142+
assert arguments.stored_true_short is True
143+
assert arguments.stored_false is True
144+
```
145+
117146
## Configuration Files
118147

119148
Parser objects can get default values from environment variables or from specified configuration files.
@@ -538,3 +567,72 @@ parser.parse_args(["--gizmo=off", "--optional=10"])
538567
assert parser.gizmo is False
539568
assert parser.optional == 10
540569
```
570+
571+
# 3rd Party Libraries integration
572+
573+
`argclass` is able to integrate with some 3rd party libraries to provide additional features.
574+
575+
## `Rich` and `rich_argparse` integration examples
576+
577+
`rich_argparse` is a library that provides an ability to use `rich` for formatting help messages in `argparse`.
578+
So this library can be used with `argclass` to provide a rich help output.
579+
580+
```python
581+
from argparse import Action
582+
583+
import argclass
584+
from rich.console import ConsoleRenderable, Group
585+
from rich.markdown import Markdown
586+
from rich.panel import Panel
587+
from rich.syntax import Syntax
588+
from rich.text import Text
589+
from rich_argparse import RawTextRichHelpFormatter
590+
591+
592+
class HelpFormatter(RawTextRichHelpFormatter):
593+
def _rich_expand_help(self, action: Action) -> Text:
594+
try:
595+
if "%" in str(action.default):
596+
action.default = ""
597+
if "%" in str(action.help):
598+
action.help = ""
599+
return super()._rich_expand_help(action)
600+
except ValueError:
601+
return Text("FAILED")
602+
603+
604+
class RichParser(argclass.Parser):
605+
def __init__(self, *args, **kwargs) -> None:
606+
help = kwargs.pop("help", None)
607+
description = kwargs.pop("description", help) or ""
608+
609+
if isinstance(description, ConsoleRenderable):
610+
kwargs["description"] = description
611+
else:
612+
kwargs["description"] = Markdown(description)
613+
614+
if help is not None:
615+
kwargs["help"] = help
616+
617+
kwargs["formatter_class"] = HelpFormatter
618+
super().__init__(*args, **kwargs)
619+
620+
621+
class Parser(RichParser):
622+
log_level = argclass.LogLevel
623+
624+
625+
if __name__ == "__main__":
626+
parser = Parser(
627+
formatter_class=RawTextRichHelpFormatter,
628+
description=Group(
629+
Text("This code produces this help:\n\n"),
630+
Panel(Syntax(open(__file__).read().strip(), "python")),
631+
),
632+
)
633+
parser.parse_args()
634+
parser.sanitize_env()
635+
exit(parser())
636+
```
637+
638+
[![Help Output](https://raw.githubusercontent.com/mosquito/argclass/master/docs/images/rich_help_output.png)](https://raw.githubusercontent.com/mosquito/argclass/master/.github/rich_example.png)

argclass/__init__.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ def read_configs(
3434
kwargs.setdefault("strict", False)
3535
parser = configparser.ConfigParser(**kwargs)
3636

37-
filenames = list(
38-
map(
39-
lambda p: p.resolve(),
40-
filter(
41-
lambda p: p.is_file(),
42-
map(lambda x: Path(x).expanduser(), paths),
43-
),
44-
),
45-
)
37+
filenames = []
38+
for path in paths:
39+
path_obj = Path(path).expanduser().resolve()
40+
# check the access first, because the parent
41+
# directory may not be readable
42+
if not os.access(path_obj, os.R_OK) or not path_obj.exists():
43+
continue
44+
filenames.append(path_obj)
45+
4646
config_paths = parser.read(filenames)
4747

4848
result: Dict[str, Union[str, Dict[str, str]]] = dict(
@@ -398,8 +398,10 @@ def _make_action_true_argument(
398398
if kind is bool:
399399
if default is False:
400400
kw["action"] = Actions.STORE_TRUE
401+
kw["default"] = False
401402
elif default is True:
402403
kw["action"] = Actions.STORE_FALSE
404+
kw["default"] = True
403405
else:
404406
raise TypeError(f"Can not set default {default!r} for bool")
405407
elif kind == Optional[bool]:
@@ -602,12 +604,15 @@ def get_env_var(self, name: str, argument: TypedArgument) -> Optional[str]:
602604
def __init__(
603605
self, config_files: Iterable[Union[str, Path]] = (),
604606
auto_env_var_prefix: Optional[str] = None,
607+
strict_config: bool = False,
605608
**kwargs: Any,
606609
):
607610
super().__init__()
608611
self.current_subparsers = ()
609612
self._config_files = config_files
610-
self._config, filenames = read_configs(*config_files)
613+
self._config, filenames = read_configs(
614+
*config_files, strict=strict_config
615+
)
611616

612617
self._epilog = kwargs.pop("epilog", "")
613618

0 commit comments

Comments
 (0)