changeset 6584:770503bd211e

Validate SecretOption and support validate method Needed to validate SecretOption and verify that file is readable and valid. validator() now calls the validate method for each Option subclass. To add post config load validation, just define a method validate(self, options) for the Option subclass.
author John Rouillard <rouilj@ieee.org>
date Thu, 06 Jan 2022 21:22:26 -0500
parents 3759893f0686
children d4371b131c9c
files roundup/configuration.py test/test_config.py
diffstat 2 files changed, 51 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/roundup/configuration.py	Wed Jan 05 15:19:33 2022 -0500
+++ b/roundup/configuration.py	Thu Jan 06 21:22:26 2022 -0500
@@ -436,6 +436,28 @@
             return _val
         raise OptionValueError(self, value, self.class_description)
 
+    def validate(self, options):
+
+        if self._value in ("", "xapian"):
+            try:
+                import xapian
+            except ImportError:
+                # indexer is probably '' and xapian isn't present
+                # so just return at end of method
+                pass
+            else:
+                try:
+                    lang = options["INDEXER_LANGUAGE"]._value
+                    xapian.Stem(lang)
+                except xapian.InvalidArgumentError:
+                    import textwrap
+                    lang_avail = b2s(xapian.Stem.get_available_languages())
+                    languages = textwrap.fill(_("Valid languages: ") +
+                                              lang_avail, 75,
+                                              subsequent_indent="   ")
+                    raise OptionValueError(options["INDEXER_LANGUAGE"],
+                                            lang, languages)
+
 
 class MailAddressOption(Option):
 
@@ -607,6 +629,22 @@
                         "%s: %s." % (self.name, e.filename, e.args[1]))
         return self.str2value(_val)
 
+    def validate(self, options):
+        if self.name == 'MAIL_PASSWORD':
+            if options['MAIL_USERNAME']._value:
+                # MAIL_PASSWORD is an exception. It is mandatory only
+                # if MAIL_USERNAME is set. So check only if username
+                # is set.
+                try:
+                    self.get()
+                except OptionUnsetError:
+                    # provide error message with link to MAIL_USERNAME
+                    raise OptionValueError(options["MAIL_PASSWORD"],
+                                           "not defined",
+                            "Mail username is set, so this must be defined.")
+        else:
+            self.get()
+
 
 class WebUrlOption(Option):
     """URL MUST start with http/https scheme and end with '/'"""
@@ -1479,6 +1517,10 @@
     # actual name of the config file.  set on load.
     filepath = os.path.join(HOME, INI_FILE)
 
+    # List of option names that need additional validation after
+    # all options are loaded.
+    option_validators = []
+
     def __init__(self, config_path=None, layout=None, settings=None):
         """Initialize confing instance
 
@@ -1552,6 +1594,9 @@
         for _name in option.aliases:
             self.options[_name] = option
 
+        if hasattr(option, 'validate'):
+            self.option_validators.append(option.name)
+
     def update_option(self, name, klass,
                       default=NODEFAULT, description=None):
         """Override behaviour of early created option.
@@ -1965,32 +2010,10 @@
             on each other. E.G. indexer_language can only be
             validated if xapian indexer is used.
         """
-        if options['INDEXER']._value in ("", "xapian"):
-            try:
-                import xapian
-            except ImportError:
-                # indexer is probably '' and xapian isn't present
-                # so just return at end of method
-                pass
-            else:
-                try:
-                    lang = options["INDEXER_LANGUAGE"]._value
-                    xapian.Stem(lang)
-                except xapian.InvalidArgumentError:
-                    import textwrap
-                    lang_avail = b2s(xapian.Stem.get_available_languages())
-                    languages = textwrap.fill(_("Valid languages: ") +
-                                              lang_avail, 75,
-                                              subsequent_indent="   ")
-                    raise OptionValueError(options["INDEXER_LANGUAGE"],
-                                            lang, languages)
 
-        if options['MAIL_USERNAME']._value != "":
-            # require password to be set
-            if options['MAIL_PASSWORD']._value is NODEFAULT:
-                raise OptionValueError(options["MAIL_PASSWORD"],
-                                        "not defined",
-                "mail username is set, so this must be defined.")
+        for option in self.option_validators:
+            # validate() should throw an exception if there is an issue.
+            options[option].validate(options)
 
     def load(self, home_dir):
         """Load configuration from path designated by home_dir argument"""
--- a/test/test_config.py	Wed Jan 05 15:19:33 2022 -0500
+++ b/test/test_config.py	Thu Jan 06 21:22:26 2022 -0500
@@ -441,6 +441,7 @@
         # look for 'not defined'
         self.assertEqual("not defined", cm.exception.args[1])
 
+
     def testUnsetMailPassword_with_unset_username(self):
         """ Set [mail] username but don't set the 
             [mail] password. Should get an OptionValueError. 
@@ -461,10 +462,8 @@
 
         config = configuration.CoreConfig()
 
-        config.load(self.dirname)
-
         with self.assertRaises(configuration.OptionValueError) as cm:
-            self.assertEqual(config['WEB_SECRET_KEY'],"")
+            config.load(self.dirname)
 
         print(cm.exception)
         self.assertEqual(cm.exception.args[0].setting, "secret_key")
@@ -514,10 +513,8 @@
 
         config = configuration.CoreConfig()
 
-        config.load(self.dirname)
-
         with self.assertRaises(configuration.OptionValueError) as cm:
-            config['WEB_SECRET_KEY']
+            config.load(self.dirname)
 
         print(cm.exception.args)
         self.assertEqual(cm.exception.args[2],"Value must not be empty.")

Roundup Issue Tracker: http://roundup-tracker.org/