1111from pathlib import Path
1212from typing import (
1313 Annotated ,
14+ ClassVar ,
1415 Literal ,
1516)
1617
@@ -297,17 +298,6 @@ def do_broken(self, cmd2_handler, name: 'NonExistentType'): # noqa: F821
297298 with pytest .raises (TypeError , match = "Failed to resolve type hints" ):
298299 _validate_base_command_params (do_broken )
299300
300- def test_choices_provider_overrides_enum_choices (self ) -> None :
301- action = _get_param_action (_func_choices_provider_on_enum )
302- assert action .choices is None
303- assert action .get_choices_provider () is not None # type: ignore[attr-defined]
304- assert action .get_completer () is None # type: ignore[attr-defined]
305-
306- def test_completer_overrides_path_choices (self ) -> None :
307- action = _get_param_action (_func_completer_on_path )
308- assert action .get_choices_provider () is None # type: ignore[attr-defined]
309- assert action .get_completer () is cmd2 .Cmd .path_complete # type: ignore[attr-defined]
310-
311301 def test_dest_param_raises (self ) -> None :
312302 with pytest .raises (ValueError , match = "dest" ):
313303 build_parser_from_function (_func_dest_param )
@@ -331,13 +321,6 @@ def test_annotated_ambiguous_union_raises(self) -> None:
331321 with pytest .raises (TypeError , match = "ambiguous" ):
332322 _resolve_annotation (Annotated [str | int , Option ("--name" )])
333323
334- def test_enum_choices_match_converted_type (self ) -> None :
335- """Enum choices must be convertible by the type converter."""
336- action = _get_param_action (_func_enum )
337- converter = action .type
338- for choice in action .choices :
339- assert isinstance (converter (str (choice )), _Color )
340-
341324 def test_multi_param_order_and_presence (self ) -> None :
342325 """Positional order preserved, options generated correctly."""
343326 parser = build_parser_from_function (_func_multi )
@@ -347,6 +330,28 @@ def test_multi_param_order_and_presence(self) -> None:
347330 assert 'c' in dests
348331
349332
333+ class TestTypeInferenceBuildParser :
334+ """Type-inference behavior and override precedence when building parser actions."""
335+
336+ def test_choices_provider_overrides_inferred_enum_choices (self ) -> None :
337+ action = _get_param_action (_func_choices_provider_on_enum )
338+ assert action .choices is None
339+ assert action .get_choices_provider () is not None # type: ignore[attr-defined]
340+ assert action .get_completer () is None # type: ignore[attr-defined]
341+
342+ def test_completer_overrides_inferred_path_completion (self ) -> None :
343+ action = _get_param_action (_func_completer_on_path )
344+ assert action .get_choices_provider () is None # type: ignore[attr-defined]
345+ assert action .get_completer () is cmd2 .Cmd .path_complete # type: ignore[attr-defined]
346+
347+ def test_inferred_enum_choices_match_type_converter (self ) -> None :
348+ """Enum choices must be convertible by the type converter."""
349+ action = _get_param_action (_func_enum )
350+ converter = action .type
351+ for choice in action .choices :
352+ assert isinstance (converter (str (choice )), _Color )
353+
354+
350355# ---------------------------------------------------------------------------
351356# Argument groups and mutually exclusive groups
352357# ---------------------------------------------------------------------------
@@ -954,20 +959,64 @@ class _InferColor(str, enum.Enum):
954959
955960
956961class _RuntimeTypeInferenceApp (cmd2 .Cmd ):
962+ enum_override_choices : ClassVar [list [str ]] = ["amber" , "violet" ]
963+ path_override_values : ClassVar [list [str ]] = ["override-a" , "override-b" ]
964+
957965 path_parser = Cmd2ArgumentParser ()
958966 path_parser .add_argument ("filepath" , type = Path )
959967
960968 @cmd2 .with_argparser (path_parser )
961969 def do_read (self , args : argparse .Namespace ) -> None :
962970 self .poutput (str (args .filepath ))
963971
972+ native_path_parser = Cmd2ArgumentParser ()
973+ native_path_parser .add_argument ("filepath" , type = type (Path ("." )))
974+
975+ @cmd2 .with_argparser (native_path_parser )
976+ def do_read_native (self , args : argparse .Namespace ) -> None :
977+ self .poutput (str (args .filepath ))
978+
964979 enum_parser = Cmd2ArgumentParser ()
965980 enum_parser .add_argument ("color" , type = _InferColor )
966981
967982 @cmd2 .with_argparser (enum_parser )
968983 def do_pick_color (self , args : argparse .Namespace ) -> None :
969984 self .poutput (args .color .value )
970985
986+ def enum_choices_override (self ) -> list [cmd2 .CompletionItem ]:
987+ return [cmd2 .CompletionItem (value ) for value in self .enum_override_choices ]
988+
989+ enum_override_parser = Cmd2ArgumentParser ()
990+ enum_override_parser .add_argument ("color" , type = _InferColor , choices_provider = enum_choices_override )
991+
992+ @cmd2 .with_argparser (enum_override_parser )
993+ def do_pick_color_override (self , args : argparse .Namespace ) -> None :
994+ self .poutput (str (args .color ))
995+
996+ enum_converter_parser = Cmd2ArgumentParser ()
997+ enum_converter_parser .add_argument ("color" , type = _make_enum_type (_InferColor ))
998+
999+ @cmd2 .with_argparser (enum_converter_parser )
1000+ def do_pick_color_converter (self , args : argparse .Namespace ) -> None :
1001+ self .poutput (args .color .value )
1002+
1003+ bool_parser = Cmd2ArgumentParser ()
1004+ bool_parser .add_argument ("enabled" , type = _parse_bool )
1005+
1006+ @cmd2 .with_argparser (bool_parser )
1007+ def do_set_flag (self , args : argparse .Namespace ) -> None :
1008+ self .poutput (str (args .enabled ))
1009+
1010+ def path_completer_override (self , text : str , line : str , begidx : int , endidx : int ) -> cmd2 .Completions :
1011+ return self .basic_complete (text , line , begidx , endidx , self .path_override_values )
1012+
1013+ path_override_parser = Cmd2ArgumentParser ()
1014+ path_override_parser .add_argument ("filepath" , type = Path , completer = path_completer_override )
1015+
1016+ @cmd2 .with_argparser (path_override_parser )
1017+ def do_read_override (self , args : argparse .Namespace ) -> None :
1018+ self .poutput (str (args .filepath ))
1019+
9711020
9721021@pytest .fixture
9731022def infer_app () -> _RuntimeTypeInferenceApp :
@@ -976,10 +1025,15 @@ def infer_app() -> _RuntimeTypeInferenceApp:
9761025 return app
9771026
9781027
979- class TestTypeInference :
1028+ class TestTypeInferenceCompletion :
1029+ """Runtime completion tests for type-inferred argparse argument types."""
1030+
9801031 def test_enum_type_inference (self , infer_app ) -> None :
9811032 assert sorted (_complete_cmd (infer_app , "pick_color " , "" )) == ["green" , "red" ]
9821033
1034+ def test_enum_converter_type_inference (self , infer_app ) -> None :
1035+ assert sorted (_complete_cmd (infer_app , "pick_color_converter " , "" )) == ["green" , "red" ]
1036+
9831037 def test_path_type_inference (self , infer_app , tmp_path ) -> None :
9841038 test_file = tmp_path / "testfile.txt"
9851039 test_file .touch ()
@@ -988,6 +1042,27 @@ def test_path_type_inference(self, infer_app, tmp_path) -> None:
9881042 assert len (result_strings ) > 0
9891043 assert any ("testfile.txt" in item for item in result_strings )
9901044
1045+ def test_native_path_subclass_type_inference (self , infer_app , tmp_path ) -> None :
1046+ test_file = tmp_path / "native-test.txt"
1047+ test_file .touch ()
1048+ text = str (tmp_path ) + "/"
1049+ result_strings = _complete_cmd (infer_app , f"read_native { text } " , text )
1050+ assert len (result_strings ) > 0
1051+ assert any ("native-test.txt" in item for item in result_strings )
1052+
1053+ def test_bool_parser_type_inference (self , infer_app ) -> None :
1054+ assert sorted (_complete_cmd (infer_app , "set_flag " , "" )) == sorted (
1055+ ["true" , "false" , "yes" , "no" , "on" , "off" , "1" , "0" ]
1056+ )
1057+
1058+ def test_choices_provider_takes_precedence_over_enum_inference (self , infer_app ) -> None :
1059+ assert sorted (_complete_cmd (infer_app , "pick_color_override " , "" )) == sorted (
1060+ _RuntimeTypeInferenceApp .enum_override_choices
1061+ )
1062+
1063+ def test_completer_takes_precedence_over_path_inference (self , infer_app ) -> None :
1064+ assert sorted (_complete_cmd (infer_app , "read_override " , "" )) == sorted (_RuntimeTypeInferenceApp .path_override_values )
1065+
9911066
9921067class _AnnotatedCommandSet (cmd2 .CommandSet ):
9931068 def __init__ (self ) -> None :
0 commit comments