def get_option_help_info(self, args, kwargs): """Returns an OptionHelpInfo for the option registered with the given (args, kwargs).""" display_args = [] scoped_cmd_line_args = [] unscoped_cmd_line_args = [] for arg in args: is_short_arg = len(arg) == 2 unscoped_cmd_line_args.append(arg) if self._scope_prefix: scoped_arg = '--{}-{}'.format(self._scope_prefix, arg.lstrip('-')) else: scoped_arg = arg scoped_cmd_line_args.append(scoped_arg) if is_boolean_flag(kwargs): if is_short_arg: display_arg = scoped_arg else: unscoped_cmd_line_args.append('--no-{}'.format(arg[2:])) scoped_cmd_line_args.append('--no-{}'.format( scoped_arg[2:])) display_arg = '--[no-]{}'.format(scoped_arg[2:]) else: display_arg = '{}={}'.format(scoped_arg, self.compute_metavar(kwargs)) if kwargs.get('action') == 'append': display_arg = '{arg_str} ({arg_str}) ...'.format( arg_str=display_arg) display_args.append(display_arg) if is_boolean_flag(kwargs): typ = bool else: typ = kwargs.get('type', str) default = self.compute_default(kwargs) help_msg = kwargs.get('help', 'No help available.') deprecated_version = kwargs.get('deprecated_version') deprecated_message = ( 'DEPRECATED. Will be removed in version {}.'.format( deprecated_version) if deprecated_version else None) deprecated_hint = kwargs.get('deprecated_hint') choices = ', '.join( kwargs.get('choices')) if kwargs.get('choices') else None ret = OptionHelpInfo(registering_class=kwargs.get( 'registering_class', type(None)), display_args=display_args, scoped_cmd_line_args=scoped_cmd_line_args, unscoped_cmd_line_args=unscoped_cmd_line_args, typ=typ, fromfile=kwargs.get('fromfile', False), default=default, help=help_msg, deprecated_version=deprecated_version, deprecated_message=deprecated_message, deprecated_hint=deprecated_hint, choices=choices) return ret
def _validate(self, args, kwargs): """Validate option registration arguments.""" def error(exception_type, arg_name=None, **msg_kwargs): if arg_name is None: arg_name = args[0] if args else '<unknown>' raise exception_type(self.scope, arg_name, **msg_kwargs) if not args: error(NoOptionNames) # validate args. for arg in args: if not arg.startswith('-'): error(OptionNameDash, arg_name=arg) if not arg.startswith('--') and len(arg) > 2: error(OptionNameDoubleDash, arg_name=arg) # Validate kwargs. if kwargs.get('action', 'store') not in self._allowed_actions: error(InvalidAction, action=kwargs['action']) if is_boolean_flag(kwargs) and 'type' in kwargs: error(BooleanOptionType) if 'implicit_value' in kwargs: if is_boolean_flag(kwargs): error(BooleanOptionImplicitVal) elif kwargs['implicit_value'] is None: error(ImplicitValIsNone) for kwarg in kwargs: if kwarg not in self._allowed_registration_kwargs: error(InvalidKwarg, kwarg=kwarg) deprecated_ver = kwargs.get('deprecated_version') if deprecated_ver is not None: check_deprecated_semver(deprecated_ver, check_expired=False)
def get_option_help_info(self, args, kwargs): """Returns an OptionHelpInfo for the option registered with the given (args, kwargs). :API: public """ display_args = [] scoped_cmd_line_args = [] unscoped_cmd_line_args = [] for arg in args: is_short_arg = len(arg) == 2 unscoped_cmd_line_args.append(arg) if self._scope_prefix: scoped_arg = '--{}-{}'.format(self._scope_prefix, arg.lstrip('-')) else: scoped_arg = arg scoped_cmd_line_args.append(scoped_arg) if is_boolean_flag(kwargs): if is_short_arg: display_arg = scoped_arg else: unscoped_cmd_line_args.append('--no-{}'.format(arg[2:])) scoped_cmd_line_args.append('--no-{}'.format(scoped_arg[2:])) display_arg = '--[no-]{}'.format(scoped_arg[2:]) else: display_arg = '{}={}'.format(scoped_arg, self.compute_metavar(kwargs)) if kwargs.get('action') == 'append': display_arg = '{arg_str} ({arg_str}) ...'.format(arg_str=display_arg) display_args.append(display_arg) if is_boolean_flag(kwargs): typ = bool else: typ = kwargs.get('type', str) default = self.compute_default(kwargs) help_msg = kwargs.get('help', 'No help available.') deprecated_version = kwargs.get('deprecated_version') deprecated_message = None if deprecated_version: deprecated_tense = self._get_deprecated_tense(deprecated_version) deprecated_message = 'DEPRECATED. {} removed in version: {}'.format(deprecated_tense, deprecated_version) deprecated_hint = kwargs.get('deprecated_hint') choices = ', '.join(kwargs.get('choices')) if kwargs.get('choices') else None ret = OptionHelpInfo(registering_class=kwargs.get('registering_class', type(None)), display_args=display_args, scoped_cmd_line_args=scoped_cmd_line_args, unscoped_cmd_line_args=unscoped_cmd_line_args, typ=typ, fromfile=kwargs.get('fromfile', False), default=default, help=help_msg, deprecated_version=deprecated_version, deprecated_message=deprecated_message, deprecated_hint=deprecated_hint, choices=choices) return ret
def capture_the_flags(*args, **kwargs): for arg in args: flags.add(arg) if len(arg) == 2: short_flags.add(arg) elif is_boolean_flag(kwargs): flags.add('--no-{}'.format(arg[2:]))
def _compute_default(self, dest, kwargs): """Compute the default value to use for an option's registration. The source of the default value is chosen according to the ranking in RankedValue. """ config_section = 'DEFAULT' if self._scope == GLOBAL_SCOPE else self._scope udest = dest.upper() if self._scope == GLOBAL_SCOPE: # For convenience, we allow three forms of env var for global scope options. # The fully-specified env var is PANTS_DEFAULT_FOO, which is uniform with PANTS_<SCOPE>_FOO # for all the other scopes. However we also allow simply PANTS_FOO. And if the option name # itself starts with 'pants-' then we also allow simply FOO. E.g., PANTS_WORKDIR instead of # PANTS_PANTS_WORKDIR or PANTS_DEFAULT_PANTS_WORKDIR. We take the first specified value we # find, in this order: PANTS_DEFAULT_FOO, PANTS_FOO, FOO. env_vars = [ 'PANTS_DEFAULT_{0}'.format(udest), 'PANTS_{0}'.format(udest) ] if udest.startswith('PANTS_'): env_vars.append(udest) else: sanitized_env_var_scope = self._ENV_SANITIZER_RE.sub( '_', config_section.upper()) env_vars = ['PANTS_{0}_{1}'.format(sanitized_env_var_scope, udest)] value_type = self.str_to_bool if is_boolean_flag( kwargs) else kwargs.get('type', str) env_val_str = None if self._env: for env_var in env_vars: if env_var in self._env: env_val_str = self._env.get(env_var) break env_val = None if env_val_str is None else value_type(env_val_str) if kwargs.get('action') == 'append': config_val_strs = self._config.getlist( config_section, dest) if self._config else None config_val = (None if config_val_strs is None else [ value_type(config_val_str) for config_val_str in config_val_strs ]) default = [] else: config_val_str = (self._config.get( config_section, dest, default=None) if self._config else None) config_val = None if config_val_str is None else value_type( config_val_str) default = None hardcoded_val = kwargs.get('default') return RankedValue.choose(None, env_val, config_val, hardcoded_val, default)
def _argparse_register(self, args, kwargs): """Do the deferred argparse registration.""" # This check prevents repeat registration of the same option, e.g., during bootstrapping, # when we parse some global options multiple times. if args[0] not in self._argparse_registered_args: self._validate(args, kwargs) argparse_kwargs = self._clean_argparse_kwargs(kwargs) if is_boolean_flag(argparse_kwargs): inverse_args = self._create_inverse_args(args) if inverse_args: inverse_argparse_kwargs = self._create_inverse_kwargs(argparse_kwargs) group = self._argparser.add_mutually_exclusive_group() group.add_argument(*args, **argparse_kwargs) group.add_argument(*inverse_args, **inverse_argparse_kwargs) else: self._argparser.add_argument(*args, **argparse_kwargs) else: self._argparser.add_argument(*args, **argparse_kwargs) self._argparse_registered_args.update(args)
def _register(self, dest, args, kwargs): """Register the option for parsing (recursively if needed).""" argparse_kwargs, recursive = self._clean_argparse_kwargs(dest, args, kwargs) if is_boolean_flag(argparse_kwargs): inverse_args = self._create_inverse_args(args) if inverse_args: inverse_argparse_kwargs = self._create_inverse_kwargs(argparse_kwargs) group = self._argparser.add_mutually_exclusive_group() group.add_argument(*args, **argparse_kwargs) group.add_argument(*inverse_args, **inverse_argparse_kwargs) else: self._argparser.add_argument(*args, **argparse_kwargs) else: self._argparser.add_argument(*args, **argparse_kwargs) if recursive: # Propagate registration down to inner scopes. for child_parser in self._child_parsers: kwargs.pop("recursive_root", False) child_parser._register(dest, args, kwargs)
def _register(self, dest, args, kwargs): """Register the option for parsing (recursively if needed).""" argparse_kwargs, recursive = self._clean_argparse_kwargs(dest, args, kwargs) if is_boolean_flag(argparse_kwargs): inverse_args = self._create_inverse_args(args) if inverse_args: inverse_argparse_kwargs = self._create_inverse_kwargs(argparse_kwargs) group = self._argparser.add_mutually_exclusive_group() group.add_argument(*args, **argparse_kwargs) group.add_argument(*inverse_args, **inverse_argparse_kwargs) else: self._argparser.add_argument(*args, **argparse_kwargs) else: self._argparser.add_argument(*args, **argparse_kwargs) if recursive: # Propagate registration down to inner scopes. for child_parser in self._child_parsers: kwargs.pop('recursive_root', False) child_parser._register(dest, args, kwargs)
def _argparse_register(self, args, kwargs): """Do the deferred argparse registration.""" # This check prevents repeat registration of the same option, e.g., during bootstrapping, # when we parse some global options multiple times. if args[0] not in self._argparse_registered_args: self._validate(args, kwargs) argparse_kwargs = self._clean_argparse_kwargs(kwargs) if is_boolean_flag(argparse_kwargs): inverse_args = self._create_inverse_args(args) if inverse_args: inverse_argparse_kwargs = self._create_inverse_kwargs( argparse_kwargs) group = self._argparser.add_mutually_exclusive_group() group.add_argument(*args, **argparse_kwargs) group.add_argument(*inverse_args, **inverse_argparse_kwargs) else: self._argparser.add_argument(*args, **argparse_kwargs) else: self._argparser.add_argument(*args, **argparse_kwargs) self._argparse_registered_args.update(args)
def _compute_default(self, dest, kwargs): """Compute the default value to use for an option's registration. The source of the default value is chosen according to the ranking in RankedValue. """ config_section = 'DEFAULT' if self._scope == GLOBAL_SCOPE else self._scope udest = dest.upper() if self._scope == GLOBAL_SCOPE: # For convenience, we allow three forms of env var for global scope options. # The fully-specified env var is PANTS_DEFAULT_FOO, which is uniform with PANTS_<SCOPE>_FOO # for all the other scopes. However we also allow simply PANTS_FOO. And if the option name # itself starts with 'pants-' then we also allow simply FOO. E.g., PANTS_WORKDIR instead of # PANTS_PANTS_WORKDIR or PANTS_DEFAULT_PANTS_WORKDIR. We take the first specified value we # find, in this order: PANTS_DEFAULT_FOO, PANTS_FOO, FOO. env_vars = ['PANTS_DEFAULT_{0}'.format(udest), 'PANTS_{0}'.format(udest)] if udest.startswith('PANTS_'): env_vars.append(udest) else: sanitized_env_var_scope = self._ENV_SANITIZER_RE.sub('_', config_section.upper()) env_vars = ['PANTS_{0}_{1}'.format(sanitized_env_var_scope, udest)] value_type = self.str_to_bool if is_boolean_flag(kwargs) else kwargs.get('type', str) env_val_str = None if self._env: for env_var in env_vars: if env_var in self._env: env_val_str = self._env.get(env_var) break env_val = None if env_val_str is None else value_type(env_val_str) if kwargs.get('action') == 'append': config_val_strs = self._config.getlist(config_section, dest) if self._config else None config_val = (None if config_val_strs is None else [value_type(config_val_str) for config_val_str in config_val_strs]) default = [] else: config_val_str = (self._config.get(config_section, dest, default=None) if self._config else None) config_val = None if config_val_str is None else value_type(config_val_str) default = None hardcoded_val = kwargs.get('default') return RankedValue.choose(None, env_val, config_val, hardcoded_val, default)
def _compute_value(self, dest, kwargs, flag_vals): """Compute the value to use for an option. The source of the default value is chosen according to the ranking in RankedValue. """ is_fromfile = kwargs.get('fromfile', False) action = kwargs.get('action') if is_fromfile and action and action != 'append': raise ParseError('Cannot fromfile {} with an action ({}) in scope {}' .format(dest, action, self._scope)) config_section = GLOBAL_SCOPE_CONFIG_SECTION if self._scope == GLOBAL_SCOPE else self._scope udest = dest.upper() if self._scope == GLOBAL_SCOPE: # For convenience, we allow three forms of env var for global scope options. # The fully-specified env var is PANTS_DEFAULT_FOO, which is uniform with PANTS_<SCOPE>_FOO # for all the other scopes. However we also allow simply PANTS_FOO. And if the option name # itself starts with 'pants-' then we also allow simply FOO. E.g., PANTS_WORKDIR instead of # PANTS_PANTS_WORKDIR or PANTS_DEFAULT_PANTS_WORKDIR. We take the first specified value we # find, in this order: PANTS_DEFAULT_FOO, PANTS_FOO, FOO. env_vars = ['PANTS_DEFAULT_{0}'.format(udest), 'PANTS_{0}'.format(udest)] if udest.startswith('PANTS_'): env_vars.append(udest) else: sanitized_env_var_scope = self._ENV_SANITIZER_RE.sub('_', config_section.upper()) env_vars = ['PANTS_{0}_{1}'.format(sanitized_env_var_scope, udest)] value_type = self.str_to_bool if is_boolean_flag(kwargs) else kwargs.get('type', str) env_val_str = None if self._env: for env_var in env_vars: if env_var in self._env: env_val_str = self._env.get(env_var) break config_val_str = self._config.get(config_section, dest, default=None) config_source_file = self._config.get_source_for_option(config_section, dest) if config_source_file is not None: config_source_file = os.path.relpath(config_source_file) def expand(val_str): if is_fromfile and val_str and val_str.startswith('@') and not val_str.startswith('@@'): fromfile = val_str[1:] try: with open(fromfile) as fp: return fp.read().strip() except IOError as e: raise self.FromfileError('Failed to read {} in {} from file {}: {}'.format( dest, self._scope_str(), fromfile, e)) else: # Support a literal @ for fromfile values via @@. return val_str[1:] if is_fromfile and val_str.startswith('@@') else val_str def parse_typed_list(val_str): return None if val_str is None else [value_type(x) for x in list_option(expand(val_str))] def parse_typed_item(val_str): return None if val_str is None else value_type(expand(val_str)) flag_val = None if flag_vals: if action == 'append': flag_val = [parse_typed_item(v) for v in flag_vals] elif len(flag_vals) > 1: raise ParseError('Multiple cmd line flags specified for option {} in {}'.format( dest, self._scope_str())) else: flag_val = parse_typed_item(flag_vals[0]) default, parse = ([], parse_typed_list) if action == 'append' else (None, parse_typed_item) config_val = parse(config_val_str) env_val = parse(env_val_str) hardcoded_val = kwargs.get('default') config_details = 'in {}'.format(config_source_file) if config_source_file else None # Note: ranked_vals is guaranteed to have at least one element, and none of the values # of any of its elements will be None. ranked_vals = list(reversed(list(RankedValue.prioritized_iter( flag_val, env_val, config_val, hardcoded_val, default)))) choices = kwargs.get('choices') for ranked_val in ranked_vals: details = config_details if ranked_val.rank == RankedValue.CONFIG else None self._option_tracker.record_option(scope=self._scope, option=dest, value=ranked_val.value, rank=ranked_val.rank, deprecation_version=kwargs.get('deprecated_version'), details=details) def check(val): if choices is not None and val is not None and val not in choices: raise ParseError('{} is not an allowed value for option {} in {}. ' 'Must be one of: {}'.format( val, dest, self._scope_str(), choices )) return val if action == 'append': merged_rank = ranked_vals[-1].rank merged_val = [check(val) for vals in ranked_vals for val in vals.value] return RankedValue(merged_rank, merged_val) else: map(lambda rv: check(rv.value), ranked_vals) return ranked_vals[-1]
def parse_args(self, flags, namespace): """Set values for this parser's options on the namespace object.""" flag_value_map = self._create_flag_value_map(flags) for args, kwargs in self._unnormalized_option_registrations_iter(): self._validate(args, kwargs) dest = kwargs.get('dest') or self._select_dest(args) is_bool = is_boolean_flag(kwargs) def consume_flag(flag): self._check_deprecated(dest, kwargs) del flag_value_map[flag] # Compute the values provided on the command line for this option. Note tha there may be # multiple values, for any combination of the following reasons: # - The user used the same flag multiple times. # - The user specified a boolean flag (--foo) and its inverse (--no-foo). # - The option has multiple names, and the user used more than one of them. # # We also check if the option is deprecated, but we only do so if the option is explicitly # specified as a command-line flag, so we don't spam users with deprecated option values # specified in config, which isn't something they control. implicit_value = kwargs.get('implicit_value') flag_vals = [] def add_flag_val(v): if v is None: if implicit_value is None: raise ParseError('Missing value for command line flag {} in {}'.format( arg, self._scope_str())) else: flag_vals.append(implicit_value) else: flag_vals.append(v) for arg in args: if is_bool: if arg in flag_value_map: flag_vals.append('true' if kwargs['action'] == 'store_true' else 'false') consume_flag(arg) elif self._inverse_arg(arg) in flag_value_map: flag_vals.append('false' if kwargs['action'] == 'store_true' else 'true') consume_flag(self._inverse_arg(arg)) else: if arg in flag_value_map: for v in flag_value_map[arg]: add_flag_val(v) consume_flag(arg) # Get the value for this option, falling back to defaults as needed. try: val = self._compute_value(dest, kwargs, flag_vals) except ParseError as e: # Reraise a new exception with context on the option being processed at the time of error. # Note that other exception types can be raised here that are caught by ParseError (e.g. # BooleanConversionError), hence we reference the original exception type by e.__class__. raise type(e)( 'Error computing value for {} in {} (may also be from PANTS_* environment variables):\n' '{}'.format(', '.join(args), self._scope_str(), e) ) setattr(namespace, dest, val) # See if there are any unconsumed flags remaining. if flag_value_map: raise ParseError('Unrecognized command line flags on {}: {}'.format( self._scope_str(), ', '.join(flag_value_map.keys()))) return namespace
def _compute_default(self, kwargs): """Compute the default value to use for an option's registration. The source of the default value is chosen according to the ranking in RankedValue. Note: Only call if kwargs has a 'dest' key set. """ dest = kwargs['dest'] is_fromfile = kwargs.get('fromfile', False) action = kwargs.get('action') if is_fromfile and action and action != 'append': raise ParseError('Cannot fromfile {} with an action ({}) in scope {}' .format(dest, action, self._scope)) config_section = 'DEFAULT' if self._scope == GLOBAL_SCOPE else self._scope udest = dest.upper() if self._scope == GLOBAL_SCOPE: # For convenience, we allow three forms of env var for global scope options. # The fully-specified env var is PANTS_DEFAULT_FOO, which is uniform with PANTS_<SCOPE>_FOO # for all the other scopes. However we also allow simply PANTS_FOO. And if the option name # itself starts with 'pants-' then we also allow simply FOO. E.g., PANTS_WORKDIR instead of # PANTS_PANTS_WORKDIR or PANTS_DEFAULT_PANTS_WORKDIR. We take the first specified value we # find, in this order: PANTS_DEFAULT_FOO, PANTS_FOO, FOO. env_vars = ['PANTS_DEFAULT_{0}'.format(udest), 'PANTS_{0}'.format(udest)] if udest.startswith('PANTS_'): env_vars.append(udest) else: sanitized_env_var_scope = self._ENV_SANITIZER_RE.sub('_', config_section.upper()) env_vars = ['PANTS_{0}_{1}'.format(sanitized_env_var_scope, udest)] value_type = self.str_to_bool if is_boolean_flag(kwargs) else kwargs.get('type', str) env_val_str = None if self._env: for env_var in env_vars: if env_var in self._env: env_val_str = self._env.get(env_var) break config_val_str = self._config.get(config_section, dest, default=None) config_source_file = self._config.get_source_for_option(config_section, dest) if config_source_file is not None: config_source_file = os.path.relpath(config_source_file) def expand(val_str): if is_fromfile and val_str and val_str.startswith('@') and not val_str.startswith('@@'): fromfile = val_str[1:] try: with open(fromfile) as fp: return fp.read().strip() except IOError as e: raise self.FromfileError('Failed to read {} from file {}: {}'.format(dest, fromfile, e)) else: # Support a literal @ for fromfile values via @@. return val_str[1:] if is_fromfile and val_str.startswith('@@') else val_str def parse_typed_list(val_str): return None if val_str is None else [value_type(x) for x in list_option(expand(val_str))] def parse_typed_item(val_str): return None if val_str is None else value_type(expand(val_str)) # Handle the forthcoming conversions argparse will need to do by placing our parse hook - we # handle the conversions for env and config ourselves below. Unlike the env and config # handling, `action='append'` does not need to be handled specially since appended flag values # come as single items' thus only `parse_typed_item` is ever needed for the flag value type # conversions. if is_fromfile: kwargs['type'] = parse_typed_item default, parse = ([], parse_typed_list) if action == 'append' else (None, parse_typed_item) config_val = parse(config_val_str) env_val = parse(env_val_str) hardcoded_val = kwargs.get('default') config_details = 'in {}'.format(config_source_file) if config_source_file else None choices = list(RankedValue.prioritized_iter(None, env_val, config_val, hardcoded_val, default)) for choice in reversed(choices): details = config_details if choice.rank == RankedValue.CONFIG else None self._option_tracker.record_option(scope=self._scope, option=dest, value=choice.value, rank=choice.rank, details=details) return choices[0]
def _compute_default(self, dest, kwargs): """Compute the default value to use for an option's registration. The source of the default value is chosen according to the ranking in RankedValue. """ is_fromfile = kwargs.get('fromfile', False) action = kwargs.get('action') if is_fromfile and action and action != 'append': raise ParseError( 'Cannot fromfile {} with an action ({}) in scope {}'.format( dest, action, self._scope)) config_section = 'DEFAULT' if self._scope == GLOBAL_SCOPE else self._scope udest = dest.upper() if self._scope == GLOBAL_SCOPE: # For convenience, we allow three forms of env var for global scope options. # The fully-specified env var is PANTS_DEFAULT_FOO, which is uniform with PANTS_<SCOPE>_FOO # for all the other scopes. However we also allow simply PANTS_FOO. And if the option name # itself starts with 'pants-' then we also allow simply FOO. E.g., PANTS_WORKDIR instead of # PANTS_PANTS_WORKDIR or PANTS_DEFAULT_PANTS_WORKDIR. We take the first specified value we # find, in this order: PANTS_DEFAULT_FOO, PANTS_FOO, FOO. env_vars = [ 'PANTS_DEFAULT_{0}'.format(udest), 'PANTS_{0}'.format(udest) ] if udest.startswith('PANTS_'): env_vars.append(udest) else: sanitized_env_var_scope = self._ENV_SANITIZER_RE.sub( '_', config_section.upper()) env_vars = ['PANTS_{0}_{1}'.format(sanitized_env_var_scope, udest)] value_type = self.str_to_bool if is_boolean_flag( kwargs) else kwargs.get('type', str) env_val_str = None if self._env: for env_var in env_vars: if env_var in self._env: env_val_str = self._env.get(env_var) break config_val_str = self._config.get(config_section, dest, default=None) def expand(val_str): if is_fromfile and val_str and val_str.startswith( '@') and not val_str.startswith('@@'): fromfile = val_str[1:] try: with open(fromfile) as fp: return fp.read().strip() except IOError as e: raise self.FromfileError( 'Failed to read {} from file {}: {}'.format( dest, fromfile, e)) else: # Support a literal @ for fromfile values via @@. return val_str[1:] if is_fromfile and val_str.startswith( '@@') else val_str def parse_typed_list(val_str): return None if val_str is None else [ value_type(x) for x in list_option(expand(val_str)) ] def parse_typed_item(val_str): return None if val_str is None else value_type(expand(val_str)) # Handle the forthcoming conversions argparse will need to do by placing our parse hook - we # handle the conversions for env and config ourselves below. Unlike the env and config # handling, `action='append'` does not need to be handled specially since appended flag values # come as single items' thus only `parse_typed_item` is ever needed for the flag value type # conversions. if is_fromfile: kwargs['type'] = parse_typed_item default, parse = ([], parse_typed_list) if action == 'append' else ( None, parse_typed_item) config_val = parse(config_val_str) env_val = parse(env_val_str) hardcoded_val = kwargs.get('default') return RankedValue.choose(None, env_val, config_val, hardcoded_val, default)
def _compute_default(self, dest, kwargs): """Compute the default value to use for an option's registration. The source of the default value is chosen according to the ranking in RankedValue. """ is_fromfile = kwargs.get('fromfile', False) action = kwargs.get('action') if is_fromfile and action: raise ParseError('Cannot fromfile {} with an action ({}) in scope {}' .format(dest, action, self._scope)) config_section = 'DEFAULT' if self._scope == GLOBAL_SCOPE else self._scope udest = dest.upper() if self._scope == GLOBAL_SCOPE: # For convenience, we allow three forms of env var for global scope options. # The fully-specified env var is PANTS_DEFAULT_FOO, which is uniform with PANTS_<SCOPE>_FOO # for all the other scopes. However we also allow simply PANTS_FOO. And if the option name # itself starts with 'pants-' then we also allow simply FOO. E.g., PANTS_WORKDIR instead of # PANTS_PANTS_WORKDIR or PANTS_DEFAULT_PANTS_WORKDIR. We take the first specified value we # find, in this order: PANTS_DEFAULT_FOO, PANTS_FOO, FOO. env_vars = ['PANTS_DEFAULT_{0}'.format(udest), 'PANTS_{0}'.format(udest)] if udest.startswith('PANTS_'): env_vars.append(udest) else: sanitized_env_var_scope = self._ENV_SANITIZER_RE.sub('_', config_section.upper()) env_vars = ['PANTS_{0}_{1}'.format(sanitized_env_var_scope, udest)] value_type = self.str_to_bool if is_boolean_flag(kwargs) else kwargs.get('type', str) env_val_str = None if self._env: for env_var in env_vars: if env_var in self._env: env_val_str = self._env.get(env_var) break def expand(val_str): if is_fromfile and val_str and val_str.startswith('@') and not val_str.startswith('@@'): fromfile = val_str[1:] try: with open(fromfile) as fp: return fp.read().strip() except IOError as e: raise self.FromfileError('Failed to read {} from file {}: {}'.format(dest, fromfile, e)) else: # Support a literal @ for fromfile values via @@. return val_str[1:] if is_fromfile and val_str.startswith('@@') else val_str if is_fromfile: kwargs['type'] = lambda flag_val_str: value_type(expand(flag_val_str)) # Expand flag values. env_val = None if env_val_str is None else value_type(expand(env_val_str)) # Expand env values. config_val = None if action == 'append': config_val_strs = self._config.getlist(config_section, dest) if self._config else None if config_val_strs is not None: config_val = [value_type(config_val_str) for config_val_str in config_val_strs] default = [] else: config_val_str = (self._config.get(config_section, dest, default=None) if self._config else None) if config_val_str is not None: config_val = value_type(expand(config_val_str)) # Expand config values. default = None hardcoded_val = kwargs.get('default') # We don't expand hard coded defaults. return RankedValue.choose(None, env_val, config_val, hardcoded_val, default)