From c118ed8631c2689f6b17a01dc4ee3bcd8d422602 Mon Sep 17 00:00:00 2001 From: Brian Pugh Date: Wed, 6 Dec 2023 18:17:49 -0800 Subject: [PATCH 1/2] explicitly test args and kwargs together --- tests/test_bind_kwargs.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/test_bind_kwargs.py b/tests/test_bind_kwargs.py index ab57c2cb..f69ec5e5 100644 --- a/tests/test_bind_kwargs.py +++ b/tests/test_bind_kwargs.py @@ -33,3 +33,23 @@ def foo(a: int, **kwargs: int): actual_command, actual_bind = app.parse_args("foo 1") assert actual_command == foo assert actual_bind == expected_bind + + +def test_args_and_kwargs_int(app): + @app.command + def foo(a: int, *args: int, **kwargs: int): + pass + + signature = inspect.signature(foo) + expected_bind = signature.bind(1, 2, 3, 4, 5, bar=2, baz=3) + + actual_command, actual_bind = app.parse_args("foo 1 2 3 4 5 --bar=2 --baz 3") + assert actual_command == foo + assert actual_bind == expected_bind + + signature = inspect.signature(foo) + expected_bind = signature.bind(1) + + actual_command, actual_bind = app.parse_args("foo 1") + assert actual_command == foo + assert actual_bind == expected_bind From ac13ab863ae5aa42f24a4a3c4b275bdf38d3fc71 Mon Sep 17 00:00:00 2001 From: Brian Pugh Date: Wed, 6 Dec 2023 18:23:18 -0800 Subject: [PATCH 2/2] Add explicit check for unsupported Tuple[...] --- cyclopts/parameter.py | 9 ++++++--- tests/test_validate_command.py | 15 ++++++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/cyclopts/parameter.py b/cyclopts/parameter.py index ef6b2664..ed65f6ae 100644 --- a/cyclopts/parameter.py +++ b/cyclopts/parameter.py @@ -1,7 +1,7 @@ import inspect import typing from functools import lru_cache -from typing import Iterable, List, Optional, Tuple, Type, Union, get_origin +from typing import Iterable, List, Optional, Tuple, Type, Union, get_args, get_origin from attrs import field, frozen from typing_extensions import Annotated @@ -43,9 +43,12 @@ def validate_command(f): signature = inspect.signature(f) for iparam in signature.parameters.values(): _ = get_origin_and_validate(iparam.annotation) - _, cparam = get_hint_parameter(iparam.annotation) + type_, cparam = get_hint_parameter(iparam.annotation) if not cparam.parse and iparam.kind is not iparam.KEYWORD_ONLY: raise ValueError("Parameter.parse=False must be used with a KEYWORD_ONLY function parameter.") + if get_origin(type_) is tuple: + if ... in get_args(type_): + raise ValueError("Cannot use a variable-length tuple.") @frozen @@ -207,7 +210,7 @@ def get_hint_parameter(type_: Type) -> Tuple[Type, Parameter]: if type(type_) is AnnotatedType: annotations = type_.__metadata__ # pyright: ignore[reportGeneralTypeIssues] - type_ = typing.get_args(type_)[0] + type_ = get_args(type_)[0] cyclopts_parameters = [x for x in annotations if isinstance(x, Parameter)] if len(cyclopts_parameters) > 2: raise MultipleParameterAnnotationError diff --git a/tests/test_validate_command.py b/tests/test_validate_command.py index c4fe6371..6c690685 100644 --- a/tests/test_validate_command.py +++ b/tests/test_validate_command.py @@ -26,15 +26,24 @@ def f4(a: Tuple[int, int], b: str): validate_command(f4) + # Python automatically deduplicates the double None. + def f5(a: Union[None, None]): + pass + + validate_command(f5) + -def test_validate_command_exception(): +def test_validate_command_exception_bare_tuple(): def f1(a: tuple): pass with pytest.raises(TypeError): validate_command(f1) - def f2(a: Union[None, None]): + +def test_validate_command_exception_elipsis_tuple(): + def f1(a: Tuple[int, ...]): pass - validate_command(f2) + with pytest.raises(ValueError): + validate_command(f1)