]> git.llucax.com Git - software/pymin.git/commitdiff
Big repository (and source) rebump.
authorLeandro Lucarella <llucax@gmail.com>
Sat, 22 Sep 2007 04:50:53 +0000 (01:50 -0300)
committerLeandro Lucarella <llucax@gmail.com>
Sat, 22 Sep 2007 04:50:53 +0000 (01:50 -0300)
A lot was changed, is allmost a new repo import =P

14 files changed:
.gitignore [new file with mode: 0644]
README
TODO [new file with mode: 0644]
config.py [new file with mode: 0644]
dispatcher.py
doc/config/dhcp/dhcpd.conf [moved from config/dhcp/dhcpd.conf with 100% similarity]
doc/config/dns/%%(zone1).zone [moved from config/dns/%%(zone1).zone with 100% similarity]
doc/config/dns/named.conf [moved from config/dns/named.conf with 100% similarity]
eventloop.py [new file with mode: 0644]
pollserver.py [deleted file]
pymin [new file with mode: 0755]
pymindaemon.py [new file with mode: 0644]
services/__init__.py [new file with mode: 0644]
udp_server.py [deleted file]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..0d20b64
--- /dev/null
@@ -0,0 +1 @@
+*.pyc
diff --git a/README b/README
index 24abc5d4bfbefe1d46eaf2bb9a28f0f6599bf423..1912cbbf5ca5b2d72ba4b5e02f50f4dc4af3ce36 100644 (file)
--- a/README
+++ b/README
@@ -18,4 +18,4 @@ de errores, pero es fácil ponerlo.
 
 Para hacer una prueba simple del dispatcher: python dispatcher.py
 
 
 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
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..c7427e3
--- /dev/null
+++ b/TODO
@@ -0,0 +1,17 @@
+
+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???
+
diff --git a/config.py b/config.py
new file mode 100644 (file)
index 0000000..93a6b7b
--- /dev/null
+++ b/config.py
@@ -0,0 +1,17 @@
+# 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
+)
+
index 78ae2460d4c7b1c6e2074a26e3148ba7d864f864..2f545cd6e91fb7351c65b223092b253863b5a0a6 100644 (file)
 # vim: set et sts=4 sw=4 encoding=utf-8 :
 
 # 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 = Dispatcher(dict(
             func=test_func,
@@ -70,20 +121,16 @@ if __name__ == '__main__':
     d.dispatch('func arg1 arg2 arg3')
     d.dispatch('inst cmd1 arg1 arg2 arg3 arg4')
     d.dispatch('inst subclass subcmd arg1 arg2 arg3 arg4 arg5')
     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
 
 
diff --git a/eventloop.py b/eventloop.py
new file mode 100644 (file)
index 0000000..7da492d
--- /dev/null
@@ -0,0 +1,144 @@
+# 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')
+
diff --git a/pollserver.py b/pollserver.py
deleted file mode 100644 (file)
index 886ef3a..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-# 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)
-
diff --git a/pymin b/pymin
new file mode 100755 (executable)
index 0000000..829b9a5
--- /dev/null
+++ b/pymin
@@ -0,0 +1,8 @@
+#!/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()
+
diff --git a/pymindaemon.py b/pymindaemon.py
new file mode 100644 (file)
index 0000000..1868ae2
--- /dev/null
@@ -0,0 +1,79 @@
+# 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()
+
diff --git a/services/__init__.py b/services/__init__.py
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/udp_server.py b/udp_server.py
deleted file mode 100644 (file)
index 23248a8..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-# 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