[Checkins] SVN: zc.ngi/trunk/ Added UDP support.

Jim Fulton jim at zope.com
Fri May 22 18:57:50 EDT 2009


Log message for revision 100261:
  Added UDP support.
  
  Also, adjusted APIs to make them a bit cleaner:
    - Now there's an IImplementation interface that defines the
      callables an implementation must supply.
    - Renamed 'connector' to 'connect'. The old name is still available.
  

Changed:
  U   zc.ngi/trunk/buildout.cfg
  U   zc.ngi/trunk/src/zc/ngi/README.txt
  A   zc.ngi/trunk/src/zc/ngi/async-udp.test
  U   zc.ngi/trunk/src/zc/ngi/async.py
  U   zc.ngi/trunk/src/zc/ngi/async.txt
  U   zc.ngi/trunk/src/zc/ngi/blocking.py
  U   zc.ngi/trunk/src/zc/ngi/blocking.txt
  U   zc.ngi/trunk/src/zc/ngi/interfaces.py
  U   zc.ngi/trunk/src/zc/ngi/message.py
  U   zc.ngi/trunk/src/zc/ngi/message.txt
  U   zc.ngi/trunk/src/zc/ngi/testing.py
  U   zc.ngi/trunk/src/zc/ngi/testing.test
  U   zc.ngi/trunk/src/zc/ngi/tests.py
  U   zc.ngi/trunk/src/zc/ngi/wordcount.py

-=-
Modified: zc.ngi/trunk/buildout.cfg
===================================================================
--- zc.ngi/trunk/buildout.cfg	2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/buildout.cfg	2009-05-22 22:57:50 UTC (rev 100261)
@@ -1,8 +1,13 @@
 [buildout]
 develop = .
-parts = test
+parts = test py
 
 [test]
 recipe = zc.recipe.testrunner
 eggs = zc.ngi [test]
 
+[py]
+recipe = zc.recipe.egg
+eggs = zc.ngi
+interpreter = py
+

Modified: zc.ngi/trunk/src/zc/ngi/README.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/README.txt	2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/README.txt	2009-05-22 22:57:50 UTC (rev 100261)
@@ -13,30 +13,32 @@
 
 There are several interfaces defined by the NGI:
 
+IImplementation
+    APIs for implementing and connecting to TCP servers and for
+    implemented and sending messages to UDP servers.
+
 IConnection
     Network connection implementation.  This is the core interface that
     applications interact with,
 
 IConnectionHandler
-    Application component that handles network input.  
+    Application component that handles TCP network input.
 
-IConnector
-    Create IConnection objects by making outgoing connections.
-
 IClientConnectHandler
     Application callback that handles successful or failed outgoing
-    connections.
+    TCP connections.
 
-IListener
-    Listen for incoming connections.
-
 IServer
-    Callback to handle incoming connections.
+    Application callback to handle incoming connections.
 
+IUDPHandler
+    Application callback to handle incoming UDP messages.
+
 The interfaces are split between "implementation" and "application"
-interfaces.  An implementation of the NGI provides IConnection,
-IConnector, and IListener. An application provides IConnectionHandler
-and one or both of IClientConnectHandler and IServer.
+interfaces.  An implementation of the NGI provides Implementation,
+IConnection, IListener, and IUDPListener. An application provides
+IConnectionHandler and one or more of IClientConnectHandler,
+IServer, or IUDPHandler.
 
 For more information, see interfaces.py.
 
@@ -68,12 +70,12 @@
 
     >>> class EchoClient:
     ...
-    ...     def __init__(self, connector):
-    ...         self.connector = connector
+    ...     def __init__(self, connect):
+    ...         self.connect = connect
     ...
     ...     def check(self, addr, strings):
     ...         self.strings = strings
-    ...         self.connector(addr, self)
+    ...         self.connect(addr, self)
     ...
     ...     def connected(self, connection):
     ...         for s in self.strings:
@@ -113,9 +115,9 @@
 interfaces.  More complex clients might implement these interfaces with
 separate classes.
 
-We'll instantiate our client using the testing connector:
+We'll instantiate our client using the testing connect:
 
-    >>> client = EchoClient(zc.ngi.testing.connector)
+    >>> client = EchoClient(zc.ngi.testing.connect)
 
 Now we'll try to check a non-existent server:
 
@@ -126,7 +128,7 @@
 fails. More complex applications might retry, waiting between attempts,
 and so on.
 
-The testing connector always fails unless given a test connection
+The testing connect always fails unless given a test connection
 ahead of time.  We'll create a testing connection and register it so a
 connection can succeed:
 
@@ -211,7 +213,7 @@
 ============================
 
 Implementing network servers is very similar to implementing clients,
-except that a listener, rather than a connector is used.  Let's
+except that a listener, rather than a connect is used.  Let's
 implement a simple echo server:
 
 
@@ -288,7 +290,7 @@
     >>> connection = zc.ngi.testing.Connection()
     >>> listener.connect(connection)
     server connected
-   
+
     >>> list(listener.connections()) == [connection]
     True
 
@@ -348,7 +350,7 @@
     Traceback (most recent call last):
     ...
     TypeError: Listener closed
-    
+
 And the handler will be called when all of the listener's connections
 are closed:
 
@@ -362,7 +364,7 @@
 ===========
 
 Test requests output data written to them.  If output exceeds 50
-characters in length, it is wrapped by simply breaking the repr into 
+characters in length, it is wrapped by simply breaking the repr into
 50-characters parts:
 
     >>> connection = zc.ngi.testing.Connection()
@@ -408,16 +410,16 @@
 
 It is sometimes useful to connect a client handler and a server
 handler.  Listeners created with the zc.ngi.testing.listener class have a
-connector method that can be used to create connections to a server.
+connect method that can be used to create connections to a server.
 
 Let's connect out echo server and client. First, we'll create out
 server using the listener constructor:
 
     >>> listener = zc.ngi.testing.listener(EchoServer)
 
-Then we'll use the connector method on the listener:
+Then we'll use the connect method on the listener:
 
-    >>> client = EchoClient(listener.connector)
+    >>> client = EchoClient(listener.connect)
     >>> client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
     server connected
     server got input: 'hello\n'
@@ -434,7 +436,7 @@
   Below is an older API for connecting servers and clients in a
   testing environment.  The mechanisms defined above are prefered.
 
-  The zc.ngi.testing.peer function can be used to create a 
+  The zc.ngi.testing.peer function can be used to create a
   connection to a peer handler. To illustrate, we'll set up an echo
   client that connects to our echo server:
 
@@ -449,3 +451,34 @@
     matched: world
     matched: how are you?
     server closed: closed
+
+UDP Support
+===========
+
+To send a UDP message, just use an implementations udp method:
+
+    >>> zc.ngi.testing.udp(('', 42), "hello")
+
+If there isn't a server listening, the call will effectively be
+ignored. This is UDP. :)
+
+    >>> def my_udp_handler(addr, data):
+    ...     print 'from %r got %r' % (addr, data)
+
+    >>> listener = zc.ngi.testing.udp_listener(('', 42), my_udp_handler)
+
+    >>> zc.ngi.testing.udp(('', 42), "hello")
+    from '<test>' got 'hello'
+
+    >>> listener.close()
+    >>> zc.ngi.testing.udp(('', 42), "hello")
+
+For a handler is used if you don't pass a handler:
+
+    >>> listener = zc.ngi.testing.udp_listener(('', 43))
+    >>> zc.ngi.testing.udp(('', 43), "hello")
+    udp from '<test>' to ('', 43):
+      'hello'
+
+    >>> listener.close()
+    >>> zc.ngi.testing.udp(('', 43), "hello")

Added: zc.ngi/trunk/src/zc/ngi/async-udp.test
===================================================================
--- zc.ngi/trunk/src/zc/ngi/async-udp.test	                        (rev 0)
+++ zc.ngi/trunk/src/zc/ngi/async-udp.test	2009-05-22 22:57:50 UTC (rev 100261)
@@ -0,0 +1,23 @@
+async UDP support
+-----------------
+
+    >>> import zc.ngi.async, time
+
+    >>> zc.ngi.async.udp(('', 9644), 'test')
+
+    >>> def handler(addr, message):
+    ...     print message
+
+    >>> listener = zc.ngi.async.udp_listener(('', 9644), handler)
+    >>> time.sleep(0.1)
+
+    >>> zc.ngi.async.udp(('', 9644), 'test'); time.sleep(0.1)
+    test
+
+    >>> zc.ngi.async.udp(('', 9644), 'test'); time.sleep(0.1)
+    test
+
+    >>> listener.close()
+    >>> time.sleep(0.1)
+
+    >>> zc.ngi.async.udp(('', 9644), 'test'); time.sleep(0.1)


Property changes on: zc.ngi/trunk/src/zc/ngi/async-udp.test
___________________________________________________________________
Added: svn:eol-style
   + native

Modified: zc.ngi/trunk/src/zc/ngi/async.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/async.py	2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/async.py	2009-05-22 22:57:50 UTC (rev 100261)
@@ -330,23 +330,41 @@
     def handle_expt(self):
         self.handle_close('connection failed')
 
-class listener(asyncore.dispatcher):
+def connect(*args):
+    connector(*args)
 
+class BaseListener(asyncore.dispatcher):
+
+    def writable(self):
+        return False
+
+    def add_channel(self, map=None):
+        # work around file-dispatcher bug
+        assert (map is None) or (map is _map)
+        asyncore.dispatcher.add_channel(self, _map)
+
+    def handle_error(self):
+        reason = sys.exc_info()[1]
+        self.logger.exception('listener error')
+        self.close()
+
+class listener(BaseListener):
+
     logger = logging.getLogger('zc.ngi.async.server')
 
     def __init__(self, addr, handler):
-        self.addr = addr
         self.__handler = handler
         self.__close_handler = None
         self.__connections = {}
         asyncore.dispatcher.__init__(self)
         if isinstance(addr, str):
-            self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
+            family = socket.AF_UNIX
         else:
-            self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+            family = socket.AF_INET
+        self.create_socket(family, socket.SOCK_STREAM)
         self.set_reuse_addr()
-        self.logger.info("listening on %s", self.addr)
-        self.bind(self.addr)
+        self.logger.info("listening on %r", self.addr)
+        self.bind(addr)
         self.listen(255)
         notify_select()
 
@@ -397,16 +415,47 @@
         else:
             self.__close_handler = handler
 
-    def add_channel(self, map=None):
-        # work around file-dispatcher bug
-        assert (map is None) or (map is _map)
-        asyncore.dispatcher.add_channel(self, _map)
+class udp_listener(BaseListener):
 
-    def handle_error(self):
-        reason = sys.exc_info()[1]
-        self.logger.exception('listener error')
-        self.close()
+    logger = logging.getLogger('zc.ngi.async.udpserver')
+    connected = True
 
+    def __init__(self, addr, handler, buffer_size=4096):
+        self.__handler = handler
+        self.__buffer_size = buffer_size
+        asyncore.dispatcher.__init__(self)
+        if isinstance(addr, str):
+            family = socket.AF_UNIX
+        else:
+            family = socket.AF_INET
+        self.create_socket(family, socket.SOCK_DGRAM)
+        self.set_reuse_addr()
+        self.logger.info("listening on udp %r", self.addr)
+        self.bind(addr)
+        notify_select()
+
+    def handle_read(self):
+        message, addr = self.recvfrom(self.__buffer_size)
+        self.__handler(addr, message)
+
+    def close(self):
+        self.del_channel(_map)
+        self.socket.close()
+
+# udp uses GIL to get thread-safe socket management
+_udp_socks = {socket.AF_INET: [], socket.AF_UNIX: []}
+def udp(address, message):
+    if isinstance(address, str):
+        family = socket.AF_UNIX
+    else:
+        family = socket.AF_INET
+    try:
+        sock = _udp_socks[family].pop()
+    except IndexError:
+        sock = socket.socket(family, socket.SOCK_DGRAM)
+    sock.sendto(message, address)
+    _udp_socks[family].append(sock)
+
 # The following trigger code is greatly simplified from the Medusa
 # trigger code.
 

Modified: zc.ngi/trunk/src/zc/ngi/async.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/async.txt	2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/async.txt	2009-05-22 22:57:50 UTC (rev 100261)
@@ -41,7 +41,7 @@
     >>> import threading
     >>> addr = 'localhost', port
     >>> threads = [threading.Thread(target=zc.ngi.wordcount.client_thread,
-    ...                             args=(zc.ngi.async.connector, addr))
+    ...                             args=(zc.ngi.async.connect, addr))
     ...            for i in range(200)]
 
     >>> _ = [thread.start() for thread in threads]
@@ -54,7 +54,7 @@
 we'll use the blocking interface:
 
     >>> import zc.ngi.blocking
-    >>> output, input = zc.ngi.blocking.open(addr, zc.ngi.async.connector,
+    >>> output, input = zc.ngi.blocking.open(addr, zc.ngi.async.connect,
     ...                                      timeout=1.0)
     >>> def hello(name):
     ...     yield "hello\n"
@@ -109,7 +109,7 @@
   Handler errors cause connections to be closed.  To see this, we'll
   send the server an error message, which foreces an error:
 
-    >>> output, input = zc.ngi.blocking.open(addr, zc.ngi.async.connector,
+    >>> output, input = zc.ngi.blocking.open(addr, zc.ngi.async.connect,
     ...                                      timeout=1.0)
     >>> output.write('E\0')
     >>> input.read()
@@ -131,7 +131,7 @@
     ...         event.set()
 
     >>> handler = LameClientConnectionHandler()
-    >>> _ = zc.ngi.async.connector(addr, handler)
+    >>> zc.ngi.async.connect(addr, handler)
     >>> event.wait(1)
 
     >>> print loghandler
@@ -158,7 +158,7 @@
     ...         event.set()
 
     >>> handler = LameClientConnectionHandler()
-    >>> _ = zc.ngi.async.connector(addr, handler)
+    >>> zc.ngi.async.connect(addr, handler)
     >>> event.wait(1)
 
     >>> print loghandler
@@ -173,7 +173,7 @@
 
 .. stop the server
 
-    >>> zc.ngi.wordcount.stop_server_process(zc.ngi.async.connector, addr)
+    >>> zc.ngi.wordcount.stop_server_process(zc.ngi.async.connect, addr)
     ... # doctest: +ELLIPSIS
     handle_input failed
     Traceback (most recent call last):

Modified: zc.ngi/trunk/src/zc/ngi/blocking.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/blocking.py	2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/blocking.py	2009-05-22 22:57:50 UTC (rev 100261)
@@ -32,16 +32,16 @@
     """An attempt to connect timed out.
     """
 
-def connect(address, connector, timeout=None):
-    return _connector().connect(address, connector, timeout)
+def connect(address, connect, timeout=None):
+    return _connector().connect(address, connect, timeout)
     
 class _connector:
 
     failed = connection = None
 
-    def connect(self, address, connector, timeout):
+    def connect(self, address, connect, timeout):
         event = self.event = threading.Event()
-        connector(address, self)
+        connect(address, self)
         event.wait(timeout)
         if self.failed is not None:
             raise ConnectionFailed(self.failed)

Modified: zc.ngi/trunk/src/zc/ngi/blocking.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/blocking.txt	2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/blocking.txt	2009-05-22 22:57:50 UTC (rev 100261)
@@ -18,12 +18,12 @@
     >>> import zc.ngi.testing
     >>> connector = zc.ngi.testing.peer(('localhost', 42),
     ...                                 zc.ngi.wordcount.Server)
-    
-The open function is called with an address and a connector:
-    
+
+The open function is called with an address and a connect callable:
+
     >>> import zc.ngi.blocking
     >>> output, input = zc.ngi.blocking.open(('localhost', 42), connector)
-    
+
 The output file lets us send output to the server:
 
     >>> output.write("Hello\n")
@@ -67,7 +67,7 @@
     '4\n1 2 9\n'
 
 If read is called without a size, it won't return until the server has
-closed the connection.  
+closed the connection.
 
 In this example, we've been careful to only read as much data as the
 server produces.  For example, we called read without passing a length
@@ -126,14 +126,14 @@
 Low-level connection management
 ===============================
 
-When we used open above, we passed an address and a connector, and the
-oprn function created a connection and created file-like objects for
+When we used open above, we passed an address and a connect callable, and the
+open function created a connection and created file-like objects for
 output and input.  The connect function can be used to create a
 connection without a file-like object:
 
     >>> connection = zc.ngi.blocking.connect(('localhost', 42), connector)
 
-The if the open function is called without a connector, the the first
+The if the open function is called without a connect callable, the the first
 object must be a connection object and output and input objects for
 that connection will be returned:
 

Modified: zc.ngi/trunk/src/zc/ngi/interfaces.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/interfaces.py	2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/interfaces.py	2009-05-22 22:57:50 UTC (rev 100261)
@@ -14,12 +14,14 @@
 """Network Gateway Interface (NGI)
 
 The interfaces are split between "implementation" and "application"
-interfaces.  An implementation of the NGI provides IConnection,
-IConnector, and IListener. An application provides IConnectionHandler
-and one or both of IClientConnectHandler and IServer.
+interfaces.  An implementation of the NGI provides IImplementation,
+IConnection, IServerConnection, IServerControl, and IUDPServerControl.
+An TCP application provides IConnectionHandler and one or both of
+IClientConnectHandler and IServer. A UDP server application might
+provide IUDPHandler.
 
 The NGI is an event-based framework in the sense that applications
-register handlers that respond to input events.  There are 3 kinds of
+register handlers that respond to input events.  There are 4 kinds of
 handlers:
 
 - Input handlers receive network input and notification of connection
@@ -29,6 +31,8 @@
 
 - Servers respond to incoming connection events.
 
+- UDP handlers respond to incoming UDP messages.
+
 The interfaces are designed to allow single-threaded applications:
 
 - An implementation of the interfaces is not allowed to make multiple
@@ -54,19 +58,52 @@
 
 from zope.interface import Interface, Attribute
 
+class IImplementation(Interface):
+    """Standard interface for ngi implementations
+    """
+
+    def connect(address, handler):
+        """Try to make a connection to the given address
+
+        The handler is an IClientConnectHandler.  The handler
+        connected method will be called with an IConnection object
+        if and when the connection succeeds or failed_connect method
+        will be called if the connection fails.
+        """
+
+    def listener(address, handler):
+        """Listen for incoming TCP connections
+
+        When a connection is received, call the handler.
+
+        An IListener object is returned.
+        """
+
+    def udp(address, message):
+        """Send a UDP message
+        """
+
+    def udp_listen(address, handler, buffer_size=4096):
+        """Listen for incoming UDP messages
+
+        When a message is received, call the handler with the message.
+
+        An IUDPListener object is returned.
+        """
+
 class IConnection(Interface):
     """Network connections
 
     This is an implementation interface.
-  
+
     Network connections support communication over a network
     connection, or any connection having separate input and output
-    channels. 
+    channels.
     """
 
     def __nonzero__():
         """Return the connection status
-        
+
         True is returned if the connection is open/active and
         False otherwise.
         """
@@ -85,15 +122,15 @@
 
     def write(data):
         """Output a string to the connection.
-        
+
         The write call is non-blocking.
         """
 
     def writelines(data):
         """Output an iterable of strings to the connection.
-        
+
         The writelines call is non-blocking. Note, that the data may
-        not have been consumed when the method returns.        
+        not have been consumed when the method returns.
         """
 
     def close():
@@ -102,10 +139,10 @@
 
 class IServerConnection(IConnection):
     """Server connection
-    
+
     This is an implementation interface.
     """
-    
+
     control = Attribute("An IServerControl")
 
 class IConnectionHandler(Interface):
@@ -121,28 +158,28 @@
 
     def handle_input(connection, data):
         """Handle input data from a connection
-        
+
         The data is an 8-bit string.
 
         Note that there are no promises about blocking.  The data
         isn't necessarily record oriented.  For example, data could,
         in theory be passed one character at a time.  It is up to
         applications to organize data into records, if desired.
-        
+
         """
 
     def handle_close(connection, reason):
         """Receive notification that a connection has closed
-        
+
         The reason argument can be converted to a string for logging
         purposes.  It may have data useful for debugging, but this
         is undefined.
-        
+
         Notifications are received when the connection is closed
         externally, for example, when the other side of the
         connection is closed or in case of a network failure.  No
         notification is given when the connection's close method is
-        called.      
+        called.
         """
 
     def handle_exception(connection, exception):
@@ -154,21 +191,6 @@
         writelines methods.
         """
 
-class IConnector(Interface):
-    """Create a connection to a server
-    
-    This is an implementation interface.
-    """
-
-    def __call__(address, handler):
-        """Try to make a connection to the given address
-        
-        The handler is an IClientConnectHandler.  The handler
-        connected method will be called with an IConnection object
-        if and when the connection succeeds or failed_connect method
-        will be called if the connection fails.
-        """
-
 class IClientConnectHandler(Interface):
     """Receive notifications of connection results
 
@@ -178,7 +200,7 @@
     def connected(connection):
         """Receive notification that a connection had been established
         """
-        
+
     def failed_connect(reason):
         """Receive notification that a connection could not be established
 
@@ -187,33 +209,31 @@
         is undefined.
         """
 
-class IListener(Interface):
-    """Listed for incoming connections
-    
-    This is an implementation interface.
+class IServer(Interface):
+    """Handle server connections
+
+    This is an application interface.
     """
 
-    def __call__(address, handler):
-        """Listen for incoming connections
+    def __call__(connection):
+        """Handle a connection from a client
+        """
 
-        When a connection is received, call the handler.
 
-        An IServerControl object is returned.
-        """
+class IUDPHandler(Interface):
+    """Handle udp messages
 
-class IServer(Interface):
-    """Handle server connections
-
     This is an application interface.
     """
 
-    def __call__(connection):
+    def __call__(addr, data):
         """Handle a connection from a client
         """
 
-class IServerControl(Interface):
-    """Server information and close control
-    
+
+class IListener(Interface):
+    """Listener information and close control
+
     This is an implementation interface.
     """
 
@@ -234,12 +254,22 @@
         have been closed.
         """
 
+class IUDPListener(Interface):
+    """UDP Listener close control
+
+    This is an implementation interface.
+    """
+
+    def close():
+        """Close the listener
+        """
+
 class IBlocking(Interface):
     """Top-level blocking interface provided by the blocking module
     """
 
-    def connect(address, connector, timeout=None):
-        """Connect to the given address using the given connector
+    def connect(address, connect, timeout=None):
+        """Connect to the given address using the given connect callable
 
         A timout value may be given as a floating point number of
         seconds.
@@ -248,14 +278,14 @@
         an exception is raised.
         """
 
-    def open(connection_or_address, connector=None, timeout=None):
+    def open(connection_or_address, connect=None, timeout=None):
         """Get output and input files for a connection or address
 
         The first argument is either a connection or an address.
-        If (and only if) it is an address, then a connector must be
+        If (and only if) it is an address, then a connect callable must be
         provided as the second argument and a connection is gotten by
         calling the connect function with the given address,
-        connector, and timeout.
+        connect callable, and timeout.
 
         A pair of file-like objects is returned. The first is an
         output file-like object, an IBlockingOutput, for sending
@@ -287,7 +317,7 @@
         If whence is 2, the position is decreased by the offset.
 
         An exception is raised if the position is set to a negative
-        value. 
+        value.
         """
 
     def close():
@@ -364,8 +394,6 @@
         raised if the data cannot be read in the number of seconds given.
         """
 
-        
-
     def __iter__():
         """Return the input object
         """

Modified: zc.ngi/trunk/src/zc/ngi/message.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/message.py	2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/message.py	2009-05-22 22:57:50 UTC (rev 100261)
@@ -51,7 +51,7 @@
         self.notify(self.input, reason)
 
 
-def message(connector, addr, message, expected=None):
+def message(connect, addr, message, expected=None):
     result = []
     lock = threading.Lock()
     lock.acquire()
@@ -60,7 +60,7 @@
             return # already notified
         result.extend(args)
         lock.release()
-    connector(addr, Message(message, expected, notify))
+    connect(addr, Message(message, expected, notify))
     lock.acquire()
     data, reason = result
 

Modified: zc.ngi/trunk/src/zc/ngi/message.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/message.txt	2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/message.txt	2009-05-22 22:57:50 UTC (rev 100261)
@@ -22,20 +22,20 @@
     ...         pass
 
     >>> import zc.ngi.testing
-    >>> connector = zc.ngi.testing.peer('foo', EchoServer)
+    >>> connect = zc.ngi.testing.peer('foo', EchoServer)
 
 and we'll use the message client to send it a message and get a
 response.
-        
+
     >>> import zc.ngi.message
     >>> import re
     >>> expected = re.compile('\n').search
-    >>> zc.ngi.message.message(connector, 'foo', 'hello world!\n', expected)
+    >>> zc.ngi.message.message(connect, 'foo', 'hello world!\n', expected)
     'HELLO WORLD!\n'
 
 If we give an invalid address, we'll get an exception:
 
-    >>> zc.ngi.message.message(connector, 'bar', 'hello world!\n', expected)
+    >>> zc.ngi.message.message(connect, 'bar', 'hello world!\n', expected)
     Traceback (most recent call last):
     ...
     CouldNotConnect: connection refused

Modified: zc.ngi/trunk/src/zc/ngi/testing.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/testing.py	2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/testing.py	2009-05-22 22:57:50 UTC (rev 100261)
@@ -157,13 +157,15 @@
 
 _connectable = {}
 
-def connector(addr, handler):
+def connect(addr, handler):
     connections = _connectable.get(addr)
     if connections:
         handler.connected(connections.pop(0))
     else:
         handler.failed_connect('no such server')
 
+connector = connect
+
 def connectable(addr, connection):
     _connectable.setdefault(addr, []).append(connection)
 
@@ -174,13 +176,19 @@
         self._close_handler = None
         self._connections = []
 
-    def connect(self, connection):
+    def connect(self, connection, handler=None):
+        if handler is not None:
+            # connection is addr in this case and is ignored
+            handler.connected(Connection(None, self._handler))
+            return
         if self._handler is None:
             raise TypeError("Listener closed")
         self._connections.append(connection)
         connection.control = self
         self._handler(connection)
 
+    connector = connect
+
     def connections(self):
         return iter(self._connections)
 
@@ -199,10 +207,6 @@
         if not self._connections and self._close_handler:
             self._close_handler(self)
 
-    def connector(self, addr, handler):
-        handler.connected(Connection(None, self._handler))
-
-
 class peer:
 
     def __init__(self, addr, handler):
@@ -237,3 +241,32 @@
         finally:
             s.close()
     raise RuntimeError("Can't find port")
+
+
+class test_udp_handler:
+
+    def __init__(self, addr):
+        self.addr = addr
+
+    def __call__(self, addr, data):
+        sys.stdout.write("udp from %r to %r:\n  %r" % (addr, self.addr, data))
+
+_udp_handlers = {}
+class udp_listener:
+
+    def __init__(self, address, handler=None, buffer_size=4096):
+        if handler is None:
+            handler = test_udp_handler(address)
+        self.address = address
+        _udp_handlers[address] = handler, buffer_size
+
+    def close(self):
+        del _udp_handlers[self.address]
+
+def udp(addr, data):
+    handler = _udp_handlers.get(addr)
+    if handler is None:
+        return
+    handler, buffer_size = handler
+    if handler is not None:
+        handler('<test>', data[:buffer_size])

Modified: zc.ngi/trunk/src/zc/ngi/testing.test
===================================================================
--- zc.ngi/trunk/src/zc/ngi/testing.test	2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/testing.test	2009-05-22 22:57:50 UTC (rev 100261)
@@ -40,7 +40,7 @@
 
     >>> import zc.ngi.testing
     >>> listener = zc.ngi.testing.listener(Greet)
-    >>> listener.connector('', Connector())
+    >>> listener.connect('', Connector())
     server i got: start
     client i got: Hi
     server h got: Hi
@@ -55,7 +55,7 @@
     ...         raise ValueError('input', self.state, data)
 
     >>> listener = zc.ngi.testing.listener(Greet2)
-    >>> listener.connector('', Connector())
+    >>> listener.connect('', Connector())
     ... # doctest: +ELLIPSIS
     Error test connection calling connection handler:
     Traceback (most recent call last):
@@ -65,7 +65,7 @@
     server closed i handle_input error
 
     >>> listener = zc.ngi.testing.listener(Greet)
-    >>> listener.connector('', Connector(Greet2))
+    >>> listener.connect('', Connector(Greet2))
     ... # doctest: +ELLIPSIS
     server i got: start
     Error test connection calling connection handler:
@@ -80,7 +80,7 @@
     ...         raise ValueError('close', self.state, reason)
 
     >>> listener = zc.ngi.testing.listener(Greet2)
-    >>> listener.connector('', Connector(Greet3))
+    >>> listener.connect('', Connector(Greet3))
     ... # doctest: +ELLIPSIS
     Error test connection calling connection handler:
     Traceback (most recent call last):

Modified: zc.ngi/trunk/src/zc/ngi/tests.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/tests.py	2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/tests.py	2009-05-22 22:57:50 UTC (rev 100261)
@@ -36,7 +36,7 @@
     ...         lock.release()
 
     >>> def connect(addr):
-    ...     zc.ngi.async.connector(addr, Handler())
+    ...     zc.ngi.async.connect(addr, Handler())
     ...     lock.acquire()
 
     We find an unused port (so when we connect to it, the connection
@@ -48,13 +48,13 @@
 
     >>> connect(('localhost', port))
     failed
-    
+
     """
 
 class BrokenConnect:
 
     connected = failed_connect = __call__ = lambda: xxxxx
-    
+
 class BrokenAfterConnect:
 
     def connected(self, connection):
@@ -71,37 +71,37 @@
     # errors are logged.
     #import logging
     #logging.getLogger().addHandler(logging.StreamHandler())
-    
+
     # See if we can break the main loop before running the async test
-    
+
     # Connect to bad port with bad handler
 
     port = zc.ngi.wordcount.get_port()
     addr = 'localhost', port
-    zc.ngi.async.connector(addr, BrokenConnect())
+    zc.ngi.async.connect(addr, BrokenConnect())
 
     # Start the server and connect to a good port with a bad handler
 
     port = zc.ngi.wordcount.start_server_process(zc.ngi.async.listener)
     addr = 'localhost', port
-    zc.ngi.async.connector(addr, BrokenAfterConnect())
+    zc.ngi.async.connect(addr, BrokenAfterConnect())
 
     # Stop the server
-    zc.ngi.wordcount.stop_server_process(zc.ngi.async.connector, addr)
+    zc.ngi.wordcount.stop_server_process(zc.ngi.async.connect, addr)
 
     # Create a lister with a broken server and connect to it
     port = zc.ngi.wordcount.get_port()
     addr = 'localhost', port
     zc.ngi.async.listener(addr, BrokenConnect())
-    zc.ngi.async.connector(addr, BrokenAfterConnect())
+    zc.ngi.async.connect(addr, BrokenAfterConnect())
 
     # Create a lister with a broken Server handler and connect to it
     port = zc.ngi.wordcount.get_port()
     addr = 'localhost', port
     zc.ngi.async.listener(addr, BrokenAfterConnect())
-    zc.ngi.async.connector(addr, BrokenAfterConnect())
+    zc.ngi.async.connect(addr, BrokenAfterConnect())
 
-    
+
 def test_suite():
     return unittest.TestSuite([
         doctest.DocFileSuite(
@@ -110,6 +110,7 @@
             'message.txt',
             'adapters.txt',
             'blocking.txt',
+            'async-udp.test',
             ),
         doctest.DocFileSuite(
             'async.txt',

Modified: zc.ngi/trunk/src/zc/ngi/wordcount.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/wordcount.py	2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/wordcount.py	2009-05-22 22:57:50 UTC (rev 100261)
@@ -146,8 +146,8 @@
     wait(addr)
     return port
 
-def stop_server_process(connector, addr):
-    zc.ngi.message.message(connector, addr, 'Q\0', lambda s: s == 'Q\n')
+def stop_server_process(connect, addr):
+    zc.ngi.message.message(connect, addr, 'Q\0', lambda s: s == 'Q\n')
     wait(addr, up=False)
     log = open('server.log').read()
     os.remove('server.log')
@@ -214,12 +214,12 @@
         if self.docs:
             print 'unexpected close', reason
 
-def client_thread(connector, addr):
+def client_thread(connect, addr):
     logger.info('client started for %s', addr)
     lock = threading.Lock()
     lock.acquire()
     client = Client(notify=lock.release)
-    connector(addr, client)
+    connect(addr, client)
     logger.info('client waiting')
     lock.acquire() # wait till done
     logger.info('client done')



More information about the Checkins mailing list