.. _asyncio-magic: ================== Magic with asyncio ================== .. important:: TL; DR; If you've upgraded to Telethon 1.0 from a previous version **and you're not using events or updates**, add this line: .. code-block:: python import telethon.sync At the beginning of your main script and you will be good. If you **do** use updates or events, keep reading, or ``pip install telethon-sync``, a branch that mimics the ``asyncio`` code with threads and should work under Python 3.4. You might also want to check the :ref:`changelog`. The sync module *************** It's time to tell you the truth. The library has been doing magic behind the scenes. We're sorry to tell you this, but at least it wasn't dark magic! You may have noticed one of these lines across the documentation: .. code-block:: python from telethon import sync # or import telethon.sync Either of these lines will import the *magic* ``sync`` module. When you import this module, you can suddenly use all the methods defined in the :ref:`TelegramClient ` like so: .. code-block:: python client.send_message('me', 'Hello!') for dialog in client.iter_dialogs(): print(dialog.title) What happened behind the scenes is that all those methods, called *coroutines*, were rewritten to be normal methods that will block (with some exceptions). This means you can use the library without worrying about ``asyncio`` and event loops. However, this only works until you run the event loop yourself explicitly: .. code-block:: python import asyncio async def coro(): client.send_message('me', 'Hello!') # <- no longer works! loop = asyncio.get_event_loop() loop.run_until_complete(coro()) What things will work and when? ******************************* You can use all the methods in the :ref:`TelegramClient ` in a synchronous, blocking way without trouble, as long as you're not running the loop as we saw above (the ``loop.run_until_complete(...)`` line runs "the loop"). If you're running the loop, then *you* are the one responsible to ``await`` everything. So to fix the code above: .. code-block:: python import asyncio async def coro(): await client.send_message('me', 'Hello!') # ^ notice this new await loop = asyncio.get_event_loop() loop.run_until_complete(coro()) The library can only run the loop until the method completes if the loop isn't already running, which is why the magic can't work if you run the loop yourself. **When you work with updates or events**, the loop needs to be running one way or another (using `client.run_until_disconnected() ` runs the loop), so your event handlers must be ``async def``. .. important:: Turning your event handlers into ``async def`` is the biggest change between Telethon pre-1.0 and 1.0, but updating will likely cause a noticeable speed-up in your programs. Keep reading! So in short, you can use **all** methods in the client with ``await`` or without it if the loop isn't running: .. code-block:: python client.send_message('me', 'Hello!') # works async def main(): await client.send_message('me', 'Hello!') # also works loop.run_until_complete(main()) When you work with updates, you should stick using the ``async def main`` way, since your event handlers will be ``async def`` too. .. note:: There are two exceptions. Both `client.run_until_disconnected() ` and `client.start() ` work in and outside of ``async def`` for convenience without importing the magic module. The rest of methods remain ``async`` unless you import it. You can skip the rest if you already know how ``asyncio`` works and you already understand what the magic does and how it works. Just remember to ``await`` all your methods if you're inside an ``async def`` or are using updates and you will be good. Why asyncio? ************ Python's `asyncio `_ is the standard way to run asynchronous code from within Python. Since Python 3.5, using ``async def`` and ``await`` became possible, and Python 3.6 further improves what you can do with asynchronous code, although it's not the only way (other projects like `Trio `_ also exist). Telegram is a service where all API calls are executed in an asynchronous way. You send your request, and eventually, Telegram will process it and respond to it. It feels natural to make a library that also behaves this way: you send a request, and you can ``await`` for its result. Now that we know that Telegram's API follows an asynchronous model, you should understand the benefits of developing a library that does the same, it greatly simplifies the internal code and eases working with the API. Using ``asyncio`` keeps a cleaner library that will be easier to understand, develop, and that will be faster than using threads, which are harder to get right and can cause issues. It also enables to use the powerful ``asyncio`` system such as futures, timeouts, cancellation, etc. in a natural way. If you're still not convinced or you're just not ready for using ``asyncio``, the library offers a synchronous interface without the need for all the ``async`` and ``await`` you would otherwise see. `Follow this link `_ to find out more. How do I get started? ********************* To get started with ``asyncio``, all you need is to setup your main ``async def`` like so: .. code-block:: python import asyncio async def main(): pass # Your code goes here if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main()) You don't need to ``import telethon.sync`` if you're going to work this way. This is the best way to work in real programs since the loop won't be starting and ending all the time, but is a bit more annoying to setup. Inside ``async def main()``, you can use the ``await`` keyword. Most methods in the :ref:`TelegramClient ` are ``async def``. You must ``await`` all ``async def``, also known as a *coroutines*: .. code-block:: python async def main(): client = TelegramClient(...) # client.start() is a coroutine (async def), it needs an await await client.start() # Sending a message also interacts with the API, and needs an await await client.send_message('me', 'Hello myself!') If you don't know anything else about ``asyncio``, this will be enough to get you started. Once you're ready to learn more about it, you will be able to use that power and everything you've learnt with Telethon. Just remember that if you use ``await``, you need to be inside of an ``async def``. Another way to use ``async def`` is to use ``loop.run_until_complete(f())``, but the loop must not be running before. If you want to handle updates (and don't let the script die), you must `await client.run_until_disconnected() ` which is a property that you can wait on until you call `await client.disconnect() `: .. code-block:: python client = TelegramClient(...) @client.on(events.NewMessage) async def handler(event): print(event) async def main(): await client.start() await client.run_until_disconnected() if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main()) `client.run_until_disconnected() ` and `client.start() ` are special-cased and work inside or outside ``async def`` for convenience, even without importing the ``sync`` module, so you can also do this: .. code-block:: python client = TelegramClient(...) @client.on(events.NewMessage) async def handler(event): print(event) if __name__ == '__main__': client.start() client.run_until_disconnected() Which methods should I use and when? ************************************ Something to note is that you must always get an event loop if you want to be able to make any API calls. This is done as follows: .. code-block:: python import asyncio loop = asyncio.get_event_loop() The loop must be running, or things will never get sent. Normally, you use ``run_until_complete``: .. code-block:: python async def coroutine(): await asyncio.sleep(1) loop.run_until_complete(coroutine()) Note that ``asyncio.sleep`` is in itself a coroutine, so this will work too: .. code-block:: python loop.run_until_complete(asyncio.sleep(1)) Generally, you make an ``async def main()`` if you need to ``await`` a lot of things, instead of typing ``run_until_complete`` all the time: .. code-block:: python async def main(): message = await client.send_message('me', 'Hi') await asyncio.sleep(1) await message.delete() loop.run_until_complete(main()) # vs message = loop.run_until_complete(client.send_message('me', 'Hi')) loop.run_until_complete(asyncio.sleep(1)) loop.run_until_complete(message.delete()) You can see that the first version has more lines, but you had to type a lot less. You can also rename the run method to something shorter: .. code-block:: python # Note no parenthesis (), we're not running it, just copying the method rc = loop.run_until_complete message = rc(client.send_message('me', 'Hi')) rc(asyncio.sleep(1)) rc(message.delete()) The documentation generally runs the loop until complete behind the scenes if you've imported the magic ``sync`` module, but if you haven't, you need to run the loop yourself. We recommend that you use the ``async def main()`` method to do all your work with ``await``. It's the easiest and most performant thing to do. More resources to learn asyncio ******************************* If you would like to learn a bit more about why ``asyncio`` is something you should learn, `check out my blog post `_ that goes into more detail.