Create argparse type "existing_dir[_empty]"
authorChristian Herdtweck <christian.herdtweck@intra2net.com>
Fri, 1 Apr 2022 11:29:19 +0000 (13:29 +0200)
committerChristian Herdtweck <christian.herdtweck@intra2net.com>
Thu, 21 Apr 2022 08:57:23 +0000 (10:57 +0200)
Programs often have a file or dir as argument, check in argparse right
away whether that actually exists. Optionally, accept an empty value to
use a default value.

src/argparse_helpers.py
test/test_argparse_helpers.py

index 91e64d2..93e4bad 100644 (file)
@@ -28,7 +28,7 @@ Featuring
 """
 
 from argparse import ArgumentParser, ArgumentTypeError
-from os.path import isfile
+from os.path import isfile, isdir
 
 
 class ArgParserWantsExit(Exception):
@@ -44,7 +44,7 @@ class ArgParserWantsExit(Exception):
 class NonExitingParser(ArgumentParser):
     """ArgumentParser that does not call sys.exit(2) on parse failure.
 
-    Calling sys.exit also just raises a SystemExit exception. But that is not
+    Calling `sys.exit` also just raises a SystemExit exception. But that is not
     a subclass of Exception and not as explicit and specific as this one.
 
     Convenient e.g. for global try-except blocks e.g. in a daemon::
@@ -88,3 +88,23 @@ def existing_file_or_empty(filename=''):
     if not filename.strip():
         return ''
     return existing_file(filename)
+
+
+def existing_dir(path):
+    """
+    Function that raises ArgumentTypeError if argument is not an existing directory.
+
+    .. seealso:: :py:func:`existing_file`, :py:func:`existing_dir_or_empty`
+    """
+    if isdir(path):
+        return path
+    raise ArgumentTypeError('{} is not an existing directory'.format(path))
+
+
+def existing_dir_or_empty(path=''):
+    """
+    Like :py:func:`existing_dir` but accepts empty path (returns '' then).
+    """
+    if not path.strip():
+        return ''
+    return existing_dir(path)
index 175219e..f0c77e2 100644 (file)
@@ -27,8 +27,9 @@ For help see :py:mod:`unittest`
 
 import unittest
 import os
-from os.path import isfile
-from tempfile import mkstemp
+from os.path import isfile, isdir
+from shutil import rmtree
+from tempfile import mkstemp, mkdtemp
 
 # relative import of tested module ensures we do not test installed version
 try:
@@ -61,6 +62,8 @@ class ArgparseTester(unittest.TestCase):
             # test with existing file
             parser = argparse_helpers.NonExitingParser()
             parser.add_argument('--input', type=argparse_helpers.existing_file)
+            parser.add_argument('--input2', type=argparse_helpers.existing_file_or_empty)
+            parser.parse_args(['--input', temp_file, '--input2', temp_file])
         finally:
             if temp_handle:
                 os.close(temp_handle)
@@ -73,5 +76,45 @@ class ArgparseTester(unittest.TestCase):
                                parser.parse_args,
                                ['--input', temp_file, ])
 
+    def test_existing_dir(self):
+        temp_dir = None
+        try:
+            # create a dir
+            temp_dir = mkdtemp()
+
+            # test with existing dir
+            parser = argparse_helpers.NonExitingParser()
+            parser.add_argument('--input', type=argparse_helpers.existing_dir)
+            parser.add_argument('--input2', type=argparse_helpers.existing_dir_or_empty)
+            parser.parse_args(['--input', temp_dir, '--input2', temp_dir])
+        finally:
+            if temp_dir and isdir(temp_dir):
+                rmtree(temp_dir)
+
+        # test with non-existing dir
+        self.assertRaisesRegex(argparse_helpers.ArgParserWantsExit,
+                               'is not an existing directory',
+                               parser.parse_args,
+                               ['--input', temp_dir, ])
+
+    def test_allow_empty(self):
+        parser = argparse_helpers.NonExitingParser()
+        parser.add_argument('--file', type=argparse_helpers.existing_file_or_empty)
+        parser.add_argument('--dir', type=argparse_helpers.existing_dir_or_empty)
+
+        # both emtpy: no problem
+        parser.parse_args(['--file', '', '--dir', ''])
+
+        # test with non-existing file/dir
+        self.assertRaisesRegex(argparse_helpers.ArgParserWantsExit,
+                               'is not an existing file',
+                               parser.parse_args,
+                               ['--dir', '', '--file', 'not-an-existing-file'])
+        self.assertRaisesRegex(argparse_helpers.ArgParserWantsExit,
+                               'is not an existing directory',
+                               parser.parse_args,
+                               ['--file', '', '--dir', 'not-an-existing-dir'])
+
+
 if __name__ == '__main__':
     unittest.main()