diff options
-rw-r--r-- | README.rst | 4 | ||||
-rw-r--r-- | apscheduler/job.py | 35 | ||||
-rw-r--r-- | apscheduler/scheduler.py | 206 | ||||
-rw-r--r-- | apscheduler/triggers/__init__.py | 3 | ||||
-rw-r--r-- | apscheduler/triggers/cron/__init__.py | 28 | ||||
-rw-r--r-- | apscheduler/triggers/date.py (renamed from apscheduler/triggers/simple.py) | 7 | ||||
-rw-r--r-- | apscheduler/triggers/interval.py | 17 | ||||
-rw-r--r-- | docs/cronschedule.rst | 12 | ||||
-rw-r--r-- | docs/dateschedule.rst | 8 | ||||
-rw-r--r-- | docs/index.rst | 6 | ||||
-rw-r--r-- | docs/intervalschedule.rst | 12 | ||||
-rw-r--r-- | docs/migration.rst | 22 | ||||
-rw-r--r-- | examples/interval.py | 2 | ||||
-rw-r--r-- | examples/persistent.py | 3 | ||||
-rw-r--r-- | examples/reference.py | 2 | ||||
-rw-r--r-- | examples/threaded.py | 2 | ||||
-rw-r--r-- | setup.py | 20 | ||||
-rw-r--r-- | tests/testintegration.py | 6 | ||||
-rw-r--r-- | tests/testjob.py | 52 | ||||
-rw-r--r-- | tests/testjobstores.py | 12 | ||||
-rw-r--r-- | tests/testscheduler.py | 57 | ||||
-rw-r--r-- | tests/testtriggers.py | 20 |
22 files changed, 230 insertions, 306 deletions
@@ -15,9 +15,9 @@ provides features not present in Quartz (such as multiple job stores). Features ======== -* No (hard) external dependencies +* No (hard) external dependencies, except for setuptools/distribute * Thread-safe API -* Excellent test coverage (tested on CPython 2.5 - 2.7, 3.2 - 3.3, Jython 2.5.3, PyPy 1.9) +* Excellent test coverage (tested on CPython 2.6 - 2.7, 3.2 - 3.3, Jython 2.5.3, PyPy 1.9) * Configurable scheduling mechanisms (triggers): * Cron-like scheduling diff --git a/apscheduler/job.py b/apscheduler/job.py index adc7586..a025983 100644 --- a/apscheduler/job.py +++ b/apscheduler/job.py @@ -5,8 +5,7 @@ Jobs represent scheduled tasks. from threading import Lock from datetime import timedelta -from apscheduler.util import (to_unicode, ref_to_obj, obj_to_ref, - get_callable_name) +from apscheduler.util import to_unicode, ref_to_obj, obj_to_ref, get_callable_name class MaxInstancesReachedError(Exception): @@ -16,40 +15,12 @@ class MaxInstancesReachedError(Exception): class Job(object): """ Encapsulates the actual Job along with its metadata. Job instances are created by the scheduler when adding jobs, - and should not be directly instantiated. These options can be set when adding jobs to the scheduler - (see :ref:`job_options`). - - :var trigger: trigger that determines the execution times - :var func: callable to call when the trigger is triggered - :var args: list of positional arguments to call func with - :var kwargs: dict of keyword arguments to call func with - :var name: name of the job - :var misfire_grace_time: seconds after the designated run time that the job is still allowed to be run - :var coalesce: run once instead of many times if the scheduler determines that the job should be run more than once - in succession - :var max_runs: maximum number of times this job is allowed to be triggered - :var max_instances: maximum number of concurrently running instances allowed for this job - :var runs: number of times this job has been triggered - :var instances: number of concurrently running instances of this job + and should not be directly instantiated. """ id = None next_run_time = None - def __init__(self, trigger, func, args, kwargs, misfire_grace_time, coalesce, name=None, max_runs=None, - max_instances=1): - if not trigger: - raise ValueError('The trigger must not be None') - if not hasattr(args, '__getitem__'): - raise TypeError('args must be a list-like object') - if not hasattr(kwargs, '__getitem__'): - raise TypeError('kwargs must be a dict-like object') - if misfire_grace_time <= 0: - raise ValueError('misfire_grace_time must be a positive value') - if max_runs is not None and max_runs <= 0: - raise ValueError('max_runs must be a positive value') - if max_instances <= 0: - raise ValueError('max_instances must be a positive value') - + def __init__(self, trigger, func, args, kwargs, misfire_grace_time, coalesce, name, max_runs, max_instances): if isinstance(func, str): self.func = ref_to_obj(func) self.func_ref = func diff --git a/apscheduler/scheduler.py b/apscheduler/scheduler.py index 599073a..5117fc9 100644 --- a/apscheduler/scheduler.py +++ b/apscheduler/scheduler.py @@ -5,11 +5,13 @@ This module is the main part of the library. It houses the Scheduler class and r from threading import Thread, Event, Lock from datetime import datetime, timedelta from logging import getLogger +from collections import Mapping, Iterable import os import sys +from pkg_resources import iter_entry_points + from apscheduler.util import * -from apscheduler.triggers import SimpleTrigger, IntervalTrigger, CronTrigger from apscheduler.jobstores.ram_store import RAMJobStore from apscheduler.job import Job, MaxInstancesReachedError from apscheduler.events import * @@ -34,6 +36,7 @@ class Scheduler(object): _stopped = False _thread = None + _plugins = dict((ep.name, ep) for ep in iter_entry_points('apscheduler.triggers')) def __init__(self, gconfig={}, **options): self._wakeup = Event() @@ -42,6 +45,7 @@ class Scheduler(object): self._listeners = [] self._listeners_lock = Lock() self._pending_jobs = [] + self._triggers = {} self.configure(gconfig, **options) def configure(self, gconfig={}, **options): @@ -215,16 +219,14 @@ class Scheduler(object): logger.exception('Error notifying listener') def _real_add_job(self, job, jobstore, wakeup): + # Recalculate the next run time job.compute_next_run_time(datetime.now()) - if not job.next_run_time: - raise ValueError('Not adding job since it would never be run') - with self._jobstores_lock: - try: - store = self._jobstores[jobstore] - except KeyError: - raise KeyError('No such job store: %s' % jobstore) - store.add_job(job) + # Add the job to the given job store + store = self._jobstores.get(jobstore) + if not store: + raise KeyError('No such job store: %s' % jobstore) + store.add_job(job) # Notify listeners that a new job has been added event = JobStoreEvent(EVENT_JOBSTORE_JOB_ADDED, jobstore, job) @@ -236,8 +238,8 @@ class Scheduler(object): if wakeup: self._wakeup.set() - def add_job(self, trigger, func, args, kwargs, jobstore='default', - **options): + def add_job(self, func, trigger, trigger_args=(), args=None, kwargs=None, misfire_grace_time=None, coalesce=None, + name=None, max_runs=None, max_instances=1, jobstore='default'): """ Adds the given job to the job list and notifies the scheduler thread. @@ -245,26 +247,90 @@ class Scheduler(object): ``package.module:some.object`` format, where the first half (separated by ``:``) is an importable module and the second half is a reference to the callable object, relative to the module. - Any extra keyword arguments are passed along to the constructor of the :class:`~apscheduler.job.Job` class - (see :ref:`job_options`). + The ``trigger`` argument can either be: + + # the plugin name of the trigger (e.g. "cron"), in which case you should provide ``trigger_args`` as well + # an instance of the trigger :param trigger: trigger that determines when ``func`` is called + :param trigger_args: arguments given to the constructor of the trigger class :param func: callable (or a textual reference to one) to run at the given time :param args: list of positional arguments to call func with :param kwargs: dict of keyword arguments to call func with :param jobstore: alias of the job store to store the job in + :param name: name of the job + :param misfire_grace_time: seconds after the designated run time that the job is still allowed to be run + :param coalesce: run once instead of many times if the scheduler determines that the job should be run more than once + in succession + :param max_runs: maximum number of times this job is allowed to be triggered + :param max_instances: maximum number of concurrently running instances allowed for this job :rtype: :class:`~apscheduler.job.Job` """ - job = Job(trigger, func, args or [], kwargs or {}, - options.pop('misfire_grace_time', self.misfire_grace_time), - options.pop('coalesce', self.coalesce), **options) + # Argument sanity checking + if args is not None and (not isinstance(args, Iterable) and not isinstance(args, str)): + raise TypeError('args must be an iterable') + if kwargs is not None and not isinstance(kwargs, Mapping): + raise TypeError('kwargs must be a dict-like object') + if misfire_grace_time is not None and misfire_grace_time <= 0: + raise ValueError('misfire_grace_time must be a positive value') + if max_runs is not None and max_runs <= 0: + raise ValueError('max_runs must be a positive value') + if max_instances <= 0: + raise ValueError('max_instances must be a positive value') + + # If trigger is a string, resolve it to a class, possibly by loading an entry point if necessary + if isinstance(trigger, str): + try: + trigger_cls = self._triggers[trigger] + except KeyError: + if trigger in self._plugins: + trigger_cls = self._triggers[trigger] = self._plugins[trigger].load() + if not callable(getattr(trigger_cls, 'get_next_fire_time')): + raise TypeError('The trigger entry point does not point to a trigger class') + else: + raise KeyError('No trigger by the name "%s" was found' % trigger) + + if isinstance(trigger_args, Mapping): + trigger = trigger_cls(**trigger_args) + elif isinstance(trigger_args, Iterable): + trigger = trigger_cls(*trigger_args) + else: + raise ValueError('trigger_args must either be a dict-like object or an iterable') + elif not callable(getattr(trigger, 'get_next_fire_time')): + raise TypeError('Expected a trigger instance, got %s instead' % trigger.__class__.__name__) + + # Replace with scheduler level defaults if values are missing + if misfire_grace_time is None: + misfire_grace_time = self.misfire_grace_time + if coalesce is None: + coalesce = self.coalesce + + args = tuple(args) if args is not None else () + kwargs = dict(kwargs) if kwargs is not None else {} + job = Job(trigger, func, args, kwargs, misfire_grace_time, coalesce, name, max_runs, max_instances) + + # Ensure that dead-on-arrival jobs are never added + if job.compute_next_run_time(datetime.now()) is None: + raise ValueError('Not adding job since it would never be run') + + # Don't really add jobs to job stores before the scheduler is up and running if not self.running: self._pending_jobs.append((job, jobstore)) logger.info('Adding job tentatively -- it will be properly scheduled when the scheduler starts') else: self._real_add_job(job, jobstore, True) + return job + def scheduled_job(self, trigger, trigger_args=(), args=None, kwargs=None, jobstore='default', + misfire_grace_time=None, coalesce=None, name=None, max_runs=None, max_instances=1): + """A decorator version of :meth:`add_job`.""" + def inner(func): + func.job = self.add_job(func, trigger, trigger_args, args, kwargs, misfire_grace_time, coalesce, + name, max_runs, max_instances, jobstore) + return func + return inner + def _remove_job(self, job, alias, jobstore): jobstore.remove_job(job) @@ -274,114 +340,6 @@ class Scheduler(object): logger.info('Removed job "%s"', job) - def add_date_job(self, func, date, args=None, kwargs=None, **options): - """ - Schedules a job to be completed on a specific date and time. - Any extra keyword arguments are passed along to the constructor of the :class:`~apscheduler.job.Job` class - (see :ref:`job_options`). - - :param func: callable to run at the given time - :param date: the date/time to run the job at - :param name: name of the job - :param jobstore: stored the job in the named (or given) job store - :param misfire_grace_time: seconds after the designated run time that the job is still allowed to be run - :type date: :class:`datetime.date` - :rtype: :class:`~apscheduler.job.Job` - """ - trigger = SimpleTrigger(date) - return self.add_job(trigger, func, args, kwargs, **options) - - def add_interval_job(self, func, weeks=0, days=0, hours=0, minutes=0, - seconds=0, start_date=None, args=None, kwargs=None, - **options): - """ - Schedules a job to be completed on specified intervals. - Any extra keyword arguments are passed along to the constructor of the :class:`~apscheduler.job.Job` class - (see :ref:`job_options`). - - :param func: callable to run - :param weeks: number of weeks to wait - :param days: number of days to wait - :param hours: number of hours to wait - :param minutes: number of minutes to wait - :param seconds: number of seconds to wait - :param start_date: when to first execute the job and start the counter (default is after the given interval) - :param args: list of positional arguments to call func with - :param kwargs: dict of keyword arguments to call func with - :param name: name of the job - :param jobstore: alias of the job store to add the job to - :param misfire_grace_time: seconds after the designated run time that the job is still allowed to be run - :rtype: :class:`~apscheduler.job.Job` - """ - interval = timedelta(weeks=weeks, days=days, hours=hours, - minutes=minutes, seconds=seconds) - trigger = IntervalTrigger(interval, start_date) - return self.add_job(trigger, func, args, kwargs, **options) - - def add_cron_job(self, func, year=None, month=None, day=None, week=None, - day_of_week=None, hour=None, minute=None, second=None, - start_date=None, args=None, kwargs=None, **options): - """ - Schedules a job to be completed on times that match the given expressions. - Any extra keyword arguments are passed along to the constructor of the :class:`~apscheduler.job.Job` class - (see :ref:`job_options`). - - :param func: callable to run - :param year: year to run on - :param month: month to run on - :param day: day of month to run on - :param week: week of the year to run on - :param day_of_week: weekday to run on (0 = Monday) - :param hour: hour to run on - :param second: second to run on - :param args: list of positional arguments to call func with - :param kwargs: dict of keyword arguments to call func with - :param name: name of the job - :param jobstore: alias of the job store to add the job to - :param misfire_grace_time: seconds after the designated run time that the job is still allowed to be run - :return: the scheduled job - :rtype: :class:`~apscheduler.job.Job` - """ - trigger = CronTrigger(year=year, month=month, day=day, week=week, - day_of_week=day_of_week, hour=hour, - minute=minute, second=second, - start_date=start_date) - return self.add_job(trigger, func, args, kwargs, **options) - - def cron_schedule(self, **options): - """ - Decorator version of :meth:`add_cron_job`. - - This decorator does not wrap its host function. - - Unscheduling decorated functions is possible by passing the ``job`` attribute of the scheduled function to - :meth:`unschedule_job`. - - Any extra keyword arguments are passed along to the constructor of the :class:`~apscheduler.job.Job` class - (see :ref:`job_options`). - """ - def inner(func): - func.job = self.add_cron_job(func, **options) - return func - return inner - - def interval_schedule(self, **options): - """ - Decorator version of :meth:`add_interval_job`. - - This decorator does not wrap its host function. - - Unscheduling decorated functions is possible by passing the ``job`` attribute of the scheduled function to - :meth:`unschedule_job`. - - Any extra keyword arguments are passed along to the constructor of the :class:`~apscheduler.job.Job` class - (see :ref:`job_options`). - """ - def inner(func): - func.job = self.add_interval_job(func, **options) - return func - return inner - def get_jobs(self): """ Returns a list of all scheduled jobs. diff --git a/apscheduler/triggers/__init__.py b/apscheduler/triggers/__init__.py index 74a9788..e69de29 100644 --- a/apscheduler/triggers/__init__.py +++ b/apscheduler/triggers/__init__.py @@ -1,3 +0,0 @@ -from apscheduler.triggers.cron import CronTrigger -from apscheduler.triggers.interval import IntervalTrigger -from apscheduler.triggers.simple import SimpleTrigger diff --git a/apscheduler/triggers/cron/__init__.py b/apscheduler/triggers/cron/__init__.py index f216fae..2c4559e 100644 --- a/apscheduler/triggers/cron/__init__.py +++ b/apscheduler/triggers/cron/__init__.py @@ -17,18 +17,24 @@ class CronTrigger(object): 'second': BaseField } - def __init__(self, **values): - self.start_date = values.pop('start_date', None) - if self.start_date: - self.start_date = convert_to_datetime(self.start_date) - - # Check field names and yank out all None valued fields - for key, value in list(iteritems(values)): - if key not in self.FIELD_NAMES: - raise TypeError('Invalid field name: %s' % key) - if value is None: - del values[key] + def __init__(self, year=None, month=None, day=None, week=None, day_of_week=None, hour=None, minute=None, + second=None, start_date=None): + """ + Triggers when current time matches all specified time constraints, emulating the UNIX cron scheduler. + + :param year: year to run on + :param month: month to run on + :param day: day of month to run on + :param week: week of the year to run on + :param day_of_week: weekday to run on (0 = Monday) + :param hour: hour to run on + :param second: second to run on + :param start_date: earliest possible date/time to trigger on + """ + self.start_date = convert_to_datetime(start_date) if start_date else None + values = dict((key, value) for (key, value) in iteritems(locals()) + if key in self.FIELD_NAMES and value is not None) self.fields = [] assign_defaults = False for field_name in self.FIELD_NAMES: diff --git a/apscheduler/triggers/simple.py b/apscheduler/triggers/date.py index ea61b3f..33a30f9 100644 --- a/apscheduler/triggers/simple.py +++ b/apscheduler/triggers/date.py @@ -1,8 +1,13 @@ from apscheduler.util import convert_to_datetime -class SimpleTrigger(object): +class DateTrigger(object): def __init__(self, run_date): + """ + Triggers once on the given datetime. + + :param run_date: the date/time to run the job at + """ self.run_date = convert_to_datetime(run_date) def get_next_fire_time(self, start_date): diff --git a/apscheduler/triggers/interval.py b/apscheduler/triggers/interval.py index e7dfc17..5c3206a 100644 --- a/apscheduler/triggers/interval.py +++ b/apscheduler/triggers/interval.py @@ -5,11 +5,18 @@ from apscheduler.util import convert_to_datetime, timedelta_seconds class IntervalTrigger(object): - def __init__(self, interval, start_date=None): - if not isinstance(interval, timedelta): - raise TypeError('interval must be a timedelta') - - self.interval = interval + def __init__(self, weeks=0, days=0, hours=0, minutes=0, seconds=0, start_date=None): + """ + Triggers on specified intervals. + + :param weeks: number of weeks to wait + :param days: number of days to wait + :param hours: number of hours to wait + :param minutes: number of minutes to wait + :param seconds: number of seconds to wait + :param start_date: when to first execute the job and start the counter (default is after the given interval) + """ + self.interval = timedelta(weeks=weeks, days=days, hours=hours, minutes=minutes, seconds=seconds) self.interval_length = timedelta_seconds(self.interval) if self.interval_length == 0: self.interval = timedelta(seconds=1) diff --git a/docs/cronschedule.rst b/docs/cronschedule.rst index b381be0..6af2547 100644 --- a/docs/cronschedule.rst +++ b/docs/cronschedule.rst @@ -84,7 +84,7 @@ Example 1 # Schedules job_function to be run on the third Friday # of June, July, August, November and December at 00:00, 01:00, 02:00 and 03:00 - sched.add_cron_job(job_function, month='6-8,11-12', day='3rd fri', hour='0-3') + sched.add_job(job_function, 'cron', {'month': '6-8,11-12', 'day': '3rd fri', 'hour': '0-3'}) Example 2 @@ -95,21 +95,15 @@ Example 2 # Initialization similar as above, the backup function defined elsewhere # Schedule a backup to run once from Monday to Friday at 5:30 (am) - sched.add_cron_job(backup, day_of_week='mon-fri', hour=5, minute=30) + sched.add_job(backup, 'cron', {'day_of_week': 'mon-fri', 'hour': 5, 'minute': 30}) Decorator syntax ---------------- -As a convenience, there is an alternative syntax for using cron-style -schedules. The :meth:`~apscheduler.scheduler.Scheduler.cron_schedule` -decorator can be attached to any function, and has the same syntax as -:meth:`~apscheduler.scheduler.Scheduler.add_cron_job`, except for the ``func`` -parameter, obviously. - :: - @sched.cron_schedule(day='last sun') + @sched.scheduled_job('cron', {'day': 'last sun'}) def some_decorated_task(): print "I am printed at 00:00:00 on the last Sunday of every month!" diff --git a/docs/dateschedule.rst b/docs/dateschedule.rst index cb01ba8..494b1a6 100644 --- a/docs/dateschedule.rst +++ b/docs/dateschedule.rst @@ -22,18 +22,18 @@ This is the in-process equivalent to the UNIX "at" command. exec_date = date(2009, 11, 6) # Store the job in a variable in case we want to cancel it - job = sched.add_date_job(my_job, exec_date, ['text']) + job = sched.add_job(my_job, 'simple', [exec_date], ['text']) We could be more specific with the scheduling too:: from datetime import datetime # The job will be executed on November 6th, 2009 at 16:30:05 - job = sched.add_date_job(my_job, datetime(2009, 11, 6, 16, 30, 5), ['text']) + job = sched.add_job(my_job, 'simple', [datetime(2009, 11, 6, 16, 30, 5)], ['text']) You can even specify a date as text, with or without the time part:: - job = sched.add_date_job(my_job, '2009-11-06 16:30:05', ['text']) + job = sched.add_job(my_job, 'simple', ['2009-11-06 16:30:05'], ['text']) # Even down to the microsecond level, if you really want to! - job = sched.add_date_job(my_job, '2009-11-06 16:30:05.720400', ['text']) + job = sched.add_job(my_job, 'simple', ['2009-11-06 16:30:05.720400'], ['text']) diff --git a/docs/index.rst b/docs/index.rst index 430a40e..e8c09b3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,9 +23,9 @@ provides features not present in Quartz (such as multiple job stores). Features -------- -* No (hard) external dependencies +* No (hard) external dependencies, except for setuptools/distribute * Thread-safe API -* Excellent test coverage (tested on CPython 2.5 - 2.7, 3.2 - 3.3, Jython 2.5.3, PyPy 1.9) +* Excellent test coverage (tested on CPython 2.6 - 2.7, 3.2 - 3.3, Jython 2.5.3, PyPy 1.9) * Configurable scheduling mechanisms (triggers): * Cron-like scheduling @@ -85,7 +85,7 @@ Scheduler instance available from the very beginning:: sched = Scheduler() - @sched.interval_schedule(hours=3) + @sched.scheduled_job('interval', {'hours': 3}) def some_job(): print "Decorated job" diff --git a/docs/intervalschedule.rst b/docs/intervalschedule.rst index 328d315..1bf1b46 100644 --- a/docs/intervalschedule.rst +++ b/docs/intervalschedule.rst @@ -22,21 +22,15 @@ that. print "Hello World" # Schedule job_function to be called every two hours - sched.add_interval_job(job_function, hours=2) + sched.add_job(job_function, 'interval', {'hours': 2}) # The same as before, but start after a certain time point - sched.add_interval_job(job_function, hours=2, start_date='2010-10-10 09:30') + sched.add_job(job_function, 'interval', {'hours': 2, 'start_date': '2010-10-10 09:30'}) Decorator syntax ---------------- -As a convenience, there is an alternative syntax for using interval-based -schedules. The :meth:`~apscheduler.scheduler.Scheduler.interval_schedule` -decorator can be attached to any function, and has the same syntax as -:meth:`~apscheduler.scheduler.Scheduler.add_interval_job`, except for the -``func`` parameter, obviously. - :: from apscheduler.scheduler import Scheduler @@ -46,7 +40,7 @@ decorator can be attached to any function, and has the same syntax as sched.start() # Schedule job_function to be called every two hours - @sched.interval_schedule(hours=2) + @sched.scheduled_job('interval', {'hours': 2}) def job_function(): print "Hello World" diff --git a/docs/migration.rst b/docs/migration.rst index 11028dd..a338373 100644 --- a/docs/migration.rst +++ b/docs/migration.rst @@ -1,3 +1,25 @@ +Migrating from APScheduler v2.x to 3.0 +====================================== + +The 3.0 series is API incompatible with previous releases due to a design +overhaul. + +API changes +----------- + +* The trigger-specific scheduling methods have been removed entirely from the + Scheduler class. Instead, you have to use the + :meth:`~apscheduler.scheduler.Scheduler.add_job` method or the + :meth:`~apscheduler.scheduler.Scheduler.scheduled_job` decorator, giving the + entry point name of the trigger, or an already constructed instance. + It should also be noted that the signature of + :meth:`~apscheduler.scheduler.Scheduler.add_job` has changed due to this. +* Adding a job before the scheduler has been started can now fail if no next + execution time can be calculated for it. Previously it would only fail when + the scheduler was started. +* The "simple" trigger has been renamed to "date" + + Migrating from APScheduler v1.x to 2.0 ====================================== diff --git a/examples/interval.py b/examples/interval.py index cb7c4f4..5469fef 100644 --- a/examples/interval.py +++ b/examples/interval.py @@ -13,7 +13,7 @@ def tick(): if __name__ == '__main__': scheduler = Scheduler(standalone=True) - scheduler.add_interval_job(tick, seconds=3) + scheduler.add_job(tick, 'interval', {'seconds': 3}) print('Press Ctrl+C to exit') try: scheduler.start() diff --git a/examples/persistent.py b/examples/persistent.py index 51c2571..b75b778 100644 --- a/examples/persistent.py +++ b/examples/persistent.py @@ -18,8 +18,7 @@ if __name__ == '__main__': scheduler = Scheduler(standalone=True) scheduler.add_jobstore(ShelveJobStore('example.db'), 'shelve') alarm_time = datetime.now() + timedelta(seconds=10) - scheduler.add_date_job(alarm, alarm_time, name='alarm', - jobstore='shelve', args=[datetime.now()]) + scheduler.add_job(alarm, 'simple', [alarm_time], jobstore='shelve', args=[datetime.now()]) print('To clear the alarms, delete the example.db file.') print('Press Ctrl+C to exit') try: diff --git a/examples/reference.py b/examples/reference.py index 8be45e9..dc9756c 100644 --- a/examples/reference.py +++ b/examples/reference.py @@ -7,7 +7,7 @@ from apscheduler.scheduler import Scheduler if __name__ == '__main__': scheduler = Scheduler(standalone=True) - scheduler.add_interval_job('sys:stdout.write', args=['tick\n'], seconds=3) + scheduler.add_job('sys:stdout.write', 'interval', {'seconds': 3}, args=['tick\n']) print('Press Ctrl+C to exit') try: scheduler.start() diff --git a/examples/threaded.py b/examples/threaded.py index 2c612cd..7a11937 100644 --- a/examples/threaded.py +++ b/examples/threaded.py @@ -14,7 +14,7 @@ def tick(): if __name__ == '__main__': scheduler = Scheduler() - scheduler.add_interval_job(tick, seconds=3) + scheduler.add_job(tick, 'interval', {'seconds': 3}) print('Press Ctrl+C to exit') scheduler.start() @@ -1,13 +1,7 @@ # coding: utf-8 import os.path -try: - from setuptools import setup - - extras = dict(zip_safe=False, test_suite='nose.collector', tests_require=['nose']) -except ImportError: - from distutils.core import setup - extras = {} +from setuptools import setup, find_packages import apscheduler @@ -37,5 +31,15 @@ setup( ], keywords='scheduling cron', license='MIT', - packages=('apscheduler', 'apscheduler.jobstores', 'apscheduler.triggers', 'apscheduler.triggers.cron'), + packages=find_packages(), + test_suite='nose.collector', + tests_require=['nose'], + zip_safe=False, + entry_points={ + 'apscheduler.triggers': [ + 'date = apscheduler.triggers.date:DateTrigger', + 'interval = apscheduler.triggers.interval:IntervalTrigger', + 'cron = apscheduler.triggers.cron:CronTrigger' + ] + } ) diff --git a/tests/testintegration.py b/tests/testintegration.py index c6f0be8..7dfafa1 100644 --- a/tests/testintegration.py +++ b/tests/testintegration.py @@ -47,7 +47,7 @@ class IntegrationTestBase(object): # running when the next appointed time hits. vals = [0] - self.scheduler.add_interval_job(increment, jobstore='persistent', seconds=1, args=[vals, 2]) + self.scheduler.add_job(increment, 'interval', {'seconds': 1}, args=[vals, 2], jobstore='persistent') sleep(2.5) eq_(vals, [1]) @@ -55,8 +55,8 @@ class IntegrationTestBase(object): vals = [0] events = [] self.scheduler.add_listener(events.append, EVENT_JOB_EXECUTED | EVENT_JOB_MISSED) - self.scheduler.add_interval_job(increment, jobstore='persistent', seconds=0.3, max_instances=2, max_runs=4, - args=[vals, 1]) + self.scheduler.add_job(increment, 'interval', {'seconds': 0.3}, max_instances=2, max_runs=4, args=[vals, 1], + jobstore='persistent') sleep(2.4) eq_(vals, [2]) eq_(len(events), 4) diff --git a/tests/testjob.py b/tests/testjob.py index 726ceea..f11d2fb 100644 --- a/tests/testjob.py +++ b/tests/testjob.py @@ -1,10 +1,10 @@ from datetime import datetime, timedelta from threading import Lock -from nose.tools import eq_, raises, assert_raises # @UnresolvedImport +from nose.tools import eq_, assert_raises # @UnresolvedImport from apscheduler.job import Job, MaxInstancesReachedError -from apscheduler.triggers.simple import SimpleTrigger +from apscheduler.triggers.date import DateTrigger from apscheduler.triggers.interval import IntervalTrigger @@ -19,25 +19,23 @@ class TestJob(object): RUNTIME = datetime(2010, 12, 13, 0, 8, 0) def setup(self): - self.trigger = SimpleTrigger(self.RUNTIME) - self.job = Job(self.trigger, dummyfunc, [], {}, 1, False) + self.trigger = DateTrigger(self.RUNTIME) + self.job = Job(self.trigger, dummyfunc, [], {}, 1, False, None, None, 1) def test_compute_next_run_time(self): - self.job.compute_next_run_time( - self.RUNTIME - timedelta(microseconds=1)) + self.job.compute_next_run_time(self.RUNTIME - timedelta(microseconds=1)) eq_(self.job.next_run_time, self.RUNTIME) self.job.compute_next_run_time(self.RUNTIME) eq_(self.job.next_run_time, self.RUNTIME) - self.job.compute_next_run_time( - self.RUNTIME + timedelta(microseconds=1)) + self.job.compute_next_run_time(self.RUNTIME + timedelta(microseconds=1)) eq_(self.job.next_run_time, None) def test_compute_run_times(self): expected_times = [self.RUNTIME + timedelta(seconds=1), self.RUNTIME + timedelta(seconds=2)] - self.job.trigger = IntervalTrigger(timedelta(seconds=1), self.RUNTIME) + self.job.trigger = IntervalTrigger(seconds=1, start_date=self.RUNTIME) self.job.compute_next_run_time(expected_times[0]) eq_(self.job.next_run_time, expected_times[0]) @@ -70,7 +68,7 @@ class TestJob(object): max_instances=1, runs=0)) def test_setstate(self): - trigger = SimpleTrigger('2010-12-14 13:05:00') + trigger = DateTrigger('2010-12-14 13:05:00') state = dict(trigger=trigger, name='testjob.dummyfunc', func_ref='testjob:dummyfunc', args=[], kwargs={}, misfire_grace_time=2, max_runs=2, @@ -88,7 +86,7 @@ class TestJob(object): def test_jobs_equal(self): assert self.job == self.job - job2 = Job(SimpleTrigger(self.RUNTIME), lambda: None, [], {}, 1, False) + job2 = Job(DateTrigger(self.RUNTIME), lambda: None, [], {}, 1, False, None, None, 1) assert self.job != job2 job2.id = self.job.id = 123 @@ -119,36 +117,6 @@ class TestJob(object): def test_repr(self): self.job.compute_next_run_time(self.RUNTIME) eq_(repr(self.job), - "<Job (name=dummyfunc, trigger=<SimpleTrigger (run_date=datetime.datetime(2010, 12, 13, 0, 8))>)>") + "<Job (name=dummyfunc, trigger=<DateTrigger (run_date=datetime.datetime(2010, 12, 13, 0, 8))>)>") eq_(str(self.job), "dummyfunc (trigger: date[2010-12-13 00:08:00], next run at: 2010-12-13 00:08:00)") - - -@raises(ValueError) -def test_create_job_no_trigger(): - Job(None, lambda: None, [], {}, 1, False) - - -@raises(TypeError) -def test_create_job_invalid_args(): - Job(SimpleTrigger(datetime.now()), lambda: None, None, {}, 1, False) - - -@raises(TypeError) -def test_create_job_invalid_kwargs(): - Job(SimpleTrigger(datetime.now()), lambda: None, [], None, 1, False) - - -@raises(ValueError) -def test_create_job_invalid_misfire(): - Job(SimpleTrigger(datetime.now()), lambda: None, [], {}, 0, False) - - -@raises(ValueError) -def test_create_job_invalid_maxruns(): - Job(SimpleTrigger(datetime.now()), lambda: None, [], {}, 1, False, max_runs=0) - - -@raises(ValueError) -def test_create_job_invalid_maxinstances(): - Job(SimpleTrigger(datetime.now()), lambda: None, [], {}, 1, False, max_instances=0) diff --git a/tests/testjobstores.py b/tests/testjobstores.py index d57ae11..551348c 100644 --- a/tests/testjobstores.py +++ b/tests/testjobstores.py @@ -8,7 +8,7 @@ from nose.plugins.skip import SkipTest from apscheduler.jobstores.ram_store import RAMJobStore from apscheduler.jobstores.base import JobStore -from apscheduler.triggers import SimpleTrigger +from apscheduler.triggers.date import DateTrigger from apscheduler.job import Job try: @@ -48,8 +48,8 @@ class JobStoreTestBase(object): def setup(self): self.trigger_date = datetime(2999, 1, 1) self.earlier_date = datetime(2998, 12, 31) - self.trigger = SimpleTrigger(self.trigger_date) - self.job = Job(self.trigger, dummy_job, [], {}, 1, False) + self.trigger = DateTrigger(self.trigger_date) + self.job = Job(self.trigger, dummy_job, [], {}, 1, False, None, None, 1) self.job.next_run_time = self.trigger_date def test_jobstore_add_update_remove(self): @@ -76,9 +76,9 @@ class JobStoreTestBase(object): class PersistentJobstoreTestBase(JobStoreTestBase): def test_one_job_fails_to_load(self): global dummy_job2, dummy_job_temp - job1 = Job(self.trigger, dummy_job, [], {}, 1, False) - job2 = Job(self.trigger, dummy_job2, [], {}, 1, False) - job3 = Job(self.trigger, dummy_job3, [], {}, 1, False) + job1 = Job(self.trigger, dummy_job, [], {}, 1, False, None, None, 1) + job2 = Job(self.trigger, dummy_job2, [], {}, 1, False, None, None, 1) + job3 = Job(self.trigger, dummy_job3, [], {}, 1, False, None, None, 1) for job in job1, job2, job3: job.next_run_time = self.trigger_date self.jobstore.add_job(job) diff --git a/tests/testscheduler.py b/tests/testscheduler.py index a23dafb..dd5679a 100644 --- a/tests/testscheduler.py +++ b/tests/testscheduler.py @@ -32,12 +32,12 @@ class TestOfflineScheduler(object): self.scheduler.add_jobstore(RAMJobStore(), 'dummy') def test_add_tentative_job(self): - job = self.scheduler.add_date_job(lambda: None, datetime(2200, 7, 24), jobstore='dummy') + job = self.scheduler.add_job(lambda: None, 'date', [datetime(2200, 7, 24)], jobstore='dummy') assert isinstance(job, Job) eq_(self.scheduler.get_jobs(), []) def test_add_job_by_reference(self): - job = self.scheduler.add_date_job('copy:copy', datetime(2200, 7, 24)) + job = self.scheduler.add_job('copy:copy', 'date', [datetime(2200, 7, 24)]) eq_(job.func, copy) eq_(job.func_ref, 'copy:copy') @@ -82,7 +82,7 @@ class TestOfflineScheduler(object): def test_pending_jobs(self): # Tests that pending jobs are properly added to the jobs list when # the scheduler is started (and not before!) - self.scheduler.add_date_job(lambda: None, datetime(9999, 9, 9)) + self.scheduler.add_job(lambda: None, 'date', [datetime(9999, 9, 9)]) eq_(self.scheduler.get_jobs(), []) self.scheduler.start() @@ -138,7 +138,7 @@ class TestJobExecution(object): def my_job(): pass - job = self.scheduler.add_interval_job(my_job, start_date=datetime(2010, 5, 19)) + job = self.scheduler.add_job(my_job, 'interval', {'start_date': datetime(2010, 5, 19)}) eq_(repr(job), '<Job (name=my_job, trigger=<IntervalTrigger (interval=datetime.timedelta(0, 1), ' 'start_date=datetime.datetime(2010, 5, 19, 0, 0))>)>') @@ -153,7 +153,7 @@ class TestJobExecution(object): self.val += 1 a = A() - job = self.scheduler.add_interval_job(a, seconds=1) + job = self.scheduler.add_job(a, 'interval', {'seconds': 1}) self.scheduler._process_jobs(job.next_run_time) self.scheduler._process_jobs(job.next_run_time) eq_(a.val, 2) @@ -168,7 +168,7 @@ class TestJobExecution(object): self.val += 1 a = A() - job = self.scheduler.add_interval_job(a.method, seconds=1) + job = self.scheduler.add_job(a.method, 'interval', {'seconds': 1}) self.scheduler._process_jobs(job.next_run_time) self.scheduler._process_jobs(job.next_run_time) eq_(a.val, 2) @@ -178,7 +178,7 @@ class TestJobExecution(object): vals[0] += 1 vals = [0] - job = self.scheduler.add_cron_job(increment) + job = self.scheduler.add_job(increment, 'cron') self.scheduler._process_jobs(job.next_run_time) eq_(vals[0], 1) self.scheduler.unschedule_job(job) @@ -193,9 +193,9 @@ class TestJobExecution(object): vals[0] += 1 vals = [0] - job1 = self.scheduler.add_cron_job(increment) - job2 = self.scheduler.add_cron_job(increment2) - job3 = self.scheduler.add_cron_job(increment) + job1 = self.scheduler.add_job(increment, 'cron') + job2 = self.scheduler.add_job(increment2, 'cron') + job3 = self.scheduler.add_job(increment, 'cron') eq_(self.scheduler.get_jobs(), [job1, job2, job3]) self.scheduler.unschedule_func(increment) @@ -210,7 +210,7 @@ class TestJobExecution(object): vals[0] += 1 vals = [0] - job = self.scheduler.add_interval_job(increment, max_runs=1) + job = self.scheduler.add_job(increment, 'interval', max_runs=1) self.scheduler._process_jobs(job.next_run_time) eq_(vals, [1]) assert job not in self.scheduler.get_jobs() @@ -219,16 +219,16 @@ class TestJobExecution(object): def failure(): raise DummyException - job = self.scheduler.add_date_job(failure, datetime(9999, 9, 9)) + job = self.scheduler.add_job(failure, 'date', [datetime(9999, 9, 9)]) self.scheduler._process_jobs(job.next_run_time) assert 'DummyException' in self.logstream.getvalue() def test_misfire_grace_time(self): self.scheduler.misfire_grace_time = 3 - job = self.scheduler.add_interval_job(lambda: None, seconds=1) + job = self.scheduler.add_job(lambda: None, 'interval', {'seconds': 1}) eq_(job.misfire_grace_time, 3) - job = self.scheduler.add_interval_job(lambda: None, seconds=1, misfire_grace_time=2) + job = self.scheduler.add_job(lambda: None, 'interval', {'seconds': 1}, misfire_grace_time=2) eq_(job.misfire_grace_time, 2) def test_coalesce_on(self): @@ -241,8 +241,8 @@ class TestJobExecution(object): events = [] scheduler.datetime = FakeDateTime self.scheduler.add_listener(events.append, EVENT_JOB_EXECUTED | EVENT_JOB_MISSED) - job = self.scheduler.add_interval_job(increment, seconds=1, start_date=FakeDateTime.now(), coalesce=True, - misfire_grace_time=2) + job = self.scheduler.add_job(increment, 'interval', {'seconds': 1, 'start_date': FakeDateTime.now()}, + coalesce=True, misfire_grace_time=2) # Turn the clock 14 seconds forward FakeDateTime._now += timedelta(seconds=2) @@ -263,8 +263,8 @@ class TestJobExecution(object): events = [] scheduler.datetime = FakeDateTime self.scheduler.add_listener(events.append, EVENT_JOB_EXECUTED | EVENT_JOB_MISSED) - job = self.scheduler.add_interval_job(increment, seconds=1, start_date=FakeDateTime.now(), coalesce=False, - misfire_grace_time=2) + job = self.scheduler.add_job(increment, 'interval', {'seconds': 1, 'start_date': FakeDateTime.now()}, + coalesce=False, misfire_grace_time=2) # Turn the clock 2 seconds forward FakeDateTime._now += timedelta(seconds=2) @@ -283,13 +283,13 @@ class TestJobExecution(object): vals[1] += 1 vals = [0, 0] - job = self.scheduler.add_interval_job(increment, seconds=1, args=[2]) + job = self.scheduler.add_job(increment, 'interval', {'seconds': 1}, args=[2]) self.scheduler._process_jobs(job.next_run_time) self.scheduler._process_jobs(job.next_run_time) eq_(vals, [4, 2]) def test_interval_schedule(self): - @self.scheduler.interval_schedule(seconds=1) + @self.scheduler.scheduled_job('interval', {'seconds': 1}) def increment(): vals[0] += 1 @@ -305,7 +305,7 @@ class TestJobExecution(object): vals[1] += 1 vals = [0, 0] - job = self.scheduler.add_cron_job(increment, args=[3]) + job = self.scheduler.add_job(increment, 'cron', args=[3]) start = job.next_run_time self.scheduler._process_jobs(start) eq_(vals, [3, 1]) @@ -315,7 +315,7 @@ class TestJobExecution(object): eq_(vals, [9, 3]) def test_cron_schedule_1(self): - @self.scheduler.cron_schedule() + @self.scheduler.scheduled_job('cron') def increment(): vals[0] += 1 @@ -326,7 +326,7 @@ class TestJobExecution(object): eq_(vals[0], 2) def test_cron_schedule_2(self): - @self.scheduler.cron_schedule(minute='*') + @self.scheduler.scheduled_job('cron', {'minute': '*'}) def increment(): vals[0] += 1 @@ -344,7 +344,7 @@ class TestJobExecution(object): vals = [] date = datetime.now() + timedelta(seconds=1) - self.scheduler.add_date_job(append_val, date, kwargs={'value': 'test'}) + self.scheduler.add_job(append_val, 'date', [date], kwargs={'value': 'test'}) self.scheduler._process_jobs(date) eq_(vals, ['test']) @@ -355,7 +355,7 @@ class TestJobExecution(object): ' No scheduled jobs%s' % (os.linesep, os.linesep) eq_(out.getvalue(), expected) - self.scheduler.add_date_job(copy, datetime(2200, 5, 19)) + self.scheduler.add_job(copy, 'date', [datetime(2200, 5, 19)]) out = StringIO() self.scheduler.print_jobs(out) expected = 'Jobstore default:%s '\ @@ -365,7 +365,7 @@ class TestJobExecution(object): def test_jobstore(self): self.scheduler.add_jobstore(RAMJobStore(), 'dummy') - job = self.scheduler.add_date_job(lambda: None, datetime(2200, 7, 24), jobstore='dummy') + job = self.scheduler.add_job(lambda: None, 'date', [datetime(2200, 7, 24)], jobstore='dummy') eq_(self.scheduler.get_jobs(), [job]) self.scheduler.remove_jobstore('dummy') eq_(self.scheduler.get_jobs(), []) @@ -381,9 +381,8 @@ class TestJobExecution(object): vars = [0] scheduler.datetime = FakeDateTime - job = self.scheduler.add_interval_job( - increment, seconds=1, misfire_grace_time=3, - start_date=FakeDateTime.now()) + job = self.scheduler.add_job(increment, 'interval', {'seconds': 1, 'start_date': FakeDateTime.now()}, + misfire_grace_time=3) start = job.next_run_time self.scheduler._process_jobs(start) diff --git a/tests/testtriggers.py b/tests/testtriggers.py index 86cc64f..875d714 100644 --- a/tests/testtriggers.py +++ b/tests/testtriggers.py @@ -1,8 +1,10 @@ -from datetime import datetime, timedelta +from datetime import datetime from nose.tools import eq_, raises -from apscheduler.triggers import CronTrigger, SimpleTrigger, IntervalTrigger +from apscheduler.triggers.cron import CronTrigger +from apscheduler.triggers.date import DateTrigger +from apscheduler.triggers.interval import IntervalTrigger def test_cron_trigger_1(): @@ -140,8 +142,8 @@ def test_cron_bad_kwarg(): def test_date_trigger_earlier(): fire_date = datetime(2009, 7, 6) - trigger = SimpleTrigger(fire_date) - eq_(repr(trigger), "<SimpleTrigger (run_date=datetime.datetime(2009, 7, 6, 0, 0))>") + trigger = DateTrigger(fire_date) + eq_(repr(trigger), "<DateTrigger (run_date=datetime.datetime(2009, 7, 6, 0, 0))>") eq_(str(trigger), "date[2009-07-06 00:00:00]") start_date = datetime(2008, 12, 1) eq_(trigger.get_next_fire_time(start_date), fire_date) @@ -149,20 +151,20 @@ def test_date_trigger_earlier(): def test_date_trigger_exact(): fire_date = datetime(2009, 7, 6) - trigger = SimpleTrigger(fire_date) + trigger = DateTrigger(fire_date) start_date = datetime(2009, 7, 6) eq_(trigger.get_next_fire_time(start_date), fire_date) def test_date_trigger_later(): fire_date = datetime(2009, 7, 6) - trigger = SimpleTrigger(fire_date) + trigger = DateTrigger(fire_date) start_date = datetime(2009, 7, 7) eq_(trigger.get_next_fire_time(start_date), None) def test_date_trigger_text(): - trigger = SimpleTrigger('2009-7-6') + trigger = DateTrigger('2009-7-6') start_date = datetime(2009, 7, 6) eq_(trigger.get_next_fire_time(start_date), datetime(2009, 7, 6)) @@ -174,9 +176,7 @@ def test_interval_invalid_interval(): class TestInterval(object): def setUp(self): - interval = timedelta(seconds=1) - trigger_start_date = datetime(2009, 8, 4, second=2) - self.trigger = IntervalTrigger(interval, trigger_start_date) + self.trigger = IntervalTrigger(seconds=1, start_date=datetime(2009, 8, 4, second=2)) def test_interval_repr(self): eq_(repr(self.trigger), |