dbader / schedule

Python job scheduling for humans.
https://schedule.readthedocs.io/
MIT License
11.71k stars 960 forks source link

How to run job every weekday from 5am to 6pm #540

Closed felipecaldas closed 2 years ago

felipecaldas commented 2 years ago

Hi all,

I am trying to find a way to run my job only on the weekdays from 5am to 6pm, every 10 minutes.

I have this so far:

schedule.every().monday.at("05:00").until("19:00").do(run)
schedule.every().tuesday.at("05:00").until("19:00").do(run)
schedule.every().wednesday.at("05:00").until("19:00").do(run)
schedule.every().thursday.at("05:00").until("19:00").do(run)
schedule.every().friday.at("05:00").until("19:00").do(run)

but I am missing the every 10 minutes part.

Can this be accomplished with this library? Thank you

braintho commented 2 years ago

Not sure if it's the most elegant but maybe you could do something like

schedule.every(10).minutes.do(run)

and then it will try to run every ten minutes. You could have additional logic attached to your function to determine if should actually run or not.

For instance if you wanted it to only be on a weekday you could use a decorator:

from datetime import datetime

def days(*args):
    def wrap(func):
        if datetime.today().isoweekday() in args:
            func()

    return wrap

@days(1, 2, 3, 4, 5)
def run():
    #Do stuff
    return "run() ran"

You could add another decorator to make sure it's in between the proper hours of the day.

felipecaldas commented 2 years ago

Thanks @braintho, this definitely seems like a good idea. I am attempting to run this code but I am getting a TypeError: the first argument must be callable

Today is Sunday, so what should the wrap function do when the if statement is false? I am thinking that's the problem. Thanks

felipecaldas commented 2 years ago

Okay, here's what I've done. Not entirely tested yet, but this seems to work:

def week_days(func):
    def wrap():
        if date.today().weekday() == 0:
            logger.debug("it's a weekday")
            return func()
        else:
            logger.debug("it's a weekend")
            return

    return wrap

def run_hours(func):
    dt_string = datetime.now().strftime("%H:%M")

    def minutes_per_day(tme):
        hours, minutes = tme.split(':')
        return (hours * 60) + minutes

    def check_time(tme, range):
        return minutes_per_day(range[0]) < minutes_per_day(tme) < range[1]

    def wrap():
        if check_time(dt_string, ("05:00", "19:00")):
            logger.debug('time okay')
            return func()
        else:
            logger.debug('time not okay')
            return

    return wrap

@week_days
@run_hours
def run2():
    now = datetime.now()
    logger.debug(f"SFTP Scheduler started {now}")
    run_sftp()
    now = datetime.now()
    logger.debug(f"SFTP Scheduler finished {now}")

if __name__ == '__main__':
    schedule.every(1).second.do(run2)
    while True:
        # Checks whether a scheduled task
        # is pending to run or not
        schedule.run_pending()
        time.sleep(1)
braintho commented 2 years ago

Sorry about that. The issue with my code was that the decorator had parameters, and I didn't write the wrapper properly to handle that. It's nice to have the decorator with parameters so that the code is reusable on other jobs, This way if you have a Monday, Wednesday, Friday job, you can use the same decorator and just change the parameters. Here is what the code should look like:

from datetime import datetime

def days(*args):
    def wrap(func):
        def wrapped_func(*args):

            if datetime.today().isoweekday() in args:
                func()

        return wrapped_func

    return wrap

@days(1, 2, 3, 4, 5)
def run():
    # Do stuff
    return "run() ran"

run()