78

Based on this answer I want to build an async websoket client in a class which would be imported from another file:

#!/usr/bin/env python3

import sys, json
import asyncio
from websockets import connect

class EchoWebsocket:
    def __await__(self):
        # see: https://stackoverflow.com/a/33420721/1113207
        return self._async_init().__await__()

    async def _async_init(self):
        self._conn = connect('wss://ws.binaryws.com/websockets/v3')
        self.websocket = await self._conn.__aenter__()
        return self

    async def close(self):
        await self._conn.__aexit__(*sys.exc_info())

    async def send(self, message):
        await self.websocket.send(message)

    async def receive(self):
        return await self.websocket.recv()

class mtest:
    async def start(self):
        try:
            self.wws = await EchoWebsocket()
        finally:
            await self.wws.close()

    async def get_ticks(self):
        await self.wws.send(json.dumps({'ticks_history': 'R_50', 'end': 'latest', 'count': 1}))
        return await self.wws.receive()

if __name__ == '__main__':
    a = mtest()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(a.start())

And I import it in main.py, where I have the following:

from testws import *

a = mtest()
print (a.get_ticks())
print ("this will be printed after the ticks")

But it retrieves me the following error:

root@ubupc1:/home/dinocob# python3 test.py
<coroutine object hello.get_ticks at 0x7f13190a9200>
test.py:42: RuntimeWarning: coroutine 'mtest.get_ticks' was never awaited
  print (a.get_ticks())
this will be printed after the ticks

What's is going on here? Why I'm not able to access mtest.get_ticks if it has the async word at the begining of def?

Community
  • 1
  • 1
mllamazares
  • 7,876
  • 17
  • 61
  • 89
  • You need to use `await` when you call it. – dirn Feb 02 '17 at 19:27
  • I got a synthax error when calling the function by this way: `foo = await a.get_ticks()`... – mllamazares Feb 02 '17 at 21:23
  • 3
    You can't use `await` outside of a coroutine. If you're trying to execute a coroutine outside of another one, you need to schedule it with the event loop (e.g., `loop.run_until_complete(a.get_ticks())`). – dirn Feb 02 '17 at 21:26
  • Ok, but then how may I set the return of get_ticks into a variable? May you provide me a working example of what are you talking about, please? :) – mllamazares Feb 02 '17 at 23:07
  • 4
    `foo = loop.run_until_complete(a.get_ticks())` – dirn Feb 02 '17 at 23:09
  • @dirn hey how do I make this streaming? Please advice! Many thanks https://stackoverflow.com/questions/61216022/receiving-streaming-data-after-implementing-asyncio-websockets-as-a-class – HJA24 Apr 14 '20 at 20:02

2 Answers2

101

Finally I could find the right way to do it (special thanks to @dirn)

#!/usr/bin/env python3

import sys, json
import asyncio
from websockets import connect

class EchoWebsocket:
    async def __aenter__(self):
        self._conn = connect('wss://ws.binaryws.com/websockets/v3')
        self.websocket = await self._conn.__aenter__()        
        return self

    async def __aexit__(self, *args, **kwargs):
        await self._conn.__aexit__(*args, **kwargs)

    async def send(self, message):
        await self.websocket.send(message)

    async def receive(self):
        return await self.websocket.recv()

class mtest:
    def __init__(self):
        self.wws = EchoWebsocket()
        self.loop = asyncio.get_event_loop()

    def get_ticks(self):
        return self.loop.run_until_complete(self.__async__get_ticks())

    async def __async__get_ticks(self):
        async with self.wws as echo:
            await echo.send(json.dumps({'ticks_history': 'R_50', 'end': 'latest', 'count': 1}))
            return await echo.receive()

And this in main.py:

from testws import *

a = mtest()

foo = a.get_ticks()
print (foo)

print ("async works like a charm!")

foo = a.get_ticks()
print (foo)

This is the output:

root@ubupc1:/home/dinocob# python3 test.py
{"count": 1, "end": "latest", "ticks_history": "R_50"}
async works like a charm!
{"count": 1, "end": "latest", "ticks_history": "R_50"}

Any tip to improve it is welcomed! ;)

mllamazares
  • 7,876
  • 17
  • 61
  • 89
  • 2
    Does it blocks main thread? Because my problem is running asynchronous function blocks synchronous function until it complete. – Siddharth Jun 25 '22 at 04:15
13

Your question and answer are great! They helped me a lot!

Based on your code I was able to create the following class, better matching my need:

import asyncio
from websockets import connect

class TestClient:
    def __init__(self, URL):
        self.URL = URL
        self.conn = None
        self.loop = asyncio.get_event_loop()

    async def send(self, message):
        if self.conn == None:
            self.conn = await connect(self.URL)
        await self.conn.send(message)

    async def receive(self):
        return await self.conn.recv()

    def ping(self):
        return self.loop.run_until_complete(self._ping())

    async def _ping(self):
        await self.send("Hello World")
        return await self.receive()

test = TestClient("wss://echo.websocket.org")
print(test.ping())
Ray Hulha
  • 10,701
  • 5
  • 53
  • 53
  • 1
    Doesn't `run_until_complete` block your runloop? Works great in this circumstance, but seems like using asyncio requires commitment – atlex2 Oct 14 '21 at 21:25