ser muy simple y genérico y en caso de agregar funcionalidad no sea necesario
modificarlo.
+* Evaluar hacer un RootHandler en vez de un diccionario con los handlers de la
+ raiz para simplificar la introspección y tener un help/commands global.
+
+* Evaluar que el dispatcher vea si se llama a un HandlerContainer y de ser así
+ que tire una ayuda en vez de un CommandNotFound (por ejemplo si se pone:
+ "dhcp" solo que tire una ayuda). Y si hay un subcomando no encontrado, que
+ tire un Command Not Found in handler (por ej "dhcp lala" -> Command "lala" not
+ found in "dhcp").
+
* Agregar logging.
+* Agregar validación.
+
Estas cosas quedan sujetas a necesitada y a definición del protocolo.
Para mí lo ideal es que el protocolo de red sea igual que la consola del
usuario, porque después de todo no va a ser más que eso, mandar comanditos.
"""
pass
-
class CommandNotFoundError(Error):
r"""
CommandNotFoundError(command) -> CommandNotFoundError instance
def __str__(self):
return 'Command not found: "%s"' % ' '.join(self.command)
-def handler(f):
- f._dispatcher_handler = True
- return f
+def handler(help):
+ r"""handler(help) -> function wrapper :: Mark a callable as a handler.
+
+ This is a decorator to mark a callable object as a dispatcher handler.
+
+ help - Help string for the handler.
+ """
+ def wrapper(f):
+ if not help:
+ raise TypeError("'help' should not be empty")
+ f._dispatcher_help = help
+ return f
+ return wrapper
def is_handler(handler):
- return callable(handler) and hasattr(handler, '_dispatcher_handler') \
- and handler._dispatcher_handler
+ r"is_handler(handler) -> bool :: Tell if a object is a handler."
+ return callable(handler) and hasattr(handler, '_dispatcher_help')
+
+def get_help(handler):
+ r"get_help(handler) -> unicode :: Get a handler's help string."
+ if not is_handler(handler):
+ raise TypeError("'%s' should be a handler" % handler.__name__)
+ return handler._dispatcher_help
+
+class Handler:
+ r"""Handler() -> Handler instance :: Base class for all dispatcher handlers.
+
+ All dispatcher handlers should inherit from this class to have some extra
+ commands, like help.
+ """
+
+ @handler(u'List available commands.')
+ def commands(self):
+ r"""commands() -> generator :: List the available commands."""
+ return (a for a in dir(self) if is_handler(getattr(self, a)))
+
+ @handler(u'Show available commands with their help.')
+ def help(self, command=None):
+ r"""help([command]) -> unicode/dict :: Show help on available commands.
+
+ If command is specified, it returns the help of that particular command.
+ If not, it returns a dictionary which keys are the available commands
+ and values are the help strings.
+ """
+ if command is None:
+ return dict((a, get_help(getattr(self, a)))
+ for a in dir(self) if is_handler(getattr(self, a)))
+ if not hasattr(self, command):
+ raise CommandNotFoundError(command)
+ handler = getattr(self, command)
+ if not is_handler(handler):
+ raise CommandNotFoundError(command)
+ return get_help(handler)
class Dispatcher:
r"""Dispatcher([routes]) -> Dispatcher instance :: Command dispatcher
if __name__ == '__main__':
- @handler
+ @handler(u"test: Print all the arguments, return nothing.")
def test_func(*args):
- print 'func:', args
+ print 'func:', args
- class TestClassSubHandler:
- @handler
+ class TestClassSubHandler(Handler):
+ @handler(u"subcmd: Print all the arguments, return nothing.")
def subcmd(self, *args):
print 'class.subclass.subcmd:', args
- class TestClass:
- @handler
+ class TestClass(Handler):
+ @handler(u"cmd1: Print all the arguments, return nothing.")
def cmd1(self, *args):
print 'class.cmd1:', args
- @handler
+ @handler(u"cmd2: Print all the arguments, return nothing.")
def cmd2(self, *args):
print 'class.cmd2:', args
subclass = TestClassSubHandler()
+ test_class = TestClass()
+
d = Dispatcher(dict(
func=test_func,
- inst=TestClass(),
+ inst=test_class,
))
d.dispatch('func arg1 arg2 arg3')
+ print 'inst commands:', tuple(d.dispatch('inst commands'))
+ print 'inst help:', d.dispatch('inst help')
d.dispatch('inst cmd1 arg1 arg2 arg3 arg4')
+ d.dispatch('inst cmd2 arg1 arg2')
+ print 'inst subclass help:', d.dispatch('inst subclass help')
d.dispatch('inst subclass subcmd arg1 arg2 arg3 arg4 arg5')
try:
d.dispatch('')
if __name__ == '__main__':
- from dispatcher import handler
-
- @handler
+ @handler(u"Print all the arguments, return nothing.")
def test_handler(*args):
print 'test:', args
- @handler
+ @handler(u"Echo the message passed as argument.")
def echo_handler(message):
print 'echo:', message
return message
# NOP for testing
class Sequence: pass
try:
- from dispatcher import handler, HandlerError
+ from dispatcher import Handler, handler, HandlerError
except ImportError:
# NOP for testing
class HandlerError(RuntimeError): pass
- def handler(f): return f
+ class Handler: pass
+ def handler(help):
+ def wrapper(f):
+ return f
+ return wrapper
__ALL__ = ('DhcpHandler',)
r"Return a tuple representing the host."
return (self.name, self.ip, self.mac)
-class HostHandler:
+class HostHandler(Handler):
r"""HostHandler(hosts) -> HostHandler instance :: Handle a list of hosts.
This class is a helper for DhcpHandler to do all the work related to hosts
r"Initialize HostHandler object, see class documentation for details."
self.hosts = hosts
- @handler
+ @handler(u'Add a new host.')
def add(self, name, ip, mac):
r"add(name, ip, mac) -> None :: Add a host to the hosts list."
if name in self.hosts:
raise HostAlreadyExistsError(name)
self.hosts[name] = Host(name, ip, mac)
- @handler
+ @handler(u'Update a host.')
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:
if mac is not None:
self.hosts[name].mac = mac
- @handler
+ @handler(u'Delete a host.')
def delete(self, name):
r"delete(name) -> None :: Delete a host of the hosts list."
if not name in self.hosts:
raise HostNotFoundError(name)
del self.hosts[name]
- @handler
+ @handler(u'Get information about a host.')
def get(self, name):
r"""get(name) -> CSV string :: List all the information of a host.
raise HostNotFoundError(name)
return self.hosts[name]
- @handler
+ @handler(u'List hosts.')
def list(self):
r"""list() -> CSV string :: List all the hostnames.
"""
return self.hosts.keys()
- @handler
+ @handler(u'Get information about all hosts.')
def show(self):
r"""show() -> CSV string :: List all the complete hosts information.
"""
return self.hosts.values()
-class DhcpHandler:
+class DhcpHandler(Handler):
r"""DhcpHandler([pickle_dir[, config_dir]]) -> DhcpHandler instance.
Handles DHCP service commands for the dhcpd program.
self._write_config()
self.host = HostHandler(self.hosts)
- @handler
+ @handler(u'Set a DHCP parameter.')
def set(self, param, value):
r"set(param, value) -> None :: Set a DHCP parameter."
if not param in self.vars:
raise ParameterNotFoundError(param)
self.vars[param] = value
- @handler
+ @handler(u'Get a DHCP parameter.')
def get(self, param):
r"get(param) -> None :: Get a DHCP parameter."
if not param in self.vars:
raise ParameterNotFoundError(param)
return self.vars[param]
- @handler
+ @handler(u'List all available DHCP parameters.')
def list(self):
r"""list() -> CSV string :: List all the parameter names.
"""
return self.vars.keys()
- @handler
+ @handler(u'Get all DHCP parameters, with their values.')
def show(self):
r"""show() -> CSV string :: List all the parameters (with their values).
"""
return self.vars.items()
- @handler
+ @handler(u'Start the service.')
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
+ @handler(u'Stop the service.')
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
+ @handler(u'Restart the service.')
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
+ @handler(u'Reload the service config (without restarting, if possible).')
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
+ @handler(u'Commit the changes (reloading the service, if necessary).')
def commit(self):
r"commit() -> None :: Commit the changes and reload the DHCP service."
#esto seria para poner en una interfaz
self._write_config()
self.reload()
- @handler
+ @handler(u'Discard all the uncommited changes.')
def rollback(self):
r"rollback() -> None :: Discard the changes not yet commited."
self._load()