diff --git a/schedule/__init__.py b/schedule/__init__.py index ee7312d0..554af9a6 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -48,11 +48,22 @@ logger = logging.getLogger('schedule') +class ScheduleError(Exception): + """Base schedule exception""" + + +class ScheduleValueError(ScheduleError): + """Base schedule value error""" + + +class IntervalError(ScheduleValueError): + """An improper interval was used""" + + class CancelJob(object): """ Can be returned from a job to unschedule itself. """ - pass class Scheduler(object): @@ -228,7 +239,8 @@ def format_time(t): @property def second(self): - assert self.interval == 1, 'Use seconds instead of second' + if self.interval != 1: + raise IntervalError('Use seconds instead of second') return self.seconds @property @@ -238,7 +250,8 @@ def seconds(self): @property def minute(self): - assert self.interval == 1, 'Use minutes instead of minute' + if self.interval != 1: + raise IntervalError('Use minutes instead of minute') return self.minutes @property @@ -248,7 +261,8 @@ def minutes(self): @property def hour(self): - assert self.interval == 1, 'Use hours instead of hour' + if self.interval != 1: + raise IntervalError('Use hours instead of hour') return self.hours @property @@ -258,7 +272,8 @@ def hours(self): @property def day(self): - assert self.interval == 1, 'Use days instead of day' + if self.interval != 1: + raise IntervalError('Use days instead of day') return self.days @property @@ -268,7 +283,8 @@ def days(self): @property def week(self): - assert self.interval == 1, 'Use weeks instead of week' + if self.interval != 1: + raise IntervalError('Use weeks instead of week') return self.weeks @property @@ -278,43 +294,50 @@ def weeks(self): @property def monday(self): - assert self.interval == 1, 'Use mondays instead of monday' + if self.interval != 1: + raise IntervalError('Use mondays instead of monday') self.start_day = 'monday' return self.weeks @property def tuesday(self): - assert self.interval == 1, 'Use tuesdays instead of tuesday' + if self.interval != 1: + raise IntervalError('Use tuesdays instead of tuesday') self.start_day = 'tuesday' return self.weeks @property def wednesday(self): - assert self.interval == 1, 'Use wednesdays instead of wednesday' + if self.interval != 1: + raise IntervalError('Use wednesdays instead of wednesday') self.start_day = 'wednesday' return self.weeks @property def thursday(self): - assert self.interval == 1, 'Use thursdays instead of thursday' + if self.interval != 1: + raise IntervalError('Use thursdays instead of thursday') self.start_day = 'thursday' return self.weeks @property def friday(self): - assert self.interval == 1, 'Use fridays instead of friday' + if self.interval != 1: + raise IntervalError('Use fridays instead of friday') self.start_day = 'friday' return self.weeks @property def saturday(self): - assert self.interval == 1, 'Use saturdays instead of saturday' + if self.interval != 1: + raise IntervalError('Use saturdays instead of saturday') self.start_day = 'saturday' return self.weeks @property def sunday(self): - assert self.interval == 1, 'Use sundays instead of sunday' + if self.interval != 1: + raise IntervalError('Use sundays instead of sunday') self.start_day = 'sunday' return self.weeks @@ -344,18 +367,22 @@ def at(self, time_str): (e.g. `every().hour.at(':30')` vs. `every().minute.at(':30')`). :return: The invoked job instance """ - assert self.unit in ('days', 'hours', 'minutes') or self.start_day + if (self.unit not in ('days', 'hours', 'minutes') + and not self.start_day): + raise ScheduleValueError('Invalid unit.') if not isinstance(time_str, str): raise TypeError("at() should be passed a string.") if self.unit == 'days' or self.start_day: - assert re.match(r'^([0-2]\d:)?[0-5]\d:[0-5]\d$', time_str), \ - ValueError("Invalid time format.") + if not re.match(r'^([0-2]\d:)?[0-5]\d:[0-5]\d$', time_str): + raise ScheduleValueError("Invalid time format.") if self.unit == 'hours': - assert re.match(r'^([0-5]\d)?:[0-5]\d$', time_str), \ - ValueError("Invalid time format for an hourly job.") + if not re.match(r'^([0-5]\d)?:[0-5]\d$', time_str): + raise ScheduleValueError(("Invalid time format for" + " an hourly job.")) if self.unit == 'minutes': - assert re.match(r'^:[0-5]\d$', time_str), \ - ValueError("Invalid time format for a minutely job.") + if not re.match(r'^:[0-5]\d$', time_str): + raise ScheduleValueError(("Invalid time format for" + " a minutely job.")) time_values = time_str.split(':') if len(time_values) == 3: hour, minute, second = time_values @@ -368,16 +395,15 @@ def at(self, time_str): second = 0 if self.unit == 'days' or self.start_day: hour = int(hour) - assert 0 <= hour <= 23 + if 0 > hour or hour > 24: + raise ScheduleValueError("Invalid number of hours.") elif self.unit == 'hours': hour = 0 elif self.unit == 'minutes': hour = 0 minute = 0 minute = int(minute) - assert 0 <= minute <= 59 second = int(second) - assert 0 <= second <= 59 self.at_time = datetime.time(hour, minute, second) return self @@ -442,11 +468,12 @@ def _schedule_next_run(self): """ Compute the instant when this job should run next. """ - assert self.unit in ('seconds', 'minutes', 'hours', 'days', - 'weeks') + if self.unit not in ('seconds', 'minutes', 'hours', 'days', 'weeks'): + raise ScheduleValueError("Invalid unit.") if self.latest is not None: - assert self.latest >= self.interval + if not (self.latest >= self.interval): + raise ScheduleError interval = random.randint(self.interval, self.latest) else: interval = self.interval @@ -454,7 +481,8 @@ def _schedule_next_run(self): self.period = datetime.timedelta(**{self.unit: interval}) self.next_run = datetime.datetime.now() + self.period if self.start_day is not None: - assert self.unit == 'weeks' + if self.unit != 'weeks': + raise ScheduleValueError("`unit` should be 'weeks'") weekdays = ( 'monday', 'tuesday', @@ -464,15 +492,18 @@ def _schedule_next_run(self): 'saturday', 'sunday' ) - assert self.start_day in weekdays + if self.start_day not in weekdays: + raise ScheduleValueError("Invalid start day.") weekday = weekdays.index(self.start_day) days_ahead = weekday - self.next_run.weekday() if days_ahead <= 0: # Target day already happened this week days_ahead += 7 self.next_run += datetime.timedelta(days_ahead) - self.period if self.at_time is not None: - assert self.unit in ('days', 'hours', 'minutes') \ - or self.start_day is not None + if (self.unit not in ('days', 'hours', 'minutes') + and self.start_day is None): + raise ScheduleValueError(("Invalid unit without" + " specifying start day.")) kwargs = { 'second': self.at_time.second, 'microsecond': 0 diff --git a/test_schedule.py b/test_schedule.py index 856b2c2e..9a257507 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -9,7 +9,7 @@ # pylint: disable-msg=R0201,C0111,E0102,R0904,R0901 import schedule -from schedule import every +from schedule import every, ScheduleValueError, IntervalError def make_mock_job(name=None): @@ -58,6 +58,68 @@ def test_time_units(self): assert every().days.unit == 'days' assert every().weeks.unit == 'weeks' + job_instance = schedule.Job(interval=2) + # without a context manager, it incorrectly raises an error because + # it is not callable + with self.assertRaises(IntervalError): + job_instance.minute + with self.assertRaises(IntervalError): + job_instance.hour + with self.assertRaises(IntervalError): + job_instance.day + with self.assertRaises(IntervalError): + job_instance.week + with self.assertRaises(IntervalError): + job_instance.monday + with self.assertRaises(IntervalError): + job_instance.tuesday + with self.assertRaises(IntervalError): + job_instance.wednesday + with self.assertRaises(IntervalError): + job_instance.thursday + with self.assertRaises(IntervalError): + job_instance.friday + with self.assertRaises(IntervalError): + job_instance.saturday + with self.assertRaises(IntervalError): + job_instance.sunday + + # test an invalid unit + job_instance.unit = "foo" + self.assertRaises(ScheduleValueError, job_instance.at, "1:0:0") + self.assertRaises(ScheduleValueError, job_instance._schedule_next_run) + + # test start day exists but unit is not 'weeks' + job_instance.unit = "days" + job_instance.start_day = 1 + self.assertRaises(ScheduleValueError, job_instance._schedule_next_run) + + # test weeks with an invalid start day + job_instance.unit = "weeks" + job_instance.start_day = "bar" + self.assertRaises(ScheduleValueError, job_instance._schedule_next_run) + + # test a valid unit with invalid hours/minutes/seconds + job_instance.unit = "days" + self.assertRaises(ScheduleValueError, job_instance.at, "25:00:00") + self.assertRaises(ScheduleValueError, job_instance.at, "00:61:00") + self.assertRaises(ScheduleValueError, job_instance.at, "00:00:61") + + # test invalid time format + self.assertRaises(ScheduleValueError, job_instance.at, "25:0:0") + self.assertRaises(ScheduleValueError, job_instance.at, "0:61:0") + self.assertRaises(ScheduleValueError, job_instance.at, "0:0:61") + + # test (very specific) seconds with unspecified start_day + job_instance.unit = "seconds" + job_instance.at_time = datetime.datetime.now() + job_instance.start_day = None + self.assertRaises(ScheduleValueError, job_instance._schedule_next_run) + + # test self.latest >= self.interval + job_instance.latest = 3 + self.assertRaises(ScheduleValueError, job_instance._schedule_next_run) + def test_singular_time_units_match_plural_units(self): assert every().second.unit == every().seconds.unit assert every().minute.unit == every().minutes.unit @@ -94,14 +156,42 @@ def test_at_time(self): assert every().day.at('10:30').do(mock_job).next_run.minute == 30 assert every().day.at('10:30:50').do(mock_job).next_run.second == 50 - self.assertRaises(AssertionError, every().day.at, '2:30:000001') - self.assertRaises(AssertionError, every().day.at, '::2') - self.assertRaises(AssertionError, every().day.at, '.2') - self.assertRaises(AssertionError, every().day.at, '2') - self.assertRaises(AssertionError, every().day.at, ':2') - self.assertRaises(AssertionError, every().day.at, ' 2:30:00') + self.assertRaises(ScheduleValueError, every().day.at, '2:30:000001') + self.assertRaises(ScheduleValueError, every().day.at, '::2') + self.assertRaises(ScheduleValueError, every().day.at, '.2') + self.assertRaises(ScheduleValueError, every().day.at, '2') + self.assertRaises(ScheduleValueError, every().day.at, ':2') + self.assertRaises(ScheduleValueError, every().day.at, ' 2:30:00') + self.assertRaises(ScheduleValueError, every().do, lambda: 0) self.assertRaises(TypeError, every().day.at, 2) + # without a context manager, it incorrectly raises an error because + # it is not callable + with self.assertRaises(IntervalError): + every(interval=2).second + with self.assertRaises(IntervalError): + every(interval=2).minute + with self.assertRaises(IntervalError): + every(interval=2).hour + with self.assertRaises(IntervalError): + every(interval=2).day + with self.assertRaises(IntervalError): + every(interval=2).week + with self.assertRaises(IntervalError): + every(interval=2).monday + with self.assertRaises(IntervalError): + every(interval=2).tuesday + with self.assertRaises(IntervalError): + every(interval=2).wednesday + with self.assertRaises(IntervalError): + every(interval=2).thursday + with self.assertRaises(IntervalError): + every(interval=2).friday + with self.assertRaises(IntervalError): + every(interval=2).saturday + with self.assertRaises(IntervalError): + every(interval=2).sunday + def test_at_time_hour(self): with mock_datetime(2010, 1, 6, 12, 20): mock_job = make_mock_job() @@ -115,11 +205,14 @@ def test_at_time_hour(self): assert every().hour.at(':00').do(mock_job).next_run.minute == 0 assert every().hour.at(':00').do(mock_job).next_run.second == 0 - self.assertRaises(AssertionError, every().hour.at, '2:30:00') - self.assertRaises(AssertionError, every().hour.at, '::2') - self.assertRaises(AssertionError, every().hour.at, '.2') - self.assertRaises(AssertionError, every().hour.at, '2') - self.assertRaises(AssertionError, every().hour.at, ' 2:30') + self.assertRaises(ScheduleValueError, every().hour.at, '2:30:00') + self.assertRaises(ScheduleValueError, every().hour.at, '::2') + self.assertRaises(ScheduleValueError, every().hour.at, '.2') + self.assertRaises(ScheduleValueError, every().hour.at, '2') + self.assertRaises(ScheduleValueError, every().hour.at, ' 2:30') + self.assertRaises(ScheduleValueError, every().hour.at, "61:00") + self.assertRaises(ScheduleValueError, every().hour.at, "00:61") + self.assertRaises(ScheduleValueError, every().hour.at, "01:61") self.assertRaises(TypeError, every().hour.at, 2) def test_at_time_minute(self): @@ -132,12 +225,12 @@ def test_at_time_minute(self): assert every().minute.at(':10').do(mock_job).next_run.minute == 21 assert every().minute.at(':10').do(mock_job).next_run.second == 10 - self.assertRaises(AssertionError, every().minute.at, '::2') - self.assertRaises(AssertionError, every().minute.at, '.2') - self.assertRaises(AssertionError, every().minute.at, '2') - self.assertRaises(AssertionError, every().minute.at, '2:30:00') - self.assertRaises(AssertionError, every().minute.at, '2:30') - self.assertRaises(AssertionError, every().minute.at, ' :30') + self.assertRaises(ScheduleValueError, every().minute.at, '::2') + self.assertRaises(ScheduleValueError, every().minute.at, '.2') + self.assertRaises(ScheduleValueError, every().minute.at, '2') + self.assertRaises(ScheduleValueError, every().minute.at, '2:30:00') + self.assertRaises(ScheduleValueError, every().minute.at, '2:30') + self.assertRaises(ScheduleValueError, every().minute.at, ' :30') self.assertRaises(TypeError, every().minute.at, 2) def test_next_run_time(self):