accesing loop attribute in non-async contexts - python

async def create_db_pool():
database_url = ''
client.pg_con = await asyncpg.create_pool(database_url, ssl="require")
so i have this function in my discord bot code but when i try to run this code using
client.loop.run_until_complete(create_db_pool())
i get the following error currently i am looking for a workaround for this or any way to solve it
AttributeError: loop attribute cannot be accessed in non-async contexts. Consider using either an asynchronous main function and passing it to asyncio.run or using asynchronous initialisation hooks such as Client.setup_hook

You must be using the master version of discord.py
It recently introduced breaking changes with asyncio, with one of them being this.
client.loop is no more accessible in a sync context. This gist explains what was the change and how to make a work around.
First way would be to introduce a setup_hook() function inside a commands.Bot subclass and use await create_db_pool() in there
class MyBot(commands.Bot):
def __init__(self, **kwargs):
super().__init__(**kwarg)
self.pg_conn: = None
async def create_db_pool(self): # making it a bound function in my example
database_url = ''
self.pg_con = await asyncpg.create_pool(database_url, ssl="require")
async def setup_hook(self):
await self.create_db_pool() # no need to use `loop.run_*` here, you are inside an async function
or you could also do this inside a main() function
async def main():
await create_db_pool() # again, no need to run with AbstractLoopEvent if you can await
await bot.start(TOKEN)
asyncio.run(main())

Are you running your bot in a synchronous context? The code should look something like:
async def on_message(ctx): ...
Also please show some code. But I think learning the asyncio module will help. Anyway, try this:
import asyncio
async def create_db_pool() -> None: ... # Your code
loop = asyncio.get_event_loop()
loop.run_until_complete(function_async())
loop.close()
This will not run your function asynchronously, but it doesn't seem like you wish to do so. But it should actually successfully run the function.

Related

Discord Python cog_check issue

New to coding. I want to prevent my bot from responding to itself/other bots, and I'm trying to invoke a cog_check but can't make it work properly. Help would be greatly appreciated.
class CommandsMisc(commands.Cog):
def __init__(self, client):
self.client = client
async def cog_check(self, ctx):
if ctx.author.bot: # also tried: if ctx.author == self.client.user:
await exit()
#commands.Cog.listener("on_message") # example cog command
async def print(self, message):
if '!guide' in message.content:
await message.reply(Guide_Text)
else:
pass
Since some people seem to lack reading comprehension skills, my question is how can I best utilize "cog_check" in Discord.py so that all subsequent commands/listeners in the cog will check whether the message.author is the bot and then won't execute if it is?
It seems like what you want is a custom command decorator. You can do this as follows:
def is_not_bot():
async def predicate(ctx):
return not ctx.author.bot
return commands.check(predicate)
You place this in your cog class, then on any command inside the cog you can use the decorator #is_not_bot()
What does this do, exactly? Well, we create a function which can be used as a command decorator (#is_not_bot()). Inside this, we create a function called predicate. This predicate will return True if the user is not a bot. We then call return commands.check(predicate) inside the parent function. This will allow us to use this as a decorator.
discord.py docs reference
what you can have is a on_message event, which can do checks on the message before processing the command, something like this
bot = commands.Bot(command_prefix="!", intents=discord.Intents.all())
# or if you made your subclass of Bot or its variants
class CustomBot(commands.Bot):
def __init__(self):
...
bot = CustomBot()
#bot.event
async def on_message(message):
if message.author.bot:
return # this is a bot, so we can early exit
# any other checks
await bot.process_commands(message)
exit() will just make your entire program close. cog_check should return a Boolean that indicates if the command should run or not. In your case, you don't return anything if it's okay, so it returns None which is falsey, so your check always fails.
Make it return True if it should succeed, and False if it should fail.
Edit
The ext.commands framework already prevents bots from running your commands, so this is not something you should even have to worry about.
The reason your cog_check still doesn't do anything is because you don't have any commands. You're using an on_message listener to manually parse messages, instead of creating commands. Your "example cog command" is not a command, it's just an event listener. Those are two very different concepts.
cog_check is only invoked when actual commands are run, not before dispatching events like on_message, so this doesn't have any effect here. If you parse commands in on_message, checks aren't used at all.
See the docs for ext.commands for how to create commands: https://discordpy.readthedocs.io/en/stable/ext/commands/index.html

async function in Python not working as expected

I have the following code snippet:
import asyncio
def main():
asyncio.run(work())
print("BEFORE")
async def work():
await asyncio.sleep(1)
print("AFTER")
main()
I would like "BEFORE" to be printed first, followed by "AFTER" however this prints "AFTER" first instead - have I misunderstood how async functions work? I thought that running asyncio.run(...) would allow my code to skip to print("BEFORE") while still running work() in the background.
Ideal output is:
BEFORE
AFTER
Right now it's
AFTER
BEFORE
run seems to be blocking, so you might want to just use gather here:
import asyncio
async def main():
await asyncio.gather(work(), setup())
print('After gather')
async def work():
await asyncio.sleep(2)
print("AFTER")
async def setup():
print("BEFORE")
asyncio.run(main())
This correctly prints
BEFORE
AFTER
After gather
The entire gather statement itself is blocking as well, but all of the functions specified in the gather statement can run asynchronously.
According to the documentation:
The asyncio.run() function to run the top-level entry point “main()” function (see the above example.)
The asyncio.run should be the entry point, not used for starting a background job. So it does not skip to print("BEFORE").

Python Process blocking the rest of application

i have a program that basically does 2 things:
opens a websocket and remains on listening for messages and starting a video streaming in a forever loop.
I was trying to use multiprocess to manage both things but one piece stops the other from running.
The app is
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(start_client())
async def start_client():
async with WSClient() as client:
pass
class WSClient:
async def __aenter__(self):
async with websockets.connect(url,max_size= None) as websocket:
self.websocket = websocket
await self.on_open() ## it goes
p = Process(target = init(self)) # This is the streaming method
p.start()
async for message in websocket:
await on_message(message, websocket) ## listen for websocket messages
return self
the init method is
def init(ws):
logging.info('Firmware Version: ' + getVersion())
startStreaming(ws)
return
basically startStreaming has an infinite loop in it.
In this configuration, the stream starts but the on_message of the websocket it's not called because the Process function freezes the rest of the application.
How can I run both methods?
Thanks
In your code, you're telling multiprocessing.Process to take the function returned by init and call it in a new process. What you want is for the process to call init itself (with an argument). Here's how you can do that:
p = Process(target=init, args=(self,))
I have to note that you're passing an asynchronous websocket object to your init function. This will likely break as asyncio stuff aren't usually meant to be used in two threads, let alone two processes. Unless you're somehow recreating the websocket object in the new process and making a new loop there too, what you're actually looking for is how to create an asyncio task.
Assuming startStreaming is already an async function, you should change the init function to this:
async def init(ws): # note the async
logging.info('Firmware Version: ' + getVersion())
await startStreaming(ws) # note the await
return
and change the line creating and starting the process to this:
asyncio.create_task(init(self))
This will run your startStreaming function in a new task while you also read incoming messages at (basically) the same time.
Also, I'm not sure what you're trying to do with the async context manager as everything could be just in a normal async function. If you're interested in using one for learning purposes, I'd suggest you to check out contextlib.asynccontextmanager and have your message reading code inside the async with statement in start_client rather than inside __aenter__.

How to use python async def without awaiting on every parent function call

I was wondering if it possible to create async function without setting main as async.
I have a function call like this:
async def C:
t = asyncio.create_subprocess_shell(command)
...
await t
def B:
asyncio.set_event_loop(asyncio.new_event_loop())
C()
<How to await for C?>
def A:
B()
I'm unable to await for C (either as a task or a future). The function exits immediately after starting C(). I've tried loop.create_task and loop.run_until_complete(task) but nothing seems to work.
I don't want to set all parent function calls as async up to main(). Is there a way to do this?
Edit:
My original problem is to run multiple shell commands in parallel (which start another application) from a python function and then wait for their results.
The canonical way to use asyncio in modern Python (3.7 and later) is something like:
async def main():
... your code here ...
async.run(main())
To do multiple things in parallel, you can use asyncio.gather. For example:
async def main():
proc1 = await asyncio.create_subprocess_shell(command1)
proc2 = await asyncio.create_subprocess_shell(command2)
# wait in parallel for both processes to finish and obtain their statuses
status1, status2 = await asyncio.gather(proc1.wait(), proc2.wait())

How do I create a lazy Future in Python (asyncio) that can be kicked off after creation?

I'm trying to wrap my head around asyncio in Python. I wrote this little programm that when invoked will first print
Server booting
Do stuff called
and then after one second
Async Thingy
That is exactly what it is supposed to do but it's not quite the way I want it yet.
Basically this mimics a Server that wants to create a PeerPool in __init__ which depends on ThingThatWeAreWaitingOn. I want to be able to create the PeerPool in __init__ and pass an Awaitable[ThingThatWeAreWaitingOn] that the PeerPool can use as soon as it is ready. Again, this seems to work just fine but the catch is, as the code stands now, we kick off the task to resolve ThingThatWeAreWaitingOn directly from within __init__ but ideally I'd like to be able to kick that off from within run().
How would I do that?
import asyncio
from typing import (
Awaitable,
Any
)
class ThingThatWeAreWaitingOn():
name = "Async Thingy"
class PeerPool():
def __init__(self, discovery: Awaitable[ThingThatWeAreWaitingOn]):
self.awaitable_discovery = discovery
def do_stuff(self):
print("Do stuff called")
self.awaitable_discovery.add_done_callback(lambda d: print(d.result().name))
class Server():
def __init__(self):
# This immediately kicks of the async task but all I want is to
# create a Future to pass that would ideally be kicked off in
# the run() method
self.fut_discovery = asyncio.ensure_future(self.get_discovery())
self.peer_pool = PeerPool(self.fut_discovery)
async def get_discovery(self):
await asyncio.sleep(1)
return ThingThatWeAreWaitingOn()
def run(self):
loop = asyncio.get_event_loop()
print("Server booting")
# Here is where I want to "kick off" the self.fut_discovery but how?
# self.fut_discovery.kick_off_now()
self.peer_pool.do_stuff()
loop.run_forever()
server = Server()
server.run()
Here's a link to a runnable demo: https://repl.it/repls/PleasedHeavenlyLock
If I understand everything right, you want something like this:
class Server():
def __init__(self):
self.fut_discovery = asyncio.Future()
self.peer_pool = PeerPool(self.fut_discovery)
async def get_discovery(self):
await asyncio.sleep(1)
return ThingThatWeAreWaitingOn()
def run(self):
loop = asyncio.get_event_loop()
print("Server booting")
async def discovery_done():
res = await self.get_discovery()
self.fut_discovery.set_result(res)
asyncio.ensure_future(discovery_done()) # kick discovery to be done
self.peer_pool.do_stuff()
loop.run_forever()
You may want to rewrite code somehow to make in clearer. Right now it's not very clear what you're going to do and which part of code depends of which.
For example, awaitable_discovery name is misleading: plain awaitable not necessary has add_done_callback method. If you're planing to use this method, signature
class PeerPool():
def __init__(self, fut_discovery: asyncio.Future):
will make more sense.
May be you should create class for discovery. You can inherit asyncio.Future or implement __await__ magic method to make it's objects future-like/awaitable.

Categories

Resources