Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

run only once - : schedule.at('22:30').do(any_job) #408

Open
helarsen opened this issue Jan 3, 2021 · 9 comments
Open

run only once - : schedule.at('22:30').do(any_job) #408

helarsen opened this issue Jan 3, 2021 · 9 comments

Comments

@helarsen
Copy link

helarsen commented Jan 3, 2021

To run a job only once the manual says:

def job_that_executes_once():
    # Do some work ...
    return schedule.CancelJob

schedule.every().day.at('22:30').do(job_that_executes_once)

The problem with this implementation is that in case the job_that_executes_once is missing the return schedule.CancelJob, you will get the job run every day at "22:30" where you did not expect this.
This can be one of the very hard-to-find bugs which pops "out of the blue"

The term ".every().day." is misleading in this context but there is no way around it as of now.

The naive may try:
schedule.at('22:30').do(any_job)
AttributeError: module 'schedule' has no attribute 'at' so it does not work.

I would advocate for a method to schedule any job to run only once. But I don't know how this best fits the existing interface.

Maybe something like schedule.once().at('22:30').do(any_job) ?

@SijmenHuizenga
Copy link
Collaborator

Hi @helarsen,

This has been proposed before in #39 and addressed in this comment specifically. It was also implemented in #184 and declined for the same reason.

Your strong reasoning about hard-to-find bugs made me reconsider declining again. The fact that this has been proposed over and over throughout the years, and the relatively small impact of this change, it might be worth it to add...

And also I like the looks of this 😍

schedule.once().at('22:30').do(any_job)

Keeping this open and marking it as enhancement, no promises though ;)

@helarsen
Copy link
Author

helarsen commented Jan 5, 2021

I would extend my suggestion:

  • schedule.once().at('HH:MM:SS').do(any_job) -> first coming time instance HH:MM:SS, which may be up to 24h away from now.
  • schedule.once().at(':MM:SS').do(any_job) -> first coming time instance :MM:SS, which may be up to 1h away from now.
  • schedule.once().at(':SS').do(any_job) -> first coming time instance :SS, which may be up to 1min away from now.

This provides appealing symmetry.

There cannot be any schedule.once().at(':MM').do(any_job) or schedule.once().at('MM').do(any_job)and good so, i would say.

By the way - thumbs up to how professional the management of this library is!

@SijmenHuizenga
Copy link
Collaborator

Thanks for the compliment 😊

In regards to when the first coming invocation should be; Could we just keep the scheduling system as is and cancel the job after the first invocation? Is there a need to re-define when the the 'next' job is?

@helarsen
Copy link
Author

helarsen commented Jan 5, 2021

Sorry, I miss your point here:

in regards to when the first coming invocation should be; Could we just keep the scheduling system as is and cancel the job after the first invocation? Is there a need to re-define when the the 'next' job is?

What I suggest is that:
schedule.once().at(':MM:SS').do(any_job) will run at time MM:SS, but because the HH part is not specified the scheduler must define a default HH value.
I suggest that the obvious choice is: "the next coming time matching MM:SS" i.e. "xx:MM:SS". As I write in worst case this may be up to an hour from "now"
At first this may sound a bit silly but the "HH:MM:SS" does exactly the same just on day basis. Missing the deadline by 1 second and it will have to wait until tomorrow - but this is logically fitting the rest of the interface philosophy.
Does this clarify what I mean?

@helarsen
Copy link
Author

helarsen commented Jan 6, 2021

Could we just keep the scheduling system as is and cancel the job after the first invocation?

Yes

Is there a need to re-define when the the 'next' job is?

No

would be the short answers - hopefully just supplementing what I previously wrote

@SijmenHuizenga
Copy link
Collaborator

Ah, I now understand your point;

schedule.once().at(':MM:SS').do(any_job) will run at time MM:SS, but because the HH part is not specified the scheduler must define a default HH value.

This is how the scheduler currently works, so we can just re-use the current next-run calculations. Which makes this so much easier 😄

@helarsen
Copy link
Author

helarsen commented Jan 8, 2021

so we can just re-use the current next-run calculations

Yes the main difference to every() is that the scheduler should somehow ensure it only runs one time.
Note that the semantics calls for that it will always run exactly one time irrespective of what time was requested, because there is no defined notion of being late and missing a deadline when the timing is using modulus (MM, HH or 24h) arithmetic - just to re-state what I wrote 3 posts ago.

Further to the support of this enhancement:
Having to add return schedule.CancelJob to Job() means:

  • The Job() will have to import schedule library.
  • The Job() will have to know who called it in order to determine if it should cancel or not.
  • This affects portability and reuse of the code

@nadavgolden
Copy link

Maybe when scheduale.once() is called, a tag is added to the job (e.g. run_once). When schedual._run_job() is called, we check for the tag and handle accordingly.

@ph3ne
Copy link

ph3ne commented Jan 17, 2022

A workaround I'm using for this is adding a boolean (named once) as argument of the job function + adding "-once" after the job id in the tag:

Then at the start of the job function i have:

if once is True:
    schedule.clear(str(job.id) + '-once')

Which prevents the job from executing another time after this first run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants