Skip to content

Commit

Permalink
style: fix typing annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
pwwang committed Aug 10, 2024
1 parent ab88428 commit 3c467b9
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 55 deletions.
9 changes: 5 additions & 4 deletions argx/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,11 @@ def __call__( # type: ignore[override]
option_string: str | None = None,
) -> None:
ns, dest = get_ns_dest(namespace, self.dest)
try:
parsed = json.loads(values)
except json.JSONDecodeError:
parser.error(f"Invalid json for {option_string}: {values}")
if isinstance(values, str):
try:
parsed = json.loads(values)
except json.JSONDecodeError:
parser.error(f"Invalid json for {option_string}: {values}")

if not isinstance(parsed, dict):
parser.error(
Expand Down
4 changes: 2 additions & 2 deletions argx/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .utils import showable

if TYPE_CHECKING:
from argparse import _ArgumentGroup
from argparse import _MutuallyExclusiveGroup


def _wrap_text(text: str, width: int, indent: str = "") -> List[str]:
Expand Down Expand Up @@ -51,7 +51,7 @@ def _format_usage(
self,
usage: str | None,
actions: Iterable[Action],
groups: Iterable[_ArgumentGroup],
groups: Iterable[_MutuallyExclusiveGroup],
prefix: str | None,
) -> str:
prefix_is_none = prefix is None
Expand Down
102 changes: 58 additions & 44 deletions argx/parser.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import sys
from typing import IO, TYPE_CHECKING, Any, Callable, Sequence
from typing import IO, TYPE_CHECKING, Any, Callable, Iterable, Sequence
from pathlib import Path
from gettext import gettext as _
from argparse import (
Expand Down Expand Up @@ -45,7 +45,8 @@

@add_attribute("level", 0)
class ArgumentParser(APArgumentParser):
"""Class to handle command line arguments and configuration files"""
"""Supercharged ArgumentParser for parsing command line strings into
Python objects."""

def __init__(
self,
Expand All @@ -63,8 +64,33 @@ def __init__(
allow_abbrev: bool = True,
exit_on_error: bool = True,
exit_on_void: bool = False,
pre_parse: Callable[[ArgumentParser], None] | None = None,
pre_parse: (
Callable[[ArgumentParser, Sequence[str], Namespace], None] | None
) = None,
) -> None:
"""Create an ArgumentParser
Args:
prog (str, optional): The program name
usage (str, optional): The usage message
description (str, optional): The description message
epilog (str, optional): The epilog message
parents (Sequence[ArgumentParser], optional): The parent parsers
formatter_class (type, optional): The formatter class
prefix_chars (str, optional): The prefix characters
fromfile_prefix_chars (str, optional): The prefix characters for
configuration files
argument_default (Any, optional): The default value for arguments
conflict_handler (str, optional): The conflict handler
add_help (bool | str, optional): Whether to add the help option
allow_abbrev (bool, optional): Whether to allow abbreviation
exit_on_error (bool, optional): Whether to exit on error
exit_on_void (bool, optional): Whether to exit on void arguments
Added by `argx`.
pre_parse (Callable[[ArgumentParser], None], optional): The
function to call before parsing.
Added by `argx`.
"""
old_add_help = add_help
add_help = False
kwargs = {
Expand All @@ -87,7 +113,7 @@ def __init__(

self.exit_on_void = exit_on_void
self.pre_parse = pre_parse
self._subparsers_action = None
self._subparsers_action: _SubParsersAction | None = None

# Register our actions to override argparse's or add new ones
self.register("action", None, StoreAction)
Expand All @@ -111,9 +137,7 @@ def __init__(
self.register("type", "auto", type_.auto)

# Add help option to support + for more options
default_prefix = (
"-" if "-" in self.prefix_chars else self.prefix_chars[0]
)
default_prefix = "-" if "-" in self.prefix_chars else self.prefix_chars[0]
if old_add_help is True:
self.add_argument(
f"{default_prefix}h",
Expand All @@ -130,9 +154,7 @@ def __init__(
f"{default_prefix * 2}help+",
action="help",
default=SUPPRESS,
help=_(
"show help message (with + to show more options) and exit"
),
help=_("show help message (with + to show more options) and exit"),
)
# restore add_help
self.add_help = old_add_help # type: ignore[assignment]
Expand All @@ -142,11 +164,12 @@ def __init__(
order=-1,
)

def add_subparsers(self, order: int = 99, **kwargs):
def add_subparsers(self, order: int = 99, **kwargs) -> _SubParsersAction:
"""Add subparsers to the parser.
Args:
order (int): The order of the subparsers group
Added by `argx`.
**kwargs: The arguments to pass to add_argument_group()
Returns:
Expand All @@ -162,7 +185,7 @@ def add_subparsers(self, order: int = 99, **kwargs):
return action

def add_subparser(self, name: str, **kwargs) -> ArgumentParser | Any:
"""Add a subparser directly.
"""Add a subparser directly, a shortcut to add sub-commands by `argx`.
Instead of
>>> subparsers = parser.add_subparsers(...)
Expand Down Expand Up @@ -191,18 +214,16 @@ def add_subparser(self, name: str, **kwargs) -> ArgumentParser | Any:
self._subparsers_action = self.add_subparsers(
title=_("subcommands"),
required=True,
dest="COMMAND"
if self.level == 0
else f"COMMAND{self.level+1}",
dest="COMMAND" if self.level == 0 else f"COMMAND{self.level + 1}",
)
kwargs.setdefault("help", f"The {name} command")
return self._subparsers_action.add_parser(
name, level=self.level + 1, **kwargs
)
out = self._subparsers_action.add_parser(name, **kwargs)
out.level = self.level + 1
return out

add_command = add_subparser

def parse_known_args(
def parse_known_args( # type: ignore[override]
self,
args: Sequence[str] | None = None,
namespace: Namespace | None = None,
Expand All @@ -211,16 +232,18 @@ def parse_known_args(
) -> tuple[Namespace, list[str]]:
"""Parse known arguments.
Modify to handle exit_on_void and @file to load default values.
Modify to handle exit_on_void and @file to load default values by `argx`.
Args:
args: The arguments to parse.
namespace: The namespace to use.
fromfile_parse: Whether to parse @file.
Added by `argx`.
fromfile_keep: Whether to keep @file in the unknown arguments.
Note that @file.txt file will be treated as a normal argument,
thus, it will be parsed and not kept anyway. This means at
this point, @file.txt will be expanded right away.
Added by `argx`.
"""
if args is None: # pragma: no cover
# args default to the system args
Expand Down Expand Up @@ -268,12 +291,12 @@ def parse_known_args(

if fromfile_parse:
# parse @file to set the default values
conf = arg[1:]
if conf.endswith(".py"):
conf_file = arg[1:]
if conf_file.endswith(".py"):
try:
conf = import_pyfile(conf)
conf = import_pyfile(conf_file)
except Exception as e:
self.error(f"Cannot import [{conf}]: {e}")
self.error(f"Cannot import [{conf_file}]: {e}")
self.set_defaults_from_configs(conf)

# add any action defaults that aren't present
Expand Down Expand Up @@ -338,12 +361,8 @@ def _add_action(self, action: _ActionT) -> _ActionT:
Modify to handle namespace actions, like "--group.abc"
"""
if (
isinstance(action, APArgumentGroup)
or (
not isinstance(action, NamespaceAction)
and "." not in action.dest
)
if isinstance(action, APArgumentGroup) or (
not isinstance(action, NamespaceAction) and "." not in action.dest
):
if action.required:
return self._required_actions._add_action(action)
Expand All @@ -353,7 +372,7 @@ def _add_action(self, action: _ActionT) -> _ActionT:
action.dest = action.option_strings[0].lstrip(self.prefix_chars)
# Split the destination into a list of keys
keys = action.dest.split(".")
seq = range(len(keys) - 1, 0, -1)
seq: Iterable[int] = range(len(keys) - 1, 0, -1)
# Add --ns, --ns.subns also to their now group
if isinstance(action, NamespaceAction):
seq = [None] + list(seq)
Expand Down Expand Up @@ -406,7 +425,8 @@ def add_namespace(
if title is None:
title = f"{_('namespace')} <{name}>"

group = _NamespaceArgumentGroup(self, title, name=name, **kwargs)
group = _NamespaceArgumentGroup(self, title, **kwargs)
group.name = name
self._action_groups.append(group)
return group

Expand Down Expand Up @@ -441,9 +461,7 @@ def format_help(self, plus: bool = True) -> str:
formatter = self._get_formatter()

# usage
formatter.add_usage(
self.usage, self._actions, self._mutually_exclusive_groups
)
formatter.add_usage(self.usage, self._actions, self._mutually_exclusive_groups)

# description
formatter.add_text(self.description)
Expand All @@ -459,9 +477,7 @@ def format_help(self, plus: bool = True) -> str:
continue

formatter.start_section(
"\033[1m\033[4m"
f"{format_title(action_group.title)}"
"\033[0m\033[0m"
"\033[1m\033[4m" f"{format_title(action_group.title)}" "\033[0m\033[0m"
)
formatter.add_text(action_group.description)
formatter.add_arguments( # type: ignore[call-arg]
Expand Down Expand Up @@ -538,11 +554,7 @@ def _registry_get(
default: Any = None,
) -> Any:
# Allow type to be string
if (
registry_name == "type"
and isinstance(value, str)
and default == value
):
if registry_name == "type" and isinstance(value, str) and default == value:
import builtins

# "int", "float", "str", "open", etc
Expand All @@ -565,7 +577,9 @@ def _registry_get(

@classmethod
def from_configs(cls, *configs: dict | str, **kwargs) -> ArgumentParser:
"""Create an ArgumentParser from a configuration file
"""Create an ArgumentParser from a configuration file.
Added by `argx`.
Args:
*configs: The configuration files or dicts to load
Expand Down
13 changes: 9 additions & 4 deletions argx/utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Callable, Type, TypeVar
from argparse import Namespace
from typing import TYPE_CHECKING, Any, Callable, Type

if TYPE_CHECKING:
from os import PathLike

T = TypeVar("T", bound=Type)


def import_pyfile(pyfile: PathLike | str) -> dict:
"""Import a python file and return the globals dictionary"""
Expand Down Expand Up @@ -57,17 +59,20 @@ def add_attribute(
default: Any = None,
attr2: str | None = None,
default2: Any = None,
) -> Callable[[Type], Type]:
) -> Callable[[T], T]:
"""Add an attribute to a class, working as a decorator
Args:
attr: The attribute name
default: The default value
attr2: The second attribute name
default2: The second default value
Returns:
The decorator function
"""

def deco(cls):
def deco(cls: T) -> T:
old_init = cls.__init__

def new_init(self, *args, **kwargs):
Expand All @@ -76,7 +81,7 @@ def new_init(self, *args, **kwargs):
if attr2 is not None:
value2 = kwargs.pop(attr2, default2)
setattr(self, attr2, value2)
old_init(self, *args, **kwargs)
return old_init(self, *args, **kwargs)

cls.__init__ = new_init
return cls
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ ignore = E203, W503
per-file-ignores =
# imported but unused
__init__.py: F401
max-line-length = 80
max-line-length = 88

0 comments on commit 3c467b9

Please sign in to comment.