85

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?

7
  • You need to use await when you call it. Commented Feb 2, 2017 at 19:27
  • I got a synthax error when calling the function by this way: foo = await a.get_ticks()... Commented Feb 2, 2017 at 21:23
  • 4
    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())). Commented Feb 2, 2017 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? :) Commented Feb 2, 2017 at 23:07
  • 6
    foo = loop.run_until_complete(a.get_ticks()) Commented Feb 2, 2017 at 23:09

2 Answers 2

107

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! ;)

Sign up to request clarification or add additional context in comments.

1 Comment

Does it blocks main thread? Because my problem is running asynchronous function blocks synchronous function until it complete.
18

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())

1 Comment

Doesn't run_until_complete block your runloop? Works great in this circumstance, but seems like using asyncio requires commitment

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.