user2460638
user2460638

Reputation: 43

Python asyncio: is the error me or the API?

I am using a third party API Wrapper that supports async. I am having a difficult time figuring out how to use it as I am new to asyncio, and I'm not sure if the errors I'm getting are my usage (most likely) or the wrapper itself.

The API can be found here: https://clashroyale.readthedocs.io/en/latest/api.html#royaleapi

The goal of my code is to make three API requests at once (the .get_clan(*lClanGroup)), asynchronously instead of synchronously.

My Code:

import clashroyale
import asyncio
import aiohttp

# Define Tokens
unofficialAPIToken = "secretToken"

# Get Client Objects
session1 = aiohttp.ClientSession()
unofficialClient = clashroyale.royaleapi.Client(unofficialAPIToken, session=session1, is_async=True)

lClanGroups = ['PPCLCJG9', '2LRU2J', 'PGLQ0VQ', 'YU2RQG9', '2LVRQ29'],['PYP8UPJV', 'P9L0CYY0', 'Y2RGQPJ', '8P2GYJ8', '9VQJPL2L'],['RYPRGCJ', '809R8PG8', 'PJY9PP98', '2GCQLC', '2GL2QPPL']

async def x(lClanGroup):
    print(*lClanGroup)
    a = await unofficialClient.get_clan(*lClanGroup) # Iterates five tags for the API to request at once
    return a

async def y(lClanGroups):
    result = await asyncio.gather(x(lClanGroups[0]),x(lClanGroups[1]),x(lClanGroups[2]))
    return result

async def close_sessions():
    await session1.close()

asyncio.run(y(lClanGroups))
asyncio.run(close_sessions())

The error I'm getting is long and difficult to make sense of:

C:\Users\Adam\PycharmProjects\ClashRoyaleMeta\venv2\Scripts\python.exe C:/Users/Adam/PycharmProjects/ClashRoyaleMeta/testme2.py
PPCLCJG9 2LRU2J PGLQ0VQ YU2RQG9 2LVRQ29
PYP8UPJV P9L0CYY0 Y2RGQPJ 8P2GYJ8 9VQJPL2L
RYPRGCJ 809R8PG8 PJY9PP98 2GCQLC 2GL2QPPL
C:/Users/Adam/PycharmProjects/ClashRoyaleMeta/testme2.py:16: DeprecationWarning: The object should be created from async function
  session1 = aiohttp.ClientSession()
Traceback (most recent call last):
  File "C:/Users/Adam/PycharmProjects/ClashRoyaleMeta/testme2.py", line 33, in <module>
    asyncio.run(y(lClanGroups))
  File "C:\Program Files\Python38\lib\asyncio\runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "C:\Program Files\Python38\lib\asyncio\base_events.py", line 612, in run_until_complete
    return future.result()
  File "C:/Users/Adam/PycharmProjects/ClashRoyaleMeta/testme2.py", line 27, in y
    result = await asyncio.gather(x(lClanGroups[0]),x(lClanGroups[1]),x(lClanGroups[2]))
  File "C:/Users/Adam/PycharmProjects/ClashRoyaleMeta/testme2.py", line 23, in x
    a = await unofficialClient.get_clan(*lClanGroup)
  File "C:\Users\Adam\PycharmProjects\ClashRoyaleMeta\venv2\lib\site-packages\clashroyale\royaleapi\client.py", line 203, in _aget_model
    raise e
  File "C:\Users\Adam\PycharmProjects\ClashRoyaleMeta\venv2\lib\site-packages\clashroyale\royaleapi\client.py", line 196, in _aget_model
    data, cached, ts, resp = await self._request(url, **params)
  File "C:\Users\Adam\PycharmProjects\ClashRoyaleMeta\venv2\lib\site-packages\clashroyale\royaleapi\client.py", line 150, in _arequest
    async with self.session.get(url, timeout=timeout, headers=self.headers, params=params) as resp:
  File "C:\Users\Adam\PycharmProjects\ClashRoyaleMeta\venv2\lib\site-packages\aiohttp\client.py", line 1012, in __aenter__
    self._resp = await self._coro
  File "C:\Users\Adam\PycharmProjects\ClashRoyaleMeta\venv2\lib\site-packages\aiohttp\client.py", line 426, in _request
    with timer:
  File "C:\Users\Adam\PycharmProjects\ClashRoyaleMeta\venv2\lib\site-packages\aiohttp\helpers.py", line 579, in __enter__
    raise RuntimeError('Timeout context manager should be used '
RuntimeError: Timeout context manager should be used inside a task
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x0000016C5AD50EB0>

Process finished with exit code 1

Thanks for your help!

Upvotes: 1

Views: 247

Answers (1)

Mikhail Gerasimov
Mikhail Gerasimov

Reputation: 39546

Each asyncio.run() creates new event loop. Moreover, if you create objects like aiohttp.ClientSession() globally, they may be bind to default event loop.

Using multiple event loops in your program is dangerous and may lead to non-obvious problems. To avoid the situation:

  • Run asyncio.run() only once
  • Create all asyncio-related stuff inside main coroutine

I didn't work with royaleapi, but it seems API developers do things differently than you in their tests. Here's how they innit/close stuff and here's how they get clan.

Let's give it a try?

import clashroyale
import asyncio


TOKEN = "secretToken"

clan_groups = [
    ['PPCLCJG9', '2LRU2J', 'PGLQ0VQ', 'YU2RQG9', '2LVRQ29'],
    ['PYP8UPJV', 'P9L0CYY0', 'Y2RGQPJ', '8P2GYJ8', '9VQJPL2L'],
    ['RYPRGCJ', '809R8PG8', 'PJY9PP98', '2GCQLC', '2GL2QPPL']
]


async def get_clans(cr, clan_groups):
    return await asyncio.gather(*[
        cr.get_clan(tag)
        for group in clan_groups
        for tag in group
    ])


async def main():
    cr = clashroyale.RoyaleAPI(
        TOKEN, 
        is_async=True,
        timeout=30
    )
    try:
        results = await get_clans(cr, clan_groups)
        print(results)
    finally:
        await cr.close()
        await asyncio.sleep(2)

asyncio.run(main())

Didn't test it.

Upvotes: 1

Related Questions