From d46203541c7463746edf19344d3ddbd1fb412128 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Mon, 24 Sep 2007 14:09:37 -0300 Subject: [PATCH] Add a decorator to mark which callables are exported by the dispatcher. All callables which should handle a command should be maked with the @handler decorator. This is for the sake of security, so users can't call arbitrary python code (like a constructor). --- config.py | 2 ++ dispatcher.py | 36 +++++++++++++++++++++++++++--------- services/dhcp/__init__.py | 18 ++++++++++++++++++ 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/config.py b/config.py index f433170..2addc68 100644 --- a/config.py +++ b/config.py @@ -1,8 +1,10 @@ # vim: set et sts=4 sw=4 encoding=utf-8 : from services import * +from dispatcher import handler # XXX for testing only +@handler def test_func(*args): print 'func:', args diff --git a/dispatcher.py b/dispatcher.py index 2f545cd..addd45f 100644 --- a/dispatcher.py +++ b/dispatcher.py @@ -15,7 +15,8 @@ class Error(RuntimeError): All exceptions raised by the Dispatcher inherits from this one, so you can easily catch any dispatching exception. - command - is the command that raised the exception. + command - is the command that raised the exception, expressed as a list of + paths (or subcommands). """ def __init__(self, command): @@ -26,7 +27,7 @@ class Error(RuntimeError): self.command = command def __str__(self): - return repr(self.command) + return ' '.join(self.command) class CommandNotFoundError(Error): r""" @@ -37,6 +38,14 @@ class CommandNotFoundError(Error): """ pass +def handler(f): + f._dispatcher_handler = True + return f + +def is_handler(handler): + return callable(handler) and hasattr(handler, '_dispatcher_handler') \ + and handler._dispatcher_handler + class Dispatcher: r"""Dispatcher([routes]) -> Dispatcher instance :: Command dispatcher @@ -82,16 +91,21 @@ class Dispatcher: "tree" and call it, or raises a CommandNotFoundError if the command can't be dispatched. """ + command = list() route = route.split() # TODO support "" and keyword arguments if not route: - raise CommandNotFoundError('') # TODO better error reporting + raise CommandNotFoundError(command) + command.append(route[0]) handler = self.routes.get(route[0], None) + if handler is None: + raise CommandNotFoundError(command) route = route[1:] - while not callable(handler): - if not route: - raise CommandNotFoundError('XXX') # TODO better error reporting + while not is_handler(handler): + if len(route) is 0: + raise CommandNotFoundError(command) + command.append(route[0]) if not hasattr(handler, route[0]): - raise CommandNotFoundError(route[0]) # TODO better error rep. + raise CommandNotFoundError(command) handler = getattr(handler, route[0]) route = route[1:] handler(*route) @@ -99,16 +113,20 @@ class Dispatcher: if __name__ == '__main__': + @handler def test_func(*args): print 'func:', args class TestClassSubHandler: + @handler def subcmd(self, *args): print 'class.subclass.subcmd:', args class TestClass: + @handler def cmd1(self, *args): print 'class.cmd1:', args + @handler def cmd2(self, *args): print 'class.cmd2:', args subclass = TestClassSubHandler() @@ -126,11 +144,11 @@ if __name__ == '__main__': except CommandNotFoundError, e: print 'Not found:', e try: - d.dispatch('sucutrule') + d.dispatch('sucutrule piquete culete') except CommandNotFoundError, e: print 'Not found:', e try: - d.dispatch('inst cmd3') + d.dispatch('inst cmd3 arg1 arg2 arg3') except CommandNotFoundError, e: print 'Not found:', e diff --git a/services/dhcp/__init__.py b/services/dhcp/__init__.py index 46b7d81..3d956f9 100644 --- a/services/dhcp/__init__.py +++ b/services/dhcp/__init__.py @@ -7,6 +7,10 @@ try: import cPickle as pickle except ImportError: import pickle +try: + from dispatcher import handler +except ImportError: + def handler(f): return f # NOP for testing __ALL__ = ('DhcpHandler',) @@ -45,6 +49,7 @@ class HostHandler: r"Initialize HostHandler object, see class documentation for details." self.hosts = hosts + @handler def add(self, name, ip, mac): r"add(name, ip, mac) -> None :: Add a host to the hosts list." # XXX deberia indexar por hostname o por ip? o por mac? :) @@ -52,6 +57,7 @@ class HostHandler: # nombres? Una MAC con muchas IP? una MAC con muchos nombre? Etc... self.hosts[name] = Host(name, ip, mac) + @handler def update(self, name, ip=None, mac=None): r"update(name[, ip[, mac]]) -> None :: Update a host of the hosts list." if not name in self.hosts: @@ -61,12 +67,14 @@ class HostHandler: if mac is not None: self.hosts[name].mac = mac + @handler def delete(self, name): r"delete(name) -> None :: Delete a host of the hosts list." if not name in self.hosts: raise KeyError('Host not found') del self.hosts[name] + @handler def list(self): r"""list() -> CSV string :: List all the hostnames. @@ -74,6 +82,7 @@ class HostHandler: """ return ','.join(self.hosts) + @handler def show(self): r"""show() -> CSV string :: List all the complete hosts information. @@ -121,12 +130,14 @@ class DhcpHandler: self._write_config() self.host = HostHandler(self.hosts) + @handler def set(self, param, value): r"set(param, value) -> None :: Set a DHCP parameter." if not param in self.vars: raise KeyError('Parameter ' + param + ' not found') self.vars[param] = value + @handler def list(self): r"""list() -> CSV string :: List all the parameter names. @@ -134,6 +145,7 @@ class DhcpHandler: """ return ','.join(self.vars) + @handler def show(self): r"""show() -> CSV string :: List all the parameters (with their values). @@ -143,30 +155,35 @@ class DhcpHandler: """ return '\n'.join(('%s,%s' % (k, v) for (k, v) in self.vars.items())) + @handler def start(self): r"start() -> None :: Start the DHCP service." #esto seria para poner en una interfaz #y seria el hook para arrancar el servicio pass + @handler def stop(self): r"stop() -> None :: Stop the DHCP service." #esto seria para poner en una interfaz #y seria el hook para arrancar el servicio pass + @handler def restart(self): r"restart() -> None :: Restart the DHCP service." #esto seria para poner en una interfaz #y seria el hook para arrancar el servicio pass + @handler def reload(self): r"reload() -> None :: Reload the configuration of the DHCP service." #esto seria para poner en una interfaz #y seria el hook para arrancar el servicio pass + @handler def commit(self): r"commit() -> None :: Commit the changes and reload the DHCP service." #esto seria para poner en una interfaz @@ -176,6 +193,7 @@ class DhcpHandler: self._write_config() self.reload() + @handler def rollback(self): r"rollback() -> None :: Discard the changes not yet commited." self._load() -- 2.43.0