Use frappe scheduler class to schedule event at a specific time

I was browsing the Frappe scheduler docs, looking for a way to schedule a event at a specific time (with precision in seconds). I am interfacing with an external service that has a Queries-per-Minute threshold. I am always trying to use the Frappe internals over external libraries, but I don’t see an obvious way to do this. I would be happy to contribute back a solution that adds an execute_at(datetime) method but would like some guidance on approach.

Wait! @JoEz, is this what I’m looking for? I looks like this only supports precision in minutes?
https://github.com/frappe/frappe/pull/4339

And do you have an example implementation I could look at?

1 Like

You can use cron string to define job schedule, have a look at frappe docs on how to set it.

Frappe schedule support only precision in minute.

Doc and sample:

1 Like

So I have an API call that I need committed often, here is what I came up with:

# Scheduled Tasks
# ---------------
scheduler_events = {
	"cron": {
		"1-59 * * * *": [
			"my_custom_app.utils.sync_api"
		]
	}
}

This runs every minute except at the top of the hour per the best practices for this particular API. 0-59 would run every minute. This method should absolutely be wrapped in the frappe.enqueue method, and most likely in the long worker queue. @JoEz gets the gold star for helping me out and for making this contribution in the first place.

you should do something like:

# Scheduled Tasks
# ---------------
scheduler_events = {
	"cron": {
		"1-59 * * * *": [
			"my_custom_app.utils.sync_now"
		]
	}
}

then define sync_now as:

def sync_now():
    from frappe.utils.background_jobs import enqueue
    enqueue('my_custom_app.utils.sync_api', timeout=2000, queue="long")
6 Likes

Here’s what I ended up with:

from frappe.utils.background_jobs import enqueue
from frappe.core.page.background_jobs.background_jobs import get_info

if not is_queue_running("my_custom_app.utils.sync_now"):
    	frappe.enqueue("my_custom_app.utils.sync_now",
    		queue="long",
    		timeout=1000,
    		settings=settings)


def get_job_queue(job_name):
	queue_info = get_info()
	queue_by_job_name = [queue for queue in queue_info if queue.get("job_name") == job_name]
	return queue_by_job_name


def is_queue_running(job_name):
	queue = get_job_queue(job_name)
	return queue and len(queue) > 0 and queue[0].get("status") in ["started", "queued"]

This structure came from the MNT team that I have refactored a bit to add some features. I like the “is running” check.

5 Likes

Does this mean that you neednt start the scheduler in hooks.py??

I dont quite get which event is running the statement below

Scheduler needs to be running for any scheduled job to run.

If you want to wait for a function in the long queue to complete, you should check if its still running or not. Otherwise you may be adding unintended multiples onto the queue.

1 Like