pythontelegram

Why does await client(...) in Telethon work but give an __await__ warning in my IDE?


I'm learning Telethon and have encountered some confusing IDE behavior. My asynchronous code to call Telegram API methods works perfectly, but my IDE (PyCharm) shows a warning:

Class 'TelegramClient' does not define 'await', so the 'await' operator cannot be used on its instances

async def main():
    client = TelegramClient('test', api_id, api_hash)
    await client.start()
    result = await client(functions.payments.GetStarGiftsRequest(hash=0))
    for i in result.gifts:
            print(f'''Имя: {i.title},
            - id {i.id}
            - Stars: {i.stars},
            - Сколько продаётся на рынке перепродажи: {i.availability_resale}
            - Осталось для покупки: {i.availability_remains}
            - Всего: {i.availability_total}
            - first_sale_date: {i.first_sale_date}
            - require_premium: {i.require_premium}
            - sold_out: {i.sold_out}
            - attributes:{i.sticker.attributes[1].alt}''')
    me = await client.get_entity('me')
    print(me)
    text = TextWithEntities('123', entities=[
        MessageEntityBold(offset=0, length=12)
    ])
    receiver_peer = await client.get_input_entity('@misha20062006')
    invoice = InputInvoiceStarGift(peer=receiver_peer, gift_id=bar_gift, hide_name=True, message=text)
    payment_form = await client(GetPaymentFormRequest(invoice=invoice))
    try:
        pay = await client(functions.payments.SendStarsFormRequest(
        form_id=payment_form.form_id,
        invoice=invoice))
        print(pay)
    except Exception as e:
        print('Ошибка: Слишком низкий баланс', e)

asyncio.run(main())

I would like to learn correctly, so despite the code working, it is important for me to understand why I am getting this warning and what would be the most correct way to write it?

I expected not to see any unnecessary warnings since my code works.


Solution

  • The library is not correctly typed for asynchronous code. It only has a correct function signature if you were running the code synchronously. The signature for start() is: start(...) -> 'TelegramClient'. Which if it were correct would mean that await client.start() would return an error, as TelegramClient does not have an __await__() function. However, if you look in the code of start() you can see that it is doing something tricky. That is, it conditionally returns a coroutine, depending on the context in which start() was called.

         def start(self, ...) -> 'TelegramClient':
            ...
            coro = self._start(...)
            return (
                coro if self.loop.is_running()
                else self.loop.run_until_complete(coro)
            )
    

    You can see that if the code is called from within a running event loop (eg. await client.start()), then the function does not actually return an instance of TelegramClient, but rather it returns a coroutine that when awaited returns an instance of TelegramClient. If the code is not called from a running event loop, then the code instead awaits the coroutine and returns the result (self.loop.run_until_complete(coro)).

    Basically, you are using the library correctly, but the library maintainers have not written correct function signatures for when the client is being used asynchronously. And so any static type checker will incorrectly think you are using the library incorrectly. The correct return annotation is actually: TelegramClient | typing.Awaitable[TelegramClient].