Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Abstract
3--------
5Backend (that is, server-side) jobs are a common necessity for many web-based applications. On POSIX systems, the cron
6job system is sometimes adequate, sometimes not so much.
8The scheduler app provides a solution for job scheduling that does not depend on cron while allowing app developers to
9define callbacks in Python that maybe configured to execute on a pre-determined schedule.
11Administrators or authorized users may optionally configure these jobs.
13Install
14-------
16Make sure a ``config/`` directory exists in project root.
18If automatic discovery of jobs is desired, add ``superdjango.contrib.scheduler.apps.AutoConfig`` to ``INSTALLED_APPS``.
19Otherwise, create a ``scheduler.ini`` file in the ``config/`` directory (see below).
21For automated discovery, the Python module name defaults to ``scheduler``, but may changed using the
22``SUPERDJANGO_SCHEDULER_MODULE_NAME`` setting.
24Usage
25-----
27There are two (2) means of using the scheduler:
291. Manually configuring jobs by creating an maintaining the ``scheduler.ini`` file.
302. Auto discovery of jobs.
32.. important::
33 Note that the included (concrete) models are only used when auto-discovery is enabled.
35Manually Configuring Jobs
36.........................
38Scheduled jobs are stored in a ``scheduler.ini`` file. This is assumed to be located in the ``config/`` directory of
39project root, but the location may be specified using the ``SUPERDJANGO_SCHEDULER_PATH`` setting.
41Each section of the INI represents a recurring job. The section name becomes the label of the job. Section parameters
42establish the attributes of the job to be performed. These are:
44- ``active``: Optional. ``yes`` (the default) or ``no``.
45- ``at``: Optional. The time of day to run the job in ``HH:MM`` (24 hour) format.
46- ``call``: Required. The callback to use for the job in dotted path format.
47- ``description``: Optional. A description may be provided to further explain or comment on the job.
48- ``every``: Required. Establishes the timing and interval in ``frequency interval`` format. For example:
49 ``every = 10 minutes``.
51The valid frequencies are:
53- second
54- seconds
55- minute
56- minutes
57- hour
58- hours
59- day
60- days
61- week
62- weeks
63- monday
64- tuesday
65- wednesday
66- thursday
67- friday
68- saturday
69- sunday
71.. tip::
72 ``at`` is only applied when specifying a weekday frequency.
74Additional parameters within the section become keyword arguments passed to the callback.
76Here are some examples:
78.. code-block:: ini
80 [run a test job]
81 call = example.scheduler.test_job
82 every = 5 seconds
83 testing = True
85 [this job should never make it to the scheduler]
86 call = example.scheduler.nonexistent_job
87 description = A nonexistent callback will not make it into the schedule.
88 every = 5 seconds
90 [this job is inactive]
91 active = no
92 call = example.automation.inactive_job
93 every = 10 minutes
95 [this job runs on sunday]
96 callback = example.scheduler.sunday_cleanup
97 frequency = sunday
98 at = 23:30
100Automatic Discovery of Jobs
101...........................
103The provided registry interface allows for any app in the project to report that it has one or more jobs that may be
104included in the scheduler.
106 1. To build a list of jobs in the database, run: ``./manage.py scheduler_discover_jobs``
107 2. Activate/deactivate jobs in the UI.
108 3. Export the ``scheduler.ini`` file: ``./manage.py scheduler_export_ini``
110See `Creating Job Callbacks`_ below.
112You may provide a user interface for automated discovery by registering the UI menu provided with the scheduler.
114.. code-block:: python
116 # glue/ui.py
117 from superdjango import ui
118 from superdjango.contrib.scheduler.ui import ScheduledJobsMenu
120 ui.site.register(ScheduledJobsMenu)
123Listing the Available Jobs
124..........................
126Regardless of manual or automatic discovery, the ``scheduler_list_jobs`` command will list the jobs found in the
127``scheduler.ini`` file.
129Setting Up Supervisor D
130.......................
132Some means of executing jobs is required. One way to do this is to set up `supervisord`_.
134.. _supervisord: http://supervisord.org
136.. tip::
137 You can use the management command: ``./manage.py scheduler_export_supervisord`` to create the configuration on the
138 server.
140This requires a supervisord configuration file which points to a shell script.
142.. code-block:: text
144 [program:superdjango_scheduler]
145 command=/path/to/project/cron/scheduler.sh
146 autostart=true
147 autorestart=true
148 stderr_logfile=/path/to/project/logs/scheduler.err.log
149 stdout_logfile=/path/to/project/logs/scheduler.out.log
151The shell script is needed to a) load the Python virtual environment, and b) execute the management commands which
152queues up the jobs. For example:
154.. code-block:: bash
156 #! /usr/bin/env bash
158 source /path/to/project/python/bin/active;
159 (cd /path/to/project/source && ./manage.py scheduler_run_jobs);
161Creating Job Callbacks
162......................
164A callback function is needed to execute a job.
166The function must accept ``**kwargs`` and return an instance of
167:py:class:`superdjango.contrib.scheduler.library.Result`.
169.. code-block:: python
171 # myapp/scheduler.py
172 from superdjango.contrib.scheduler import STATUS, Result
174 def do_something_on_a_recurring_basis(**kwargs):
175 # do some work ...
176 return Result(
177 STATUS.SUCCESS,
178 message="This may be displayed to a user.",
179 output="This may be displayed to administrators or developers."
180 )
182The label and start/end datetimes are automatically set at runtime.
184With manual configuration this callback would be added to the ``scheduler.ini`` file like so:
186.. code-block:: ini
188 [do something on a recurring basis]
189 callback = myapp.scheduler.do_something_on_a_recurring_basis
190 description = It's good practice to include a description, but it will default to the callback's docstring.
191 every = 5 minutes
193When auto-discovery is configured, you must also register the callback:
195.. code-block:: python
197 # myapp/scheduler.py
198 from superdjango.contrib.scheduler import STATUS, Result, schedule
200 def do_something_on_a_recurring_basis(**kwargs):
201 # do some work ...
202 return Result(
203 STATUS.SUCCESS,
204 message="This may be displayed to a user.",
205 output="This may be displayed to administrators or developers."
206 )
208 schedule.register("myapp", do_something_on_a_recurring_basis)
210Creating Activity Records
211.........................
213When auto-discovery is enabled, it is up to the developer to create the activity records for a given job.
215.. code-block:: python
217 from superdjango.contrib.scheduler import STATUS, Result, schedule
218 from superdjango.contrib.scheduler.models import Activity
220 def do_something_on_a_recurring_basis(**kwargs):
221 job_id = kwargs.pop("pk", None)
223 # do some work ...
225 result = Result(
226 STATUS.SUCCESS,
227 message="This may be displayed to a user.",
228 output="This may be displayed to administrators or developers."
229 )
231 if job_id:
232 Activity.log(job_id, result)
234 return result
236 schedule.register("myapp", do_something_on_a_recurring_basis)
238Setting Up the INI Export
239.........................
241A simple cron job may be used to set up the regular export of auto-configured the INI file:
243.. code-block:: bash
245 # /etc/cron.d/project_name
246 SHELL=/bin/bash
247 PATH=/path/to/project/python/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
249 # Times are in UTC. Starts at 4am EST.
250 0 8 * * 0 root /path/to/project/cron/export_scheduler_ini.sh
252And the script itself:
254.. code-block:: bash
256 #! /usr/bin/env bash
258 source /path/to/project/python/bin/active;
259 (cd /path/to/project/source && ./manage.py scheduler_run_jobs);
261Multi-Tenant Considerations for Scheduled Jobs
262..............................................
264In a multi-tenant environment, scheduled jobs may need to be specific to each tenant.
265`When using WSGI for tenant separation`_, there are fewer concerns with data separation, but a scheduler for each tenant
266must be maintained.
268.. _When using WSGI for tenant separation: https://superdjango.com/blog/mutli-tenancy-with-wsgi/
270To accomplish this, a separate ``scheduler.ini`` file is required for each tenant. We suggest creating this in the
271tenant's directory, such as ``/path/to/example_app/shared/tenants/acme_example_app/config/scheduler.ini``.
273With manual configuration, simply create or copy the file to the appropriate location.
275Or with auto configuration, this is easily accomplished using preview and bash output redirection:
277.. code-block:: bash
279 # from project root with virtual env activated
280 ./manage.py scheduler_export_ini \\
281 --settings=tenants.acme_example_app.settings \\
282 -p > tenants/acme_example_app/config/scheduler.ini;
284This assumed job discovery has already been performed with:
286.. code-block:: bash
288 ./manage.py scheduler_discover_jobs --settings=tenants.acme_example_app.settings;
290Listing jobs requires specifying the path, as in:
292.. code-block:: bash
294 ./manage.py scheduler_list_jobs -P tenants/acme_example_app/config/scheduler.ini;
296The supervisord configuration may be exported using a tenant-specific name:
298.. code-block:: bash
300 ./manage.py scheduler_export_supervisord \\
301 /path/to/example_app/shared/tenants/acme_example_app/cron/scheduler.sh \\
302 -w -n acme_example_app;
304And the cron script would look something like:
306.. code-block:: bash
308 #! /usr/bin/env bash
310 source /path/to/example_app/current/python/bin/active;
311 (cd /path/to/example_app/current/source && ./manage.py scheduler_run_jobs \\
312 --settings=tenants.acme_example_app.settings \\
313 -P tenants/acme_example_app/config/scheduler.ini);
315"""
316__author__ = "Shawn Davis <shawn@superdjango.com>"
317__maintainer__ = "Shawn Davis <shawn@superdjango.com>"
318__version__ = "0.5.0-x"
320from django.conf import settings
321from django.utils.module_loading import autodiscover_modules
322from .constants import STATUS
323from .library import Result
324from .interfaces import schedule
326__all__ = (
327 "STATUS",
328 "Result",
329 "schedule",
330)
332SCHEDULER_MODULE_NAME = getattr(settings, "SUPERDJANGO_SCHEDULER_MODULE_NAME", "scheduler")
335def autodiscover():
336 """Automatically find (and load) ``ajax.py`` files located in installed Django apps."""
337 autodiscover_modules(SCHEDULER_MODULE_NAME)
340default_app_config = "superdjango.contrib.scheduler.apps.DefaultConfig"