def test_failure_type(self) -> None: # Prove there is no syntax error in the raw value. literal = parse_expression("1.0", acceptable_types=float) assert 1.0 == literal # Now we can safely assume the ValueError is raise due to type checking. with pytest.raises(ValueError): parse_expression("1.0", acceptable_types=int)
def test_failure_type(self): # Prove there is no syntax error in the raw value. literal = parse_expression('1.0', acceptable_types=float) self.assertEqual(1.0, literal) # Now we can safely assume the ValueError is raise due to type checking. with self.assertRaises(ValueError): parse_expression('1.0', acceptable_types=int)
def test_custom_error_type(self): class CustomError(Exception): pass with self.assertRaises(CustomError): parse_expression('1.0', acceptable_types=int, raise_type=CustomError)
def _convert(val, acceptable_types): """Ensure that val is one of the acceptable types, converting it if needed. :param string val: The value we're parsing. :param acceptable_types: A tuple of expected types for val. :returns: The parsed value. :raises :class:`pants.options.errors.ParseError`: if there was a problem parsing the val as an acceptable type. """ return parse_expression(val, acceptable_types, raise_type=ParseError)
def _getinstance(self, section, option, type_, default=None): if not self.has_option(section, option): return default raw_value = self.get_value(section, option) if type_ == str or issubclass(type_, str): return raw_value key = '{}.{}'.format(section, option) return parse_expression(name=key, val=raw_value, acceptable_types=type_, raise_type=self.ConfigError)
def _getinstance(self, section, option, type_, default=None): if not self.has_option(section, option): return default raw_value = self.get_value(section, option) # We jump through some hoops here to deal with the fact that `six.string_types` is a tuple of # types. if (type_ == six.string_types or (isinstance(type_, type) and issubclass(type_, six.string_types))): return raw_value key = '{}.{}'.format(section, option) return parse_expression(name=key, val=raw_value, acceptable_types=type_, raise_type=self.ConfigError)
def _convert(val, acceptable_types): """Ensure that val is one of the acceptable types, converting it if needed. :param val: The value we're parsing (either a string or one of the acceptable types). :param acceptable_types: A tuple of expected types for val. :returns: The parsed value. :raises :class:`pants.options.errors.ParseError`: if there was a problem parsing the val as an acceptable type. """ if isinstance(val, acceptable_types): return val try: return parse_expression(val, acceptable_types) except ValueError as e: raise ParseError(str(e)) from e
def get(self, section, option, type_=str, default=None): """Retrieves option from the specified section (or 'DEFAULT') and attempts to parse it as type. If the specified section does not exist or is missing a definition for the option, the value is looked up in the DEFAULT section. If there is still no definition found, the default value supplied is returned. """ if not self.has_option(section, option): return default raw_value = self.get_value(section, option) if issubclass(type_, str): return raw_value key = f"{section}.{option}" return parse_expression(name=key, val=raw_value, acceptable_types=type_, raise_type=self.ConfigError)
def test_success_mixed(self) -> None: literal = parse_expression("42", acceptable_types=(float, int)) assert 42 == literal
def test_success_list_concat(self): # This is actually useful in config files. self.assertEqual([1, 2, 3], parse_expression('[1, 2] + [3]', acceptable_types=list))
def test_success_complex_syntax(self): self.assertEqual(3, parse_expression('1+2', acceptable_types=int))
def test_success_complex_syntax(self) -> None: assert 3 == parse_expression("1+2", acceptable_types=int)
def test_success_simple(self): literal = parse_expression("'42'", acceptable_types=string_types) self.assertEqual('42', literal)
def test_success_list_concat(self) -> None: # This is actually useful in config files. assert [1, 2, 3] == parse_expression("[1, 2] + [3]", acceptable_types=list)
def test_success_simple(self) -> None: literal = parse_expression("'42'", acceptable_types=str) assert "42" == literal
def test_success_complex_syntax(self) -> None: self.assertEqual(3, parse_expression("1+2", acceptable_types=int))
def test_success_simple(self) -> None: literal = parse_expression("'42'", acceptable_types=str) self.assertEqual("42", literal)
def test_success_mixed(self): literal = parse_expression('42', acceptable_types=(float, int)) self.assertEqual(42, literal)
def test_success_simple(self): literal = parse_expression("'42'", acceptable_types=future.utils.string_types) self.assertEqual('42', literal)
def create( cls, env: Mapping[str, str], args: Sequence[str], *, allow_pantsrc: bool ) -> OptionsBootstrapper: """Parses the minimum amount of configuration necessary to create an OptionsBootstrapper. :param env: An environment dictionary. :param args: An args array. :param allow_pantsrc: True to allow pantsrc files to be used. Unless tests are expecting to consume pantsrc files, they should pass False in order to avoid reading files from absolute paths. Production usecases should pass True to allow options values to make the decision of whether to respect pantsrc files. """ with warnings.catch_warnings(record=True): # We can't use pants.engine.fs.FileContent here because it would cause a circular dep. @dataclass(frozen=True) class FileContent: path: str content: bytes def filecontent_for(path: str) -> FileContent: return FileContent( ensure_text(path), read_file(path, binary_mode=True), ) bargs = cls._get_bootstrap_args(args) config_file_paths = cls.get_config_file_paths(env=env, args=args) config_files_products = [filecontent_for(p) for p in config_file_paths] pre_bootstrap_config = Config.load(config_files_products, env=env) initial_bootstrap_options = cls.parse_bootstrap_options( env, bargs, pre_bootstrap_config ) bootstrap_option_values = initial_bootstrap_options.for_global_scope() # Now re-read the config, post-bootstrapping. Note the order: First whatever we bootstrapped # from (typically pants.toml), then config override, then rcfiles. full_config_paths = pre_bootstrap_config.sources() if allow_pantsrc and bootstrap_option_values.pantsrc: rcfiles = [ os.path.expanduser(str(rcfile)) for rcfile in bootstrap_option_values.pantsrc_files ] existing_rcfiles = list(filter(os.path.exists, rcfiles)) full_config_paths.extend(existing_rcfiles) full_config_files_products = [filecontent_for(p) for p in full_config_paths] post_bootstrap_config = Config.load( full_config_files_products, seed_values=bootstrap_option_values.as_dict(), env=env, ) # Finally, we expand any aliases and re-populate the bootstrap args, in case there # were any from aliases. # stuhood: This could potentially break the rust client when aliases are used: # https://github.com/pantsbuild/pants/pull/13228#discussion_r728223889 alias_vals = post_bootstrap_config.get("cli", "alias") alias_dict = parse_expression( name="cli.alias", val=alias_vals[-1] if alias_vals else "{}", acceptable_types=dict, ) alias = CliAlias.from_dict(alias_dict) args = alias.expand_args(tuple(args)) bargs = cls._get_bootstrap_args(args) # We need to set this env var to allow various static help strings to reference the # right name (via `pants.util.docutil`), and we need to do it as early as possible to # avoid needing to lazily import code to avoid chicken-and-egg-problems. This is the # earliest place it makes sense to do so and is generically used by both the local and # remote pants runners. os.environ["PANTS_BIN_NAME"] = bootstrap_option_values.pants_bin_name env_tuples = tuple( sorted( (item for item in env.items() if item[0].startswith("PANTS_")), ) ) return cls( env_tuples=env_tuples, bootstrap_args=bargs, args=args, config=post_bootstrap_config, alias=alias, )