Reputation: 1245
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
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
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
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
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