This may simply be a basic python callback question but I am working with threads and a third party library, so excuse (and clarify) if I am mixing up different issues.
I'm looking at the pika example of implementing thread safe responses which uses a callback and functools partial as a way of passing additional arguments to the callback function.
def connect(self):
connection = pika.BlockingConnection(
pika.connection.URLParameters(params)
)
channel = connection.channel()
cb = partial(self._on_message, connection=connection)
channel.basic_consume(queue=queue, on_message_callback=cb)
def _on_message(self, channel, method, properties, body, connection):
print(connection)
If I am using classes can I just set the additional arguments (in this case connection) as class properties if the property is only ever set once? Is the equivalent the same thing?
class MyClass:
def connect(self):
self.connection = pika.BlockingConnection(
pika.connection.URLParameters(params)
)
channel = self.connection.channel()
channel.basic_consume(queue=queue, on_message_callback=self._on_message)
def _on_message(self, channel, method, properties, body):
print(self.connection)
I think this is correct given that the docs states the callback needs
The function to call when consuming with the signature on_message_callback(channel, method, properties, body)
Building on this, can I transform the basic_ack as per the example
def example():
cb = functools.partial(ack_message, channel, delivery_tag)
connection.add_callback_threadsafe(cb)
def ack_message(channel, delivery_tag):
if channel.is_open:
channel.basic_ack(delivery_tag)
to become the following?
class MyClass
def example(self):
self.channel = channel
self.delivery_tag = delivery_tag
connection.add_callback_threadsafe(self.ack_message)
def ack_message(self):
if self.channel.is_open:
self.channel.basic_ack(self.delivery_tag)
It doesn't look like an ack_message needs a particular parameter signature for add_callback_threadsafe to work.
Will my implementation with class properties and methods keep this implementation thread safe?
Related
I'm trying to write a script that saves mqtt data and sends it to influxDB. The issue I'm having is that the callback function of the mqtt-paho module keeps giving the error:
AttributeError: 'Client' object has no attribute 'write_api'. I think this is because of the self in the internal 'Client' class of the mqtt-paho. My full script can be found below:
# Imported modules
# standard time module
from datetime import datetime
import time
# InfluxDB specific modules
from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS
#MQTT paho specific modules
import paho.mqtt.client as mqtt
class data_handler(): # Default namespaces are just for all the ESPs.
def __init__(self, namespace_list=["ESP01","ESP02","ESP03","ESP04","ESP05","ESP06","ESP07","ESP08"]):
# initialize influxdb client and define access token and data bucket
token = "XXXXXXXXXX" # robotlab's token
self.org = "Home"
self.bucket = "HomeSensors"
self.flux_client = InfluxDBClient(url="http://localhost:8086", token=token)
self.write_api = self.flux_client.write_api(write_options=SYNCHRONOUS)
# Initialize and establish connection to MQTT broker
broker_address="XXX.XXX.XXX.XXX"
self.mqtt_client = mqtt.Client("influx_client") #create new instance
self.mqtt_client.on_message=data_handler.mqtt_message #attach function to callback
self.mqtt_client.connect(broker_address) #connect to broker
# Define list of namespaces
self.namespace_list = namespace_list
print(self.namespace_list)
def mqtt_message(self, client, message):
print("message received " ,str(message.payload.decode("utf-8")))
print("message topic=",message.topic)
print("message qos=",message.qos)
print("message retain flag=",message.retain)
sequence = [message.topic, message.payload.decode("utf-8")]
self.write_api.write(self.bucket, self.org, sequence)
def mqtt_listener(self):
for namespace in self.namespace_list:
self.mqtt_client.loop_start() #start the loop
print("Subscribing to topics!")
message = namespace+"/#"
self.mqtt_client.subscribe(message, 0)
time.sleep(4) # wait
self.mqtt_client.loop_stop() #stop the loop
def main():
influxHandler = data_handler(["ESP07"])
influxHandler.mqtt_listener()
if __name__ == '__main__':
main()
The code works fine until I add self.someVariable in the callback function. What would be a good way to solve this problem? I don't really want to be making global variables hence why I chose to use a class.
Thanks in advance!
Dealing with self when there are multiple classes involved can get confusing. The paho library calls on_message as follows:
on_message(self, self._userdata, message)
So the first argument passed is the instance of Client so what you are seeing is expected (in the absence of any classes).
If the callback is a method object (which appears to be your aim) "the instance object is passed as the first argument of the function". This means your function would take four arguments and the definition be:
mqtt_message(self, client, userdata, msg)
Based upon this you might expect your application to fail earlier than it is but lets look at how you are setting the callback:
self.mqtt_client.on_message=data_handler.mqtt_message
datahandler is the class itself, not an instance of the class. This means you are effectively setting the callback to a static function (with no binding to any instance of the class - this answer might help). You need to change this to:
self.mqtt_client.on_message=self.mqtt_message
However this will not work as the method currently only takes three arguments; update the definition to:
def mqtt_message(self, client, userdata, msg)
with those changes I believe this will work (or at least you will find another issue :-) ).
An example might be a better way to explain this:
class mqtt_sim():
def __init__(self):
self._on_message = None
#property
def on_message(self):
return self._on_message
#on_message.setter
def on_message(self, func):
self._on_message = func
# This is what you are doing
class data_handler1(): # Default namespaces are just for all the ESPs.
def __init__(self):
self.mqtt = mqtt_sim()
self.mqtt.on_message = data_handler1.mqtt_message # xxxxx
def mqtt_message(self, client, message):
print("mqtt_message1", self, client, message)
# This is what you should be doing
class data_handler2(): # Default namespaces are just for all the ESPs.
def __init__(self):
self.mqtt = mqtt_sim()
self.mqtt.on_message = self.mqtt_message #attach function to callback
def mqtt_message(self, mqttself, client, message):
print("mqtt_message2", self, mqttself, client, message)
# Lets try using both of the above
d = data_handler1()
d.mqtt._on_message("self", "userdata", "message")
d = data_handler2()
d.mqtt._on_message("self", "userdata", "message")
I am trying to store the content of a callback function, in order to access and manipulate the data within the script.
As far as I am concerned, the given function .subscribe() in my code below does not return anything (None). My function is only passed as a reference to the function as an argument. Is there a way to return the data from the function that calls my function?
My code is a simple example with roslibpy (a library for Python that interacts with the open-source robotics framework ROS through Websockets). It is mentioned, that the data is published as a stream via a Websocket each time a message is published into the topic /turtle1/pose. My goal here is to return the data that is being published into the topic. The print command provides a nice visualization of the data, which just works fine.
import roslibpy
client = roslibpy.Ros(host='localhost', port=9090)
client.run()
def myfunc(msg):
print(msg)
listener = roslibpy.Topic(client, '/turtle1/pose', 'turtlesim/Pose')
#my function is passed as an argument
listener.subscribe(myfunc)
try:
while True:
pass
except KeyboardInterrupt:
client.terminate()
The subscribe() method in the roslibpy library is defined as follows:
def subscribe(self, callback):
"""Register a subscription to the topic.
Every time a message is published for the given topic,
the callback will be called with the message object.
Args:
callback: Function to be called when messages of this topic are published.
"""
# Avoid duplicate subscription
if self._subscribe_id:
return
self._subscribe_id = 'subscribe:%s:%d' % (
self.name, self.ros.id_counter)
self.ros.on(self.name, callback)
self._connect_topic(Message({
'op': 'subscribe',
'id': self._subscribe_id,
'type': self.message_type,
'topic': self.name,
'compression': self.compression,
'throttle_rate': self.throttle_rate,
'queue_length': self.queue_length
}))
Is there common way to deal with such problems? Does it make more sense to store the output as an external source (e.g. .txt) and then access the source trough the script?
You can define a Python class that acts like a function, that can modify its own state when called, by defining the magic __call__ method. When obj(whatever) is done on a non-function obj, Python will run obj.__call__(whatever). subscribe only needs its input to be callable; whether it is an actual function or an object with a __call__ method does not matter to subscribe.
Here's an example of what you could do:
class MessageRecorder():
def __init__(self):
self.messages = []
# Magic python 'dunder' method
# Whenever a MessageRecorder is called as a function
# This function defined here will be called on it
# In this case, adds the message to a list of received messages
def __call__(self, msg):
self.messages.append(msg)
recorder = MessageRecorder()
# recorder can be called like a function
recorder("Hello")
listener.subscribe(recorder)
try:
while True:
pass
except KeyboardInterrupt:
client.terminate()
"""Now you can do whatever you'd like with recorder.messages,
which contains all messages received before termination,
in order of reception. If you wanted to print all of them, do:"""
for m in recorder.messages:
print(m)
I just jump into websocket programing with basic knowledge of "Asynchronous" and "Threads", i have something like this
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
import socket
import uuid
import json
import datetime
class WSHandler(tornado.websocket.WebSocketHandler):
clients = []
def open(self):
self.id = str(uuid.uuid4())
self.user_info = self.request.remote_ip +' - '+ self.id
print (f'[{self.user_info}] Conectado')
client = {"sess": self, "id" : self.id}
self.clients.append(client.copy())
def on_message(self, message):
print (f'[{self.user_info}] Mensaje Recivido: {message}')
print (f'[{self.user_info}] Respuesta al Cliente: {message[::-1]}')
self.write_message(message[::-1])
self.comm(message)
def on_close(self):
print (f'[{self.user_info}] Desconectado')
for x in self.clients:
if x["id"] == self.id :
self.clients.remove(x)
def check_origin(self, origin):
return True
application = tornado.web.Application([
(r'/', WSHandler),
])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(80)
myIP = socket.gethostbyname(socket.gethostname())
print ('*** Websocket Server Started at %s***' % myIP)
tornado.ioloop.IOLoop.instance().start()
my question is where do I add code ?, should I add everything inside the WShandler class, or outside, or in another file ? and when to use #classmethod?. for now there is no problem with the code when i add code inside the handler but i have just few test clients.
maybe not the full solution but just a few thoughts..
You can maybe look at the tornado websocket chat example,
here.
First good change is, that their clients (waiters) is a set()
which makes sure that every client is only contained once by default. And it is defined and accessed as a class variable. So you don't use self.waiters but cls.waiters or ClassName.waiters (in this case ChatSocketHandler.waiters) to access it.
class ChatSocketHandler(tornado.websocket.WebSocketHandler):
waiters = set()
Second change is that they update every client (you could choose here
to send the update not to all but only some) as a #classmethod, since
they dont want to receive the instance (self) but the class (cls) and
refer to the class variables (in their case waiters, cache and cach_size)
We can forget about the cache and cache size here.
So like this:
#classmethod
def send_updates(cls, chat):
logging.info("sending message to %d waiters", len(cls.waiters))
for waiter in cls.waiters:
try:
waiter.write_message(chat)
except:
logging.error("Error sending message", exc_info=True)
On every API call a new instance of your handler will be created, refered to as self. And every parameter in self is really unique to the instance and related to the actual client, calling your methods. This is good to identify a client on each call.
So a instance based client list like (self.clients) would always be empty on each call. And adding a client would only add it to this instance's view of the world.
But sometimes you want to have some variables like the list of clients the same for all instances created from your class.
This is where class variables (the ones you define directly under the class definition) and the #classmethod decorator come into play.
#classmethod makes the method call independant from the a instance. This means that you can only access class variables in those methods. But in the case of a
message broker this is pretty much what we want:
add clients to the class variable which is the same for all instances of your handler. And since it is defined as a set, each client is unique.
when receiving messages, send them out to all (or a subset of clients)
so on_message is a "normal" instance method but it calls something like: send_updates() which is a #classmethod in the end.
send_updates() iterates over all (or a subset) of clients (waiters) and uses this to send the actual updates in the end.
From the example:
#classmethod
def send_updates(cls, chat):
logging.info("sending message to %d waiters", len(cls.waiters))
for waiter in cls.waiters:
try:
waiter.write_message(chat)
except:
logging.error("Error sending message", exc_info=True)
Remember that you added waiters with waiters.append(self) so every waiter is really an instance and you are "simply" calling the instances (the instance is representing a caller) write_message() method. So this is not broadcasted but send to every caller one by one. This would be the place where you can separate by some criteria like topics or groups ...
So in short: use #classmethod for methods that are independant from a specific instance (like caller or client in your case) and you want to make actions for "all" or a subset of "all" of your clients. But you can only access class variables in those methods. Which should be fine since it's their purpose ;)
I just want to know how to get data from within the callback.
import pika
def callback(channel, method, properties, body):
print(method.get_body())
print(method.get_properties())
channel.basic_ack(delivery_tag=method.delivery_tag)
def on_open(connection):
connection.channel(on_open_callback=on_channel_open)
def on_channel_open(channel):
channel.basic_consume(on_message_callback = callback, queue='q1')
channel.basic_consume(on_message_callback = callback, queue='q2')
credentials = pika.PlainCredentials('user', 'password', erase_on_connect=False)
params = pika.ConnectionParameters("localhost", 5672, '/', credentials)
connection = pika.SelectConnection(parameters=params,
on_open_callback=on_open)
try:
connection.ioloop.start()
except KeyboardInterrupt:
connection.close()
connection.ioloop.start()
The output to the two print lines in callback are:
<class 'pika.spec.Basic.Deliver'>
<Basic.Deliver(['consumer_tag=ctag1.2607da3f5f9f4e5592991a16cc0aca6e', 'delivery_tag=1', 'exchange=gatekeeper', 'redelivered=True', 'routing_key=laa'])>
How can I extract the 'routing_key'? Having a look at the source code it led me to believe method.get_properties() would work, but it did not.
Although poorly documented, the callback function will be called with 4 arguments:
The channel you consumed with
A Method instance (in this case a Deliver instance)
A BasicProperties instance
A body (bytes)
The Deliver instance will have an attribute called routing_key. So your function could look like this:
def callback(channel, method, properties, body):
print(method.get_body())
print(method.get_properties())
print(method.routing_key)
channel.basic_ack(delivery_tag=method.delivery_tag)
PS. The arguments the callback will be called with are the same as described in here, where they are actually documented.
I want the functions to be part of the class I am building, but I get an error There is probably a problem that the decorator function will be a function in the department. Is there a solution to the problem ? Thank you.
import engineio
class Websocket:
def __init__(self):
self.eio = engineio.Client()
self.eio.connect('http://localhost:5000')
self.eio.wait()
# get error in this function
#eio.on('connect')
def on_connect():
print('connection established')
You can't use a decorator on a method where the decorator expression refers to an instance attribute. That's because decorators are executed when the function they decorate is being created. Inside a class statement body that means that when decorators are applied, there is no class yet, and without a class, there can't be any instances either.
You have two options:
Just call self.eio.on('connect') in the __init__ of your class, passing in a bound method:
class Websocket:
def __init__(self):
self.eio = engineio.Client()
self.eio.connect('http://localhost:5000')
self.eio.on('connect', self.on_connect)
# don't call wait, see below
def on_connect(self):
print('connection established')
This works because by the time __init__ is being called you have a class and an instance of that class (referenced by self), and self.on_connect returns a reference to a bound method (calling it will have self passed in). #.... decorator syntax is just syntactic sugar, you don't have to use it. The engineio.Client.on() method accepts the handler as a second argument, but you could also use self.eio.on('connect')(self.on_connect), which is a literal translation of what the decorator syntax does.
Use a nested function inside __init__, and decorate that:
class Websocket:
def __init__(self):
self.eio = engineio.Client()
self.eio.connect('http://localhost:5000')
#self.eio.on('connect')
def on_connect():
print('connection established')
# don't call wait, see below
but that makes it much harder to use that function directly from other code.
Note that the engineio.Client.wait() method blocks, it doesn't return, until the connection has ended. You would not normally put that kind of call in the __init__ method of a class!
Using a class to handle engineio events is great, but don't start the client connection from the class. I'd do this instead:
class Websocket:
def __init__(self, socket_url):
self.eio = engineio.Client()
self.eio.connect(socket_url)
self.eio.on('connect', self.on_connect)
# other registrations
def on_connect(self):
# handle connection
# other handlers
def wait(self):
self.eio.wait()
websocket = Websocket('http://localhost:5000)
websocket.wait()