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 hosts. For example, if you want remote code that writes to stdout (on a remote tty) to print on your client's tty, you can do it like so:

>>> 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:

>>> from __future__ import with_statement
>>> c.execute("print 'hi there'")
>>> 
>>> with rpyc.classic.redirected_stdio(c):
...     c.execute("print 'hi here'")
... 
hi here
>>> c.execute("print 'hi there again'")
>>>
redirected.png

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, and other separate networks, which may prevent you from establishing an IP connection between two machines 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:

tunneling.png

Machine A is on network A, and it wants to connect to machine B on network B. Assuming there's a third machine (C) that has access to both networks (e.g., it has multiple NICs or belongs to multiple VLANs), all you have to do is run an RPyC server on it and you're ready to go.

# machine A: connect to machine C, which sees both networks
>>> import rpyc 
>>> c = rpyc.classic.connect("machine-c")
 
# from machine C: connect to machine B
>>> s = c.modules.socket.socket() 
>>> s.connect(("machine-b", 12345))

If you have python utilities (i.e., tftp server, etc.) that make use of the socket module, and you want them to be able to serve on the remote network, you can use monkey-patching to solve the problem:
myserver.py

import socket
import os
 
# ... server code ...

run_my_server.py

import myserver
import rpyc
c = rpyc.classic.connect("machine-c")
myserver.socket = c.modules.socket # <<< monkey-patch
# ... run myserver, which will now open sockets on machine-c instead of the local machine

Monkey-Patching

As shown under Tunneling above, you can override (monkey-patch) modules, functions, and classes, transparently:

import mymodule
import rpyc
# ...
mymodule.os = c.modules.os
mymodule.open = c.modules.__builtin__.open
mymodule.Telnet = c.modules.telnetlib.Telnet

So that when mymodule runs and makes use of the environment, the environment is that of the remote machine.
page_revision: 10, last_edited: 1225620315|%e %b %Y, %H:%M %Z (%O ago)
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License