35
35
)
36
36
37
37
import typing_extensions
38
- from pydantic import BaseModel
38
+ from pydantic import BaseModel , Field
39
39
from pydantic ._internal ._repr import Representation
40
40
from pydantic ._internal ._utils import is_model_class
41
41
from pydantic .dataclasses import is_pydantic_dataclass
47
47
48
48
from ...exceptions import SettingsError
49
49
from ...utils import _lenient_issubclass , _WithArgsTypes
50
- from ..types import _CliExplicitFlag , _CliImplicitFlag , _CliPositionalArg , _CliSubCommand
50
+ from ..types import NoDecode , _CliExplicitFlag , _CliImplicitFlag , _CliPositionalArg , _CliSubCommand , _CliUnknownArgs
51
51
from ..utils import (
52
52
_annotation_contains_types ,
53
53
_annotation_enum_val_to_name ,
@@ -86,6 +86,7 @@ class CliMutuallyExclusiveGroup(BaseModel):
86
86
CliExplicitFlag = Annotated [_CliBoolFlag , _CliExplicitFlag ]
87
87
CLI_SUPPRESS = SUPPRESS
88
88
CliSuppress = Annotated [T , CLI_SUPPRESS ]
89
+ CliUnknownArgs = Annotated [list [str ], Field (default = []), _CliUnknownArgs , NoDecode ]
89
90
90
91
91
92
class CliSettingsSource (EnvSettingsSource , Generic [T ]):
@@ -364,6 +365,8 @@ def _load_env_vars(
364
365
if not any (field_name for field_name in parsed_args .keys () if f'{ last_selected_subcommand } .' in field_name ):
365
366
parsed_args [last_selected_subcommand ] = '{}'
366
367
368
+ parsed_args .update (self ._cli_unknown_args )
369
+
367
370
self .env_vars = parse_env_vars (
368
371
cast (Mapping [str , str ], parsed_args ),
369
372
self .case_sensitive ,
@@ -630,8 +633,13 @@ def _connect_root_parser(
630
633
add_subparsers_method : Callable [..., Any ] | None = ArgumentParser .add_subparsers ,
631
634
formatter_class : Any = RawDescriptionHelpFormatter ,
632
635
) -> None :
636
+ self ._cli_unknown_args : dict [str , list [str ]] = {}
637
+
633
638
def _parse_known_args (* args : Any , ** kwargs : Any ) -> Namespace :
634
- return ArgumentParser .parse_known_args (* args , ** kwargs )[0 ]
639
+ args , unknown_args = ArgumentParser .parse_known_args (* args , ** kwargs )
640
+ for dest in self ._cli_unknown_args :
641
+ self ._cli_unknown_args [dest ] = unknown_args
642
+ return cast (Namespace , args )
635
643
636
644
self ._root_parser = root_parser
637
645
if parse_args_method is None :
@@ -756,10 +764,7 @@ def _add_parser_args(
756
764
if not arg_names or (kwargs ['dest' ] in added_args ):
757
765
continue
758
766
759
- if is_append_action :
760
- kwargs ['action' ] = 'append'
761
- if _annotation_contains_types (field_info .annotation , (dict , Mapping ), is_strip_annotated = True ):
762
- self ._cli_dict_args [kwargs ['dest' ]] = field_info .annotation
767
+ self ._convert_append_action (kwargs , field_info , is_append_action )
763
768
764
769
if _CliPositionalArg in field_info .metadata :
765
770
arg_names , flag_prefix = self ._convert_positional_arg (
@@ -785,6 +790,8 @@ def _add_parser_args(
785
790
model_default = model_default ,
786
791
is_model_suppressed = is_model_suppressed ,
787
792
)
793
+ elif _CliUnknownArgs in field_info .metadata :
794
+ self ._cli_unknown_args [kwargs ['dest' ]] = []
788
795
elif not is_alias_path_only :
789
796
if group is not None :
790
797
if isinstance (group , dict ):
@@ -807,6 +814,12 @@ def _check_kebab_name(self, name: str) -> str:
807
814
return name .replace ('_' , '-' )
808
815
return name
809
816
817
+ def _convert_append_action (self , kwargs : dict [str , Any ], field_info : FieldInfo , is_append_action : bool ) -> None :
818
+ if is_append_action :
819
+ kwargs ['action' ] = 'append'
820
+ if _annotation_contains_types (field_info .annotation , (dict , Mapping ), is_strip_annotated = True ):
821
+ self ._cli_dict_args [kwargs ['dest' ]] = field_info .annotation
822
+
810
823
def _convert_bool_flag (self , kwargs : dict [str , Any ], field_info : FieldInfo , model_default : Any ) -> None :
811
824
if kwargs ['metavar' ] == 'bool' :
812
825
if (self .cli_implicit_flags or _CliImplicitFlag in field_info .metadata ) and (
0 commit comments