mirror of
				https://github.com/LonamiWebs/Telethon.git
				synced 2025-10-30 23:47:33 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			323 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			323 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
| .. _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 <telethon-client>` 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 <telethon-client>`
 | |
| 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()
 | |
| <telethon.client.updates.UpdateMethods.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()
 | |
|     <telethon.client.updates.UpdateMethods.run_until_disconnected>` and
 | |
|     `client.start() <telethon.client.auth.AuthMethods.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 <https://docs.python.org/3/library/asyncio.html>`_ 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 <https://github.com/python-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
 | |
| <https://github.com/LonamiWebs/Telethon/tree/sync>`_ 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 <telethon-client>` 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()
 | |
| <telethon.client.updates.UpdateMethods.run_until_disconnected>`
 | |
| which is a property that you can wait on until you call
 | |
| `await client.disconnect()
 | |
| <telethon.client.telegrambaseclient.TelegramBaseClient.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()
 | |
| <telethon.client.updates.UpdateMethods.run_until_disconnected>` and
 | |
| `client.start()
 | |
| <telethon.client.auth.AuthMethods.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
 | |
| <https://lonamiwebs.github.io/blog/asyncio/>`_ that goes into more detail.
 |