How to intergrate telethon script into my event loop? - python

I have two bots, one is using pydle for IRC, like:
async def start_ircbot ():
try:
client = MyOwnBot(NICK,
realname=REALNAME,
sasl_username=SASL_USERNAME,
sasl_password=SASL_PASSWORD,
sasl_identity=SASL_IDENTITY,)
loop = asyncio.get_event_loop()
asyncio.ensure_future(client.connect(HOST, PORT, tls=True, tls_verify=False), loop=loop)
loop.run_forever()
loop.close()
except Exception as e:
print (e)
and another is using telethon for Telegram:
#client.on(events.NewMessage)
async def my_event_handler(event):
...
async def start_client ():
print ("Telegram monitor started...")
await client.start()
await client.run_until_disconnected()
Both of them work without problem separately.
Now, I want to integrate both of them, I tried to launch both of them in my main function like this,
import Notifier
...
async def main():
await asyncio.gather (Notifier.start_client (), start_ircbot ())
asyncio.run(main())
It starts without issue but my_event_handler seems never to get new messages. If I swap the order of functions:
await asyncio.gather (start_ircbot (), Notifier.start_client ())
The script will be stuck at launching, I suspect it has to be something within events loops and tried some different methods but without luck, could anyone shed light on this for me?

Newer Python versions are removing the loop parameter from most methods, so you should try to avoid using it. As long as you don't use asyncio.run (which creates a new loop) or you don't create a new loop yourself, both libraries should be using the default loop from the main thread (I can't speak for pydle, but Telethon does this).
As long as the asyncio event loop is running, Telethon should have no trouble receiving updates. You can use client.loop to make sure it's using the same loop:
tlclient = TelegramClient(...)
irclient = MyOwnBot(...)
#tlclient.on(events.NewMessage)
async def my_event_handler(event):
...
async def main():
await tlclient.start()
await irclient.connect(HOST, PORT, tls=True, tls_verify=False), loop=tlclient.tlclient)
await tlclient.run_until_disconnected() # similar to loop.run_forever() but stops when the client disconnects
client.loop.run_until_complete(main())

Related

Using OSC to update sliders in GANSpace

I'm trying to update the sliders in interactive.py from GANSpace with messages from python-osc.
Ideally the on_draw function should run after receiving a series of OSC messages.
But I'm having trouble with implementing it, because the serve.forever() function is blocking and i can't figure out how to implement the async version.
When i do like this:
def set_slider_osc(addr, args):
print("[{0}] ~ {1}".format(args, addr))
#print(re.split('/',addr)[2])
if "/0" in addr:
ui_state.sliders[0].set(args)
if "/1" in addr:
ui_state.sliders[1].set(args)
async def loop():
while not pending_close:
root.update()
app.update()
on_draw()
reposition_toolbar()
await asyncio.sleep(0)
async def init_main():
setup_model()
setup_ui()
resample_latent()
pending_close = False
dispatcher = dispatcher.Dispatcher()
dispatcher.map("/Chroma/*", set_slider_osc)
server = AsyncIOOSCUDPServer(("127.0.0.1", 8000), dispatcher, asyncio.get_event_loop())
transport, protocol = await server.create_serve_endpoint() # Create datagram endpoint and start serving
await loop() # Enter main loop of program
transport.close() # Clean up serve endpoint
root.destroy()
#
if __name__=='__main__':
await init_main()
I get the error that i try to run 'await' outside a function and another way i tried it seemed like there already was a main loop running and therefore couldn't run a new..
I know it's allot to aks people to install GANSpace, but hope someone may shine some light on how one could do this.
await cannot be used outside of a function defined with async def. You are trying to use it outside a function. The fix is to run init_main in an event loop.
import asyncio
asyncio.run(init_main())

Call Async function in Signal Handler, Asyncio

I am making a Discord bot using discord.py and sometimes the script I use to run it needs to close out the bot. When I close it out without using signal handlers there is a lot of errors about a loop not closing, so I added a signal handler (using the code below) and inside I need to call client.close() and client.logout(), but the problem is those are async functions and thus require me to await them, but I can't await the functions since the signal handler can't be an async function.
Here is the code:
def handler():
print("Logging out of Discord Bot")
client.logout()
client.close()
sys.exit()
#client.event
async def on_ready():
print('We have logged in as {0.user}'.format(client))
for signame in ('SIGINT', 'SIGTERM'):
client.loop.add_signal_handler(getattr(signal, signame),
lambda: asyncio.ensure_future(handler()))
Is there a way to either logout properly using the signal handler, or at least just silence the warnings and errors from inside the code so no errors are printed in the console.
Your approach is on the right track - since add_signal_handler expects an ordinary function and not an async function, you do need to call ensure_future (or its cousin create_task) to submit an async function to run in the event loop. The next step is to actually make handler async, and await the coroutines it invokes:
async def handler():
print("Logging out of Discord Bot")
await client.logout()
await client.close()
asyncio.get_event_loop().stop()
Note that I changed sys.exit() to explicit stopping of the event loop, because asyncio doesn't react well to sys.exit() being invoked from the middle of a callback (it catches the SystemExit exception and complains of an unretrieved exception).
Since I don't have discord to test, I tested it by changing the logout and close with a sleep:
import asyncio, signal
async def handler():
print("sleeping a bit...")
await asyncio.sleep(0.2)
print('exiting')
asyncio.get_event_loop().stop()
def setup():
loop = asyncio.get_event_loop()
for signame in ('SIGINT', 'SIGTERM'):
loop.add_signal_handler(getattr(signal, signame),
lambda: asyncio.create_task(handler()))
setup()
asyncio.get_event_loop().run_forever()
If you are starting the event loop using something other than run_forever, such as asyncio.run(some_function()), then you will need to replace loop.stop() with the code that sets whatever event the main coroutine awaits. For example, if it awaits server.serve_forever() on a server, then you'd pass the server object to handler and call server.close(), and so on.

discord.py way to use the bot in a new thread

I'm making my discord.py bot, and I want a way to send custom messages. I tried using on_message but kept having error about threading.
#bot.event
async def on_ready():
print(f'{bot.user.name} is now on Discord!')
#Here I want a loop that asks for input, then, if it gets it, the bot sends it.
I've tried using Thread's, but I can't await in a thread.
#I want to do somthing like:
channel = bot.get_channel(my_channel_id)
while True:
msg = input("Bot: ")
await channel.send(msg)
Thanks for all your answers!
EDIT:
I'm having trouble getting your solutions to work, and I'm pretty sure it's my fault. Is there any way for the bot to run normally, but while it does, there is a loop asking for input and sending it to discord as the bot when it gets it.
Like a working version of this?:
c = bot.get_channel(my_channel_id)
while True:
message = input("Bot: ")
await c.send(message)
AFAIK There is no async equivalent of input() in standard library. There are some workarounds for it, here is my suggestion that I think is the cleanest:
Fire up a thread when your program starts that you can run the blocking input() call in it. I used an executor because asyncio has a handy function to communicate with executor of any kind. Then from async code schedule a new job in executor and wait for it.
import asyncio
from concurrent.futures.thread import ThreadPoolExecutor
async def main():
loop = asyncio.get_event_loop()
while True:
line = await loop.run_in_executor(executor, input)
print('Got text:', line)
executor = ThreadPoolExecutor(max_workers=1)
asyncio.run(main())

Python await a function with loop inside

I am using the Sanic as the server and try to handle multiple request concurrently.
I have used the await for the encode function(I use for loop to simulate do something) but when I try the time curl http://0.0.0.0:8000/ in two separate consoles, it doesn't run concurrently.
I have searched google but only find event_loop but it is to schedule registered conroutines.
How do I await the for loop so the requests won't be blocked?
Thank you.
from sanic import Sanic
from sanic import response
from signal import signal, SIGINT
import asyncio
import uvloop
app = Sanic(__name__)
#app.route("/")
async def test(request):
# await asyncio.sleep(5)
await encode()
return response.json({"answer": "42"})
async def encode():
print('encode')
for i in range(0, 300000000):
pass
asyncio.set_event_loop(uvloop.new_event_loop())
server = app.create_server(host="0.0.0.0", port=8000)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(server)
signal(SIGINT, lambda s, f: loop.stop())
try:
loop.run_forever()
except:
loop.stop()
Running for i in range() is blocking. If you change that to put your await asyncio.sleep(5) into the encode method, you will see that it operates as expected.
#app.route("/")
async def test(request):
await encode()
return response.json({"answer": "42"})
async def encode():
print('encode')
await asyncio.sleep(5)
When you call await encode() and encode is a blocking method, then it still is going to block because you are not "awaiting" anything else. Your thread is still locked up.
You could also add another worker:
app.create_server(worker=2)
Try looking through this answer
Since the async handler is actually running in an eventloop, it is running asynchronously as callback rather than concurrently.
loop.run_forever() would call loop._run_once over and over again to run all the registered event, each await would stop the coroutine and yield control back to the eventloop and eventloop arrange to run the next event.
So basically if you don't want blocking in a long running for-loop, you need to manually hand over the control back to the eventloop inside the for-loop, see the issue about relinquishing control:
async def encode():
print('encode')
for i in range(0, 300000000):
await asyncio.sleep(0)
Here is a quote from Guido:
asyncio.sleep(0) means just that -- let any other tasks run and then
come back here.

How to await a select.select call in python asyncio

I have a python 3.6 program where I am using the asyncio package event loops. One of my data sources comes from an api which was not build around asyncio. My connection object contains a member called _connection which is just a python socket. Right now I can use this in a select statement to tell when data is ready.
async def run(self):
while True:
if select.select([self._q._connection], [], [])[0]:
msg = self._q.receive()
print(msg)
What I would really like is...
async def run(self):
while True:
if await select.select([self._q._connection], [], [])[0]:
msg = self._q.receive()
print(msg)
I know there is a sock_recv function in the asyncio event loop however I need the api to do the actual reading and decoding. I tried this but it would just fall through the await which I guess makes sense since I said 0 bytes.
async def run(self):
while True:
print('A')
await asyncio.get_event_loop().sock_recv(self._q._connection, 0)
print('B')
msg = self._q.receive()
print(msg)
The only solution I can think of for now is to add a small timeout to the select and then call asyncio.sleep while there is no data but this seems like an inefficent approach. I wish there was something like asyncio.select. Do anyone want to recommend another approach?
EDIT: Right now I have come up with this. I don't like it because it adds an extra quarter second latency (probably doesn't matter much for my application but it still bugs me.)
async def run(self):
while True:
if select.select([self._q._connection], [], [], 0)[0]:
print(self._q.receive())
else:
await asyncio.sleep(0.25)
You could use loop.add_reader to wait for the read availability of your socket:
async def watch(fd):
future = asyncio.Future()
loop.add_reader(fd, future.set_result, None)
future.add_done_callback(lambda f: loop.remove_reader(fd))
await future
async def run(self):
while True:
await watch(self._q._connection)
msg = self._q.receive()
print(msg)
However, it'll be very tricky to avoid all the blocking IO calls of the library you mentioned without rewriting it completely. Instead, I'd recommend to use the loop.run_in_executor method to schedule the blocking IO calls in a thread pool:
async def run(self):
loop = asyncio.get_event_loop()
while True:
msg = await loop.run_in_executor(None, self._q.receive)
print(msg)

Categories