From 27a17262544786b8f2df149679929121f9ead7d7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= <piotr.maslanka@henrietta.com.pl>
Date: Mon, 10 May 2021 17:09:47 +0200
Subject: [PATCH] v2.16 added Directory

---
 CHANGELOG.md                             |  1 +
 docs/configuration/schema.rst            | 28 +++++++++----
 satella/__init__.py                      |  2 +-
 satella/configuration/schema/__init__.py |  3 +-
 satella/configuration/schema/basic.py    | 52 +++++++++++++++++++++++-
 5 files changed, 74 insertions(+), 12 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index df366856..25e15965 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,4 @@
 # v2.16
 
 * changed semantics of `wrap_future`
+* added `Directory` to schema configuration
diff --git a/docs/configuration/schema.rst b/docs/configuration/schema.rst
index ae14194c..ef83f298 100644
--- a/docs/configuration/schema.rst
+++ b/docs/configuration/schema.rst
@@ -17,6 +17,14 @@ you should instantiate a Descriptor. Descriptor reflects how your config is nest
 
 .. autoclass:: satella.configuration.schema.File
 
+.. autoclass:: satella.configuration.schema.FileObject
+    :members:
+
+.. autoclass:: satella.configuration.schema.Directory
+
+.. autoclass:: satella.configuration.schema.DirectoryObject
+    :members:
+
 .. autoclass:: satella.configuration.schema.basic.FileObject
 
 .. autoclass:: satella.configuration.schema.IPv4
@@ -71,15 +79,17 @@ Note that providing a short-hand, string type is impossible for descriptors that
 
 Available string types are:
 
-* **int** - Integer
-* **str** - String
-* **list** - List
-* **dict** - Dict
-* **ipv4** - IPv4
-* **any** - Descriptor
-* **bool** - Boolean
-* **union** - Union
-* **caster** - Caster
+* **int** - :class:`~satella.configuration.schema.Integer`
+* **str** - :class:`~satella.configuration.schema.String`
+* **list** - :class:`~satella.configuration.schema.List`
+* **dict** - :class:`~satella.configuration.schema.Dict`
+* **ipv4** - :class:`~satella.configuration.schema.IPv4`
+* **any** - :class:`~satella.configuration.schema.Descriptor`
+* **bool** - :class:`~satella.configuration.schema.Boolean`
+* **union** - :class:`~satella.configuration.schema.Union`
+* **caster** - :class:`~satella.configuration.schema.Caster`
+* **file** - :class:`~satella.configuration.schema.File`
+* **dir** - :class:`~satella.configuration.schema.Directory`
 
 Lists you define as following
 
diff --git a/satella/__init__.py b/satella/__init__.py
index e74eabb2..50a1c84b 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1 +1 @@
-__version__ = '2.16a1'
+__version__ = '2.16'
diff --git a/satella/configuration/schema/__init__.py b/satella/configuration/schema/__init__.py
index a1f61b57..e1355704 100644
--- a/satella/configuration/schema/__init__.py
+++ b/satella/configuration/schema/__init__.py
@@ -1,9 +1,10 @@
 from .base import CheckerCondition, Descriptor
-from .basic import IPv4, Integer, String, Float, Boolean
+from .basic import IPv4, Integer, String, Float, Boolean, File, Directory, FileObject, DirectoryObject
 from .from_json import descriptor_from_dict
 from .registry import register_custom_descriptor
 from .structs import Union, List, Dict, Caster, create_key
 
 __all__ = ['CheckerCondition', 'Descriptor', 'descriptor_from_dict', 'IPv4', 'Integer',
            'String', 'Float', 'Boolean', 'Union', 'List', 'Dict', 'Caster',
+           'File', 'FileObject', 'DirectoryObject', 'Directory',
            'register_custom_descriptor', 'create_key']
diff --git a/satella/configuration/schema/basic.py b/satella/configuration/schema/basic.py
index 5c16bd26..4eff8c35 100644
--- a/satella/configuration/schema/basic.py
+++ b/satella/configuration/schema/basic.py
@@ -71,7 +71,7 @@ class FileObject:
         return self.path
 
     def __eq__(self, other) -> bool:
-        return self.path == str(other)
+        return self.path == str(other) and isinstance(other, FileObject)
 
     def __hash__(self) -> int:
         return hash(self.path)
@@ -95,6 +95,37 @@ class FileObject:
         return open(self.path, mode)
 
 
+class DirectoryObject:
+    """
+    What you get for values in schema of :class:`~satella.configuration.schema.Directory`.
+
+    This object is comparable and hashable, and is equal to the string of it's path
+    """
+    __slots__ = 'path',
+
+    def __init__(self, path: str):
+        self.path = path
+
+    def __repr__(self):
+        return '<Directory object %s>' % (self.path, )
+
+    def __str__(self):
+        return self.path
+
+    def __eq__(self, other) -> bool:
+        return self.path == str(other) and isinstance(other, DirectoryObject)
+
+    def __hash__(self) -> int:
+        return hash(self.path)
+
+    def get_files(self) -> tp.Iterable[str]:
+        """
+        Return a list of files inside this directory
+        :return:
+        """
+        return os.listdir(self.path)
+
+
 @staticmethod
 def _make_file(v: str) -> bool:
 
@@ -114,6 +145,25 @@ class File(Descriptor):
     BASIC_MAKER = _make_file
 
 
+@staticmethod
+def _make_directory(v: str) -> bool:
+
+    if not os.path.isdir(v):
+        raise ConfigurationValidationError('Expected to find a directory under %s'
+                                           % (v,))
+    return DirectoryObject(v)
+
+
+@register_custom_descriptor('dir')
+class Directory(Descriptor):
+    """
+    This value must be a valid path to a file. The value in your schema will be
+    an instance of :class:`~satella.configuration.schema.basic.FileObject`
+    """
+
+    BASIC_MAKER = _make_directory
+
+
 class Regexp(String):
     """
     Base class for declaring regexp-based descriptors. Overload it's attribute REGEXP. Use as
-- 
GitLab