Handle deprecations in asyncio (#3509)

* passing loop to certain things was deprecated. additionally, `asyncio.get_event_loop()` is being deprecated

* awesome, checks are functioning as intended

* fun with fixtures

* we can just stop misuing that anyhow

* Update redbot/pytest/downloader.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
This commit is contained in:
Michael H 2020-02-05 17:16:13 -05:00 committed by GitHub
parent 61ed864e02
commit 00cf395483
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 41 additions and 39 deletions

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# Discord Version check
import asyncio import asyncio
import functools import functools
import getpass import getpass
@ -20,7 +18,7 @@ from typing import NoReturn
import discord import discord
# Set the event loop policies here so any subsequent `get_event_loop()` # Set the event loop policies here so any subsequent `new_event_loop()`
# calls, in particular those as a result of the following imports, # calls, in particular those as a result of the following imports,
# return the correct loop object. # return the correct loop object.
from redbot import _update_event_loop_policy, __version__ from redbot import _update_event_loop_policy, __version__
@ -298,7 +296,8 @@ def handle_edit(cli_flags: Namespace):
""" """
This one exists to not log all the things like it's a full run of the bot. This one exists to not log all the things like it's a full run of the bot.
""" """
loop = asyncio.get_event_loop() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
data_manager.load_basic_configuration(cli_flags.instance_name) data_manager.load_basic_configuration(cli_flags.instance_name)
red = Red(cli_flags=cli_flags, description="Red V3", dm_help=None, fetch_offline_members=True) red = Red(cli_flags=cli_flags, description="Red V3", dm_help=None, fetch_offline_members=True)
try: try:
@ -310,6 +309,7 @@ def handle_edit(cli_flags: Namespace):
print("Aborted!") print("Aborted!")
finally: finally:
loop.run_until_complete(asyncio.sleep(1)) loop.run_until_complete(asyncio.sleep(1))
asyncio.set_event_loop(None)
loop.stop() loop.stop()
loop.close() loop.close()
sys.exit(0) sys.exit(0)
@ -460,7 +460,8 @@ def main():
handle_edit(cli_flags) handle_edit(cli_flags)
return return
try: try:
loop = asyncio.get_event_loop() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
if cli_flags.no_instance: if cli_flags.no_instance:
print( print(
@ -524,6 +525,7 @@ def main():
# results in a resource warning instead # results in a resource warning instead
log.info("Please wait, cleaning up a bit more") log.info("Please wait, cleaning up a bit more")
loop.run_until_complete(asyncio.sleep(2)) loop.run_until_complete(asyncio.sleep(2))
asyncio.set_event_loop(None)
loop.stop() loop.stop()
loop.close() loop.close()
exit_code = red._shutdown_mode if red is not None else 1 exit_code = red._shutdown_mode if red is not None else 1

View File

@ -462,7 +462,7 @@ class Downloader(commands.Cog):
if not deps: if not deps:
await ctx.send_help() await ctx.send_help()
return return
repo = Repo("", "", "", "", Path.cwd(), loop=ctx.bot.loop) repo = Repo("", "", "", "", Path.cwd())
async with ctx.typing(): async with ctx.typing():
success = await repo.install_raw_requirements(deps, self.LIB_PATH) success = await repo.install_raw_requirements(deps, self.LIB_PATH)

View File

@ -135,7 +135,6 @@ class Repo(RepoJSONMixin):
commit: str, commit: str,
folder_path: Path, folder_path: Path,
available_modules: Tuple[Installable, ...] = (), available_modules: Tuple[Installable, ...] = (),
loop: Optional[asyncio.AbstractEventLoop] = None,
): ):
self.url = url self.url = url
self.branch = branch self.branch = branch
@ -154,8 +153,6 @@ class Repo(RepoJSONMixin):
self._repo_lock = asyncio.Lock() self._repo_lock = asyncio.Lock()
self._loop = loop if loop is not None else asyncio.get_event_loop()
@property @property
def clean_url(self) -> str: def clean_url(self) -> str:
"""Sanitized repo URL (with removed HTTP Basic Auth)""" """Sanitized repo URL (with removed HTTP Basic Auth)"""
@ -529,7 +526,7 @@ class Repo(RepoJSONMixin):
env["LANGUAGE"] = "C" env["LANGUAGE"] = "C"
kwargs["env"] = env kwargs["env"] = env
async with self._repo_lock: async with self._repo_lock:
p: CompletedProcess = await self._loop.run_in_executor( p: CompletedProcess = await asyncio.get_running_loop().run_in_executor(
self._executor, self._executor,
functools.partial(sp_run, *args, stdout=PIPE, stderr=PIPE, **kwargs), functools.partial(sp_run, *args, stdout=PIPE, stderr=PIPE, **kwargs),
) )

View File

@ -1,4 +1,5 @@
import asyncio import asyncio
import warnings
from asyncio import AbstractEventLoop, as_completed, Semaphore from asyncio import AbstractEventLoop, as_completed, Semaphore
from asyncio.futures import isfuture from asyncio.futures import isfuture
from itertools import chain from itertools import chain
@ -177,14 +178,20 @@ def bounded_gather_iter(
TypeError TypeError
When invalid parameters are passed When invalid parameters are passed
""" """
if loop is None: if loop is not None:
loop = asyncio.get_event_loop() warnings.warn(
"Explicitly passing the loop will not work in Red 3.4+ and is currently ignored."
"Call this from the related event loop.",
DeprecationWarning,
)
loop = asyncio.get_running_loop()
if semaphore is None: if semaphore is None:
if not isinstance(limit, int) or limit <= 0: if not isinstance(limit, int) or limit <= 0:
raise TypeError("limit must be an int > 0") raise TypeError("limit must be an int > 0")
semaphore = Semaphore(limit, loop=loop) semaphore = Semaphore(limit)
pending = [] pending = []
@ -195,7 +202,7 @@ def bounded_gather_iter(
cof = _sem_wrapper(semaphore, cof) cof = _sem_wrapper(semaphore, cof)
pending.append(cof) pending.append(cof)
return as_completed(pending, loop=loop) return as_completed(pending)
def bounded_gather( def bounded_gather(
@ -228,15 +235,21 @@ def bounded_gather(
TypeError TypeError
When invalid parameters are passed When invalid parameters are passed
""" """
if loop is None: if loop is not None:
loop = asyncio.get_event_loop() warnings.warn(
"Explicitly passing the loop will not work in Red 3.4+ and is currently ignored."
"Call this from the related event loop.",
DeprecationWarning,
)
loop = asyncio.get_running_loop()
if semaphore is None: if semaphore is None:
if not isinstance(limit, int) or limit <= 0: if not isinstance(limit, int) or limit <= 0:
raise TypeError("limit must be an int > 0") raise TypeError("limit must be an int > 0")
semaphore = Semaphore(limit, loop=loop) semaphore = Semaphore(limit)
tasks = (_sem_wrapper(semaphore, task) for task in coros_or_futures) tasks = (_sem_wrapper(semaphore, task) for task in coros_or_futures)
return asyncio.gather(*tasks, loop=loop, return_exceptions=return_exceptions) return asyncio.gather(*tasks, return_exceptions=return_exceptions)

View File

@ -5,6 +5,7 @@
import asyncio import asyncio
import contextlib import contextlib
import functools import functools
import warnings
from typing import Union, Iterable, Optional from typing import Union, Iterable, Optional
import discord import discord
@ -200,7 +201,9 @@ def start_adding_reactions(
await message.add_reaction(emoji) await message.add_reaction(emoji)
if loop is None: if loop is None:
loop = asyncio.get_event_loop() loop = asyncio.get_running_loop()
else:
warnings.warn("Explicitly passing the loop will not work in Red 3.4+", DeprecationWarning)
return loop.create_task(task()) return loop.create_task(task())

View File

@ -76,7 +76,6 @@ def bot_repo(event_loop):
commit="", commit="",
url="https://empty.com/something.git", url="https://empty.com/something.git",
folder_path=cwd, folder_path=cwd,
loop=event_loop,
) )
@ -163,14 +162,7 @@ def _init_test_repo(destination: Path):
async def _session_git_repo(tmp_path_factory, event_loop): async def _session_git_repo(tmp_path_factory, event_loop):
# we will import repo only once once per session and duplicate the repo folder # we will import repo only once once per session and duplicate the repo folder
repo_path = tmp_path_factory.mktemp("session_git_repo") repo_path = tmp_path_factory.mktemp("session_git_repo")
repo = Repo( repo = Repo(name="redbot-testrepo", url="", branch="master", commit="", folder_path=repo_path)
name="redbot-testrepo",
url="",
branch="master",
commit="",
folder_path=repo_path,
loop=event_loop,
)
git_dirparams = _init_test_repo(repo_path) git_dirparams = _init_test_repo(repo_path)
fast_import = sp.Popen((*git_dirparams, "fast-import", "--quiet"), stdin=sp.PIPE) fast_import = sp.Popen((*git_dirparams, "fast-import", "--quiet"), stdin=sp.PIPE)
with TEST_REPO_EXPORT_PTH.open(mode="rb") as f: with TEST_REPO_EXPORT_PTH.open(mode="rb") as f:
@ -193,7 +185,6 @@ async def git_repo(_session_git_repo, tmp_path, event_loop):
branch=_session_git_repo.branch, branch=_session_git_repo.branch,
commit=_session_git_repo.commit, commit=_session_git_repo.commit,
folder_path=repo_path, folder_path=repo_path,
loop=event_loop,
) )
return repo return repo
@ -208,7 +199,6 @@ async def cloned_git_repo(_session_git_repo, tmp_path, event_loop):
branch=_session_git_repo.branch, branch=_session_git_repo.branch,
commit=_session_git_repo.commit, commit=_session_git_repo.commit,
folder_path=repo_path, folder_path=repo_path,
loop=event_loop,
) )
sp.run(("git", "clone", str(_session_git_repo.folder_path), str(repo_path)), check=True) sp.run(("git", "clone", str(_session_git_repo.folder_path), str(repo_path)), check=True)
return repo return repo
@ -224,7 +214,6 @@ async def git_repo_with_remote(git_repo, tmp_path, event_loop):
branch=git_repo.branch, branch=git_repo.branch,
commit=git_repo.commit, commit=git_repo.commit,
folder_path=repo_path, folder_path=repo_path,
loop=event_loop,
) )
sp.run(("git", "clone", str(git_repo.folder_path), str(repo_path)), check=True) sp.run(("git", "clone", str(git_repo.folder_path), str(repo_path)), check=True)
return repo return repo

View File

@ -371,8 +371,7 @@ def delete(
remove_datapath: Optional[bool], remove_datapath: Optional[bool],
): ):
"""Removes an instance.""" """Removes an instance."""
loop = asyncio.get_event_loop() asyncio.run(
loop.run_until_complete(
remove_instance( remove_instance(
instance, interactive, delete_data, _create_backup, drop_db, remove_datapath instance, interactive, delete_data, _create_backup, drop_db, remove_datapath
) )
@ -391,14 +390,12 @@ def convert(instance, backend):
default_dirs = deepcopy(data_manager.basic_config_default) default_dirs = deepcopy(data_manager.basic_config_default)
default_dirs["DATA_PATH"] = str(Path(instance_data[instance]["DATA_PATH"])) default_dirs["DATA_PATH"] = str(Path(instance_data[instance]["DATA_PATH"]))
loop = asyncio.get_event_loop()
if current_backend == BackendType.MONGOV1: if current_backend == BackendType.MONGOV1:
raise RuntimeError("Please see the 3.2 release notes for upgrading a bot using mongo.") raise RuntimeError("Please see the 3.2 release notes for upgrading a bot using mongo.")
elif current_backend == BackendType.POSTGRES: # TODO: GH-3115 elif current_backend == BackendType.POSTGRES: # TODO: GH-3115
raise RuntimeError("Converting away from postgres isn't currently supported") raise RuntimeError("Converting away from postgres isn't currently supported")
else: else:
new_storage_details = loop.run_until_complete(do_migration(current_backend, target)) new_storage_details = asyncio.run(do_migration(current_backend, target))
if new_storage_details is not None: if new_storage_details is not None:
default_dirs["STORAGE_TYPE"] = target.value default_dirs["STORAGE_TYPE"] = target.value
@ -422,8 +419,7 @@ def convert(instance, backend):
) )
def backup(instance: str, destination_folder: Union[str, Path]) -> None: def backup(instance: str, destination_folder: Union[str, Path]) -> None:
"""Backup instance's data.""" """Backup instance's data."""
loop = asyncio.get_event_loop() asyncio.run(create_backup(instance, Path(destination_folder)))
loop.run_until_complete(create_backup(instance, Path(destination_folder)))
def run_cli(): def run_cli():

View File

@ -12,8 +12,10 @@ _update_event_loop_policy()
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def event_loop(request): def event_loop(request):
"""Create an instance of the default event loop for entire session.""" """Create an instance of the default event loop for entire session."""
loop = asyncio.get_event_loop_policy().new_event_loop() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
yield loop yield loop
asyncio.set_event_loop(None)
loop.close() loop.close()