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

@ -4,16 +4,13 @@ Telethon
⭐️ Thanks **everyone** who has starred the project, it means a lot! ⭐️ Thanks **everyone** who has starred the project, it means a lot!
|logo| **Telethon** is an `asyncio |logo| **Telethon** is an asyncio_ **Python 3**
<https://docs.python.org/3/library/asyncio.html>`_ **Python 3** library MTProto_ library to interact with Telegram_'s API.
to interact with Telegram's API.
**If you're upgrading from Telethon pre-1.0 to 1.0, please make sure to read** .. important::
`this section of the documentation
<https://telethon.readthedocs.io/en/latest/extra/basic/asyncio-magic.html>`_, If you have code using Telethon before its 1.0 version, you must
or ``pip install telethon-sync`` which is compatible with `synchronous code read `Compatibility and Convenience`_ to learn how to migrate.
<https://github.com/LonamiWebs/Telethon/tree/sync>`_. Don't forget to remove
the asynchronous version (``pip uninstall telethon``) if you do install sync.
What is this? What is this?
------------- -------------
@ -27,7 +24,7 @@ heavy job for you, so you can focus on developing an application.
Installing Installing
---------- ----------
.. code:: sh .. code-block:: sh
pip3 install telethon pip3 install telethon
@ -35,9 +32,9 @@ Installing
Creating a client Creating a client
----------------- -----------------
.. code:: python .. code-block:: python
from telethon import TelegramClient, sync from telethon import TelegramClient, events, sync
# These example values won't work. You must get your own api_id and # These example values won't work. You must get your own api_id and
# api_hash from https://my.telegram.org, under API Development. # api_hash from https://my.telegram.org, under API Development.
@ -51,7 +48,7 @@ Creating a client
Doing stuff Doing stuff
----------- -----------
.. code:: python .. code-block:: python
print(client.get_me().stringify()) print(client.get_me().stringify())
@ -62,14 +59,23 @@ Doing stuff
messages = client.get_messages('username') messages = client.get_messages('username')
messages[0].download_media() messages[0].download_media()
@client.on(events.NewMessage(pattern='(?i)hi|hello'))
async def handler(event):
await event.respond('Hey!')
Next steps Next steps
---------- ----------
Do you like how Telethon looks? Check out `Read The Docs Do you like how Telethon looks? Check out `Read The Docs`_ for a more
<http://telethon.rtfd.io/>`_ for a more in-depth explanation, in-depth explanation, with examples, troubleshooting issues, and more
with examples, troubleshooting issues, and more useful information. useful information.
.. _asyncio: https://docs.python.org/3/library/asyncio.html
.. _MTProto: https://core.telegram.org/mtproto
.. _Telegram: https://telegram.org
.. _Compatibility and Convenience: https://telethon.readthedocs.io/en/latest/extra/basic/compatibility-and-convenience.html
.. _Read The Docs: https://telethon.readthedocs.io
.. |logo| image:: logo.svg .. |logo| image:: logo.svg
:width: 24pt :width: 24pt

View File

@ -29,9 +29,9 @@ can go through a sorted list of everything you can do.
.. important:: .. important::
All the examples in this documentation assume that you have All the examples in this documentation assume that you have
``from telethon import sync`` or ``import telethon.sync`` ``from telethon import sync`` or ``import telethon.sync`` for the
for the sake of simplicity and that you understand what sake of simplicity and that you understand what it does (see
it does (see :ref:`asyncio-magic` for more). Simply add :ref:`compatibility-and-convenience` for more). Simply add
either line at the beginning of your project and it will work. either line at the beginning of your project and it will work.
@ -108,10 +108,9 @@ every time its used, simply call `telethon.utils.get_input_peer`:
.. note:: .. note::
Since ``v0.16.2`` this is further simplified. The ``Request`` itself Since ``v0.16.2`` this is further simplified. The ``Request`` itself
will call `client.get_input_entity < will call `client.get_input_entity
telethon.client.users.UserMethods.get_input_entity>` for you when required, <telethon.client.users.UserMethods.get_input_entity>` for you when
but it's good to remember what's happening. required, but it's good to remember what's happening.
After this small parenthesis about `client.get_entity After this small parenthesis about `client.get_entity
<telethon.client.users.UserMethods.get_entity>` versus <telethon.client.users.UserMethods.get_entity>` versus

View File

@ -0,0 +1,319 @@
.. _mastering-asyncio:
=================
Mastering asyncio
=================
.. contents::
What's asyncio?
***************
asyncio_ is a Python 3's built-in library. This means it's already installed if
you have Python 3. Since Python 3.5, it is convenient to work with asynchronous
code. Before (Python 3.4) we didn't have ``async`` or ``await``, but now we do.
asyncio_ stands for *Asynchronous Input Output*. This is a very powerful
concept to use whenever you work IO. Interacting with the web or external
APIs such as Telegram's makes a lot of sense this way.
Why asyncio?
************
Asynchronous IO makes a lot of sense in a library like Telethon.
You send a request to the server (such as "get some message"), and
thanks to asyncio_, your code won't block while a response arrives.
The alternative would be to spawn a thread for each update so that
other code can run while the response arrives. That is *a lot* more
expensive.
The code will also run faster, because instead of switching back and
forth between the OS and your script, your script can handle it all.
Avoiding switching saves quite a bit of time, in Python or any other
language that supports asynchronous IO. It will also be cheaper,
because tasks are smaller than threads, which are smaller than processes.
What are asyncio basics?
************************
.. code-block:: python
# First we need the asyncio library
import asyncio
# Then we need a loop to work with
loop = asyncio.get_event_loop()
# We also need something to run
async def main():
for char in 'Hello, world!\n':
print(char, end='', flush=True)
await asyncio.sleep(0.2)
# Then, we need to run the loop with a task
loop.run_until_complete(main())
What does telethon.sync do?
***************************
The moment you import any of these:
.. code-block:: python
from telethon import sync, ...
# or
from telethon.sync import ...
# or
import telethon.sync
The ``sync`` module rewrites most ``async def``
methods in Telethon to something similar to this:
.. code-block:: python
def new_method():
result = original_method()
if loop.is_running():
# the loop is already running, return the await-able to the user
return result
else:
# the loop is not running yet, so we can run it for the user
return loop.run_until_complete(result)
That means you can do this:
.. code-block:: python
print(client.get_me().username)
Instead of this:
.. code-block:: python
import asyncio
loop = asyncio.get_event_loop()
me = loop.run_until_complete(client.get_me())
print(me.username)
As you can see, it's a lot of boilerplate and noise having to type
``run_until_complete`` all the time, so you can let the magic module
to rewrite it for you. But notice the comment above: it won't run
the loop if it's already running, because it can't. That means this:
.. code-block:: python
async def main():
# 3. the loop is running here
print(
client.get_me() # 4. this will return a coroutine!
.username # 5. this fails, coroutines don't have usernames
)
loop.run_until_complete( # 2. run the loop and the ``main()`` coroutine
main() # 1. calling ``async def`` "returns" a coroutine
)
Will fail. So if you're inside an ``async def``, then the loop is
running, and if the loop is running, you must ``await`` things yourself:
.. code-block:: python
async def main():
print((await client.get_me()).username)
loop.run_until_complete(main())
What are async, await and coroutines?
*************************************
The ``async`` keyword lets you define asynchronous functions,
also known as coroutines, and also iterate over asynchronous
loops or use ``async with``:
.. code-block:: python
import asyncio
async def main():
# ^ this declares the main() coroutine function
async with client:
# ^ this is an asynchronous with block
async for message in client.iter_messages(chat):
# ^ this is a for loop over an asynchronous generator
print(message.sender.username)
loop = asyncio.get_event_loop()
# ^ this assigns the default event loop from the main thread to a variable
loop.run_until_complete(main())
# ^ this runs the *entire* loop until the main() function finishes.
# While the main() function does not finish, the loop will be running.
# While the loop is running, you can't run it again.
The ``await`` keyword blocks the *current* task, and the loop can run
other tasks. Tasks can be thought of as "threads", since many can run
concurrently:
.. code-block:: python
import asyncio
async def hello(delay):
await asyncio.sleep(delay) # await tells the loop this task is "busy"
print('hello') # eventually the loop resumes the code here
async def world(delay):
# the loop decides this method should run first
await asyncio.sleep(delay) # await tells the loop this task is "busy"
print('world') # eventually the loop finishes all tasks
loop = asyncio.get_event_loop() # get the default loop for the main thread
loop.create_task(world(2)) # create the world task, passing 2 as delay
loop.create_task(hello(delay=1)) # another task, but with delay 1
try:
# run the event loop forever; ctrl+c to stop it
# we could also run the loop for three seconds:
# loop.run_until_complete(asyncio.sleep(3))
loop.run_forever()
except KeyboardInterrupt:
pass
The same example, but without the comment noise:
.. code-block:: python
import asyncio
async def hello(delay):
await asyncio.sleep(delay)
print('hello')
async def world(delay):
await asyncio.sleep(delay)
print('world')
loop = asyncio.get_event_loop()
loop.create_task(world(2))
loop.create_task(hello(1))
loop.run_until_complete(asyncio.sleep(3))
Can I use threads?
******************
Yes, you can, but you must understand that the loops themselves are
not thread safe. and you must be sure to know what is happening. You
may want to create a loop in a new thread and make sure to pass it to
the client:
.. code-block:: python
import asyncio
import threading
def go():
loop = asyncio.new_event_loop()
client = TelegramClient(..., loop=loop)
...
threading.Thread(target=go).start()
Generally, **you don't need threads** unless you know what you're doing.
Just create another task, as shown above. If you're using the Telethon
with a library that uses threads, you must be careful to use ``threading.Lock``
whenever you use the client, or enable the compatible mode. For that, see
:ref:`compatibility-and-convenience`.
You may have seen this error:
.. code-block:: text
RuntimeError: There is no current event loop in thread 'Thread-1'.
It just means you didn't create a loop for that thread, and if you don't
pass a loop when creating the client, it uses ``asyncio.get_event_loop()``,
which only works in the main thread.
What else can asyncio do?
*************************
Asynchronous IO is a really powerful tool, as we've seen. There are plenty
of other useful libraries that also use asyncio_ and that you can integrate
with Telethon.
* `aiocron <https://github.com/gawel/aiocron>`_ lets you schedule
things to run things at a desired time, or run some tasks daily.
* `aiohttp <https://github.com/aio-libs/aiohttp>`_ is like the infamous
`requests <https://github.com/requests/requests/>`_ but asynchronous.
* `quart <https://gitlab.com/pgjones/quart>`_ is an asynchronous alternative
to `Flask <http://flask.pocoo.org/>`_.
And of course, `asyncio <https://docs.python.org/3/library/asyncio.html>`_
itself! It has a lot of methods that let you do nice things. For example,
you can run requests in parallel:
.. code-block:: python
async def main():
last, sent, download_path = await asyncio.gather(
client.get_messages('TelethonChat', 10),
client.send_message('TelethonOfftopic', 'Hey guys!'),
client.download_profile_photo('TelethonChat')
)
loop.run_until_complete(main())
This code will get the 10 last messages from `@TelethonChat
<https://t.me/TelethonChat>`_, send one to `@TelethonOfftopic
<https://t.me/TelethonOfftopic>`_, and also download the profile
photo of the main group. asyncio_ will run all these three tasks
at the same time. You can run all the tasks you want this way.
A different way would be:
.. code-block:: python
loop.create_task(client.get_messages('TelethonChat', 10))
loop.create_task(client.send_message('TelethonOfftopic', 'Hey guys!'))
loop.create_task(client.download_profile_photo('TelethonChat'))
They will run in the background as long as the loop is running too.
Why does client.start() work outside async?
*******************************************
Because it's so common that it's really convenient to offer said
functionality by default. This means you can set up all your event
handlers and start the client without worrying about loops at all.
Using the client in a ``with`` block, `start
<telethon.client.auth.AuthMethods.start>`, `run_until_disconnected
<telethon.client.updates.UpdateMethods.run_until_disconnected>`, and
`disconnect <telethon.client.telegrambaseclient.TelegramBaseClient.disconnect>`
all support this.
Where can I read more?
**********************
`Check out my blog post
<https://lonamiwebs.github.io/blog/asyncio/>`_ about asyncio_, which
has some more examples and pictures to help you understand what happens
when the loop runs.
.. _asyncio: https://docs.python.org/3/library/asyncio.html

View File

@ -4,6 +4,11 @@
Session Files Session Files
============== ==============
.. contents::
What are sessions?
******************
The first parameter you pass to the constructor of the The first parameter you pass to the constructor of the
:ref:`TelegramClient <telethon-client>` is :ref:`TelegramClient <telethon-client>` is
the ``session``, and defaults to be the session name (or full path). That is, the ``session``, and defaults to be the session name (or full path). That is,

View File

@ -27,7 +27,6 @@ Behind the scenes, this method is ``await``'ing on the `client.disconnected
<telethon.client.telegrambaseclient.TelegramBaseClient.disconnected>` property, <telethon.client.telegrambaseclient.TelegramBaseClient.disconnected>` property,
so the code above and the following are equivalent: so the code above and the following are equivalent:
.. code-block:: python .. code-block:: python
import asyncio import asyncio
@ -63,3 +62,12 @@ also be ran while the loop is running, so you can do this:
await client.run_until_disconnected() await client.run_until_disconnected()
loop.run_until_complete(main()) loop.run_until_complete(main())
If you need to process updates sequentially (i.e. not in parallel),
you should set ``sequential_updates=True`` when creating the client:
.. code-block:: python
with TelegramClient(..., sequential_updates=True) as client:
...

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 Introduction
************ ************

View File

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

View File

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

View File

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

View File

@ -6,14 +6,9 @@ Working with Updates
.. important:: .. important::
Make sure you have read at least the first part of :ref:`asyncio-magic` Coming from Telethon before it reached its version 1.0?
before working with updates. **This is a big change from Telethon pre-1.0 Make sure to read :ref:`compatibility-and-convenience`!
and 1.0, and your old handlers won't work with this version**. Otherwise, you can ignore this note and just follow along.
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.
The library comes with the `telethon.events` module. *Events* are an abstraction 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 over what Telegram calls `updates`__, and are meant to ease simple and common

View File

@ -453,11 +453,8 @@ Synchronous magic (v1.0)
.. important:: .. important::
If you come from Telethon pre-1.0 you **really** want to read If you come from Telethon pre-1.0 you **really** want to read
:ref:`asyncio-magic` to port your scripts to the new version. :ref:`compatibility-and-convenience` to port your scripts to
the new version.
If you're not ready for this, you can ``pip install telethon-sync``.
It's a synchronous branch that mimics the ``asyncio`` version with
threads and should work under Python 3.4
The library has been around for well over a year. A lot of improvements have The library has been around for well over a year. A lot of improvements have
been made, a lot of user complaints have been fixed, and a lot of user desires been made, a lot of user complaints have been fixed, and a lot of user desires

View File

@ -7,6 +7,8 @@ Bots
These examples assume you have read :ref:`accessing-the-full-api`. These examples assume you have read :ref:`accessing-the-full-api`.
.. contents::
Talking to Inline Bots Talking to Inline Bots
********************** **********************

View File

@ -7,6 +7,8 @@ Working with Chats and Channels
These examples assume you have read :ref:`accessing-the-full-api`. These examples assume you have read :ref:`accessing-the-full-api`.
.. contents::
Joining a chat or channel Joining a chat or channel
************************* *************************

View File

@ -60,6 +60,7 @@ the ``telethon.sync`` package and that you have a client ready to use.
.. contents:: .. contents::
Authorization Authorization
************* *************

View File

@ -7,6 +7,8 @@ Users
These examples assume you have read :ref:`accessing-the-full-api`. These examples assume you have read :ref:`accessing-the-full-api`.
.. contents::
Retrieving full information Retrieving full information
*************************** ***************************

View File

@ -7,6 +7,8 @@ Working with messages
These examples assume you have read :ref:`accessing-the-full-api`. These examples assume you have read :ref:`accessing-the-full-api`.
.. contents::
Forwarding messages Forwarding messages
******************* *******************

View File

@ -15,25 +15,10 @@ or use the menu on the left. Remember to read the :ref:`changelog`
when you upgrade! when you upgrade!
.. important:: .. important::
If you're new here, you want to read :ref:`getting-started`. If you're
looking for the method reference, you should check :ref:`telethon-client`.
The mentioned :ref:`telethon-client` is an important section and it * Are you new here? Jump straight into :ref:`getting-started`!
contains the friendly methods that **you should use** most of the time. * Looking for available friendly methods? See :ref:`telethon-client`.
* Used Telethon before v1.0? See :ref:`compatibility-and-convenience`.
.. note::
The library uses `asyncio <https://docs.python.org/3/library/asyncio.html>`_
under the hood, but you don't need to know anything about it unless you're
going to work with updates! If you're a user of Telethon pre-1.0 and you
aren't ready to convert your event handlers into ``async``, you can use
`a simpler version <https://github.com/LonamiWebs/Telethon/tree/sync>`_
(select the "sync" version in ``readthedocs``' bottom left corner).
If you used Telethon pre-1.0 but your scripts don't use updates or threads,
running ``import telethon.sync`` should make them Just Work. Otherwise,
we have :ref:`asyncio-magic` to teach you why ``asyncio`` is good and
how to use it.
What is this? What is this?
@ -56,8 +41,8 @@ heavy job for you, so you can focus on developing an application.
extra/basic/creating-a-client extra/basic/creating-a-client
extra/basic/telegram-client extra/basic/telegram-client
extra/basic/entities extra/basic/entities
extra/basic/asyncio-magic
extra/basic/working-with-updates extra/basic/working-with-updates
extra/basic/compatibility-and-convenience
.. _Advanced-usage: .. _Advanced-usage:
@ -70,6 +55,7 @@ heavy job for you, so you can focus on developing an application.
extra/advanced-usage/sessions extra/advanced-usage/sessions
extra/advanced-usage/update-modes extra/advanced-usage/update-modes
extra/advanced-usage/mastering-telethon extra/advanced-usage/mastering-telethon
extra/advanced-usage/mastering-asyncio
.. _Examples: .. _Examples:

View File

@ -25,11 +25,3 @@ telethon\.extensions\.html module
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
telethon\.extensions\.tcpclient module
--------------------------------------
.. automodule:: telethon.extensions.tcpclient
:members:
:undoc-members:
:show-inheritance:

View File

@ -28,7 +28,6 @@ class MessageMethods(UploadMethods, ButtonMethods, MessageParseMethods):
reverse=False, _total=None): reverse=False, _total=None):
""" """
Iterator over the message history for the specified entity. Iterator over the message history for the specified entity.
If either `search`, `filter` or `from_user` are provided, If either `search`, `filter` or `from_user` are provided,
:tl:`messages.Search` will be used instead of :tl:`messages.getHistory`. :tl:`messages.Search` will be used instead of :tl:`messages.getHistory`.
@ -48,6 +47,7 @@ class MessageMethods(UploadMethods, ButtonMethods, MessageParseMethods):
Number of messages to be retrieved. Due to limitations with Number of messages to be retrieved. Due to limitations with
the API retrieving more than 3000 messages will take longer the API retrieving more than 3000 messages will take longer
than half a minute (or even more based on previous calls). than half a minute (or even more based on previous calls).
The limit may also be ``None``, which would eventually return The limit may also be ``None``, which would eventually return
the whole history. the whole history.
@ -411,8 +411,7 @@ class MessageMethods(UploadMethods, ButtonMethods, MessageParseMethods):
Whether the existing draft should be cleared or not. Whether the existing draft should be cleared or not.
Has no effect when sending a file. Has no effect when sending a file.
buttons (`list`, `custom.Button <telethon.tl.custom.button.Button>`, buttons (`list`, `custom.Button <telethon.tl.custom.button.Button>`, :tl:`KeyboardButton`):
:tl:`KeyboardButton`):
The matrix (list of lists), row list or button to be shown The matrix (list of lists), row list or button to be shown
after sending the message. This parameter will only work if after sending the message. This parameter will only work if
you have signed in as a bot. You can also pass your own you have signed in as a bot. You can also pass your own
@ -616,8 +615,7 @@ class MessageMethods(UploadMethods, ButtonMethods, MessageParseMethods):
The file object that should replace the existing media The file object that should replace the existing media
in the message. in the message.
buttons (`list`, `custom.Button <telethon.tl.custom.button.Button>`, buttons (`list`, `custom.Button <telethon.tl.custom.button.Button>`, :tl:`KeyboardButton`):
:tl:`KeyboardButton`):
The matrix (list of lists), row list or button to be shown The matrix (list of lists), row list or button to be shown
after sending the message. This parameter will only work if after sending the message. This parameter will only work if
you have signed in as a bot. You can also pass your own you have signed in as a bot. You can also pass your own

View File

@ -71,6 +71,10 @@ class UploadMethods(ButtonMethods, MessageParseMethods, UserMethods):
Optional JPEG thumbnail (for documents). **Telegram will Optional JPEG thumbnail (for documents). **Telegram will
ignore this parameter** unless you pass a ``.jpg`` file! ignore this parameter** unless you pass a ``.jpg`` file!
The file must also be small in dimensions and in-disk size.
Successful thumbnails were files below 20kb and 200x200px.
Width/height and dimensions/size ratios may be important.
allow_cache (`bool`, optional): allow_cache (`bool`, optional):
Whether to allow using the cached version stored in the Whether to allow using the cached version stored in the
database or not. Defaults to ``True`` to avoid re-uploads. database or not. Defaults to ``True`` to avoid re-uploads.
@ -94,8 +98,7 @@ class UploadMethods(ButtonMethods, MessageParseMethods, UserMethods):
Set `allow_cache` to ``False`` if you sent the same file Set `allow_cache` to ``False`` if you sent the same file
without this setting before for it to work. without this setting before for it to work.
buttons (`list`, `custom.Button <telethon.tl.custom.button.Button>`, buttons (`list`, `custom.Button <telethon.tl.custom.button.Button>`, :tl:`KeyboardButton`):
:tl:`KeyboardButton`):
The matrix (list of lists), row list or button to be shown The matrix (list of lists), row list or button to be shown
after sending the message. This parameter will only work if after sending the message. This parameter will only work if
you have signed in as a bot. You can also pass your own you have signed in as a bot. You can also pass your own

View File

@ -304,7 +304,7 @@ class UserMethods(TelegramBaseClient):
return utils.get_input_peer(peer) return utils.get_input_peer(peer)
raise ValueError( raise ValueError(
'Could not find the input entity for "{}". Please read https://' 'Could not find the input entity for {!r}. Please read https://'
'telethon.readthedocs.io/en/latest/extra/basic/entities.html to' 'telethon.readthedocs.io/en/latest/extra/basic/entities.html to'
' find out more details.' ' find out more details.'
.format(peer) .format(peer)

View File

@ -34,8 +34,7 @@ class InlineBuilder:
game (`bool`, optional): game (`bool`, optional):
May be ``True`` to indicate that the game will be sent. May be ``True`` to indicate that the game will be sent.
buttons (`list`, `custom.Button <telethon.tl.custom.button.Button>`, buttons (`list`, `custom.Button <telethon.tl.custom.button.Button>`, :tl:`KeyboardButton`, optional):
:tl:`KeyboardButton`, optional):
Same as ``buttons`` for `client.send_message Same as ``buttons`` for `client.send_message
<telethon.client.messages.MessageMethods.send_message>`. <telethon.client.messages.MessageMethods.send_message>`.

View File

@ -33,6 +33,9 @@ mimetypes.add_type('audio/ogg', '.ogg')
USERNAME_RE = re.compile( USERNAME_RE = re.compile(
r'@|(?:https?://)?(?:www\.)?(?:telegram\.(?:me|dog)|t\.me)/(joinchat/)?' r'@|(?:https?://)?(?:www\.)?(?:telegram\.(?:me|dog)|t\.me)/(joinchat/)?'
) )
TG_JOIN_RE = re.compile(
r'tg://(join)?invite='
)
# The only shorter-than-five-characters usernames are those used for some # The only shorter-than-five-characters usernames are those used for some
# special, very well known bots. This list may be incomplete though: # special, very well known bots. This list may be incomplete though:
@ -646,15 +649,16 @@ def parse_phone(phone):
def parse_username(username): def parse_username(username):
"""Parses the given username or channel access hash, given """
Parses the given username or channel access hash, given
a string, username or URL. Returns a tuple consisting of a string, username or URL. Returns a tuple consisting of
both the stripped, lowercase username and whether it is both the stripped, lowercase username and whether it is
a joinchat/ hash (in which case is not lowercase'd). a joinchat/ hash (in which case is not lowercase'd).
Returns ``None`` if the ``username`` is not valid. Returns ``(None, False)`` if the ``username`` or link is not valid.
""" """
username = username.strip() username = username.strip()
m = USERNAME_RE.match(username) m = USERNAME_RE.match(username) or TG_JOIN_RE.match(username)
if m: if m:
username = username[m.end():] username = username[m.end():]
is_invite = bool(m.group(1)) is_invite = bool(m.group(1))

View File

@ -108,6 +108,7 @@ LEARN_PYTHON = (
"• [Official Docs](https://docs.python.org/3/tutorial/index.html).\n" "• [Official Docs](https://docs.python.org/3/tutorial/index.html).\n"
"• [Dive Into Python 3](http://www.diveintopython3.net/).\n" "• [Dive Into Python 3](http://www.diveintopython3.net/).\n"
"• [Learn Python](https://www.learnpython.org/).\n" "• [Learn Python](https://www.learnpython.org/).\n"
"• [Project Python](http://projectpython.net/).\n"
"• [Computer Science Circles](https://cscircles.cemc.uwaterloo.ca/).\n" "• [Computer Science Circles](https://cscircles.cemc.uwaterloo.ca/).\n"
"• [MIT OpenCourse](https://ocw.mit.edu/courses/electrical-engineering-" "• [MIT OpenCourse](https://ocw.mit.edu/courses/electrical-engineering-"
"and-computer-science/6-0001-introduction-to-computer-science-and-progr" "and-computer-science/6-0001-introduction-to-computer-science-and-progr"