RPyC (Remote Python Call)
RPyC (Remote Python Call) is a transparent and symmetric distributed computing library for Python. It allows remote objects to be manipulated as if they were local, making it a powerful tool for building distributed systems and remote execution. The library is actively maintained, with frequent releases addressing bug fixes, performance improvements, and security enhancements. The current version is 6.0.2.
Warnings
- breaking RPyC 6.0.0 introduced a security fix for a Remote Code Execution (RCE) vulnerability related to the `__array__` attribute, which breaks backward compatibility for code relying on `numpy`'s usage of this attribute. The RCE was exploitable if the server-side explicitly called `np.array(x)` on a remote object.
- breaking RPyC 5.0.0 officially dropped support for Python 2, coinciding with its end-of-life. Attempting to connect Python 2 clients to Python 3 servers (or vice-versa) or using RPyC 5+ on Python 2 will lead to incompatibility issues due to fundamental differences in Python's object model and bytecode.
- gotcha Teleporting functions (e.g., using `rpyc.utils.classic.teleport_function`) between different Python major versions or even different RPyC versions is not officially supported and can lead to errors due to Python bytecode differences. It is recommended to ensure both client and server run the same Python and RPyC versions when using this feature.
- gotcha Running a classic RPyC server with `--host 0.0.0.0` (which was the default in older versions) exposes it to arbitrary code execution from any connecting client. This can be a significant security risk.
- breaking RPyC 5.3.1 included a fix for an experimental thread binding struct that, while fixing issues on some platforms, was not backward compatible. This primarily affects users experimenting with the thread binding feature.
Install
-
pip install rpyc
Imports
- rpyc
import rpyc
- rpyc.utils.server.ThreadedServer
from rpyc.utils.server import ThreadedServer
- rpyc.Service
import rpyc class MyService(rpyc.Service): # ... - rpyc.classic.connect
import rpyc conn = rpyc.classic.connect('localhost', 18812)
Quickstart
# server.py
import rpyc
from rpyc.utils.server import ThreadedServer
class MathService(rpyc.Service):
def on_connect(self, conn):
print("Client connected!")
def on_disconnect(self, conn):
print("Client disconnected!")
def exposed_fib(self, n):
"""Calculates the Fibonacci sequence up to n."""
seq = []
a, b = 0, 1
while a < n:
seq.append(a)
a, b = b, a + b
return seq
if __name__ == '__main__':
# Using a fixed port for demonstration
port = 18812
print(f"Starting MathService on port {port}...")
ts = ThreadedServer(MathService, port=port)
ts.start()
# --- Save the above as server.py and run: python server.py ---
# client.py
import rpyc
import time
def run_client():
host = "localhost" # Replace with your server's address if remote
port = 18812
try:
conn = rpyc.connect(host, port)
print(f"Connected to RPyC server at {host}:{port}")
# Access an exposed method on the root object
result = conn.root.fib(1000)
print(f"Fibonacci sequence up to 1000: {result}")
# Example of accessing a remote module (classic RPyC behavior)
remote_sys_version = conn.modules.sys.version
print(f"Remote Python version: {remote_sys_version.splitlines()[0]}")
conn.close()
print("Connection closed.")
except ConnectionRefusedError:
print(f"Error: Connection refused. Is the server running on {host}:{port}?")
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == '__main__':
# Optional: Give the server a moment to start if run immediately after
# time.sleep(1)
run_client()