handlers: Make create_action pass any kwargs to the constructor.
[matthijs/projects/backupninja.git] / src / lib / backupninja / handlers / __init__.py
1 #
2 #    Backupninja python reimplementation, based on original backupninja program
3 #    by riseup.net.
4 #    Copyright (C) 2010  Matthijs Kooijman <matthijs@stdin.nl>
5 #
6 #    This program is free software; you can redistribute it and/or modify
7 #    it under the terms of the GNU General Public License as published by
8 #    the Free Software Foundation; either version 2 of the License, or
9 #    (at your option) any later version.
10 #
11 #    This program is distributed in the hope that it will be useful,
12 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #    GNU General Public License for more details.
15 #
16 #    You should have received a copy of the GNU General Public License along
17 #    with this program; if not, write to the Free Software Foundation, Inc.,
18 #    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 """ Action superclass with common functionality """
21
22 import sys, ConfigParser
23 import logging as log
24
25 from backupninja import config
26
27 def fail_on_exception(f):
28     """
29     This is a decorator meant for methods on the Action class. It
30     catches any exceptions thrown, sets the failed attribute to True and
31     rethrows the exception.
32     """
33     def inner(self, *args, **kwargs):
34         try:
35             f(self, *args, **kwargs)
36         except:
37             self.failed = True
38             raise
39     return inner
40
41 class Action(object):
42     """
43     Subclasses of Action represent handlers for various action types.
44     This class is called Action instead of Handler, since even though the
45     classes could be referred to as handlers, the instances of this
46     class are really actions (i.e., it represents a specific action,
47     which is a combination of a action type and a specific action
48     configuration).
49     """
50     def __init__(self):
51         # Subclasses should overwrite this with their default config
52         # See backupninja.config.load_config for the structure of this
53         # value.
54         self.default_config = {}
55         # Assume we'll run succesfully. If anything fails in the
56         # meanwhile, set this to True.
57         self.failed = False
58         # A logger object for this action. In the future, this might
59         # become a specific logger, that includes the action name and
60         # type.
61         self.log = log
62
63     def run(self, **kwargs):
64         """
65         Run this action for a single target. Override this method
66         in a subclass
67         """
68         pass
69
70     def finish(self, **kwargs):
71         """
72         Called when all targets have been processed. Can be overridden
73         in a subclass.
74         """
75         pass
76
77     def load_config(self, filename):
78         """
79         Load the configuration for this action from the given filename.
80         """
81         self.conf = config.load_config(filename, self.default_config)
82
83     def get_config_optional(self, section, option):
84         """
85         Returns the value of the given option. If the option was not set
86         (and no default was set in self.default_config), return None.
87
88         This is a convenience wrapper for ConfigParser.get(), since that
89         throws an exception on unset options.
90         """
91         try:
92             return self.conf.get(section, option)
93         except ConfigParser.NoOptionError:
94             return None
95
96     def get_config_mandatory(self, section, option):
97         """
98         Returns the value of the given option. If the option was not set
99         (and no default was set in self.default_config), raises a
100         backupninja.config.ConfigError.
101
102         This is a convenience wrapper for ConfigParser.get(), since that
103         has a very generic exception message on unknown options.
104         """
105         try:
106             return self.conf.get(section, option)
107         except ConfigParser.NoOptionError:
108             raise config.ConfigError("Option '%s' in section '%s' is mandatory, please configure it" % (option, section))
109
110 def create_action(ty, **kwargs):
111     """
112     Create a new (subclass of) Action object for an action with the
113     given type. Any extra keyword arguments are passed to the
114     constructor.
115
116     If the handler class for this type cannot be loaded, an exception is
117     thrown.
118     """
119     modname = 'backupninja.handlers.%s' % ty
120     # Load the handler if it is not loaded yet
121     if not modname in sys.modules:
122         log.debug('Loading handler for type "%s"', ty)
123         try:
124             __import__(modname, globals(), locals(), [])
125         except ImportError, e:
126             # Add some extra info, since the default exception does not
127             # show the full module name.
128             raise ImportError('Cannot load module %s: %s' % (modname, e))
129         log.debug('Loaded handler for type "%s" from "%s"', ty, sys.modules[modname].__file__)
130     # Get the module from the module table
131     module = sys.modules[modname]
132
133     # Check that the module has a "handler" top level function, which
134     # should create a new Action object.
135     if not hasattr(module, 'handler'):
136         raise ImportError('%s is not valid: it '
137                           'does not have a "handler" top level function.' 
138                           % (module.__file__))
139
140     # Call the "handler" function to create the actual action
141     action = module.handler(**kwargs)
142    
143     # Check if the handler returned is really a subclass of Action
144     if not isinstance(action, Action):
145         raise TypeError('%s is not valid, %s.handler did not return a '
146                         'subclass of backupninja.handlers.Handler.'
147                         % (module.__file__, modname))
148     return action