Package: django-apscheduler
Django APSchedulerAPSchedulerforDjango.This is a Django app that adds a lightweight wrapper around APScheduler. It enables storing persistent jobs in the
database using Django's ORM.django-apscheduler is a great choice for quickly and easily adding basic scheduling features to your Django applications
with minimal dependencies and very little additional configuration. The ideal use case probably involves running a
handful of tasks on a fixed execution schedule.PLEASE NOTE:the trade-off of this simplicity is that you need tobe careful to ensure that you have only ONE
scheduler actively running at a particular point in time.This limitation is due to the fact that APScheduler does not currently have
anyinterprocess synchronization and signalling schemethat would enable the scheduler to be notified when a job has been added, modified, or removed from a job store (in
other words, different schedulers won't be able to tell if a job has already been run by another scheduler, and changing
a job's scheduled run time directly in the database does nothing unless you also restart the scheduler).Depending on how you are currently doing your
Djangodeployments, working with this
limitation might require a bit of thought. It is quite common to start up many webserver worker process in production
environments in order to scale and handle large volumes of user traffic. If each of these worker processes end up
running their own scheduler then this can result in jobs being missed or executed multiple times, as well as duplicate
entries being created in theDjangoJobExecutiontable.Support for sharing a persistent job store between multiple schedulers appears to be planned for
anupcoming APScheduler 4.0 release.So for now your options are to either:Use a custom Django management command to start a single scheduler in its own dedicated process (recommended-
see therunapscheduler.pyexample below); orImplement your
ownremote processinglogic to ensure that a singleDjangoJobStorecan be used by all of the webserver's worker processes in a
coordinated and synchronized way (might not be worth the extra effort and increased complexity for most use cases);
orSelect an alternative task processing library thatdoessupport inter-process communication using some sort of
shared message broker like Redis, RabbitMQ, Amazon SQS or the like (see:https://djangopackages.org/grids/g/workers-queues-tasks/for popular options).FeaturesA customAPScheduler job store(DjangoJobStore) that persists scheduled jobs to the Django database. You can view the scheduled jobs and monitor
the job execution directly via the Django admin interface:The job store also maintains a history of all job executions of the currently scheduled jobs, along with status codes
and exceptions (if any):Note:APScheduler
willautomatically remove jobsfrom the job store as soon as their last scheduled execution has been triggered. This will also delete the
corresponding job execution entries from the database (i.e. job execution logs are only maintained for 'active'
jobs.):Job executions can also be triggered manually via theDjangoJobadmin page:Note:In order to prevent long running jobs from causing the Django HTTP request to time out, the combined maximum
run time for all APScheduler jobs that are started via the Django admin site is 25 seconds. This timeout value can be
configured via theAPSCHEDULER_RUN_NOW_TIMEOUTsetting.Installationpipinstalldjango-apschedulerQuick startAdddjango_apschedulerto yourINSTALLED_APPSsetting like this:INSTALLED_APPS=(# ..."django_apscheduler",)django-apscheduler comes with sensible configuration defaults out of the box. The defaults can be overridden by adding
the following settings to your Djangosettings.pyfile:# Format string for displaying run time timestamps in the Django admin site. The default# just adds seconds to the standard Django format, which is useful for displaying the timestamps# for jobs that are scheduled to run on intervals of less than one minute.## See https://docs.djangoproject.com/en/dev/ref/settings/#datetime-format for format string# syntax details.APSCHEDULER_DATETIME_FORMAT="N j, Y, f:s a"# Maximum run time allowed for jobs that are triggered manually via the Django admin site, which# prevents admin site HTTP requests from timing out.## Longer running jobs should probably be handed over to a background task processing library# that supports multiple background worker processes instead (e.g. Dramatiq, Celery, Django-RQ,# etc. See: https://djangopackages.org/grids/g/workers-queues-tasks/ for popular options).APSCHEDULER_RUN_NOW_TIMEOUT=25# SecondsRunpython manage.py migrateto create the django_apscheduler models.Add acustom Django management commandto your project
that schedules the APScheduler jobs and starts the scheduler:# runapscheduler.pyimportloggingfromdjango.confimportsettingsfromapscheduler.schedulers.blockingimportBlockingSchedulerfromapscheduler.triggers.cronimportCronTriggerfromdjango.core.management.baseimportBaseCommandfromdjango_apscheduler.jobstoresimportDjangoJobStorefromdjango_apscheduler.modelsimportDjangoJobExecutionfromdjango_apschedulerimportutillogger=logging.getLogger(__name__)defmy_job():# Your job processing logic here...pass# The `close_old_connections` decorator ensures that database connections, that have become# unusable or are obsolete, are closed before and after your job has run. You should use it# to wrap any jobs that you schedule that access the Django database in any way.@util.close_old_connectionsdefdelete_old_job_executions(max_age=604_800):"""This job deletes APScheduler job execution entries older than `max_age` from the database.It helps to prevent the database from filling up with old historical records that are nolonger useful.:param max_age: The maximum length of time to retain historical job execution records.Defaults to 7 days."""DjangoJobExecution.objects.delete_old_job_executions(max_age)classCommand(BaseCommand):help="Runs APScheduler."defhandle(self,*args,**options):scheduler=BlockingScheduler(timezone=settings.TIME_ZONE)scheduler.add_jobstore(DjangoJobStore(),"default")scheduler.add_job(my_job,trigger=CronTrigger(second="*/10"),# Every 10 secondsid="my_job",# The `id` assigned to each job MUST be uniquemax_instances=1,replace_existing=True,)logger.info("Added job 'my_job'.")scheduler.add_job(delete_old_job_executions,trigger=CronTrigger(day_of_week="mon",hour="00",minute="00"),# Midnight on Monday, before start of the next work week.id="delete_old_job_executions",max_instances=1,replace_existing=True,)logger.info("Added weekly job: 'delete_old_job_executions'.")try:logger.info("Starting scheduler...")scheduler.start()exceptKeyboardInterrupt:logger.info("Stopping scheduler...")scheduler.shutdown()logger.info("Scheduler shut down successfully!")The management command defined above should be invoked via./manage.py runapschedulerwhenever the webserver serving
your Django application is started. The details of how and where this should be done is implementation specific, and
depends on which webserver you are using and how you are deploying your application to production. For most people
this should involve configuring asupervisorprocess of sorts.Register any APScheduler jobs as you would normally. Note that if you haven't setDjangoJobStoreas the'default'job store, then you will need to includejobstore='djangojobstore'in yourscheduler.add_job()calls.Advanced usagedjango-apscheduler assumes that you are already familiar with APScheduler and its proper use. If not, then please head
over to the project page and have a look through
theAPScheduler documentation.It is possible to make use
ofdifferent types of schedulersdepending on your environment and use case. If you would prefer running aBackgroundSchedulerinstead of using aBlockingScheduler, then you should be aware that using APScheduler with uWSGI requires some additionalconfiguration stepsin
order to re-enable threading support.Supported databasesPlease take note of the list of databases that
areofficially supported by Django. django-apscheduler
probably won't work with unsupported databases like Microsoft SQL Server, MongoDB, and the like.Database connections and timeoutsdjango-apscheduler is dependent on the standard Django
databaseconfiguration settings. These settings,
in combination with how your database server has been configured, determine how connection management will be performed
for your specific deployment.Theclose_old_connectionsdecorator should be applied to APScheduler jobs that require database access. Doing so
ensures that Django'sCONN_MAX_AGEconfiguration setting is enforced before and after your job is run. This mirrors the standard Django functionality of
doing the same before and after handling each HTTP request.If you still encounter any kind of 'lost database connection' errors then it probably means that:Your database connections timed out in the middle of executing a job. You should probably consider incorporating a
connection pooler as part of your deployment for more robust database connection management
(e.g.pgbouncerfor PostgreSQL, or the equivalent for other DB platforms).Your database server has crashed / been restarted.
Djangowill not reconnect automaticallyand you need to re-start django-apscheduler as well.Common footgunsUnless you have a very specific set of requirements, and have intimate knowledge of the inner workings of APScheduler,
you really shouldn't be usingBackgroundScheduler. Doing so can lead to all sorts of temptations like:Firing up a scheduler inside of a Django view:this will most likely cause more than one scheduler to run
concurrently and lead to jobs running multiple times (see the above introduction to this README for a more thorough
treatment of the subject).Bootstrapping a scheduler somewhere else inside your Django application: it feels like this should solve the
problem mentioned above and guarantee that only one scheduler is running. The downside is that you have just delegated
the management of all of your background task processing threads to whatever webserver you are using (Gunicorn, uWSGI,
etc.). The webserver will probably kill any long-running threads (your jobs) with extreme prejudice (thinking that
they are caused by misbehaving HTTP requests).Relying onBlockingSchedulerforces you to run APScheduler in its own dedicated process that is not handled or
monitored by the webserver. The example code provided inrunapscheduler.pyabove is a good starting point.Project resourcesChangelogRelease procedures