Pyro Manual

version 0.6 (august 18, 1999)

Irmen de Jong (irmen@bigfoot.com)

Pyro is an acronym for PYthon Remote Objects. It is a basic Distributed Object Technology system written entirely in Python, and for use in Python only. With this, it closely resembles Java's Remote Method Invocation (RMI). It has less similarity to CORBA - which is a system- and language independent Distributed Object Technology and has much more to offer than Pyro or RMI. But Pyro is small, simple, fun and free!

Contents
Introduction
Installation
Pieces of Pyro
The Rules of the Game
Example

Introduction

For a short overview of what Pyro is, see the Pyro page online - available through the resources section of my AmigaPython page (http://www.bigfoot.com/~irmen/python.html). You can also download the package from there.

Copyright
This software is copyright © by Irmen de Jong. It is subject to change without notice. It is completely without warranty, and provided "as is". Use it at your own risk. In no event shall Irmen de Jong be liable for any damage resulting from errors in or improper use of it. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the copyright notice appears in all copies and that both the copyright notice and the permission appears in supporting documentation.

To get an idea of the way Pyro works, here is a scenario:

  1. The programmer writes a module 'test' containing a class 'testclass', which will be accessed remotely.
  2. If the programmer wishes, client proxy code is generated for 'testclass', using the PyroC proxy compiler. It is written to the 'test_proxy' module. This is not necessary however, because Pyro has Dynamic Proxies too.
  3. The server creates one or more instances of the 'testclass', and registers them with the Pyro Naming Service.
  4. The client queries the Naming Service for the location of those objects. It gets a Pyro URI (Universal Resource Identifier) for them.
  5. The client creates proxies for the remote objects by creating 'test_proxy.testclass' instances for the given URI, or just using a Dynamic Proxy.
  6. As the proxy appears just like the real 'testclass', the client can now invoke methods on the remote objects.

Using dynamic proxies has some small drawbacks, one of them being the little extra processing time each method call takes. Two big advantages are that you no longer have to make and deploy proxy files, and that invocations on a derived class work - they don't if you use generated proxies (XXX this is a known shortcoming of the proxy compiler. It will be fixed in a future release).

Related technology:

Installation

Extract the Pyro archive. It has five subdirectories:

Pyro/
move this directory somewhere in your Python search path. On most systems, the lib/site-python directory is a nice place. On Windows, move it in the Python folder itself.
bin/
move the contents of this directory somewhere in your shell search path.
docs/   and   test/
put those whereever you like.
extra/
contains some extra files. For instance, 'SocketServer.py' is in here. This is the Python 1.5.2 version of the SocketServer module. Pyro requires this version so if you don't already use Python 1.5.2, make sure this newer (and bugfixed) SocketServer.py is used, for instance, by copying it over the old version in the Python library directory.

Alternatively, you can keep the Pyro/ and bin/ directories where they are after extraction, and manually add the Pyro directory to your Python search path, and the bin directory to your shell search path.

Pieces of Pyro - XXX details must still be added XXX

Pyro package (Pyro)

Pyro is a Python package and as such is contained in the 'Pyro' directory. This directory contains the different module files which make up the Pyro system. One of them is the initialization file, __init__.py.

Pyro.config
Defined in __init__.py and initialized at the time the Pyro package is initialized (= as soon as something in the package is imported). Pyro.config contains all configuration items of Pyro, like the default network port numbers. You can simply access them as Pyro.config.<item>

Core library (Pyro.core)

ObjBase class
Server side object implementation base class. Pyro object implementations must inherit from this class.
getProtocolAdapter()
Creates a protocol adapter for a given protocol ID. You wouldn't normally have to use this.
PyroError/ProtocolError/URIError/DaemonError classes
Different Pyro exceptions.
PyroException class
For remote transportation of exceptions.
PyroURI class
Pyro Universal Resource Indicator. Pyro uses this to locate objects. You wouldn't normally have to use it directly.
DynamicProxy
A dynamic proxy. Clients don't need to have a pregenerated proxy file available if they use this instead. It is slightly slower though and has some drawbacks.
getProxyForURI
Creates a new Dynamic Proxy for the specified object URI.
Daemon class
Server side Pyro daemon. It accepts incoming method calls and dispatches them to the object implementations.
initClient()
Pyro client applications must call this function before using Pyro.
initServer()
Pyro server applications must call this function before using Pyro.

Naming Service (Pyro.naming)

NamingError class
Naming Service exception
NameServerLocator class
To locate the Pyro Naming Service. It uses a broadcast mechanism to find it, and returns a Pyro Proxy for the NS.
NameServerProxy class
The proxy for the NameServer - this code has been generated by the PyroC proxy compiler.
NameServer class
The Pyro Naming Service itself.
PersistentNameServer class
An experimental NS which uses a persistent database for the naming table.
startServer()
Starts the Naming Service, including a broadcast server which will provide clients with the address of the NS.
main()
Function to easily start the NS from a script.

Naming Service Control tool (Pyro.nsc)

NOTE: Use the nsc or nsc.bat script to start this tool from a shell.

This tool can be used to control a Pyro Naming Service remotely. You can examine the database, add/remove items, resolve objects by hand, and just check if the Naming Service is running by sending a 'ping'.

GUI Naming Service Control tool (Pyro.xnsc)

NOTE: Use the xnsc or xnsc.bat script to start this tool from a shell.

This utility is the same as the preceding one, but this one has a nice GUI.

Proxy Compiler (Pyro.pyroc)

NOTE: Use the pyroc or pyroc.bat script to start this tool from a shell.

This tool parses a Python source and generates static Pyro proxies for the classes it finds.

Utilities (Pyro.util)

A module which contains miscellaneous utility functions.

ArgVParser
Basic argument list (sys.argv) parser class. It is slightly similar to the getopt() Unix C function. The command line utilities use this to parse the command line arguments.
getUUID()
Creates a Universal Unique Identifier (ho-hum...). This is a string which can and is used as a unique identifier.

Other files

ns & ns.bat
Scripts to start the Naming Service. You can provide the port number to use instead of the default.
nsc & nsc.bat, xnsc & xnsc.bat
Scripts to start the Naming Service Control tool. With this you can manually control the naming service. The 'x-scripts' will start the GUI version of the NSC tool.
pyroc & pyroc.bat
Scripts to start the Pyro Proxy Compiler. You must provide the name of a module (without .py) as an argument.
genuuid & genuuid.bat
Simply print a new UUID to the output.
test/*
The test directory contains some test files (client and server and object implementation).

The Rules of the Game

You should follow the following guidelines when using Pyro.

Concerning the class which is made remotely available:

  1. The class can't have its own __init__ method. You should instead use a regular initialization method which you must call explicitly after object instantiation.
  2. The proxy compiler doesn't support inherited classes. This is an issue future versions of Pyro might deal with. Note that when you use the Dynamic Proxy everything works just fine!
  3. The class which is actually instantiated in the server should inherit from Pyro.core.ObjBase. You could define a new (probably empty) class in the server which inherits both from Pyro.core.ObjBase and the class which is made remotely available. This approach is used in the example below.

Example

(Note: this example is from Pyro 0.5. There are some slight differences, mostly in the output, when you use the actual version.)
  1. Write a module 'test' containing a class 'testclass', which will be accessed remotely.
    from Pyro.core import PyroException
    class testclass:
        def mul(self, arg1, arg2): return arg1*arg2
        def add(self, arg1, arg2): return arg1+arg2
        def sub(self, arg1, arg2): return arg1-arg2
        def div(self, arg1, arg2): return arg1/arg2
        def error(self): raise PyroException(ValueError('Server generated exception'))
  2. Using PyroC, generate client proxy code for 'testclass'. You can skip this part if you use the Dynamic Proxy in step 5.
    [e:\irmen\projects\pyro]pyroc test
    Python Remote Object Compiler (c)1999 by Irmen de Jong. Pyro V0.5
    
    processing module 'test' (test.pyc)...
    examining class PyroException ... imported from another module. Skipped.
    examining class testclass ... 5 methods processed
    Generating proxy for testclass
    This release of Pyro doesn't need server-side skeleton code.
    All done. Output can be found in test_proxy.py .
  3. Write a server, testserver.py, that creates one or more instances of the 'testclass', and registers them with the Pyro Naming Service.
    import sys
    import Pyro.naming
    import Pyro.core
    
    import test
    
    ######## testclass object
    
    class testclass(Pyro.core.ObjBase, test.testclass):
        pass
    
    ######## main program
    
    def main():
        Pyro.core.initServer()
        PyroDaemon = Pyro.core.Daemon()
        locator = Pyro.naming.NameServerLocator()
        print 'searching for Naming Service...'
        ns = locator.getNS()
        print ' found at',ns.URI.host,ns.URI.port
    
        PyroDaemon.useNameServer(ns)
    
        # connect a new object implementation:
        PyroDaemon.connect(testclass(),'test')
    
        print 'Ready.'
        while 1:
            PyroDaemon.handleRequests(3.0)
            print '.',
            sys.stdout.flush()
    
    if __name__=='__main__':
        main()
  4. Write a client, testclient.py, that will find the Naming Service, then query the NS for the location of the object.
    import sys
    import Pyro.naming, Pyro.core
    import test_proxy
    
    
    Pyro.core.initClient()
    
    locator = Pyro.naming.NameServerLocator()
    print 'Searching Naming Service...',
    ns = locator.getNS()
    print 'Naming Service found at',ns.URI.host,ns.URI.port
    
    print 'binding to object'
    try:
        URI=ns.resolve('test')
        print 'URI:',URI
    except Pyro.core.PyroError,x:
        print 'Couldn\'t bind object, nameserver says:',x
        raise SystemExit
  5. Let the client create a proxy for the remote object by creating a 'test_proxy.testclass' instance for the given URI, or creating a Dynamic Proxy for the URI. Because the proxy appears the same as the real 'testclass', the client can now invoke methods on the remote objects.
    test = test_proxy.testclass(URI)
    # you could use the following line if you'd rather use a Dynamic Proxy:
    #test = Pyro.core.getProxyForURI(URI)
        
    print test.mul(111,9)
    print test.add(100,222)
    print test.sub(222,100)
    print test.div(2.0,9.0)
    print test.mul('*',10)
    print test.add('String1','String2')
    print test.error()
  6. Run the application in the network. First, start the Naming Service on one computer.
    [e:\irmen\projects\pyro]ns
    Pyro Server Initialized. Using Pyro V0.5
    *** registered 'Pyro.NameServer' with URI PYRO://atlantis:9090/c0a8003768b7-37542e7e63f830ff-217e4cbb
    Pyro Naming Service V0.5.
    Running on atlantis port 9090 ('192.168.0.55', 9090)
    Broadcast daemon on 192.168.0.55 port 9091
    Ready.
  7. Start the server on another computer (or in a different shell).
    [e:\irmen\projects\pyro\pyro-0_5\test]python testserver.py
    Pyro Server Initialized. Using Pyro V0.5
    searching for Naming Service...
     found at atlantis 9090
    Ready.
    . . .
  8. Finally, run the client on a third computer (or in a different shell).
    [e:\irmen\projects\pyro\pyro-0_5\test]python testclient.py
    Pyro Client Initialized. Using Pyro V0.5
    Searching Naming Service... Naming Service found at atlantis 9090
    binding to object
    URI: PYRO://atlantis/c0a80037c2ac-37542ec57df417ff-d018e974
    999
    322
    122
    0.222222222222
    **********
    String1String2
    Traceback (innermost last):
      File "testclient.py", line 31, in ?
        print test.error()
      File "test_proxy.py", line 27, in error
        return self.adapter.remoteInvocation('error',0)
      File "Pyro\core.py", line 109, in remoteInvocation
      File "Pyro\core.py", line 157, in raiseEx
    ValueError: Server generated exception
  9. You might want to peek in the Naming Server:
    [e:\irmen\projects\pyro]nsc list
    Finding NS using broadcast @ port 9091
    LOCATOR: Searching Pyro Naming Service...
    NS is at atlantis port 9090
    -------------- START DATABASE
    Pyro.NameServer  -->  PYRO://atlantis:9090/c0a8003768b7-37542e7e63f830ff-217e4cbb
    test  -->  PYRO://atlantis/c0a80037c2ac-37542ec57df417ff-d018e974
    -------------- END