]> git.llucax.com Git - software/pymin.git/blobdiff - pymin/services/util.py
Add a new, backward compatible, way to specify specific config dirs.
[software/pymin.git] / pymin / services / util.py
index a61045c6d7f61a8e4271c5aabae5939e134e9feb..95a07e9992d8b0f9b92df39e7a1c41e2cd34f6aa 100644 (file)
@@ -9,13 +9,16 @@ try:
 except ImportError:
     import pickle
 
 except ImportError:
     import pickle
 
-from pymin.dispatcher import Handler, handler, HandlerError
+from pymin.dispatcher import Handler, handler, HandlerError, \
+                                CommandNotFoundError
 
 
-DEBUG = False
-#DEBUG = True
+#DEBUG = False
+DEBUG = True
 
 
-__ALL__ = ('ServiceHandler', 'InitdHandler', 'Persistent', 'ConfigWriter',
-            'Error', 'ReturnNot0Error', 'ExecutionError', 'call')
+__ALL__ = ('ServiceHandler', 'InitdHandler', 'SubHandler', 'DictSubHandler',
+            'ListSubHandler', 'Persistent', 'ConfigWriter', 'Error',
+            'ReturnNot0Error', 'ExecutionError', 'ItemError',
+            'ItemAlreadyExistsError', 'ItemNotFoundError', 'call')
 
 class Error(HandlerError):
     r"""
 
 class Error(HandlerError):
     r"""
@@ -26,13 +29,7 @@ class Error(HandlerError):
 
     message - A descriptive error message.
     """
 
     message - A descriptive error message.
     """
-
-    def __init__(self, message):
-        r"Initialize the object. See class documentation for more info."
-        self.message = message
-
-    def __str__(self):
-        return self.message
+    pass
 
 class ReturnNot0Error(Error):
     r"""
 
 class ReturnNot0Error(Error):
     r"""
@@ -47,7 +44,7 @@ class ReturnNot0Error(Error):
         r"Initialize the object. See class documentation for more info."
         self.return_value = return_value
 
         r"Initialize the object. See class documentation for more info."
         self.return_value = return_value
 
-    def __str__(self):
+    def __unicode__(self):
         return 'The command returned %d' % self.return_value
 
 class ExecutionError(Error):
         return 'The command returned %d' % self.return_value
 
 class ExecutionError(Error):
@@ -66,7 +63,7 @@ class ExecutionError(Error):
         self.command = command
         self.error = error
 
         self.command = command
         self.error = error
 
-    def __str__(self):
+    def __unicode__(self):
         command = self.command
         if not isinstance(self.command, basestring):
             command = ' '.join(command)
         command = self.command
         if not isinstance(self.command, basestring):
             command = ' '.join(command)
@@ -85,7 +82,7 @@ class ParameterError(Error, KeyError):
 
 class ParameterNotFoundError(ParameterError):
     r"""
 
 class ParameterNotFoundError(ParameterError):
     r"""
-    ParameterNotFoundError(hostname) -> ParameterNotFoundError instance
+    ParameterNotFoundError(paramname) -> ParameterNotFoundError instance
 
     This exception is raised when trying to operate on a parameter that doesn't
     exists.
 
     This exception is raised when trying to operate on a parameter that doesn't
     exists.
@@ -95,6 +92,40 @@ class ParameterNotFoundError(ParameterError):
         r"Initialize the object. See class documentation for more info."
         self.message = 'Parameter not found: "%s"' % paramname
 
         r"Initialize the object. See class documentation for more info."
         self.message = 'Parameter not found: "%s"' % paramname
 
+class ItemError(Error, KeyError):
+    r"""
+    ItemError(key) -> ItemError instance.
+
+    This is the base exception for all item related errors.
+    """
+
+    def __init__(self, key):
+        r"Initialize the object. See class documentation for more info."
+        self.message = u'Item error: "%s"' % key
+
+class ItemAlreadyExistsError(ItemError):
+    r"""
+    ItemAlreadyExistsError(key) -> ItemAlreadyExistsError instance.
+
+    This exception is raised when trying to add an item that already exists.
+    """
+
+    def __init__(self, key):
+        r"Initialize the object. See class documentation for more info."
+        self.message = u'Item already exists: "%s"' % key
+
+class ItemNotFoundError(ItemError):
+    r"""
+    ItemNotFoundError(key) -> ItemNotFoundError instance
+
+    This exception is raised when trying to operate on an item that doesn't
+    exists.
+    """
+
+    def __init__(self, key):
+        r"Initialize the object. See class documentation for more info."
+        self.message = u'Item not found: "%s"' % key
+
 
 def call(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
             stderr=subprocess.PIPE, close_fds=True, universal_newlines=True,
 
 def call(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
             stderr=subprocess.PIPE, close_fds=True, universal_newlines=True,
@@ -252,18 +283,24 @@ class ConfigWriter:
 
     class TestHandler(ConfigWriter):
         _config_writer_files = ('base.conf', 'custom.conf')
 
     class TestHandler(ConfigWriter):
         _config_writer_files = ('base.conf', 'custom.conf')
-        _config_writer_cfg_dir = '/etc/service'
+        _config_writer_cfg_dir = {
+                                    'base.conf': '/etc/service',
+                                    'custom.conf': '/etc/service/conf.d',
+                                 }
         _config_writer_tpl_dir = 'templates'
 
     The generated configuration files directory defaults to '.' and the
     templates directory to 'templates'. _config_writer_files has no default and
     must be specified in either way. It can be string or a tuple if more than
         _config_writer_tpl_dir = 'templates'
 
     The generated configuration files directory defaults to '.' and the
     templates directory to 'templates'. _config_writer_files has no default and
     must be specified in either way. It can be string or a tuple if more than
-    one configuration file must be generated.
+    one configuration file must be generated. _config_writer_cfg_dir could be a
+    dict mapping which file should be stored in which directory, or a single
+    string if all the config files should go to the same directory.
 
     The template filename and the generated configuration filename are both the
     same (so if you want to generate some /etc/config, you should have some
     templates/config template). That's why _config_writer_cfg_dir and
 
     The template filename and the generated configuration filename are both the
     same (so if you want to generate some /etc/config, you should have some
     templates/config template). That's why _config_writer_cfg_dir and
-    _config_writer_tpl_dir can't be the same.
+    _config_writer_tpl_dir can't be the same. This is not true for very
+    specific cases where _write_single_config() is used.
 
     When you write your Handler, you should call _config_build_templates() in
     you Handler constructor to build the templates.
 
     When you write your Handler, you should call _config_build_templates() in
     you Handler constructor to build the templates.
@@ -319,6 +356,15 @@ class ConfigWriter:
             vars = vars(template_name)
         return self._config_writer_templates[template_name].render(**vars)
 
             vars = vars(template_name)
         return self._config_writer_templates[template_name].render(**vars)
 
+    def _get_config_path(self, template_name, config_filename=None):
+        r"Get a complete configuration path."
+        if not config_filename:
+            config_filename = template_name
+        if isinstance(self._config_writer_cfg_dir, basestring):
+            return path.join(self._config_writer_cfg_dir, config_filename)
+        return path.join(self._config_writer_cfg_dir[template_name],
+                            config_filename)
+
     def _write_single_config(self, template_name, config_filename=None, vars=None):
         r"""_write_single_config(template_name[, config_filename[, vars]]).
 
     def _write_single_config(self, template_name, config_filename=None, vars=None):
         r"""_write_single_config(template_name[, config_filename[, vars]]).
 
@@ -331,8 +377,6 @@ class ConfigWriter:
         variables to replace in the templates, if not, it looks for a
         _get_config_vars() method to get it.
         """
         variables to replace in the templates, if not, it looks for a
         _get_config_vars() method to get it.
         """
-        if not config_filename:
-            config_filename = template_name
         if vars is None:
             if hasattr(self, '_get_config_vars'):
                 vars = self._get_config_vars(template_name)
         if vars is None:
             if hasattr(self, '_get_config_vars'):
                 vars = self._get_config_vars(template_name)
@@ -340,7 +384,7 @@ class ConfigWriter:
                 vars = dict()
         elif callable(vars):
             vars = vars(template_name)
                 vars = dict()
         elif callable(vars):
             vars = vars(template_name)
-        f = file(path.join(self._config_writer_cfg_dir, config_filename), 'w')
+        f = file(self._get_config_path(template_name, config_filename), 'w')
         ctx = Context(f, **vars)
         self._config_writer_templates[template_name].render_context(ctx)
         f.close()
         ctx = Context(f, **vars)
         self._config_writer_templates[template_name].render_context(ctx)
         f.close()
@@ -533,6 +577,176 @@ class ParametersHandler(Handler):
         r"show() -> (key, value) tuples :: List all the parameters."
         return self.params.items()
 
         r"show() -> (key, value) tuples :: List all the parameters."
         return self.params.items()
 
+class SubHandler(Handler):
+    r"""SubHandler(parent) -> SubHandler instance :: Handles subcommands.
+
+    This is a helper class to build sub handlers that needs to reference the
+    parent handler.
+
+    parent - Parent Handler object.
+    """
+
+    def __init__(self, parent):
+        r"Initialize the object, see the class documentation for details."
+        self.parent = parent
+
+class ListSubHandler(SubHandler):
+    r"""ListSubHandler(parent) -> ListSubHandler instance.
+
+    This is a helper class to inherit from to automatically handle subcommands
+    that operates over a list parent attribute.
+
+    The list attribute to handle and the class of objects that it contains can
+    be defined by calling the constructor or in a more declarative way as
+    class attributes, like:
+
+    class TestHandler(ListSubHandler):
+        _list_subhandler_attr = 'some_list'
+        _list_subhandler_class = SomeClass
+
+    This way, the parent's some_list attribute (self.parent.some_list) will be
+    managed automatically, providing the commands: add, update, delete, get,
+    list and show. New items will be instances of SomeClass, which should
+    provide a cmp operator to see if the item is on the list and an update()
+    method, if it should be possible to modify it.
+    """
+
+    def __init__(self, parent, attr=None, cls=None):
+        r"Initialize the object, see the class documentation for details."
+        self.parent = parent
+        if attr is not None:
+            self._list_subhandler_attr = attr
+        if cls is not None:
+            self._list_subhandler_class = cls
+
+    def _list(self):
+        return getattr(self.parent, self._list_subhandler_attr)
+
+    @handler(u'Add a new item')
+    def add(self, *args, **kwargs):
+        r"add(...) -> None :: Add an item to the list."
+        item = self._list_subhandler_class(*args, **kwargs)
+        if item in self._list():
+            raise ItemAlreadyExistsError(item)
+        self._list().append(item)
+
+    @handler(u'Update an item')
+    def update(self, index, *args, **kwargs):
+        r"update(index, ...) -> None :: Update an item of the list."
+        # TODO make it right with metaclasses, so the method is not created
+        # unless the update() method really exists.
+        # TODO check if the modified item is the same of an existing one
+        index = int(index) # TODO validation
+        if not hasattr(self._list_subhandler_class, 'update'):
+            raise CommandNotFoundError(('update',))
+        try:
+            self._list()[index].update(*args, **kwargs)
+        except IndexError:
+            raise ItemNotFoundError(index)
+
+    @handler(u'Delete an item')
+    def delete(self, index):
+        r"delete(index) -> None :: Delete an item of the list."
+        index = int(index) # TODO validation
+        try:
+            return self._list().pop(index)
+        except IndexError:
+            raise ItemNotFoundError(index)
+
+    @handler(u'Get information about an item')
+    def get(self, index):
+        r"get(index) -> Host :: List all the information of an item."
+        index = int(index) # TODO validation
+        try:
+            return self._list()[index]
+        except IndexError:
+            raise ItemNotFoundError(index)
+
+    @handler(u'Get how many items are in the list')
+    def len(self):
+        r"len() -> int :: Get how many items are in the list."
+        return len(self._list())
+
+    @handler(u'Get information about all items')
+    def show(self):
+        r"show() -> list of Hosts :: List all the complete items information."
+        return self._list()
+
+class DictSubHandler(SubHandler):
+    r"""DictSubHandler(parent) -> DictSubHandler instance.
+
+    This is a helper class to inherit from to automatically handle subcommands
+    that operates over a dict parent attribute.
+
+    The dict attribute to handle and the class of objects that it contains can
+    be defined by calling the constructor or in a more declarative way as
+    class attributes, like:
+
+    class TestHandler(DictSubHandler):
+        _dict_subhandler_attr = 'some_dict'
+        _dict_subhandler_class = SomeClass
+
+    This way, the parent's some_dict attribute (self.parent.some_dict) will be
+    managed automatically, providing the commands: add, update, delete, get,
+    list and show. New items will be instances of SomeClass, which should
+    provide a constructor with at least the key value and an update() method,
+    if it should be possible to modify it.
+    """
+
+    def __init__(self, parent, attr=None, cls=None):
+        r"Initialize the object, see the class documentation for details."
+        self.parent = parent
+        if attr is not None:
+            self._dict_subhandler_attr = attr
+        if cls is not None:
+            self._dict_subhandler_class = cls
+
+    def _dict(self):
+        return getattr(self.parent, self._dict_subhandler_attr)
+
+    @handler(u'Add a new item')
+    def add(self, key, *args, **kwargs):
+        r"add(key, ...) -> None :: Add an item to the dict."
+        item = self._dict_subhandler_class(key, *args, **kwargs)
+        if key in self._dict():
+            raise ItemAlreadyExistsError(key)
+        self._dict()[key] = item
+
+    @handler(u'Update an item')
+    def update(self, key, *args, **kwargs):
+        r"update(key, ...) -> None :: Update an item of the dict."
+        # TODO make it right with metaclasses, so the method is not created
+        # unless the update() method really exists.
+        if not hasattr(self._dict_subhandler_class, 'update'):
+            raise CommandNotFoundError(('update',))
+        if not key in self._dict():
+            raise ItemNotFoundError(key)
+        self._dict()[key].update(*args, **kwargs)
+
+    @handler(u'Delete an item')
+    def delete(self, key):
+        r"delete(key) -> None :: Delete an item of the dict."
+        if not key in self._dict():
+            raise ItemNotFoundError(key)
+        del self._dict()[key]
+
+    @handler(u'Get information about an item')
+    def get(self, key):
+        r"get(key) -> Host :: List all the information of an item."
+        if not key in self._dict():
+            raise ItemNotFoundError(key)
+        return self._dict()[key]
+
+    @handler(u'List all the items by key')
+    def list(self):
+        r"list() -> tuple :: List all the item keys."
+        return self._dict().keys()
+
+    @handler(u'Get information about all items')
+    def show(self):
+        r"show() -> list of Hosts :: List all the complete items information."
+        return self._dict().values()
+
 
 if __name__ == '__main__':
 
 
 if __name__ == '__main__':