Implementing a while loop without interrupting the bot's main event loop - python-3.x

I am in the middle of having two of my bots interfacing with each other via a ZMQ server, unfortunately that also requires a second loop for the receiver, so i started looking around the web for solutions and came up with this:
async def interfaceSocket():
while True:
message = socket.recv()
time.sleep(1)
socket.send(b"World")
await asyncio.sleep(3)
#client.event
async def on_ready():
print('logged in as:')
print(client.user.name)
client.loop.create_task(interfaceSocket())
client.run(TOKEN)
I basically added the interfaceSocket function to the event loop as a task as another while loop so i can constantly check the socket receiver while also checking the on_message listener from the discord bot itself but for some reason, the loop still interrupts the main event loop. Why is this?

Although interfaceSocket is technically a task, it doesn't await anything in its while loop and uses blocking calls such as socket.recv() and time.sleep(). Because of that it blocks the whole event loop while it waits for something to happen.
If socket refers to a ZMQ socket, you should be using the ZMQ asyncio interface, i.e. use zmq.asyncio.Context to create a zmq.asyncio.Socket instead of. Then interfaceSocket can use await and become a well-behaved coroutine:
async def interfaceSocket():
while True:
message = await socket.recv()
await asyncio.sleep(1)
await socket.send(b"World")

Related

Python aiogram bot: send message from another thread

The telegram bot I'm making can execute a function that takes a few minutes to process and I'd like to be able to continue to use the bot while it's processing the function.
I'm using aiogram, asyncio and I tried using Python threading to make this possible.
The code I currently have is:
import asyncio
from queue import Queue
from threading import Thread
import time
import logging
from aiogram import Bot, types
from aiogram.types.message import ContentType
from aiogram.contrib.middlewares.logging import LoggingMiddleware
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from aiogram.dispatcher import Dispatcher, FSMContext
from aiogram.utils.executor import start_webhook
from aiogram.types import InputFile
...
loop = asyncio.get_event_loop()
bot = Bot(token=BOT_TOKEN, loop=loop)
dp = Dispatcher(bot, storage=MemoryStorage())
dp.middleware.setup(LoggingMiddleware())
task_queue = Queue()
...
async def send_result(id):
logging.warning("entered send_result function")
image_res = InputFile(path_or_bytesio="images/result/res.jpg")
await bot.send_photo(id, image_res, FINISHED_MESSAGE)
def queue_processing():
while True:
if not task_queue.empty():
task = task_queue.get()
if task["type"] == "nst":
nst.run(task["style"], task["content"])
send_fut = asyncio.run_coroutine_threadsafe(send_result(task['id']), loop)
send_fut.result()
task_queue.task_done()
time.sleep(2)
if __name__ == "__main__":
executor_images = Thread(target=queue_processing, args=())
executor_images.start()
start_webhook(
dispatcher=dp,
webhook_path=WEBHOOK_PATH,
skip_updates=False,
on_startup=on_startup,
host=WEBAPP_HOST,
port=WEBAPP_PORT,
)
So I'm trying to setup a separate thread that's running a loop that is processing a queue of slow tasks thus allowing to continue chatting with the bot in the meantime and which would send the result message (image) to the chat after it's finished with a task.
However, this doesn't work. My friend came up with this solution while doing a similar task about a year ago, and it does work in his bot, but it doesn't seem to work in mine.
Judging by logs, it never even enters the send_result function, because the warning never comes through. The second thread does work properly though and the result image is saved and is located in its assigned path by the time nst.run finishes working.
I tried A LOT of different things and I'm very puzzled why this solution doesn't work for me because it does work with another bot. For example, I tried using asyncio.create_task instead of asyncio.run_coroutine_threadsafe, but to no avail.
To my understanding, you don't need to pass a loop to aiogram's Bot or Dispatcher anymore, but in that case I don't know how to send a task to the main thread from the second one.
Versions I'm using: aiogram 2.18, asyncio 3.4.3, Python 3.9.10.
Solved, the issue was that you can't access the bot's loop directly (with bot.loop or dp.loop) even if you pass your own asyncio loop to the bot or the dispatcher.
So the solution was to access the main thread's loop by using asyncio.get_event_loop() (which returns currently running loop, if there's one) from within one of the message handlers, because the loop is running at this point, and pass it to asyncio.run_coroutine_threadsafe (I used the "task" dictionary for that) like this: asyncio.run_coroutine_threadsafe(send_result(task['id']), task['loop']).

Input Messages in Discord.py

I'm trying to find a way to program my bot to clear a specific amount of messages in a channel. However, I do not know how to get my bot to run it's code based on the user's input data. For example, let's say I'm a user who wants to clear a specific amount of messages, like let's say 15 messages. I want my bot to then clear 15 messages exactly, no more, no less. How do I do that?
if message.content == "{clear":
await message.channel.send("Okay")
await message.channel.send("How many messages should I clear my dear sir?")
This is legit all I got lmao. I'm sorry that I'm such a disappointment to this community ;(
Using a on_message event, you'd have to use the startswith mehtod and create a amount variable which takes your message content without {clear as a value:
if message.content.startswith("{clear"):
amount = int(message.content[7:])
await message.channel.purge(limit=amount+1)
However, I don't recommend using on_message events to create commands. You could use the commands framework of discord.py. It will be much easier for you to create commands.
A quick example:
from discord.ext import commands
bot = commands.Bot(command_prefix='{')
#bot.event
async def on_ready():
print("Bot's ready to go")
#bot.command(name="clear")
async def clear(ctx, amount: int):
await ctx.channel.purge(limit=amount+1)
bot.run("Your token")
ctx will allow you to access the message author, channel, guild, ... and will allow you to call methods such as send, ...

Why is run_forever required in this sample code?

In the python asyncio websockets library, the example calls run_forever(). Why is this required?
Shouldn't run_until_complete() block and run the websockets loop?
#!/usr/bin/env python
# WS server example
import asyncio
import websockets
async def hello(websocket, path):
name = await websocket.recv()
print(f"< {name}")
greeting = f"Hello {name}!"
await websocket.send(greeting)
print(f"> {greeting}")
start_server = websockets.serve(hello, "localhost", 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
# if you comment out the above line, this doesn't work, i.e., the server
# doesn't actually block waiting for data...
If I comment out run_forever(), the program ends immediately.
start_server is an awaitable returned by the library. Why isn't run_until_complete sufficient to cause it to block/await on hello()?
websockets.serve simply starts the server and exits immediately. (It still needs to be awaited because configuring the server can require network communication.) Because of that, you need to actually run the event loop.
Since the server is designed to run indefinitely, you cannot run the event loop in the usual way, by passing a coroutine to run_until_complete. As the server has already started, there is no coroutine to run, you just need to let the event loop run and do its job. This is where run_forever comes in useful - it tells the event loop to run (executing the tasks previously scheduled, such as those belonging to the server) indefinitely, or until told to stop by a call to loop.stop.
In Python 3.7 and later one should use asyncio.run to run asyncio code, which will create a new event loop, so the above trick won't work. A good way to accomplish the above in modern asyncio code would be to use the serve_forever method (untested):
async def my_server():
ws_server = await websockets.serve(hello, "localhost", 8765)
await ws_server.server.serve_forever()
asyncio.run(my_server())

Non-blocking /dev/hidrawX reading with asyncio in Linux

what is the right spell to do something like:
async def read(fd):
return fd.readline()
with open('/dev/hidraw0', 'rb') as fd:
while True:
line = await read(fd)
if line is None:
break
consume(line)
I need to poll /dev/hidrawX from a program built around asyncio.
How can I do it in non-blocking fashion?
I would like to avoid to go the /dev/input/eventXX way with all associated conversion problems (and also because I tried and events are lost in transit)

Why do I have to use async with when using the aiohttp module?

When writing asynchronous crawlers using asyncio and aiohttp in Python, I have always had a question: why you must use async with, and it's easy to report errors if you don't use them.
Although aiohttp also has a method request, it can support calling a simpler api. I want to know what is the difference. I still like the requests module very much, I don't know if it can be used as simple as the requests module.
why you must use async with
It's not like you must use async with, it's just a fail-safe device for ensuring that the resources get cleaned up. Taking a classic example from the documentation:
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
You can re-write it as:
async def fetch(session, url):
response = await session.get(url)
return await response.text()
This version appears to work the same, but it doesn't close the response object, so some OS resources (e.g. the underlying connection) may continue to be held indefinitely. A more correct version would look like this:
async def fetch(session, url):
response = await session.get(url)
content = await response.text()
response.close()
return content
This version would still fail to close the response if an exception gets raised while reading the text. It could be fixed by using finally - which is exactly what with and async with do under the hood. With an async with block the code is more robust because the language makes sure that the cleanup code is invoked whenever execution leaves the block.

Resources