Skip to content

Commit

Permalink
add error handling for argparse's choices
Browse files Browse the repository at this point in the history
  • Loading branch information
ric-evans committed Oct 10, 2023
1 parent 0b2b690 commit b3be7e5
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 7 deletions.
34 changes: 27 additions & 7 deletions rest_tools/server/arghandler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Handle argument parsing, defaulting, and casting."""
"""Handle argument parsing, defaulting, casting, and more."""


import argparse
Expand All @@ -10,33 +10,49 @@
import sys
import time
import traceback
from typing import Any, List, TypeVar, Union, cast
from typing import Any, List, Union, cast

import tornado.web
from tornado.escape import to_unicode
from wipac_dev_tools import strtobool

from .handler import RestHandler

T = TypeVar("T")

LOGGER = logging.getLogger(__name__)


###############################################################################
# Constants

USE_CACHED_VALUE_PLACEHOLDER = "PLACEHOLDER"


class ArgumentSource(enum.Enum):
"""For mapping to argument sources."""

QUERY_ARGUMENTS = enum.auto()
JSON_BODY_ARGUMENTS = enum.auto()


###############################################################################
# Error Patterns

# __main__.py: error: the following arguments are required: --reqd, --bar
ARGUMENTS_REQUIRED_PATTERN = re.compile(
r".+: error: (the following arguments are required: .+)"
)

# __main__.py: error: unrecognized arguments: --xtra 1 --another True False who knows
UNRECOGNIZED_ARGUMENTS_PATTERN = re.compile(r".+ error: (unrecognized arguments:) (.+)")

# argument --foo: invalid int value: 'hank'
INVALID_VALUE_PATTERN = re.compile(r"(argument .+: invalid) .+ value: '.+'")

USE_CACHED_VALUE_PLACEHOLDER = "PLACEHOLDER"
# argument --pick_it: invalid choice: 'hammer' (choose from 'rock', 'paper', 'scissors')
INVALID_CHOICE_PATTERN = re.compile(r"(argument .+: invalid choice: .+)")


###############################################################################


class ArgumentHandler:
Expand Down Expand Up @@ -152,11 +168,15 @@ def _translate_error(
return f"{match.group(1)} {', '.join(args)}"

# INVALID VALUE -- not a system error bc 'exit_on_error=False' (in __init__)
# ex:
# argument --foo: invalid int value: 'hank'
elif isinstance(exc, argparse.ArgumentError):
# ex:
# argument --foo: invalid int value: 'hank'
if match := INVALID_VALUE_PATTERN.search(str(exc)):
return f"{match.group(1).replace('--', '')} type"
# ex:
# argument --pick_it: invalid choice: 'hammer' (choose from 'rock', 'paper', 'scissors')
elif match := INVALID_CHOICE_PATTERN.search(str(exc)):
return match.group(1).replace("--", "")

# FALL-THROUGH -- log unknown exception
ts = time.time() # log timestamp to aid debugging
Expand Down
42 changes: 42 additions & 0 deletions tests/unit_server/arghandler_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,48 @@ def test_210__argparse_choices(argument_source: str) -> None:
assert val == getattr(outargs, arg.replace("-", "_"))


@pytest.mark.parametrize(
"argument_source",
[QUERY_ARGUMENTS, JSON_BODY_ARGUMENTS],
)
def test_211__argparse_choices__error(argument_source: str) -> None:
"""Test `argument_source` arguments using argparse's advanced options."""
args: Dict[str, Any] = {
"pick_it": "paper",
"bar": "True",
}
choices: List = ["rock", "paper", "scissors"]
if argument_source == JSON_BODY_ARGUMENTS:
args.update(
{
"pick_it": {"abc": 123},
"listo": [1, 2, 3],
}
)
choices = [{"abc": 123}, {"def": 456}, {"ghi": 789}]

# set up ArgumentHandler
arghand = setup_argument_handler(
argument_source,
{k: v if k != "pick_it" else "hank" for k, v in args.items()},
)

for arg, _ in args.items():
print()
print(arg)
if arg == "pick_it":
arghand.add_argument(arg, choices=choices)
else:
arghand.add_argument(arg)

with pytest.raises(tornado.web.HTTPError) as e:
arghand.parse_args()
assert (
str(e.value)
== f"HTTP 400: argument pick_it: invalid choice: 'hank' (choose from {', '.join(repr(c) for c in choices)})"
)


@pytest.mark.parametrize(
"argument_source",
[QUERY_ARGUMENTS, JSON_BODY_ARGUMENTS],
Expand Down

0 comments on commit b3be7e5

Please sign in to comment.