@tmatteson (Tyler Matteson) posted a new topic, subject "Use frappe scheduler class to schedule event at a specific time". It read:

   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?  
    github.com/frappe/frappe
    
    ####  Cron like events scheduler
    
    `frappe:develop` ← `GSLabIt:cron-like-events-scheduler`
    
    opened 03:21PM - 17 Oct 17 UTC
    
    GSLabIt 
    
    +86 -59
    
    And do you have an example implementation I could look at?

After which, @JoEz (Giovanni) added

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

To which @magic-overflow (Magic Overflow) replied

    Frappe schedule support only precision in minute.
    
    Doc and sample:  
    github.com
    
    #### frappe/frappe/blob/299ab6198a76eec3267f6e2b0eb1e7fd06ea46d0/frappe/docs/user/en/tutorial/task-runner.md
    [code] 
        # Scheduled Tasks
        
        Finally, an application also has to send email notifications and do other kind of scheduled tasks. In Frappé, if you have setup the bench, the task / scheduler is setup via RQ using Redis Queue.
        
        To add a new task handler, go to `hooks.py` and add a new handler. Default handlers are `all`, `daily`, `weekly`, `monthly`, `cron`. The `all` handler is called every 4 minutes by default.
        
        	# Scheduled Tasks
        	# ---------------
        
        	scheduler_events = {
        		"daily": [
        			"library_management.tasks.daily"
        		],
        		"cron": {
        			"0/10 * * * *": [
        				"library_management.task.run_every_ten_mins"
        			],
        			"15 18 * * *": [
        				"library_management.task.every_day_at_18_15"
        			]
        
    [/code]
    
    This file has been truncated. show original

After which, @tmatteson (Tyler Matteson) added

    So I have an API call that I need committed often, here is what I came up with:
    [code] 
        # Scheduled Tasks
        # ---------------
        scheduler_events = {
        	"cron": {
        		"1-59 * * * *": [
        			"my_custom_app.utils.sync_api"
        		]
        	}
        }
        
    [/code]
    
    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.

Then @JoEz (Giovanni) said

    you should do something like:
    [code] 
        # Scheduled Tasks
        # ---------------
        scheduler_events = {
        	"cron": {
        		"1-59 * * * *": [
        			"my_custom_app.utils.sync_now"
        		]
        	}
        }
        
    [/code]
    
    then define `sync_now` as:
    [code] 
        def sync_now():
            from frappe.utils.background_jobs import enqueue
            enqueue('my_custom_app.utils.sync_api', timeout=2000, queue="long")
    [/code]

After which, @tmatteson (Tyler Matteson) added

    Here's what I ended up with:
    [code] 
        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"]
        
    [/code]
    
    This structure came from the MNT team that I have refactored a bit to add some features. I like the “is running” check.

After which, @adam26d (Adam Dawoodjee) added

    tmatteson:
    
    > if not is_queue_running(“my_custom_app.utils.sync_now”):
    
    Does this mean that you neednt start the scheduler in hooks.py??
    
    I dont quite get which event is running the statement below
    
    tmatteson:
    
    > if not is_queue_running(“my_custom_app.utils.sync_now”):

@tmatteson (Tyler Matteson) replied

    adam26d:
    
    > Does this mean that you neednt start the scheduler in hooks.py??
    
    Scheduler needs to be running for any scheduled job to run.
    
    adam26d:
    
    > I dont quite get which event is running the statement below
    
    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.


{'source': 'discourse', 'url': 'https://discuss.erpnext.com/t/use-frappe-scheduler-class-to-schedule-event-at-a-specific-time/37064', 'title': 'Use frappe scheduler class to schedule event at a specific time', 'views': 4825, 'has_accepted_answer': True, 'word_count': 525, 'like_count': 12, 'reply_count': 3, 'participant_count': 4, 'posts': 8, 'tags': '', 'author': '@tmatteson (Tyler Matteson)', 'participants': '@tmatteson (Tyler Matteson), @JoEz (Giovanni), @magic-overflow (Magic Overflow), @adam26d (Adam Dawoodjee)', 'image_url': [None], 'created_at': '2018-05-27T14:09:11.644Z', 'last_posted_at': '2021-09-28T18:41:00.900Z'}