Hide keyboard shortcuts

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-------- 

4 

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. 

7 

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. 

10 

11Administrators or authorized users may optionally configure these jobs. 

12 

13Install 

14------- 

15 

16Make sure a ``config/`` directory exists in project root. 

17 

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). 

20 

21For automated discovery, the Python module name defaults to ``scheduler``, but may changed using the 

22``SUPERDJANGO_SCHEDULER_MODULE_NAME`` setting. 

23 

24Usage 

25----- 

26 

27There are two (2) means of using the scheduler: 

28 

291. Manually configuring jobs by creating an maintaining the ``scheduler.ini`` file. 

302. Auto discovery of jobs. 

31 

32.. important:: 

33 Note that the included (concrete) models are only used when auto-discovery is enabled. 

34 

35Manually Configuring Jobs 

36......................... 

37 

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. 

40 

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: 

43 

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``. 

50 

51The valid frequencies are: 

52 

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 

70 

71.. tip:: 

72 ``at`` is only applied when specifying a weekday frequency. 

73 

74Additional parameters within the section become keyword arguments passed to the callback. 

75 

76Here are some examples: 

77 

78.. code-block:: ini 

79 

80 [run a test job] 

81 call = example.scheduler.test_job 

82 every = 5 seconds 

83 testing = True 

84 

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 

89 

90 [this job is inactive] 

91 active = no 

92 call = example.automation.inactive_job 

93 every = 10 minutes 

94 

95 [this job runs on sunday] 

96 callback = example.scheduler.sunday_cleanup 

97 frequency = sunday 

98 at = 23:30 

99 

100Automatic Discovery of Jobs 

101........................... 

102 

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. 

105 

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`` 

109 

110See `Creating Job Callbacks`_ below. 

111 

112You may provide a user interface for automated discovery by registering the UI menu provided with the scheduler. 

113 

114.. code-block:: python 

115 

116 # glue/ui.py 

117 from superdjango import ui 

118 from superdjango.contrib.scheduler.ui import ScheduledJobsMenu 

119 

120 ui.site.register(ScheduledJobsMenu) 

121 

122 

123Listing the Available Jobs 

124.......................... 

125 

126Regardless of manual or automatic discovery, the ``scheduler_list_jobs`` command will list the jobs found in the 

127``scheduler.ini`` file. 

128 

129Setting Up Supervisor D 

130....................... 

131 

132Some means of executing jobs is required. One way to do this is to set up `supervisord`_. 

133 

134.. _supervisord: http://supervisord.org 

135 

136.. tip:: 

137 You can use the management command: ``./manage.py scheduler_export_supervisord`` to create the configuration on the 

138 server. 

139 

140This requires a supervisord configuration file which points to a shell script. 

141 

142.. code-block:: text 

143 

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 

150 

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: 

153 

154.. code-block:: bash 

155 

156 #! /usr/bin/env bash 

157 

158 source /path/to/project/python/bin/active; 

159 (cd /path/to/project/source && ./manage.py scheduler_run_jobs); 

160 

161Creating Job Callbacks 

162...................... 

163 

164A callback function is needed to execute a job. 

165 

166The function must accept ``**kwargs`` and return an instance of 

167:py:class:`superdjango.contrib.scheduler.library.Result`. 

168 

169.. code-block:: python 

170 

171 # myapp/scheduler.py 

172 from superdjango.contrib.scheduler import STATUS, Result 

173 

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 ) 

181 

182The label and start/end datetimes are automatically set at runtime. 

183 

184With manual configuration this callback would be added to the ``scheduler.ini`` file like so: 

185 

186.. code-block:: ini 

187 

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 

192 

193When auto-discovery is configured, you must also register the callback: 

194 

195.. code-block:: python 

196 

197 # myapp/scheduler.py 

198 from superdjango.contrib.scheduler import STATUS, Result, schedule 

199 

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 ) 

207 

208 schedule.register("myapp", do_something_on_a_recurring_basis) 

209 

210Creating Activity Records 

211......................... 

212 

213When auto-discovery is enabled, it is up to the developer to create the activity records for a given job. 

214 

215.. code-block:: python 

216 

217 from superdjango.contrib.scheduler import STATUS, Result, schedule 

218 from superdjango.contrib.scheduler.models import Activity 

219 

220 def do_something_on_a_recurring_basis(**kwargs): 

221 job_id = kwargs.pop("pk", None) 

222 

223 # do some work ... 

224 

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 ) 

230 

231 if job_id: 

232 Activity.log(job_id, result) 

233 

234 return result 

235 

236 schedule.register("myapp", do_something_on_a_recurring_basis) 

237 

238Setting Up the INI Export 

239......................... 

240 

241A simple cron job may be used to set up the regular export of auto-configured the INI file: 

242 

243.. code-block:: bash 

244 

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 

248 

249 # Times are in UTC. Starts at 4am EST. 

250 0 8 * * 0 root /path/to/project/cron/export_scheduler_ini.sh 

251 

252And the script itself: 

253 

254.. code-block:: bash 

255 

256 #! /usr/bin/env bash 

257 

258 source /path/to/project/python/bin/active; 

259 (cd /path/to/project/source && ./manage.py scheduler_run_jobs); 

260 

261Multi-Tenant Considerations for Scheduled Jobs 

262.............................................. 

263 

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. 

267 

268.. _When using WSGI for tenant separation: https://superdjango.com/blog/mutli-tenancy-with-wsgi/ 

269 

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``. 

272 

273With manual configuration, simply create or copy the file to the appropriate location. 

274 

275Or with auto configuration, this is easily accomplished using preview and bash output redirection: 

276 

277.. code-block:: bash 

278 

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; 

283 

284This assumed job discovery has already been performed with: 

285 

286.. code-block:: bash 

287 

288 ./manage.py scheduler_discover_jobs --settings=tenants.acme_example_app.settings; 

289 

290Listing jobs requires specifying the path, as in: 

291 

292.. code-block:: bash 

293 

294 ./manage.py scheduler_list_jobs -P tenants/acme_example_app/config/scheduler.ini; 

295 

296The supervisord configuration may be exported using a tenant-specific name: 

297 

298.. code-block:: bash 

299 

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; 

303 

304And the cron script would look something like: 

305 

306.. code-block:: bash 

307 

308 #! /usr/bin/env bash 

309 

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); 

314 

315""" 

316__author__ = "Shawn Davis <shawn@superdjango.com>" 

317__maintainer__ = "Shawn Davis <shawn@superdjango.com>" 

318__version__ = "0.5.0-x" 

319 

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 

325 

326__all__ = ( 

327 "STATUS", 

328 "Result", 

329 "schedule", 

330) 

331 

332SCHEDULER_MODULE_NAME = getattr(settings, "SUPERDJANGO_SCHEDULER_MODULE_NAME", "scheduler") 

333 

334 

335def autodiscover(): 

336 """Automatically find (and load) ``ajax.py`` files located in installed Django apps.""" 

337 autodiscover_modules(SCHEDULER_MODULE_NAME) 

338 

339 

340default_app_config = "superdjango.contrib.scheduler.apps.DefaultConfig"