def add_argument(self, *name_or_flags, **kwargs) -> None: """Adds an argument to the argument buffer, which will later be passed to _add_argument.""" if self._initialized: raise ValueError('add_argument cannot be called after initialization. ' 'Arguments must be added either as class variables or by overriding ' 'configure and including a self.add_argument call there.') variable = get_argument_name(*name_or_flags).replace('-', '_') self.argument_buffer[variable] = (name_or_flags, kwargs)
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)