From 1346f3fe27795903a1843720934a85eccb297c5c Mon Sep 17 00:00:00 2001 From: Taro Sato Date: Wed, 22 Apr 2020 10:58:30 -0700 Subject: [PATCH] Ensure an empty config file load as an empty dict --- src/resconfig/io/json.py | 17 ++++++- src/resconfig/io/paths.py | 7 +-- src/resconfig/io/yaml.py | 3 +- tests/resconfig/io/bases.py | 10 +++++ tests/resconfig/io/test_ini.py | 20 +++++---- tests/resconfig/io/test_json.py | 80 +++++++++++++++++++++++++++++++++ tests/resconfig/io/test_toml.py | 20 +++++---- tests/resconfig/io/test_yaml.py | 78 ++++++++++++++++++++++++++++++++ 8 files changed, 208 insertions(+), 27 deletions(-) create mode 100644 tests/resconfig/io/bases.py create mode 100644 tests/resconfig/io/test_json.py create mode 100644 tests/resconfig/io/test_yaml.py diff --git a/src/resconfig/io/json.py b/src/resconfig/io/json.py index c9e773a..444ae30 100644 --- a/src/resconfig/io/json.py +++ b/src/resconfig/io/json.py @@ -1,2 +1,15 @@ -from json import dump # noqa -from json import load # noqa +from json import dump as _dump +from json import load as _load +from json.decoder import JSONDecodeError + + +def dump(content, f): + return _dump(content, f) + + +def load(f): + try: + content = _load(f) + except JSONDecodeError: + content = {} + return content diff --git a/src/resconfig/io/paths.py b/src/resconfig/io/paths.py index eaf313c..9b2d955 100644 --- a/src/resconfig/io/paths.py +++ b/src/resconfig/io/paths.py @@ -21,12 +21,7 @@ def dump(self, content): def load(self): with open(self) as f: - try: - content = self.module.load(f) or {} - except Exception: - log.exception("Error occured loading from %s; assume empty...", self) - content = {} - return content + return self.module.load(f) @classmethod def from_extension(cls, filename: FilePath) -> "ConfigPath": diff --git a/src/resconfig/io/yaml.py b/src/resconfig/io/yaml.py index 3c2958b..68a5ab3 100644 --- a/src/resconfig/io/yaml.py +++ b/src/resconfig/io/yaml.py @@ -21,4 +21,5 @@ def _dict_representer(dumper, data): def load(stream, **kwargs): - return yaml.load(stream, yaml.FullLoader, **kwargs) + content = yaml.load(stream, yaml.FullLoader, **kwargs) + return content if content else {} diff --git a/tests/resconfig/io/bases.py b/tests/resconfig/io/bases.py new file mode 100644 index 0000000..9c5aa0a --- /dev/null +++ b/tests/resconfig/io/bases.py @@ -0,0 +1,10 @@ +from io import StringIO + + +class BaseTestLoad: + module = None + + def test_empty(self): + stream = StringIO("") + loaded = self.module.load(stream) + assert loaded == {}, "Blank file should load as an empty dict" diff --git a/tests/resconfig/io/test_ini.py b/tests/resconfig/io/test_ini.py index 93cede4..a0b3bfc 100644 --- a/tests/resconfig/io/test_ini.py +++ b/tests/resconfig/io/test_ini.py @@ -1,9 +1,10 @@ -import io +from io import StringIO import pytest -from resconfig.io.ini import dump -from resconfig.io.ini import load +from resconfig.io import ini + +from .bases import BaseTestLoad content = """ [DEFAULT] @@ -27,23 +28,24 @@ @pytest.fixture def stream(): - yield io.StringIO(content) + yield StringIO(content) @pytest.fixture def loaded(stream): - yield load(stream) + yield ini.load(stream) @pytest.fixture -def dumped(loaded): - stream = io.StringIO() - dump(loaded, stream) +def dumped(loaded, stream): + ini.dump(loaded, stream) stream.seek(0) yield stream.read() -class TestLoad: +class TestLoad(BaseTestLoad): + module = ini + def test_default_pass_through(self, loaded): assert loaded[r"bitbucket\.org"]["serveraliveinterval"] == "45" diff --git a/tests/resconfig/io/test_json.py b/tests/resconfig/io/test_json.py new file mode 100644 index 0000000..0c73e79 --- /dev/null +++ b/tests/resconfig/io/test_json.py @@ -0,0 +1,80 @@ +from io import StringIO + +import pytest + +from resconfig.io import json + +from .bases import BaseTestLoad + +content = """ +{ + "str": "str", + "int": 10, + "bool": true, + "null": null, + "array": [0, 1, 2], + "nested": { + "str": "mystr", + "int": 10 + } +} +""" + + +@pytest.fixture +def stream(): + yield StringIO(content) + + +@pytest.fixture +def loaded(stream): + yield json.load(stream) + + +@pytest.fixture +def dumped(loaded, stream): + json.dump(loaded, stream) + stream.seek(0) + yield stream.read() + + +class TestLoad(BaseTestLoad): + module = json + + def test_integer(self, loaded): + assert loaded["int"] == 10 + + def test_string(self, loaded): + assert loaded["str"] == "str" + + def test_bool(self, loaded): + assert loaded["bool"] is True + + def test_null(self, loaded): + assert loaded["null"] is None + + def test_array(self, loaded): + assert loaded["array"] == [0, 1, 2] + + def test_nested(self, loaded): + assert loaded["nested"]["int"] == 10 + + +class TestDump: + def test_string(self, dumped): + assert '"str": "str"' in dumped + + def test_integer(self, dumped): + assert '"int": 10' in dumped + + def test_bool(self, dumped): + assert '"bool": true' in dumped + + def test_null(self, dumped): + assert '"null": null' in dumped + + def test_array(self, dumped): + assert '"array": [0, 1, 2]' in dumped + + def test_nested(self, dumped): + assert '"nested": {' in dumped diff --git a/tests/resconfig/io/test_toml.py b/tests/resconfig/io/test_toml.py index f59c59a..fc02abe 100644 --- a/tests/resconfig/io/test_toml.py +++ b/tests/resconfig/io/test_toml.py @@ -1,12 +1,13 @@ -import io from datetime import datetime from datetime import timedelta from datetime import timezone +from io import StringIO import pytest -from resconfig.io.toml import dump -from resconfig.io.toml import load +from resconfig.io import toml + +from .bases import BaseTestLoad content = """ title = "TOML Example" @@ -46,23 +47,24 @@ @pytest.fixture def stream(): - yield io.StringIO(content) + yield StringIO(content) @pytest.fixture def loaded(stream): - yield load(stream) + yield toml.load(stream) @pytest.fixture -def dumped(loaded): - stream = io.StringIO() - dump(loaded, stream) +def dumped(loaded, stream): + toml.dump(loaded, stream) stream.seek(0) yield stream.read() -class TestLoad: +class TestLoad(BaseTestLoad): + module = toml + def test_no_section(self, loaded): assert loaded["title"] == "TOML Example" diff --git a/tests/resconfig/io/test_yaml.py b/tests/resconfig/io/test_yaml.py new file mode 100644 index 0000000..d6c8021 --- /dev/null +++ b/tests/resconfig/io/test_yaml.py @@ -0,0 +1,78 @@ +from io import StringIO + +import pytest + +from resconfig.io import yaml + +from .bases import BaseTestLoad + +content = """ +--- +str: str +int: 10 +bool: true +null: null +array: [0, 1, 2] +nested: + str: mystr + int: 10 +""" + + +@pytest.fixture +def stream(): + yield StringIO(content) + + +@pytest.fixture +def loaded(stream): + yield yaml.load(stream) + + +@pytest.fixture +def dumped(loaded, stream): + yaml.dump(loaded, stream) + stream.seek(0) + yield stream.read() + + +class TestLoad(BaseTestLoad): + module = yaml + + def test_integer(self, loaded): + assert loaded["int"] == 10 + + def test_string(self, loaded): + assert loaded["str"] == "str" + + def test_bool(self, loaded): + assert loaded["bool"] is True + + def test_null(self, loaded): + assert loaded[None] is None + + def test_array(self, loaded): + assert loaded["array"] == [0, 1, 2] + + def test_nested(self, loaded): + assert loaded["nested"]["int"] == 10 + + +class TestDump: + def test_string(self, dumped): + assert "str: str" in dumped + + def test_integer(self, dumped): + assert "int: 10" in dumped + + def test_bool(self, dumped): + assert "bool: true" in dumped + + def test_null(self, dumped): + assert "null: null" in dumped + + def test_array(self, dumped): + assert "array:\n- 0\n- 1\n- 2\n" in dumped + + def test_nested(self, dumped): + assert "nested:" in dumped