2
2
from datetime import timedelta
3
3
import traceback
4
4
import time
5
+ import sys
5
6
6
7
from django .conf import settings
7
8
from django .utils .timezone import now as utc_now , localtime , is_naive
12
13
logger = logging .getLogger ('django_cron' )
13
14
14
15
16
+ class BadCronJobError (AssertionError ):
17
+ pass
18
+
19
+
15
20
def get_class (kls ):
16
21
"""
17
22
TODO: move to django-common app.
18
23
Converts a string to a class.
19
24
Courtesy: http://stackoverflow.com/questions/452969/does-python-have-an-equivalent-to-java-class-forname/452981#452981
20
25
"""
21
26
parts = kls .split ('.' )
27
+
28
+ if len (parts ) == 1 :
29
+ raise ImportError ("'{0}'' is not a valid import path" .format (kls ))
30
+
22
31
module = "." .join (parts [:- 1 ])
23
32
m = __import__ (module )
24
33
for comp in parts [1 :]:
@@ -77,12 +86,11 @@ class CronJobManager(object):
77
86
Used as a context manager via 'with' statement to ensure
78
87
proper logger in cases of job failure.
79
88
"""
80
-
81
- def __init__ (self , cron_job_class , silent = False , * args , ** kwargs ):
82
- super (CronJobManager , self ).__init__ (* args , ** kwargs )
83
-
89
+ def __init__ (self , cron_job_class , silent = False , dry_run = False , stdout = None ):
84
90
self .cron_job_class = cron_job_class
85
91
self .silent = silent
92
+ self .dry_run = dry_run
93
+ self .stdout = stdout or sys .stdout
86
94
self .lock_class = self .get_lock_class ()
87
95
self .previously_ran_successful_cron = None
88
96
@@ -92,7 +100,6 @@ def should_run_now(self, force=False):
92
100
"""
93
101
Returns a boolean determining whether this cron should run now or not!
94
102
"""
95
-
96
103
self .user_time = None
97
104
self .previously_ran_successful_cron = None
98
105
@@ -170,11 +177,20 @@ def __enter__(self):
170
177
return self
171
178
172
179
def __exit__ (self , ex_type , ex_value , ex_traceback ):
173
- if ex_type == self .lock_class .LockFailedException :
180
+ if ex_type is None :
181
+ return True
182
+
183
+ non_logging_exceptions = [
184
+ BadCronJobError , self .lock_class .LockFailedException
185
+ ]
186
+
187
+ if ex_type in non_logging_exceptions :
174
188
if not self .silent :
189
+ self .stdout .write ("{0}\n " .format (ex_value ))
175
190
logger .info (ex_value )
176
-
177
- elif ex_type is not None :
191
+ else :
192
+ if not self .silent :
193
+ self .stdout .write (u"[\N{HEAVY BALLOT X} ] {0}\n " .format (self .cron_job_class .code ))
178
194
try :
179
195
trace = "" .join (traceback .format_exception (ex_type , ex_value , ex_traceback ))
180
196
self .make_log (self .msg , trace , success = False )
@@ -189,17 +205,29 @@ def run(self, force=False):
189
205
apply the logic of the schedule and call do() on the CronJobBase class
190
206
"""
191
207
cron_job_class = self .cron_job_class
208
+
192
209
if not issubclass (cron_job_class , CronJobBase ):
193
- raise Exception ('The cron_job to be run must be a subclass of %s' % CronJobBase .__name__ )
210
+ raise BadCronJobError ('The cron_job to be run must be a subclass of %s' % CronJobBase .__name__ )
211
+
212
+ if not hasattr (cron_job_class , 'code' ):
213
+ raise BadCronJobError (
214
+ "Cron class '{0}' does not have a code attribute"
215
+ .format (cron_job_class .__name__ )
216
+ )
194
217
195
218
with self .lock_class (cron_job_class , self .silent ):
196
219
self .cron_job = cron_job_class ()
197
220
198
221
if self .should_run_now (force ):
199
- logger .debug ("Running cron: %s code %s" , cron_job_class .__name__ , self .cron_job .code )
200
- self .msg = self .cron_job .do ()
201
- self .make_log (self .msg , success = True )
202
- self .cron_job .set_prev_success_cron (self .previously_ran_successful_cron )
222
+ if not self .dry_run :
223
+ logger .debug ("Running cron: %s code %s" , cron_job_class .__name__ , self .cron_job .code )
224
+ self .msg = self .cron_job .do ()
225
+ self .make_log (self .msg , success = True )
226
+ self .cron_job .set_prev_success_cron (self .previously_ran_successful_cron )
227
+ if not self .silent :
228
+ self .stdout .write (u"[\N{HEAVY CHECK MARK} ] {0}\n " .format (self .cron_job .code ))
229
+ elif not self .silent :
230
+ self .stdout .write (u"[ ] {0}\n " .format (self .cron_job .code ))
203
231
204
232
def get_lock_class (self ):
205
233
name = getattr (settings , 'DJANGO_CRON_LOCK_BACKEND' , DEFAULT_LOCK_BACKEND )
0 commit comments