Stop using asyncio.get_event_loop()

It is deprecated in newer Python versions.
Closes #4013.
This commit is contained in:
Lonami Exo 2023-01-11 21:02:29 +01:00
parent fb97a8aa87
commit 83bafa25e3
14 changed files with 82 additions and 99 deletions

View File

@ -40,22 +40,22 @@ because tasks are smaller than threads, which are smaller than processes.
What are asyncio basics?
========================
The code samples below assume that you have Python 3.7 or greater installed.
.. 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())
# Then, we can create a new asyncio loop and use it to run our coroutine.
# The creation and tear-down of the loop is hidden away from us.
asyncio.run(main())
What does telethon.sync do?
@ -101,7 +101,7 @@ Instead of this:
# or, using asyncio's default loop (it's the same)
import asyncio
loop = asyncio.get_event_loop() # == client.loop
loop = asyncio.get_running_loop() # == client.loop
me = loop.run_until_complete(client.get_me())
print(me.username)
@ -158,13 +158,10 @@ loops or use ``async with``:
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.
asyncio.run(main())
# ^ this will create a new asyncio loop behind the scenes and tear it down
# once the function returns. It will run the loop untiil main finishes.
# You should only use this function if there is no other loop running.
The ``await`` keyword blocks the *current* task, and the loop can run
@ -184,14 +181,14 @@ concurrently:
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
async def main():
asyncio.create_task(world(2)) # create the world task, passing 2 as delay
asyncio.create_task(hello(delay=1)) # another task, but with delay 1
await asyncio.sleep(3) # wait for three seconds before exiting
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()
# create a new temporary asyncio loop and use it to run main
asyncio.run(main())
except KeyboardInterrupt:
pass
@ -209,10 +206,15 @@ The same example, but without the comment noise:
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))
async def main():
asyncio.create_task(world(2))
asyncio.create_task(hello(delay=1))
await asyncio.sleep(3)
try:
asyncio.run(main())
except KeyboardInterrupt:
pass
Can I use threads?
@ -250,9 +252,9 @@ You may have seen this error:
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.
It just means you didn't create a loop for that thread. Please refer to
the ``asyncio`` documentation to correctly learn how to set the event loop
for non-main threads.
client.run_until_disconnected() blocks!

View File

@ -191,8 +191,7 @@ so the code above and the following are equivalent:
async def main():
await client.disconnected
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
asyncio.run(main())
You could also run `client.disconnected

View File

@ -2088,7 +2088,7 @@ the scenes! This means you're now able to do both of the following:
async def main():
await client.send_message('me', 'Hello!')
asyncio.get_event_loop().run_until_complete(main())
asyncio.run(main())
# ...can be rewritten as:

View File

@ -161,19 +161,17 @@ just get rid of ``telethon.sync`` and work inside an ``async def``:
await client.run_until_disconnected()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
asyncio.run(main())
The ``telethon.sync`` magic module simply wraps every method behind:
The ``telethon.sync`` magic module essentially wraps every method behind:
.. code-block:: python
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
asyncio.run(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.
With some other tricks, 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
========

View File

@ -192,7 +192,7 @@ class TelegramBaseClient(abc.ABC):
Defaults to `lang_code`.
loop (`asyncio.AbstractEventLoop`, optional):
Asyncio event loop to use. Defaults to `asyncio.get_event_loop()`.
Asyncio event loop to use. Defaults to `asyncio.get_running_loop()`.
This argument is ignored.
base_logger (`str` | `logging.Logger`, optional):
@ -470,7 +470,7 @@ class TelegramBaseClient(abc.ABC):
# Join the task (wait for it to complete)
await task
"""
return asyncio.get_event_loop()
return helpers.get_running_loop()
@property
def disconnected(self: 'TelegramClient') -> asyncio.Future:

View File

@ -4,7 +4,7 @@ import re
import asyncio
from .common import EventBuilder, EventCommon, name_inner_event
from .. import utils
from .. import utils, helpers
from ..tl import types, functions, custom
from ..tl.custom.sendergetter import SenderGetter
@ -242,6 +242,6 @@ class InlineQuery(EventBuilder):
if inspect.isawaitable(obj):
return asyncio.ensure_future(obj)
f = asyncio.get_event_loop().create_future()
f = helpers.get_running_loop().create_future()
f.set_result(obj)
return f

View File

@ -426,7 +426,10 @@ class _FileStream(io.IOBase):
# endregion
def get_running_loop():
if sys.version_info[:2] <= (3, 6):
return asyncio._get_running_loop()
if sys.version_info >= (3, 7):
try:
return asyncio.get_running_loop()
except RuntimeError:
return asyncio.get_event_loop_policy().get_event_loop()
else:
return asyncio.get_event_loop()

View File

@ -155,7 +155,7 @@ class Connection(abc.ABC):
# Actual TCP connection is performed here.
await asyncio.wait_for(
asyncio.get_event_loop().sock_connect(sock=sock, address=address),
helpers.get_running_loop().sock_connect(sock=sock, address=address),
timeout=timeout
)
@ -190,7 +190,7 @@ class Connection(abc.ABC):
# Actual TCP connection and negotiation performed here.
await asyncio.wait_for(
asyncio.get_event_loop().sock_connect(sock=sock, address=address),
helpers.get_running_loop().sock_connect(sock=sock, address=address),
timeout=timeout
)
@ -244,7 +244,7 @@ class Connection(abc.ABC):
await self._connect(timeout=timeout, ssl=ssl)
self._connected = True
loop = asyncio.get_event_loop()
loop = helpers.get_running_loop()
self._send_task = loop.create_task(self._send_loop())
self._recv_task = loop.create_task(self._recv_loop())

View File

@ -68,7 +68,7 @@ class MTProtoSender:
# pending futures should be cancelled.
self._user_connected = False
self._reconnecting = False
self._disconnected = asyncio.get_event_loop().create_future()
self._disconnected = helpers.get_running_loop().create_future()
self._disconnected.set_result(None)
# We need to join the loops upon disconnection
@ -261,7 +261,7 @@ class MTProtoSender:
await self._disconnect(error=e)
raise e
loop = asyncio.get_event_loop()
loop = helpers.get_running_loop()
self._log.debug('Starting send loop')
self._send_loop_handle = loop.create_task(self._send_loop())
@ -400,7 +400,7 @@ class MTProtoSender:
self._pending_state.clear()
if self._auto_reconnect_callback:
asyncio.get_event_loop().create_task(self._auto_reconnect_callback())
helpers.get_running_loop().create_task(self._auto_reconnect_callback())
break
else:
@ -425,7 +425,7 @@ class MTProtoSender:
# gets stuck.
# TODO It still gets stuck? Investigate where and why.
self._reconnecting = True
asyncio.get_event_loop().create_task(self._reconnect(error))
helpers.get_running_loop().create_task(self._reconnect(error))
def _keepalive_ping(self, rnd_id):
"""

View File

@ -14,7 +14,7 @@ import asyncio
import functools
import inspect
from . import events, errors, utils, connection
from . import events, errors, utils, connection, helpers
from .client.account import _TakeoutClient
from .client.telegramclient import TelegramClient
from .tl import types, functions, custom
@ -32,7 +32,7 @@ def _syncify_wrap(t, method_name):
@functools.wraps(method)
def syncified(*args, **kwargs):
coro = method(*args, **kwargs)
loop = asyncio.get_event_loop()
loop = helpers.get_running_loop()
if loop.is_running():
return coro
else:

View File

@ -53,7 +53,7 @@ def callback(func):
def wrapped(*args, **kwargs):
result = func(*args, **kwargs)
if inspect.iscoroutine(result):
aio_loop.create_task(result)
asyncio.create_task(result)
return wrapped
@ -369,10 +369,4 @@ async def main(interval=0.05):
if __name__ == "__main__":
# Some boilerplate code to set up the main method
aio_loop = asyncio.get_event_loop()
try:
aio_loop.run_until_complete(main())
finally:
if not aio_loop.is_closed():
aio_loop.close()
asyncio.run(main())

View File

@ -9,9 +9,6 @@ from telethon.errors import SessionPasswordNeededError
from telethon.network import ConnectionTcpAbridged
from telethon.utils import get_display_name
# Create a global variable to hold the loop we will be using
loop = asyncio.get_event_loop()
def sprint(string, *args, **kwargs):
"""Safe Print (handle UnicodeEncodeErrors on some terminals)"""
@ -50,7 +47,7 @@ async def async_input(prompt):
let the loop run while we wait for input.
"""
print(prompt, end='', flush=True)
return (await loop.run_in_executor(None, sys.stdin.readline)).rstrip()
return (await asyncio.get_running_loop().run_in_executor(None, sys.stdin.readline)).rstrip()
def get_env(name, message, cast=str):
@ -109,34 +106,34 @@ class InteractiveTelegramClient(TelegramClient):
# media known the message ID, for every message having media.
self.found_media = {}
async def init(self):
# Calling .connect() may raise a connection error False, so you need
# to except those before continuing. Otherwise you may want to retry
# as done here.
print('Connecting to Telegram servers...')
try:
loop.run_until_complete(self.connect())
await self.connect()
except IOError:
# We handle IOError and not ConnectionError because
# PySocks' errors do not subclass ConnectionError
# (so this will work with and without proxies).
print('Initial connection failed. Retrying...')
loop.run_until_complete(self.connect())
await self.connect()
# If the user hasn't called .sign_in() or .sign_up() yet, they won't
# be authorized. The first thing you must do is authorize. Calling
# .sign_in() should only be done once as the information is saved on
# the *.session file so you don't need to enter the code every time.
if not loop.run_until_complete(self.is_user_authorized()):
if not await self.is_user_authorized():
print('First run. Sending code request...')
user_phone = input('Enter your phone: ')
loop.run_until_complete(self.sign_in(user_phone))
await self.sign_in(user_phone)
self_user = None
while self_user is None:
code = input('Enter the code you just received: ')
try:
self_user =\
loop.run_until_complete(self.sign_in(code=code))
self_user = await self.sign_in(code=code)
# Two-step verification may be enabled, and .sign_in will
# raise this error. If that's the case ask for the password.
@ -146,8 +143,7 @@ class InteractiveTelegramClient(TelegramClient):
pw = getpass('Two step verification is enabled. '
'Please enter your password: ')
self_user =\
loop.run_until_complete(self.sign_in(password=pw))
self_user = await self.sign_in(password=pw)
async def run(self):
"""Main loop of the TelegramClient, will wait for user action"""
@ -397,9 +393,14 @@ class InteractiveTelegramClient(TelegramClient):
))
if __name__ == '__main__':
async def main():
SESSION = os.environ.get('TG_SESSION', 'interactive')
API_ID = get_env('TG_API_ID', 'Enter your API ID: ', int)
API_HASH = get_env('TG_API_HASH', 'Enter your API hash: ')
client = InteractiveTelegramClient(SESSION, API_ID, API_HASH)
loop.run_until_complete(client.run())
await client.init()
await client.run()
if __name__ == '__main__':
asyncio.run()

View File

@ -7,8 +7,6 @@ import os
import time
import sys
loop = asyncio.get_event_loop()
"""
Provider token can be obtained via @BotFather. more info at https://core.telegram.org/bots/payments#getting-a-token
@ -180,4 +178,4 @@ if __name__ == '__main__':
if not provider_token:
logger.error("No provider token supplied.")
exit(1)
loop.run_until_complete(main())
asyncio.run(main())

View File

@ -1,7 +1,6 @@
import base64
import os
import hypercorn.asyncio
from quart import Quart, render_template_string, request
from telethon import TelegramClient, utils
@ -82,6 +81,8 @@ async def format_message(message):
# Connect the client before we start serving with Quart
@app.before_serving
async def startup():
# After connecting, the client will create additional asyncio tasks that run until it's disconnected again.
# Be careful to not mix different asyncio loops during a client's lifetime, or things won't work properly!
await client.connect()
@ -129,24 +130,11 @@ async def root():
return await render_template_string(BASE_TEMPLATE, content=CODE_FORM)
async def main():
await hypercorn.asyncio.serve(app, hypercorn.Config())
# By default, `Quart.run` uses `asyncio.run()`, which creates a new asyncio
# event loop. If we create the `TelegramClient` before, `telethon` will
# use `asyncio.get_event_loop()`, which is the implicit loop in the main
# thread. These two loops are different, and it won't work.
# event loop. If we had connected the `TelegramClient` before, `telethon` will
# use `asyncio.get_running_loop()` to create some additional tasks. If these
# loops are different, it won't work.
#
# So, we have to manually pass the same `loop` to both applications to
# make 100% sure it works and to avoid headaches.
#
# To run Quart inside `async def`, we must use `hypercorn.asyncio.serve()`
# directly.
#
# This example creates a global client outside of Quart handlers.
# If you create the client inside the handlers (common case), you
# won't have to worry about any of this, but it's still good to be
# explicit about the event loop.
# To keep things simple, be sure to not create multiple asyncio loops!
if __name__ == '__main__':
client.loop.run_until_complete(main())
app.run()