@@ -789,6 +789,60 @@ class runopt:
789789 is_required : bool
790790 help : str
791791
792+ @property
793+ def is_type_list_of_str (self ) -> bool :
794+ """
795+ Checks if the option type is a list of strings.
796+
797+ Returns:
798+ bool: True if the option type is either List[str] or list[str], False otherwise.
799+ """
800+ return self .opt_type in (List [str ], list [str ])
801+
802+ @property
803+ def is_type_dict_of_str (self ) -> bool :
804+ """
805+ Checks if the option type is a dict of string keys to string values.
806+
807+ Returns:
808+ bool: True if the option type is either Dict[str, str] or dict[str, str], False otherwise.
809+ """
810+ return self .opt_type in (Dict [str , str ], dict [str , str ])
811+
812+ def cast_to_type (self , value : str ) -> CfgVal :
813+ """Casts the given `value` (in its string representation) to the type of this run option.
814+ Below are the cast rules for each option type and value literal:
815+
816+ 1. opt_type=str, value="foo" -> "foo"
817+ 1. opt_type=bool, value="True"/"False" -> True/False
818+ 1. opt_type=int, value="1" -> 1
819+ 1. opt_type=float, value="1.1" -> 1.1
820+ 1. opt_type=list[str]/List[str], value="a,b,c" or value="a;b;c" -> ["a", "b", "c"]
821+ 1. opt_type=dict[str,str]/Dict[str,str],
822+ value="key1:val1,key2:val2" or value="key1:val1;key2:val2" -> {"key1": "val1", "key2": "val2"}
823+
824+ NOTE: dict parsing uses ":" as the kv separator (rather than the standard "=") because "=" is used
825+ at the top-level cfg to parse runopts (notice the plural) from the CLI. Originally torchx only supported
826+ primitives and list[str] as CfgVal but dict[str,str] was added in https://github.com/pytorch/torchx/pull/855
827+ """
828+
829+ if self .opt_type is None :
830+ raise ValueError ("runopt's opt_type cannot be `None`" )
831+ elif self .opt_type == bool :
832+ return value .lower () == "true"
833+ elif self .opt_type in (List [str ], list [str ]):
834+ # lists may be ; or , delimited
835+ # also deal with trailing "," by removing empty strings
836+ return [v for v in value .replace (";" , "," ).split ("," ) if v ]
837+ elif self .opt_type in (Dict [str , str ], dict [str , str ]):
838+ return {
839+ s .split (":" , 1 )[0 ]: s .split (":" , 1 )[1 ]
840+ for s in value .replace (";" , "," ).split ("," )
841+ }
842+ else :
843+ assert self .opt_type in (str , int , float )
844+ return self .opt_type (value )
845+
792846
793847class runopts :
794848 """
@@ -948,27 +1002,11 @@ def cfg_from_str(self, cfg_str: str) -> Dict[str, CfgVal]:
9481002
9491003 """
9501004
951- def _cast_to_type (value : str , opt_type : Type [CfgVal ]) -> CfgVal :
952- if opt_type == bool :
953- return value .lower () == "true"
954- elif opt_type in (List [str ], list [str ]):
955- # lists may be ; or , delimited
956- # also deal with trailing "," by removing empty strings
957- return [v for v in value .replace (";" , "," ).split ("," ) if v ]
958- elif opt_type in (Dict [str , str ], dict [str , str ]):
959- return {
960- s .split (":" , 1 )[0 ]: s .split (":" , 1 )[1 ]
961- for s in value .replace (";" , "," ).split ("," )
962- }
963- else :
964- # pyre-ignore[19, 6] type won't be dict here as we handled it above
965- return opt_type (value )
966-
9671005 cfg : Dict [str , CfgVal ] = {}
9681006 for key , val in to_dict (cfg_str ).items ():
969- runopt_ = self .get (key )
970- if runopt_ :
971- cfg [key ] = _cast_to_type (val , runopt_ . opt_type )
1007+ opt = self .get (key )
1008+ if opt :
1009+ cfg [key ] = opt . cast_to_type (val )
9721010 else :
9731011 logger .warning (
9741012 f"{ YELLOW_BOLD } Unknown run option passed to scheduler: { key } ={ val } { RESET } "
@@ -982,16 +1020,16 @@ def cfg_from_json_repr(self, json_repr: str) -> Dict[str, CfgVal]:
9821020 cfg : Dict [str , CfgVal ] = {}
9831021 cfg_dict = json .loads (json_repr )
9841022 for key , val in cfg_dict .items ():
985- runopt_ = self .get (key )
986- if runopt_ :
1023+ opt = self .get (key )
1024+ if opt :
9871025 # Optional runopt cfg values default their value to None,
9881026 # but use `_type` to specify their type when provided.
9891027 # Make sure not to treat None's as lists/dictionaries
9901028 if val is None :
9911029 cfg [key ] = val
992- elif runopt_ . opt_type == List [ str ] :
1030+ elif opt . is_type_list_of_str :
9931031 cfg [key ] = [str (v ) for v in val ]
994- elif runopt_ . opt_type == Dict [ str , str ] :
1032+ elif opt . is_type_dict_of_str :
9951033 cfg [key ] = {str (k ): str (v ) for k , v in val .items ()}
9961034 else :
9971035 cfg [key ] = val
0 commit comments