From 17187e6cb05d8fecdd3dce8b39fa0ed79217e76c Mon Sep 17 00:00:00 2001 From: Travis AutoPEP8 Fixes Date: Wed, 9 Oct 2019 13:46:27 +0200 Subject: [PATCH 1/4] Fix lookup of non existent keys and add option to allow nested lookup with multiple results --- delira/utils/config.py | 19 ++++++++++++++----- tests/utils/test_config.py | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/delira/utils/config.py b/delira/utils/config.py index c5b35a95..cfcdc79a 100644 --- a/delira/utils/config.py +++ b/delira/utils/config.py @@ -134,7 +134,11 @@ def _traverse_keys(self, keys, create=False): current_level = self for k in keys: if k not in current_level: - current_level[k] = self._create_internal_dict() + if create: + current_level[k] = self._create_internal_dict() + else: + raise KeyError( + "{} was not found in internal dict.".format(k)) # traverse to needed dict current_level = current_level[k] return current_level @@ -546,12 +550,12 @@ def __contains__(self, key): """ contain = True try: - self.nested_get(key) + self.nested_get(key, allow_multiple=True) except KeyError: contain = False return contain - def nested_get(self, key, *args, **kwargs): + def nested_get(self, key, *args, allow_multiple=False, **kwargs): """ Returns all occurances of :param`key` in :param`self` and subdicts @@ -561,13 +565,15 @@ def nested_get(self, key, *args, **kwargs): the key to search for *args : positional arguments to provide default value + allow_multiple: bool + allow multiple results **kwargs : keyword arguments to provide default value Raises ------ KeyError - Multiple Values are found for key + Multiple Values are found for key and allow_multiple is False (unclear which value should be returned) OR No Value was found for key and no default value was given @@ -583,7 +589,10 @@ def nested_get(self, key, *args, **kwargs): return self[key] results = nested_lookup(key, self) if len(results) > 1: - raise KeyError("Multiple Values found for key %s" % key) + if allow_multiple: + return results + else: + raise KeyError("Multiple Values found for key %s" % key) elif len(results) == 0: if "default" in kwargs: return kwargs["default"] diff --git a/tests/utils/test_config.py b/tests/utils/test_config.py index 13e6891c..a8a115f6 100644 --- a/tests/utils/test_config.py +++ b/tests/utils/test_config.py @@ -94,6 +94,18 @@ def test_config_access(self): with self.assertWarns(RuntimeWarning, msg=warning_msg): cf[5] = 10 + @unittest.skipUnless( + check_for_no_backend(), + "Test should only be executed if no backend is specified") + def test_config_access_with_non_existing_keys(self): + cf = self.config_cls(self.example_dict) + + with self.assertRaises(KeyError): + cf["unknown_key"] + + with self.assertRaises(KeyError): + cf["shallowStr.unknown_key"] + @unittest.skipUnless( check_for_no_backend(), "Test should only be executed if no backend is specified") @@ -228,6 +240,14 @@ def test_nested_lookpup(self): self.assertIsNone(cf.nested_get("nonExistingKey", None)) self.assertIsNone(cf.nested_get("nonExistingKey", default=None)) + cf["nested_duplicate.deep"] = "duplicate" + with self.assertRaises(KeyError): + cf.nested_get("deep") + + multiple_val = cf.nested_get("deep", allow_multiple=True) + self.assertEqual(multiple_val, [{"deepStr": "b", "deepNum": 2}, + "duplicate"]) + class DeliraConfigTest(LookupConfigTest): def setUp(self): From 9dc65aa4e49e20edb32679755aa899a086d09570 Mon Sep 17 00:00:00 2001 From: Travis AutoPEP8 Fixes Date: Wed, 9 Oct 2019 14:04:43 +0200 Subject: [PATCH 2/4] adjust docstring --- delira/utils/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/delira/utils/config.py b/delira/utils/config.py index cfcdc79a..246c68be 100644 --- a/delira/utils/config.py +++ b/delira/utils/config.py @@ -555,7 +555,7 @@ def __contains__(self, key): contain = False return contain - def nested_get(self, key, *args, allow_multiple=False, **kwargs): + def nested_get(self, key, *args, allow_multiple=False, **kwargs): """ Returns all occurances of :param`key` in :param`self` and subdicts @@ -573,8 +573,8 @@ def nested_get(self, key, *args, allow_multiple=False, **kwargs): Raises ------ KeyError - Multiple Values are found for key and allow_multiple is False - (unclear which value should be returned) + Multiple Values are found for key and :param`allow_multiple` is + False (unclear which value should be returned) OR No Value was found for key and no default value was given From 23efa5be7f0b51061bd16d1d7e0836f0fd6eb838 Mon Sep 17 00:00:00 2001 From: Travis AutoPEP8 Fixes Date: Wed, 9 Oct 2019 12:21:23 +0000 Subject: [PATCH 3/4] PEP-8 Auto-Fix --- delira/utils/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/delira/utils/config.py b/delira/utils/config.py index 246c68be..0b92945a 100644 --- a/delira/utils/config.py +++ b/delira/utils/config.py @@ -573,7 +573,7 @@ def nested_get(self, key, *args, allow_multiple=False, **kwargs): Raises ------ KeyError - Multiple Values are found for key and :param`allow_multiple` is + Multiple Values are found for key and :param`allow_multiple` is False (unclear which value should be returned) OR No Value was found for key and no default value was given From 4ae8c8064e2e3a3ecf8ce0a05eb85622b5d8d647 Mon Sep 17 00:00:00 2001 From: Travis AutoPEP8 Fixes Date: Wed, 9 Oct 2019 18:41:07 +0200 Subject: [PATCH 4/4] fix param typo in docstrings --- delira/utils/config.py | 44 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/delira/utils/config.py b/delira/utils/config.py index 0b92945a..ed692849 100644 --- a/delira/utils/config.py +++ b/delira/utils/config.py @@ -17,7 +17,7 @@ def warning_wrapper(config, key, *args, **kwargs): Parameters ---------- config: :class:`Config` - decorated function receive :param`self` as first argument + decorated function receive :param:`self` as first argument key : immutable type key which is checked @@ -253,7 +253,7 @@ def update(self, update_dict, deepcopy=False, overwrite=False): update_dict : dictlike values which should be added to config deepcopy : bool, optional - copies values from :param`update_dict`, by default False + copies values from :param:`update_dict`, by default False overwrite : bool, optional overwrite existing values inside config, by default False @@ -278,7 +278,7 @@ def _update(self, key, item, deepcopy=False, overwrite=False): item : Any item which should be assigned deepcopy : bool, optional - copies :param`item`, by default False + copies :param:`item`, by default False overwrite : bool, optional overwrite existing values inside config, by default False """ @@ -326,9 +326,9 @@ def dump(self, path, formatter=yaml.dump, encoder_cls=Encoder, **kwargs): defines the format how the config is saved, by default yaml.dump encoder_cls : :class:`Encoder`, optional transforms config to a format which can be formatted by the - :param`formatter`, by default Encoder + :param:`formatter`, by default Encoder kwargs: - additional keyword arguments passed to :param`formatter` + additional keyword arguments passed to :param:`formatter` """ self._timestamp = now() encoded_self = encoder_cls().encode(self) @@ -346,9 +346,9 @@ def dumps(self, formatter=yaml.dump, encoder_cls=Encoder, **kwargs): defines the format how the config is saved, by default yaml.dump encoder_cls : :class:`Encoder`, optional transforms config to a format which can be formatted by the - :param`formatter`, by default Encoder + :param:`formatter`, by default Encoder kwargs: - additional keyword arguments passed to :param`formatter` + additional keyword arguments passed to :param:`formatter` """ self._timestamp = now() encoded_self = encoder_cls().encode(self) @@ -366,9 +366,9 @@ def load(self, path, formatter=yaml.load, decoder_cls=Decoder, **kwargs): defines the format how the config is saved, by default yaml.dump decoder_cls : :class:`Encoder`, optional transforms config to a format which can be formatted by the - :param`formatter`, by default Encoder + :param:`formatter`, by default Encoder kwargs: - additional keyword arguments passed to :param`formatter` + additional keyword arguments passed to :param:`formatter` """ with open(path, "r") as f: decoded_format = formatter(f, **kwargs) @@ -387,9 +387,9 @@ def loads(self, data, formatter=yaml.load, decoder_cls=Decoder, **kwargs): defines the format how the config is saved, by default yaml.dump decoder_cls : :class:`Encoder`, optional transforms config to a format which can be formatted by the - :param`formatter`, by default Encoder + :param:`formatter`, by default Encoder kwargs: - additional keyword arguments passed to :param`formatter` + additional keyword arguments passed to :param:`formatter` """ decoded_format = formatter(data, **kwargs) decoded_format = decoder_cls().decode(decoded_format) @@ -415,7 +415,7 @@ def create_from_dict(cls, value, deepcopy=False): Raises ------ TypeError - raised if :param`value` is not a dict (or a subclass of dict) + raised if :param:`value` is not a dict (or a subclass of dict) """ if not isinstance(value, dict): raise TypeError("Value must be an instance of dict but type {} " @@ -471,9 +471,9 @@ def create_from_file(cls, path, formatter=yaml.load, decoder_cls=Decoder, defines the format how the config is saved, by default yaml.dump decoder_cls : :class:`Encoder`, optional trasforms config to a format which can be formatted by the - :param`formatter`, by default Encoder + :param:`formatter`, by default Encoder kwargs: - additional keyword arguments passed to :param`formatter` + additional keyword arguments passed to :param:`formatter` Returns ------- @@ -499,9 +499,9 @@ def create_from_str(cls, data, formatter=yaml.load, decoder_cls=Decoder, defines the format how the config is saved, by default yaml.dump decoder_cls : :class:`Encoder`, optional trasforms config to a format which can be formatted by the - :param`formatter`, by default Encoder + :param:`formatter`, by default Encoder kwargs: - additional keyword arguments passed to :param`formatter` + additional keyword arguments passed to :param:`formatter` Returns ------- @@ -557,7 +557,7 @@ def __contains__(self, key): def nested_get(self, key, *args, allow_multiple=False, **kwargs): """ - Returns all occurances of :param`key` in :param`self` and subdicts + Returns all occurances of :param:`key` in :param:`self` and subdicts Parameters ---------- @@ -573,7 +573,7 @@ def nested_get(self, key, *args, allow_multiple=False, **kwargs): Raises ------ KeyError - Multiple Values are found for key and :param`allow_multiple` is + Multiple Values are found for key and :param:`allow_multiple` is False (unclear which value should be returned) OR No Value was found for key and no default value was given @@ -698,7 +698,7 @@ def variable_params(self, new_params: dict): Raises ------ TypeError - raised if :param`new_params` is not a dict (or a subclass of dict) + raised if :param:`new_params` is not a dict (or a subclass of dict) """ if not isinstance(new_params, dict): raise TypeError("new_params must be an instance of dict but " @@ -736,7 +736,7 @@ def fixed_params(self, new_params: dict): Raises ------ TypeError - raised if :param`new_params` is not a dict (or a subclass of dict) + raised if :param:`new_params` is not a dict (or a subclass of dict) """ if not isinstance(new_params, dict): raise TypeError("new_params must be an instance of dict but " @@ -773,7 +773,7 @@ def model_params(self, new_params: dict): Raises ------ TypeError - raised if :param`new_params` is not a dict (or a subclass of dict) + raised if :param:`new_params` is not a dict (or a subclass of dict) """ if not isinstance(new_params, dict): raise TypeError("new_params must be an instance of dict but " @@ -810,7 +810,7 @@ def training_params(self, new_params: dict): Raises ------ TypeError - raised if :param`new_params` is not a dict (or a subclass of dict) + raised if :param:`new_params` is not a dict (or a subclass of dict) """ if not isinstance(new_params, dict): raise TypeError("new_params must be an instance of dict but "