def test_tuple_type_enforcer_multi_types_same(self): enforcer = TupleTypeEnforcer(types=[str, str]) args = ['hi', 'bye'] output = [enforcer(arg) for arg in args] self.assertEqual(output, args) enforcer = TupleTypeEnforcer(types=[int, int, int]) args = [123, 456, -789] output = [enforcer(str(arg)) for arg in args] self.assertEqual(output, args) enforcer = TupleTypeEnforcer(types=[float, float, float, float]) args = [1.23, 4.56, -7.89, 3.14159] output = [enforcer(str(arg)) for arg in args] self.assertEqual(output, args) enforcer = TupleTypeEnforcer(types=[bool, bool, bool, bool, bool]) args = ['True', 'False', '1', '0', 'tru'] true_output = [True, False, True, False, True] output = [enforcer(str(arg)) for arg in args] self.assertEqual(output, true_output)
def test_tuple_type_enforcer_one_type_bool(self): enforcer = TupleTypeEnforcer(types=[bool]) self.assertEqual(enforcer('True'), True) enforcer = TupleTypeEnforcer(types=[bool]) self.assertEqual(enforcer('true'), True) enforcer = TupleTypeEnforcer(types=[bool]) self.assertEqual(enforcer('False'), False) enforcer = TupleTypeEnforcer(types=[bool]) self.assertEqual(enforcer('false'), False) enforcer = TupleTypeEnforcer(types=[bool]) self.assertEqual(enforcer('tRu'), True) enforcer = TupleTypeEnforcer(types=[bool]) self.assertEqual(enforcer('faL'), False) enforcer = TupleTypeEnforcer(types=[bool]) self.assertEqual(enforcer('1'), True) enforcer = TupleTypeEnforcer(types=[bool]) self.assertEqual(enforcer('0'), False)
def _add_argument(self, *name_or_flags, **kwargs) -> None: """Adds an argument to self (i.e. the super class ArgumentParser). Sets the following attributes of kwargs when not explicitly provided: - type: Set to the type annotation of the argument. - default: Set to the default value of the argument (if provided). - required: True if a default value of the argument is not provided, False otherwise. - action: Set to "store_true" if the argument is a required bool or a bool with default value False. Set to "store_false" if the argument is a bool with default value True. - nargs: Set to "*" if the type annotation is List[str], List[int], or List[float]. - help: Set to the argument documentation from the class docstring. :param name_or_flags: Either a name or a list of option strings, e.g. foo or -f, --foo. :param kwargs: Keyword arguments. """ # Set explicit bool explicit_bool = self._explicit_bool # Get variable name variable = get_argument_name(*name_or_flags) # Get default if not specified if hasattr(self, variable): kwargs['default'] = kwargs.get('default', getattr(self, variable)) # Set required if option arg if (is_option_arg(*name_or_flags) and variable != 'help' and 'default' not in kwargs and kwargs.get('action') != 'version'): kwargs['required'] = kwargs.get('required', not hasattr(self, variable)) # Set help if necessary if 'help' not in kwargs: kwargs['help'] = '(' # Type if variable in self._annotations: kwargs['help'] += type_to_str( self._annotations[variable]) + ', ' # Required/default if kwargs.get('required', False): kwargs['help'] += 'required' else: kwargs['help'] += f'default={kwargs.get("default", None)}' kwargs['help'] += ')' # Description if variable in self.class_variables: kwargs[ 'help'] += ' ' + self.class_variables[variable]['comment'] # Set other kwargs where not provided if variable in self._annotations: # Get type annotation var_type = self._annotations[variable] # If type is not explicitly provided, set it if it's one of our supported default types if 'type' not in kwargs: # Unbox Optional[type] and set var_type = type if get_origin(var_type) in OPTIONAL_TYPES: var_args = get_args(var_type) if len(var_args) > 0: var_type = get_args(var_type)[0] # If var_type is tuple as in Python 3.6, change to a typing type # (e.g., (typing.List, <class 'bool'>) ==> typing.List[bool]) if isinstance(var_type, tuple): var_type = var_type[0][var_type[1:]] explicit_bool = True # First check whether it is a literal type or a boxed literal type if is_literal_type(var_type): var_type, kwargs['choices'] = get_literals( var_type, variable) elif (get_origin(var_type) in (List, list, Set, set) and len(get_args(var_type)) > 0 and is_literal_type(get_args(var_type)[0])): var_type, kwargs['choices'] = get_literals( get_args(var_type)[0], variable) kwargs['nargs'] = kwargs.get('nargs', '*') # Handle Tuple type (with type args) by extracting types of Tuple elements and enforcing them elif get_origin(var_type) in (Tuple, tuple) and len( get_args(var_type)) > 0: loop = False types = get_args(var_type) # Don't allow Tuple[()] if len(types) == 1 and types[0] == tuple(): raise ValueError( 'Empty Tuples (i.e. Tuple[()]) are not a valid Tap type ' 'because they have no arguments.') # Handle Tuple[type, ...] if len(types) == 2 and types[1] == Ellipsis: types = types[0:1] loop = True kwargs['nargs'] = '*' else: kwargs['nargs'] = len(types) var_type = TupleTypeEnforcer(types=types, loop=loop) if get_origin(var_type) in BOXED_TYPES: # If List or Set type, set nargs if (get_origin(var_type) in BOXED_COLLECTION_TYPES and kwargs.get('action') not in {'append', 'append_const'}): kwargs['nargs'] = kwargs.get('nargs', '*') # Extract boxed type for Optional, List, Set arg_types = get_args(var_type) # Set defaults type to str for Type and Type[()] if len(arg_types) == 0 or arg_types[0] == EMPTY_TYPE: var_type = str else: var_type = arg_types[0] # Handle the cases of List[bool], Set[bool], Tuple[bool] if var_type == bool: var_type = boolean_type # If bool then set action, otherwise set type if var_type == bool: if explicit_bool: kwargs['type'] = boolean_type kwargs['choices'] = [ True, False ] # this makes the help message more helpful else: action_cond = "true" if kwargs.get( "required", False) or not kwargs["default"] else "false" kwargs['action'] = kwargs.get('action', f'store_{action_cond}') elif kwargs.get('action') not in {'count', 'append_const'}: kwargs['type'] = var_type if self._underscores_to_dashes: name_or_flags = [ name_or_flag.replace('_', '-') for name_or_flag in name_or_flags ] super(Tap, self).add_argument(*name_or_flags, **kwargs)
def test_tuple_type_enforcer_infinite(self): enforcer = TupleTypeEnforcer(types=[int], loop=True) args = [1, 2, -5, 20] output = [enforcer(str(arg)) for arg in args] self.assertEqual(output, args)
def test_tuple_type_enforcer_multi_types_different(self): enforcer = TupleTypeEnforcer(types=[str, int, float, bool]) args = ['hello', 77, 0.2, 'tru'] true_output = ['hello', 77, 0.2, True] output = [enforcer(str(arg)) for arg in args] self.assertEqual(output, true_output)
def test_tuple_type_enforcer_one_type_float(self): enforcer = TupleTypeEnforcer(types=[float]) self.assertEqual(enforcer('3.14159'), 3.14159)
def test_tuple_type_enforcer_one_type_int(self): enforcer = TupleTypeEnforcer(types=[int]) self.assertEqual(enforcer('123'), 123)
def test_tuple_type_enforcer_one_type_str(self): enforcer = TupleTypeEnforcer(types=[str]) self.assertEqual(enforcer('hi'), 'hi')
def test_tuple_type_enforcer_zero_types(self): enforcer = TupleTypeEnforcer(types=[]) with self.assertRaises(IndexError): enforcer('hi')
def _add_argument(self, *name_or_flags, **kwargs) -> None: """Adds an argument to self (i.e. the super class ArgumentParser). Sets the following attributes of kwargs when not explicitly provided: - type: Set to the type annotation of the argument. - default: Set to the default value of the argument (if provided). - required: True if a default value of the argument is not provided, False otherwise. - action: Set to "store_true" if the argument is a required bool or a bool with default value False. Set to "store_false" if the argument is a bool with default value True. - nargs: Set to "*" if the type annotation is List[str], List[int], or List[float]. - help: Set to the argument documentation from the class docstring. :param name_or_flags: Either a name or a list of option strings, e.g. foo or -f, --foo. :param kwargs: Keyword arguments. """ # Get variable name variable = get_dest(*name_or_flags, **kwargs) # Get default if not specified if hasattr(self, variable): kwargs['default'] = kwargs.get('default', getattr(self, variable)) # Set required if option arg if is_option_arg(*name_or_flags) and variable != 'help': kwargs['required'] = kwargs.get('required', not hasattr(self, variable)) # Set help if necessary if 'help' not in kwargs: kwargs['help'] = '(' # Type if variable in self._annotations: kwargs['help'] += type_to_str( self._annotations[variable]) + ', ' # Required/default if kwargs.get('required', False): kwargs['help'] += 'required' else: kwargs['help'] += f'default={kwargs.get("default", None)}' kwargs['help'] += ')' # Description if variable in self.class_variables: kwargs[ 'help'] += ' ' + self.class_variables[variable]['comment'] # Set other kwargs where not provided if variable in self._annotations: # Get type annotation var_type = self._annotations[variable] # If type is not explicitly provided, set it if it's one of our supported default types if 'type' not in kwargs: # First check whether it is a literal type or a boxed literal type if is_literal_type(var_type): var_type, kwargs['choices'] = get_literals( var_type, variable) elif (get_origin(var_type) in (List, list, Set, set) and len(get_args(var_type)) > 0 and is_literal_type(get_args(var_type)[0])): var_type, kwargs['choices'] = get_literals( get_args(var_type)[0], variable) kwargs['nargs'] = kwargs.get('nargs', '*') # Handle Tuple type (with type args) by extracting types of Tuple elements and enforcing them elif get_origin(var_type) in (Tuple, tuple) and len( get_args(var_type)) > 0: loop = False types = get_args(var_type) # Don't allow Tuple[()] if len(types) == 1 and types[0] == tuple(): raise ValueError( 'Empty Tuples (i.e. Tuple[()]) are not a valid Tap type ' 'because they have no arguments.') # Handle Tuple[type, ...] if len(types) == 2 and types[1] == Ellipsis: types = types[0:1] loop = True kwargs['nargs'] = '*' else: kwargs['nargs'] = len(types) var_type = TupleTypeEnforcer(types=types, loop=loop) # To identify an Optional type, check if it's a union of a None and something else elif (is_union_type(var_type) and len(get_args(var_type)) == 2 and isinstance(None, get_args(var_type)[1]) and is_literal_type(get_args(var_type)[0])): var_type, kwargs['choices'] = get_literals( get_args(var_type)[0], variable) elif var_type not in SUPPORTED_DEFAULT_TYPES: raise ValueError( f'Variable "{variable}" has type "{var_type}" which is not supported by default.\n' f'Please explicitly add the argument to the parser by writing:\n\n' f'def add_arguments(self) -> None:\n' f' self.add_argument("--{variable}", type=func, {"required=True" if kwargs["required"] else f"default={getattr(self, variable)}"})\n\n' f'where "func" maps from str to {var_type}.') if var_type in SUPPORTED_DEFAULT_BOXED_TYPES: # If List or Set type, set nargs if var_type in SUPPORTED_DEFAULT_COLLECTION_TYPES: kwargs['nargs'] = kwargs.get('nargs', '*') # Extract boxed type for Optional, List, Set arg_types = get_args(var_type) # Set defaults type to str for Type and Type[()] if len(arg_types) == 0 or arg_types[0] == EMPTY_TYPE: var_type = str else: var_type = arg_types[0] # Handle the cases of Optional[bool], List[bool], Set[bool] if var_type == bool: var_type = boolean_type # If bool then set action, otherwise set type if var_type == bool: if self._explicit_bool: kwargs['type'] = boolean_type kwargs['choices'] = [ True, False ] # this makes the help message more helpful else: kwargs['action'] = kwargs.get( 'action', f'store_{"true" if kwargs["required"] or not kwargs["default"] else "false"}' ) else: kwargs['type'] = var_type super(Tap, self).add_argument(*name_or_flags, **kwargs)