1 # vim: set encoding=utf-8 et sw=4 sts=4 :
4 from mako.template import Template
5 from mako.runtime import Context
8 import cPickle as pickle
12 from pymin.dispatcher import Handler, handler, HandlerError, \
18 __ALL__ = ('ServiceHandler', 'RestartHandler', 'ReloadHandler', 'InitdHandler',
19 'SubHandler', 'DictSubHandler', 'ListSubHandler', 'Persistent',
20 'ConfigWriter', 'Error', 'ReturnNot0Error', 'ExecutionError',
21 'ItemError', 'ItemAlreadyExistsError', 'ItemNotFoundError', 'call')
23 class Error(HandlerError):
25 Error(message) -> Error instance :: Base ServiceHandler exception class.
27 All exceptions raised by the ServiceHandler inherits from this one, so
28 you can easily catch any ServiceHandler exception.
30 message - A descriptive error message.
34 class ReturnNot0Error(Error):
36 ReturnNot0Error(return_value) -> ReturnNot0Error instance.
38 A command didn't returned the expected 0 return value.
40 return_value - Return value returned by the command.
43 def __init__(self, return_value):
44 r"Initialize the object. See class documentation for more info."
45 self.return_value = return_value
47 def __unicode__(self):
48 return 'The command returned %d' % self.return_value
50 class ExecutionError(Error):
52 ExecutionError(command, error) -> ExecutionError instance.
54 Error executing a command.
56 command - Command that was tried to execute.
58 error - Error received when trying to execute the command.
61 def __init__(self, command, error):
62 r"Initialize the object. See class documentation for more info."
63 self.command = command
66 def __unicode__(self):
67 command = self.command
68 if not isinstance(self.command, basestring):
69 command = ' '.join(command)
70 return "Can't execute command %s: %s" % (command, self.error)
72 class ParameterError(Error, KeyError):
74 ParameterError(paramname) -> ParameterError instance
76 This is the base exception for all DhcpHandler parameters related errors.
79 def __init__(self, paramname):
80 r"Initialize the object. See class documentation for more info."
81 self.message = 'Parameter error: "%s"' % paramname
83 class ParameterNotFoundError(ParameterError):
85 ParameterNotFoundError(paramname) -> ParameterNotFoundError instance
87 This exception is raised when trying to operate on a parameter that doesn't
91 def __init__(self, paramname):
92 r"Initialize the object. See class documentation for more info."
93 self.message = 'Parameter not found: "%s"' % paramname
95 class ItemError(Error, KeyError):
97 ItemError(key) -> ItemError instance.
99 This is the base exception for all item related errors.
102 def __init__(self, key):
103 r"Initialize the object. See class documentation for more info."
104 self.message = u'Item error: "%s"' % key
106 class ItemAlreadyExistsError(ItemError):
108 ItemAlreadyExistsError(key) -> ItemAlreadyExistsError instance.
110 This exception is raised when trying to add an item that already exists.
113 def __init__(self, key):
114 r"Initialize the object. See class documentation for more info."
115 self.message = u'Item already exists: "%s"' % key
117 class ItemNotFoundError(ItemError):
119 ItemNotFoundError(key) -> ItemNotFoundError instance.
121 This exception is raised when trying to operate on an item that doesn't
125 def __init__(self, key):
126 r"Initialize the object. See class documentation for more info."
127 self.message = u'Item not found: "%s"' % key
130 def call(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
131 stderr=subprocess.PIPE, close_fds=True, universal_newlines=True,
134 if not isinstance(command, basestring):
135 command = ' '.join(command)
136 print 'Executing command:', command
139 r = subprocess.call(command, stdin=stdin, stdout=stdout, stderr=stderr,
140 universal_newlines=universal_newlines,
141 close_fds=close_fds, **kw)
143 raise ExecutionError(command, e)
145 raise ExecutionError(command, ReturnNot0Error(r))
148 r"""Persistent([attrs[, dir[, ext]]]) -> Persistent.
150 This is a helper class to inherit from to automatically handle data
151 persistence using pickle.
153 The variables attributes to persist (attrs), and the pickle directory (dir)
154 and file extension (ext) can be defined by calling the constructor or in a
155 more declarative way as class attributes, like:
157 class TestHandler(Persistent):
158 _persistent_attrs = ('some_attr', 'other_attr')
159 _persistent_dir = 'persistent-data'
160 _persistent_ext = '.pickle'
162 The default dir is '.' and the default extension is '.pkl'. There are no
163 default variables, and they should be specified as string if a single
164 attribute should be persistent or as a tuple of strings if they are more.
165 The strings should be the attribute names to be persisted. For each
166 attribute a separated pickle file is generated in the pickle directory.
168 You can call _dump() and _load() to write and read the data respectively.
170 # TODO implement it using metaclasses to add the handlers method by demand
171 # (only for specifieds commands).
173 _persistent_attrs = ()
174 _persistent_dir = '.'
175 _persistent_ext = '.pkl'
177 def __init__(self, attrs=None, dir=None, ext=None):
178 r"Initialize the object, see the class documentation for details."
179 if attrs is not None:
180 self._persistent_attrs = attrs
182 self._persistent_dir = dir
184 self._persistent_ext = ext
187 r"_dump() -> None :: Dump all persistent data to pickle files."
188 if isinstance(self._persistent_attrs, basestring):
189 self._persistent_attrs = (self._persistent_attrs,)
190 for attrname in self._persistent_attrs:
191 self._dump_attr(attrname)
194 r"_load() -> None :: Load all persistent data from pickle files."
195 if isinstance(self._persistent_attrs, basestring):
196 self._persistent_attrs = (self._persistent_attrs,)
197 for attrname in self._persistent_attrs:
198 self._load_attr(attrname)
200 def _dump_attr(self, attrname):
201 r"_dump_attr() -> None :: Dump a specific variable to a pickle file."
202 f = file(self._pickle_filename(attrname), 'wb')
203 pickle.dump(getattr(self, attrname), f, 2)
206 def _load_attr(self, attrname):
207 r"_load_attr() -> object :: Load a specific pickle file."
208 f = file(self._pickle_filename(attrname))
209 setattr(self, attrname, pickle.load(f))
212 def _pickle_filename(self, name):
213 r"_pickle_filename() -> string :: Construct a pickle filename."
214 return path.join(self._persistent_dir, name) + self._persistent_ext
216 class Restorable(Persistent):
217 r"""Restorable([defaults]) -> Restorable.
219 This is a helper class to inherit from that provides a nice _restore()
220 method to restore the persistent data if any, or load some nice defaults
223 The defaults can be defined by calling the constructor or in a more
224 declarative way as class attributes, like:
226 class TestHandler(Restorable):
227 _persistent_attrs = ('some_attr', 'other_attr')
228 _restorable_defaults = dict(
229 some_attr = 'some_default',
230 other_attr = 'other_default')
232 The defaults is a dictionary, very coupled with the _persistent_attrs
233 attribute inherited from Persistent. The defaults keys should be the
234 values from _persistent_attrs, and the values the default values.
236 The _restore() method returns True if the data was restored successfully
237 or False if the defaults were loaded (in case you want to take further
238 actions). If a _write_config method if found, it's executed when a restore
241 # TODO implement it using metaclasses to add the handlers method by demand
242 # (only for specifieds commands).
244 _restorable_defaults = dict()
246 def __init__(self, defaults=None):
247 r"Initialize the object, see the class documentation for details."
248 if defaults is not None:
249 self._restorable_defaults = defaults
252 r"_restore() -> bool :: Restore persistent data or create a default."
255 # TODO tener en cuenta servicios que hay que levantar y los que no
256 if hasattr(self, 'commit'): # TODO deberia ser reload y/o algo para comandos
260 for (k, v) in self._restorable_defaults.items():
262 # TODO tener en cuenta servicios que hay que levantar y los que no
263 if hasattr(self, 'commit'):
267 if hasattr(self, '_write_config'):
269 if hasattr(self, 'reload'):
274 r"""ConfigWriter([initd_name[, initd_dir]]) -> ConfigWriter.
276 This is a helper class to inherit from to automatically handle
277 configuration generation. Mako template system is used for configuration
280 The configuration filenames, the generated configuration files directory
281 and the templates directory can be defined by calling the constructor or
282 in a more declarative way as class attributes, like:
284 class TestHandler(ConfigWriter):
285 _config_writer_files = ('base.conf', 'custom.conf')
286 _config_writer_cfg_dir = {
287 'base.conf': '/etc/service',
288 'custom.conf': '/etc/service/conf.d',
290 _config_writer_tpl_dir = 'templates'
292 The generated configuration files directory defaults to '.' and the
293 templates directory to 'templates'. _config_writer_files has no default and
294 must be specified in either way. It can be string or a tuple if more than
295 one configuration file must be generated. _config_writer_cfg_dir could be a
296 dict mapping which file should be stored in which directory, or a single
297 string if all the config files should go to the same directory.
299 The template filename and the generated configuration filename are both the
300 same (so if you want to generate some /etc/config, you should have some
301 templates/config template). That's why _config_writer_cfg_dir and
302 _config_writer_tpl_dir can't be the same. This is not true for very
303 specific cases where _write_single_config() is used.
305 When you write your Handler, you should call _config_build_templates() in
306 you Handler constructor to build the templates.
308 To write the configuration files, you must use the _write_config() method.
309 To know what variables to replace in the template, you have to provide a
310 method called _get_config_vars(tamplate_name), which should return a
311 dictionary of variables to pass to the template system to be replaced in
312 the template for the configuration file 'config_file'.
314 # TODO implement it using metaclasses to add the handlers method by demand
315 # (only for specifieds commands).
317 _config_writer_files = ()
318 _config_writer_cfg_dir = '.'
319 _config_writer_tpl_dir = 'templates'
321 def __init__(self, files=None, cfg_dir=None, tpl_dir=None):
322 r"Initialize the object, see the class documentation for details."
323 if files is not None:
324 self._config_writer_files = files
325 if cfg_dir is not None:
326 self._config_writer_cfg_dir = cfg_dir
327 if tpl_dir is not None:
328 self._config_writer_tpl_dir = tpl_dir
329 self._config_build_templates()
331 def _config_build_templates(self):
332 r"_config_writer_templates() -> None :: Build the template objects."
333 if isinstance(self._config_writer_files, basestring):
334 self._config_writer_files = (self._config_writer_files,)
335 if not hasattr(self, '_config_writer_templates') \
336 or not self._config_writer_templates:
337 self._config_writer_templates = dict()
338 for t in self._config_writer_files:
339 f = path.join(self._config_writer_tpl_dir, t)
340 self._config_writer_templates[t] = Template(filename=f)
342 def _render_config(self, template_name, vars=None):
343 r"""_render_config(template_name[, config_filename[, vars]]).
345 Render a single config file using the template 'template_name'. If
346 vars is specified, it's used as the dictionary with the variables
347 to replace in the templates, if not, it looks for a
348 _get_config_vars() method to get it.
351 if hasattr(self, '_get_config_vars'):
352 vars = self._get_config_vars(template_name)
356 vars = vars(template_name)
357 return self._config_writer_templates[template_name].render(**vars)
359 def _get_config_path(self, template_name, config_filename=None):
360 r"Get a complete configuration path."
361 if not config_filename:
362 config_filename = template_name
363 if isinstance(self._config_writer_cfg_dir, basestring):
364 return path.join(self._config_writer_cfg_dir, config_filename)
365 return path.join(self._config_writer_cfg_dir[template_name],
368 def _write_single_config(self, template_name, config_filename=None, vars=None):
369 r"""_write_single_config(template_name[, config_filename[, vars]]).
371 Write a single config file using the template 'template_name'. If no
372 config_filename is specified, the config filename will be the same as
373 the 'template_name' (but stored in the generated config files
374 directory). If it's specified, the generated config file is stored in
375 the file called 'config_filename' (also in the generated files
376 directory). If vars is specified, it's used as the dictionary with the
377 variables to replace in the templates, if not, it looks for a
378 _get_config_vars() method to get it.
381 if hasattr(self, '_get_config_vars'):
382 vars = self._get_config_vars(template_name)
386 vars = vars(template_name)
387 f = file(self._get_config_path(template_name, config_filename), 'w')
388 ctx = Context(f, **vars)
389 self._config_writer_templates[template_name].render_context(ctx)
392 def _write_config(self):
393 r"_write_config() -> None :: Generate all the configuration files."
394 for t in self._config_writer_files:
395 self._write_single_config(t)
398 class ServiceHandler(Handler):
399 r"""ServiceHandler([start[, stop[, restart[, reload]]]]) -> ServiceHandler.
401 This is a helper class to inherit from to automatically handle services
402 with start, stop, restart, reload actions.
404 The actions can be defined by calling the constructor with all the
405 parameters or in a more declarative way as class attributes, like:
407 class TestHandler(ServiceHandler):
408 _service_start = ('command', 'start')
409 _service_stop = ('command', 'stop')
410 _service_restart = ('command', 'restart')
411 _service_reload = 'reload-command'
413 Commands are executed without using the shell, that's why they are specified
414 as tuples (where the first element is the command and the others are the
415 command arguments). If only a command is needed (without arguments) a single
416 string can be specified.
418 All commands must be specified.
420 # TODO implement it using metaclasses to add the handlers method by demand
421 # (only for specifieds commands).
423 def __init__(self, start=None, stop=None, restart=None, reload=None):
424 r"Initialize the object, see the class documentation for details."
425 for (name, action) in dict(start=start, stop=stop, restart=restart,
426 reload=reload).items():
427 if action is not None:
428 setattr(self, '_service_%s' % name, action)
430 @handler(u'Start the service.')
432 r"start() -> None :: Start the service."
433 call(self._service_start)
435 @handler(u'Stop the service.')
437 r"stop() -> None :: Stop the service."
438 call(self._service_stop)
440 @handler(u'Restart the service.')
442 r"restart() -> None :: Restart the service."
443 call(self._service_restart)
445 @handler(u'Reload the service config (without restarting, if possible).')
447 r"reload() -> None :: Reload the configuration of the service."
448 call(self._service_reload)
450 class RestartHandler(Handler):
451 r"""RestartHandler() -> RestartHandler :: Provides generic restart command.
453 This is a helper class to inherit from to automatically add a restart
454 command that first stop the service and then starts it again (using start
455 and stop commands respectively).
458 @handler(u'Restart the service (alias to stop + start).')
460 r"restart() -> None :: Restart the service calling stop() and start()."
464 class ReloadHandler(Handler):
465 r"""ReloadHandler() -> ReloadHandler :: Provides generic reload command.
467 This is a helper class to inherit from to automatically add a reload
468 command that calls restart.
471 @handler(u'Reload the service config (alias to restart).')
473 r"reload() -> None :: Reload the configuration of the service."
476 class InitdHandler(Handler):
477 r"""InitdHandler([initd_name[, initd_dir]]) -> InitdHandler.
479 This is a helper class to inherit from to automatically handle services
480 with start, stop, restart, reload actions using a /etc/init.d like script.
482 The name and directory of the script can be defined by calling the
483 constructor or in a more declarative way as class attributes, like:
485 class TestHandler(ServiceHandler):
486 _initd_name = 'some-service'
487 _initd_dir = '/usr/local/etc/init.d'
489 The default _initd_dir is '/etc/init.d', _initd_name has no default and
490 must be specified in either way.
492 Commands are executed without using the shell.
494 # TODO implement it using metaclasses to add the handlers method by demand
495 # (only for specifieds commands).
497 _initd_dir = '/etc/init.d'
499 def __init__(self, initd_name=None, initd_dir=None):
500 r"Initialize the object, see the class documentation for details."
501 if initd_name is not None:
502 self._initd_name = initd_name
503 if initd_dir is not None:
504 self._initd_dir = initd_dir
506 @handler(u'Start the service.')
508 r"start() -> None :: Start the service."
509 call((path.join(self._initd_dir, self._initd_name), 'start'))
511 @handler(u'Stop the service.')
513 r"stop() -> None :: Stop the service."
514 call((path.join(self._initd_dir, self._initd_name), 'stop'))
516 @handler(u'Restart the service.')
518 r"restart() -> None :: Restart the service."
519 call((path.join(self._initd_dir, self._initd_name), 'restart'))
521 @handler(u'Reload the service config (without restarting, if possible).')
523 r"reload() -> None :: Reload the configuration of the service."
524 call((path.join(self._initd_dir, self._initd_name), 'reload'))
526 class TransactionalHandler(Handler):
527 r"""Handle command transactions providing a commit and rollback commands.
529 This is a helper class to inherit from to automatically handle
530 transactional handlers, which have commit and rollback commands.
532 The handler should provide a reload() method (see ServiceHandler and
533 InitdHandler for helper classes to provide this) which will be called
534 when a commit command is issued (if a reload() command is present).
535 The persistent data will be written too (if a _dump() method is provided,
536 see Persistent and Restorable for that), and the configuration files
537 will be generated (if a _write_config method is present, see ConfigWriter).
539 # TODO implement it using metaclasses to add the handlers method by demand
540 # (only for specifieds commands).
542 @handler(u'Commit the changes (reloading the service, if necessary).')
544 r"commit() -> None :: Commit the changes and reload the service."
545 if hasattr(self, '_dump'):
547 if hasattr(self, '_write_config'):
549 if hasattr(self, 'reload'):
552 @handler(u'Discard all the uncommited changes.')
554 r"rollback() -> None :: Discard the changes not yet commited."
555 if hasattr(self, '_load'):
558 class ParametersHandler(Handler):
559 r"""ParametersHandler([attr]) -> ParametersHandler.
561 This is a helper class to inherit from to automatically handle
562 service parameters, providing set, get, list and show commands.
564 The attribute that holds the parameters can be defined by calling the
565 constructor or in a more declarative way as class attributes, like:
567 class TestHandler(ServiceHandler):
568 _parameters_attr = 'some_attr'
570 The default is 'params' and it should be a dictionary.
572 # TODO implement it using metaclasses to add the handlers method by demand
573 # (only for specifieds commands).
575 _parameters_attr = 'params'
577 def __init__(self, attr=None):
578 r"Initialize the object, see the class documentation for details."
580 self._parameters_attr = attr
582 @handler(u'Set a service parameter.')
583 def set(self, param, value):
584 r"set(param, value) -> None :: Set a service parameter."
585 if not param in self.params:
586 raise ParameterNotFoundError(param)
587 self.params[param] = value
589 @handler(u'Get a service parameter.')
590 def get(self, param):
591 r"get(param) -> None :: Get a service parameter."
592 if not param in self.params:
593 raise ParameterNotFoundError(param)
594 return self.params[param]
596 @handler(u'List all available service parameters.')
598 r"list() -> tuple :: List all the parameter names."
599 return self.params.keys()
601 @handler(u'Get all service parameters, with their values.')
603 r"show() -> (key, value) tuples :: List all the parameters."
604 return self.params.items()
606 class SubHandler(Handler):
607 r"""SubHandler(parent) -> SubHandler instance :: Handles subcommands.
609 This is a helper class to build sub handlers that needs to reference the
612 parent - Parent Handler object.
615 def __init__(self, parent):
616 r"Initialize the object, see the class documentation for details."
619 class ContainerSubHandler(SubHandler):
620 r"""ContainerSubHandler(parent) -> ContainerSubHandler instance.
622 This is a helper class to implement ListSubHandler and DictSubHandler. You
623 should not use it directly.
625 The container attribute to handle and the class of objects that it
626 contains can be defined by calling the constructor or in a more declarative
627 way as class attributes, like:
629 class TestHandler(ContainerSubHandler):
630 _cont_subhandler_attr = 'some_cont'
631 _cont_subhandler_class = SomeClass
633 This way, the parent's some_cont attribute (self.parent.some_cont)
634 will be managed automatically, providing the commands: add, update,
635 delete, get and show. New items will be instances of SomeClass,
636 which should provide a cmp operator to see if the item is on the
637 container and an update() method, if it should be possible to modify
638 it. If SomeClass has an _add, _update or _delete attribute, it set
639 them to true when the item is added, updated or deleted respectively
640 (in case that it's deleted, it's not removed from the container,
641 but it's not listed either).
644 def __init__(self, parent, attr=None, cls=None):
645 r"Initialize the object, see the class documentation for details."
648 self._cont_subhandler_attr = attr
650 self._cont_subhandler_class = cls
652 def _attr(self, attr=None):
654 return getattr(self.parent, self._cont_subhandler_attr)
655 setattr(self.parent, self._cont_subhandler_attr, attr)
658 if isinstance(self._attr(), dict):
659 return dict([(k, i) for (k, i) in self._attr().items()
660 if not hasattr(i, '_delete') or not i._delete])
661 return [i for i in self._attr()
662 if not hasattr(i, '_delete') or not i._delete]
664 @handler(u'Add a new item')
665 def add(self, *args, **kwargs):
666 r"add(...) -> None :: Add an item to the list."
667 item = self._cont_subhandler_class(*args, **kwargs)
668 if hasattr(item, '_add'):
671 if isinstance(self._attr(), dict):
672 key = item.as_tuple()[0]
673 # do we have the same item? then raise an error
674 if key in self._vattr():
675 raise ItemAlreadyExistsError(item)
676 # do we have the same item, but logically deleted? then update flags
677 if key in self._attr():
679 if not isinstance(self._attr(), dict):
680 index = self._attr().index(item)
681 if hasattr(item, '_add'):
682 self._attr()[index]._add = False
683 if hasattr(item, '_delete'):
684 self._attr()[index]._delete = False
685 else: # it's *really* new
686 if isinstance(self._attr(), dict):
687 self._attr()[key] = item
689 self._attr().append(item)
691 @handler(u'Update an item')
692 def update(self, index, *args, **kwargs):
693 r"update(index, ...) -> None :: Update an item of the container."
694 # TODO make it right with metaclasses, so the method is not created
695 # unless the update() method really exists.
696 # TODO check if the modified item is the same of an existing one
697 if not isinstance(self._attr(), dict):
698 index = int(index) # TODO validation
699 if not hasattr(self._cont_subhandler_class, 'update'):
700 raise CommandNotFoundError(('update',))
702 item = self._vattr()[index]
703 item.update(*args, **kwargs)
704 if hasattr(item, '_update'):
707 raise ItemNotFoundError(index)
709 @handler(u'Delete an item')
710 def delete(self, index):
711 r"delete(index) -> None :: Delete an item of the container."
712 if not isinstance(self._attr(), dict):
713 index = int(index) # TODO validation
715 item = self._vattr()[index]
716 if hasattr(item, '_delete'):
719 del self._attr()[index]
722 raise ItemNotFoundError(index)
724 @handler(u'Get information about an item')
725 def get(self, index):
726 r"get(index) -> item :: List all the information of an item."
727 if not isinstance(self._attr(), dict):
728 index = int(index) # TODO validation
730 return self._vattr()[index]
732 raise ItemNotFoundError(index)
734 @handler(u'Get information about all items')
736 r"show() -> list of items :: List all the complete items information."
737 if isinstance(self._attr(), dict):
738 return self._attr().values()
741 class ListSubHandler(ContainerSubHandler):
742 r"""ListSubHandler(parent) -> ListSubHandler instance.
744 This is a helper class to inherit from to automatically handle subcommands
745 that operates over a list parent attribute.
747 The list attribute to handle and the class of objects that it contains can
748 be defined by calling the constructor or in a more declarative way as
749 class attributes, like:
751 class TestHandler(ListSubHandler):
752 _cont_subhandler_attr = 'some_list'
753 _cont_subhandler_class = SomeClass
755 This way, the parent's some_list attribute (self.parent.some_list) will be
756 managed automatically, providing the commands: add, update, delete, get,
757 list and show. New items will be instances of SomeClass, which should
758 provide a cmp operator to see if the item is on the list and an update()
759 method, if it should be possible to modify it. If SomeClass has an _add,
760 _update or _delete attribute, it set them to true when the item is added,
761 updated or deleted respectively (in case that it's deleted, it's not
762 removed from the list, but it's not listed either).
765 @handler(u'Get how many items are in the list')
767 r"len() -> int :: Get how many items are in the list."
768 return len(self._vattr())
770 class DictSubHandler(ContainerSubHandler):
771 r"""DictSubHandler(parent) -> DictSubHandler instance.
773 This is a helper class to inherit from to automatically handle subcommands
774 that operates over a dict parent attribute.
776 The dict attribute to handle and the class of objects that it contains can
777 be defined by calling the constructor or in a more declarative way as
778 class attributes, like:
780 class TestHandler(DictSubHandler):
781 _cont_subhandler_attr = 'some_dict'
782 _cont_subhandler_class = SomeClass
784 This way, the parent's some_dict attribute (self.parent.some_dict) will be
785 managed automatically, providing the commands: add, update, delete, get,
786 list and show. New items will be instances of SomeClass, which should
787 provide a constructor with at least the key value, an as_tuple() method
788 and an update() method, if it should be possible to modify
789 it. If SomeClass has an _add, _update or _delete attribute, it set
790 them to true when the item is added, updated or deleted respectively
791 (in case that it's deleted, it's not removed from the dict, but it's
795 @handler(u'List all the items by key')
797 r"list() -> tuple :: List all the item keys."
798 return self._attr().keys()
801 if __name__ == '__main__':
804 class STestHandler1(ServiceHandler):
805 _service_start = ('service', 'start')
806 _service_stop = ('service', 'stop')
807 _service_restart = ('ls', '/')
808 _service_reload = ('cp', '/la')
809 class STestHandler2(ServiceHandler):
811 ServiceHandler.__init__(self, 'cmd-start', 'cmd-stop',
812 'cmd-restart', 'cmd-reload')
813 class ITestHandler1(InitdHandler):
814 _initd_name = 'test1'
815 class ITestHandler2(InitdHandler):
817 InitdHandler.__init__(self, 'test2', '/usr/local/etc/init.d')
825 print h.__class__.__name__
828 except ExecutionError, e:
832 except ExecutionError, e:
836 except ExecutionError, e:
840 except ExecutionError, e:
846 class PTestHandler(Persistent):
847 _persistent_attrs = 'vars'
849 self.vars = dict(a=1, b=2)
868 class RTestHandler(Restorable):
869 _persistent_attrs = 'vars'
870 _restorable_defaults = dict(vars=dict(a=1, b=2))
884 os.mkdir('templates')
885 f = file('templates/config', 'w')
886 f.write('Hello, ${name}! You are ${what}.')
889 print file('templates/config').read()
890 class CTestHandler(ConfigWriter):
891 _config_writer_files = 'config'
893 self._config_build_templates()
894 def _get_config_vars(self, config_file):
895 return dict(name='you', what='a parrot')
899 print file('config').read()
901 os.unlink('templates/config')
902 os.rmdir('templates')