How to run scheduled task only once in Python - python

I am trying to run scheduled tasks only once at the specific time but I couldn't figure out how to do that. All I understand is, every 10 seconds 1 task is stored and then when the time comes job runs them all.(I shared the result in below.) For example test_1 task scheduled to 11:02. I started code at 11:01. So for 60 seconds I got 6 results.
For second scheduled task 120 seconds passed since the beginning so I got 12 results.
Sorry for the misinformation. I updated my question. I also shared my excel date list I hope this gives more information.If today is work day (1) then run scheduled tasks else do nothing.This code will run nonstop so I have to check the current date. This is why I placed schedule inside the while loop.
import pandas as pd
from datetime import datetime
import time
import schedule
def test_1():
print("do task 1")
def test_2():
print("do task 2")
while(1 != 2):
today = datetime.today().strftime('%Y/%m/%d')
df_Calender = pd.read_excel('Calender.xlsx', sheet_name=0)
date = df_Calender['Date'] #Date column
filter_1 = date == today
df1_Calender = df_Calender.loc[filter_1]
week_day = df1_Calender['WeekDays']
if (week_day.item() != 1):
print("do nothing")
else:
schedule.every().day.at("11:02").do(test_1)
schedule.every().day.at("11:03").do(test_2)
time.sleep(10)
schedule.run_pending()
Here is the date list(Calender.xlsx)
This is the result
do task 1
do task 1
do task 1
do task 1
do task 1
do task 1
do task 2
do task 2
do task 2
do task 2
do task 2
do task 2
do task 2
do task 2
do task 2
do task 2
do task 2
do task 2

You would want to move your schedule out of the while loop, otherwise, it will keep scheduling the task after every 10-seconds due to the time.sleep(10) in the endless loop.
You can try something like:
def task1():
# Do something
schedule.every().day.at("11:02").do(task1)
# If you only want it to run once, then no loop is required
# Else if you want it to run once per day then add the while True loop
schedule.run_pending()
time.sleep(1)

Not sure if this is what you want. But it's what I understood you want
You have a list of times when you want a certain function to be called.
In this example you want task1 to be executed at 3 different times of the day and task 2 at times of the day
task1_schedule = ["11:02", "13:30", "17:00"]
task2_schedule = ["11:03", "14:20"]
Then you just iterate over the list and set a schedule
for start_time in task1_schedule:
schedule.every().day.at(start_time).do(test_1)
for start_time in task2_schedule:
schedule.every().day.at(start_time).do(test_2)
In this case since there are only 2 different taks I made 2 different list.
This code will create the schedules for each task once for each element in your schedule list.

Since your calls to schedule are inside while loop, they get placed on schedule multiple times. You need to place items on schedule once and only check if they are scheduled in loop. Try moving both schedule lines in front of while loop.
from datetime import datetime
import time
import schedule
def test_1():
print("do task 1")
def test_2():
print("do task 2")
schedule.every().day.at("11:02").do(test_1)
schedule.every().day.at("11:03").do(test_2)
while(1 != 2):
time.sleep(10)
schedule.run_pending()
See this example in documentation.
Based on comment you also need to check date before running task. I advise not to schedule conditionally, but to check for date either inside test_1 and test_2 functions or specify more day/week conditions (for example .every(3).days for each third day).

I have found the solution. Just adding schedule.clear() after schedule.run_pending() clears all stored tasks and leaves only one scheduled job. Thank you for your help.

Related

Python sleep if a function runs for 20 times

I have a function for sending an email which is used in a celery task i need to make the code sleep for a second if that email function is ran for 20 times how can i make this happpen.
#app.task(bind=True)
def send_email_reminder_due_date(self):
send_email_from_template(
subject_template_path='emails/0.txt',
body_template_path='emails/1.html',
template_data=template_data,
to_email_list=email_list,
fail_silently=False,
content_subtype='html'
)
so the above code is celery periodic task which runs daily i filter the todays date and send the email for all the records which are today's date if the number of records is more than 20 lets say so for evry 20 emails sent we need to make the code sleep for a second
send_email_from_template is function which is used for sending an email
I may be missing a vital point here around what you can/can't do because you are using celery, but I'll post in case it's useful. You can track function state by assigning attributes to functions. In the below code I assign an attribute times_without_sleep to a dummy function and track its value to sleep every 20 calls.
# ====================
def my_function():
if not hasattr(my_function, 'times_without_sleep'):
my_function.times_without_sleep = 0
print('Do the stuff')
if my_function.times_without_sleep >= 20:
print('Sleep')
my_function.times_without_sleep = 0
else:
my_function.times_without_sleep += 1
# ====================
if __name__ == "__main__":
for i in range(0, 100):
my_function()
You can also set the attribute value for the function if you need to set e.g. my_function.times_without_sleep = 0 at the end of round of emails etc.
Couldn't you just do something like this with send_email_from_template? You can also make it a decorator as in this answer to avoid cluttering the function code.

Change the Scheduling Time - Python

I am using schedule module to automatically run a function...
I am thinking of changing the scheduling time dynamically, but the solution is not success
Code -
import schedule
import pandas
from time import gmtime, strftime, sleep
import time
import random
time = 0.1
def a():
global time
print(strftime("%Y-%m-%d %H:%M:%S", gmtime()))
index = random.randint(1, 9)
print(index, time)
if(index==2):
time = 1
print(strftime("%Y-%m-%d %H:%M:%S", gmtime()))
schedule.every(time).minutes.do(a) #specify the minutes to automatically run the api
while True:
schedule.run_pending()
In this program, I scheduled the program to run every 6 seconds. And if the random integer - index value becomes 2, then the time variable is assigned as 1(1 minute). I checked, the time variable is changed to 1 after the random integer index becomes 2. The issue - After changing the time variable to 1, the scheduling still runs the function a() every 6 seconds not 1 minute.
How to change the scheduling time dynamically?
Thank you
After changing the time variable to 1, the scheduling still runs the function a() every 6 seconds not 1 minute.
This is because schedule.every(time).minutes.do(a) # specify the minutes to automatically run the api sets time to 6 seconds at beginning which does not change even if you change the value of that variable because that line has executed just once where value of time was 6 seconds at that execution.
How to change the scheduling time dynamically?
After reading DOCUMENTATION, I found nothing(I think) regarding changing time manually(when certain condition becomes satisfies) but it has built in Random Interval function where that function itself specifies random time within the range.
In your case you could do:
schedule.every(5).to(10).seconds.do(a)
The problem is that you cannot change time when certain condition satisfies.
Maybe there might be some way to fix that issue but could not figure out. And these information may help to investigate further to solve your problem.
I usually use custom schedulers, as they allow greater control and are also less memory intensive. The variable "time" needs to be shared between processes. This is where Manager().Namespace() comes to rescue. It talks 'between' processes.
import time
import random
from multiprocessing import Process, Manager
ns = Manager().Namespace()
ns.time = 0.1
processes = []
def a():
print(time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime()))
index = random.randint(1, 4)
if(index==2):
ns.time = 1
print(index, ns.time)
while True:
try:
s = time.time() + ns.time*60
for x in processes:
if not x.is_alive():
x.join()
processes.remove(x)
print('Sleeping :',round(s - time.time()))
time.sleep(round(s - time.time()))
p = Process(target = a)
p.start()
processes.append(p)
except:
print('Killing all to prevent orphaning ...')
[p.terminate() for p in processes]
[processes.remove(p) for p in processes]
break

How to run a python script that executes every 5 minutes BUT in the meantime executes some other script

I don't want the code to go the sleep in these 5 minutes and just waits. I want to run some other code block or script in the meantime.
How to run a python script that executes every 5 minutes BUT in the meantime executes some other script it code block until the 5 minute time is reached again.
e.g I want to run 3 functions . One to run every 5 minutes. another every 1 minutes. another every 10-20 seconds.
You can use a Thread to control your subprocess and eventually kill it after 5 minutes
import time
delay = 1 # time between your next script execution
wait = delay
t1 = time.time()
while True:
t2 = time.time() - t1
if t2 >= wait:
wait += delay
# execute your script once every 5 minutes (now it is set to 1 second)
# execute your other code here
First, you need to get the time of your script, then you need a variable to store "wait time" of your script (in this case "wait").
Every time your script time is higher or equal to "wait" delay variable is added to wait and code is executed.
And for multiple delays it's:
import time
delay = [1, 3]
wait = [delay[0], delay[1]]
t1 = time.time()
while True:
t2 = time.time() - t1
for i in range(len(wait)):
if t2 >= wait[i]:
wait[i] += delay[i]
if i==0:
print("This is executed every second")
if i==1:
print("This is executed every 3 second")

How to run Celery schedule only a number of times and quite until the task is called again?

I am using django + celery task scheduler to run a task scheduled for once every month. But I just want this task to run for a few number of months only, e.g 3 months or 6 months or 9 months..
How do I get to stop the worker from executing further task and then restarting whenever the task is called again?
here's my task
#task(name="add_profit")
def count():
portfolios = Portfolio.objects.filter(status='ACTIVE')
if portfolios.exists():
for portfolio in portfolios:
user = portfolio.user
#calculates portfolio profit
amount = portfolio.amount * 0.1
if portfolio.duration == '3 Months':
PortfolioProfit.objects.create(user=user, amount=amount)
user.useraccount.account_balance += amount
user.useraccount.save()
and here's my celery task schedule
app.conf.beat_schedule = {
# Executes 1st day of every Month.
'every-minute': {
'task': 'add_profit',
# crontab can be changes to change Schedule
# http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html
'schedule': crontab(0, 0, day_of_month = 1),
},
}
Making a new answer. I want to keep the previous one for the record as it might help other people having a different problem. For your question I think you just have to call the celery task every month unconditionally.
I think easiest would be if you change the data stored in the database such, that you can identify active portfolios, that you store the date of the first time and one date for the last time you automatically want to add a value.
Now the monthly celery task will identify users for which first_date <= today <= last_date and add the value if the condition is true.
Option 1:
you start the task every month with a celery crontab entry and within the task you add a test: if the current date is not within a certain range you just quit processing.
This has a slight overhead, but a sight overhead once per month should be acceptable.
#task(name="add_profit")
def count():
today = datetime.datetime.now()
if today > datetime.datetime(2020,1, 1):
return
# the remaining part of your task follows here
Option 2:
You execute once (with or without a for loop) a small code snippet, that schedules the tasks for the months in question.
http://docs.celeryproject.org/en/latest/userguide/calling.html#eta-and-countdown
In below I example I just show the idea with scheduling a task for the next three days:
today = datetime.utcnow()
for delta in range(1, 4):
task.apply_async(args=[arg1, arg2, ...), eta=today + timedelta(days=delta))
Here the task will be executed exactly as often as you want.
However if you migrate the server to a different location, if you reset rabbitmq (or whatever broker you have) the tasks to be scheduled will be lost / will disappear
ok. thanks alot #gelonida for your input. I have been able to achieve my goal.
I created a database input of the expiry date (3months, 6months etc, depending on the selection of the user), which is computed using the date the portfolio was created and sometime in the future as follows:
portfolio.expiry_date = timezone.now() + timedelta(days = 93) # for 3months
then in my task i did this
from django.utils import timezone
#task(name="add_profit")
def count():
portfolios = Portfolio.objects.filter(status='ACTIVE')
current_datetime = timezone.now()
if portfolios.exists():
for portfolio in portfolios:
if current_datetime > portfolio.expiry_date:
portfolio.status = 'COMPLETED'
portfolio.save()
return
else:
user = portfolio.user
amount = portfolio.amount * 0.1
PortfolioProfit.objects.create(user=user, amount=amount)
user.useraccount.account_balance += amount
user.useraccount.save()
This worked perfectly for me.

Python: How to compare two hours

I need to call a function, exactly 08:00, 18:00, 22:00 hours. I've created a example to test the comparison between hours. When the current time reaches one of those horary. Put in inside a While loop thinking this example would work as a stopwatch, but I think I'm wrong. How is the best way to compare those values?
currentH= dt.datetime.now().strftime("%H:%M:%S")
h = "16:15:10"
while True:
if(currentH==h):
print 'Ok'
print 'The current Hour is: '+h
import datetime as dt
import time
currentH= dt.datetime.now().replace(microsecond=0).time()
hrs = ['00:02', '12:00']
for i in range(len(hrs)):
h = [int(x) for x in hrs[i].split(':')]
h = dt.datetime.now().replace(hour=h[0], minute=h[1], second=0,microsecond=0).time()
hrs[i] = h
while True:
currentH = dt.datetime.now().replace(microsecond=0).time()
print(currentH)
if currentH in hrs:
print('Time is now',currentH)
time.sleep(1)
The biggest problem with your code is that you never call now() again inside the loop, so you're just spinning forever comparing the initial time to 16:15:10.
While we're at it: Why convert the time to a string for comparison instead of just comparing times?
But there are bigger problems with this design that can't be fixed as easily.
What happens if you check the time at 16:15, then go to sleep, then wake up at 16:25? Then now() never returns 16:15:10.
Also, do you really want to burn 100% CPU for 10 hours?
A better solution is to write a sleep_until function:
def sleep_until(target):
left = target - dt.datetime.now()
if left > dt.timedelta(seconds=0):
time.sleep(left.total_seconds())
(If you're using Python 2.7 or 3.4, it's a bit more complicated, because sleep will wake up early if there's a signal. But to handle that case, you just need to add a while True: loop around the whole thing.)
Now, the only tricky bit is working out the first time you need to sleep until, which isn't all that tricky:
waits = itertools.cycle(dt.timedelta(hours=wait) for wait in (10, 4, 10))
now = dt.datetime.now()
start = dt.datetime.combine(dt.date.today(), dt.time(hour=8))
for wait in waits:
start += wait
if start > now:
break
And now, we just loop over the waits forever, sleeping until each next time:
for wait in waits:
sleep_until(start)
print('Time to make the donuts')
start += wait
Or, of course, you could just grab one of the many scheduling libraries off PyPI.
Or just use your platform's cron/launchd/Scheduled Tasks API to run your script.

Categories