Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-configargparse for openSUSE:Factory checked in at 2021-07-12 01:25:14 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-configargparse (Old) and /work/SRC/openSUSE:Factory/.python-configargparse.new.2625 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-configargparse" Mon Jul 12 01:25:14 2021 rev:13 rq:905704 version:1.5.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-configargparse/python-configargparse.changes 2021-06-09 21:52:48.926529642 +0200 +++ /work/SRC/openSUSE:Factory/.python-configargparse.new.2625/python-configargparse.changes 2021-07-12 01:25:35.128978103 +0200 @@ -1,0 +2,6 @@ +Sun Jul 4 19:52:10 UTC 2021 - Dirk M??ller <dmuel...@suse.com> + +- update to 1.5.1: + * no changes file available + +------------------------------------------------------------------- Old: ---- ConfigArgParse-1.3.tar.gz New: ---- ConfigArgParse-1.5.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-configargparse.spec ++++++ --- /var/tmp/diff_new_pack.Hace3N/_old 2021-07-12 01:25:35.500975243 +0200 +++ /var/tmp/diff_new_pack.Hace3N/_new 2021-07-12 01:25:35.504975212 +0200 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %bcond_without python2 Name: python-configargparse -Version: 1.3 +Version: 1.5.1 Release: 0 Summary: A drop-in replacement for argparse License: MIT ++++++ ConfigArgParse-1.3.tar.gz -> ConfigArgParse-1.5.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ConfigArgParse-1.3/ConfigArgParse.egg-info/PKG-INFO new/ConfigArgParse-1.5.1/ConfigArgParse.egg-info/PKG-INFO --- old/ConfigArgParse-1.3/ConfigArgParse.egg-info/PKG-INFO 2021-02-15 00:17:35.000000000 +0100 +++ new/ConfigArgParse-1.5.1/ConfigArgParse.egg-info/PKG-INFO 2021-07-01 15:11:14.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ConfigArgParse -Version: 1.3 +Version: 1.5.1 Summary: A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables. Home-page: https://github.com/bw2/ConfigArgParse License: MIT @@ -95,7 +95,7 @@ .. code:: py - # settings for my_script.py + # settings for config_test.py genome = HCMV # cytomegalovirus genome dbsnp = /data/dbsnp/variants.vcf @@ -486,7 +486,9 @@ Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* +Provides-Extra: test Provides-Extra: yaml diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ConfigArgParse-1.3/ConfigArgParse.egg-info/SOURCES.txt new/ConfigArgParse-1.5.1/ConfigArgParse.egg-info/SOURCES.txt --- old/ConfigArgParse-1.3/ConfigArgParse.egg-info/SOURCES.txt 2021-02-15 00:17:36.000000000 +0100 +++ new/ConfigArgParse-1.5.1/ConfigArgParse.egg-info/SOURCES.txt 2021-07-01 15:11:14.000000000 +0200 @@ -10,5 +10,4 @@ ConfigArgParse.egg-info/top_level.txt tests/__init__.py tests/__init__.pyc -tests/test_configargparse.py -tests/test_configargparse.pyc \ No newline at end of file +tests/test_configargparse.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ConfigArgParse-1.3/ConfigArgParse.egg-info/requires.txt new/ConfigArgParse-1.5.1/ConfigArgParse.egg-info/requires.txt --- old/ConfigArgParse-1.3/ConfigArgParse.egg-info/requires.txt 2021-02-15 00:17:35.000000000 +0100 +++ new/ConfigArgParse-1.5.1/ConfigArgParse.egg-info/requires.txt 2021-07-01 15:11:14.000000000 +0200 @@ -1,3 +1,8 @@ +[test] +mock +PyYAML +pytest + [yaml] PyYAML diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ConfigArgParse-1.3/PKG-INFO new/ConfigArgParse-1.5.1/PKG-INFO --- old/ConfigArgParse-1.3/PKG-INFO 2021-02-15 00:17:36.000000000 +0100 +++ new/ConfigArgParse-1.5.1/PKG-INFO 2021-07-01 15:11:14.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ConfigArgParse -Version: 1.3 +Version: 1.5.1 Summary: A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables. Home-page: https://github.com/bw2/ConfigArgParse License: MIT @@ -95,7 +95,7 @@ .. code:: py - # settings for my_script.py + # settings for config_test.py genome = HCMV # cytomegalovirus genome dbsnp = /data/dbsnp/variants.vcf @@ -486,7 +486,9 @@ Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* +Provides-Extra: test Provides-Extra: yaml diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ConfigArgParse-1.3/README.rst new/ConfigArgParse-1.5.1/README.rst --- old/ConfigArgParse-1.3/README.rst 2021-02-14 23:41:31.000000000 +0100 +++ new/ConfigArgParse-1.5.1/README.rst 2021-05-10 21:38:05.000000000 +0200 @@ -89,7 +89,7 @@ .. code:: py - # settings for my_script.py + # settings for config_test.py genome = HCMV # cytomegalovirus genome dbsnp = /data/dbsnp/variants.vcf diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ConfigArgParse-1.3/configargparse.py new/ConfigArgParse-1.5.1/configargparse.py --- old/ConfigArgParse-1.3/configargparse.py 2021-02-15 00:09:27.000000000 +0100 +++ new/ConfigArgParse-1.5.1/configargparse.py 2021-07-01 15:10:04.000000000 +0200 @@ -6,6 +6,7 @@ import sys import types from collections import OrderedDict +import textwrap if sys.version_info >= (3, 0): from io import StringIO @@ -82,8 +83,8 @@ stream: A config file input stream (such as an open file object). Returns: - OrderedDict of items where the keys have type string and the - values have type either string or list (eg. to support config file + OrderedDict: Items where the keys are strings and the + values are either strings or lists (eg. to support config file formats like YAML which allow lists). """ raise NotImplementedError("parse(..) not implemented") @@ -94,8 +95,8 @@ Args: items: an OrderedDict of items to be converted to the config file - format. Keys should be strings, and values should be either strings - or lists. + format. Keys should be strings, and values should be either strings + or lists. Returns: Contents of config file as a string @@ -109,7 +110,7 @@ class DefaultConfigFileParser(ConfigFileParser): """Based on a simplified subset of INI and YAML formats. Here is the - supported syntax: + supported syntax:: # this is a comment @@ -148,22 +149,19 @@ line = line.strip() if not line or line[0] in ["#", ";", "["] or line.startswith("---"): continue - white_space = "\\s*" - key = r"(?P<key>[^:=;#\s]+?)" - value = white_space+r"[:=\s]"+white_space+"(?P<value>.+?)" - comment = white_space+"(?P<comment>\\s[;#].*)?" - - key_only_match = re.match("^" + key + comment + "$", line) - if key_only_match: - key = key_only_match.group("key") - items[key] = "true" - continue - - key_value_match = re.match("^"+key+value+comment+"$", line) - if key_value_match: - key = key_value_match.group("key") - value = key_value_match.group("value") + match = re.match(r'^(?P<key>[^:=;#\s]+)\s*' + r'(?:(?P<equal>[:=\s])\s*([\'"]?)(?P<value>.+?)?\3)?' + r'\s*(?:\s[;#]\s*(?P<comment>.*?)\s*)?$', line) + if match: + key = match.group("key") + equal = match.group('equal') + value = match.group("value") + comment = match.group("comment") + if value is None and equal is not None and equal != ' ': + value = '' + elif value is None: + value = "true" if value.startswith("[") and value.endswith("]"): # handle special case of k=[1,2,3] or other json-like syntax try: @@ -171,13 +169,12 @@ except Exception as e: # for backward compatibility with legacy format (eg. where config value is [a, b, c] instead of proper json ["a", "b", "c"] value = [elem.strip() for elem in value[1:-1].split(",")] - + if comment: + comment = comment.strip()[1:].strip() items[key] = value - continue - - - raise ConfigFileParserException("Unexpected line {} in {}: {}".format(i, - getattr(stream, 'name', 'stream'), line)) + else: + raise ConfigFileParserException("Unexpected line {} in {}: {}".format(i, + getattr(stream, 'name', 'stream'), line)) return items def serialize(self, items): @@ -230,6 +227,7 @@ config.read_string(stream.read()) except Exception as e: raise ConfigFileParserException("Couldn't parse config file: %s" % e) + # convert to dict and remove INI section names result = OrderedDict() for section in config.sections(): @@ -259,13 +257,14 @@ strict=True, empty_lines_in_values=False, ) - items = {"DEFAULT":items} + items = {"DEFAULT": items} config.read_dict(items) stream = io.StringIO() config.write(stream) stream.seek(0) return stream.read() + class YAMLConfigFileParser(ConfigFileParser): """Parses YAML config files. Depends on the PyYAML module. https://pypi.python.org/pypi/PyYAML @@ -313,7 +312,6 @@ return result - def serialize(self, items, default_flow_style=False): """Does the inverse of config parsing by taking parsed values and converting them back to a string representing config file contents. @@ -344,10 +342,10 @@ def __init__(self, *args, **kwargs): - """Supports args of the argparse.ArgumentParser constructor - as **kwargs, as well as the following additional args. + r"""Supports args of the argparse.ArgumentParser constructor + as \*\*kwargs, as well as the following additional args. - Additional Args: + Arguments: add_config_file_help: Whether to add a description of config file syntax to the help message. add_env_var_help: Whether to add something to the help message for @@ -363,12 +361,14 @@ taking precedence over previous ones. This allows an application to look for config files in multiple standard locations such as the install directory, home directory, and current directory. - Also, shell * syntax can be used to specify all conf files in a - directory. For example: - ["/etc/conf/app_config.ini", - "/etc/conf/conf-enabled/*.ini", - "~/.my_app_config.ini", - "./app_config.txt"] + Also, shell \* syntax can be used to specify all conf files in a + directory. For example:: + + ["/etc/conf/app_config.ini", + "/etc/conf/conf-enabled/*.ini", + "~/.my_app_config.ini", + "./app_config.txt"] + ignore_unknown_config_file_keys: If true, settings that are found in a config file but don't correspond to any defined configargparse args will be ignored. If false, they will be @@ -451,29 +451,39 @@ """Supports all the same args as the ArgumentParser.parse_args(..), as well as the following additional args. - Additional Args: + Arguments: args: a list of args as in argparse, or a string (eg. "-x -y bla") config_file_contents: String. Used for testing. env_vars: Dictionary. Used for testing. """ - args, argv = self.parse_known_args(args = args, - namespace = namespace, - config_file_contents = config_file_contents, - env_vars = env_vars) + args, argv = self.parse_known_args( + args=args, + namespace=namespace, + config_file_contents=config_file_contents, + env_vars=env_vars, + ignore_help_args=False) + if argv: self.error('unrecognized arguments: %s' % ' '.join(argv)) return args - - def parse_known_args(self, args = None, namespace = None, - config_file_contents = None, env_vars = os.environ): + def parse_known_args( + self, + args=None, + namespace=None, + config_file_contents=None, + env_vars=os.environ, + ignore_help_args=False, + ): """Supports all the same args as the ArgumentParser.parse_args(..), as well as the following additional args. - Additional Args: + Arguments: args: a list of args as in argparse, or a string (eg. "-x -y bla") - config_file_contents: String. Used for testing. - env_vars: Dictionary. Used for testing. + config_file_contents (str). Used for testing. + env_vars (dict). Used for testing. + ignore_help_args (bool): This flag determines behavior when user specifies --help or -h. If False, + it will have the default behavior - printing help and exiting. If True, it won't do either. """ if args is None: args = sys.argv[1:] @@ -485,6 +495,9 @@ for a in self._actions: a.is_positional_arg = not a.option_strings + if ignore_help_args: + args = [arg for arg in args if arg not in ("-h", "--help")] + # maps a string describing the source (eg. env var) to a settings dict # to keep track of where values came from (used by print_values()). # The settings dicts for env vars and config files will then map @@ -628,6 +641,20 @@ self.write_config_file(namespace, output_file_paths, exit_after=True) return namespace, unknown_args + def get_source_to_settings_dict(self): + """If called after parse_args() or parse_known_args(), this dict. will contain up to 4 keys corresponding + to where a given option's value is coming from: + "command_line" + "environment_variables" + "config_file" + "defaults" + Each such key, will be mapped to another dictionary containing the options set via that method. Here the key + will be the option name, and the value will be a 2-tuple of the form (argparse Action obj, string value). + """ + + return self._source_to_settings + + def write_config_file(self, parsed_namespace, output_file_paths, exit_after=False): """Write the given settings to output files. @@ -652,12 +679,10 @@ for output_file_path in output_file_paths: with self._config_file_open_func(output_file_path, "w") as output_file: output_file.write(file_contents) - message = "Wrote config file to " + ", ".join(output_file_paths) - if exit_after: - self.exit(0, message) - else: - print(message) + print("Wrote config file to " + ", ".join(output_file_paths)) + if exit_after: + self.exit(0) def get_command_line_key_for_unknown_config_file_setting(self, key): """Compute a commandline arg key to be used for a config file setting @@ -681,7 +706,7 @@ source_to_settings: the dictionary described in parse_known_args() parsed_namespace: namespace object created within parse_known_args() Returns: - an OrderedDict where keys are strings and values are either strings + OrderedDict: where keys are strings and values are either strings or lists """ config_file_items = OrderedDict() @@ -745,6 +770,11 @@ elif value.lower() in ("false", "no", "0"): # don't append when set to "false" / "no" pass + elif isinstance(action, argparse._CountAction): + for arg in args: + if any([arg.startswith(s) for s in action.option_strings]): + value = 0 + args += [action.option_strings[0]] * int(value) else: self.error("Unexpected value for %s: '%s'. Expecting 'true', " "'false', 'yes', 'no', '1' or '0'" % (key, value)) @@ -795,7 +825,6 @@ return keys - def _open_config_files(self, command_line_args): """Tries to parse config file path(s) from within command_line_args. Returns a list of opened config files, including files specified on the @@ -854,6 +883,12 @@ errno, msg = e.args else: msg = str(e) + # close previously opened config files + for config_file in config_files: + try: + config_file.close() + except Exception: + pass self.error("Unable to open config file: %s. Error: %s" % ( user_config_file, msg )) @@ -946,10 +981,12 @@ msg += (" If an arg is specified in more than one place, then " "commandline values override %s.") % ( " which override ".join(value_sources)) - if msg: - self.description = (self.description or "") + " " + msg - return argparse.ArgumentParser.format_help(self) + text_width = max(self._get_formatter()._width, 11) + msg = textwrap.fill(msg, text_width) + + return (argparse.ArgumentParser.format_help(self) + + ("\n{}\n".format(msg) if msg != "" else "")) def add_argument(self, *args, **kwargs): @@ -957,7 +994,7 @@ This method supports the same args as ArgumentParser.add_argument(..) as well as the additional args below. - Additional Args: + Arguments: env_var: If set, the value of this environment variable will override any config file or default values for this arg (but can itself be overridden on the commandline). Also, if auto_env_var_prefix is @@ -1018,6 +1055,12 @@ else: arg_names.append(arg_string) + """ + return any( + arg_name.startswith(potential_arg) for potential_arg in potential_command_line_args for arg_name in arg_names + ) + # TODO: need to support allow_abbrev, exit_on.. new args etc. + """ return any( potential_arg in arg_names for potential_arg in potential_command_line_args ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ConfigArgParse-1.3/setup.py new/ConfigArgParse-1.5.1/setup.py --- old/ConfigArgParse-1.3/setup.py 2021-02-15 00:14:13.000000000 +0100 +++ new/ConfigArgParse-1.5.1/setup.py 2021-07-01 14:41:49.000000000 +0200 @@ -49,7 +49,10 @@ command = sys.argv[-1] if command == 'publish': - os.system('python setup.py sdist upload') + os.system('rm -rf dist') + os.system('python setup.py sdist') + os.system('python3 setup.py bdist_wheel') + os.system('twine upload dist/*whl dist/*gz') sys.exit() elif command == "coverage": try: @@ -70,13 +73,15 @@ install_requires = [] tests_require = [ + 'mock', 'PyYAML', + 'pytest', ] setup( name='ConfigArgParse', - version="1.3", + version="1.5.1", description='A drop-in replacement for argparse that allows options to ' 'also be set via config files and/or environment variables.', long_description=long_description, @@ -99,6 +104,7 @@ 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], @@ -108,5 +114,6 @@ tests_require=tests_require, extras_require = { 'yaml': ["PyYAML"], + 'test': tests_require, } ) Binary files old/ConfigArgParse-1.3/tests/__init__.pyc and new/ConfigArgParse-1.5.1/tests/__init__.pyc differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ConfigArgParse-1.3/tests/test_configargparse.py new/ConfigArgParse-1.5.1/tests/test_configargparse.py --- old/ConfigArgParse-1.3/tests/test_configargparse.py 2021-02-14 23:41:54.000000000 +0100 +++ new/ConfigArgParse-1.5.1/tests/test_configargparse.py 2021-07-01 15:04:28.000000000 +0200 @@ -19,6 +19,11 @@ else: from StringIO import StringIO +if sys.version_info >= (3, 10): + OPTIONAL_ARGS_STRING="options" +else: + OPTIONAL_ARGS_STRING="optional arguments" + # set COLUMNS to get expected wrapping os.environ['COLUMNS'] = '80' @@ -35,7 +40,7 @@ def error_method(self, message): raise argparse.ArgumentError(None, message) - def exit_method(self, status, message): + def exit_method(self, status, message=None): self._exit_method_called = True arg_parser._exit_method_called = False @@ -77,9 +82,9 @@ return self.parser - def assertParseArgsRaises(self, regex, *args, **kwargs): + def assertParseArgsRaises(self, regex, args, **kwargs): self.assertRaisesRegex(argparse.ArgumentError, regex, self.parse, - *args, **kwargs) + args=args, **kwargs) class TestBasicUseCases(TestCase): @@ -249,25 +254,24 @@ if not use_groups: self.assertRegex(self.format_help(), 'usage: .* \\[-h\\] --genome GENOME \\[-v\\] -g MY_CFG_FILE\n?' - '\\s+\\[-d DBSNP\\]\\s+\\[-f FRMT\\]\\s+vcf \\[vcf ...\\]\n\n' + - 9*r'(.+\s+)'+ # repeated 8 times because .+ matches atmost 1 line + '\\s+\\[-d DBSNP\\]\\s+\\[-f FRMT\\]\\s+vcf \\[vcf ...\\]\n\n' 'positional arguments:\n' ' vcf \\s+ Variant file\\(s\\)\n\n' - 'optional arguments:\n' + '%s:\n' ' -h, --help \\s+ show this help message and exit\n' ' --genome GENOME \\s+ Path to genome file\n' ' -v\n' ' -g MY_CFG_FILE, --my-cfg-file MY_CFG_FILE\n' ' -d DBSNP, --dbsnp DBSNP\\s+\\[env var: DBSNP_PATH\\]\n' - ' -f FRMT, --format FRMT\\s+\\[env var: OUTPUT_FORMAT\\]\n') + ' -f FRMT, --format FRMT\\s+\\[env var: OUTPUT_FORMAT\\]\n\n'%OPTIONAL_ARGS_STRING + + 7*r'(.+\s*)') else: self.assertRegex(self.format_help(), 'usage: .* \\[-h\\] --genome GENOME \\[-v\\] -g MY_CFG_FILE\n?' - '\\s+\\[-d DBSNP\\]\\s+\\[-f FRMT\\]\\s+vcf \\[vcf ...\\]\n\n'+ - 9*r'.+\s+'+ # repeated 8 times because .+ matches atmost 1 line + '\\s+\\[-d DBSNP\\]\\s+\\[-f FRMT\\]\\s+vcf \\[vcf ...\\]\n\n' 'positional arguments:\n' ' vcf \\s+ Variant file\\(s\\)\n\n' - 'optional arguments:\n' + '%s:\n' ' -h, --help \\s+ show this help message and exit\n\n' 'g1:\n' ' --genome GENOME \\s+ Path to genome file\n' @@ -275,7 +279,8 @@ ' -g MY_CFG_FILE, --my-cfg-file MY_CFG_FILE\n\n' 'g2:\n' ' -d DBSNP, --dbsnp DBSNP\\s+\\[env var: DBSNP_PATH\\]\n' - ' -f FRMT, --format FRMT\\s+\\[env var: OUTPUT_FORMAT\\]\n') + ' -f FRMT, --format FRMT\\s+\\[env var: OUTPUT_FORMAT\\]\n\n'%OPTIONAL_ARGS_STRING + + 7*r'(.+\s*)') self.assertParseArgsRaises("invalid choice: 'ZZZ'", args="--genome hg19 -g %s --format ZZZ f.vcf" % config_file2.name) @@ -311,6 +316,16 @@ output = out.getvalue().strip() self.assertEqual(output, expected_output) + def testIgnoreHelpArgs(self): + p = self.initParser() + self.add_arg('--arg1') + args, _ = self.parse_known('--arg2 --help', ignore_help_args=True) + self.assertEqual(args.arg1, None) + self.add_arg('--arg2') + args, _ = self.parse_known('--arg2 3 --help', ignore_help_args=True) + self.assertEqual(args.arg2, "3") + self.assertRaisesRegex(TypeError, "exit", self.parse_known, '--arg2 3 --help', ignore_help_args=False) + def testPositionalAndConfigVarLists(self): self.initParser() self.add_arg("a") @@ -321,7 +336,6 @@ self.assertEqual(ns.arg, ['Shell', 'someword', 'anotherword']) self.assertEqual(ns.a, "positional_value") - def testMutuallyExclusiveArgs(self): config_file = tempfile.NamedTemporaryFile(mode="w", delete=True) @@ -360,9 +374,8 @@ self.assertRegex(self.format_help(), r'usage: .* \[-h\] --genome GENOME \[-v\]\s+ \(-f1 TYPE1_CFG_FILE \|' - ' \\s*-f2 TYPE2_CFG_FILE\\)\\s+\\(-f FRMT \\| -b\\)\n\n' + - 7*r'.+\s+'+ # repeated 7 times because .+ matches atmost 1 line - 'optional arguments:\n' + ' \\s*-f2 TYPE2_CFG_FILE\\)\\s+\\(-f FRMT \\| -b\\)\n\n' + '%s:\n' ' -h, --help show this help message and exit\n' ' -f1 TYPE1_CFG_FILE, --type1-cfg-file TYPE1_CFG_FILE\n' ' -f2 TYPE2_CFG_FILE, --type2-cfg-file TYPE2_CFG_FILE\n' @@ -370,7 +383,8 @@ ' -b, --bam\\s+\\[env var: BAM_FORMAT\\]\n\n' 'group1:\n' ' --genome GENOME Path to genome file\n' - ' -v\n') + ' -v\n\n'%OPTIONAL_ARGS_STRING + + 5*r'(.+\s*)') config_file.close() def testSubParsers(self): @@ -486,6 +500,7 @@ self.assertParseArgsRaises("argument -x is required" if sys.version_info.major < 3 else "the following arguments are required: -x, --y", + args="", config_file_contents="-x 3") self.assertParseArgsRaises("invalid float value: 'abc'", args="-x 5", @@ -511,6 +526,46 @@ env_vars={"bla": "2"}) self.assertEqual(set(args), {"--bla=3", "-x", "1"}) + + def testQuotedArgumentValues(self): + self.initParser() + self.add_arg("-a") + self.add_arg("--b") + self.add_arg("-c") + self.add_arg("--d") + self.add_arg("-e") + self.add_arg("-q") + self.add_arg("--quotes") + + # sys.argv equivalent of -a="1" --b "1" -c= --d "" -e=: -q "\"'" --quotes "\"'" + ns = self.parse(args=['-a=1', '--b', '1', '-c=', '--d', '', '-e=:', + '-q', '"\'', '--quotes', '"\''], + env_vars={}, config_file_contents="") + + self.assertEqual(ns.a, "1") + self.assertEqual(ns.b, "1") + self.assertEqual(ns.c, "") + self.assertEqual(ns.d, "") + self.assertEqual(ns.e, ":") + self.assertEqual(ns.q, '"\'') + self.assertEqual(ns.quotes, '"\'') + + def testQuotedConfigFileValues(self): + self.initParser() + self.add_arg("--a") + self.add_arg("--b") + self.add_arg("--c") + + ns = self.parse(args="", env_vars={}, config_file_contents=""" + a="1" + b=: + c= + """) + + self.assertEqual(ns.a, "1") + self.assertEqual(ns.b, ":") + self.assertEqual(ns.c, "") + def testBooleanValuesCanBeExpressedAsNumbers(self): self.initParser() store_true_env_var_name = "STORE_TRUE" @@ -563,6 +618,7 @@ self.add_arg("-v", "--verbose", env_var="VERBOSE", action="store_true") self.assertParseArgsRaises("Unexpected value for VERBOSE: 'bla'. " "Expecting 'true', 'false', 'yes', 'no', '1' or '0'", + args="", env_vars={"VERBOSE" : "bla"}) ns = self.parse("", config_file_contents="verbose=true", @@ -703,6 +759,28 @@ self.assertEqual(ns.arg, ['Shell', 'someword', 'anotherword']) self.assertEqual(ns.a, "positional_value") + def testCounterCommandLine(self): + self.initParser() + self.add_arg("--verbose", "-v", action="count", default=0) + + ns = self.parse(args="-v -v -v", env_vars={}) + self.assertEqual(ns.verbose, 3) + + ns = self.parse(args="-vvv", env_vars={}) + self.assertEqual(ns.verbose, 3) + + def testCounterConfigFile(self): + self.initParser() + self.add_arg("--verbose", "-v", action="count", default=0) + + ns = self.parse(args="", env_vars={}, config_file_contents=""" + verbose""") + self.assertEqual(ns.verbose, 1) + + ns = self.parse(args="", env_vars={}, config_file_contents=""" + verbose=3""") + self.assertEqual(ns.verbose, 3) + class TestMisc(TestCase): # TODO test different action types with config file, env var @@ -770,7 +848,8 @@ self.add_arg('--genome', help='Path to genome file', required=True) self.assertParseArgsRaises("argument -c/--config is required" if sys.version_info.major < 3 else - "arguments are required: -c/--config",) + "arguments are required: -c/--config", + args="") temp_cfg2 = tempfile.NamedTemporaryFile(mode="w", delete=True) ns = self.parse("-c " + temp_cfg2.name) @@ -783,15 +862,17 @@ self.assertEqual(ns.genome, "hg20") self.assertRegex(self.format_help(), - 'usage: .* \\[-h\\] -c CONFIG_FILE --genome GENOME\n\n'+ - 7*r'.+\s+'+ # repeated 7 times because .+ matches atmost 1 line - 'optional arguments:\n' + 'usage: .* \\[-h\\] -c CONFIG_FILE --genome GENOME\n\n' + '%s:\n' ' -h, --help\\s+ show this help message and exit\n' ' -c CONFIG_FILE, --config CONFIG_FILE\\s+ my config file\n' - ' --genome GENOME\\s+ Path to genome file\n') + ' --genome GENOME\\s+ Path to genome file\n\n'%OPTIONAL_ARGS_STRING + + 5*r'(.+\s*)') # just run print_values() to make sure it completes and returns None - self.assertIsNone(self.parser.print_values(file=sys.stderr)) + output = StringIO() + self.assertIsNone(self.parser.print_values(file=output)) + self.assertIn("Command Line Args:", output.getvalue()) # test ignore_unknown_config_file_keys=False self.initParser(ignore_unknown_config_file_keys=False) @@ -842,21 +923,21 @@ self.assertRegex(self.format_help(), r'usage: .* \[-h\] -c CONFIG_FILE\s+' r'\[-w CONFIG_OUTPUT_PATH\]\s* --arg1\s+ARG1\s*\[--flag\]\s*' - 'Args that start with \'--\' \\(eg. --arg1\\) can also be set in a ' - r'config file\s*\(~/.myconfig or specified via -c\).\s*' - r'Config file syntax allows: key=value,\s*flag=true, stuff=\[a,b,c\] ' - r'\(for details, see syntax at https://goo.gl/R74nmi\).\s*' - r'If an arg is specified in more than\s*one place, then ' - r'commandline values\s*override config file values which override\s*' - r'defaults.\s*' - r'optional arguments:\s*' + '%s:\\s*' '-h, --help \\s* show this help message and exit\n\\s*' r'-c CONFIG_FILE, --config CONFIG_FILE\s+my config file\s*' r'-w CONFIG_OUTPUT_PATH, --write-config CONFIG_OUTPUT_PATH\s*takes\s*' r'the current command line args and writes them\s*' r'out to a config file at the given path, then exits\s*' r'--arg1 ARG1\s*Arg1 help text\s*' - r'--flag \s*Flag help text' + r'--flag \s*Flag help text\s*' + 'Args that start with \'--\' \\(eg. --arg1\\) can also be set in a ' + r'config file\s*\(~/.myconfig or specified via -c\).\s*' + r'Config file syntax allows: key=value,\s*flag=true, stuff=\[a,b,c\] ' + r'\(for details, see syntax at https://goo.gl/R74nmi\).\s*' + r'If an arg is specified in more than\s*one place, then ' + r'commandline values\s*override config file values which override\s*' + r'defaults.'%OPTIONAL_ARGS_STRING ) def test_FormatHelpProg(self): @@ -918,6 +999,7 @@ expected_config_file_contents.strip()) self.assertRaisesRegex(ValueError, "Couldn't open / for writing:", self.parse, args = command_line_args + " -w /") + cfg_f.close() def testConstructor_WriteOutConfigFileArgs2(self): # Test constructor args: @@ -962,6 +1044,7 @@ expected_config_file_contents.strip()) self.assertRaisesRegex(ValueError, "Couldn't open / for writing:", self.parse, args = command_line_args + " -w /") + cfg_f.close() def testConstructor_WriteOutConfigFileArgsLong(self): """Test config writing with long version of arg @@ -1005,6 +1088,7 @@ expected_config_file_contents.strip()) self.assertRaisesRegex(ValueError, "Couldn't open / for writing:", self.parse, args = command_line_args + " --write-config /") + cfg_f.close() def testMethodAliases(self): p = self.parser @@ -1102,6 +1186,249 @@ self.assertListEqual(parsed_obj['_list_arg1'], ['a', 'b', 'c']) self.assertListEqual(parsed_obj['_list_arg2'], [1, 2, 3]) + def testDefaultConfigFileParser_BasicValues(self): + p = configargparse.DefaultConfigFileParser() + + # test the all syntax case + config_lines = [ + {'line': 'key = value # comment # comment', 'expected': ('key', 'value', 'comment # comment')}, + {'line': 'key=value#comment ', 'expected': ('key', 'value#comment', None)}, + {'line': 'key=value', 'expected': ('key', 'value', None)}, + {'line': 'key =value', 'expected': ('key', 'value', None)}, + {'line': 'key= value', 'expected': ('key', 'value', None)}, + {'line': 'key = value', 'expected': ('key', 'value', None)}, + {'line': 'key = value', 'expected': ('key', 'value', None)}, + {'line': ' key = value ', 'expected': ('key', 'value', None)}, + {'line': 'key:value', 'expected': ('key', 'value', None)}, + {'line': 'key :value', 'expected': ('key', 'value', None)}, + {'line': 'key: value', 'expected': ('key', 'value', None)}, + {'line': 'key : value', 'expected': ('key', 'value', None)}, + {'line': 'key : value', 'expected': ('key', 'value', None)}, + {'line': ' key : value ', 'expected': ('key', 'value', None)}, + {'line': 'key value', 'expected': ('key', 'value', None)}, + {'line': 'key value', 'expected': ('key', 'value', None)}, + {'line': ' key value ', 'expected': ('key', 'value', None)}, + ] + + for test in config_lines: + parsed_obj = p.parse(StringIO(test['line'])) + parsed_obj = dict(parsed_obj) + expected = {test['expected'][0]: test['expected'][1]} + self.assertDictEqual(parsed_obj, expected, + msg="Line %r" % (test['line'])) + + def testDefaultConfigFileParser_QuotedValues(self): + p = configargparse.DefaultConfigFileParser() + + # test the all syntax case + config_lines = [ + {'line': 'key="value"', 'expected': ('key', 'value', None)}, + {'line': 'key = "value"', 'expected': ('key', 'value', None)}, + {'line': ' key = "value" ', 'expected': ('key', 'value', None)}, + {'line': 'key=" value "', 'expected': ('key', ' value ', None)}, + {'line': 'key = " value "', 'expected': ('key', ' value ', None)}, + {'line': ' key = " value " ', 'expected': ('key', ' value ', None)}, + {'line': "key='value'", 'expected': ('key', 'value', None)}, + {'line': "key = 'value'", 'expected': ('key', 'value', None)}, + {'line': " key = 'value' ", 'expected': ('key', 'value', None)}, + {'line': "key=' value '", 'expected': ('key', ' value ', None)}, + {'line': "key = ' value '", 'expected': ('key', ' value ', None)}, + {'line': " key = ' value ' ", 'expected': ('key', ' value ', None)}, + {'line': 'key="', 'expected': ('key', '"', None)}, + {'line': 'key = "', 'expected': ('key', '"', None)}, + {'line': ' key = " ', 'expected': ('key', '"', None)}, + {'line': 'key = \'"value"\'', 'expected': ('key', '"value"', None)}, + {'line': 'key = "\'value\'"', 'expected': ('key', "'value'", None)}, + {'line': 'key = ""value""', 'expected': ('key', '"value"', None)}, + {'line': 'key = \'\'value\'\'', 'expected': ('key', "'value'", None)}, + {'line': 'key="value', 'expected': ('key', '"value', None)}, + {'line': 'key = "value', 'expected': ('key', '"value', None)}, + {'line': ' key = "value ', 'expected': ('key', '"value', None)}, + {'line': 'key=value"', 'expected': ('key', 'value"', None)}, + {'line': 'key = value"', 'expected': ('key', 'value"', None)}, + {'line': ' key = value " ', 'expected': ('key', 'value "', None)}, + {'line': "key='value", 'expected': ('key', "'value", None)}, + {'line': "key = 'value", 'expected': ('key', "'value", None)}, + {'line': " key = 'value ", 'expected': ('key', "'value", None)}, + {'line': "key=value'", 'expected': ('key', "value'", None)}, + {'line': "key = value'", 'expected': ('key', "value'", None)}, + {'line': " key = value ' ", 'expected': ('key', "value '", None)}, + ] + + for test in config_lines: + parsed_obj = p.parse(StringIO(test['line'])) + parsed_obj = dict(parsed_obj) + expected = {test['expected'][0]: test['expected'][1]} + self.assertDictEqual(parsed_obj, expected, + msg="Line %r" % (test['line'])) + + def testDefaultConfigFileParser_BlankValues(self): + p = configargparse.DefaultConfigFileParser() + + # test the all syntax case + config_lines = [ + {'line': 'key=', 'expected': ('key', '', None)}, + {'line': 'key =', 'expected': ('key', '', None)}, + {'line': 'key= ', 'expected': ('key', '', None)}, + {'line': 'key = ', 'expected': ('key', '', None)}, + {'line': 'key = ', 'expected': ('key', '', None)}, + {'line': ' key = ', 'expected': ('key', '', None)}, + {'line': 'key:', 'expected': ('key', '', None)}, + {'line': 'key :', 'expected': ('key', '', None)}, + {'line': 'key: ', 'expected': ('key', '', None)}, + {'line': 'key : ', 'expected': ('key', '', None)}, + {'line': 'key : ', 'expected': ('key', '', None)}, + {'line': ' key : ', 'expected': ('key', '', None)}, + ] + + for test in config_lines: + parsed_obj = p.parse(StringIO(test['line'])) + parsed_obj = dict(parsed_obj) + expected = {test['expected'][0]: test['expected'][1]} + self.assertDictEqual(parsed_obj, expected, + msg="Line %r" % (test['line'])) + + def testDefaultConfigFileParser_UnspecifiedValues(self): + p = configargparse.DefaultConfigFileParser() + + # test the all syntax case + config_lines = [ + {'line': 'key ', 'expected': ('key', 'true', None)}, + {'line': 'key', 'expected': ('key', 'true', None)}, + {'line': 'key ', 'expected': ('key', 'true', None)}, + {'line': ' key ', 'expected': ('key', 'true', None)}, + ] + + for test in config_lines: + parsed_obj = p.parse(StringIO(test['line'])) + parsed_obj = dict(parsed_obj) + expected = {test['expected'][0]: test['expected'][1]} + self.assertDictEqual(parsed_obj, expected, + msg="Line %r" % (test['line'])) + + def testDefaultConfigFileParser_ColonEqualSignValue(self): + p = configargparse.DefaultConfigFileParser() + + # test the all syntax case + config_lines = [ + {'line': 'key=:', 'expected': ('key', ':', None)}, + {'line': 'key =:', 'expected': ('key', ':', None)}, + {'line': 'key= :', 'expected': ('key', ':', None)}, + {'line': 'key = :', 'expected': ('key', ':', None)}, + {'line': 'key = :', 'expected': ('key', ':', None)}, + {'line': ' key = : ', 'expected': ('key', ':', None)}, + {'line': 'key:=', 'expected': ('key', '=', None)}, + {'line': 'key :=', 'expected': ('key', '=', None)}, + {'line': 'key: =', 'expected': ('key', '=', None)}, + {'line': 'key : =', 'expected': ('key', '=', None)}, + {'line': 'key : =', 'expected': ('key', '=', None)}, + {'line': ' key : = ', 'expected': ('key', '=', None)}, + {'line': 'key==', 'expected': ('key', '=', None)}, + {'line': 'key ==', 'expected': ('key', '=', None)}, + {'line': 'key= =', 'expected': ('key', '=', None)}, + {'line': 'key = =', 'expected': ('key', '=', None)}, + {'line': 'key = =', 'expected': ('key', '=', None)}, + {'line': ' key = = ', 'expected': ('key', '=', None)}, + {'line': 'key::', 'expected': ('key', ':', None)}, + {'line': 'key ::', 'expected': ('key', ':', None)}, + {'line': 'key: :', 'expected': ('key', ':', None)}, + {'line': 'key : :', 'expected': ('key', ':', None)}, + {'line': 'key : :', 'expected': ('key', ':', None)}, + {'line': ' key : : ', 'expected': ('key', ':', None)}, + ] + + for test in config_lines: + parsed_obj = p.parse(StringIO(test['line'])) + parsed_obj = dict(parsed_obj) + expected = {test['expected'][0]: test['expected'][1]} + self.assertDictEqual(parsed_obj, expected, + msg="Line %r" % (test['line'])) + + def testDefaultConfigFileParser_ValuesWithComments(self): + p = configargparse.DefaultConfigFileParser() + + # test the all syntax case + config_lines = [ + {'line': 'key=value#comment ', 'expected': ('key', 'value#comment', None)}, + {'line': 'key=value #comment', 'expected': ('key', 'value', 'comment')}, + {'line': ' key = value # comment', 'expected': ('key', 'value', 'comment')}, + {'line': 'key:value#comment', 'expected': ('key', 'value#comment', None)}, + {'line': 'key:value #comment', 'expected': ('key', 'value', 'comment')}, + {'line': ' key : value # comment', 'expected': ('key', 'value', 'comment')}, + {'line': 'key=value;comment ', 'expected': ('key', 'value;comment', None)}, + {'line': 'key=value ;comment', 'expected': ('key', 'value', 'comment')}, + {'line': ' key = value ; comment', 'expected': ('key', 'value', 'comment')}, + {'line': 'key:value;comment', 'expected': ('key', 'value;comment', None)}, + {'line': 'key:value ;comment', 'expected': ('key', 'value', 'comment')}, + {'line': ' key : value ; comment', 'expected': ('key', 'value', 'comment')}, + {'line': 'key = value # comment # comment', 'expected': ('key', 'value', 'comment # comment')}, + {'line': 'key = "value # comment" # comment', 'expected': ('key', 'value # comment', 'comment')}, + {'line': 'key = "#" ; comment', 'expected': ('key', '#', 'comment')}, + {'line': 'key = ";" # comment', 'expected': ('key', ';', 'comment')}, + ] + + for test in config_lines: + parsed_obj = p.parse(StringIO(test['line'])) + parsed_obj = dict(parsed_obj) + expected = {test['expected'][0]: test['expected'][1]} + self.assertDictEqual(parsed_obj, expected, + msg="Line %r" % (test['line'])) + + def testDefaultConfigFileParser_NegativeValues(self): + p = configargparse.DefaultConfigFileParser() + + # test the all syntax case + config_lines = [ + {'line': 'key = -10', 'expected': ('key', '-10', None)}, + {'line': 'key : -10', 'expected': ('key', '-10', None)}, + {'line': 'key -10', 'expected': ('key', '-10', None)}, + {'line': 'key = "-10"', 'expected': ('key', '-10', None)}, + {'line': "key = '-10'", 'expected': ('key', '-10', None)}, + {'line': 'key=-10', 'expected': ('key', '-10', None)}, + ] + + for test in config_lines: + parsed_obj = p.parse(StringIO(test['line'])) + parsed_obj = dict(parsed_obj) + expected = {test['expected'][0]: test['expected'][1]} + self.assertDictEqual(parsed_obj, expected, + msg="Line %r" % (test['line'])) + + def testDefaultConfigFileParser_KeySyntax(self): + p = configargparse.DefaultConfigFileParser() + + # test the all syntax case + config_lines = [ + {'line': 'key_underscore = value', 'expected': ('key_underscore', 'value', None)}, + {'line': 'key_underscore=', 'expected': ('key_underscore', '', None)}, + {'line': 'key_underscore', 'expected': ('key_underscore', 'true', None)}, + {'line': '_key_underscore = value', 'expected': ('_key_underscore', 'value', None)}, + {'line': '_key_underscore=', 'expected': ('_key_underscore', '', None)}, + {'line': '_key_underscore', 'expected': ('_key_underscore', 'true', None)}, + {'line': 'key_underscore_ = value', 'expected': ('key_underscore_', 'value', None)}, + {'line': 'key_underscore_=', 'expected': ('key_underscore_', '', None)}, + {'line': 'key_underscore_', 'expected': ('key_underscore_', 'true', None)}, + {'line': 'key-dash = value', 'expected': ('key-dash', 'value', None)}, + {'line': 'key-dash=', 'expected': ('key-dash', '', None)}, + {'line': 'key-dash', 'expected': ('key-dash', 'true', None)}, + {'line': 'key@word = value', 'expected': ('key@word', 'value', None)}, + {'line': 'key@word=', 'expected': ('key@word', '', None)}, + {'line': 'key@word', 'expected': ('key@word', 'true', None)}, + {'line': 'key$word = value', 'expected': ('key$word', 'value', None)}, + {'line': 'key$word=', 'expected': ('key$word', '', None)}, + {'line': 'key$word', 'expected': ('key$word', 'true', None)}, + {'line': 'key.word = value', 'expected': ('key.word', 'value', None)}, + {'line': 'key.word=', 'expected': ('key.word', '', None)}, + {'line': 'key.word', 'expected': ('key.word', 'true', None)}, + ] + + for test in config_lines: + parsed_obj = p.parse(StringIO(test['line'])) + parsed_obj = dict(parsed_obj) + expected = {test['expected'][0]: test['expected'][1]} + self.assertDictEqual(parsed_obj, expected, + msg="Line %r" % (test['line'])) + def testYAMLConfigFileParser_Basic(self): try: import yaml @@ -1171,7 +1498,14 @@ else: test_argparse_source_code = inspect.getsource(test.test_argparse) test_argparse_source_code = test_argparse_source_code.replace( - 'argparse.ArgumentParser', 'configargparse.ArgumentParser') + 'argparse.ArgumentParser', 'configargparse.ArgumentParser').replace( + 'TestHelpFormattingMetaclass', '_TestHelpFormattingMetaclass').replace( + 'test_main', '_test_main') + + # pytest tries to collect tests from TestHelpFormattingMetaclass, and + # test_main, and raises a warning when it finds it's not a test class + # nor test function. Renaming TestHelpFormattingMetaclass and test_main + # prevents pytest from trying. # run or debug a subset of the argparse tests #test_argparse_source_code = test_argparse_source_code.replace( Binary files old/ConfigArgParse-1.3/tests/test_configargparse.pyc and new/ConfigArgParse-1.5.1/tests/test_configargparse.pyc differ