Pyro5 Remote Object Communication Library
Pyro5 is a Python library for remote object communication, enabling client-server interactions across different processes or machines. It's the fifth major version of the Pyro project, emphasizing ease of use and flexibility. As of version 5.16, the project has entered a low maintenance mode, with active development paused unless critical bugs are reported.
Common errors
-
ImportError: cannot import name 'server' from 'Pyro5'
cause Attempting to import `server` from the top-level `Pyro5` package. Core components like `Daemon` are primarily exposed through `Pyro5.api`.fixAlways use `from Pyro5.api import ...` for accessing core Pyro5 classes and functions. For example, `from Pyro5.api import Daemon, expose, locate_ns, Proxy`. -
Pyro5.errors.NamingError: no such object with name "example.myserver"
cause The Pyro Name Server cannot find the registered object. This typically means the server object was not registered, the Name Server isn't running, or the client is looking up a different name, host, or port.fix1. Ensure the Pyro Name Server is running (`python -m Pyro5.nameserver`). 2. Verify the server registered its object correctly (e.g., `ns.register('name', uri)`). 3. Confirm the client is using the exact same name for lookup and connecting to the correct Name Server host/port. -
AttributeError: remote object has no attribute 'my_method'
cause The method `my_method` on the remote object was not exposed for remote calls. Pyro5 requires methods or classes to be explicitly exposed.fixEnsure the remote class itself is decorated with `@Pyro5.api.expose` (which exposes all public methods), or if not, that each specific method intended for remote calls is decorated with `@Pyro5.api.expose`. Remember that methods inherited from base classes also need explicit `@expose` if the base class isn't itself exposed. -
Pyro5.errors.DaemonError: no valid URI for the given object: <object at ...>
cause This error occurs when an object is registered with the Daemon, but there's a problem generating or associating a valid URI with it, often due to configuration issues or the object not being suitable for exposure.fixEnsure the object passed to `daemon.register()` is properly set up for exposure (e.g., its class or methods are `@expose`d). Check the Daemon's host and port configuration to ensure it can bind correctly and generate a reachable URI. For example, explicitly set `Daemon(host='0.0.0.0')` if you need to be reachable from other machines.
Warnings
- breaking Pyro5 has aggressively dropped support for older Python versions. As of v5.16, only Python 3.10 and newer are supported.
- deprecated The Pyro5 project is in a 'super low maintenance mode' as of v5.16. While critical bug fixes might be applied, active development of new features or significant changes is not planned.
- gotcha The `@expose` decorator on a class does not automatically expose methods inherited from its base classes. Each class in the inheritance hierarchy must explicitly use `@expose` on its methods or class to make them remotely accessible.
- breaking The `Daemon.serve()` method's `host` parameter no longer defaults to an empty string `''` but `None`. This can change binding behavior, especially for IPv6 or specific interface binding.
- gotcha Using `@expose` on `staticmethod` or `classmethod` in Python 3.10+ caused issues due to API changes in Python. While fixed in v5.13.1, older minor versions could be affected.
Install
-
pip install pyro5
Imports
- Daemon
from Pyro5.api import Daemon
- expose
from Pyro5.api import expose
- locate_ns
from Pyro5.api import locate_ns
- Proxy
from Pyro5.api import Proxy
- URI
from Pyro5.api import URI
- Pyro5.server
import Pyro5.server
from Pyro5.api import Daemon
Quickstart
import Pyro5.api
import time
import threading
# --- Server side (run in one terminal) ---
@Pyro5.api.expose
class MyServer:
def greet(self, name):
return f"Hello, {name}! This is Pyro5."
def echo(self, message):
return f"Server received: {message}"
def get_time(self):
return time.time()
def run_server():
# Start a Name Server in a separate terminal: python -m Pyro5.nameserver
# Or, start one programmatically if not using a shared nameserver
# For simplicity, we'll try to connect to one assumed to be running on localhost
try:
ns = Pyro5.api.locate_ns(host="localhost")
except Pyro5.errors.NamingError:
print("No Name Server found. Please start one: python -m Pyro5.nameserver")
return
daemon = Pyro5.api.Daemon(host="localhost")
uri = daemon.register(MyServer, "example.myserver") # Register with a name
ns.register("example.myserver", uri) # Register the URI with the Name Server
print(f"Server URI: {uri}")
print("Server ready. Object registered as 'example.myserver'.")
print("Starting event loop (Ctrl+C to stop)...")
daemon.requestLoop()
# --- Client side (run in another terminal) ---
def run_client():
# Locate the Name Server to find the object
try:
ns = Pyro5.api.locate_ns(host="localhost")
except Pyro5.errors.NamingError:
print("No Name Server found. Cannot connect to server.")
return
uri = ns.lookup("example.myserver") # Look up the object's URI by its name
# Create a proxy to the remote object
with Pyro5.api.Proxy(uri) as server_proxy:
print(f"Greeting: {server_proxy.greet('Pyro User')}")
print(f"Echo: {server_proxy.echo('This is a test message.')}")
print(f"Remote time: {server_proxy.get_time()}")
if __name__ == '__main__':
# This quickstart demonstrates client-server.
# For actual usage, run server and client in separate processes.
print("--- Pyro5 Quickstart ---")
print("To run, first start the Pyro Name Server in a *separate* terminal:")
print(" python -m Pyro5.nameserver
")
print("Then run this script. It will run the server, wait, then the client.")
print("Or, copy the server part to 'server.py' and client to 'client.py' and run them separately.")
# Simplified running for quickstart; normally they are separate processes
server_thread = threading.Thread(target=run_server)
server_thread.daemon = True # Allow main program to exit even if server is running
server_thread.start()
print("\nWaiting for server to start and register...")
time.sleep(2) # Give server a moment to start and register
print("\n--- Running Client ---")
run_client()
print("\nClient finished.")
# The server thread will continue running until Ctrl+C if it was main,
# but here it's a daemon thread so it will exit with main.