def test_interval_clock_interval_must_be_positive(self): with pytest.raises(ValueError, match="greater than 0"): clocks.IntervalClock(interval=timedelta(hours=-1)) with pytest.raises(ValueError, match="greater than 0"): clocks.IntervalClock(interval=timedelta(seconds=-1)) with pytest.raises(ValueError, match="greater than 0"): clocks.IntervalClock(interval=timedelta(0))
def test_start_date_and_end_date_none(): s = schedules.Schedule(clocks=[ clocks.IntervalClock(timedelta(hours=1)), clocks.IntervalClock(timedelta(hours=1)), ]) assert s.start_date is None assert s.end_date is None
def test_interval_clock_interval_must_be_more_than_one_minute(self): with pytest.raises(ValueError): clocks.IntervalClock(interval=timedelta(seconds=59)) with pytest.raises(ValueError): clocks.IntervalClock(interval=timedelta(microseconds=59999999)) with pytest.raises(ValueError): clocks.IntervalClock(interval=timedelta(0))
def test_interval_clocks_with_exactly_one_minute_intervals_can_be_serialized(): s = schedules.Schedule( clocks=[clocks.IntervalClock(timedelta(seconds=60))]) t = schedules.Schedule(clocks=[clocks.IntervalClock(timedelta(minutes=1))]) s2 = serialize_and_deserialize(s) t2 = serialize_and_deserialize(t) assert s2.next(1, after=pendulum.datetime( 2019, 1, 1)) == [pendulum.datetime(2019, 1, 1, 0, 1)] assert t2.next(1, after=pendulum.datetime( 2019, 1, 1)) == [pendulum.datetime(2019, 1, 1, 0, 1)]
def test_start_date_and_end_date(): s = schedules.Schedule(clocks=[ clocks.IntervalClock( timedelta(hours=1), start_date=pendulum.datetime(2018, 1, 1), end_date=pendulum.datetime(2019, 1, 1), ), clocks.IntervalClock( timedelta(hours=1), start_date=pendulum.datetime(2019, 1, 1), end_date=pendulum.datetime(2020, 1, 1), ), ]) assert s.start_date == pendulum.datetime(2018, 1, 1) assert s.end_date == pendulum.datetime(2020, 1, 1)
def test_create_schedule_multiple_overlapping_clocks(): dt = pendulum.datetime(2019, 1, 1) s = schedules.Schedule(clocks=[ clocks.IntervalClock(timedelta(days=1)), clocks.IntervalClock(timedelta(hours=12), start_date=pendulum.datetime(2019, 1, 3)), ]) assert s.next(6, after=dt) == [ dt.add(days=1), dt.add(days=2), dt.add(days=2, hours=12), dt.add(days=3), dt.add(days=3, hours=12), dt.add(days=4), ]
def test_create_schedule(): dt = pendulum.datetime(2019, 1, 1) s = schedules.Schedule(clocks=[clocks.IntervalClock(timedelta(days=1))]) assert s.next( 3, after=dt) == [dt.add(days=1), dt.add(days=2), dt.add(days=3)]
def test_end_date(self): c = clocks.IntervalClock(timedelta(hours=1), end_date=pendulum.datetime(2020, 1, 1)) assert c.start_date is None assert c.interval == timedelta(hours=1) assert c.end_date == pendulum.datetime(2020, 1, 1) assert c.parameter_defaults == dict()
def test_create_interval_clock_with_parameters(self): c = clocks.IntervalClock( start_date=pendulum.now("UTC"), interval=timedelta(days=1), parameter_defaults=dict(x=42), ) assert c.parameter_defaults == dict(x=42)
def test_interval_clocks_with_sub_minute_intervals_cant_be_deserialized(): schema = ScheduleSchema() s = schedules.Schedule(clocks=[clocks.IntervalClock(timedelta(seconds=100))]) data = schema.dump(s) data["clocks"][0]["interval"] = 59 * 1e6 with pytest.raises(ValueError, match="can not be less than one minute"): schema.load(data)
def test_serialize_complex_schedule(): dt = pendulum.datetime(2019, 1, 3) s = schedules.Schedule( # fire every hour clocks=[clocks.IntervalClock(timedelta(hours=1))], # only on weekdays filters=[filters.is_weekday], # only at 9am or 3pm or_filters=[ filters.at_time(pendulum.time(9)), filters.between_times(pendulum.time(15), pendulum.time(15)), ], # not on january 8 not_filters=[filters.between_dates(1, 8, 1, 8)], # add three hours adjustments=[adjustments.add(timedelta(hours=3))], ) s2 = serialize_and_deserialize(s) assert s2.next(8, after=dt) == [ dt.replace(hour=12), dt.replace(hour=18), dt.add(days=1).replace(hour=12), dt.add(days=1).replace(hour=18), # skip weekend dt.add(days=4).replace(hour=12), dt.add(days=4).replace(hour=18), # skip jan 8! dt.add(days=6).replace(hour=12), dt.add(days=6).replace(hour=18), ]
def test_start_date_can_be_none(self): c = clocks.IntervalClock(timedelta(hours=1)) assert c.start_date is None assert c.interval == timedelta(hours=1) assert c.end_date is None assert c.parameter_defaults == dict() assert c.labels is None
def test_create_schedule_multiple_filters(): # jan 3 was a thursday dt = pendulum.datetime(2019, 1, 3) s = schedules.Schedule( # fire every hour clocks=[clocks.IntervalClock(timedelta(hours=1))], # only on weekdays filters=[filters.is_weekday], # only at 9am or 3pm or_filters=[ filters.between_times(pendulum.time(9), pendulum.time(9)), filters.between_times(pendulum.time(15), pendulum.time(15)), ], # not on january 8 not_filters=[filters.between_dates(1, 8, 1, 8)], ) assert s.next(8, after=dt) == [ dt.replace(hour=9), dt.replace(hour=15), dt.add(days=1).replace(hour=9), dt.add(days=1).replace(hour=15), # skip weekend dt.add(days=4).replace(hour=9), dt.add(days=4).replace(hour=15), # skip jan 8! dt.add(days=6).replace(hour=9), dt.add(days=6).replace(hour=15), ]
def test_create_interval_clock_with_labels(self): c = clocks.IntervalClock( start_date=pendulum.now("UTC"), interval=timedelta(days=1), labels=["foo"], ) assert c.labels == ["foo"]
def test_create_schedule(): dt = pendulum.datetime(2019, 1, 1) s = schedules.Schedule(clocks=[clocks.IntervalClock(timedelta(days=1))]) output = s.next(3, after=dt) assert all([not isinstance(e, clocks.ClockEvent) for e in output]) assert output == [dt.add(days=1), dt.add(days=2), dt.add(days=3)]
def test_interval_clock_end_date(self): start_date = pendulum.datetime(2018, 1, 1) end_date = pendulum.datetime(2018, 1, 2) c = clocks.IntervalClock( start_date=start_date, interval=timedelta(days=1), end_date=end_date ) assert islice(c.events(after=start_date), 3) == [start_date.add(days=1)] assert islice(c.events(after=pendulum.datetime(2018, 2, 1)), 3) == []
def test_interval_clock_doesnt_compute_dates_before_start_date(self): start_date = pendulum.datetime(2018, 1, 1) c = clocks.IntervalClock(start_date=start_date, interval=timedelta(hours=1)) assert islice(c.events(after=pendulum.datetime(2000, 1, 1)), 3) == [ start_date, start_date.add(hours=1), start_date.add(hours=2), ]
def test_interval_clock_respects_microseconds(self): start_date = pendulum.datetime(2018, 1, 1, 0, 0, 0, 1) c = clocks.IntervalClock(start_date=start_date, interval=timedelta(hours=1)) assert islice(c.events(after=pendulum.datetime(2010, 1, 1)), 3) == [ start_date, start_date.add(hours=1), start_date.add(hours=2), ]
def test_create_schedule_multiple_overlapping_clocks_emit_events_if_asked(): dt = pendulum.datetime(2019, 1, 1) s = schedules.Schedule(clocks=[ clocks.IntervalClock(timedelta(days=1)), clocks.IntervalClock(timedelta(hours=12), start_date=pendulum.datetime(2019, 1, 3)), ]) output = s.next(6, after=dt, return_events=True) assert all(isinstance(e, clocks.ClockEvent) for e in output) assert all(e.parameter_defaults == dict() for e in output) assert output == [ dt.add(days=1), dt.add(days=2), dt.add(days=2, hours=12), dt.add(days=3), dt.add(days=3, hours=12), dt.add(days=4), ]
def test_create_schedule_multiple_overlapping_clocks(): dt = pendulum.datetime(2019, 1, 1) s = schedules.Schedule(clocks=[ clocks.IntervalClock(timedelta(days=1)), clocks.IntervalClock(timedelta(hours=12), start_date=pendulum.datetime(2019, 1, 3)), ]) output = s.next(6, after=dt) assert all(not isinstance(e, clocks.ClockEvent) for e in output) assert output == [ dt.add(days=1), dt.add(days=2), dt.add(days=2, hours=12), dt.add(days=3), dt.add(days=3, hours=12), dt.add(days=4), ]
def test_serialize_multiple_clocks(): dt = pendulum.datetime(2019, 1, 1) s = schedules.Schedule(clocks=[ clocks.IntervalClock(timedelta(days=1)), clocks.IntervalClock(timedelta(hours=12), start_date=pendulum.datetime(2019, 1, 3)), ]) s2 = serialize_and_deserialize(s) assert s2.next(6, after=dt) == [ dt.add(days=1), dt.add(days=2), dt.add(days=2, hours=12), dt.add(days=3), dt.add(days=3, hours=12), dt.add(days=4), ]
def test_create_schedule_emits_events_if_asked(): dt = pendulum.datetime(2019, 1, 1) s = schedules.Schedule(clocks=[clocks.IntervalClock(timedelta(days=1))]) output = s.next(3, after=dt, return_events=True) assert all([isinstance(e, clocks.ClockEvent) for e in output]) assert all([e.parameter_defaults == dict() for e in output]) assert output == [dt.add(days=1), dt.add(days=2), dt.add(days=3)]
def test_interval_clock_events_with_after_argument(self): start_date = pendulum.datetime(2018, 1, 1) after = pendulum.datetime(2025, 1, 5) c = clocks.IntervalClock(timedelta(days=1), start_date=start_date) assert islice(c.events(after=after.add(hours=1)), 3) == [ after.add(days=1), after.add(days=2), after.add(days=3), ]
def test_interval_clock_events(self): """Test that default after is *now*""" start_date = pendulum.datetime(2018, 1, 1) today = pendulum.today("UTC") c = clocks.IntervalClock(timedelta(days=1), start_date=start_date) assert islice(c.events(), 3) == [ today.add(days=1), today.add(days=2), today.add(days=3), ]
def test_create_schedule_multiple_exclusive_filters(): dt = pendulum.datetime(2019, 1, 1) s = schedules.Schedule( clocks=[clocks.IntervalClock(timedelta(hours=1))], filters=[ filters.between_times(pendulum.time(9), pendulum.time(10)), filters.between_times(pendulum.time(15), pendulum.time(16)), ], ) assert s.next(6, after=dt) == []
def test_create_schedule_multiple_overlapping_clocks_emit_events_with_correct_parameters(): s = schedules.Schedule( clocks=[ clocks.IntervalClock(timedelta(days=1), parameter_defaults=dict(x=0)), clocks.IntervalClock(timedelta(hours=12), parameter_defaults=dict(x=1)), ] ) dt = pendulum.datetime(2019, 1, 1) output = s.next(6, after=dt, return_events=True) assert all([isinstance(e, clocks.ClockEvent) for e in output]) assert [e.parameter_defaults["x"] for e in output] == [1, 0, 1, 1, 0, 1] assert [e.start_time for e in output] == [ dt.add(hours=12), dt.add(days=1), dt.add(days=1), dt.add(days=1, hours=12), dt.add(days=2), dt.add(days=2), ]
def test_interval_clock_respects_after_in_middle_of_interval(self): """ If the "after" date is in the middle of an interval, then the IntervalClock should advance to the next interval. """ start_date = pendulum.datetime(2018, 1, 1) c = clocks.IntervalClock(start_date=start_date, interval=timedelta(hours=1)) assert islice(c.events(after=start_date + timedelta(microseconds=1)), 2) == [ start_date.add(hours=1), start_date.add(hours=2), ]
def test_interval_clock_hourly_daylight_savings_time_backward(self, serialize): """ 11/4/2018, at 2am, America/New_York switched clocks back an hour. """ dt = pendulum.datetime(2018, 11, 3, 23, tz="America/New_York") c = clocks.IntervalClock(timedelta(hours=1), start_date=dt) if serialize: c = ClockSchema().load(ClockSchema().dump(c)) next_4 = islice(c.events(after=dt), 4) # repeat the 1am run in local time assert [t.in_tz("America/New_York").hour for t in next_4] == [0, 1, 1, 2] # runs every hour UTC assert [t.in_tz("UTC").hour for t in next_4] == [4, 5, 6, 7]
def test_interval_clock_hourly_daylight_savings_time_forward(self, serialize): """ On 3/11/2018, at 2am, America/New_York switched clocks forward an hour. """ dt = pendulum.datetime(2018, 3, 10, 23, tz="America/New_York") c = clocks.IntervalClock(timedelta(hours=1), start_date=dt) if serialize: c = ClockSchema().load(ClockSchema().dump(c)) next_4 = islice(c.events(after=dt), 4) # skip 2am assert [t.in_tz("America/New_York").hour for t in next_4] == [0, 1, 3, 4] # constant hourly schedule in utc time assert [t.in_tz("UTC").hour for t in next_4] == [5, 6, 7, 8]
def test_interval_clock_always_has_the_right_offset(self): """ Tests the situation where a long duration has passed since the start date that crosses a DST boundary; for very short intervals this occasionally could result in "next" scheduled times that are in the past by one hour. """ start_date = pendulum.from_timestamp(1582002945.964696).astimezone( pendulum.timezone("US/Pacific")) current_date = pendulum.from_timestamp(1593643144.233938).astimezone( pendulum.timezone("UTC")) c = clocks.IntervalClock(timedelta(minutes=1, seconds=15), start_date=start_date) next_4 = islice(c.events(after=current_date), 4) assert all(d > current_date for d in next_4)