20

I'm trying access some data using websockets, but I cannot really get around the examples given in the websockets documentation.

I have this code (https://pypi.org/project/websocket_client/) and want to transform it into a class.

import websocket
import thread
import time

def on_message(ws, message):
    print message

def on_error(ws, error):
    print error

def on_close(ws):
    print "### closed ###"

def on_open(ws):
    def run(*args):
        for i in range(3):
            time.sleep(1)
            ws.send("Hello %d" % i)
        time.sleep(1)
        ws.close()
        print "thread terminating..."
    thread.start_new_thread(run, ())


if __name__ == "__main__":
    websocket.enableTrace(True)
    ws = websocket.WebSocketApp("ws://echo.websocket.org/",
                                on_message = on_message,
                                on_error = on_error,
                                on_close = on_close)
    ws.on_open = on_open

    ws.run_forever()

The idea is to have this all websocket functionality in a class so that I can just create an object of that class.

I tried to start doing it but I cannot even get passed this:

class MySocket(object):
    def __init__(self):
        websocket.enableTrace(True)
        self.ws = websocket.WebSocketApp("ws://echo.websocket.org:12300/foo",
                                    on_message = on_message,
                                    on_error = on_error,
                                    on_close = on_close)

    def on_message(ws, message):
        print message

    def on_error(ws, error):
        print error

    def on_close(ws):
        print "### closed ###"

    def on_open(ws):
    ws.send("Hello %d" % i)

The error starts right away in on_message saying that's an "unresolved reference".

8 Answers 8

31

Package the call inside an anonymous lambda function to achieve a proper call with the correct self:

class Client:
    def __init__(self, db, symbols):
        self.ws = websocket.WebSocketApp("wss://the.server.com/api",
                    on_message = lambda ws,msg: self.on_message(ws, msg),
                    on_error   = lambda ws,msg: self.on_error(ws, msg),
                    on_close   = lambda ws:     self.on_close(ws),
                    on_open    = lambda ws:     self.on_open(ws))

    def on_message(self, ws, message):
            msg = json.loads(message)
            print(msg)
    ...
Sign up to request clarification or add additional context in comments.

4 Comments

This seems like the correct way to solve the problem
It can be simpler without lambdas: on_message=self.on_message, on_error=self.on_error, ...
@gil9red without lambdas, you lose self. In other words, inside the on_message, self will not point to the instance of your object.
I've tried both with lambda and self.on_message but I still get on_open() missing 1 required positional argument: 'ws'
8

The WebSocketApp needs callable objects for its callbacks (both the ones you pass in the constructor, like on_message, and the one you're setting after the fact, on_open).

Plain functions are callable objects, so your non-OO version works fine, because you're passing the plain functions.

Bound methods are also callable objects. But your OO version isn't passing bound methods. A bound method is, as the name implies, bound to an object. You do this by using the obj.method notation. In your case, that's self.on_message:

self.ws = websocket.WebSocketApp("ws://echo.websocket.org/",
                                 on_message = self.on_message,
                                 on_error = self.on_error,
                                 on_close = self.on_close)
self.ws.on_open = self.on_open

However, you've got another problem. While this will make your error go away, it won't make your code actually work. A normal method has to take self as its first argument:

def on_message(self, ws, message):
    print message

It's also worth noting that you're not really using the class for anything. If you never access anything off self, the class is just acting like a namespace. Not that this is always a bad thing, but it's usually a sign that you need to at least think through your design. Is there really any state that you need to maintain? If not, why do you want a class in the first place?

You may want to reread the tutorial section on Classes to understand about methods, self, etc.

3 Comments

Well, I can't get it to work like that. I try: ws = MySocket(); ws.run_forever(); and I get the error: 'MySocket' object has no attribute 'run_forever'. But perhaps you are right I should put this in a class.
@jbssm: Well, you define a class MySocket that doesn't have a run_forever() method. You create an instance of that class and try to call run_forever() on it. What do you expect to happen? If you want your class to automatically delegate all unknown methods to self.ws, you can do that, but you need to write code for that.
Thank you. I'll stop using the class and try to use normal functions.
4
import websocket

try:
    import thread
except ImportError:
    import _thread as thread
import time


class OnyxGenericClient:
    """
    Onyx Client Interface

    """

    def __init__(self, ):
        websocket.enableTrace(True)
        ws = websocket.WebSocketApp("ws://localhost:3000/",
                                         on_message=self.on_message,
                                         on_error=self.on_error,
                                         on_close=self.on_close)
        self.ws = ws
        self.ws.on_open = self.on_open
        self.ws.run_forever()

    # def initiate(self):

    def on_message(self, message):
        print(message)
        return message

    def on_error(self, error):
        return error

    def on_close(self):
        print("### closed ###")

    def run(self, *args):
        global driver
        driver = True
        while driver:
            try:
                time.sleep(1)
                print("Say something nice")
                p = input()
                self.ws.send(p)
            except KeyboardInterrupt:
                driver = False
        time.sleep(1)
        self.ws.close()
        print("thread terminating...")

    def on_open(self):
        thread.start_new_thread(self.run, ())


if __name__ == "__main__":
    websocket.enableTrace(True)
    onyx_client = OnyxGenericClient()

I wonder why everyone is still putting the ws parameter.

Read the error log.

File "venv/lib/python3.7/site-packages/websocket/_app.py", line 343, in _callback callback(*args)

    def _callback(self, callback, *args):
    if callback:
        try:
            if inspect.ismethod(callback):
                callback(*args)
            else:
                callback(self, *args)

        except Exception as e:
            _logging.error("error from callback {}: {}".format(callback, e))
            if _logging.isEnabledForDebug():
                _, _, tb = sys.exc_info()
                traceback.print_tb(tb)

Looking at our callbacks, on_open(self, ws)

When the try block executes it checks if our callback is a method or a function. if it is a method it would execute the callback(*args) already our self from our CustomClient is already passed as an argument in (*args). Mind you it already has its own self in def _callback(self, callback, *args). Hence, every callback that is an instance of your CustomClient should not have the ws argument.

1 Comment

Thanks man @vc74 . Can’t even remember when I answered this. Makes alot of sense though.
3

Just updating the code written by other authors on this page, that worked for me. The problem is that in the event callback functions definition like on_message we should not use ws as parameter. self takes care of it and in the body of these event handler functions we should use self.ws

class MySocket(object):
    def __init__(self):
        websocket.enableTrace(True)
        self.ws = websocket.WebSocketApp("ws://echo.websocket.org:12300/foo",
                                on_message = self.on_message,
                                on_error = self.on_error,
                                on_close = self.on_close)

    
    def on_message(self, message):
        # if you want to send something use like this
        # self.ws.send()
        print message

    def on_error(self, error):
        print error

    def on_close(self):
        print "### closed ###"

    def on_open(self):
        self.ws.send("Hello Message")

Comments

2

You need to add "self" to you class methods:

class MySocket(object):
    def __init__(self):
        websocket.enableTrace(True)
        self.ws = websocket.WebSocketApp("ws://echo.websocket.org:12300/foo",
                                on_message = self.on_message,
                                on_error = self.on_error,
                                on_close = self.on_close)

    def on_message(self, ws, message):
        print message

    def on_error(self, ws, error):
        print error

    def on_close(self, ws):
        print "### closed ###"

    def on_open(self, ws):
        ws.send("Hello %d" % i)

3 Comments

I does not work like that. I try: ws = MySocket(); ws.run_forever(); and I get the error: 'MySocket' object has no attribute 'run_forever'.
ws is not a WebSocketApp object. its a MySocket object. You would need to add code in the object to start it or simply call the run_foreever on the attribute like: ws.ws.run_forever()
Thank you. I think I'm just not understanding the principles behind this. I'll stop using the classes for now and just use normal functions.
1

The Self makes those methods as Class methods , Got this one working as the on_error/message/close methods signature will get satisfied if called by self as will refer to the class itself .

 class MySocket(object):
   def __init__(self,x):
     websocket.enableTrace(True)
     ## Only Keep the object Initialisation here  
     self.x=x
     self.ws=None

     # call This method from a Object and it will create and run the websocket 
    def ws_comm(self):
        self.ws = websocket.WebSocketApp(self.WS_URL,on_message = 
        self.on_message,on_error =self.on_error,on_close = self.on_close)
        self.ws.on_open = self.on_open
        self.ws.run_forever()

    def on_error(self,ws, error):
        print "onError", error

    def on_close(self,ws):
       print "onClosed"

    #Send some message on open 
    def on_open(self,ws):
       self.ws.send(json.dumps(register_msg))

    def on_message(self,ws, msg):
       self.ws.send(json.dumps(msg))


 user1=Userapp('x')
 user1.ws_comm()

Comments

0

I would like try this way:

class FooClient(object):
    def __init__(self):
        def on_message(ws, message):
            print message
            # use 'self' variable to access other resource
            # handle message from websocket server like this
            self.handler.handle(message)

        def on_error(ws, error):
            print error

        def on_close(ws):
            print "### closed ###"

        def on_open(ws):
            ws.send("Hello %d" % i)

        # assign to 'self.handler'
        self.handler = FooHandler()
        # maybe there are another module should be initiated
        # ...

        websocket.enableTrace(True)
        self.ws = websocket.WebSocketApp("ws://echo.websocket.org:12300/foo",
                                         on_message = on_message,
                                         on_error = on_error,
                                         on_close = on_close)

    def run_forever(self):
        self.ws.run_forever()

    def close(self):
        """clean other resources"""
        pass

Using inner function in method __init__(self) could avoid the problem that the arguments number of on_message(self, ws, message) method not match with the number of WebSocketApp provides to its argument on_message (class method has one more argument self).

I have a handler above to handle the message, method close(self) to clean some resources if I have, run_forever(self) to run websocket.

2 Comments

please describe the solution you provided
@DineshGhule I added some describe, if have something wrong in it please comment.
0

This is working:

class MySocket(object):
    def __init__(self):
        websocket.enableTrace(True)
        self.ws = websocket.WebSocketApp("ws://echo.websocket.org:12300/foo",
                                on_message = self.on_message,
                                on_error = self.on_error,
                                on_close = self.on_close)

    @staticmethod
    def on_message(ws, message):
        print message

    @staticmethod
    def on_error(ws, error):
        print error

    @staticmethod
    def on_close(ws):
        print "### closed ###"

    @staticmethod
    def on_open(ws):
        ws.send("Hello %d" % i)

But you don't have access to self

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.