How To’s

This page contains a collection of useful concepts and examples for developing with RPyC

Redirecting Standard Input/Output

You can “rewire” stdin, stdout and stderr between RPyC hosts. For example, if you want to “forward” the stdout of a remote process to your local tty, you can use the following receipt:

>>> import rpyc
>>> c = rpyc.classic.connect("localhost")
>>> c.execute("print('hi there')")   # this will print on the host
>>> import sys
>>> c.modules.sys.stdout = sys.stdout
>>> c.execute("print('hi here')")   # now this will be redirected here
hi here

Also note that if you are using classic mode RPyC, you can use the context manager rpyc.classic.redirected_stdio:

>>> c.execute("print('hi there')")                   # printed on the server
>>>
>>> with rpyc.classic.redirected_stdio(c):
...     c.execute("print('hi here')")                # printed on the client
...
hi here
>>> c.execute("print('hi there again')")             # printed on the server
>>>
../_images/howto-redirected.png

A screenshot of an RPyC client redirecting standard output from the server to its own console.

Debugging

If you are using the classic mode, you will be glad to know that you can debug remote exceptions with pdb:

>>> c = rpyc.classic.connect("localhost")
>>> c.modules["xml.dom.minidom"].parseString("<<invalid xml>/>")
======= Remote traceback =======
Traceback (most recent call last):
...
  File "/usr/lib/python2.5/xml/dom/minidom.py", line 1925, in parseString
    return expatbuilder.parseString(string)
  File "/usr/lib/python2.5/xml/dom/expatbuilder.py", line 940, in parseString
    return builder.parseString(string)
  File "/usr/lib/python2.5/xml/dom/expatbuilder.py", line 223, in parseString
    parser.Parse(string, True)
ExpatError: not well-formed (invalid token): line 1, column 1
...
  File "/home/tomer/workspace/rpyc/core/protocol.py", line 298, in sync_request
    raise obj
xml.parsers.expat.ExpatError: not well-formed (invalid token): line 1, column 1
>>>
>>> rpyc.classic.pm(c) # start post-portem pdb
> /usr/lib/python2.5/xml/dom/expatbuilder.py(226)parseString()
-> pass
(Pdb) l
221             parser = self.getParser()
222             try:
223                 parser.Parse(string, True)
224                 self._setup_subset(string)
225             except ParseEscape:
226  ->             pass
227             doc = self.document
228             self.reset()
229             self._parser = None
230             return doc
231
(Pdb) w
...
  /home/tomer/workspace/rpyc/core/protocol.py(381)_handle_call()
-> return self._local_objects[oid](*args, **dict(kwargs))
  /usr/lib/python2.5/xml/dom/minidom.py(1925)parseString()
-> return expatbuilder.parseString(string)
  /usr/lib/python2.5/xml/dom/expatbuilder.py(940)parseString()
-> return builder.parseString(string)
> /usr/lib/python2.5/xml/dom/expatbuilder.py(226)parseString()
-> pass
(Pdb)

Tunneling

Many times, especially in testing environments, you have subnets, VLANs, VPNs, firewalls etc., which may prevent you from establishing a direct TCP connection between two machines, crossing network in two different networks. This may be done for security reasons or to simulate the environment where your product will be running, but it also hinders your ability to conduct tests. However, with RPyC you can overcome this limitation very easily: simply use the remote machine’s socket module!

Consider the following diagram:

../_images/howto-tunneling.png

Machine A belongs to network A, and it wants to connect to machine B, which belongs to network B. Assuming there’s a third machine, C that has access to both networks (for instance, it has multiple network cards or it belongs to multiple VLANs), you can use it as a transparent bridge between machines A and B very easily: simply run an RPyC server on machine C, to which machine A would connect, and use its socket module to connect to machine B. It’s really simple:

# this runs on machine `A`
import rpyc

machine_c = rpyc.classic.connect("machine-c")
sock = machine_c.modules.socket.socket()
sock.connect(("machine-b", 12345))

sock.send(...)
sock.recv(...)

Monkey-Patching

If you have python modules that make use of the socket module (say, telnetlib or asyncore), and you want them to be able to cross networks over such a bridge, you can use the recipe above to “inject” C’s socket module into your third-party module, like so:

import rpyc
import telnetlib

machine_c = rpyc.classic.connect("machine-c")
telnetlib.socket = rpyc.modules.socket

This is called monkey-patching, it’s a very handy technique which you can use in other places as well, to override functions, classes and entire modules. For instance

import mymodule
import rpyc
# ...
mymodule.os = conn.modules.os
mymodule.open = conn.builtins.open
mymodule.Telnet = conn.modules.telnetlib.Telnet

That way, when mymodule makes use of supposedly local modules, these modules actually perform operations on the remote machine, transparently.