Exceptions and remote tracebacks

There is an example that shows various ways to deal with exceptions when writing Pyro code. Have a look at the exceptions example in the examples directory.

Pyro exceptions

Pyro’s exception classes can be found in Pyro4.errors. They are used by Pyro if something went wrong inside Pyro itself or related to something Pyro was doing.

Remote exceptions

More interesting are the exceptions that occur in your own objects (the remote Pyro objects). Pyro is doing its best to make the remote objects appear as normal, local, Python objects. That also means that if they raise an error, Pyro will make it appear in the caller, as if the error occurred locally.

Say you have a remote object that can divide arbitrary numbers. It will probably raise a ZeroDivisionError when you supply 0 as the divisor. This can be dealt with as follows:

import Pyro4

divider=Pyro4.Proxy( ... )
try:
    result = divider.div(999,0)
except ZeroDivisionError:
    print("yup, it crashed")

Just catch the exception as if you were writing code that deals with normal objects.

But, since the error occurred in a remote object, and Pyro itself raises it again on the client side, you lose some information: the actual traceback of the error at the time it occurred in the server. Pyro fixes this because it stores the traceback information on a special attribute on the exception object (_pyroTraceback). The traceback is stored as a list of strings (each is a line from the traceback text, including newlines). You can use this data on the client to print or process the traceback text from the exception as it occurred in the Pyro object on the server.

There is a utility function in Pyro4.util to make it easy to deal with this: Pyro4.util.getPyroTraceback()

You use it like this:

import Pyro4.util
try:
    result = proxy.method()
except Exception:
    print("Pyro traceback:")
    print("".join(Pyro4.util.getPyroTraceback()))

Also, there is another function that you can install in sys.excepthook, if you want Python to automatically print the complete Pyro traceback including the remote traceback, if any: Pyro4.util.excepthook()

A full Pyro exception traceback, including the remote traceback on the server, looks something like this:

Traceback (most recent call last):
  File "client.py", line 50, in <module>
    print(test.complexerror())     # due to the excepthook, the exception will show the pyro error
  File "E:\Projects\Pyro4\src\Pyro4\core.py", line 130, in __call__
    return self.__send(self.__name, args, kwargs)
  File "E:\Projects\Pyro4\src\Pyro4\core.py", line 242, in _pyroInvoke
    raise data
TypeError: unsupported operand type(s) for //: 'str' and 'int'
 +--- This exception occured remotely (Pyro) - Remote traceback:
 | Traceback (most recent call last):
 |   File "E:\Projects\Pyro4\src\Pyro4\core.py", line 760, in handleRequest
 |     data=method(*vargs, **kwargs)   # this is the actual method call to the Pyro object
 |   File "E:\projects\Pyro4\examples\exceptions\excep.py", line 17, in complexerror
 |     x.crash()
 |   File "E:\projects\Pyro4\examples\exceptions\excep.py", line 22, in crash
 |     s.crash2('going down...')
 |   File "E:\projects\Pyro4\examples\exceptions\excep.py", line 25, in crash2
 |     x=arg//2
 | TypeError: unsupported operand type(s) for //: 'str' and 'int'
 +--- End of remote traceback

As you can see, the first part is only the exception as it occurs locally on the client (raised by Pyro). The indented part marked with ‘Remote traceback’ is the exception as it occurred in the remote Pyro object.

Detailed traceback information

There is another utility that Pyro has to make it easier to debug remote object exceptions. If you enable the DETAILED_TRACEBACK config item on the server (see Overview of Config Items), the remote traceback is extended with details of the values of the local variables in every frame:

+--- This exception occured remotely (Pyro) - Remote traceback:
| ----------------------------------------------------
|  EXCEPTION <type 'exceptions.TypeError'>: unsupported operand type(s) for //: 'str' and 'int'
|  Extended stacktrace follows (most recent call last)
| ----------------------------------------------------
| File "E:\Projects\Pyro4\src\Pyro4\core.py", line 760, in Daemon.handleRequest
| Source code:
|     data=method(*vargs, **kwargs)   # this is the actual method call to the Pyro object
| ----------------------------------------------------
| File "E:\projects\Pyro4\examples\exceptions\excep.py", line 17, in TestClass.complexerror
| Source code:
|     x.crash()
| Local values:
|     self = <excep.TestClass object at 0x02392830>
|         self._pyroDaemon = <Pyro4.core.Daemon object at 0x02392330>
|         self._pyroId = 'obj_c63d47dd140f44dca8782151643e0c55'
|     x = <excep.Foo object at 0x023929D0>
| ----------------------------------------------------
| File "E:\projects\Pyro4\examples\exceptions\excep.py", line 22, in Foo.crash
| Source code:
|     self.crash2('going down...')
| Local values:
|     self = <excep.Foo object at 0x023929D0>
| ----------------------------------------------------
| File "E:\projects\Pyro4\examples\exceptions\excep.py", line 25, in Foo.crash2
| Source code:
|     x=arg//2
| Local values:
|     arg = 'going down...'
|     self = <excep.Foo object at 0x023929D0>
| ----------------------------------------------------
|  EXCEPTION <type 'exceptions.TypeError'>: unsupported operand type(s) for //: 'str' and 'int'
| ----------------------------------------------------
+--- End of remote traceback

You can immediately see why the call produced a TypeError without the need to have a debugger running (the arg variable is a string and dividing that string by 2 is the cause of the error).

Of course it is also possible to enable DETAILED_TRACEBACK on the client, but it is not as useful there (normally it is no problem to run the client code inside a debugger).