diff --git a/CHANGELOG.md b/CHANGELOG.md index f553ed0..df666fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,27 +23,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Replaced deprecated use of `ruamel.yaml` methods in the YAML parser (via [#28](https://github.com/eugenetriguba/config-file/pull/28), thanks [@JaWeRn](https://github.com/JaWeRn)). +### Removed + +- Depreciated `stringify()` method on `ConfigFile`. Use `str()` + on the `ConfigFile` instead. + ## 0.12.0 - 2020-10-03 ### Added - - Python's built-in `in` keyword now works with a ConfigFile. +- Python's built-in `in` keyword now works with a ConfigFile. + + Example: - Example: - ```python - config = ConfigFile('./pyproject.toml') + ```python + config = ConfigFile('./pyproject.toml') - 'tool.poetry' in config - >>> True - ``` + 'tool.poetry' in config + >>> True + ``` ### Changed - - Depreciated `stringify()` in favor of just using the built-in `str()`. +- Depreciated `stringify()` in favor of just using the built-in `str()`. ### Fixed - - Addresses issue #25 (INI parser isn't converting back to string). +- Addresses issue #25 (INI parser isn't converting back to string). ## 0.11.0 - 2020-08-07 @@ -232,4 +238,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.1.0 - 2020-01-04 - - Initial Release \ No newline at end of file +- Initial Release diff --git a/README.md b/README.md index 49faf65..f480b2d 100644 --- a/README.md +++ b/README.md @@ -299,11 +299,6 @@ built-in `str()` method on the ConfigFile. This will always show us our latest c str(config) >>> '[section]\nnum_key = 5\nstr_key = blah\nbool_key = true\nlist_key = [1, 2]\n\n[second_section]\ndict_key = { "another_num": 5 }\n\n' -# Depreciated but also works. -config.stringify() ->>> '[section]\nnum_key = 5\nstr_key = blah\nbool_key = true\nlist_key = [1, 2]\n\n[second_section]\ndict_key = { "another_num": 5 }\n\n' -``` - ### Using `restore_original()` diff --git a/config_file/config_file.py b/config_file/config_file.py index f4979bb..079c903 100644 --- a/config_file/config_file.py +++ b/config_file/config_file.py @@ -47,10 +47,10 @@ def __contains__(self, key: str) -> bool: return self.has(key) def __str__(self) -> str: - return self.stringify() + return str(self.__parser) def __repr__(self) -> str: - return f"{self.path}\n\n{self.stringify()}" + return f"{self.path}\n\n{str(self.__parser)}" def get( self, @@ -85,7 +85,7 @@ def get( key_value = self.__parser.get(key) except KeyError as error: if isinstance(default, Default) and default.value is None: - raise KeyError(error) + raise error else: key_value = default @@ -125,18 +125,6 @@ def delete(self, key: str) -> None: """ self.__parser.delete(key) - def stringify(self) -> str: - """Retrieves file contents as a string. - - Returns: - The internal representation of the file - that has been read in converted to a string. - - Depreciated: - Use str() on the ConfigFile object instead. - """ - return self.__parser.stringify() - def has(self, key: str, wild: bool = False) -> bool: """ Check if a section, sub-section, or key exists. @@ -198,4 +186,4 @@ def save(self) -> None: object's constructor. """ with open(str(self.__path), "w") as config_file: - config_file.write(self.stringify()) + config_file.write(str(self)) diff --git a/config_file/nested_lookup.py b/config_file/nested_lookup.py index fbbf8f2..c67109c 100644 --- a/config_file/nested_lookup.py +++ b/config_file/nested_lookup.py @@ -149,7 +149,6 @@ def get_occurrence_of_value(dictionary, value): def _recursion(dictionary, item, keyword, occurrence, with_values=False): - global values_list if item == "key": @@ -380,7 +379,6 @@ def _nested_alter( in_place, key_len, ): - # return data if no callback_function is provided if callback_function is None: warnings.warn("Please provide a callback_function to nested_alter().") diff --git a/config_file/parsers/abstract_parser.py b/config_file/parsers/abstract_parser.py index 460b238..40983b2 100644 --- a/config_file/parsers/abstract_parser.py +++ b/config_file/parsers/abstract_parser.py @@ -99,8 +99,9 @@ def delete(self, key: str) -> None: raise NotImplementedError @abstractmethod - def stringify(self) -> str: - """Stringify the working file. + def __str__(self) -> str: + """ + Stringify the working file. Since the ConfigFile does not write out to disk after every operation, this method is what is diff --git a/config_file/parsers/base_parser.py b/config_file/parsers/base_parser.py index ea94164..b1ef1c2 100644 --- a/config_file/parsers/base_parser.py +++ b/config_file/parsers/base_parser.py @@ -9,7 +9,8 @@ class BaseParser(AbstractParser): def __init__(self, file_contents: str): - """The BaseParser implements the AbstractParser for us, as long as the + """ + The BaseParser implements the AbstractParser for us, as long as the subclasses implement `loads`, `dumps`, and `decode_error` methods. Internally, every parser uses the BaseParser so it does not @@ -28,14 +29,15 @@ def __init__(self, file_contents: str): @abstractmethod def loads(self, contents: str) -> dict: - """Transforms the file contents into a dictionary. + """ + Transforms the file contents into a dictionary. Args: contents: The file contents to transform. Raises: self.decode_error: If there is a decoding error - while attempting to parse the string. + while attempting to parse the string. Returns: The contents of a particular file as a dictionary @@ -45,18 +47,18 @@ def loads(self, contents: str) -> dict: @abstractmethod def dumps(self, loaded_contents: dict) -> str: - """Transform the parsed contents back into a string. + """ + Transform the parsed contents back into a string. Args: loaded_contents: The file contents from a self.loads call. - - It doesn't necessary need to be from a loads call, could just - be a valid dictionary for the given format, but that is the - typical usecase. + It doesn't necessary need to be from a loads call, could just + be a valid dictionary for the given format, but that is the + typical usecase. Raises: self.decode_error: If there is a decoding error - while attempting to transform the dict back into a string. + while attempting to transform the dict back into a string. Returns: The dictionary parsed back into a string. @@ -66,7 +68,8 @@ def dumps(self, loaded_contents: dict) -> str: @property @abstractmethod def decode_error(self) -> Type[Exception]: - """The decoding error raised by the subclass on loads/dumps. + """ + The decoding error raised by the subclass on loads/dumps. Returns: The decoding error @@ -77,13 +80,17 @@ def decode_error(self) -> Type[Exception]: def parsed_content(self) -> dict: return self.__parsed_content + def __str__(self) -> str: + return self.dumps(self.__parsed_content) + def parse_file_contents(self) -> dict: - """Parse the file contents by running the `loads` method on the module. + """ + Parse the file contents by running the `loads` method on the module. Args: module: The module to use to parse the file. decode_error: The error that is raised from the module if the file - cannot be decoded. + cannot be decoded. Raises: ParsingError: If the decode_error is raised while running `loads`. @@ -97,7 +104,8 @@ def parse_file_contents(self) -> dict: raise ParsingError(error) def reset_internal_contents(self, file_contents: str) -> None: - """Reset the file contents and parsed contents of the parser. + """ + Reset the file contents and parsed contents of the parser. Args: file_contents: The new file contents. @@ -109,27 +117,41 @@ def reset_internal_contents(self, file_contents: str) -> None: self.__parsed_content = self.parse_file_contents() def get(self, search_key: str) -> Any: - key_causing_error = None + """ + Retrieve a key from the parsed content. + + Args: + search_key: The key to search from in the + parsed content in a "dot" syntax. + i.e. "section.key" + + Raises: + KeyError: If the key or one of the sections + we're subscripting into does not exist. + + Returns: + The value of the key we're searching for. + """ + error_key = None try: if "." not in search_key: - key_causing_error = search_key + error_key = search_key return self.__parsed_content[search_key] else: split_keys = split_on_dot(search_key) - content_reference = self.__parsed_content + parsed_content = self.__parsed_content for key in split_keys: - key_causing_error = key - content_reference = content_reference[key] - + error_key = key + parsed_content = parsed_content[key] except (KeyError, TypeError): raise KeyError( f"cannot `get` {search_key} because " - f"{key_causing_error} is not subscriptable." + f"{error_key} is not subscriptable." ) - return content_reference + return parsed_content def set(self, key: str, value: Any) -> None: if "." not in key: @@ -137,17 +159,17 @@ def set(self, key: str, value: Any) -> None: return keys = split_on_dot(key) - content_reference = self.__parsed_content + parsed_content = self.__parsed_content for index, key in enumerate(keys): if index == len(keys) - 1: - content_reference[key] = value + parsed_content[key] = value return try: - content_reference = content_reference[key] + parsed_content = parsed_content[key] except KeyError: - content_reference[key] = {} - content_reference = content_reference[key] + parsed_content[key] = {} + parsed_content = parsed_content[key] def delete(self, key: str) -> None: try: @@ -157,19 +179,16 @@ def delete(self, key: str) -> None: indexes = split_on_dot(key) indexes_length = len(indexes) - content_reference = self.__parsed_content + parsed_content = self.__parsed_content for index, key in enumerate(indexes): if index == indexes_length - 1: - del content_reference[key] + del parsed_content[key] break - content_reference = content_reference[key] + parsed_content = parsed_content[key] except (KeyError, TypeError): raise KeyError(f"The specified key '{key}' to delete was not found.") - def stringify(self) -> str: - return self.dumps(self.__parsed_content) - def has(self, search_key: str, wild: bool = False) -> bool: if wild: return get_occurrence_of_key(self.__parsed_content, key=search_key) > 0 diff --git a/config_file/parsers/parse_value.py b/config_file/parsers/parse_value.py index b324a4b..2009585 100644 --- a/config_file/parsers/parse_value.py +++ b/config_file/parsers/parse_value.py @@ -156,9 +156,9 @@ def strtobool(val): 'val' is anything else. """ val = val.lower() - if val in ('y', 'yes', 't', 'true', 'on', '1'): + if val in ("y", "yes", "t", "true", "on", "1"): return 1 - elif val in ('n', 'no', 'f', 'false', 'off', '0'): + elif val in ("n", "no", "f", "false", "off", "0"): return 0 else: raise ValueError("invalid truth value %r" % (val,)) diff --git a/config_file/parsers/yaml_parser.py b/config_file/parsers/yaml_parser.py index 4c39582..39dd2d6 100644 --- a/config_file/parsers/yaml_parser.py +++ b/config_file/parsers/yaml_parser.py @@ -18,14 +18,17 @@ def __init__(self, file_contents: str): @property def decode_error(self) -> Type[Exception]: from ruamel.yaml import YAMLError + return YAMLError def loads(self, contents: str) -> dict: from ruamel.yaml import YAML + return YAML().load(contents) def dumps(self, loaded_contents: dict) -> str: from ruamel.yaml import YAML + buffer = StringIO() YAML().dump(loaded_contents, buffer) return buffer.getvalue() diff --git a/tests/test_abstract_parser.py b/tests/test_abstract_parser.py index 67469a6..a74d753 100644 --- a/tests/test_abstract_parser.py +++ b/tests/test_abstract_parser.py @@ -26,8 +26,8 @@ def delete(self, section_key): def has(self, section_key: str, wild: bool = False): super().has(section_key) - def stringify(self): - super().stringify() + def __str__(self): + super().__str__() def reset_internal_contents(self, file_contents: str) -> None: super().reset_internal_contents(file_contents) @@ -64,7 +64,7 @@ def test_that_base_parser_can_not_be_instantiated(): (ConcreteAbstractParser("").get, ("",)), (ConcreteAbstractParser("").set, ("", "")), (ConcreteAbstractParser("").delete, ("",)), - (ConcreteAbstractParser("").stringify, None), + (ConcreteAbstractParser("").__str__, None), (ConcreteAbstractParser("").has, ("",)), (ConcreteAbstractParser("").reset_internal_contents, ("",)), (ConcreteAbstractParser("").parsed_content, None), diff --git a/tests/test_config_file.py b/tests/test_config_file.py index 72dbf13..0aea2c8 100644 --- a/tests/test_config_file.py +++ b/tests/test_config_file.py @@ -154,7 +154,7 @@ def test_config_file_can_save(template_and_config_file): config.set("header_one.number_key", 25) config.save() - assert config.stringify() == template_file.read_text() + assert str(config) == template_file.read_text() def test_restore_original_can_restore_with_calculated_in_path( @@ -176,7 +176,7 @@ def test_restore_original_can_restore_with_calculated_in_path( config.set("header_one.number_key", 5) config.restore_original() - assert config.stringify() == original_file.read_text() + assert str(config) == original_file.read_text() assert config.path.read_text() == original_file.read_text() @@ -201,7 +201,7 @@ def test_restore_original_can_restore_with_passed_in_path( config.restore_original(original_path=original_file) - assert config.stringify() == original_config.stringify() + assert str(config) == str(original_config) assert config.path.read_text() == original_file.read_text() @@ -365,7 +365,7 @@ def test_keys_can_be_set_with_array_notation(template_and_config_file): config["header_one"] = {} config.save() - assert config.stringify() == template_file.read_text() + assert str(config) == template_file.read_text() def test_keys_can_be_retrieved_with_array_notation(templated_config_file): @@ -397,7 +397,6 @@ def test_the_config_file_can_be_stringified(template_and_config_file): """ config_file.config_file.ConfigFile.__str__ config_file.config_file.ConfigFile.__repr__ - config_file.config_file.ConfigFile.stringify() Ensure that the stringified version of the config file is the string version of the file and that the repr also @@ -405,5 +404,4 @@ def test_the_config_file_can_be_stringified(template_and_config_file): """ template, config = template_and_config_file() assert template.read_text() == str(config) - assert template.read_text() == config.stringify() assert f"{str(template)}\n\n{str(config)}" == repr(config) diff --git a/tests/test_config_file_path.py b/tests/test_config_file_path.py index 41235f8..30b3b7f 100644 --- a/tests/test_config_file_path.py +++ b/tests/test_config_file_path.py @@ -1,4 +1,3 @@ -import os from pathlib import Path import pytest diff --git a/tests/test_nested_lookup.py b/tests/test_nested_lookup.py index 1239fad..1dd274a 100644 --- a/tests/test_nested_lookup.py +++ b/tests/test_nested_lookup.py @@ -856,7 +856,6 @@ def test_sample_data4(self): class TestNestedAlter(BaseLookUpApi): def test_nested_alter_in_place_true(self): - # callback functions def callback(data): return str(data) + "###" @@ -870,7 +869,6 @@ def callback(data): self.assertEqual(vorgangsid, "1###") def test_nested_alter_in_place_false(self): - # callback functions def callback(data): return str(data) + "###" @@ -886,7 +884,6 @@ def callback(data): self.assertEqual(vorgangsid, "1###") def test_nested_alter_list_input_in_place_true(self): - # callback functions def callback(data): return str(data) + "###" @@ -906,7 +903,6 @@ def callback(data): self.assertEqual(vorgangsid, "1###") def test_nested_alter_list_input_with_args_in_place_true(self): - # callback functions def callback(data, str1, str2): return str(data) + str1 + str2 @@ -930,7 +926,6 @@ def callback(data, str1, str2): self.assertEqual(vorgangsid, "1abcdef") def test_nested_alter_list_input_with_args_in_place_false(self): - # callback functions def callback(data, str1, str2): return str(data) + str1 + str2 @@ -954,7 +949,6 @@ def callback(data, str1, str2): self.assertEqual(vorgangsid, "1abcdef") def test_nested_alter_list_input_in_place_false(self): - # callback functions def callback(data): return str(data) + "###" @@ -1010,7 +1004,6 @@ def callback(data): self.assertEqual(altered_document["key"], 150) def test_sample_data4(self): - result = { "modelversion": "1.1.0", "vorgangsID": "1", diff --git a/tests/test_toml_parser.py b/tests/test_toml_parser.py index 7fc7d4e..8c6a905 100644 --- a/tests/test_toml_parser.py +++ b/tests/test_toml_parser.py @@ -13,7 +13,6 @@ def test_that_import_error_is_raised_if_no_tomlkit(): installed. """ with pytest.raises(ImportError) as error: - with patch("builtins.__import__") as mock_import: mock_import.side_effect = ImportError TomlParser("") diff --git a/tests/test_yaml_parser.py b/tests/test_yaml_parser.py index e375dd9..cb25f4d 100644 --- a/tests/test_yaml_parser.py +++ b/tests/test_yaml_parser.py @@ -13,7 +13,6 @@ def test_that_import_error_is_raised_if_no_ruamelyaml(): installed. """ with pytest.raises(ImportError) as error: - with patch("builtins.__import__") as mock_import: mock_import.side_effect = ImportError YamlParser("")