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

add support for crontab expressions #581

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Usage
schedule.every().wednesday.at("13:15").do(job)
schedule.every().day.at("12:42", "Europe/Amsterdam").do(job)
schedule.every().minute.at(":17").do(job)
schedule.every().crontab_expression("5 4 * * 2,5", "Europe/Amsterdam").do(job)

def job_with_argument(name):
print(f"I am {name}")
Expand Down
4 changes: 4 additions & 0 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ Run a job every x minute
schedule.every().monday.do(job)
schedule.every().wednesday.at("13:15").do(job)

# Run job based on a crontab expression
schedule.every().crontab_expression("5 4 * * 2,5").do(job)
schedule.every().crontab_expression("5 4 * * 2,5", "Europe/Amsterdam").do(job)

while True:
schedule.run_pending()
time.sleep(1)
Expand Down
3 changes: 2 additions & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ black==20.8b1
click==8.0.4
mypy
pytz
types-pytz
types-pytz
freezegun
32 changes: 31 additions & 1 deletion schedule/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
import time
from typing import Set, List, Optional, Callable, Union

from .crontab import Crontab


logger = logging.getLogger("schedule")


Expand Down Expand Up @@ -228,7 +231,7 @@ def __init__(self, interval: int, scheduler: Optional[Scheduler] = None):
self.latest: Optional[int] = None # upper limit to the interval
self.job_func: Optional[functools.partial] = None # the job job_func to run

# time units, e.g. 'minutes', 'hours', ...
# time units, e.g. 'minutes', 'hours', ... Only relevant when not using crontab
self.unit: Optional[str] = None

# optional time at which this job runs
Expand All @@ -237,6 +240,9 @@ def __init__(self, interval: int, scheduler: Optional[Scheduler] = None):
# optional time zone of the self.at_time field. Only relevant when at_time is not None
self.at_time_zone = None

# crontab
self.crontab: Optional[Crontab] = None

# datetime of the last run
self.last_run: Optional[datetime.datetime] = None

Expand Down Expand Up @@ -469,6 +475,18 @@ def tag(self, *tags: Hashable):
self.tags.update(tags)
return self

def crontab_expression(self, expression: str, tz: Optional[str] = None) -> "Job":
if self.unit is not None:
raise ScheduleValueError(
"crontab expression cannot be used if a unit is already defined")
if self.at_time is not None:
raise ScheduleValueError(
"crontab expression cannot be used if an `at` time is already defined")

c = Crontab.from_expression(expression, tz=tz)
self.crontab = c
return self

def at(self, time_str: str, tz: Optional[str] = None):

"""
Expand Down Expand Up @@ -497,6 +515,11 @@ def at(self, time_str: str, tz: Optional[str] = None):
"Invalid unit (valid units are `days`, `hours`, and `minutes`)"
)

if self.crontab is not None:
raise ScheduleValueError(
"`at` function cannot be used if a crontab expression is already defined"
)

if tz is not None:
import pytz

Expand Down Expand Up @@ -703,6 +726,13 @@ def _schedule_next_run(self) -> None:
"""
Compute the instant when this job should run next.
"""
if self.crontab is not None:
self.next_run = self.crontab.next_run_time()
# the whole library lacks proper timezone support
# -> convert to unaware local time.
self.next_run = self.next_run.astimezone().replace(tzinfo=None)
return

if self.unit not in ("seconds", "minutes", "hours", "days", "weeks"):
raise ScheduleValueError(
"Invalid unit (valid units are `seconds`, `minutes`, `hours`, "
Expand Down