Commits

Marc-Alexandre Chan committed 7331248

Added rudimentary value checking to configs

  • Participants
  • Parent commits b9cd88a

Comments (0)

Files changed (1)

minibot/config.py

 a bad idea to store a copy of a configuration option which persists between run
 calls in an Event class. """
 
+from minibot import logger
+
 from ConfigParser import SafeConfigParser
 from logging import DEBUG, INFO, WARN, ERROR
 
+import re
+
+class ConfigValueError(ValueError):
+    """ Raised when an invalid configuration value is set or loaded from a file.
+    """
+
 class Section(object):
     """ Abstract class representing a configuration section. Defines a generic
     constructor that loads a dictionary, as well as a method to collapse the
         self.options = {}
         # first set defaults
         for key, val in default.iteritems():
-            if allowed is None or key in allowed:
+            if (allowed is None or key in allowed) and val:
                 self._opts[key] = val
 
         # then specified options to overwrite defaults if applicable
         """
         self._opts[name] = value
 
+    def verify(self):
+        """ Verifies the current options. Must be defined by subclasses. """
+        raise NotImplementedError()
+
 
 class SMinibot(Section):
     """ Represents the [minibot] section of the configuration file. """
     @property
     def pidfile_timeout(self): return self.get('pidfile_timeout', int)
 
+    def verify(self):
+        """ Verifies the current options. Returns ConfigValueError or
+        ValueError on invalid values. """
+        if not self.salt:
+            raise ConfigValueError('config.minibot.salt cannot be blank')
+        if not self.user_agent:
+            raise ConfigValueError('config.minibot.user_agent cannot be blank')
+
+        if self.refresh_rate <= 0:
+            raise ConfigValueError(
+                'config.minibot.refresh_rate must be positive: %d',
+                self.refresh_rate)
+        if self.queue_rate <= 0:
+            raise ConfigValueError(
+                'config.minibot.queue_rate must be positive: %d',
+                self.queue_rate)
+        if self.msg_rate <= 0:
+            raise ConfigValueError(
+                'config.minibot.msg_rate must be positive: %d',
+                self.msg_rate)
+        if self.msg_chunk <= 0:
+            raise ConfigValueError(
+                'config.minibot.msg_chunk must be positive: %d',
+                self.msg_rate)
+
+        if not self.pidfile_path:
+            raise ConfigValueError(
+                'config.minibot.pidfile_path cannot be blank')
+        if self.pidfile_timeout < 0:
+            raise ConfigValueError(
+                'config.minibot.pidfile_timeout must be positive: %d',
+                self.pidfile_timeout)
+
 
 class SEvents(Section):
     """ Represents the [events] section of the configuration file. """
     def suggestions_day(self):
         return self.get('suggestions_day', int)
 
+    def verify(self):
+        """ Verifies the current options. Returns ConfigValueError or
+        ValueError on invalid values. """
+        time_re = re.compile('\d{1,2}:\d{1,2}(:\d{1,2})?')
+        if not time_re.match(self.default_time):
+            raise ConfigValueError("config.events.default_time must be in the "
+                "format HH:MM:SS: %s", self.default_time)
+        if not time_re.match(self.suggestions_time):
+            raise ConfigValueError("config.events.suggestions_time must be in "
+                "the format HH:MM:SS: %s", self.default_time)
+        if not 0 <= self.suggestions_day < 7:
+            raise ConfigValueError("config.events.suggestions_day must be "
+                "between 0 and 6, corresponding to Monday to Sunday: %d",
+                self.suggestions_day)
 
 
 class SLog(Section):
     @property
     def db_file(self): return self.get('db_file')
     @property
-    def format(self): return self.get('format')
-    @property
-    def date_format(self): return self.get('date_format')
-    @property
     def level(self): return self.get('level', self._convert_level_string)
     @property
     def db_level(self): return self.get('db_level', self._convert_level_string)
         logging value if the current value is invalid. """
         return self.levels_dict.get(value.upper(), self.levels_dict[''])
 
+    def verify(self):
+        """ Verifies the current options. Returns ConfigValueError or
+        ValueError on invalid values. """
+        if not self.file:
+            raise ConfigValueError('config.log.file cannot be blank')
+        if not self.db_file:
+            raise ConfigValueError('config.log.db_file cannot be blank')
+
 
 class SSqlite(Section):
     """ Represents the [sqlite] section of the configuration file. """
     @property
     def tableprefix(self): return self.get('tableprefix')
 
+    def verify(self):
+        """ Verifies the current options. Returns ConfigValueError or
+        ValueError on invalid values. """
+        if not self.file:
+            raise ConfigValueError('config.sqlite.file cannot be blank')
+
 
 class SReddit(Section):
     """ Represents the [reddit] section of the configuration file. """
     @property
     def target_url(self): return 'http://reddit.com/r/' + self.target
 
+    def verify(self):
+        """ Verifies the current options. Returns ConfigValueError or
+        ValueError on invalid values. """
+        if not self.user:
+            raise ConfigValueError('config.reddit.user cannot be blank')
+        if not self.password:
+            raise ConfigValueError('config.reddit.password cannot be blank')
+        if not self.target:
+            raise ConfigValueError('config.reddit.target cannot be blank')
+
 
 class SUsers(Section):
     """ Represents the [users] section of the configuration file. """
     def __init__(self, values_dict):
+        """ Raises a ConfigValueError if any value cannot be interpreted as a
+        base-10 int. """
         self.users = {}
         for key, val in values_dict.iteritems():
-            self.users[key] = int(val)
+            try:
+                self.users[key] = int(val, 10)
+            except ValueError:
+                raise ConfigValueError("User level for user '%s' is not "
+                    "an integer: %s", key, val)
 
     def get_level(self, user):
         return self.users[user]
     def list(self):
         return self.users.keys()
 
+    def verify(self):
+        """ Verifies the current options. Returns ConfigValueError or
+        ValueError on invalid values. """
+        return
+
 
 class Config(object):
     """ Configuration class for Daily Prompt Mini-Bot. Options are readable
 
         for section in SECTIONS.iterkeys():
             if parser.has_section(section):
-                self.__dict__[section] = \
-                    SECTIONS[section](parser.items(section, True))
+                setattr(self, section,
+                    SECTIONS[section](parser.items(section, True)))
             else:
-                self.__dict__[section] = SECTIONS[section](dict())
+                setattr(self, section, SECTIONS[section](dict()))
+            # raises ConfigValueError/ValueError on failure
+            getattr(self, section).verify()
 
     def get_filename(self):
         return self.file
         for section in SECTIONS.iterkeys():
             parser.add_section(section)
 
-            for opt, val in self.__dict__[section].items():
+            for opt, val in getattr(self, section).items():
                 parser.set(section, opt, val)
 
         parser.write(fp)