still.Learning
still.Learning

Reputation: 1245

passing parameters to apscheduler handler function

I am using apscheduler and I am trying to pass in parameters to the handler function that gets called when the scheduled job is launched:

from apscheduler.scheduler import Scheduler
import time

def printit(sometext):
    print "this happens every 5 seconds"
    print sometext

sched = Scheduler()
sched.start()

sometext = "this is a passed message"
sched.add_cron_job(printit(sometext), second="*/5")

while True:
    time.sleep(1)

Doing this gives me the following error:

TypeError: func must be callable

Is it possible to pass parameters into the function handler. If not, are there any alternatives? Basically, I need each scheduled job to return a string that I pass in when I create the schedule. Thanks!

Upvotes: 37

Views: 42072

Answers (4)

James_SO
James_SO

Reputation: 1387

I really like this library, so despite the age of the original question, I wanted to provide a fully worked through example for calling a function with args on a schedule, which is surely the main reason to use such a thing.

Let's say we have a thread-safe Queue in which we put stuff that we want to look for and express on a schedule (meaning, the Queue and the Schedule are loosely coupled). We'll assume this Queue is managed by a blocking thread. (this Queue could also be a database table or etcd prefix)

We start with a function we want to execute on a schedule:

def doSomething(cueType):
    """Get a cue and express it
    
    Args:
        cueType (str): a Cue Type (e.g. Verbalization)
    """
    # Get the most recently queued dict
    cueDict = q.queue[-1]
    expressCue(cueType, cueDict)

So we're assuming a habit-formation use case, where we want to cue the user to do something on a schedule, so they form a habit. In this case, it's brushing teeth at 8am, 2pm and 8pm. (this is not dental advice) And we're assuming an expressCue() function which is going to express the cue using the type provided.

thisSched = {"Name":"Brush Teeth", 
           "Cron":"0 8,14,20 * * *"}

With one exception, I'm using the standard cron syntax, so we'll want a simplistic function to convert that into APScheduler arguments:

def fromCron(cronExpression):
    """Convert cron expression into APScheduler arguments

    Args:
        cronExpression (str): Valid cron expression (plus optional WOY)

    Returns:
        dict: Arg dict for APScheduler - pass in with **argDict
    """
    if len(cronExpression.split()) == 5:
        try:
            MIN,H,DOM,M,DOW = cronExpression.split()
            argDict = {"month":M,
                    "day":DOM,
                    "day_of_week":DOW,
                    "hour":H,
                    "minute":MIN}
        except Exception as e:
            argDict = {"Error":str(e)}
    elif len(cronExpression.split()) == 6:
        try:
            MIN,H,DOM,M,DOW,WOY = cronExpression.split()
            argDict = {"month":M,
                    "day":DOM,
                    "day_of_week":DOW,
                    "hour":H,
                    "minute":MIN,
                    "week":WOY}
        except Exception as e:
            argDict = {"Error":str(e)}
    else:
        argDict = {"Error":"What the..."}
    return argDict

As you can see from the code above, the one thing I added was support for "week of year", as this is a very cron-like idea that cron doesn't support.

If you want to provide different cues on alternating weeks you build out WOY with:

alternatingWeeks1 = ",".join([str(i) for i in range(1,53)][::2])
alternatingWeeks2 = ",".join([str(i) for i in range(2,53)][::2])

Next we setup the scheduler with pretty standard args

executors = {
    'default': ThreadPoolExecutor(20),
    'processpool': ProcessPoolExecutor(5)
}
job_defaults = {
    'coalesce': False,
    'max_instances': 3
}

Now we can instantiate the schedule:

ourScheduler = BackgroundScheduler(executors=executors, 
                                   job_defaults=job_defaults, 
                                   timezone='America/Toronto')

We choose the BackgroundScheduler (the default) here because we have a blocking thread we'll setup subsequently (not shown here), and we're not running asyncio or gevent, etc.

Next we add the job

cueType = "Verbalization"
schedCronDict = fromCron(thisSched.get("Cron"))
addedJob = ourScheduler.add_job(func=doSomething,
                               name=thisSched.get("Name"),
                               trigger="cron", args=[cueType], 
                               **schedCronDict)
thisSched["Job"] = addedJob

and start the scheduler (you can also add jobs after the scheduler has started)

ourScheduler.start()

we can check the jobs with:

ourScheduler.print_jobs()

and

thisSched["Job"].next_run_time

Putting the Job in the sched dict is a convenience if you want to know next_run_time, but also if you want to modify the job.

Keep in mind that the args we provide to the scheduler to pass into the function are static (meaning, they're the same each time the schedule executes) - that's why I use the Queue in the function to get a dynamic value (which could be different each time the schedule executes). Obviously I could have used a database query or an etcd get to achieve something similar.

There are many use cases where this library is useful, but in particular where you have a blocking thread (it could be an etcd watch, or waiting on a keypress, or waiting on a tk window mainloop) and you want repeating/scheduled events to occur too, it really simplifies things, and provides an easily repeatable pattern.

Thank you, Alex Grönholm!

Upvotes: 1

aldwinp35
aldwinp35

Reputation: 199

Like Niel mention, with the current apscheduler you can use:
args (list|tuple) – list of positional arguments to call func with. See doc

Example:

sched.add_job(printit, args=[sometext], trigger='cron', minutes=2)

Upvotes: 7

Niel
Niel

Reputation: 2006

Since this is the first result I found when having the same problem, I'm adding an updated answer:

According to the docs for the current apscheduler (v3.3.0) you can pass along the function arguments in the add_job() function.

So in the case of OP it would be:

sched.add_job(printit, "cron", [sometext], second="*/5")

Upvotes: 44

Ali Afshar
Ali Afshar

Reputation: 41657

printit(sometext) is not a callable, it is the result of the call.

You can use:

lambda: printit(sometext)

Which is a callable to be called later which will probably do what you want.

Upvotes: 44

Related Questions