Repository: markusressel/container-app-conf

Issue:
Use of pathlib for File- and DirectoryConfigEntry
Since **container-app-conf** uses an object-oriented approach and [pathlib](https://docs.python.org/3.5/library/pathlib.html) provides objects for paths it would be the more suitable implementation. This also makes further handling of the path way easier.

Discussion:

Patch:
diff --git a/README.md b/README.md
index 28db477..07383a2 100644
--- a/README.md
+++ b/README.md
@@ -53,8 +53,8 @@ class AppConfig(Config):
 | `StringConfigEntry`      | Takes the raw string input | `str` |
 | `DateConfigEntry`        | Parses various datetime formats (see [python-dateutil](https://github.com/dateutil/dateutil/)) | `datetime` |
 | `TimeDeltaConfigEntry`   | Parses various timedelta formats (see [pytimeparse](https://github.com/wroberts/pytimeparse)) | `timedelta` |
-| `FileConfigEntry`        | Parses a file path | `str` |
-| `DirectoryConfigEntry`   | Parses a directory path | `str` |
+| `FileConfigEntry`        | Parses a file path | `Path` |
+| `DirectoryConfigEntry`   | Parses a directory path | `Path` |
 | `ListConfigEntry`        | Parses a comma separated string to a list of items specified in another `ConfigEntry` (in yaml it can also be specified as a yaml list) | `[]` |
 
 If none of the existing types suit your needs you can easily create your 
diff --git a/container_app_conf/entry/file.py b/container_app_conf/entry/file.py
index 32ffe3e..8feb494 100644
--- a/container_app_conf/entry/file.py
+++ b/container_app_conf/entry/file.py
@@ -18,6 +18,7 @@
 #  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 #  SOFTWARE.
 import os
+import pathlib
 
 from container_app_conf import ConfigEntry
 
@@ -41,18 +42,18 @@ class FileConfigEntry(ConfigEntry):
         :return: the parsed file value
         """
         str_value = str(value)
+        file = pathlib.Path(str_value)
 
         if str_value.endswith(os.sep):
-            raise AssertionError("File path should not end with delimiter: {}".format(str_value))
+            raise AssertionError("File path should not end with '{}' delimiter: {}".format(os.sep, str_value))
 
-        if os.path.exists(str_value):
-            if not os.path.isfile(str_value):
-                IsADirectoryError("Path is not a file: {}".format(str_value))
-        else:
-            if self.check_existence:
-                raise FileNotFoundError("File does not exist: {}".format(value))
+        if file.is_dir():
+            raise IsADirectoryError("Path is not a file: {}".format(str_value))
 
-        return os.path.abspath(str_value)
+        if self.check_existence and not file.exists():
+            raise FileNotFoundError("File does not exist: {}".format(value))
+
+        return file
 
 
 class DirectoryConfigEntry(ConfigEntry):
@@ -74,12 +75,15 @@ class DirectoryConfigEntry(ConfigEntry):
         :return: the parsed folder value
         """
         str_value = str(value)
+        directory = pathlib.Path(str_value)
+
+        if not str_value.endswith(os.sep):
+            raise AssertionError("Directory path should end with '{}' delimiter: {}".format(os.sep, str_value))
+
+        if directory.is_file():
+            raise NotADirectoryError("Path is not a directory: {}".format(str_value))
 
-        if os.path.exists(str_value):
-            if not os.path.isdir(str_value):
-                raise NotADirectoryError("Path is not a directory: {}".format(str_value))
-        else:
-            if self.check_existence:
-                raise FileNotFoundError("directory does not exist: {}".format(value))
+        if self.check_existence and not directory.exists():
+            raise FileNotFoundError("directory does not exist: {}".format(value))
 
-        return os.path.abspath(str_value)
+        return directory


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
index 5dedea8..63fb44f 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -104,8 +104,7 @@ class TestBase(unittest.TestCase):
 
 class EntryTestBase(TestBase):
 
-    @staticmethod
-    def assert_input_output(entry: ConfigEntry, list_of_tuples: [()]):
+    def assert_input_output(self, entry: ConfigEntry, list_of_tuples: [()]):
         for item in list_of_tuples:
             assert len(item) == 2
 
@@ -119,4 +118,4 @@ class EntryTestBase(TestBase):
                 except:
                     assert True
             else:
-                assert entry._parse_value(input) == result
+                self.assertEquals(entry._parse_value(input), result)
diff --git a/tests/entry_test.py b/tests/entry_test.py
index c7662f3..4059875 100644
--- a/tests/entry_test.py
+++ b/tests/entry_test.py
@@ -18,7 +18,7 @@
 #  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 #  SOFTWARE.
 
-import os
+from pathlib import Path
 
 from py_range_parse import Range
 
@@ -34,8 +34,7 @@ from tests import EntryTestBase
 
 class EntryTest(EntryTestBase):
 
-    @staticmethod
-    def test_bool_entry():
+    def test_bool_entry(self):
         config_entry = BoolConfigEntry(yaml_path=["bool"])
 
         true_values = ["y", "yes", "true", "t", 1, True]
@@ -51,10 +50,9 @@ class EntryTest(EntryTestBase):
         for iv in invalid_values:
             input_output.append((iv, ValueError))
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)
 
-    @staticmethod
-    def test_int_entry():
+    def test_int_entry(self):
         config_entry = IntConfigEntry(yaml_path=["int"])
         input_output = [
             ("5", 5),
@@ -63,10 +61,9 @@ class EntryTest(EntryTestBase):
             (-3, -3)
         ]
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)
 
-    @staticmethod
-    def test_float_entry():
+    def test_float_entry(self):
         config_entry = FloatConfigEntry(yaml_path=["float"])
         input_output = [
             ("5", 5.0),
@@ -78,10 +75,9 @@ class EntryTest(EntryTestBase):
             ("3%", 0.03)
         ]
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)
 
-    @staticmethod
-    def test_date_entry():
+    def test_date_entry(self):
         from datetime import datetime
         from dateutil.tz import tzutc
 
@@ -91,10 +87,9 @@ class EntryTest(EntryTestBase):
             ("2008-09-03", datetime(2008, 9, 3, 0, 0, 0, 0)),
         ]
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)
 
-    @staticmethod
-    def test_timedelta_entry():
+    def test_timedelta_entry(self):
         from datetime import timedelta
 
         config_entry = TimeDeltaConfigEntry(yaml_path=["timedelta"])
@@ -106,18 +101,17 @@ class EntryTest(EntryTestBase):
             ("4:13", timedelta(hours=0, minutes=4, seconds=13)),
         ]
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)
 
-    @staticmethod
-    def test_file_entry():
+    def test_file_entry(self):
         config_entry = FileConfigEntry(yaml_path=["file"])
         input_output = [
-            ("/tmp/test", "/tmp/test"),
-            ("./test", os.path.abspath("./test")),
+            ("/tmp/test", Path("/tmp/test")),
+            ("./test", Path("./test")),
             ("/something/", AssertionError),
         ]
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)
 
         config_entry = FileConfigEntry(yaml_path=["file"],
                                        check_existence=True)
@@ -125,33 +119,32 @@ class EntryTest(EntryTestBase):
             ("/tmp/test", FileNotFoundError),
         ]
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)
 
-    @staticmethod
-    def test_directory_entry():
+    def test_directory_entry(self):
         config_entry = DirectoryConfigEntry(yaml_path=["directory"])
         input_output = [
-            ("/tmp", "/tmp"),
-            ("./test", os.path.abspath("./test")),
-            ("/something/", "/something"),
+            ("/tmp", AssertionError),
+            ("/tmp/", Path("/tmp")),
+            ("./test/", Path("./test")),
+            ("/something/", Path("/something")),
         ]
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)
 
         config_entry = DirectoryConfigEntry(yaml_path=["directory"],
                                             check_existence=True)
 
         input_output = [
-            ("./", os.path.abspath("./")),
+            ("./", Path("./")),
         ]
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)
 
-    @staticmethod
-    def test_range_entry():
+    def test_range_entry(self):
         config_entry = RangeConfigEntry(yaml_path=["range"])
         input_output = [
             ("[-5..5]", Range(-5, 5)),
         ]
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)
diff --git a/tests/list_entry_test.py b/tests/list_entry_test.py
index 669602e..a6882e5 100644
--- a/tests/list_entry_test.py
+++ b/tests/list_entry_test.py
@@ -17,9 +17,9 @@
 #  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 #  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 #  SOFTWARE.
-import os
 from datetime import datetime
 from datetime import timedelta
+from pathlib import Path
 
 from dateutil.tz import tzutc
 
@@ -34,8 +34,7 @@ from tests import EntryTestBase
 
 class ListEntryTest(EntryTestBase):
 
-    @staticmethod
-    def test_str_list_entry_custom_delimiter():
+    def test_str_list_entry_custom_delimiter(self):
         config_entry = ListConfigEntry(item_type=IntConfigEntry,
                                        yaml_path=["int_list"],
                                        delimiter="::")
@@ -43,10 +42,9 @@ class ListEntryTest(EntryTestBase):
             ("1::2::3", [1, 2, 3])
         ]
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)
 
-    @staticmethod
-    def test_int_list_entry():
+    def test_int_list_entry(self):
         config_entry = ListConfigEntry(item_type=IntConfigEntry,
                                        yaml_path=["int_list"])
         input_output = [
@@ -55,10 +53,9 @@ class ListEntryTest(EntryTestBase):
             ("1,2,3", [1, 2, 3])
         ]
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)
 
-    @staticmethod
-    def test_float_list_entry():
+    def test_float_list_entry(self):
         config_entry = ListConfigEntry(item_type=FloatConfigEntry,
                                        yaml_path=["float_list"])
         input_output = [
@@ -68,10 +65,9 @@ class ListEntryTest(EntryTestBase):
             ("1,2.5,3", [1.0, 2.5, 3.0])
         ]
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)
 
-    @staticmethod
-    def test_date_list_entry():
+    def test_date_list_entry(self):
         config_entry = ListConfigEntry(item_type=DateConfigEntry,
                                        yaml_path=["date_list"])
 
@@ -84,10 +80,9 @@ class ListEntryTest(EntryTestBase):
             (",".join(input_example_1), output_example_1)
         ]
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)
 
-    @staticmethod
-    def test_timedelta_entry():
+    def test_timedelta_entry(self):
         config_entry = ListConfigEntry(item_type=TimeDeltaConfigEntry,
                                        yaml_path=["timedelta_list"])
         input_output = [
@@ -102,10 +97,9 @@ class ListEntryTest(EntryTestBase):
             )
         ]
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)
 
-    @staticmethod
-    def test_file_entry():
+    def test_file_entry(self):
         config_entry = ListConfigEntry(item_type=FileConfigEntry,
                                        item_args={
                                            "check_existence": False
@@ -117,9 +111,9 @@ class ListEntryTest(EntryTestBase):
         input_output = [
             (None, None),
             ("/tmp/", AssertionError),
-            (example1, [example1]),
-            (example2, [os.path.abspath(example2)]),
-            (",".join(example3), [example1, os.path.abspath(example2)]),
+            (example1, [Path(example1)]),
+            (example2, [Path(example2)]),
+            (",".join(example3), [Path(example1), Path(example2)]),
         ]
 
-        EntryTestBase.assert_input_output(config_entry, input_output)
+        self.assert_input_output(config_entry, input_output)