mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-07-10 16:12:22 +03:00
Remove sync hack
This commit is contained in:
parent
34e7b7cc9f
commit
2a933ac3bd
2
.github/ISSUE_TEMPLATE/bug-report.md
vendored
2
.github/ISSUE_TEMPLATE/bug-report.md
vendored
|
@ -14,7 +14,7 @@ assignees: ''
|
||||||
|
|
||||||
**Code that causes the issue**
|
**Code that causes the issue**
|
||||||
```python
|
```python
|
||||||
from telethon.sync import TelegramClient
|
from telethon import TelegramClient
|
||||||
...
|
...
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
20
README.rst
20
README.rst
|
@ -35,15 +35,19 @@ Creating a client
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from telethon import TelegramClient, events, sync
|
import asyncio
|
||||||
|
from telethon import TelegramClient, events
|
||||||
|
|
||||||
# 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.
|
||||||
api_id = 12345
|
api_id = 12345
|
||||||
api_hash = '0123456789abcdef0123456789abcdef'
|
api_hash = '0123456789abcdef0123456789abcdef'
|
||||||
|
|
||||||
|
async def main():
|
||||||
client = TelegramClient('session_name', api_id, api_hash)
|
client = TelegramClient('session_name', api_id, api_hash)
|
||||||
client.start()
|
await client.start()
|
||||||
|
|
||||||
|
asyncio.run(main())
|
||||||
|
|
||||||
|
|
||||||
Doing stuff
|
Doing stuff
|
||||||
|
@ -51,14 +55,14 @@ Doing stuff
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
print(client.get_me().stringify())
|
print((await client.get_me()).stringify())
|
||||||
|
|
||||||
client.send_message('username', 'Hello! Talking to you from Telethon')
|
await client.send_message('username', 'Hello! Talking to you from Telethon')
|
||||||
client.send_file('username', '/home/myself/Pictures/holidays.jpg')
|
await client.send_file('username', '/home/myself/Pictures/holidays.jpg')
|
||||||
|
|
||||||
client.download_profile_photo('me')
|
await client.download_profile_photo('me')
|
||||||
messages = client.get_messages('username')
|
messages = await client.get_messages('username')
|
||||||
messages[0].download_media()
|
await messages[0].download_media()
|
||||||
|
|
||||||
@client.on(events.NewMessage(pattern='(?i)hi|hello'))
|
@client.on(events.NewMessage(pattern='(?i)hi|hello'))
|
||||||
async def handler(event):
|
async def handler(event):
|
||||||
|
|
|
@ -100,12 +100,8 @@ proceeding. We will see all the available methods later on.
|
||||||
# Most of your code should go here.
|
# Most of your code should go here.
|
||||||
# You can of course make and use your own async def (do_something).
|
# You can of course make and use your own async def (do_something).
|
||||||
# They only need to be async if they need to await things.
|
# They only need to be async if they need to await things.
|
||||||
|
async with client:
|
||||||
me = await client.get_me()
|
me = await client.get_me()
|
||||||
await do_something(me)
|
await do_something(me)
|
||||||
|
|
||||||
with client:
|
|
||||||
client.loop.run_until_complete(main())
|
client.loop.run_until_complete(main())
|
||||||
|
|
||||||
After you understand this, you may use the ``telethon.sync`` hack if you
|
|
||||||
want do so (see :ref:`compatibility-and-convenience`), but note you may
|
|
||||||
run into other issues (iPython, Anaconda, etc. have some issues with it).
|
|
||||||
|
|
|
@ -55,9 +55,12 @@ We can finally write some code to log into our account!
|
||||||
api_id = 12345
|
api_id = 12345
|
||||||
api_hash = '0123456789abcdef0123456789abcdef'
|
api_hash = '0123456789abcdef0123456789abcdef'
|
||||||
|
|
||||||
|
async def main():
|
||||||
# The first parameter is the .session file name (absolute paths allowed)
|
# The first parameter is the .session file name (absolute paths allowed)
|
||||||
with TelegramClient('anon', api_id, api_hash) as client:
|
async with TelegramClient('anon', api_id, api_hash) as client:
|
||||||
client.loop.run_until_complete(client.send_message('me', 'Hello, myself!'))
|
await client.send_message('me', 'Hello, myself!')
|
||||||
|
|
||||||
|
client.loop.run_until_complete(main())
|
||||||
|
|
||||||
|
|
||||||
In the first line, we import the class name so we can create an instance
|
In the first line, we import the class name so we can create an instance
|
||||||
|
@ -95,7 +98,7 @@ You will still need an API ID and hash, but the process is very similar:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from telethon.sync import TelegramClient
|
from telethon import TelegramClient
|
||||||
|
|
||||||
api_id = 12345
|
api_id = 12345
|
||||||
api_hash = '0123456789abcdef0123456789abcdef'
|
api_hash = '0123456789abcdef0123456789abcdef'
|
||||||
|
@ -104,10 +107,13 @@ You will still need an API ID and hash, but the process is very similar:
|
||||||
# We have to manually call "start" if we want an explicit bot token
|
# We have to manually call "start" if we want an explicit bot token
|
||||||
bot = TelegramClient('bot', api_id, api_hash).start(bot_token=bot_token)
|
bot = TelegramClient('bot', api_id, api_hash).start(bot_token=bot_token)
|
||||||
|
|
||||||
|
async def main():
|
||||||
# But then we can use the client instance as usual
|
# But then we can use the client instance as usual
|
||||||
with bot:
|
async with bot:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
client.loop.run_until_complete(main())
|
||||||
|
|
||||||
|
|
||||||
To get a bot account, you need to talk
|
To get a bot account, you need to talk
|
||||||
with `@BotFather <https://t.me/BotFather>`_.
|
with `@BotFather <https://t.me/BotFather>`_.
|
||||||
|
|
|
@ -58,84 +58,6 @@ What are asyncio basics?
|
||||||
loop.run_until_complete(main())
|
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
|
|
||||||
|
|
||||||
me = client.loop.run_until_complete(client.get_me())
|
|
||||||
print(me.username)
|
|
||||||
|
|
||||||
# or, using asyncio's default loop (it's the same)
|
|
||||||
import asyncio
|
|
||||||
loop = asyncio.get_event_loop() # == client.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?
|
What are async, await and coroutines?
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
@ -275,7 +197,7 @@ in it. So if you want to run *other* code, create tasks for it:
|
||||||
|
|
||||||
loop.create_task(clock())
|
loop.create_task(clock())
|
||||||
...
|
...
|
||||||
client.run_until_disconnected()
|
await client.run_until_disconnected()
|
||||||
|
|
||||||
This creates a task for a clock that prints the time every second.
|
This creates a task for a clock that prints the time every second.
|
||||||
You don't need to use `client.run_until_disconnected()
|
You don't need to use `client.run_until_disconnected()
|
||||||
|
@ -344,19 +266,6 @@ When you use a library, you're not limited to use only its methods. You can
|
||||||
combine all the libraries you want. People seem to forget this simple fact!
|
combine all the libraries you want. People seem to forget this simple fact!
|
||||||
|
|
||||||
|
|
||||||
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?
|
Where can I read more?
|
||||||
======================
|
======================
|
||||||
|
|
||||||
|
|
|
@ -73,10 +73,10 @@ You can import these ``from telethon.sessions``. For example, using the
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from telethon.sync import TelegramClient
|
from telethon import TelegramClient
|
||||||
from telethon.sessions import StringSession
|
from telethon.sessions import StringSession
|
||||||
|
|
||||||
with TelegramClient(StringSession(string), api_id, api_hash) as client:
|
async with TelegramClient(StringSession(string), api_id, api_hash) as client:
|
||||||
... # use the client
|
... # use the client
|
||||||
|
|
||||||
# Save the string session as a string; you should decide how
|
# Save the string session as a string; you should decide how
|
||||||
|
@ -129,10 +129,10 @@ The easiest way to generate a string session is as follows:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from telethon.sync import TelegramClient
|
from telethon import TelegramClient
|
||||||
from telethon.sessions import StringSession
|
from telethon.sessions import StringSession
|
||||||
|
|
||||||
with TelegramClient(StringSession(), api_id, api_hash) as client:
|
async with TelegramClient(StringSession(), api_id, api_hash) as client:
|
||||||
print(client.session.save())
|
print(client.session.save())
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,17 +4,21 @@ Telethon's Documentation
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from telethon.sync import TelegramClient, events
|
import asyncio
|
||||||
|
from telethon import TelegramClient, events
|
||||||
|
|
||||||
with TelegramClient('name', api_id, api_hash) as client:
|
async def main():
|
||||||
client.send_message('me', 'Hello, myself!')
|
async with TelegramClient('name', api_id, api_hash) as client:
|
||||||
print(client.download_profile_photo('me'))
|
await client.send_message('me', 'Hello, myself!')
|
||||||
|
print(await client.download_profile_photo('me'))
|
||||||
|
|
||||||
@client.on(events.NewMessage(pattern='(?i).*Hello'))
|
@client.on(events.NewMessage(pattern='(?i).*Hello'))
|
||||||
async def handler(event):
|
async def handler(event):
|
||||||
await event.reply('Hey!')
|
await event.reply('Hey!')
|
||||||
|
|
||||||
client.run_until_disconnected()
|
await client.run_until_disconnected()
|
||||||
|
|
||||||
|
asyncio.run(main())
|
||||||
|
|
||||||
|
|
||||||
* Are you new here? Jump straight into :ref:`installation`!
|
* Are you new here? Jump straight into :ref:`installation`!
|
||||||
|
|
|
@ -32,3 +32,22 @@ change even across minor version changes, and thus have your code break.
|
||||||
|
|
||||||
* The ``telethon.client`` module is now ``telethon._client``, meaning you should stop relying on
|
* The ``telethon.client`` module is now ``telethon._client``, meaning you should stop relying on
|
||||||
anything inside of it. This includes all of the subclasses that used to exist (like ``UserMethods``).
|
anything inside of it. This includes all of the subclasses that used to exist (like ``UserMethods``).
|
||||||
|
|
||||||
|
|
||||||
|
Synchronous compatibility mode has been removed
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
The "sync hack" (which kicked in as soon as anything from ``telethon.sync`` was imported) has been
|
||||||
|
removed. This implies:
|
||||||
|
|
||||||
|
* The ``telethon.sync`` module is gone.
|
||||||
|
* Synchronous context-managers (``with`` as opposed to ``async with``) are no longer supported.
|
||||||
|
Most notably, you can no longer do ``with client``. It must be ``async with client`` now.
|
||||||
|
* The "smart" behaviour of the following methods has been removed and now they no longer work in
|
||||||
|
a synchronous context when the ``asyncio`` event loop was not running. This means they now need
|
||||||
|
to be used with ``await`` (or, alternatively, manually used with ``loop.run_until_complete``):
|
||||||
|
* ``start``
|
||||||
|
* ``disconnect``
|
||||||
|
* ``run_until_disconnected``
|
||||||
|
|
||||||
|
// TODO provide standalone alternative for this?
|
||||||
|
|
|
@ -127,14 +127,7 @@ This is basic Python knowledge. You should use the dot operator:
|
||||||
AttributeError: 'coroutine' object has no attribute 'id'
|
AttributeError: 'coroutine' object has no attribute 'id'
|
||||||
========================================================
|
========================================================
|
||||||
|
|
||||||
You either forgot to:
|
Telethon is an asynchronous library. This means you need to ``await`` most methods:
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
import telethon.sync
|
|
||||||
# ^^^^^ import sync
|
|
||||||
|
|
||||||
Or:
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -218,19 +211,7 @@ Check out `quart_login.py`_ for an example web-application based on Quart.
|
||||||
Can I use Anaconda/Spyder/IPython with the library?
|
Can I use Anaconda/Spyder/IPython with the library?
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Yes, but these interpreters run the asyncio event loop implicitly,
|
Yes, but these interpreters run the asyncio event loop implicitly, so be wary of that.
|
||||||
which interferes with the ``telethon.sync`` magic module.
|
|
||||||
|
|
||||||
If you use them, you should **not** import ``sync``:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
# Change any of these...:
|
|
||||||
from telethon import TelegramClient, sync, ...
|
|
||||||
from telethon.sync import TelegramClient, ...
|
|
||||||
|
|
||||||
# ...with this:
|
|
||||||
from telethon import TelegramClient, ...
|
|
||||||
|
|
||||||
You are also more likely to get "sqlite3.OperationalError: database is locked"
|
You are also more likely to get "sqlite3.OperationalError: database is locked"
|
||||||
with them. If they cause too much trouble, just write your code in a ``.py``
|
with them. If they cause too much trouble, just write your code in a ``.py``
|
||||||
|
|
|
@ -56,9 +56,6 @@ class _TakeoutClient:
|
||||||
raise ValueError("Failed to finish the takeout.")
|
raise ValueError("Failed to finish the takeout.")
|
||||||
self.session.takeout_id = None
|
self.session.takeout_id = None
|
||||||
|
|
||||||
__enter__ = helpers._sync_enter
|
|
||||||
__exit__ = helpers._sync_exit
|
|
||||||
|
|
||||||
async def __call__(self, request, ordered=False):
|
async def __call__(self, request, ordered=False):
|
||||||
takeout_id = self.__client.session.takeout_id
|
takeout_id = self.__client.session.takeout_id
|
||||||
if takeout_id is None:
|
if takeout_id is None:
|
||||||
|
|
|
@ -12,7 +12,7 @@ if typing.TYPE_CHECKING:
|
||||||
from .telegramclient import TelegramClient
|
from .telegramclient import TelegramClient
|
||||||
|
|
||||||
|
|
||||||
def start(
|
async def start(
|
||||||
self: 'TelegramClient',
|
self: 'TelegramClient',
|
||||||
phone: typing.Callable[[], str] = lambda: input('Please enter your phone (or bot token): '),
|
phone: typing.Callable[[], str] = lambda: input('Please enter your phone (or bot token): '),
|
||||||
password: typing.Callable[[], str] = lambda: getpass.getpass('Please enter your password: '),
|
password: typing.Callable[[], str] = lambda: getpass.getpass('Please enter your password: '),
|
||||||
|
@ -39,7 +39,7 @@ def start(
|
||||||
raise ValueError('Both a phone and a bot token provided, '
|
raise ValueError('Both a phone and a bot token provided, '
|
||||||
'must only provide one of either')
|
'must only provide one of either')
|
||||||
|
|
||||||
coro = self._start(
|
return await self._start(
|
||||||
phone=phone,
|
phone=phone,
|
||||||
password=password,
|
password=password,
|
||||||
bot_token=bot_token,
|
bot_token=bot_token,
|
||||||
|
@ -49,10 +49,6 @@ def start(
|
||||||
last_name=last_name,
|
last_name=last_name,
|
||||||
max_attempts=max_attempts
|
max_attempts=max_attempts
|
||||||
)
|
)
|
||||||
return (
|
|
||||||
coro if self.loop.is_running()
|
|
||||||
else self.loop.run_until_complete(coro)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _start(
|
async def _start(
|
||||||
self: 'TelegramClient', phone, password, bot_token, force_sms,
|
self: 'TelegramClient', phone, password, bot_token, force_sms,
|
||||||
|
|
|
@ -76,9 +76,6 @@ class _ChatAction:
|
||||||
|
|
||||||
self._task = None
|
self._task = None
|
||||||
|
|
||||||
__enter__ = helpers._sync_enter
|
|
||||||
__exit__ = helpers._sync_exit
|
|
||||||
|
|
||||||
async def _update(self):
|
async def _update(self):
|
||||||
try:
|
try:
|
||||||
while self._running:
|
while self._running:
|
||||||
|
|
|
@ -137,9 +137,6 @@ class _DirectDownloadIter(RequestIter):
|
||||||
async def __aexit__(self, *args):
|
async def __aexit__(self, *args):
|
||||||
await self.close()
|
await self.close()
|
||||||
|
|
||||||
__enter__ = helpers._sync_enter
|
|
||||||
__exit__ = helpers._sync_exit
|
|
||||||
|
|
||||||
|
|
||||||
class _GenericDownloadIter(_DirectDownloadIter):
|
class _GenericDownloadIter(_DirectDownloadIter):
|
||||||
async def _load_next_chunk(self):
|
async def _load_next_chunk(self):
|
||||||
|
|
|
@ -337,19 +337,8 @@ def is_connected(self: 'TelegramClient') -> bool:
|
||||||
sender = getattr(self, '_sender', None)
|
sender = getattr(self, '_sender', None)
|
||||||
return sender and sender.is_connected()
|
return sender and sender.is_connected()
|
||||||
|
|
||||||
def disconnect(self: 'TelegramClient'):
|
async def disconnect(self: 'TelegramClient'):
|
||||||
if self.loop.is_running():
|
return await self._disconnect_coro()
|
||||||
return self._disconnect_coro()
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
self.loop.run_until_complete(self._disconnect_coro())
|
|
||||||
except RuntimeError:
|
|
||||||
# Python 3.5.x complains when called from
|
|
||||||
# `__aexit__` and there were pending updates with:
|
|
||||||
# "Event loop stopped before Future completed."
|
|
||||||
#
|
|
||||||
# However, it doesn't really make a lot of sense.
|
|
||||||
pass
|
|
||||||
|
|
||||||
def set_proxy(self: 'TelegramClient', proxy: typing.Union[tuple, dict]):
|
def set_proxy(self: 'TelegramClient', proxy: typing.Union[tuple, dict]):
|
||||||
init_proxy = None if not issubclass(self._connection, TcpMTProxy) else \
|
init_proxy = None if not issubclass(self._connection, TcpMTProxy) else \
|
||||||
|
|
|
@ -619,9 +619,6 @@ class TelegramClient:
|
||||||
async def __aexit__(self, *args):
|
async def __aexit__(self, *args):
|
||||||
await self.disconnect()
|
await self.disconnect()
|
||||||
|
|
||||||
__enter__ = helpers._sync_enter
|
|
||||||
__exit__ = helpers._sync_exit
|
|
||||||
|
|
||||||
# endregion Auth
|
# endregion Auth
|
||||||
|
|
||||||
# region Bots
|
# region Bots
|
||||||
|
|
|
@ -40,7 +40,7 @@ async def set_receive_updates(self: 'TelegramClient', receive_updates):
|
||||||
if receive_updates:
|
if receive_updates:
|
||||||
await self(functions.updates.GetStateRequest())
|
await self(functions.updates.GetStateRequest())
|
||||||
|
|
||||||
def run_until_disconnected(self: 'TelegramClient'):
|
async def run_until_disconnected(self: 'TelegramClient'):
|
||||||
"""
|
"""
|
||||||
Runs the event loop until the library is disconnected.
|
Runs the event loop until the library is disconnected.
|
||||||
|
|
||||||
|
@ -75,15 +75,7 @@ def run_until_disconnected(self: 'TelegramClient'):
|
||||||
# script from exiting.
|
# script from exiting.
|
||||||
await client.run_until_disconnected()
|
await client.run_until_disconnected()
|
||||||
"""
|
"""
|
||||||
if self.loop.is_running():
|
return await self._run_until_disconnected()
|
||||||
return self._run_until_disconnected()
|
|
||||||
try:
|
|
||||||
return self.loop.run_until_complete(self._run_until_disconnected())
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
# No loop.run_until_complete; it's already syncified
|
|
||||||
self.disconnect()
|
|
||||||
|
|
||||||
def on(self: 'TelegramClient', event: EventBuilder):
|
def on(self: 'TelegramClient', event: EventBuilder):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -165,34 +165,6 @@ async def _cancel(log, **tasks):
|
||||||
'%s (%s)', name, type(task), task)
|
'%s (%s)', name, type(task), task)
|
||||||
|
|
||||||
|
|
||||||
def _sync_enter(self):
|
|
||||||
"""
|
|
||||||
Helps to cut boilerplate on async context
|
|
||||||
managers that offer synchronous variants.
|
|
||||||
"""
|
|
||||||
if hasattr(self, 'loop'):
|
|
||||||
loop = self.loop
|
|
||||||
else:
|
|
||||||
loop = self._client.loop
|
|
||||||
|
|
||||||
if loop.is_running():
|
|
||||||
raise RuntimeError(
|
|
||||||
'You must use "async with" if the event loop '
|
|
||||||
'is running (i.e. you are inside an "async def")'
|
|
||||||
)
|
|
||||||
|
|
||||||
return loop.run_until_complete(self.__aenter__())
|
|
||||||
|
|
||||||
|
|
||||||
def _sync_exit(self, *args):
|
|
||||||
if hasattr(self, 'loop'):
|
|
||||||
loop = self.loop
|
|
||||||
else:
|
|
||||||
loop = self._client.loop
|
|
||||||
|
|
||||||
return loop.run_until_complete(self.__aexit__(*args))
|
|
||||||
|
|
||||||
|
|
||||||
def _entity_type(entity):
|
def _entity_type(entity):
|
||||||
# This could be a `utils` method that just ran a few `isinstance` on
|
# This could be a `utils` method that just ran a few `isinstance` on
|
||||||
# `utils.get_peer(...)`'s result. However, there are *a lot* of auto
|
# `utils.get_peer(...)`'s result. However, there are *a lot* of auto
|
||||||
|
|
|
@ -12,9 +12,6 @@ class RequestIter(abc.ABC):
|
||||||
It has some facilities, such as automatically sleeping a desired
|
It has some facilities, such as automatically sleeping a desired
|
||||||
amount of time between requests if needed (but not more).
|
amount of time between requests if needed (but not more).
|
||||||
|
|
||||||
Can be used synchronously if the event loop is not running and
|
|
||||||
as an asynchronous iterator otherwise.
|
|
||||||
|
|
||||||
`limit` is the total amount of items that the iterator should return.
|
`limit` is the total amount of items that the iterator should return.
|
||||||
This is handled on this base class, and will be always ``>= 0``.
|
This is handled on this base class, and will be always ``>= 0``.
|
||||||
|
|
||||||
|
@ -82,12 +79,6 @@ class RequestIter(abc.ABC):
|
||||||
self.index += 1
|
self.index += 1
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def __next__(self):
|
|
||||||
try:
|
|
||||||
return self.client.loop.run_until_complete(self.__anext__())
|
|
||||||
except StopAsyncIteration:
|
|
||||||
raise StopIteration
|
|
||||||
|
|
||||||
def __aiter__(self):
|
def __aiter__(self):
|
||||||
self.buffer = None
|
self.buffer = None
|
||||||
self.index = 0
|
self.index = 0
|
||||||
|
@ -95,15 +86,6 @@ class RequestIter(abc.ABC):
|
||||||
self.left = self.limit
|
self.left = self.limit
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
if self.client.loop.is_running():
|
|
||||||
raise RuntimeError(
|
|
||||||
'You must use "async for" if the event loop '
|
|
||||||
'is running (i.e. you are inside an "async def")'
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.__aiter__()
|
|
||||||
|
|
||||||
async def collect(self):
|
async def collect(self):
|
||||||
"""
|
"""
|
||||||
Create a `self` iterator and collect it into a `TotalList`
|
Create a `self` iterator and collect it into a `TotalList`
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
"""
|
|
||||||
This magical module will rewrite all public methods in the public interface
|
|
||||||
of the library so they can run the loop on their own if it's not already
|
|
||||||
running. This rewrite may not be desirable if the end user always uses the
|
|
||||||
methods they way they should be ran, but it's incredibly useful for quick
|
|
||||||
scripts and the runtime overhead is relatively low.
|
|
||||||
|
|
||||||
Some really common methods which are hardly used offer this ability by
|
|
||||||
default, such as ``.start()`` and ``.run_until_disconnected()`` (since
|
|
||||||
you may want to start, and then run until disconnected while using async
|
|
||||||
event handlers).
|
|
||||||
"""
|
|
||||||
import asyncio
|
|
||||||
import functools
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
from . import events, errors, utils, connection
|
|
||||||
from .client.account import _TakeoutClient
|
|
||||||
from .client.telegramclient import TelegramClient
|
|
||||||
from .tl import types, functions, custom
|
|
||||||
from .tl.custom import (
|
|
||||||
Draft, Dialog, MessageButton, Forward, Button,
|
|
||||||
Message, InlineResult, Conversation
|
|
||||||
)
|
|
||||||
from .tl.custom.chatgetter import ChatGetter
|
|
||||||
from .tl.custom.sendergetter import SenderGetter
|
|
||||||
|
|
||||||
|
|
||||||
def _syncify_wrap(t, method_name):
|
|
||||||
method = getattr(t, method_name)
|
|
||||||
|
|
||||||
@functools.wraps(method)
|
|
||||||
def syncified(*args, **kwargs):
|
|
||||||
coro = method(*args, **kwargs)
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
if loop.is_running():
|
|
||||||
return coro
|
|
||||||
else:
|
|
||||||
return loop.run_until_complete(coro)
|
|
||||||
|
|
||||||
# Save an accessible reference to the original method
|
|
||||||
setattr(syncified, '__tl.sync', method)
|
|
||||||
setattr(t, method_name, syncified)
|
|
||||||
|
|
||||||
|
|
||||||
def syncify(*types):
|
|
||||||
"""
|
|
||||||
Converts all the methods in the given types (class definitions)
|
|
||||||
into synchronous, which return either the coroutine or the result
|
|
||||||
based on whether ``asyncio's`` event loop is running.
|
|
||||||
"""
|
|
||||||
# Our asynchronous generators all are `RequestIter`, which already
|
|
||||||
# provide a synchronous iterator variant, so we don't need to worry
|
|
||||||
# about asyncgenfunction's here.
|
|
||||||
for t in types:
|
|
||||||
for name in dir(t):
|
|
||||||
if not name.startswith('_') or name == '__call__':
|
|
||||||
if inspect.iscoroutinefunction(getattr(t, name)):
|
|
||||||
_syncify_wrap(t, name)
|
|
||||||
|
|
||||||
|
|
||||||
syncify(TelegramClient, _TakeoutClient, Draft, Dialog, MessageButton,
|
|
||||||
ChatGetter, SenderGetter, Forward, Message, InlineResult, Conversation)
|
|
||||||
|
|
||||||
|
|
||||||
# Private special case, since a conversation's methods return
|
|
||||||
# futures (but the public function themselves are synchronous).
|
|
||||||
_syncify_wrap(Conversation, '_get_result')
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'TelegramClient', 'Button',
|
|
||||||
'types', 'functions', 'custom', 'errors',
|
|
||||||
'events', 'utils', 'connection'
|
|
||||||
]
|
|
|
@ -524,6 +524,3 @@ class Conversation(ChatGetter):
|
||||||
del self._client._conversations[chat_id]
|
del self._client._conversations[chat_id]
|
||||||
|
|
||||||
self._cancel_all()
|
self._cancel_all()
|
||||||
|
|
||||||
__enter__ = helpers._sync_enter
|
|
||||||
__exit__ = helpers._sync_exit
|
|
||||||
|
|
|
@ -396,7 +396,7 @@ def _write_html_pages(tlobjects, methods, layer, input_res):
|
||||||
docs.write('<details>')
|
docs.write('<details>')
|
||||||
|
|
||||||
docs.write('''<pre>\
|
docs.write('''<pre>\
|
||||||
<strong>from</strong> telethon.sync <strong>import</strong> TelegramClient
|
<strong>from</strong> telethon <strong>import</strong> TelegramClient
|
||||||
<strong>from</strong> telethon <strong>import</strong> functions, types
|
<strong>from</strong> telethon <strong>import</strong> functions, types
|
||||||
|
|
||||||
<strong>with</strong> TelegramClient(name, api_id, api_hash) <strong>as</strong> client:
|
<strong>with</strong> TelegramClient(name, api_id, api_hash) <strong>as</strong> client:
|
||||||
|
|
|
@ -14,43 +14,6 @@ def test_strip_text():
|
||||||
# I can't interpret the rest of the code well enough yet
|
# I can't interpret the rest of the code well enough yet
|
||||||
|
|
||||||
|
|
||||||
class TestSyncifyAsyncContext:
|
|
||||||
class NoopContextManager:
|
|
||||||
def __init__(self, loop):
|
|
||||||
self.count = 0
|
|
||||||
self.loop = loop
|
|
||||||
|
|
||||||
async def __aenter__(self):
|
|
||||||
self.count += 1
|
|
||||||
return self
|
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, *args):
|
|
||||||
assert exc_type is None
|
|
||||||
self.count -= 1
|
|
||||||
|
|
||||||
__enter__ = helpers._sync_enter
|
|
||||||
__exit__ = helpers._sync_exit
|
|
||||||
|
|
||||||
def test_sync_acontext(self, event_loop):
|
|
||||||
contm = self.NoopContextManager(event_loop)
|
|
||||||
assert contm.count == 0
|
|
||||||
|
|
||||||
with contm:
|
|
||||||
assert contm.count == 1
|
|
||||||
|
|
||||||
assert contm.count == 0
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_async_acontext(self, event_loop):
|
|
||||||
contm = self.NoopContextManager(event_loop)
|
|
||||||
assert contm.count == 0
|
|
||||||
|
|
||||||
async with contm:
|
|
||||||
assert contm.count == 1
|
|
||||||
|
|
||||||
assert contm.count == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_generate_key_data_from_nonce():
|
def test_generate_key_data_from_nonce():
|
||||||
gkdfn = helpers.generate_key_data_from_nonce
|
gkdfn = helpers.generate_key_data_from_nonce
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user