=======================================
Swift RPC - Simple ZeroMQ RPC in Python
=======================================
Overview
========
Swift RPC (szrpc) is a framework for creating remote python servers and and clients able to connect to them.
It uses ZeroMQ for socket communications, and MessagePack for serialization. The key features which distinguish it from
other existing solutions are:
- Simple and clean API for creating clients, servers
- Servers can support one or more workers running on the same host or many distinct hosts, with transparent load balancing
- Supports multiple replies per request. Can be used to report progress for long running tasks or simply to send
replies in chunks if the application needs it.
- Reply objects can be transparently integrated into Gtk or Qt graphical frameworks through signals.
Getting Started
===============
Installing inside a virtual environment as follows
::
$ python -m venv myproject
$ source myproject/bin/activate
(myproject) $ pip3 install szrpc
Write your first RPC Service
============================
The following example illustrates how simple it is to create one.
.. code-block:: python
from szrpc.server import Service
class MyService(Service):
def remote__hello_world(self, request, name=None):
"""
Single reply after a long duration
"""
request.reply(f'Please wait, {name}. This will take a while.')
time.sleep(10)
return f'Hello, {name}. How is your world today?'
def remote__date(self, request):
"""
Single reply after a short duration
""" time.sleep(0.1)
return f"Today's date is {datetime.now()}"
def remote__progress(self, request):
for i in range(10):
time.sleep(0.1)
request.reply(f'{i*10}% complete')
return f"Progress done"
The above example demonstrates the following key points applicable to Services:
- Sevices must be sub-classes of **szrpc.server.Service**.
- All methods prefixed with a `remote__` will be exposed remotely.
- the very first argument to all remote methods is a request instance which contains all the information about the request.
- The remaining arguments where present, must be keyword arguments. Positional arguments other than the initial `request`
are not permitted.
- Remote methods may block.
- Multiple replies can be send back before the method completes. The return value will be the final reply sent to the client.
Running a Server instance
-------------------------
Once a service is defined, it can easily be used to start a server which can listen for incoming connections from multiple clients as follows:
.. code-block:: python
from szrpc.server import Server
if __name__ == '__main__':
service = MyService()
server = Server(service=service, ports=(9990, 9991))
server.run()
This says that our server will be available at the TCP address 'tcp://localhost:9990' for clients, and at the address
'tcp://localhost:9991' for workers. For simple cases, you don't need to worry about workers but by default, one worker
created behind the scenes to provide the service, thus it is mandatory to specify both ports. Additionally,
you can change your mind and run additional workers at any point in the future on any host after the server is started.
To start the server with more than one worker on the local host, modify the `instances` keyword argument as follows:
.. code-block:: python
server = Server(service=service, ports=(9990, 9991), instances=1)
It is possible to start the server with `instances = 0` however, it will obviously not be able to handle any requests
until at least one worker is started.
Starting External Workers
-------------------------
Starting external workers is very similar to starting Servers.
.. code-block:: python
from szrpc import log
from szrpc.server import Server, Service, WorkerManager
from test_server import MyService
if __name__ == '__main__':
service = MyService()
log.log_to_console()
server = WorkerManager(service=service, backend="tcp://localhost:9991", instances=2)
server.run()
In the above example, we are staring two instances of workers on this host which are connected to the backend address
of the main server.
Creating Clients
----------------
Clients are just as easy, if not easier to create. Here is a test client for the above service.
.. code-block:: python
import time
from szrpc import log
from szrpc.client import Client
# Define response handlers
def on_done(res, data):
print(f"Done: {res} {data!r}")
def on_err(res, data):
print(f"Failed: {res} : {data!r}")
def on_update(res, data):
print(f"Update: {res} {data!r}")
if __name__ == '__main__':
log.log_to_console()
client = Client('tcp://localhost:9990')
# wait for client to be ready before sending commands
while not client.is_ready():
time.sleep(.001)
res = client.hello_world(name='Joe')
res.connect('done', on_done)
res.connect('update', on_update)
res.connect('failed', on_err)
Here we have defined a few handler functions to get called once the replies are received. A few things are noteworthy in
the above client code:
- The client automatically figures out from the server, which methods to generate. For this reason, you will get
"InvalidAttribute" errors if the initial handshake has not completed before method calls are made. For most production
situations, this is not a problem but in the example above, we wait until the `client.is_ready()` returns `True` before
proceeding.
- The method names at the client end do not nave the `remote__` prefix. This means, overriding remote methods in the client
will clobber the name.
- Only key-worded arguments are allowed for remote methods.
- Results are delivered asynchronously. To write synchronous code, you can call the `res.wait()` method on `Result` objects.
There are three signal types corresponding to the three types of replies a server can send:
'done'
the server has completed processing the request, no further replies should be expected for this request
'update'
partial data is has been received for the request. More replies should be expected.
'failed'
The request has failed. No more replies should be expected.
Handler functions take two arguments, the first is always the `result` object, which is an instance of **szrpc.result.Result**,
and the second is the decoded message from the server.
Result Classes
--------------
All results are instances of **szrpc.result.Result** or sub-classes thereof. The types of result objects produced can be changed to allow better integration with various frameworks.
Presently, alternatives are available Gtk, Qt as well as a pure Python-based class. The pure Python result class is the default but it can easily be changed as follows.
.. code-block:: python
from szrpc.result.gresult import GResult
import szrpc.client
szrpc.client.use(GResult)
my_client = szrpc.client.Client('tcp://localhost:9990')
All subsequent result objects will be proper GObjects usable with the Gtk Main loop.
Raw data
{
"_id": null,
"home_page": "https://github.com/michel4j/swift-rpc",
"name": "szrpc",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "rpc networking development",
"author": "Michel Fodje",
"author_email": "michel4j@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/bf/aa/f924a1a2d78d3b714a682dcfcbb59658d6bace992287b91512796b4530a3/szrpc-2024.9.0.tar.gz",
"platform": null,
"description": "=======================================\nSwift RPC - Simple ZeroMQ RPC in Python\n=======================================\n\nOverview\n========\nSwift RPC (szrpc) is a framework for creating remote python servers and and clients able to connect to them.\nIt uses ZeroMQ for socket communications, and MessagePack for serialization. The key features which distinguish it from\nother existing solutions are:\n\n- Simple and clean API for creating clients, servers\n- Servers can support one or more workers running on the same host or many distinct hosts, with transparent load balancing\n- Supports multiple replies per request. Can be used to report progress for long running tasks or simply to send\n replies in chunks if the application needs it.\n- Reply objects can be transparently integrated into Gtk or Qt graphical frameworks through signals.\n\n\nGetting Started\n===============\nInstalling inside a virtual environment as follows\n\n::\n\n $ python -m venv myproject\n $ source myproject/bin/activate\n (myproject) $ pip3 install szrpc\n\n\nWrite your first RPC Service\n============================\nThe following example illustrates how simple it is to create one.\n\n.. code-block:: python\n\n from szrpc.server import Service\n\n class MyService(Service):\n def remote__hello_world(self, request, name=None):\n \"\"\"\n Single reply after a long duration\n \"\"\"\n request.reply(f'Please wait, {name}. This will take a while.')\n time.sleep(10)\n return f'Hello, {name}. How is your world today?'\n\n def remote__date(self, request):\n \"\"\"\n Single reply after a short duration\n \"\"\" time.sleep(0.1)\n return f\"Today's date is {datetime.now()}\"\n\n def remote__progress(self, request):\n for i in range(10):\n time.sleep(0.1)\n request.reply(f'{i*10}% complete')\n return f\"Progress done\"\n\n\n\nThe above example demonstrates the following key points applicable to Services:\n\n- Sevices must be sub-classes of **szrpc.server.Service**.\n- All methods prefixed with a `remote__` will be exposed remotely.\n- the very first argument to all remote methods is a request instance which contains all the information about the request.\n- The remaining arguments where present, must be keyword arguments. Positional arguments other than the initial `request`\n are not permitted.\n- Remote methods may block.\n- Multiple replies can be send back before the method completes. The return value will be the final reply sent to the client.\n\nRunning a Server instance\n-------------------------\nOnce a service is defined, it can easily be used to start a server which can listen for incoming connections from multiple clients as follows:\n\n.. code-block:: python\n\n from szrpc.server import Server\n\n if __name__ == '__main__':\n service = MyService()\n server = Server(service=service, ports=(9990, 9991))\n server.run()\n\n\nThis says that our server will be available at the TCP address 'tcp://localhost:9990' for clients, and at the address\n'tcp://localhost:9991' for workers. For simple cases, you don't need to worry about workers but by default, one worker\ncreated behind the scenes to provide the service, thus it is mandatory to specify both ports. Additionally,\nyou can change your mind and run additional workers at any point in the future on any host after the server is started.\n\nTo start the server with more than one worker on the local host, modify the `instances` keyword argument as follows:\n\n\n.. code-block:: python\n\n server = Server(service=service, ports=(9990, 9991), instances=1)\n\nIt is possible to start the server with `instances = 0` however, it will obviously not be able to handle any requests\nuntil at least one worker is started.\n\nStarting External Workers\n-------------------------\nStarting external workers is very similar to starting Servers.\n\n.. code-block:: python\n\n from szrpc import log\n from szrpc.server import Server, Service, WorkerManager\n\n from test_server import MyService\n\n if __name__ == '__main__':\n\n service = MyService()\n log.log_to_console()\n server = WorkerManager(service=service, backend=\"tcp://localhost:9991\", instances=2)\n server.run()\n\n\nIn the above example, we are staring two instances of workers on this host which are connected to the backend address\nof the main server.\n\nCreating Clients\n----------------\n\nClients are just as easy, if not easier to create. Here is a test client for the above service.\n\n.. code-block:: python\n\n import time\n from szrpc import log\n from szrpc.client import Client\n\n # Define response handlers\n def on_done(res, data):\n print(f\"Done: {res} {data!r}\")\n\n def on_err(res, data):\n print(f\"Failed: {res} : {data!r}\")\n\n def on_update(res, data):\n print(f\"Update: {res} {data!r}\")\n\n if __name__ == '__main__':\n log.log_to_console()\n client = Client('tcp://localhost:9990')\n\n # wait for client to be ready before sending commands\n while not client.is_ready():\n time.sleep(.001)\n\n res = client.hello_world(name='Joe')\n res.connect('done', on_done)\n res.connect('update', on_update)\n res.connect('failed', on_err)\n\nHere we have defined a few handler functions to get called once the replies are received. A few things are noteworthy in\nthe above client code:\n\n- The client automatically figures out from the server, which methods to generate. For this reason, you will get\n \"InvalidAttribute\" errors if the initial handshake has not completed before method calls are made. For most production\n situations, this is not a problem but in the example above, we wait until the `client.is_ready()` returns `True` before\n proceeding.\n- The method names at the client end do not nave the `remote__` prefix. This means, overriding remote methods in the client\n will clobber the name.\n- Only key-worded arguments are allowed for remote methods.\n- Results are delivered asynchronously. To write synchronous code, you can call the `res.wait()` method on `Result` objects.\n\n\nThere are three signal types corresponding to the three types of replies a server can send:\n\n'done'\n the server has completed processing the request, no further replies should be expected for this request\n\n'update'\n partial data is has been received for the request. More replies should be expected.\n\n'failed'\n The request has failed. No more replies should be expected.\n\nHandler functions take two arguments, the first is always the `result` object, which is an instance of **szrpc.result.Result**,\nand the second is the decoded message from the server.\n\nResult Classes\n--------------\nAll results are instances of **szrpc.result.Result** or sub-classes thereof. The types of result objects produced can be changed to allow better integration with various frameworks.\nPresently, alternatives are available Gtk, Qt as well as a pure Python-based class. The pure Python result class is the default but it can easily be changed as follows.\n\n.. code-block:: python\n\n\n from szrpc.result.gresult import GResult\n import szrpc.client\n\n szrpc.client.use(GResult)\n\n my_client = szrpc.client.Client('tcp://localhost:9990')\n\nAll subsequent result objects will be proper GObjects usable with the Gtk Main loop.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A simple Python RPC Library using ZeroMQ & MsgPack",
"version": "2024.9.0",
"project_urls": {
"Homepage": "https://github.com/michel4j/swift-rpc"
},
"split_keywords": [
"rpc",
"networking",
"development"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "ef86f9de81ce940f7d562c6c35dc4d4cc4afbf71455fd93d49cb472d860c4a88",
"md5": "352a1511edfb60ab1a9a2541854e788c",
"sha256": "499570c28b92edf26bc702d92172c7d4f4d0b2b78fdc6b34408a8d6161406393"
},
"downloads": -1,
"filename": "szrpc-2024.9.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "352a1511edfb60ab1a9a2541854e788c",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 14008,
"upload_time": "2024-09-17T22:48:44",
"upload_time_iso_8601": "2024-09-17T22:48:44.277448Z",
"url": "https://files.pythonhosted.org/packages/ef/86/f9de81ce940f7d562c6c35dc4d4cc4afbf71455fd93d49cb472d860c4a88/szrpc-2024.9.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "bfaaf924a1a2d78d3b714a682dcfcbb59658d6bace992287b91512796b4530a3",
"md5": "04ab7ec6c2d3b9a534ef930a3f3b81a4",
"sha256": "6a692f31128f3588f4f7d61a7e7fcba7b8a3cb49276a4a06ed8d2c09ac311f3a"
},
"downloads": -1,
"filename": "szrpc-2024.9.0.tar.gz",
"has_sig": false,
"md5_digest": "04ab7ec6c2d3b9a534ef930a3f3b81a4",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 17213,
"upload_time": "2024-09-17T22:48:45",
"upload_time_iso_8601": "2024-09-17T22:48:45.666244Z",
"url": "https://files.pythonhosted.org/packages/bf/aa/f924a1a2d78d3b714a682dcfcbb59658d6bace992287b91512796b4530a3/szrpc-2024.9.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-09-17 22:48:45",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "michel4j",
"github_project": "swift-rpc",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"requirements": [],
"lcname": "szrpc"
}