Update documentation with new sections

This commit is contained in:
Lonami Exo
2018-10-06 20:20:11 +02:00
parent fb9660afe0
commit 8c14259728
26 changed files with 610 additions and 406 deletions

View File

@@ -1,322 +0,0 @@
.. _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.

View File

@@ -0,0 +1,171 @@
.. _compatibility-and-convenience:
=============================
Compatibility and Convenience
=============================
Telethon is an ``asyncio`` library. Compatibility is an important concern,
and while it can't always be kept and mistakes happens, the :ref:`changelog`
is there to tell you when these important changes happen.
.. contents::
Compatibility
*************
.. important::
**You should not enable the thread-compatibility mode for new projects.**
It comes with a cost, and new projects will greatly benefit from using
``asyncio`` by default such as increased speed and easier reasoning about
the code flow. You should only enable it for old projects you don't have
the time to upgrade to ``asyncio``.
There exists a fair amount of code online using Telethon before it reached
its 1.0 version, where it became fully asynchronous by default. Since it was
necessary to clean some things, compatibility was not kept 100% but the
changes are simple:
.. code-block:: python
# 1. The library no longer uses threads.
# Add this at the **beginning** of your script to work around that.
from telethon import full_sync
full_sync.enable()
# 2. client.connect() no longer returns True.
# Change this...
assert client.connect()
# ...for this:
client.connect()
# 3. client.idle() no longer exists.
# Change this...
client.idle()
# ...to this:
client.run_until_disconnected()
# 4. client.add_update_handler no longer exists.
# Change this...
client.add_update_handler(handler)
# ...to this:
client.add_event_handler(handler)
# 5. It's good practice to stop the full_sync mode once you're done
try:
... # all your code in here
finally:
full_sync.stop()
Convenience
***********
.. note::
The entire documentation assumes you have done one of the following:
.. code-block:: python
from telethon import TelegramClient, sync
# or
from telethon.sync import TelegramClient
This makes the examples shorter and easier to think about.
For quick scripts that don't need updates, it's a lot more convenient to
forget about ``full_sync`` or ``asyncio`` and just work with sequential code.
This can prove to be a powerful hybrid for running under the Python REPL too.
.. code-block:: python
from telethon.sync import TelegramClient
# ^~~~~ note this part; it will manage the asyncio loop for you
with TelegramClient(...) as client:
print(client.get_me().username)
# ^ notice the lack of await, or loop.run_until_complete().
# Since there is no loop running, this is done behind the scenes.
#
message = client.send_message('me', 'Hi!')
import time
time.sleep(5)
message.delete()
# You can also have an hybrid between a synchronous
# part and asynchronous event handlers.
#
from telethon import events
@client.on(events.NewMessage(pattern='(?i)hi|hello'))
async def handler(event):
await event.reply('hey')
client.run_until_disconnected()
Some methods, such as ``with``, ``start``, ``disconnect`` and
``run_until_disconnected`` work both in synchronous and asynchronous
contexts by default for convenience, and to avoid the little overhead
it has when using methods like sending a message, getting messages, etc.
This keeps the best of both worlds as a sane default.
.. note::
As a rule of thumb, if you're inside an ``async def`` and you need
the client, you need to ``await`` calls to the API. If you call other
functions that also need API calls, make them ``async def`` and ``await``
them too. Otherwise, there is no need to do so with this mode.
Speed
*****
When you're ready to micro-optimize your application, or if you simply
don't need to call any non-basic methods from a synchronous context,
just get rid of both ``telethon.sync`` and ``telethon.full_sync``:
.. code-block:: python
import asyncio
from telethon import TelegramClient, events
async def main():
async with TelegramClient(...) as client:
print((await client.get_me()).username)
# ^_____________________^ notice these parenthesis
# You want to ``await`` the call, not the username.
#
message = await client.send_message('me', 'Hi!')
await asyncio.sleep(5)
await message.delete()
@client.on(events.NewMessage(pattern='(?i)hi|hello'))
async def handler(event):
await event.reply('hey')
await client.run_until_disconnected()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
The ``telethon.sync`` magic module simply wraps every method behind:
.. code-block:: python
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
So that you don't have to write it yourself every time. That's the
overhead you pay if you import it, and what you save if you don't.
Learning
********
You know the library uses ``asyncio`` everywhere, and you want to learn
how to do things right. Even though ``asyncio`` is its own topic, the
documentation wants you to learn how to use Telethon correctly, and for
that, you need to use ``asyncio`` correctly too. For this reason, there
is a section called :ref:`mastering-asyncio` that will introduce you to
the ``asyncio`` world, with links to more resources for learning how to
use it. Feel free to check that section out once you have read the rest.

View File

@@ -5,6 +5,40 @@ Users, Chats and Channels
=========================
.. important::
TL;DR; If you're here because of *"Could not find the input entity for"*,
you must ask yourself "how did I find this entity through official
applications"? Now do the same with the library. Use what applies:
.. code-block:: python
with client:
# Does it have an username? Use it!
entity = client.get_entity(username)
# Do you have a conversation open with them? Get dialogs.
client.get_dialogs()
# Are they participant of some group? Get them.
client.get_participants('TelethonChat')
# Is the entity the original sender of a forwarded message? Get it.
client.get_messages('TelethonChat', 100)
# NOW you can use the ID, anywhere!
entity = client.get_entity(123456)
client.send_message(123456, 'Hi!')
Once the library has "seen" the entity, you can use their **integer** ID.
You can't use entities from IDs the library hasn't seen. You must make the
library see them *at least once* and disconnect properly. You know where
the entities are and you must tell the library. It won't guess for you.
.. contents::
Introduction
************

View File

@@ -5,6 +5,8 @@
Getting Started
===============
.. contents::
Simple Installation
*******************

View File

@@ -4,6 +4,8 @@
Installation
============
.. contents::
Automatic Installation
**********************

View File

@@ -4,10 +4,6 @@
TelegramClient
==============
Introduction
************
.. note::
Make sure to use the friendly methods described in :ref:`telethon-client`!

View File

@@ -6,14 +6,9 @@ Working with Updates
.. important::
Make sure you have read at least the first part of :ref:`asyncio-magic`
before working with updates. **This is a big change from Telethon pre-1.0
and 1.0, and your old handlers won't work with this version**.
To port your code to the new version, you should just prefix all your
event handlers with ``async`` and ``await`` everything that makes an
API call, such as replying, deleting messages, etc.
Coming from Telethon before it reached its version 1.0?
Make sure to read :ref:`compatibility-and-convenience`!
Otherwise, you can ignore this note and just follow along.
The library comes with the `telethon.events` module. *Events* are an abstraction
over what Telegram calls `updates`__, and are meant to ease simple and common