Pyvent is a dead-simple IPC to use that uses ZMQ and PyDispatcher under the hood.
For python, ZMQ is one of the best options for a fast, easy IPC. The only problem is the relatively high amount of boilerplate code to get basic communication going between two processes. Pyvent is made to have all of the benefits of a ZMQ IPC, with zero boiler plate code. The API uses a basic event emitter pattern, handling all threads and ZMQ behind the scenes. The ZMQ messages are basic enough that you could hook into the server from another language.
pip install pyvent
Pyvent requires python 3.6 or higher
Pyvent employs a observer, event based pattern common in javascript and Qt. It uses PyDispatcher under the hood, and follows syntax as closely as possible to that.
# script1.py
import pyvent
@pyvent.connect('order-food')
def order(food, drink='water'):
print(f'You ordered {food} and {drink}!')
# script2.py
import pyvent
pyvent.send('order-food', food='sushi', drink='sake')
$ python3 script1.py &
$ python3 script2.py
> You ordered sushi and sake!
Connect can be called as a decorator or a normal function with a callback argument
import pyvent
@pyvent.connect('food-burnt')
def fn():
print('Food is burnt :(')
# Same as
pyvent.connect('food-burnt', fn)
Calling methods on the module are all proxy functions that call an underlying class called Node
, which is the combination of a client and sometimes a server. For most use cases you can simply import pyvent
and call it's module functions but for use in multiprocessing
threads you likely need to manually start a node.
import pyvent
from pyvent import Node
from multiprocessing import Process
from time import time
def process():
node = Node()
@node.connect('make-food')
def fn(food):
print(f'Making {food}')
time.sleep(1)
# Create a process
p = Process(target=process)
p.start()
# Create a node on this process
# (Note that pyvent global functions would work too)
pyvent.send('make-food', food='chicken')
p.terminate()
You may want more control over which process the server belongs to, and when it is started. Note that this happens on the first send
or connect
call
pyvent.start_server()
pyvent.wait_for
is similar to connect, but will wait for a response and return the results. A timeout can be placed, and if it times out the response will be a tuple of (None, None)
import pyvent
args, kargs = pyvent.wait_for('food-ready', timeout=1)
if kargs:
print(f'Food ready: {kargs["food"]}')
else:
print('Took too long!')
There are several options that can be configured. As of v0.1.1, these has to be entered before any other calls
import pyvent
pyvent.configure(
# Default port that communication will be on
# Note that as of v0.1.1 port and port + 1 will be used
port=50020,
# If False, the this process will not spawn a server
# Ff True, will only start up if needed
server=True,
# ID of this process for identifying its calls
id='pyvent'
)
# These options can also be entered into Node as construction arguments
node = pyvent.Node(server=False)
To close all communication and halt threads, a pyvent.stop()
call is needed. Upon deletion, stop()
will be called.
Note that as of v0.1.1 stopping is sometimes buggy and blocks
To differentiate pyvent calls with other pydispatch calls, the default
sender parameter on all connects and sends is 'pyvent'.
If a client has an id set, it's calls will be 'pyvent.{id}'.
If you call send
or connect
with a sender
parameter, it will overwrite that. Note that due to traveling to another process,
instances can not be senders, only serializable objects.
import pyvent
from pydispatch import dispatcher
@pyvent.connect('new-customer', sender='customer-manager')
def fn(name):
print(f'A customer has arrived: {name}')
# only sender='customer-manager.*' will trigger the vent
pyvent.send('new-customer', sender='customer-manager')
# This will also trigger the event
dispatcher.send(signal='new-customer', sender='customer-manager')