Correction crash
[auf_roundup.git] / build / lib / roundup / instance.py
CommitLineData
c638d827
CR
1#
2# Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
3# This module is free software, and you may redistribute it and/or modify
4# under the same terms as Python, so long as this copyright message and
5# disclaimer are retained in their original form.
6#
7# IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
8# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
9# OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
10# POSSIBILITY OF SUCH DAMAGE.
11#
12# BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
13# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14# FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
15# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
16# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
17#
18
19"""Tracker handling (open tracker).
20
21Backwards compatibility for the old-style "imported" trackers.
22"""
23__docformat__ = 'restructuredtext'
24
25import os
26import sys
27from roundup import configuration, mailgw
28from roundup import hyperdb, backends, actions
29from roundup.cgi import client, templating
30from roundup.cgi import actions as cgi_actions
31
32class Vars:
33 def __init__(self, vars):
34 self.__dict__.update(vars)
35
36class Tracker:
37 def __init__(self, tracker_home, optimize=0):
38 """New-style tracker instance constructor
39
40 Parameters:
41 tracker_home:
42 tracker home directory
43 optimize:
44 if set, precompile html templates
45
46 """
47 self.tracker_home = tracker_home
48 self.optimize = optimize
49 # if set, call schema_hook after executing schema.py will get
50 # same variables (in particular db) as schema.py main purpose is
51 # for regression tests
52 self.schema_hook = None
53 self.config = configuration.CoreConfig(tracker_home)
54 self.actions = {}
55 self.cgi_actions = {}
56 self.templating_utils = {}
57 self.load_interfaces()
58 self.templates = templating.Templates(self.config["TEMPLATES"])
59 self.backend = backends.get_backend(self.get_backend_name())
60 if self.optimize:
61 libdir = os.path.join(self.tracker_home, 'lib')
62 if os.path.isdir(libdir):
63 sys.path.insert(1, libdir)
64 self.templates.precompileTemplates()
65 # initialize tracker extensions
66 for extension in self.get_extensions('extensions'):
67 extension(self)
68 # load database schema
69 schemafilename = os.path.join(self.tracker_home, 'schema.py')
70 # Note: can't use built-in open()
71 # because of the global function with the same name
72 schemafile = file(schemafilename, 'rt')
73 self.schema = compile(schemafile.read(), schemafilename, 'exec')
74 schemafile.close()
75 # load database detectors
76 self.detectors = self.get_extensions('detectors')
77 # db_open is set to True after first open()
78 self.db_open = 0
79 if libdir in sys.path:
80 sys.path.remove(libdir)
81
82 def get_backend_name(self):
83 o = __builtins__['open']
84 f = o(os.path.join(self.config.DATABASE, 'backend_name'))
85 name = f.readline().strip()
86 f.close()
87 return name
88
89 def open(self, name=None):
90 # load the database schema
91 # we cannot skip this part even if self.optimize is set
92 # because the schema has security settings that must be
93 # applied to each database instance
94 backend = self.backend
95 vars = {
96 'Class': backend.Class,
97 'FileClass': backend.FileClass,
98 'IssueClass': backend.IssueClass,
99 'String': hyperdb.String,
100 'Password': hyperdb.Password,
101 'Date': hyperdb.Date,
102 'Link': hyperdb.Link,
103 'Multilink': hyperdb.Multilink,
104 'Interval': hyperdb.Interval,
105 'Boolean': hyperdb.Boolean,
106 'Number': hyperdb.Number,
107 'db': backend.Database(self.config, name)
108 }
109
110 if self.optimize:
111 # execute preloaded schema object
112 exec(self.schema, vars)
113 if callable (self.schema_hook):
114 self.schema_hook(**vars)
115 # use preloaded detectors
116 detectors = self.detectors
117 else:
118 libdir = os.path.join(self.tracker_home, 'lib')
119 if os.path.isdir(libdir):
120 sys.path.insert(1, libdir)
121 # execute the schema file
122 self._load_python('schema.py', vars)
123 if callable (self.schema_hook):
124 self.schema_hook(**vars)
125 # reload extensions and detectors
126 for extension in self.get_extensions('extensions'):
127 extension(self)
128 detectors = self.get_extensions('detectors')
129 if libdir in sys.path:
130 sys.path.remove(libdir)
131 db = vars['db']
132 # apply the detectors
133 for detector in detectors:
134 detector(db)
135 # if we are running in debug mode
136 # or this is the first time the database is opened,
137 # do database upgrade checks
138 if not (self.optimize and self.db_open):
139 # As a consistency check, ensure that every link property is
140 # pointing at a defined class. Otherwise, the schema is
141 # internally inconsistent. This is an important safety
142 # measure as it protects against an accidental schema change
143 # dropping a table while there are still links to the table;
144 # once the table has been dropped, there is no way to get it
145 # back, so it is important to drop it only if we are as sure
146 # as possible that it is no longer needed.
147 classes = db.getclasses()
148 for classname in classes:
149 cl = db.getclass(classname)
150 for propname, prop in cl.getprops().iteritems():
151 if not isinstance(prop, (hyperdb.Link,
152 hyperdb.Multilink)):
153 continue
154 linkto = prop.classname
155 if linkto not in classes:
156 raise ValueError, \
157 ("property %s.%s links to non-existent class %s"
158 % (classname, propname, linkto))
159
160 db.post_init()
161 self.db_open = 1
162 return db
163
164 def load_interfaces(self):
165 """load interfaces.py (if any), initialize Client and MailGW attrs"""
166 vars = {}
167 if os.path.isfile(os.path.join(self.tracker_home, 'interfaces.py')):
168 self._load_python('interfaces.py', vars)
169 self.Client = vars.get('Client', client.Client)
170 self.MailGW = vars.get('MailGW', mailgw.MailGW)
171
172 def get_extensions(self, dirname):
173 """Load python extensions
174
175 Parameters:
176 dirname:
177 extension directory name relative to tracker home
178
179 Return value:
180 list of init() functions for each extension
181
182 """
183 extensions = []
184 dirpath = os.path.join(self.tracker_home, dirname)
185 if os.path.isdir(dirpath):
186 sys.path.insert(1, dirpath)
187 for name in os.listdir(dirpath):
188 if not name.endswith('.py'):
189 continue
190 vars = {}
191 self._load_python(os.path.join(dirname, name), vars)
192 extensions.append(vars['init'])
193 sys.path.remove(dirpath)
194 return extensions
195
196 def init(self, adminpw):
197 db = self.open('admin')
198 self._load_python('initial_data.py', {'db': db, 'adminpw': adminpw,
199 'admin_email': self.config['ADMIN_EMAIL']})
200 db.commit()
201 db.close()
202
203 def exists(self):
204 return self.backend.db_exists(self.config)
205
206 def nuke(self):
207 self.backend.db_nuke(self.config)
208
209 def _load_python(self, file, vars):
210 file = os.path.join(self.tracker_home, file)
211 execfile(file, vars)
212 return vars
213
214 def registerAction(self, name, action):
215
216 # The logic here is this:
217 # * if `action` derives from actions.Action,
218 # it is executable as a generic action.
219 # * if, moreover, it also derives from cgi.actions.Bridge,
220 # it may in addition be called via CGI
221 # * in all other cases we register it as a CGI action, without
222 # any check (for backward compatibility).
223 if issubclass(action, actions.Action):
224 self.actions[name] = action
225 if issubclass(action, cgi_actions.Bridge):
226 self.cgi_actions[name] = action
227 else:
228 self.cgi_actions[name] = action
229
230 def registerUtil(self, name, function):
231 self.templating_utils[name] = function
232
233class TrackerError(Exception):
234 pass
235
236
237class OldStyleTrackers:
238 def __init__(self):
239 self.number = 0
240 self.trackers = {}
241
242 def open(self, tracker_home, optimize=0):
243 """Open the tracker.
244
245 Parameters:
246 tracker_home:
247 tracker home directory
248 optimize:
249 if set, precompile html templates
250
251 Raise ValueError if the tracker home doesn't exist.
252
253 """
254 import imp
255 # sanity check existence of tracker home
256 if not os.path.exists(tracker_home):
257 raise ValueError, 'no such directory: "%s"'%tracker_home
258
259 # sanity check tracker home contents
260 for reqd in 'config dbinit select_db interfaces'.split():
261 if not os.path.exists(os.path.join(tracker_home, '%s.py'%reqd)):
262 raise TrackerError, 'File "%s.py" missing from tracker '\
263 'home "%s"'%(reqd, tracker_home)
264
265 if self.trackers.has_key(tracker_home):
266 return imp.load_package(self.trackers[tracker_home],
267 tracker_home)
268 # register all available backend modules
269 backends.list_backends()
270 self.number = self.number + 1
271 modname = '_roundup_tracker_%s'%self.number
272 self.trackers[tracker_home] = modname
273
274 # load the tracker
275 tracker = imp.load_package(modname, tracker_home)
276
277 # ensure the tracker has all the required bits
278 for required in 'open init Client MailGW'.split():
279 if not hasattr(tracker, required):
280 raise TrackerError, \
281 'Required tracker attribute "%s" missing'%required
282
283 # load and apply the config
284 tracker.config = configuration.CoreConfig(tracker_home)
285 tracker.dbinit.config = tracker.config
286
287 tracker.optimize = optimize
288 tracker.templates = templating.Templates(tracker.config["TEMPLATES"])
289 if optimize:
290 tracker.templates.precompileTemplates()
291
292 return tracker
293
294OldStyleTrackers = OldStyleTrackers()
295def open(tracker_home, optimize=0):
296 if os.path.exists(os.path.join(tracker_home, 'dbinit.py')):
297 # user should upgrade...
298 return OldStyleTrackers.open(tracker_home, optimize=optimize)
299
300 return Tracker(tracker_home, optimize=optimize)
301
302# vim: set filetype=python sts=4 sw=4 et si :