Cannot run a While Loop in AWS Python Lambda - python-3.x

import asyncio
from aiogram import Bot, Dispatcher, types
# Handlers
async def echo_hello(message: types.Message):
while True:
await message.reply("Hello!")
await asyncio.sleep(20)
# AWS Lambda funcs
async def register_handlers(dp: Dispatcher):
dp.register_message_handler(echo_hello, commands=['sayhello'])
async def process_event(event, dp: Dispatcher):
Bot.set_current(dp.bot)
update = types.Update.to_object(event)
await dp.process_update(update)
async def main(event):
bot = Bot(token=TOKEN)
dp = Dispatcher(bot)
await register_handlers(dp)
await process_event(event, dp)
return 'ok'
def lambda_handler(event, context):
return asyncio.get_event_loop().run_until_complete(main(event))
I can't seem to get my telegram bot in AWS Python Lambda to loop the Hello message. Provided I got no idea how else to get the lambda_handler function, I only saw this as an example from https://github.com/DavisDmitry/aiogram-aws-serverless-example
EDIT: NVM the default timeout for functions is 15 minutes at best so any loop will only work for 15 minutes at the most.

As #luk2302 mentions in the comment above, indeed Lambdas only have an upper limit of at most 15 minutes customizable by the user in the settings.
If set to 15 minutes, any loop will execute for that amount of time given the conditions make it so. At the time I asked this question I was unaware of the fact and by default the timeout value was set to 5 seconds, while my code was putting the while loop to sleep for 20 seconds. Hence I was unable to get any repeated messages. Once I set it to 2 minutes (for example), it repeated the message about 6 times after which it stopped.
Lambda functions do run almost forever but these are not suitable for functions which might have long execution process (including loops). For that, EC2 instances can be used.

Related

Python Asyncio - Trying to solve a simple mystery

This is probably the second time I've asked for help here so my apologies for little or extra detail / wording in my question *
The following code, though very basic is something I've written as an imitation (in its simplest form) of another piece of code written by an ex-employee at my firm. I am currently working on a project she was working on, and I do not understand how the following is executing without it being awaited or gathered.
In the original code, the 'wait_and_print' function is of course is an async function that that does a single RESTful web API call using aiohttp.ClientSession (using an async context manager) which returns nothing yet appends/extends a list with the response it gets.
So far, it has been 2 weeks since I've been using (and trying to understand) asyncio or asynchronous programming thus I am not very savvy with it. I've used Python, however for 3 years - on and off. I understand what task creation does and how asyncio.gather can run multiple API calls concurrently. BUT, this is something I do not get;
import asyncio
import time
L = []
async def wait_and_print(wait_time):
print(f"starting function {wait_time}")
for x in range(1, wait_time + 1):
print("Sleeping for {} time.".format(x))
await asyncio.sleep(1)
print(f"ending function {wait_time}")
L.append(wait_time)
async def main_loop():
tasks = [ asyncio.create_task(wait_and_print(x)) for x in [3,1,2]]
while len(tasks) != 0:
tasks = [t for t in tasks if not t.done()]
await asyncio.sleep(0) # HOW IS THIS MAKING IT WORK WITHOUT ACTUALLY AWAITING tasks?
print("Main loop ended!")
def final(func):
a = time.time()
asyncio.run(func())
b = time.time()
print(b-a, "seconds taken to run all!")
print(L)
final(main_loop)

How can I force asyncio task to run?

I'm noticing that when I spawn an asyncio task using create_task, it's first completing the rest of the logic rather than starting that task. I'm forced to add an await asyncio.sleep(0) to get the task started, which seems a bit hacky and unclean to me.
Here is some example code:
async def make_rpc_calls(...some args...)
val_1, val_2 = await asyncio.gather(rpc_call_1(...), rpc_call_2(...))
return process(val_1, val_2)
def some_very_cpu_intensive_function(...some args...):
// Does a lot of computation, can take 20 seconds to run
task_1 = asyncio.get_running_loop().create_task(make_rpc_calls(...))
intensive_result = some_very_cpu_intensive_function(...)
await task_1
process(intensive_result, task_1.result())
Anytime I run the above, it runs the some_very_cpu_intensive_function function before the kicking off the expensive RPCs. The only way I've gotten this to work is to do:
async def make_rpc_calls(...some args...)
val_1, val_2 = await asyncio.gather(rpc_call_1(...), rpc_call_2(...))
return process(val_1, val_2)
def some_very_cpu_intensive_function(...some args...):
// Does a lot of computation, can take 20 seconds to run
task_1 = asyncio.get_running_loop().create_task(make_rpc_calls(...))
await asyncio.sleep(0)
intensive_result = some_very_cpu_intensive_function(...)
await task_1
process(intensive_result, task_1.result())
This feels like a hack to me - I'm forcing the event loop to context switch, and doesn't feel like I'm using the asyncio framework correctly. Is there another way I should be approaching this?
sleep() always suspends the current task, allowing other tasks to run.
Setting the delay to 0 provides an optimized path to allow other tasks to run. This can be used by long-running functions to avoid blocking the event loop for the full duration of the function call.
Source: https://docs.python.org/3/library/asyncio-task.html

How to add a cooldown timer for commands

I am very new to coding and want to just add a command timer to the code i got off the internet. I don't know how to do this and all the other codes I find are too much for me to know. I just want to be able to add around a 10 second cooldown timer to each command.
import discord
import asyncio
from discord.ext import commands
import datetime as DT
import discord.utils
class MyClient (discord.Client):
async def on_ready(self):
print('Logged in as')
print(self.user.name)
print(self.user.id)
print('------')
async def on_message(self, message):
# we do not want the bot to reply to itself
if message.author.id == self.user.id:
return
if message.content.startswith('!discord'): # I want to add a timer
channel = client.get_user(message.author.id)
await channel.send(''.format(message))
async def on_member_join(self, member):
guild = member.guild
if guild.system_channel is not None:
to_send = 'Welcome {0.mention} to {1.name}!'.format(member, guild)
await guild.system_channel.send(to_send)
client = MyClient()
client.run('')
The time.sleep() function, which another user suggested is a blocking call. Which means it will block the entire thread from being ran. I am not sure exactly how the Discord framework works, but I imagine thread blocking might be a problem.
A better solution I think would be to use asyncio, especially since you've already imported it. It will allow the program to do other stuff, while waiting the 10 seconds. Another Stackoverflow thread if you're interested
if message.content.startswith('!discord'):
channel = client.get_user(message.author.id)
await asyncio.sleep(10)
await channel.send(''.format(message))
Edit
You can save the last time the function was called, and check with an IF-statement if 10 seconds have passed since the last call.
class MyClient (discord.Client):
last_called = None
async def on_ready(self):
# other stuff
async def on_message(self, message):
# check if 10 seconds have passed since the last call
if MyClient.last_called and DT.datetime.now() < MyClient.last_called + DT.timedelta(seconds=10):
return
# we do not want the bot to reply to itself
if message.author.id == self.user.id:
return
if message.content.startswith('!discord'):
channel = client.get_user(message.author.id)
await channel.send(''.format(message))
MyClient.last_called = DT.datetime.now() # save the last call time
If you add this to the imports at the top of the file:
from time import sleep
then you can use the sleep() function to pause the script for a set number of seconds when execution reaches that line. So for instance, adding this:
sleep(10)
somewhere inside the functions would sleep for 10 seconds whenever the script reaches that line.

How to set timeout for a block of code which is not a function python3

After spending a lot of hours looking for a solution in stackoverflow, I did not find a good solution to set a timeout for a block of code. There are approximations to set a timeout for a function. Nevertheless, I would like to know how to set a timeout without having a function. Let's take the following code as an example:
print("Doing different things")
for i in range(0,10)
# Doing some heavy stuff
print("Done. Continue with the following code")
So, How would you break the for loop if it has not finished after x seconds? Just continue with the code (maybe saving some bool variables to know that timeout was reached), despite the fact that the for loop did not finish properly.
i think implement this efficiently without using functions not possible , look this code ..
import datetime as dt
print("Doing different things")
# store
time_out_after = dt.timedelta(seconds=60)
start_time = dt.datetime.now()
for i in range(10):
if dt.datetime.now() > time_started + time_out:
break
else:
# Doing some heavy stuff
print("Done. Continue with the following code")
the problem : the timeout will checked in the beginning of every loop cycle, so it may be take more than the specified timeout period to break of the loop, or in worst case it maybe not interrupt the loop ever becouse it can't interrupt the code that never finish un iteration.
update :
as op replayed, that he want more efficient way, this is a proper way to do it, but using functions.
import asyncio
async def test_func():
print('doing thing here , it will take long time')
await asyncio.sleep(3600) # this will emulate heaven task with actual Sleep for one hour
return 'yay!' # this will not executed as the timeout will occur early
async def main():
# Wait for at most 1 second
try:
result = await asyncio.wait_for(test_func(), timeout=1.0) # call your function with specific timeout
# do something with the result
except asyncio.TimeoutError:
# when time out happen program will break from the test function and execute code here
print('timeout!')
print('lets continue to do other things')
asyncio.run(main())
Expected output:
doing thing here , it will take long time
timeout!
lets continue to do other things
note:
now timeout will happen after exactly the time you specify. in this example code, after one second.
you would replace this line:
await asyncio.sleep(3600)
with your actual task code.
try it and let me know what do you think. thank you.
read asyncio docs:
link
update 24/2/2019
as op noted that asyncio.run introduced in python 3.7 and asked for altrnative on python 3.6
asyncio.run alternative for python older than 3.7:
replace
asyncio.run(main())
with this code for older version (i think 3.4 to 3.6)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
You may try the following way:
import time
start = time.time()
for val in range(10):
# some heavy stuff
time.sleep(.5)
if time.time() - start > 3: # 3 is timeout in seconds
print('loop stopped at', val)
break # stop the loop, or sys.exit() to stop the script
else:
print('successfully completed')
I guess it is kinda viable approach. Actual timeout is greater than 3 seconds and depends on the single step execution time.

Why can I not send a message via Discord.py from a function?

I have created a script which takes in a message formatted as !notice [MM/DD/YY HH:mm], message, target which then calls a function using threading.Timer to call it at the time given in the message in UTC.
Where I am having trouble is sending a message from this function, I can't seem to get a message to send from the function regardless of the input of the message.
See below:
import discord
import asyncio
from datetime import *
import threading
client = discord.Client()
#client.event
async def on_message(message):
if message.content[:7].lower() == "!notice".lower():
try:
notice = [datetime.strptime(message.content[message.content.find("[")+1:message.content.find("]")], "%m/%d/%y %H:%M"), message.content.split(", ")[1], message.content.split(", ")[2]]
await client.send_message(message.channel, 'Created notice "'+notice[1]+'" to be sent to '+notice[2]+' at '+str(notice[0])+' UTC.')
threading.Timer((notice[0] - datetime.utcnow()).total_seconds(), lambda a=notice[1], b=notice[2]: func(a, b)).start()
print(str((notice[0] - datetime.utcnow()).total_seconds())+" seconds until message is sent")
except (ValueError, IndexError):
await client.send_message(message.channel, 'Incorrect Notice Format.\nMust be "!notice [MM/DD/YY HH:mm], Notice contents, Target".\nEG: "!notice [01/01/2017 12:00], This is a notice, Siren Raid Team".')
def func(message, target):
print("Func called")
for i in client.servers:
for c in i.channels:
client.send_message(c, target+message)
client.run(MY_SESSION_KEY)
This returns "Func called" so I know the function is being called, but no exceptions are raised and no message is posted in my chat.
I also tried substituting func with:
async def func(message, target):
print("Func called")
for i in client.servers:
for c in i.channels:
await client.send_message(c, target+message)
However this throws up an exception:
RuntimeWarning: coroutine 'func' was never awaited
Frankly, I'm out of my depth here. Is there any reason why this won't work?
I saw online that asyncio is not thread-safe. But, unless I'm misunderstanding, my first example didn't use that library in the function. Could is still be causing problems?
discord.py's discord.Client.send_message is a coroutine and must be awaited, like you did in your second code snippet. However, threading.Timer does not support coroutines.
What you're looking for is create_task, which enables you to run a coroutine on the event loop. Since most of what your coroutine does is sleeping (mimicking threading.Timer), your on_message will proceed to run, given that you use asyncio.sleep and not time.sleep - the latter blocks the event loop. Here's an example, including passing arguments to functions:
import asyncio
loop = asyncio.get_event_loop()
async def sleep_and_add(a, b):
await asyncio.sleep(3)
print(a, '+', b, 'is', a + b)
async def on_message():
# prepare arguments to your function
loop.create_task(sleep_and_add(2, 3))
# continue doing other things
In case anyone needs help with just sending messages to a server from inside of a flask function. I spent hours with this problem myself after going down the rabbit hole of asyncio and threading. Turned out to be way easier than I thought. Webhooks, unlike a regular Discord bot, are entirely synchronous, which means they can run in a flask function no problem.
If your goal is to just simply send a message to a channel from a flask endpoint and not use any other functionality, try webhooks.
from discord import Webhook, RequestsWebhookAdapter
webhook = Webhook.partial(WEB_HOOK_ID, 'WEB_HOOK_TOKEN', adapter=RequestsWebhookAdapter())
and then post the message
webhook.send('MESSAGE', username='WEBHOOK_BOT')
Creating Webhook tutorial
Discord.py Webhook Info

Resources