1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292 | #Author:orcun avsar <orc.avs@gmail.com>
import sys, os, time, atexit
from signal import SIGTERM
class Daemon:
def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.pidfile = pidfile
def daemonize(self):
try:
pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)
except OSError, e:
sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)
# decouple from parent environment
os.chdir("/")
os.setsid()
os.umask(0)
# do second fork
try:
pid = os.fork()
if pid > 0:
# exit from second parent
sys.exit(0)
except OSError, e:
sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)
# redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = file(self.stdin, 'r')
so = file(self.stdout, 'a+')
se = file(self.stderr, 'a+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# write pidfile
atexit.register(self.delpid)
pid = str(os.getpid())
file(self.pidfile,'w+').write("%s\n" % pid)
def delpid(self):
os.remove(self.pidfile)
def start(self):
# Check for a pidfile to see if the daemon already runs
try:
pf = file(self.pidfile,'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None
if pid:
message = "pidfile %s already exist. Daemon already running?\n"
sys.stderr.write(message % self.pidfile)
sys.exit(1)
# Start the daemon
self.daemonize()
self.run()
def stop(self):
# Get the pid from the pidfile
try:
pf = file(self.pidfile,'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None
if not pid:
message = "pidfile %s does not exist. Daemon not running?\n"
sys.stderr.write(message % self.pidfile)
return # not an error in a restart
# Try killing the daemon process
try:
while 1:
os.kill(pid, SIGTERM)
time.sleep(0.1)
except OSError, err:
err = str(err)
if err.find("No such process") > 0:
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
else:
print str(err)
sys.exit(1)
def restart(self):
self.stop()
self.start()
def run(self):
pass
import datetime
import imp
import settings
import django
from django.core.management import setup_environ
DIR=os.path.abspath(__file__)
sys.path.append(imp.find_module("django")[1])
sys.path.append(os.path.split(os.path.split(DIR)[0])[0])
setup_environ(settings)
class ComponentError(Exception):
def __init__(self,component,components):
self.components=components
self.component=component
def __str__(self):
return "Invalid component:"+self.component+". Available components: "+",".join(self.components)
class BaseCron(Daemon):
def __init__(self, pid):
Daemon.__init__(self, pid)
self.events={}
self.components=["year","month","day","hour","minute","second"]
def add_event(self,event,period,component,round=False):
if not self.components.count(component):
raise ComponentError(component,self.components)
self.events[event]={"period":period,"component":component,"round":round}
self.find_next(event)
def find_next(self,event):
now=datetime.datetime.now()
comps={"year":now.year,
"month":now.month,
"day":now.day,
"hour":now.hour,
"minute":now.minute,
"second":now.second}
component=self.events[event]["component"]
period=self.events[event]["period"]
if component=="year":
comps[component]+=period
elif component=="month":
comps[component]=range(1, 13)[(now.month+period-1)%12]
if comps[component]==1:comps["year"]+=1
else:
karg={component+"s":period}
time_delta=datetime.timedelta(**karg)
next=now+time_delta
comps={"year":next.year,
"month":next.month,
"day":next.day,
"hour":next.hour,
"minute":next.minute,
"second":next.second}
round=self.events[event]["round"]
if round:
for comp in self.components[self.components.index(component)+1:]:
comps[comp]=1
next_visit=datetime.datetime(**comps)
self.events[event]["next_visit"]=next_visit
def run(self):
while 1:
###sorting job###
list=[(self.events[x]["next_visit"],x) for x in self.events.keys()]
list.sort()
event_name=list[0][1]
event_date=list[0][0]
now=datetime.datetime.now()
timedelta=event_date-now
seconds=(timedelta.days*24*60*60)+timedelta.seconds
print "\nnext job: '"+event_name +"' after:", str(timedelta)
if timedelta.days>=0:
while seconds:
if seconds>1000:
time.sleep(1000)
seconds-=1000
else:
time.sleep(seconds)
seconds=0
time.sleep(seconds)
print "processing job..."
getattr(self,event_name)()
print "finished succesfully."
self.find_next(event_name)
from django.db.models.loading import get_apps
get_apps()
#########################################################
#########YOU DONT NEED TO CHANGE ANYTHING ABOVE##########
#########################################################
#your optimization starts here
"""
you can directly use your project name as we
already imported our project dynamically
make your model imports here like:
from my_site.poll import Poll
from django.contrib.auth.models import User
start your MyCron class here. inherit it from BaseCron above
class MyCron(BaseCron):
def __init__(self,pid):
BaseCron.__init__(self,pid)
self.add_event("test_job",3,"minute",round=True)
self.add_event("another_test_job", 1 ,"day",round=True)
def test_job(self):
Entry.objects.all()
def another_test_job(self):
super_users=User.objects.filter(is_superuser=True)
super_users.delete()
thats all you have to do with this script... read details for more info.
####DETAILS#####
we added a test_job function which is going to run in every three minutes and
will be rounded minutely
we addedn a another test_job function add that will run on every day at
midnight bacause we rounded it to its period compenent(day) .
add_event function adds a new job to schedule.
-first argument is function name
-second is period length
-third is period component ("second","minute","hour","day","month" or "year"
-last one tells if time should be rounded to period component otherwise
function will be called without taking care of component head.
name this script and put this script in your projects folder to the same level with
your settings.py. a pid file will be automatically created into your project's
folder
#####SHELL COMMANDS######
to start:
python deamon.py start
to restart:
python deamon.py restart
to stop:
python deamon.py stop
if you are running on a windows environment or just want to test how it works
use:
python deamon.py run
"""
if __name__ == "__main__":
daemon = MyCron(os.path.join(os.path.split(DIR)[0],'django-cron-daemon.pid'))
if len(sys.argv) == 2:
if 'start' == sys.argv[1]:
daemon.start()
elif 'stop' == sys.argv[1]:
daemon.stop()
elif 'restart' == sys.argv[1]:
daemon.restart()
elif 'run'== sys.argv[1]:
daemon.run()
else:
print "Unknown command"
sys.exit(2)
sys.exit(0)
else:
print "usage: %s start|stop|restart|run" % sys.argv[0]
sys.exit(2)
|
Comments
Why not use normal cron? */1 * * * * user /path/to/project/manage.py command
#
Yeah, why not just use cron or supervise? Sounds like a lot of code for something that can be done very easily with existing tools...
#
it doesn't sound like a lot of code. you dont have to chage anything in code and you can set your tasks with little effort. Why not normal cron? First this is easy . at least i think its easy.and think you re developing on windows and have to deploy your project on a linux machine or have to deploy on windows. this is not platform dependent(if you dont daemonize it ).you just need to write a cron class that contains your task functions and its ready. you dont need to set your python path or settings variable.
#
Useful to keep cron from being installed in the FastCGI chroot,. Thanks!
#
find_next function is changed. its working correctly now.
#