Para hacer una prueba simple del dispatcher: python dispatcher.py
-http://www.cisco.com/en/US/products/sw/iosswrel/ps5187/prod_command_reference_list.html
\ No newline at end of file
+http://www.cisco.com/en/US/products/sw/iosswrel/ps5187/prod_command_reference_list.html
--- /dev/null
+
+Ideas / TODO:
+
+* Soportar comillas para argumentos con espacios y otros caracteres, onda:
+ 'misc set motd "Hola!\nEste es el servidor de garombia"'
+
+* Soportar keyword arguments, onda que:
+ 'dns set pepe=10.10.10.1 juan=10.10.10.2'
+ se mapee a algo como: dns.set(pepe='10.10.10.1', juan='10.10.10.2')
+
+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.
+
+Por otro lado, el cliente de consola, por que no es el cliente web pero
+accedido via ssh usando un navegador de texto como w3m???
+
--- /dev/null
+# vim: set et sts=4 sw=4 encoding=utf-8 :
+
+# XXX for testing only
+def test_func(*args):
+ print 'func:', args
+
+routes = dict \
+(
+ test = test_func,
+)
+
+bind_addr = \
+(
+ '', # Bind IP ('' is ANY)
+ 9999, # Port
+)
+
# vim: set et sts=4 sw=4 encoding=utf-8 :
-class BadRouteError(Exception):
+r"""
+Command dispatcher.
- def __init__(self,cmd):
- self.cmd = cmd
+This module provides a convenient and extensible command dispatching mechanism.
+It's based on Zope or Cherrypy dispatching (but implemented from the scratch)
+and translates commands to functions/objects/methods.
+"""
- def __str__(self):
- return repr(cmd)
+class Error(RuntimeError):
+ r"""
+ Error(command) -> Error instance :: Base dispatching exceptions class.
-class CommandNotFoundError(Exception):
+ All exceptions raised by the Dispatcher inherits from this one, so you can
+ easily catch any dispatching exception.
- def __init__(self,cmd):
- self.cmd = cmd
+ command - is the command that raised the exception.
+ """
- def __str__(self):
- return repr(cmd)
+ def __init__(self, command):
+ r"""Initialize the Error object.
-class Dispatcher:
-
- def __init__(self, routes=dict()):
- self.routes = routes
-
- def dispatch(self, route):
- route = route.split() # TODO considerar comillas
- try:
- handler = self.routes[route[0]]
- route = route[1:]
- while not callable(handler):
- handler = getattr(handler, route[0])
- route = route[1:]
- handler(*route)
-
- except KeyError:
- raise CommandNotFoundError(route[0])
- except AttributeError:
- raise BadRouteError(route[0])
- except IndexError:
- pass
-
-
-def test_func(*args):
- print 'func:', args
+ See Error class documentation for more info.
+ """
+ self.command = command
+ def __str__(self):
+ return repr(self.command)
-class TestClassSubHandler:
+class CommandNotFoundError(Error):
+ r"""
+ CommandNotFoundError(command) -> CommandNotFoundError instance
- def subcmd(self, *args):
- print 'class.subclass.subcmd:', args
+ This exception is raised when the command received can't be dispatched
+ because there is no handlers to process it.
+ """
+ pass
+class Dispatcher:
+ r"""Dispatcher([routes]) -> Dispatcher instance :: Command dispatcher
+
+ This class provides a modular and extensible dispatching mechanism. You
+ can specify root 'routes' (as a dict where the key is the string of the
+ root command and the value is a callable object to handle that command,
+ or a subcommand if the callable is an instance and the command can be
+ sub-routed).
+
+ The command can have arguments, separated by (any number of) spaces.
+
+ The dispatcher tries to route the command as deeply as it can, passing
+ the other "path" components as arguments to the callable. To route the
+ command it inspects the callable attributes to find a suitable callable
+ attribute to handle the command in a more specific way, and so on.
+
+ Example:
+ >>> d = Dispatcher(dict(handler=some_handler))
+ >>> d.dispatch('handler attribute method arg1 arg2')
+
+ If 'some_handler' is an object with an 'attribute' that is another
+ object which has a method named 'method', then
+ some_handler.attribute.method('arg1', 'arg2') will be called. If
+ some_handler is a function, then some_handler('attribute', 'method',
+ 'arg1', 'arg2') will be called. The handler "tree" can be as complex
+ and deep as you want.
+
+ If some command can't be dispatched (because there is no root handler or
+ there is no matching callable attribute), a CommandNotFoundError is raised.
+ """
+
+ def __init__(self, routes=dict()):
+ r"""Initialize the Dispatcher object.
+
+ See Dispatcher class documentation for more info.
+ """
+ self.routes = routes
+
+ def dispatch(self, route):
+ r"""dispatch(route) -> None :: Dispatch a command string.
+
+ This method searches for a suitable callable object in the routes
+ "tree" and call it, or raises a CommandNotFoundError if the command
+ can't be dispatched.
+ """
+ route = route.split() # TODO support "" and keyword arguments
+ if not route:
+ raise CommandNotFoundError('') # TODO better error reporting
+ handler = self.routes.get(route[0], None)
+ route = route[1:]
+ while not callable(handler):
+ if not route:
+ raise CommandNotFoundError('XXX') # TODO better error reporting
+ if not hasattr(handler, route[0]):
+ raise CommandNotFoundError(route[0]) # TODO better error rep.
+ handler = getattr(handler, route[0])
+ route = route[1:]
+ handler(*route)
-class TestClass:
- def cmd1(self, *args):
- print 'class.cmd1:', args
+if __name__ == '__main__':
- def cmd2(self, *args):
- print 'class.cmd2:', args
+ def test_func(*args):
+ print 'func:', args
- subclass = TestClassSubHandler()
+ class TestClassSubHandler:
+ def subcmd(self, *args):
+ print 'class.subclass.subcmd:', args
-
-if __name__ == '__main__':
+ class TestClass:
+ def cmd1(self, *args):
+ print 'class.cmd1:', args
+ def cmd2(self, *args):
+ print 'class.cmd2:', args
+ subclass = TestClassSubHandler()
d = Dispatcher(dict(
func=test_func,
d.dispatch('func arg1 arg2 arg3')
d.dispatch('inst cmd1 arg1 arg2 arg3 arg4')
d.dispatch('inst subclass subcmd arg1 arg2 arg3 arg4 arg5')
-
-# Ideas / TODO:
-#
-# * Soportar comillas para argumentos con espacios y otros caracteres, onda:
-# 'misc set motd "Hola!\nEste es el servidor de garombia"'
-#
-# * Soportar keyword arguments, onda que:
-# 'dns set pepe=10.10.10.1 juan=10.10.10.2'
-# se mapee a algo como: dns.set(pepe='10.10.10.1', juan='10.10.10.2')
-#
-# 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.
-#
-# Por otro lado, el cliente de consola, por que no es el cliente web pero
-# accedido via ssh usando un navegador de texto como w3m???
+ try:
+ d.dispatch('')
+ except CommandNotFoundError, e:
+ print 'Not found:', e
+ try:
+ d.dispatch('sucutrule')
+ except CommandNotFoundError, e:
+ print 'Not found:', e
+ try:
+ d.dispatch('inst cmd3')
+ except CommandNotFoundError, e:
+ print 'Not found:', e
--- /dev/null
+# vim: set encoding=utf-8 et sw=4 sts=4 :
+
+r"""
+A simple event loop.
+
+Please see EventLoop class documentation for more info.
+"""
+
+from select import poll, POLLIN, POLLPRI, POLLERR
+
+__ALL__ = ('EventLoop')
+
+class EventLoop:
+ r"""EventLoop(file[, handler]) -> EventLoop instance
+
+ This class implements a simple event loop based on select module.
+ It "listens" to activity a single 'file' object (a file, a pipe,
+ a socket, or even a simple file descriptor) and calls a 'handler'
+ function (or the handle() method if you prefer subclassing) every
+ time the file is ready for reading (or has an error).
+
+ This is a really simple example of usage using a hanlder callable:
+
+ >>> import os
+ >>> def handle(event_loop):
+ data = os.read(event_loop.fileno, 100)
+ os.write(1, 'Received message: %r\n' % data)
+ >>> p = EventLoop(0, handle)
+ >>> p.loop(once=True)
+
+ In this example only one event is handled (see the 'once' argument
+ of loop).
+
+ A more complex example, making a subclass and explicitly stopping
+ the loop, looks something like this:
+
+ >>> class Test(EventLoop):
+ >>> def handle(self):
+ >>> data = os.read(self.fileno, 100)
+ >>> if data == 'q\n':
+ >>> self.stop()
+ >>> else:
+ >>> os.write(1, 'Received message: %r\n' % data)
+ >>> p = Test(0)
+ >>> p.loop()
+
+ This example loops until the user enters a single "q", when stop()
+ is called and the event loop is exited.
+ """
+
+ def __init__(self, file, handler=None):
+ r"""Initialize the EventLoop object.
+
+ See EventLoop class documentation for more info.
+ """
+ self.poll = poll()
+ self._stop = False
+ self.__register(file)
+ self.handler = handler
+
+ def __register(self, file):
+ r"__register(file) -> None :: Register a new file for polling."
+ self._file = file
+ self.poll.register(self.fileno, POLLIN | POLLPRI | POLLERR)
+
+ def set_file(self, file):
+ r"""set_file(file) -> None :: New file object to be monitored
+
+ Unregister the previous file object being monitored and register
+ a new one.
+ """
+ self.poll.unregister(self.fileno)
+ self.__register(file)
+
+ def get_file(self):
+ r"get_file() -> file object/int :: Get the current file object/fd."
+ return self._file
+
+ file = property(get_file, set_file, doc='File object (or descriptor)')
+
+ def get_fileno(self):
+ r"get_fileno() -> int :: Get the current file descriptor"
+ if hasattr(self.file, 'fileno'):
+ return self.file.fileno()
+ return self.file
+
+ fileno = property(get_fileno, doc='File descriptor (never a file object)')
+
+ def stop(self):
+ r"""stop() -> None :: Stop the event loop.
+
+ The event loop will be interrupted as soon as the current handler
+ finishes.
+ """
+ self._stop = True
+
+ def loop(self, once=False):
+ r"""loop([once]) -> None :: Wait for events.
+
+ Wait for events and handle then when they arrive. If once is True,
+ then only 1 event is processed and then this method returns.
+ """
+ while True:
+ res = self.poll.poll()
+ if self.handler is not None:
+ self.handler(self)
+ else:
+ self.handle()
+ if self._stop or once:
+ self._stop = False
+ break
+
+ def handle(self):
+ r"handle() -> None :: Abstract method to be overriden to handle events."
+ raise NotImplementedError
+
+if __name__ == '__main__':
+
+ import os
+
+ def handle(event_loop):
+ data = os.read(event_loop.fileno, 100)
+ os.write(1, 'Received message: %r\n' % data)
+
+ p = EventLoop(0, handle)
+
+ os.write(1, 'Say something once: ')
+ p.loop(once=True)
+ os.write(1, 'Great!\n')
+
+ class Test(EventLoop):
+ def handle(self):
+ data = os.read(self.fileno, 100)
+ if data == 'q\n':
+ self.stop()
+ else:
+ os.write(1, 'Received message: %r\n' % data)
+
+ p = Test(0)
+
+ os.write(1, 'Say a lot of things, then press write just "q" to stop: ')
+ p.loop()
+ os.write(1, 'Ok, bye!\n')
+
+++ /dev/null
-# vim: set encoding=utf-8 et sw=4 sts=4 :
-
-import signal
-import select
-from sys import exit
-
-import dispatcher as dis
-import udp_server as us
-
-def quit(signum, frame):
- print "Shuting down ..."
- exit(0)
-
-signal.signal(signal.SIGINT, quit)
-signal.signal(signal.SIGTERM, quit)
-
-server = us.UDPServer(9999)
-
-poll = select.poll()
-poll.register(server.sock.fileno(), select.POLLIN | select.POLLPRI)
-
-d = dis.Dispatcher(dict(
- func=dis.test_func,
- inst=dis.TestClass()
- ))
-
-def handle_recv(sock):
- (msg, addr) = sock.recvfrom(65535)
- try:
- d.dispatch(msg)
- except dis.BadRouteError, inst:
- sock.sendto('Bad route from : ' + inst.cmd + '\n', addr)
- except dis.CommandNotFoundError, inst:
- sock.sendto('Command not found : ' + inst.cmd + '\n', addr)
-
-while True:
- l = poll.poll()
- handle_recv(server.sock)
-
--- /dev/null
+#!/usr/bin/env python
+# vim: set encoding=utf-8 et sw=4 sts=4 :
+
+from pymindaemon import PyminDaemon
+import config
+
+PyminDaemon(config.bind_addr, config.routes).run()
+
--- /dev/null
+# vim: set encoding=utf-8 et sw=4 sts=4 :
+
+r"""
+Python Administration Daemon.
+
+Python Administration Daemon is an modular, extensible administration tool
+to administrate a set of services remotely (or localy) throw a simple
+command-line.
+"""
+
+import signal
+import socket
+from dispatcher import Dispatcher
+from eventloop import EventLoop
+
+class PyminDaemon(EventLoop):
+ r"""PyminDaemon(bind_addr, routes) -> PyminDaemon instance
+
+ This class is well suited to run as a single process. It handles
+ signals for controlled termination (SIGINT and SIGTERM), as well as
+ a user signal to reload the configuration files (SIGUSR1).
+
+ bind_addr - is a tuple of (ip, port) where to bind the UDP socket to.
+
+ routes - is a dictionary where the key is a command string and the value
+ is the command handler. This is passed directly to the Dispatcher.
+
+ Here is a simple usage example:
+
+ >>> def test_handler(*args): print 'test:', args
+ >>> PyminDaemon(('', 9999), dict(test=test_handler)).run()
+ """
+
+ def __init__(self, bind_addr, routes):
+ r"""Initialize the PyminDaemon object.
+
+ See PyminDaemon class documentation for more info.
+ """
+ # Create and bind socket
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.bind(bind_addr)
+ # Create EventLoop
+ EventLoop.__init__(self, sock)
+ # Create Dispatcher
+ self.dispatcher = Dispatcher(routes)
+ # Signal handling
+ def quit(signum, frame):
+ print "Shuting down ..."
+ loop.stop() # tell main event loop to stop
+ def reload_config(signum, frame):
+ print "Reloading configuration..."
+ # TODO iterate handlers list propagating reload action
+ signal.signal(signal.SIGINT, quit)
+ signal.signal(signal.SIGTERM, quit)
+ signal.signal(signal.SIGUSR1, reload_config)
+
+ def handle(self):
+ r"handle() -> None :: Handle incoming events using the dispatcher."
+ (msg, addr) = self.file.recvfrom(65535)
+ self.dispatcher.dispatch(msg)
+ #try:
+ # d.dispatch(msg)
+ #except dis.BadRouteError, inst:
+ # sock.sendto('Bad route from : ' + inst.cmd + '\n', addr)
+ #except dis.CommandNotFoundError, inst:
+ # sock.sendto('Command not found : ' + inst.cmd + '\n', addr)
+
+ def run(self):
+ r"run() -> None :: Run the event loop (shortcut to loop())"
+ return self.loop()
+
+if __name__ == '__main__':
+
+ def test_handler(*args):
+ print 'test:', args
+
+ PyminDaemon(('', 9999), dict(test=test_handler)).run()
+
+++ /dev/null
-# vim: set encoding=utf-8 et sw=4 sts=4 :
-
-import socket as s
-
-class UDPServer:
- "Udp server class"
-
- def __init__(self, port):
- self.sock = s.socket(s.AF_INET, s.SOCK_DGRAM)
- self.sock.setsockopt(s.SOL_SOCKET, s.SO_REUSEADDR, 1)
- self.sock.bind(('', port))
\ No newline at end of file