Skip to content

Commit

Permalink
Addresses #110
Browse files Browse the repository at this point in the history
Introduce UnknownOptionError.
BREAKING: Change negative-flag-assignment-exception from ValidationError to generic CycloptsError.
BREAKING: Change exception for unknown option token from ValidationError to UnknownOptionError.
  • Loading branch information
BrianPugh committed Feb 22, 2024
1 parent 5c8cf62 commit ae8fa62
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 15 deletions.
2 changes: 2 additions & 0 deletions cyclopts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"InvalidCommandError",
"MissingArgumentError",
"Parameter",
"UnknownOptionError",
"UnusedCliTokensError",
"ValidationError",
"convert",
Expand All @@ -28,6 +29,7 @@
DocstringError,
InvalidCommandError,
MissingArgumentError,
UnknownOptionError,
UnusedCliTokensError,
ValidationError,
)
Expand Down
5 changes: 3 additions & 2 deletions cyclopts/bind.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CycloptsError,
MissingArgumentError,
RepeatArgumentError,
UnknownOptionError,
ValidationError,
)
from cyclopts.parameter import get_hint_parameter, validate_command
Expand Down Expand Up @@ -89,7 +90,7 @@ def _parse_kw_and_flags(command: ResolvedCommand, tokens, mapping):
if implicit_value: # Only accept values to the positive flag
pass
else:
raise ValidationError(value=f'Cannot assign value to negative flag "{cli_key}".')
raise CycloptsError(msg=f'Cannot assign value to negative flag "{cli_key}".')
else:
cli_values.append(implicit_value)
tokens_per_element, consume_all = 0, False
Expand Down Expand Up @@ -148,7 +149,7 @@ def _is_option_like(token: str) -> bool:

def _validate_is_not_option_like(token):
if _is_option_like(token):
raise ValidationError(value=f'Unknown option: "{token}".')
raise UnknownOptionError(token=token)


def _parse_pos(
Expand Down
10 changes: 10 additions & 0 deletions cyclopts/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,16 @@ def __str__(self):
return super().__str__() + f"Invalid value for {parameter_cli_name}. {self.value}"


@define(kw_only=True)
class UnknownOptionError(CycloptsError):
"""Unknown/unregistered option provided by the cli."""

token: str

def __str__(self):
return super().__str__() + f'Unknown option: "{self.token}".'


@define(kw_only=True)
class CoercionError(CycloptsError):
"""There was an error performing automatic type coercion."""
Expand Down
4 changes: 4 additions & 0 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,10 @@ Exceptions
:show-inheritance:
:members:

.. autoexception:: cyclopts.UnknownOptionError
:show-inheritance:
:members:

.. autoexception:: cyclopts.CoercionError
:show-inheritance:
:members:
Expand Down
18 changes: 10 additions & 8 deletions tests/test_bind_boolean_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
else:
from typing import Annotated

from cyclopts import Group, Parameter, ValidationError
from cyclopts import CycloptsError, Group, Parameter, UnknownOptionError, ValidationError


@pytest.mark.parametrize(
Expand Down Expand Up @@ -39,9 +39,11 @@ def test_boolean_flag_negative_assignment_not_allowed(app, cmd_str, assert_parse
def foo(my_flag: bool = True):
pass

with pytest.raises(ValidationError):
with pytest.raises(CycloptsError) as e:
app.parse_args(cmd_str, exit_on_error=False, print_error=True)

assert str(e.value) == 'Cannot assign value to negative flag "--no-my-flag".'


def test_boolean_flag_app_parameter_default(app, assert_parse_args):
app.default_parameter = Parameter(negative="")
Expand All @@ -53,9 +55,9 @@ def foo(my_flag: bool = True):
# Normal positive flag should still work.
assert_parse_args(foo, "--my-flag", True)

with pytest.raises(ValidationError) as e:
with pytest.raises(UnknownOptionError) as e:
app.parse_args("--no-my-flag", exit_on_error=False)
assert e.value.value == 'Unknown option: "--no-my-flag".'
assert str(e.value) == 'Unknown option: "--no-my-flag".'


def test_boolean_flag_app_parameter_default_negative_only(app, assert_parse_args):
Expand All @@ -65,13 +67,13 @@ def foo(my_flag: Annotated[bool, Parameter("", negative="--no-my-flag")] = True)

assert_parse_args(foo, "--no-my-flag", False)

with pytest.raises(ValidationError):
with pytest.raises(UnknownOptionError):
app.parse_args("--my-flag", exit_on_error=False, print_error=True)

with pytest.raises(ValidationError):
with pytest.raises(CycloptsError):
app.parse_args("--no-my-flag=True", exit_on_error=False, print_error=True)

with pytest.raises(ValidationError):
with pytest.raises(CycloptsError):
app.parse_args("--no-my-flag=False", exit_on_error=False, print_error=True)


Expand Down Expand Up @@ -166,5 +168,5 @@ def foo(my_flag: Annotated[bool, Parameter(negative=negative)] = True):
pass

assert_parse_args(foo, "--my-flag", True)
with pytest.raises(ValidationError):
with pytest.raises(UnknownOptionError):
assert_parse_args(foo, "--no-my-flag", True)
4 changes: 2 additions & 2 deletions tests/test_bind_pos_only.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from cyclopts import CoercionError, MissingArgumentError, ValidationError
from cyclopts import CoercionError, MissingArgumentError, UnknownOptionError, ValidationError


@pytest.mark.parametrize(
Expand Down Expand Up @@ -36,7 +36,7 @@ def foo(a: int, b: int, c: int, /):
@pytest.mark.parametrize(
"cmd_str_e",
[
("foo 1 2 --c=3", ValidationError), # Unknown option "--c"
("foo 1 2 --c=3", UnknownOptionError), # Unknown option "--c"
],
)
def test_pos_only_exceptions(app, cmd_str_e):
Expand Down
14 changes: 11 additions & 3 deletions tests/test_parameter_allow_leading_hyphen.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

from cyclopts.exceptions import ValidationError
from cyclopts.exceptions import UnknownOptionError, ValidationError

if sys.version_info < (3, 9):
from typing_extensions import Annotated
Expand All @@ -12,10 +12,18 @@
from cyclopts import Parameter


def test_allow_leading_hyphen(app):
def test_allow_leading_hyphen_false(app):
@app.default
def foo(bar: Annotated[str, Parameter()]):
pass

with pytest.raises(ValidationError):
with pytest.raises(UnknownOptionError):
app("--buzz", exit_on_error=False, print_error=True)


def test_allow_leading_hyphen_true(app, assert_parse_args):
@app.default
def foo(bar: Annotated[str, Parameter(allow_leading_hyphen=True)]):
pass

assert_parse_args(foo, "--buzz", bar="--buzz")

0 comments on commit ae8fa62

Please sign in to comment.