Ejemplo n.º 1
0
    def parse_args(self: TapType,
                   args: Optional[Sequence[str]] = None,
                   known_only: bool = False) -> TapType:
        """Parses arguments, sets attributes of self equal to the parsed arguments, and processes arguments.

        :param args: List of strings to parse. The default is taken from `sys.argv`.
        :param known_only: If true, ignores extra arguments and only parses known arguments.
        Unparsed arguments are saved to self.extra_args.
        :return: self, which is a Tap instance containing all of the parsed args.
        """
        # Prevent double parsing
        if self._parsed:
            raise ValueError('parse_args can only be called once.')

        # Collect arguments from all of the configs
        config_args = [
            arg for args_from_config in self.args_from_configs
            for arg in args_from_config.split()
        ]

        # Add config args at lower precedence and extract args from the command line if they are not passed explicitly
        args = config_args + (sys.argv[1:] if args is None else list(args))

        # Parse args using super class ArgumentParser's parse_args or parse_known_args function
        if known_only:
            default_namespace, self.extra_args = super(
                Tap, self).parse_known_args(args)
        else:
            default_namespace = super(Tap, self).parse_args(args)

        # Copy parsed arguments to self
        for variable, value in vars(default_namespace).items():
            # Conversion from list to set or tuple
            if variable in self._annotations:
                if type(value) == list:
                    var_type = get_origin(self._annotations[variable])

                    # Unpack nested boxed types such as Optional[List[int]]
                    if var_type is Union:
                        var_type = get_origin(
                            get_args(self._annotations[variable])[0])

                        # If var_type is tuple as in Python 3.6, change to a typing type
                        # (e.g., (typing.Tuple, <class 'bool'>) ==> typing.Tuple)
                        if isinstance(var_type, tuple):
                            var_type = var_type[0]

                    if var_type in (Set, set):
                        value = set(value)
                    elif var_type in (Tuple, tuple):
                        value = tuple(value)

            # Set variable in self (and deepcopy)
            setattr(self, variable, deepcopy(value))

        # Process args
        self.process_args()

        # Indicate that args have been parsed
        self._parsed = True

        return self
Ejemplo n.º 2
0
    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)