def schedule_every_monday_at(process, str_time, run_at_start=True): scheduler1 = Scheduler() scheduler1.every().monday.at(str_time).do(process) if run_at_start: # Run the job now scheduler1.run_all() while True: scheduler1.run_pending() time.sleep(1)
def test_exception_handling(): # The normal scheduler will fail when a job raises an exception and the job # will be marked as if it was never run. normal_scheduler = Scheduler() normal_scheduler.every(1).hour.do(_failjob) with pytest.raises(Exception) as excinfo: normal_scheduler.run_all() assert "I will always fail" in str(excinfo) assert normal_scheduler.jobs[0].last_run is None assert normal_scheduler.jobs[0].next_run > datetime.now() # The Safe scheduler can deal with this and just schedules the next # execution of this job safe_scheduler = SafeScheduler() safe_scheduler.every(1).hour.do(_failjob) safe_scheduler.run_all() assert safe_scheduler.jobs[0].last_run < datetime.now() assert safe_scheduler.jobs[0].next_run > datetime.now()
class Crontab: def __init__(self): self.scheduler = Scheduler() self.funcs_time_attrs = [] self.timer_type_map = { 's': 'seconds', 'm': 'minutes', 'h': 'hours', 'd': 'days', 'w': 'weeks', 'mon': 'monday', 'tue': 'tuesday', 'wed': 'wednesday', 'thu': 'thursday', 'fri': 'friday', 'sat': 'saturday', 'sun': 'sunday' } def every(self, timer_value, timer_type, timer_concrete=None): """ Decorate function change to time function. """ def decorator(func): nonlocal timer_value nonlocal timer_type if isinstance(timer_value, str): if '-' not in timer_value: timer_value = int(timer_value) else: timer_value = timer_value else: raise Exception("timer_value should be str!") timer_type_list = list(chain(*self.timer_type_map.items())) if timer_type in timer_type_list: if isinstance(timer_value, int) and timer_concrete is None: func_time_dict = {func: timer_value} if self.timer_type_map.get(timer_type): attr_name = 'funcs_%s_timer' % self.timer_type_map[ timer_type] else: attr_name = 'funcs_%s_timer' % timer_type elif isinstance(timer_value, int) and timer_concrete is not None: func_time_dict = {func: [timer_value, timer_concrete]} if self.timer_type_map.get(timer_type): attr_name = 'funcs_%s_concrete_timer' % self.timer_type_map[ timer_type] else: attr_name = 'funcs_%s_concrete_timer' % timer_type elif isinstance(timer_value, str) and timer_concrete is None: func_time_dict = {func: timer_value} if self.timer_type_map.get(timer_type): attr_name = 'funcs_%s_random_timer' % self.timer_type_map[ timer_type] else: attr_name = 'funcs_%s_random_timer' % timer_type elif isinstance(timer_value, str) and timer_concrete is not None: func_time_dict = {func: [timer_value, timer_concrete]} if self.timer_type_map.get(timer_type): attr_name = 'funcs_%s_random_concrete_timer' % self.timer_type_map[ timer_type] else: attr_name = 'funcs_%s_random_concrete_timer' % timer_type else: raise Exception('parameter error!') if hasattr(self, attr_name): attr_name_func_dict = getattr(self, attr_name) attr_name_func_dict.update(func_time_dict) setattr(self, attr_name, attr_name_func_dict) else: setattr(self, attr_name, func_time_dict) self.funcs_time_attrs.append(attr_name) else: raise Exception('timer_type error!') return decorator def load_time_funcs(self, attr_name): """ Load the time type but not specific exact time function. """ for k, v in zip( getattr(self, attr_name).keys(), getattr(self, attr_name).values()): time_type = attr_name.split('_')[1] getattr(self.scheduler.every(v), time_type).do(job_func=k) def load_concrete_time_funcs(self, attr_name): """ Load the time type and specific exact time function. """ for k, v in zip( getattr(self, attr_name).keys(), getattr(self, attr_name).values()): time_type = attr_name.split('_')[1] getattr(self.scheduler.every(v[0]), time_type).at(v[1]).do(job_func=k) def load_random_time_funcs(self, attr_name): """ Load the time range but not specific exact time function. """ for k, v in zip( getattr(self, attr_name).keys(), getattr(self, attr_name).values()): v_list = v.split('-') left_value = int(v_list[0]) right_value = int(v_list[-1]) time_type = attr_name.split('_')[1] getattr(self.scheduler.every(left_value), time_type).to(right_value).do(job_func=k) def load_random_concrete_time_funcs(self, attr_name): """ Load the time range and specific exact time function. """ for k, v in zip( getattr(self, attr_name).keys(), getattr(self, attr_name).values()): v_list = v.split('-') left_value = int(v_list[0]) right_value = int(v_list[-1]) time_type = attr_name.split('_')[1] getattr(self.scheduler.every(left_value), time_type).to(right_value).at(v[1]).do(job_func=k) def load_scheduler_funcs(self): """ Load all function decorated. """ for attr_name in self.funcs_time_attrs: if attr_name.endswith('_random_concrete_timer'): self.load_random_concrete_time_funcs(attr_name) elif attr_name.endswith('_random_timer'): self.load_random_time_funcs(attr_name) elif attr_name.endswith('_concrete_timer'): self.load_concrete_time_funcs(attr_name) elif attr_name.endswith('_timer'): self.load_time_funcs(attr_name) def run_all_jobs(self): """ Run all function once immediately. """ self.load_scheduler_funcs() self.scheduler.run_all() def job_start(self): """ Scheduler into pending. """ while True: self.scheduler.run_pending() time.sleep(self.interval) def run(self, interval=0): """ Run all tasks. """ if not isinstance(interval, (int, float)): raise TypeError('interval should be int or float.') self.interval = interval self.load_scheduler_funcs() self.job_start()
class Polling(AsyncPull, AsyncPullNowMixin): """ Base class for polling plugins. You may specify duration literals such as 60 (60 secs), 1m, 1h (...) to realize a periodic polling or cron expressions (*/1 * * * * > every min) to realize cron like behaviour. """ __REPR_FIELDS__ = ['interval', 'is_cron'] def __init__( self, interval: Optional[DurationLiteral] = 60, instant_run: bool = False, **kwargs: Any ): super().__init__(**kwargs) self._assert_polling_compat() if interval is None: # No scheduled execution. Use endpoint `/trigger` of api to execute. self._poll_interval = None self.interval = None self.is_cron = False else: try: # Literals such as 60s, 1m, 1h, ... self._poll_interval = parse_duration_literal(interval) self.interval = interval self.is_cron = False except TypeError: # ... or a cron-like expression is valid from cronex import CronExpression # type: ignore self._cron_interval = CronExpression(interval) self.interval = self._cron_interval self.is_cron = True self._is_running = False self._scheduler: Optional[Scheduler] = None self._instant_run = try_parse_bool(instant_run, False) def _assert_polling_compat(self) -> None: self._assert_abstract_compat((SyncPolling, AsyncPolling)) self._assert_fun_compat('_poll') async def _pull(self) -> None: def _callback() -> None: loop = asyncio.get_event_loop() if loop.is_running(): asyncio.ensure_future(self._run_schedule()) self._scheduler = Scheduler() self._configure_scheduler(self._scheduler, _callback) if self._instant_run: self._scheduler.run_all() while not self.stopped: self._scheduler.run_pending() await self._sleep(0.5) while self._is_running: # Keep the loop alive until the job is finished await asyncio.sleep(0.1) async def _pull_now(self) -> None: await self._run_now() async def _run_now(self) -> Payload: """Runs the poll right now. It will not run, if the last poll is still running.""" if self._is_running: self.logger.warning("Polling job is still running. Skipping current run") return self._is_running = True try: payload = await self.poll() if payload is not None: self.notify(payload) return payload finally: self._is_running = False async def _run_schedule(self) -> None: try: if self.is_cron: dtime = datetime.now() if not self._cron_interval.check_trigger(( dtime.year, dtime.month, dtime.day, dtime.hour, dtime.minute )): return # It is not the time for the cron to trigger await self._run_now() except StopPollingError: await self._stop() except Exception: # pragma: no cover, pylint: disable=broad-except self.logger.exception("Polling of '%s' failed", self.name) def _configure_scheduler(self, scheduler: Scheduler, callback: Callable[[], None]) -> None: """ Configures the scheduler. You have to differ between "normal" intervals and cron like expressions by checking `self.is_cron`. Override in subclasses to fir the behaviour to your needs. Args: scheduler (schedule.Scheduler): The actual scheduler. callback (callable): The callback to call when the time is right. Returns: None """ if self.is_cron: # Scheduler always executes at the exact minute to check for cron triggering scheduler.every().minute.at(":00").do(callback) else: # Only activate when an interval is specified # If not the only way is to trigger the poll by the api `trigger` endpoint if self._poll_interval: # Scheduler executes every interval seconds to execute the poll scheduler.every(self._poll_interval).seconds.do(callback) async def poll(self) -> Payload: """Performs polling.""" poll_fun = getattr(self, '_poll') if inspect.iscoroutinefunction(poll_fun): return await poll_fun() return await run_sync(poll_fun)