NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #540 most downloaded on PyPI
In-process task scheduler with Cron-like capabilities
Last release 3 months ago
28 Jun 2026
Ships fairly regularly
a new release about every 5 months
Most releases are documented
notes for 40 of 47 stable releases
3 versions withdrawn
withdrawn after publishing
17 years old
59 releases · first in 2009
BREAKING Changed the cleanup() data store method to also be responsible for releasing jobs whose leases have expired (so the schedulers responsible fo…
AsyncpgEventBroker to directly accept a connection string, thus eliminating the need for the AsyncpgEventBroker.from_dsn() class methodextend_acquired_schedule_leases() data store method to prevent other schedulers from acquiring schedules already being processed by a scheduler, if that's taking unexpectedly long for some reasonextend_acquired_job_leases() data store method to prevent jobs from being cleaned up as if they had been abandoned (#864)cleanup() data store method to also be responsible for releasing jobs whose leases have expired (so the schedulers responsible for them have probably died)Task and Schedule classes to be read-onlyrelease_schedules() data store method to take a sequence of ScheduleResult instances instead of a sequence of schedules, to enable the memory data store to handle schedule updates more efficientlylock_expiration_delay parameter with a new scheduler-level parameter, lease_duration which is then used to call the various data store methodsjob_result_expiration_time field to the Schedule class, to allow the job results from scheduled jobs to stay around for some time (#927)created_at job field, so acquiring jobs would be faster when there are a lot of themjob_executor and max_running_jobs parameters from add_schedule() and add_run_job() (explicitly configure the task using configure_task() or by using the new @task decoratordefault_job_executor scheduler parameter with a more comprehensive task_defaults parameter@task decorator for specifying task configuration parameters bound to a functiontimezone argument to CronTrigger.from_crontab() into a keyword-only argumentmetadata field to tasks, schedules and jobslast_fire_time in datastore implementations (PR by @hlobit)reap_abandoned_jobs() abstract method to DataStore which the scheduler calls before processing any jobs in order to immediately mark jobs left in an acquired state when the scheduler crashedstart_time and end_time arguments to CronTrigger.from_crontab() (#676)psycopg event brokerSQLAlchemyDatastore and MongoDBDataStorelock_expiration_delay parameter of built-in data stores to accept a timedelta as well as int or floatCronTrigger when pausing a schedule (#864)TypeError: object NoneType can't be used in 'await' expression at teardown of SQLAlchemyDataStore when it was passed a URL that implicitly created a synchronous engineSerializationError and DeserializationError as appropriaterepr() outputs of schedulers, data stores and event brokers to be much more useful and reasonableMongoDBDataStore that allowed multiple schedulers to acquire the same schedules at onceSQLAlchemyDataStore to automatically create the explicitly specified schema if it's missing (PR by @zhu0629)CronTrigger infinitely looping to get next date when DST ends (#980; PR by @hlobit)add_job() overwriting task configuration (PR by @mattewid)One column per quarter.
BREAKING Added the cleanup() scheduler method and a configuration option ( cleanup_interval ). A corresponding abstract method was added to the DataSt
cleanup() scheduler method and a configuration option (cleanup_interval). A corresponding abstract method was added to the DataStore class. This method purges expired job results and schedules that have exhausted their triggers and have no more associated jobs running. Previously, schedules were automatically deleted instantly once their triggers could no longer produce any fire times.JobReleased events the responsibility of the DataStore implementation, rather than the scheduler, for consistency with the acquire_jobs() methodstarted_at field was moved from Job to JobResultfrom_url() class methods of SQLAlchemyDataStore, MongoDBDataStore and RedisEventBroker in favor of the ability to pass a connection url to the initializerscheduled_start field to the JobAcquired eventscheduled_start and started_at fields to the JobReleased eventMongoDBDataStore still calling blocking functions in the event loop threadmax_concurrent_jobs had been reached and then a job was completed, thus making job processing possible again (PR by MohammadAmin Vahedinia)SQLAlchemyDataStore not respecting custom schema name when creating enumsAndTrigger (#911 <#911>_; PR by Bennett Meares)BREAKING Renamed any leftover fields named executor to job_executor (this breaks data store compatibility)
executor to job_executor (this breaks data store compatibility)worker_id field to scheduler_id in the JobAcquired and JobReleased eventstask_id attribute to the ScheduleAdded, ScheduleUpdated and ScheduleRemoved eventsfinished attribute to the ScheduleRemoved eventlogger parameter to Datastore.start() and EventBroker.start() to make both use the scheduler's assigned loggerapscheduler.marshalling module privateconfigure_task() and get_tasks() scheduler methodsBREAKING Changed the initialization of data stores and event brokers to use a single start() method that accepts an AsyncExitStack (and, depending on…
apscheduler package (apscheduler.Scheduler and apscheduler.AsyncScheduler)JobInfo class in favor of just using the Job class (which is now immutable)Worker and AsyncWorker classes have been removed, you now need to pass role=SchedulerRole.scheduler to the scheduler to prevent it from processing due jobs. The worker event classes (WorkerEvent, WorkerStarted, WorkerStopped) have also been removed.anyio.to_thread.run_sync().current_worker context variable has been removedcurrent_scheduler context variable is now specified to only contain the currently running instance of a synchronous scheduler (apscheduler.Scheduler). The asynchronous scheduler instance can be fetched from the new current_async_scheduler context variable, and will always be available when a scheduler is running in the current context, while current_scheduler is only available when the synchronous wrapper is being run.start() method that accepts an AsyncExitStack (and, depending on the interface, other arguments too)job_executor. This addition requires database schema changes too.processpool executorqt executorget_jobs() scheduler methodAsyncSchedulerschema parameter in SQLAlchemyDataStore not being appliedNothing published for this version
Nothing published for this version
Fixed sub-minute interval jobs stalling for the duration of a DST spring-forward gap when the scheduler was configured with a ZoneInfo time zone, caus
ZoneInfo time zone, caused by the wakeup delay being computed from the naive wall-clock difference instead of the actual UTC difference (#1103)Fixed an issue where a job using a CronTrigger scheduled in a repeated time interval during DST transitions could cause the scheduler to get stuck in
CronTrigger scheduled in a repeated time interval during DST transitions could cause the scheduler to get stuck in an infinite loop (#1021; PR by @SoulofAkuma)Fixed scheduler.shutdown() not raising SchedulerNotRunning (or raising the wrong exception) for asynchronous schedulers when the scheduler is in fact
scheduler.shutdown() not raising SchedulerNotRunning (or raising the wrong exception) for asynchronous schedulers when the scheduler is in fact not runningCronTrigger sticking on a folded datetime during the fall-back DST transition (#1021 <#1021>_; PR by @berianjames)`Added support for ZoneInfo time zones and deprecated support for pytz time zones
ZoneInfo time zones and deprecated support for pytz time zonesCalendarIntervalTrigger, backported from the 4.x seriesscheduler.export_jobs() and scheduler.import_jobs(), respectivelysixProcessPoolExecutor to spawn new subprocesses from scratch instead of forking on all platformAsyncIOScheduler inadvertently creating a defunct event loop at start, leading to the scheduler not working at allProcessPoolExecutor not respecting the passed keyword arguments when a broken pool was being replacedAdded the release version
Added the release version
Fixed incorrect module name being generated for references to class methods via a subclass where the subclass does not override the method
Added changelog note for #773
Added changelog note for #773
Fixed TypeError related to entry point iteration on Python 3.9 (PR by CrypticDriver)
- Replaced uses of the deprecated pkg_resources module with importlib.metadata (PR by Ran Benita)
Ensured consistent support for both PySide and PyQt (v6 to v2) on QtScheduler
Replaced uses of the deprecated pkg_resources module with importlib.metadata (PR by Ran Benita)
Fixed scheduling class methods like B.methodname where the B class inherits from class A and methodname is a class method of class A
Fixed scheduler sleeping for too long (and thus jobs missing their scheduled run times) if its wakeup cycle takes too much time (fix by kbasten)
- Fixed TypeError: arguments did not match any overloaded call: in QtScheduler due to wait_time being float and not an integer which QT needs it to be
Fixed TypeError: arguments did not match any overloaded call: in QtScheduler due to wait_time being float and not an integer which QT needs it to be
- Fixed compatibility with SQLAlchemy 2.0 and bumped minimum supported version to 1.4
Fixed compatibility with SQLAlchemy 2.0 and bumped minimum supported version to 1.4
Dropped support for Python versions older than 3.6
Nothing published for this version
- Removed a leftover check for pytz localize() and normalize() methods
Removed a leftover check for pytz localize() and normalize() methods
Nothing published for this version
Nothing published for this version
- Fixed pytz deprecation warnings
Added support for PySide6 to the Qt scheduler
No longer enforce pytz time zones (support for others is experimental in the 3.x series)
Fixed compatibility with PyMongo 4
Fixed pytz deprecation warnings
Fixed RuntimeError when shutting down the scheduler from a scheduled job
- Allowed the use of tzlocal v4.0+ in addition to v2.*
Allowed the use of tzlocal v4.0+ in addition to v2.*
- Allowed passing through keyword arguments to the underlying stdlib executors in the thread/process pool executors (PR by Albert Xu)
Allowed passing through keyword arguments to the underlying stdlib executors in the thread/process pool executors (PR by Albert Xu)
- Fixed deprecation warnings on the MongoDB job store and increased the minimum PyMongo
Dropped support for Python 3.4
Added PySide2 support (PR by Abdulla Ibrahim)
Pinned tzlocal to a version compatible with pytz
Ensured that jitter is always non-negative to prevent triggers from firing more often than intended
Changed AsyncIOScheduler to obtain the event loop in start() instead of init() , to prevent situations where the scheduler won’t run because it’s using a different event loop than then one currently running
Made it possible to create weak references to Job instances
Made the schedulers explicitly raise a descriptive TypeError when serialization is attempted
Fixed Zookeeper job store using backslashes instead of forward slashes for paths on Windows (PR by Laurel-rao)
Fixed deprecation warnings on the MongoDB job store and increased the minimum PyMongo
- Fixed Python 2.7 accidentally depending on the trollius package (regression from v3.6.2)
Fixed Python 2.7 accidentally depending on the trollius package (regression from v3.6.2)
- Fixed handling of partial() wrapped coroutine functions in AsyncIOExecutor and TornadoExecutor (PR by shipmints)
Fixed handling of partial() wrapped coroutine functions in AsyncIOExecutor and TornadoExecutor (PR by shipmints)
- Fixed OverflowError on Qt scheduler when the wait time is very long
Fixed OverflowError on Qt scheduler when the wait time is very long
Fixed methods inherited from base class could not be executed by processpool executor (PR by Yang Jian)
- Fixed DeprecationWarnings about collections.abc on Python 3.7 (PR by Roman Levin)
Adapted RedisJobStore to v3.0 of the redis library
Adapted RethinkDBJobStore to v2.4 of the rethink library
Fixed DeprecationWarnings about collections.abc on Python 3.7 (PR by Roman Levin)
- Fixed regression introduced in 3.5.2: Class methods were mistaken for instance methods and thus were broken during serialization
Fixed regression introduced in 3.5.2: Class methods were mistaken for instance methods and thus were broken during serialization
Fixed callable name detection for methods in old style classes
- Fixed scheduling of bound methods on persistent job stores (the workaround of scheduling YourClass.methodname along with an explicit self argument i
Fixed scheduling of bound methods on persistent job stores (the workaround of scheduling YourClass.methodname along with an explicit self argument is no longer necessary as this is now done automatically for you)
Added the FAQ section to the docs
Made BaseScheduler.start() raise a RuntimeError if running under uWSGI with threads disabled
- Fixed OverflowError on Windows when the wait time is too long
Fixed OverflowError on Windows when the wait time is too long
Fixed CronTrigger sometimes producing fire times beyond end_date when jitter is enabled (thanks to gilbsgilbs for the tests)
Fixed ISO 8601 UTC offset information being silently discarded from string formatted datetimes by adding support for parsing them
- Added the engine_options option to SQLAlchemyJobStore
Added the engine_options option to SQLAlchemyJobStore
Added the jitter options to IntervalTrigger and CronTrigger (thanks to gilbsgilbs)
Added combining triggers ( AndTrigger and OrTrigger )
Added better validation for the steps and ranges of different expressions in CronTrigger
Added support for named months ( jan – dec ) in CronTrigger month expressions
Added support for creating a CronTrigger from a crontab expression
Allowed spaces around commas in CronTrigger fields
Fixed memory leak due to a cyclic reference when jobs raise exceptions (thanks to gilbsgilbs for help on solving this)
Fixed passing wait=True to AsyncIOScheduler.shutdown() (although it doesn’t do much)
Cancel all pending futures when AsyncIOExecutor is shut down
- Dropped support for Python 3.3
Dropped support for Python 3.3
Added the ability to specify the table schema for SQLAlchemyJobStore (thanks to Meir Tseitlin)
Added a workaround for the ImportError when used with PyInstaller and the likes (caused by the missing packaging metadata when APScheduler is packaged with these tools)
- Fixed Python 2.7 compatibility in TornadoExecutor
Fixed Python 2.7 compatibility in TornadoExecutor
- The asyncio and Tornado schedulers can now run jobs targeting coroutine functions (requires Python 3.5; only native coroutines ( async def ) are sup
The asyncio and Tornado schedulers can now run jobs targeting coroutine functions (requires Python 3.5; only native coroutines ( async def ) are supported)
The Tornado scheduler now uses TornadoExecutor as its default executor (see above as for why)
Added ZooKeeper job store (thanks to Jose Ignacio Villar for the patch)
Fixed job store failure ( get_due_jobs() ) causing the scheduler main loop to exit (it now waits a configurable number of seconds before retrying)
Fixed @scheduled_job not working when serialization is required (persistent job stores and ProcessPoolScheduler )
Improved import logic in ref_to_obj() to avoid errors in cases where traversing the path with getattr() would not work (thanks to Jarek Glowacki for the patch)
Fixed CronTrigger’s weekday position expressions failing on Python 3
Fixed CronTrigger’s range expressions sometimes allowing values outside the given range
- Added the ability to pause and unpause the scheduler
Added the ability to pause and unpause the scheduler
Fixed pickling problems with persistent jobs when upgrading from 3.0.x
Fixed AttributeError when importing apscheduler with setuptools < 11.0
Fixed some events missing from apscheduler.events.all and apscheduler.events.EVENTS_ALL
Fixed wrong run time being set for date trigger when the timezone isn’t the same as the local one
Fixed builtin id() erroneously used in MongoDBJobStore’s JobLookupError()
- Deprecated the various version related variables in the apscheduler module ( apscheduler.version_info , apscheduler.version , apscheduler.release ,…
Added RethinkDB job store (contributed by Allen Sanabria)
Added method chaining to the modify_job() , reschedule_job() , pause_job() and resume_job() methods in BaseScheduler and the corresponding methods in the Job class
Added the EVENT_JOB_SUBMITTED event that indicates a job has been submitted to its executor.
Added the EVENT_JOB_MAX_INSTANCES event that indicates a job’s execution was skipped due to its maximum number of concurrently running instances being reached
Added the time zone to the repr() output of CronTrigger and IntervalTrigger
Fixed rare race condition on scheduler shutdown()
Dropped official support for CPython 2.6 and 3.2 and PyPy3
Moved the connection logic in database backed job stores to the start() method
Migrated to setuptools_scm for versioning
Deprecated the various version related variables in the apscheduler module ( apscheduler.version_info , apscheduler.version , apscheduler.release , apscheduler.version )
- Fixed bug in the cron trigger that produced off-by-1-hour datetimes when crossing the daylight saving threshold (thanks to Tim Strazny for reporting
Fixed bug in the cron trigger that produced off-by-1-hour datetimes when crossing the daylight saving threshold (thanks to Tim Strazny for reporting)
- Fixed cron trigger always coalescing missed run times into a single run time (contributed by Chao Liu)
Fixed cron trigger always coalescing missed run times into a single run time (contributed by Chao Liu)
Fixed infinite loop in the cron trigger when an out-of-bounds value was given in an expression
Fixed debug logging displaying the next wakeup time in the UTC timezone instead of the scheduler’s configured timezone
Allowed unicode function references in Python 2
- Fixed memory leak in the base executor class (contributed by Stefan Nordhausen)
Fixed memory leak in the base executor class (contributed by Stefan Nordhausen)
- Fixed compatibility with pymongo 3.0
Fixed compatibility with pymongo 3.0
- Fixed ValueError when the target callable has a default keyword argument that wasn’t overridden
Fixed ValueError when the target callable has a default keyword argument that wasn’t overridden
Fixed wrong job sort order in some job stores
Fixed exception when loading all jobs from the redis job store when there are paused jobs in it
Fixed AttributeError when printing a job list when there were pending jobs
Added setuptools as an explicit requirement in install requirements
- A wider variety of target callables can now be scheduled so that the jobs are still serializable (static methods on Python 3.3+, unbound methods on
A wider variety of target callables can now be scheduled so that the jobs are still serializable (static methods on Python 3.3+, unbound methods on all except Python 3.2)
Attempting to serialize a non-serializable Job now raises a helpful exception during serialization. Thanks to Jeremy Morgan for pointing this out.
Fixed table creation with SQLAlchemyJobStore on MySQL/InnoDB
Fixed start date getting set too far in the future with a timezone different from the local one
Fixed _run_job_error() being called with the incorrect number of arguments in most executors
- Added support for timezones (special thanks to Curtis Vogt for help with this one)
Added support for timezones (special thanks to Curtis Vogt for help with this one)
Split the old Scheduler class into BlockingScheduler and BackgroundScheduler and added integration for asyncio (PEP 3156), Gevent, Tornado, Twisted and Qt event loops
Overhauled the job store system for much better scalability
Added the ability to modify, reschedule, pause and resume jobs
Dropped the Shelve job store because it could not work with the new job store system
Dropped the max_runs option and run counting of jobs since it could not be implemented reliably
Adding jobs is now done exclusively through add_job() – the shortcuts to triggers were removed
Added the end_date parameter to cron and interval triggers
It is now possible to add a job directly to an executor without scheduling, by omitting the trigger argument
Replaced the thread pool with a pluggable executor system
Added support for running jobs in subprocesses (via the processpool executor)
Switched from nose to py.test for running unit tests
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
- Added a “standalone” mode that runs the scheduler in the calling thread
Added Redis job store
Added a “standalone” mode that runs the scheduler in the calling thread
Fixed disk synchronization in ShelveJobStore
Switched to PyPy 1.9 for PyPy compatibility testing
Dropped Python 2.4 support
Fixed SQLAlchemy 0.8 compatibility in SQLAlchemyJobStore
Various documentation improvements
- The scheduler now closes the job store that is being removed, and all job stores on shutdown() by default
The scheduler now closes the job store that is being removed, and all job stores on shutdown() by default
Added the last expression in the day field of CronTrigger (thanks rcaselli)
Raise a TypeError when fields with invalid names are passed to CronTrigger (thanks Christy O’Reilly)
Fixed the persistent.py example by shutting down the scheduler on Ctrl+C
Added PyPy 1.8 and CPython 3.3 to the test suite
- Removed the unique constraint from the “name” column in the SQLAlchemy job store
Removed the unique constraint from the “name” column in the SQLAlchemy job store
Fixed output from Scheduler.print_jobs() which did not previously output a line ending at the end
- Fixed cron style jobs getting wrong default values
Fixed cron style jobs getting wrong default values
- Added configurable job stores with several persistent back-ends (shelve, SQLAlchemy and MongoDB)
Added configurable job stores with several persistent back-ends (shelve, SQLAlchemy and MongoDB)
Added the possibility to listen for job events (execution, error, misfire, finish) on a scheduler
Added an optional start time for cron-style jobs
Added optional job execution coalescing for situations where several executions of the job are due
Added an option to limit the maximum number of concurrently executing instances of the job
Allowed configuration of misfire grace times on a per-job basis
Allowed jobs to be explicitly named
All triggers now accept dates in string form (YYYY-mm-dd HH:MM:SS)
Jobs are now run in a thread pool; you can either supply your own PEP 3148 compliant thread pool or let APScheduler create its own
Maximum run count can be configured for all jobs, not just those using interval-based scheduling
Fixed a v1.x design flaw that caused jobs to be executed twice when the scheduler thread was woken up while still within the allowable range of their previous execution time (issues #5, #7)
Changed defaults for cron-style jobs to be more intuitive – it will now default to all minimum values for fields lower than the least significant explicitly defined field
- Fixed time difference calculation to take into account shifts to and from daylight saving time
Fixed time difference calculation to take into account shifts to and from daylight saving time
Nothing published for this version
- Fixed regression: add_cron_job() in Scheduler was creating a CronTrigger with the wrong parameters (fixes #1, #3)
Fixed regression: add_cron_job() in Scheduler was creating a CronTrigger with the wrong parameters (fixes #1, #3)
Fixed: if the scheduler is restarted, clear the “stopped” flag to allow jobs to be scheduled again
Nothing published for this version
- Fixed a corner case where the combination of hour and day_of_week parameters would cause incorrect timing for a cron trigger
Fixed a corner case where the combination of hour and day_of_week parameters would cause incorrect timing for a cron trigger
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →