Qluxzz / avanza

A Python library for the unofficial Avanza API
https://qluxzz.github.io/avanza/
MIT License
85 stars 40 forks source link

Handling exceptions in the API: The cause of the issue and a bad kludge workaround #86

Open 00prometheus opened 1 year ago

00prometheus commented 1 year ago

Right now, the Task returned by asyncio.ensure_future() in avanza_socket.init() is not stored or handled by the API. A reference to the Task must be maintained for as long as the Task is active, or else the Task gets garbage collected at some random time. You also need the reference to be able to either await or asyncio.gather() the Task so you can catch any exception from the connection (e.g. ConnectionClosedError).

You further need to regularly access Avanza within the account's timeout period to keep the login alive (you can select the timeout in your account's settings on Avanza).

Since the Task is not returned from the API right now, I have implemented a bad kludge as a temporary workaround. I use asyncio.all_tasks() to get all Tasks in the process and dig into the Task's internals (works in Python 3.11) to guess which one is the right one. I have a separate loop where I call avanza.get_position() and an asyncio.sleep() to do the keep-alive. So this is my code right now:

task_list = asyncio.all_tasks()
avanza_coro=None
for coro in task_list:
    if coro.get_stack()[0].f_code.co_name == "__create_socket":
        avanza_coro=coro                                                                               
        break
keep_alive_future = asyncio.create_task(keep_alive(avanza))
task_results=await asyncio.gather(avanza_coro, keep_alive_future)

I have a try-catch around the whole thing and recreate the Avanza connection whenever I get an exception.

It would be good if the API would give us the Task properly so I could make that code a bit nicer.

Qluxzz commented 2 months ago

What code changes would be needed for this to work? Is just returning the task from ensure_future enough for it to work, or do you see a better way to structure it?

async def init(self):
    task = asyncio.ensure_future(self.__create_socket())
    await self.__wait_for_websocket_to_be_connected()
    return task