From 68e0f5da382d86cb2980eb7bd4925c21ceae589b Mon Sep 17 00:00:00 2001 From: Ziad Sawalha Date: Tue, 25 Aug 2015 18:37:03 +0200 Subject: [PATCH 1/4] fix(config): handle deprecation warning SafeConfigParser is deprecated as of Python 3.2. --- simpl/config.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/simpl/config.py b/simpl/config.py index d89f3058f..6c59b2e7c 100644 --- a/simpl/config.py +++ b/simpl/config.py @@ -542,7 +542,13 @@ def parse_ini(self, paths=None, namespace=None, permissive=False): """ namespace = namespace or self.prog results = {} - self.ini_config = configparser.SafeConfigParser() + # DeprecationWarning: SafeConfigParser has been renamed to ConfigParser + # in Python 3.2. This alias will be removed in future versions. Use + # ConfigParser directly instead. + if sys.version_info < (3, 2): + self.ini_config = configparser.SafeConfigParser() + else: + self.ini_config = configparser.ConfigParser() parser_errors = (configparser.NoOptionError, configparser.NoSectionError) From b759b236bcf7626872491b22b55243ca4bf0aadb Mon Sep 17 00:00:00 2001 From: Ziad Sawalha Date: Tue, 25 Aug 2015 18:42:05 +0200 Subject: [PATCH 2/4] feat(config): support strict argument handling Passing in an invalid argument (or mistyping one) resulted in arguments being ignored. Setting `strict` to True now will raise an error if an invalid argument is supplied. This is backwards compatible (so the default behavior has not been changed, but a workaround has been supplied). --- simpl/config.py | 29 +++++++++++++++++++---------- tests/test_config.py | 9 ++++++++- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/simpl/config.py b/simpl/config.py index 6c59b2e7c..211e86572 100644 --- a/simpl/config.py +++ b/simpl/config.py @@ -407,7 +407,8 @@ def _metaconfigure(self, argv=None): options=self._metaconf._options, permissive=False, **override) self._parser_kwargs.setdefault('parents', []) self._parser_kwargs['parents'].append(metaparser) - self._metaconf.parse(argv=argv) + self._metaconf._values = self._metaconf.load_options( + argv=argv, permissive=True) self._metaconf.provision(self) @staticmethod @@ -489,8 +490,8 @@ def parse_cli(self, argv=None, permissive=False): valid, pass_thru = self.parse_passthru_args(argv[1:]) parsed, extras = parser.parse_known_args(valid) if extras and not permissive: - raise AttributeError("Unrecognized arguments: %s" % - ' ,'.join(extras)) + self.build_parser(options, permissive=permissive) + parser.parse_args(argv[1:]) self.pass_thru_args = pass_thru + extras else: # maybe reset pass_thru_args on subsequent calls @@ -609,10 +610,10 @@ def parse_keyring(self, namespace=None): results[option.dest] = option.type(secret) return results - def load_options(self, argv=None, keyring_namespace=None): + def load_options(self, argv=None, keyring_namespace=None, permissive=True): """Find settings from all sources.""" defaults = self.get_defaults() - args = self.parse_cli(argv=argv, permissive=True) + args = self.parse_cli(argv=argv, permissive=permissive) env = self.parse_env() secrets = self.parse_keyring(keyring_namespace) ini = self.parse_ini() @@ -624,10 +625,16 @@ def load_options(self, argv=None, keyring_namespace=None): results.update(args) return results - def parse(self, argv=None, keyring_namespace=None): - """Find settings from all sources.""" + def parse(self, argv=None, keyring_namespace=None, strict=False): + """Find settings from all sources. + + :returns: dict of parsed option name and values + :raises: SystemExit if invalid arguments supplied along with stdout + message (same as argparser). + """ results = self.load_options(argv=argv, - keyring_namespace=keyring_namespace) + keyring_namespace=keyring_namespace, + permissive=not strict) # Run validation raise_for_group = {} for option in self._options: @@ -821,9 +828,11 @@ def parse_key_format(value): SINGLETON = None -def init(options=None, ini_paths=None, argv=None): +def init(options=None, ini_paths=None, argv=None, strict=False): """Initialize singleton config and read/parse configuration. + :keyword bool strict: when true, will error out on invalid arguments + (default behavior is to ignore them) :returns: the loaded configuration. """ global SINGLETON @@ -831,7 +840,7 @@ def init(options=None, ini_paths=None, argv=None): options=options, ini_paths=ini_paths, argv=argv) - SINGLETON.parse(argv) + SINGLETON.parse(argv, strict=strict) return SINGLETON diff --git a/tests/test_config.py b/tests/test_config.py index d977c6172..b86ccd57a 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -21,7 +21,6 @@ import copy import errno import os -import string import sys import tempfile import textwrap @@ -35,6 +34,7 @@ class TestParsers(unittest.TestCase): + def test_comma_separated_strings(self): expected = ['1', '2', '3'] result = config.comma_separated_strings("1,2,3") @@ -88,6 +88,13 @@ def test_items(self): self.assertEqual(cfg['one'], 1) self.assertIsNone(cfg['none']) + def test_strict(self): + cfg = config.Config(options=[ + config.Option('--one', default=1), + ]) + with self.assertRaises(SystemExit): + cfg.parse(['prog', '--foo'], strict=True) + @mock.patch.dict('os.environ', {'TEST_TWO': '2'}) def test_required(self): self.assertEqual(os.environ['TEST_TWO'], '2') From c618b39c2fc9d57bdcd5654cf55f7cb3c910b230 Mon Sep 17 00:00:00 2001 From: Ziad Sawalha Date: Thu, 27 Aug 2015 11:36:52 +0200 Subject: [PATCH 3/4] fix(config): increase test coverage - exclude test code --- simpl/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simpl/config.py b/simpl/config.py index 211e86572..24950ba94 100644 --- a/simpl/config.py +++ b/simpl/config.py @@ -870,7 +870,7 @@ def current(): return SINGLETON -def main(): +def main(): # pragma: no cover """Simple tests.""" opts = [ Option('--foo'), @@ -891,5 +891,5 @@ def main(): myconf.parse() print(myconf) -if __name__ == '__main__': +if __name__ == '__main__': # pragma: no cover main() From 1955d6185963a80c9265e41b3e37aef2cd6b2d41 Mon Sep 17 00:00:00 2001 From: Ziad Sawalha Date: Thu, 27 Aug 2015 11:37:27 +0200 Subject: [PATCH 4/4] fix(config): remove unnecessary try/catch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pop with a default won’t raise a KeyError --- simpl/config.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/simpl/config.py b/simpl/config.py index 24950ba94..c09d4d40b 100644 --- a/simpl/config.py +++ b/simpl/config.py @@ -259,10 +259,7 @@ def add_argument(self, parser, permissive=False, **override_kwargs): kwargs['help'] = "%s (or set %s)" % (kwargs['help'], kwargs['env']) if permissive: - try: - required = kwargs.pop('required', None) - except KeyError: - pass + required = kwargs.pop('required', None) try: del kwargs['env'] except KeyError: