Asyncio event loop is closed when using asyncio.run() - python

I'm getting started to AsyncIO and AioHTTP, and i'm writing some basic code to get familiar with the syntax. I tried the following code that should perform 3 requests concurrently:
import time
import logging
import asyncio
import aiohttp
import json
from aiohttp import ClientSession, ClientResponseError
from aiocfscrape import CloudflareScraper
async def nested(url):
async with CloudflareScraper() as session:
async with session.get(url) as resp:
return await resp.text()
async def main():
URL = "https://www.binance.com/api/v3/exchangeInfo"
await asyncio.gather(nested(URL), nested(URL), nested(URL))
asyncio.run(main())
Here is the output:
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
I don't understand why do i get that error, can anyone help me on this?

Update
Originally I was recommending Greg's answer below:
import asyncio
import sys
if sys.platform:
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
Turned out, using WindowsSelectorEventLoop has functionality issues such as:
Can't support more than 512 sockets
Can't use pipe
Can't use subprocesses
due to the fact that Windows uses I/O completion Ports unlike *nix - Therefore SelectorEventLoop is not designed for Windows nor is implemented as full.
If those limitations matters to you - You might be better off using lengthy workaround in this answer.
Check out more about differences at documents.
Or alternatively, consider using Trio over asyncio, which is much more stable and consistent.
import trio
async def task():
await trio.sleep(5)
trio.run(task)
Original post
I've finally figured out how to keep ProactorEventLoop running, preventing unsuccessful IO closure.
Really not sure why windows' Event loop is so faulty, as this also happens for asyncio.open_connection and asyncio.start_server.
To workaround this, you need to run event loop in forever loop and close manually.
Following code will cover both windows and other environments.
import asyncio
from aiocfscrape import CloudflareScraper
async def nested(url):
async with CloudflareScraper() as session:
async with session.get(url) as resp:
return await resp.text()
async def main():
await nested("https://www.binance.com/api/v3/exchangeInfo")
try:
assert isinstance(loop := asyncio.new_event_loop(), asyncio.ProactorEventLoop)
# No ProactorEventLoop is in asyncio on other OS, will raise AttributeError in that case.
except (AssertionError, AttributeError):
asyncio.run(main())
else:
async def proactor_wrap(loop_: asyncio.ProactorEventLoop, fut: asyncio.coroutines):
await fut
loop_.stop()
loop.create_task(proactor_wrap(loop, main()))
loop.run_forever()
This code will check if new EventLoop is ProactorEventLoop.
If so, keep loop forever until proactor_wrap awaits main and schedules loop stop.
Else - possibly all other OS than Windows - doesn't need these additional steps, simply call asyncio.run() instead.
IDE like Pycharm will complain about passing AbstractEventLoop to ProactorEventLoop parameter, safe to ignore.

Whilst this has been answered and accepted. You can fix this issue with one line of code: asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
Event loop is closed is a known issue on Windows (see https://github.com/encode/httpx/issues/914). I suspect this will be fixed in later versions of Python. To get around the error, simply set the event loop policy to WindowsSelectorEventLoopPolicy.
If you plan to run the code on non-windows environment; then you'll want to either add an if statement to prevent error. E.g: if sys.platform == 'win32'. Or add code to set the policies.
Working example:
import asyncio
from aiocfscrape import CloudflareScraper
import sys
async def nested(url):
async with CloudflareScraper() as session:
async with session.get(url) as resp:
print(resp.status)
return await resp.text()
async def main():
URL = "https://www.binance.com/api/v3/exchangeInfo"
await asyncio.gather(nested(URL), nested(URL), nested(URL))
# Only preform check if your code will run on non-windows environments.
if sys.platform == 'win32':
# Set the policy to prevent "Event loop is closed" error on Windows - https://github.com/encode/httpx/issues/914
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
asyncio.run(main())

Related

Discord cogs doesn't load

I am trying to run two different Discord Bots using a single python script using cogs. But when I try to run the 2nd bot it throws an ImportError even-though I didn't use that specific Library. The reaction roles bot works fine without the anti spam bot. Here's my code. FYI I am working inside a Virtual Env.
main.py
if __name__ == "__main__":
try:
reaction_role_bot = commands.Bot(command_prefix=config["reaction_role_bot"]["bot_prefix"], intents=discord.Intents.all())
reaction_slash = SlashCommand(reaction_role_bot, sync_commands=True)
reaction_role_bot.load_extension(f"cogs.{str(os.path.basename('cogs/reaction_roles.py')[:-3])}")
anti_spam_bot = commands.Bot(command_prefix=config["anti_spam_bot"]["bot_prefix"], intents=discord.Intents.default())
spam_slash = SlashCommand(anti_spam_bot, sync_commands=True)
anti_spam_bot.load_extension(f"cogs.{str(os.path.basename('cogs/anti_spam.py')[:-3])}")
event_loop = asyncio.get_event_loop()
event_loop.create_task(reaction_role_bot.run(config["reaction_role_bot"]["token"]))
event_loop.create_task(anti_spam_bot.run(config["anti_spam_bot"]["token"]))
event_loop.run_forever()
except Exception as e:
print(e)
anti_spam.py
import platform
import os
import discord
from discord.ext import commands
from antispam import AntiSpamHandler
from antispam.plugins import AntiSpamTracker, Options
class AntiSpamBot(commands.Cog):
def __init__(self, client):
self.client = client
# Initialize the AntiSpamHandler
self.client.handler = AntiSpamHandler(self.client, options=Options(no_punish=True))
# 3 Being how many 'punishment requests' before is_spamming returns True
self.client.tracker = AntiSpamTracker(self.client.handler, 3)
self.client.handler.register_extension(self.client.tracker)
#commands.Cog.listener()
async def on_ready(self):
print("---------------------------------")
print(f"Logged in as {str(self.client.user)}")
print(f"Discord.py API version: {discord.__version__}")
print(f"Python version: {platform.python_version()}")
print(f"Running on: {platform.system()} {platform.release()} ({os.name})")
await self.client.change_presence(status=discord.Status.idle, activity=discord.Game(name="Head of Security"))
print("---------------------------------\n")
# The code in this event is executed every time a valid commands catches an error
#commands.Cog.listener()
async def on_command_error(context, error):
raise error
#commands.Cog.listener()
async def on_message(self, message):
await self.client.handler.propagate(message)
if self.client.tracker.is_spamming(message):
await message.delete()
await message.channel.send(f"{message.author.mention} has been automatically kicked for spamming.")
await message.author.kick()
await self.client.process_commands(message)
def setup(client):
client.add_cog(AntiSpamBot(client))
Error
Extension 'cogs.anti_spam' raised an error: ImportError: cannot import name 'AsyncMock' from 'unittest.mock' (/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/mock.py)
I've no experiences using cogs and its bit confusing me. Any kind of help would help me to sort this out! Thanks in advance!
I do not believe this is a cog registration issue. I believe this is an import error from some of the dependencies in your cog file. I googled your error and found something similar, I recommend checking it out here for some more information.
As a blanket statement, I would double check that you have mock installed, and that you're installing it on the version of Python that you think you're installing it on. It can get wonky if you have multiple python versions insealled.
Also, on an unrelated note:: It is best to avoid running multiple bot instances in one python file, but I can help you do it the best way possible.
For starters, you have to realize that Client.run is an abstraction of a couple of more lower level concepts.
There is Client.login which logs in the client and then Client.connect which actually runs the processing. These are coroutines.
asyncio provides the capability of putting things in the event loop for it to work whenever it has time to.
Something like this e.g.
loop = asyncio.get_event_loop()
async def foo():
await asyncio.sleep(10)
loop.close()
loop.create_task(foo())
loop.run_forever()
If we want to wait for something to happen from another coroutine, asyncio provides us with this functionality as well through the means of synchronisation via asyncio.Event. You can consider this as a boolean that you are waiting for:
e = asyncio.Event()
loop = asyncio.get_event_loop()
async def foo():
await e.wait()
print('we are done waiting...')
loop.stop()
async def bar():
await asyncio.sleep(20)
e.set()
loop.create_task(bar())
loop.create_task(foo())
loop.run_forever() # foo will stop this event loop when 'e' is set to true
loop.close()
Using this concept we can apply it to the discord bots themselves.
import asyncio
import discord
from collections import namedtuple
# First, we must attach an event signalling when the bot has been
# closed to the client itself so we know when to fully close the event loop.
Entry = namedtuple('Entry', 'client event')
entries = [
Entry(client=discord.Client(), event=asyncio.Event()),
Entry(client=discord.Client(), event=asyncio.Event())
]
# Then, we should login to all our clients and wrap the connect call
# so it knows when to do the actual full closure
loop = asyncio.get_event_loop()
async def login():
for e in entries:
await e.client.login()
async def wrapped_connect(entry):
try:
await entry.client.connect()
except Exception as e:
await entry.client.close()
print('We got an exception: ', e.__class__.__name__, e)
entry.event.set()
# actually check if we should close the event loop:
async def check_close():
futures = [e.event.wait() for e in entries]
await asyncio.wait(futures)
# here is when we actually login
loop.run_until_complete(login())
# now we connect to every client
for entry in entries:
loop.create_task(wrapped_connect(entry))
# now we're waiting for all the clients to close
loop.run_until_complete(check_close())
# finally, we close the event loop
loop.close()

How to use asyncio.wait_for to run_until_complete to synchronously call async method in Python

To allow timeouts receiving data via Python websocket, the FAQ: How do I set a timeout on recv()? recommends using asynchronously receive data:
await asyncio.wait_for(websocket.recv(), timeout=10)
Since the function I receive data is not async, I've adapted this answer to run the asyncio loop until data was received or the timeout occurred:
loop.run_until_complete(asyncio.wait_for(ws.recv(), timeout=10))
Unfortunately this statement seems to be not valid, since the following exception occurs:
An asyncio.Future, a coroutine or an awaitable is required
For me, it looks like asyncio.wait_for is no valid parameter for the run_until_complete although the documentation clearly shows an example which awaits for it.
What am I missing here - and what would be the correct way to use asyncio.wait_for in a synchronous method?
You need to feed loop.run_until_complete with a coroutine. To do so, you can wrap your receiving code into an async function:
async def receive_message():
return await asyncio.wait_for(ws.recv(), timeout=10)
loop.run_until_complete(receive_message())
Here's a fully working code:
import asyncio
import websockets
URI = "ws://0.0.0.0:8765"
TIMEOUT = 2
async def create_ws():
return await websockets.connect(URI)
async def receive_message():
ws = await create_ws()
print("Connected")
message = await asyncio.wait_for(ws.recv(), timeout=TIMEOUT)
print(f"Received message in less than {TIMEOUT} seconds: {message}")
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(receive_message())
There are two common websocket modules available in Python.
While websockets is imported with import websockets there is also the websocket-client which is imported with import websocket.
Both of the modules offering nearly the same API where ws.recv() allows to receive data.
The fact the modules are so similar to each other may cause confusion which at least lead to my exception.
While websockets are capable of async operations, websocket is not. This means my statement
loop.run_until_complete(asyncio.wait_for(ws.recv(), timeout=10))
will only work for websockets, if used with websocket, the exception
An asyncio.Future, a coroutine or an awaitable is required
will occur.

Get realtime updates with Cloud Firestore with asyncio

How can I run this example through asyncio? The error I got on startup:
name col_snapshot is not defined
sample:
async def on_snapshot(col_snapshot, changes, read_time):
logger.debug('Received updates')
col_query = db.collection(u'cities')
query_watch = col_query.on_snapshot(await on_snapshot(col_snapshot, changes, read_time))
Update: Async callbacks example
import os
import asyncio
from loguru import logger
from google.cloud.firestore import Client
os.environ['GOOGLE_APPLICATION_CREDENTIALS']='E:/Python/listener_firebase/creds.json'
async def callback(col_snapshot, changes, read_time):
logger.debug('Received updates')
for change in changes:
if change.type.name == 'ADDED':
logger.debug(f'New document: {change.document.id}')
await asyncio.sleep(1)
logger.debug('Finished handling the updates')
Client().collection('cities').on_snapshot(callback)
while True:
pass
This doesn't work for me:
tch.py:568: RuntimeWarning: coroutine 'callback' was never awaited
self._snapshot_callback(keys, appliedChanges, read_time)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
As it's written, your code shouldn't use asyncio at all. If, however, await asyncio.sleep(1) is there to simulate some I/O heavy code that takes advantage of asyncio, your options here aren't great. The first solution would be to use an asyncio-aware client for Firestore. I have no idea if one exists. Assuming one doesn't, and, as you say, you need to use an event loop that is already running, you need to define a synchronous function (since that's what needs to be passed to the client's on_snapshot) that schedules your asynchronous callback with the event loop.
import os
import asyncio
from loguru import logger
from google.cloud.firestore import Client
os.environ['GOOGLE_APPLICATION_CREDENTIALS']='E:/Python/listener_firebase/creds.json'
async def asynchronous_callback(changes):
logger.debug('Received updates')
for change in changes:
if change.type.name == 'ADDED':
logger.debug(f'New document: {change.document.id}')
await asyncio.sleep(1)
logger.debug('Finished handling the updates')
def synchronous_callback(col_snapshot, changes, read_time):
asyncio.create_task(asynchronous_callback(changes))
Client().collection('cities').on_snapshot(synchronous_callback)
while True:
pass
Really, though, your best option is not to use asyncio for your callback since the Firestore client is already running your code synchronously.

Using Requests library to make asynchronous requests with Python 3.7

I need to make asynchronous requests using the Requests library. In Python 3.7 if I try from requests import async I get SyntaxError: invalid syntax.
async has become a reserved with in Python 3.7. How to I get around this situation?
Lukasa who is with the requests lib said:
At the current time there are no plans to support async and await. This is not because they aren't a good idea: they are. It's because to use them requires quite substantial code changes.
Right now requests is a purely synchronous library that, at the bottom of its stack, uses httplib to send and receive data. We cannot move to an async model unless we replace httplib. The best we could do is provide a shorthand to run a request in a thread, but asyncio already has just such a shorthand, so I don't believe it would be valuable.
Right now I am quietly looking at whether we can rewrite requests to work just as well in a synchronous environment as in an async one. However, the reality is that doing so will be a lot of work, involving rewriting a lot of our stack, and may not happen for many years, if ever.
But don't worry aiohttp is very similar to requests.
Here's an example.
import aiohttp
import asyncio
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
html = await fetch(session, 'http://python.org')
print(html)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
You can use asyncio to make asynchronous requests. Here is an example:
import asyncio
import requests
async def main():
loop = asyncio.get_event_loop()
futures = [
loop.run_in_executor(
None,
requests.get,
'http://example.org/'
)
for i in range(20)
]
for response in await asyncio.gather(*futures):
pass
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Python asyncio (aiohttp, aiofiles)

I seem to be having a difficult time understanding pythons asyncio. I have not written any code, as all the examples I see are for one-off runs. Create a few coroutine's, add them to an event loop, then run the loop, they run the tasks switching between them, done. Which does not seem all that helpful for me.
I want to use asyncio to not interrupt the operation in my application (using pyqt5). I want to create some functions that when called run in the asyncio event loop, then when they are done they do a callback.
What I imagine is. Create a separate thread for asyncio, create the loop and run it forever. Create some functions getFile(url, fp), get(url), readFile(file), etc. Then in the UI, I have a text box with a submit button, user enters url, clicks submit, it downloads the file.
But, every example I see, I cannot see how to add a coroutine to a running loop. And I do not see how I could do what I want without adding to a running loop.
#!/bin/python3
import asyncio
import aiohttp
import threading
loop = asyncio.get_event_loop()
def async_in_thread(loop):
asyncio.set_event_loop(loop)
loop.run_forever()
async def _get(url, callback):
print("get: " + url)
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
result = await response.text()
callback(result)
return
def get(url, callback):
asyncio.ensure_future(_get(url, callback))
thread = threading.Thread(target=async_in_thread, args=(loop, ))
thread.start()
def stop():
loop.close()
def callme(data):
print(data)
stop()
get("http://google.com", callme)
thread.join()
This is what I imagine, but it does not work.
To add a coroutine to a loop running in a different thread, use asyncio.run_coroutine_threadsafe:
def get(url, callback):
asyncio.run_coroutine_threadsafe(_get(url, callback))
In general, when you are interacting with the event loop from outside the thread that runs it, you must run everything through either run_coroutine_threadsafe (for coroutines) or loop.call_soon_threadsafe (for functions). For example, to stop the loop, use loop.call_soon_threadsafe(loop.stop). Also note that loop.close() must not be invoked inside a loop callback, so you should place that call in async_in_thread, right after the call to run_forever(), at which point the loop has definitely stopped running.
Another thing with asyncio is that passing explicit when_done callbacks isn't idiomatic because asyncio exposes the concept of futures (akin to JavaScript promises), which allow attaching callbacks to a not-yet-available result. For example, one could write _get like this:
async def _get(url):
print("get: " + url)
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
It doesn't need a callback argument because any interested party can convert it to a task using loop.create_task and use add_done_callback to be notified when the task is complete. For example:
def _get_with_callback(url, callback):
loop = asyncio.get_event_loop()
task = loop.create_task(_get(url))
task.add_done_callback(lambda _fut: callback(task.result()))
In your case you're not dealing with the task directly because your code aims to communicate with the event loop from another thread. However, run_coroutine_threadsafe returns a very useful value - a full-fledged concurrent.futures.Future which you can use to register done callbacks. Instead of accepting a callback argument, you can expose the future object to the caller:
def get(url):
return asyncio.run_coroutine_threadsafe(_get(url), loop)
Now the caller can choose a callback-based approach:
future = get(url)
# call me when done
future.add_done_callback(some_callback)
# ... proceed with other work ...
or, when appropriate, they can even wait for the result:
# give me the response, I'll wait for it
result = get(url).result()
The latter is by definition blocking, but since the event loop is safely running in a different thread, it is not affected by the blocking call.
Install QualMash to smooth integration between Qt and asyncio.
Example from the project's README gives an inspiration for how it looks like:
import sys
import asyncio
import time
from PyQt5.QtWidgets import QApplication, QProgressBar
from quamash import QEventLoop, QThreadExecutor
app = QApplication(sys.argv)
loop = QEventLoop(app)
asyncio.set_event_loop(loop) # NEW must set the event loop
progress = QProgressBar()
progress.setRange(0, 99)
progress.show()
async def master():
await first_50()
with QThreadExecutor(1) as exec:
await loop.run_in_executor(exec, last_50)
async def first_50():
for i in range(50):
progress.setValue(i)
await asyncio.sleep(.1)
def last_50():
for i in range(50,100):
loop.call_soon_threadsafe(progress.setValue, i)
time.sleep(.1)
with loop: ## context manager calls .close() when loop completes, and releases all resources
loop.run_until_complete(master())

Categories

Resources