]> git.llucax.com Git - software/pymin.git/blob - pymin/services/util.py
Add new service helper classes: SubHandler and DictSubHandler.
[software/pymin.git] / pymin / services / util.py
1 # vim: set encoding=utf-8 et sw=4 sts=4 :
2
3 import subprocess
4 from mako.template import Template
5 from mako.runtime import Context
6 from os import path
7 try:
8     import cPickle as pickle
9 except ImportError:
10     import pickle
11
12 from pymin.dispatcher import Handler, handler, HandlerError
13
14 #DEBUG = False
15 DEBUG = True
16
17 __ALL__ = ('ServiceHandler', 'InitdHandler', 'SubHandler', 'DictSubHandler',
18             'ListSubHandler', 'Persistent', 'ConfigWriter', 'Error',
19             'ReturnNot0Error', 'ExecutionError', 'ItemError',
20             'ItemAlreadyExistsError', 'ItemNotFoundError', 'call')
21
22 class Error(HandlerError):
23     r"""
24     Error(message) -> Error instance :: Base ServiceHandler exception class.
25
26     All exceptions raised by the ServiceHandler inherits from this one, so
27     you can easily catch any ServiceHandler exception.
28
29     message - A descriptive error message.
30     """
31     pass
32
33 class ReturnNot0Error(Error):
34     r"""
35     ReturnNot0Error(return_value) -> ReturnNot0Error instance.
36
37     A command didn't returned the expected 0 return value.
38
39     return_value - Return value returned by the command.
40     """
41
42     def __init__(self, return_value):
43         r"Initialize the object. See class documentation for more info."
44         self.return_value = return_value
45
46     def __unicode__(self):
47         return 'The command returned %d' % self.return_value
48
49 class ExecutionError(Error):
50     r"""
51     ExecutionError(command, error) -> ExecutionError instance.
52
53     Error executing a command.
54
55     command - Command that was tried to execute.
56
57     error - Error received when trying to execute the command.
58     """
59
60     def __init__(self, command, error):
61         r"Initialize the object. See class documentation for more info."
62         self.command = command
63         self.error = error
64
65     def __unicode__(self):
66         command = self.command
67         if not isinstance(self.command, basestring):
68             command = ' '.join(command)
69         return "Can't execute command %s: %s" % (command, self.error)
70
71 class ParameterError(Error, KeyError):
72     r"""
73     ParameterError(paramname) -> ParameterError instance
74
75     This is the base exception for all DhcpHandler parameters related errors.
76     """
77
78     def __init__(self, paramname):
79         r"Initialize the object. See class documentation for more info."
80         self.message = 'Parameter error: "%s"' % paramname
81
82 class ParameterNotFoundError(ParameterError):
83     r"""
84     ParameterNotFoundError(paramname) -> ParameterNotFoundError instance
85
86     This exception is raised when trying to operate on a parameter that doesn't
87     exists.
88     """
89
90     def __init__(self, paramname):
91         r"Initialize the object. See class documentation for more info."
92         self.message = 'Parameter not found: "%s"' % paramname
93
94 class ItemError(Error, KeyError):
95     r"""
96     ItemError(key) -> ItemError instance.
97
98     This is the base exception for all item related errors.
99     """
100
101     def __init__(self, key):
102         r"Initialize the object. See class documentation for more info."
103         self.message = u'Item error: "%s"' % key
104
105 class ItemAlreadyExistsError(ItemError):
106     r"""
107     ItemAlreadyExistsError(key) -> ItemAlreadyExistsError instance.
108
109     This exception is raised when trying to add an item that already exists.
110     """
111
112     def __init__(self, key):
113         r"Initialize the object. See class documentation for more info."
114         self.message = u'Item already exists: "%s"' % key
115
116 class ItemNotFoundError(ItemError):
117     r"""
118     ItemNotFoundError(key) -> ItemNotFoundError instance
119
120     This exception is raised when trying to operate on an item that doesn't
121     exists.
122     """
123
124     def __init__(self, key):
125         r"Initialize the object. See class documentation for more info."
126         self.message = u'Item not found: "%s"' % key
127
128
129 def call(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
130             stderr=subprocess.PIPE, close_fds=True, universal_newlines=True,
131             **kw):
132     if DEBUG:
133         if not isinstance(command, basestring):
134             command = ' '.join(command)
135         print 'Executing command:', command
136         return
137     try:
138         r = subprocess.call(command, stdin=stdin, stdout=stdout, stderr=stderr,
139                                 universal_newlines=universal_newlines,
140                                 close_fds=close_fds, **kw)
141     except Exception, e:
142         raise ExecutionError(command, e)
143     if r is not 0:
144         raise ExecutionError(command, ReturnNot0Error(r))
145
146 class Persistent:
147     r"""Persistent([attrs[, dir[, ext]]]) -> Persistent.
148
149     This is a helper class to inherit from to automatically handle data
150     persistence using pickle.
151
152     The variables attributes to persist (attrs), and the pickle directory (dir)
153     and file extension (ext) can be defined by calling the constructor or in a
154     more declarative way as class attributes, like:
155
156     class TestHandler(Persistent):
157         _persistent_attrs = ('some_attr', 'other_attr')
158         _persistent_dir = 'persistent-data'
159         _persistent_ext = '.pickle'
160
161     The default dir is '.' and the default extension is '.pkl'. There are no
162     default variables, and they should be specified as string if a single
163     attribute should be persistent or as a tuple of strings if they are more.
164     The strings should be the attribute names to be persisted. For each
165     attribute a separated pickle file is generated in the pickle directory.
166
167     You can call _dump() and _load() to write and read the data respectively.
168     """
169     # TODO implement it using metaclasses to add the handlers method by demand
170     # (only for specifieds commands).
171
172     _persistent_attrs = ()
173     _persistent_dir = '.'
174     _persistent_ext = '.pkl'
175
176     def __init__(self, attrs=None, dir=None, ext=None):
177         r"Initialize the object, see the class documentation for details."
178         if attrs is not None:
179             self._persistent_attrs = attrs
180         if dir is not None:
181             self._persistent_dir = dir
182         if ext is not None:
183             self._persistent_ext = ext
184
185     def _dump(self):
186         r"_dump() -> None :: Dump all persistent data to pickle files."
187         if isinstance(self._persistent_attrs, basestring):
188             self._persistent_attrs = (self._persistent_attrs,)
189         for attrname in self._persistent_attrs:
190             self._dump_attr(attrname)
191
192     def _load(self):
193         r"_load() -> None :: Load all persistent data from pickle files."
194         if isinstance(self._persistent_attrs, basestring):
195             self._persistent_attrs = (self._persistent_attrs,)
196         for attrname in self._persistent_attrs:
197             self._load_attr(attrname)
198
199     def _dump_attr(self, attrname):
200         r"_dump_attr() -> None :: Dump a specific variable to a pickle file."
201         f = file(self._pickle_filename(attrname), 'wb')
202         pickle.dump(getattr(self, attrname), f, 2)
203         f.close()
204
205     def _load_attr(self, attrname):
206         r"_load_attr() -> object :: Load a specific pickle file."
207         f = file(self._pickle_filename(attrname))
208         setattr(self, attrname, pickle.load(f))
209         f.close()
210
211     def _pickle_filename(self, name):
212         r"_pickle_filename() -> string :: Construct a pickle filename."
213         return path.join(self._persistent_dir, name) + self._persistent_ext
214
215 class Restorable(Persistent):
216     r"""Restorable([defaults]) -> Restorable.
217
218     This is a helper class to inherit from that provides a nice _restore()
219     method to restore the persistent data if any, or load some nice defaults
220     if not.
221
222     The defaults can be defined by calling the constructor or in a more
223     declarative way as class attributes, like:
224
225     class TestHandler(Restorable):
226         _persistent_attrs = ('some_attr', 'other_attr')
227         _restorable_defaults = dict(
228                 some_attr = 'some_default',
229                 other_attr = 'other_default')
230
231     The defaults is a dictionary, very coupled with the _persistent_attrs
232     attribute inherited from Persistent. The defaults keys should be the
233     values from _persistent_attrs, and the values the default values.
234
235     The _restore() method returns True if the data was restored successfully
236     or False if the defaults were loaded (in case you want to take further
237     actions). If a _write_config method if found, it's executed when a restore
238     fails too.
239     """
240     # TODO implement it using metaclasses to add the handlers method by demand
241     # (only for specifieds commands).
242
243     _restorable_defaults = dict()
244
245     def __init__(self, defaults=None):
246         r"Initialize the object, see the class documentation for details."
247         if defaults is not None:
248             self._restorable_defaults = defaults
249
250     def _restore(self):
251         r"_restore() -> bool :: Restore persistent data or create a default."
252         try:
253             self._load()
254             # TODO tener en cuenta servicios que hay que levantar y los que no
255             if hasattr(self, 'commit'): # TODO deberia ser reload y/o algo para comandos
256                 self.commit()
257             return True
258         except IOError:
259             for (k, v) in self._restorable_defaults.items():
260                 setattr(self, k, v)
261             # TODO tener en cuenta servicios que hay que levantar y los que no
262             if hasattr(self, 'commit'):
263                 self.commit()
264                 return False
265             self._dump()
266             if hasattr(self, '_write_config'):
267                 self._write_config()
268             if hasattr(self, 'reload'):
269                 self.reload()
270             return False
271
272 class ConfigWriter:
273     r"""ConfigWriter([initd_name[, initd_dir]]) -> ConfigWriter.
274
275     This is a helper class to inherit from to automatically handle
276     configuration generation. Mako template system is used for configuration
277     files generation.
278
279     The configuration filenames, the generated configuration files directory
280     and the templates directory can be defined by calling the constructor or
281     in a more declarative way as class attributes, like:
282
283     class TestHandler(ConfigWriter):
284         _config_writer_files = ('base.conf', 'custom.conf')
285         _config_writer_cfg_dir = '/etc/service'
286         _config_writer_tpl_dir = 'templates'
287
288     The generated configuration files directory defaults to '.' and the
289     templates directory to 'templates'. _config_writer_files has no default and
290     must be specified in either way. It can be string or a tuple if more than
291     one configuration file must be generated.
292
293     The template filename and the generated configuration filename are both the
294     same (so if you want to generate some /etc/config, you should have some
295     templates/config template). That's why _config_writer_cfg_dir and
296     _config_writer_tpl_dir can't be the same.
297
298     When you write your Handler, you should call _config_build_templates() in
299     you Handler constructor to build the templates.
300
301     To write the configuration files, you must use the _write_config() method.
302     To know what variables to replace in the template, you have to provide a
303     method called _get_config_vars(tamplate_name), which should return a
304     dictionary of variables to pass to the template system to be replaced in
305     the template for the configuration file 'config_file'.
306     """
307     # TODO implement it using metaclasses to add the handlers method by demand
308     # (only for specifieds commands).
309
310     _config_writer_files = ()
311     _config_writer_cfg_dir = '.'
312     _config_writer_tpl_dir = 'templates'
313
314     def __init__(self, files=None, cfg_dir=None, tpl_dir=None):
315         r"Initialize the object, see the class documentation for details."
316         if files is not None:
317             self._config_writer_files = files
318         if cfg_dir is not None:
319             self._config_writer_cfg_dir = cfg_dir
320         if tpl_dir is not None:
321             self._config_writer_tpl_dir = tpl_dir
322         self._config_build_templates()
323
324     def _config_build_templates(self):
325         r"_config_writer_templates() -> None :: Build the template objects."
326         if isinstance(self._config_writer_files, basestring):
327             self._config_writer_files = (self._config_writer_files,)
328         if not hasattr(self, '_config_writer_templates') \
329                                         or not self._config_writer_templates:
330             self._config_writer_templates = dict()
331             for t in self._config_writer_files:
332                 f = path.join(self._config_writer_tpl_dir, t)
333                 self._config_writer_templates[t] = Template(filename=f)
334
335     def _render_config(self, template_name, vars=None):
336         r"""_render_config(template_name[, config_filename[, vars]]).
337
338         Render a single config file using the template 'template_name'. If
339         vars is specified, it's used as the dictionary with the variables
340         to replace in the templates, if not, it looks for a
341         _get_config_vars() method to get it.
342         """
343         if vars is None:
344             if hasattr(self, '_get_config_vars'):
345                 vars = self._get_config_vars(template_name)
346             else:
347                 vars = dict()
348         elif callable(vars):
349             vars = vars(template_name)
350         return self._config_writer_templates[template_name].render(**vars)
351
352     def _write_single_config(self, template_name, config_filename=None, vars=None):
353         r"""_write_single_config(template_name[, config_filename[, vars]]).
354
355         Write a single config file using the template 'template_name'. If no
356         config_filename is specified, the config filename will be the same as
357         the 'template_name' (but stored in the generated config files
358         directory). If it's specified, the generated config file is stored in
359         the file called 'config_filename' (also in the generated files
360         directory). If vars is specified, it's used as the dictionary with the
361         variables to replace in the templates, if not, it looks for a
362         _get_config_vars() method to get it.
363         """
364         if not config_filename:
365             config_filename = template_name
366         if vars is None:
367             if hasattr(self, '_get_config_vars'):
368                 vars = self._get_config_vars(template_name)
369             else:
370                 vars = dict()
371         elif callable(vars):
372             vars = vars(template_name)
373         f = file(path.join(self._config_writer_cfg_dir, config_filename), 'w')
374         ctx = Context(f, **vars)
375         self._config_writer_templates[template_name].render_context(ctx)
376         f.close()
377
378     def _write_config(self):
379         r"_write_config() -> None :: Generate all the configuration files."
380         for t in self._config_writer_files:
381             self._write_single_config(t)
382
383
384 class ServiceHandler(Handler):
385     r"""ServiceHandler([start[, stop[, restart[, reload]]]]) -> ServiceHandler.
386
387     This is a helper class to inherit from to automatically handle services
388     with start, stop, restart, reload actions.
389
390     The actions can be defined by calling the constructor with all the
391     parameters or in a more declarative way as class attributes, like:
392
393     class TestHandler(ServiceHandler):
394         _service_start = ('command', 'start')
395         _service_stop = ('command', 'stop')
396         _service_restart = ('command', 'restart')
397         _service_reload = 'reload-command'
398
399     Commands are executed without using the shell, that's why they are specified
400     as tuples (where the first element is the command and the others are the
401     command arguments). If only a command is needed (without arguments) a single
402     string can be specified.
403
404     All commands must be specified.
405     """
406     # TODO implement it using metaclasses to add the handlers method by demand
407     # (only for specifieds commands).
408
409     def __init__(self, start=None, stop=None, restart=None, reload=None):
410         r"Initialize the object, see the class documentation for details."
411         for (name, action) in dict(start=start, stop=stop, restart=restart,
412                                                     reload=reload).items():
413             if action is not None:
414                 setattr(self, '_service_%s' % name, action)
415
416     @handler(u'Start the service.')
417     def start(self):
418         r"start() -> None :: Start the service."
419         call(self._service_start)
420
421     @handler(u'Stop the service.')
422     def stop(self):
423         r"stop() -> None :: Stop the service."
424         call(self._service_stop)
425
426     @handler(u'Restart the service.')
427     def restart(self):
428         r"restart() -> None :: Restart the service."
429         call(self._service_restart)
430
431     @handler(u'Reload the service config (without restarting, if possible).')
432     def reload(self):
433         r"reload() -> None :: Reload the configuration of the service."
434         call(self._service_reload)
435
436 class InitdHandler(Handler):
437     r"""InitdHandler([initd_name[, initd_dir]]) -> InitdHandler.
438
439     This is a helper class to inherit from to automatically handle services
440     with start, stop, restart, reload actions using a /etc/init.d like script.
441
442     The name and directory of the script can be defined by calling the
443     constructor or in a more declarative way as class attributes, like:
444
445     class TestHandler(ServiceHandler):
446         _initd_name = 'some-service'
447         _initd_dir = '/usr/local/etc/init.d'
448
449     The default _initd_dir is '/etc/init.d', _initd_name has no default and
450     must be specified in either way.
451
452     Commands are executed without using the shell.
453     """
454     # TODO implement it using metaclasses to add the handlers method by demand
455     # (only for specifieds commands).
456
457     _initd_dir = '/etc/init.d'
458
459     def __init__(self, initd_name=None, initd_dir=None):
460         r"Initialize the object, see the class documentation for details."
461         if initd_name is not None:
462             self._initd_name = initd_name
463         if initd_dir is not None:
464             self._initd_dir = initd_dir
465
466     @handler(u'Start the service.')
467     def start(self):
468         r"start() -> None :: Start the service."
469         call((path.join(self._initd_dir, self._initd_name), 'start'))
470
471     @handler(u'Stop the service.')
472     def stop(self):
473         r"stop() -> None :: Stop the service."
474         call((path.join(self._initd_dir, self._initd_name), 'stop'))
475
476     @handler(u'Restart the service.')
477     def restart(self):
478         r"restart() -> None :: Restart the service."
479         call((path.join(self._initd_dir, self._initd_name), 'restart'))
480
481     @handler(u'Reload the service config (without restarting, if possible).')
482     def reload(self):
483         r"reload() -> None :: Reload the configuration of the service."
484         call((path.join(self._initd_dir, self._initd_name), 'reload'))
485
486 class TransactionalHandler(Handler):
487     r"""Handle command transactions providing a commit and rollback commands.
488
489     This is a helper class to inherit from to automatically handle
490     transactional handlers, which have commit and rollback commands.
491
492     The handler should provide a reload() method (see ServiceHandler and
493     InitdHandler for helper classes to provide this) which will be called
494     when a commit command is issued (if a reload() command is present).
495     The persistent data will be written too (if a _dump() method is provided,
496     see Persistent and Restorable for that), and the configuration files
497     will be generated (if a _write_config method is present, see ConfigWriter).
498     """
499     # TODO implement it using metaclasses to add the handlers method by demand
500     # (only for specifieds commands).
501
502     @handler(u'Commit the changes (reloading the service, if necessary).')
503     def commit(self):
504         r"commit() -> None :: Commit the changes and reload the service."
505         if hasattr(self, '_dump'):
506             self._dump()
507         if hasattr(self, '_write_config'):
508             self._write_config()
509         if hasattr(self, 'reload'):
510             self.reload()
511
512     @handler(u'Discard all the uncommited changes.')
513     def rollback(self):
514         r"rollback() -> None :: Discard the changes not yet commited."
515         if hasattr(self, '_load'):
516             self._load()
517
518 class ParametersHandler(Handler):
519     r"""ParametersHandler([attr]) -> ParametersHandler.
520
521     This is a helper class to inherit from to automatically handle
522     service parameters, providing set, get, list and show commands.
523
524     The attribute that holds the parameters can be defined by calling the
525     constructor or in a more declarative way as class attributes, like:
526
527     class TestHandler(ServiceHandler):
528         _parameters_attr = 'some_attr'
529
530     The default is 'params' and it should be a dictionary.
531     """
532     # TODO implement it using metaclasses to add the handlers method by demand
533     # (only for specifieds commands).
534
535     _parameters_attr = 'params'
536
537     def __init__(self, attr=None):
538         r"Initialize the object, see the class documentation for details."
539         if attr is not None:
540             self._parameters_attr = attr
541
542     @handler(u'Set a service parameter.')
543     def set(self, param, value):
544         r"set(param, value) -> None :: Set a service parameter."
545         if not param in self.params:
546             raise ParameterNotFoundError(param)
547         self.params[param] = value
548
549     @handler(u'Get a service parameter.')
550     def get(self, param):
551         r"get(param) -> None :: Get a service parameter."
552         if not param in self.params:
553             raise ParameterNotFoundError(param)
554         return self.params[param]
555
556     @handler(u'List all available service parameters.')
557     def list(self):
558         r"list() -> tuple :: List all the parameter names."
559         return self.params.keys()
560
561     @handler(u'Get all service parameters, with their values.')
562     def show(self):
563         r"show() -> (key, value) tuples :: List all the parameters."
564         return self.params.items()
565
566 class SubHandler(Handler):
567     r"""SubHandler(parent) -> SubHandler instance :: Handles subcommands.
568
569     This is a helper class to build sub handlers that needs to reference the
570     parent handler.
571
572     parent - Parent Handler object.
573     """
574
575     def __init__(self, parent):
576         r"Initialize the object, see the class documentation for details."
577         self.parent = parent
578
579 class DictSubHandler(SubHandler):
580     r"""DictSubHandler(parent) -> DictSubHandler instance.
581
582     This is a helper class to inherit from to automatically handle subcommands
583     that operates over a dict parent attribute.
584
585     The dict attribute to handle and the class of objects that it contains can
586     be defined by calling the constructor or in a more declarative way as
587     class attributes, like:
588
589     class TestHandler(DictSubHandler):
590         _dict_subhandler_attr = 'some_dict'
591         _dict_subhandler_class = SomeClass
592
593     This way, the parent's some_dict attribute (self.parent.some_dict) will be
594     managed automatically, providing the commands: add, update, delete, get,
595     list and show.
596     """
597
598     def __init__(self, parent, attr=None, key=None, cls=None):
599         r"Initialize the object, see the class documentation for details."
600         self.parent = parent
601         if attr is not None:
602             self._dict_subhandler_attr = attr
603         if key is not None:
604             self._dict_subhandler_key = key
605         if cls is not None:
606             self._dict_subhandler_class = cls
607
608     def _dict(self):
609         return getattr(self.parent, self._dict_subhandler_attr)
610
611     @handler(u'Add a new item')
612     def add(self, key, *args, **kwargs):
613         r"add(key, ...) -> None :: Add an item to the dict."
614         item = self._dict_subhandler_class(key, *args, **kwargs)
615         if key in self._dict():
616             raise ItemAlreadyExistsError(key)
617         self._dict()[key] = item
618
619     @handler(u'Update an item')
620     def update(self, key, *args, **kwargs):
621         r"update(key, ...) -> None :: Update an item of the dict."
622         if not key in self._dict():
623             raise ItemNotFoundError(key)
624         self._dict()[key].update(*args, **kwargs)
625
626     @handler(u'Delete an item')
627     def delete(self, key):
628         r"delete(key) -> None :: Delete an item of the dict."
629         if not key in self._dict():
630             raise ItemNotFoundError(key)
631         del self._dict()[key]
632
633     @handler(u'Get information about an item')
634     def get(self, key):
635         r"get(key) -> Host :: List all the information of an item."
636         if not key in self._dict():
637             raise ItemNotFoundError(key)
638         return self._dict()[key]
639
640     @handler(u'List all the items by key')
641     def list(self):
642         r"list() -> tuple :: List all the item keys."
643         return self._dict().keys()
644
645     @handler(u'Get information about all items')
646     def show(self):
647         r"show() -> list of Hosts :: List all the complete items information."
648         return self._dict().values()
649
650
651
652 if __name__ == '__main__':
653
654     # Execution tests
655     class STestHandler1(ServiceHandler):
656         _service_start = ('service', 'start')
657         _service_stop = ('service', 'stop')
658         _service_restart = ('ls', '/')
659         _service_reload = ('cp', '/la')
660     class STestHandler2(ServiceHandler):
661         def __init__(self):
662             ServiceHandler.__init__(self, 'cmd-start', 'cmd-stop',
663                                         'cmd-restart', 'cmd-reload')
664     class ITestHandler1(InitdHandler):
665         _initd_name = 'test1'
666     class ITestHandler2(InitdHandler):
667         def __init__(self):
668             InitdHandler.__init__(self, 'test2', '/usr/local/etc/init.d')
669     handlers = [
670         STestHandler1(),
671         STestHandler2(),
672         ITestHandler1(),
673         ITestHandler2(),
674     ]
675     for h in handlers:
676         print h.__class__.__name__
677         try:
678             h.start()
679         except ExecutionError, e:
680             print e
681         try:
682             h.stop()
683         except ExecutionError, e:
684             print e
685         try:
686             h.restart()
687         except ExecutionError, e:
688             print e
689         try:
690             h.reload()
691         except ExecutionError, e:
692             print e
693         print
694
695     # Persistent test
696     print 'PTestHandler'
697     class PTestHandler(Persistent):
698         _persistent_attrs = 'vars'
699         def __init__(self):
700             self.vars = dict(a=1, b=2)
701     h = PTestHandler()
702     print h.vars
703     h._dump()
704     h.vars['x'] = 100
705     print h.vars
706     h._load()
707     print h.vars
708     h.vars['x'] = 100
709     h._dump()
710     print h.vars
711     del h.vars['x']
712     print h.vars
713     h._load()
714     print h.vars
715     print
716
717     # Restorable test
718     print 'RTestHandler'
719     class RTestHandler(Restorable):
720         _persistent_attrs = 'vars'
721         _restorable_defaults = dict(vars=dict(a=1, b=2))
722         def __init__(self):
723             self._restore()
724     h = RTestHandler()
725     print h.vars
726     h.vars['x'] = 100
727     h._dump()
728     h = RTestHandler()
729     print h.vars
730     print
731
732     # ConfigWriter test
733     print 'CTestHandler'
734     import os
735     os.mkdir('templates')
736     f = file('templates/config', 'w')
737     f.write('Hello, ${name}! You are ${what}.')
738     f.close()
739     print 'template:'
740     print file('templates/config').read()
741     class CTestHandler(ConfigWriter):
742         _config_writer_files = 'config'
743         def __init__(self):
744             self._config_build_templates()
745         def _get_config_vars(self, config_file):
746             return dict(name='you', what='a parrot')
747     h = CTestHandler()
748     h._write_config()
749     print 'config:'
750     print file('config').read()
751     os.unlink('config')
752     os.unlink('templates/config')
753     os.rmdir('templates')
754     print
755