Code

svn repository setup
[roundup.git] / roundup / instance.py
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 # $Id: instance.py,v 1.37 2006-12-11 23:36:15 richard Exp $
20 '''Tracker handling (open tracker).
22 Backwards compatibility for the old-style "imported" trackers.
23 '''
24 __docformat__ = 'restructuredtext'
26 import os
27 import sys
28 from roundup import configuration, mailgw
29 from roundup import hyperdb, backends
30 from roundup.cgi import client, templating
32 class Vars:
33     def __init__(self, vars):
34         self.__dict__.update(vars)
36 class Tracker:
37     def __init__(self, tracker_home, optimize=0):
38         """New-style tracker instance constructor
40         Parameters:
41             tracker_home:
42                 tracker home directory
43             optimize:
44                 if set, precompile html templates
46         """
47         self.tracker_home = tracker_home
48         self.optimize = optimize
49         self.config = configuration.CoreConfig(tracker_home)
50         self.cgi_actions = {}
51         self.templating_utils = {}
52         self.load_interfaces()
53         self.templates = templating.Templates(self.config["TEMPLATES"])
54         self.backend = backends.get_backend(self.get_backend_name())
55         if self.optimize:
56             libdir = os.path.join(self.tracker_home, 'lib')
57             if os.path.isdir(libdir):
58                 sys.path.insert(1, libdir)
59             self.templates.precompileTemplates()
60             # initialize tracker extensions
61             for extension in self.get_extensions('extensions'):
62                 extension(self)
63             # load database schema
64             schemafilename = os.path.join(self.tracker_home, 'schema.py')
65             # Note: can't use built-in open()
66             #   because of the global function with the same name
67             schemafile = file(schemafilename, 'rt')
68             self.schema = compile(schemafile.read(), schemafilename, 'exec')
69             schemafile.close()
70             # load database detectors
71             self.detectors = self.get_extensions('detectors')
72             # db_open is set to True after first open()
73             self.db_open = 0
74             if libdir in sys.path:
75                 sys.path.remove(libdir)
77     def get_backend_name(self):
78         o = __builtins__['open']
79         f = o(os.path.join(self.tracker_home, 'db', 'backend_name'))
80         name = f.readline().strip()
81         f.close()
82         return name
84     def open(self, name=None):
85         # load the database schema
86         # we cannot skip this part even if self.optimize is set
87         # because the schema has security settings that must be
88         # applied to each database instance
89         backend = self.backend
90         vars = {
91             'Class': backend.Class,
92             'FileClass': backend.FileClass,
93             'IssueClass': backend.IssueClass,
94             'String': hyperdb.String,
95             'Password': hyperdb.Password,
96             'Date': hyperdb.Date,
97             'Link': hyperdb.Link,
98             'Multilink': hyperdb.Multilink,
99             'Interval': hyperdb.Interval,
100             'Boolean': hyperdb.Boolean,
101             'Number': hyperdb.Number,
102             'db': backend.Database(self.config, name)
103         }
105         if self.optimize:
106             # execute preloaded schema object
107             exec(self.schema, vars)
108             # use preloaded detectors
109             detectors = self.detectors
110         else:
111             libdir = os.path.join(self.tracker_home, 'lib')
112             if os.path.isdir(libdir):
113                 sys.path.insert(1, libdir)
114             # execute the schema file
115             self._load_python('schema.py', vars)
116             # reload extensions and detectors
117             for extension in self.get_extensions('extensions'):
118                 extension(self)
119             detectors = self.get_extensions('detectors')
120             if libdir in sys.path:
121                 sys.path.remove(libdir)
122         db = vars['db']
123         # apply the detectors
124         for detector in detectors:
125             detector(db)
126         # if we are running in debug mode
127         # or this is the first time the database is opened,
128         # do database upgrade checks
129         if not (self.optimize and self.db_open):
130             db.post_init()
131             self.db_open = 1
132         return db
134     def load_interfaces(self):
135         """load interfaces.py (if any), initialize Client and MailGW attrs"""
136         vars = {}
137         if os.path.isfile(os.path.join(self.tracker_home, 'interfaces.py')):
138             self._load_python('interfaces.py', vars)
139         self.Client = vars.get('Client', client.Client)
140         self.MailGW = vars.get('MailGW', mailgw.MailGW)
142     def get_extensions(self, dirname):
143         """Load python extensions
145         Parameters:
146             dirname:
147                 extension directory name relative to tracker home
149         Return value:
150             list of init() functions for each extension
152         """
153         extensions = []
154         dirpath = os.path.join(self.tracker_home, dirname)
155         if os.path.isdir(dirpath):
156             sys.path.insert(1, dirpath)
157             for name in os.listdir(dirpath):
158                 if not name.endswith('.py'):
159                     continue
160                 vars = {}
161                 self._load_python(os.path.join(dirname, name), vars)
162                 extensions.append(vars['init'])
163             sys.path.remove(dirpath)
164         return extensions
166     def init(self, adminpw):
167         db = self.open('admin')
168         self._load_python('initial_data.py', {'db': db, 'adminpw': adminpw,
169             'admin_email': self.config['ADMIN_EMAIL']})
170         db.commit()
171         db.close()
173     def exists(self):
174         return self.backend.db_exists(self.config)
176     def nuke(self):
177         self.backend.db_nuke(self.config)
179     def _load_python(self, file, vars):
180         file = os.path.join(self.tracker_home, file)
181         execfile(file, vars)
182         return vars
184     def registerAction(self, name, action):
185         self.cgi_actions[name] = action
187     def registerUtil(self, name, function):
188         self.templating_utils[name] = function
190 class TrackerError(Exception):
191     pass
194 class OldStyleTrackers:
195     def __init__(self):
196         self.number = 0
197         self.trackers = {}
199     def open(self, tracker_home, optimize=0):
200         """Open the tracker.
202         Parameters:
203             tracker_home:
204                 tracker home directory
205             optimize:
206                 if set, precompile html templates
208         Raise ValueError if the tracker home doesn't exist.
210         """
211         import imp
212         # sanity check existence of tracker home
213         if not os.path.exists(tracker_home):
214             raise ValueError, 'no such directory: "%s"'%tracker_home
216         # sanity check tracker home contents
217         for reqd in 'config dbinit select_db interfaces'.split():
218             if not os.path.exists(os.path.join(tracker_home, '%s.py'%reqd)):
219                 raise TrackerError, 'File "%s.py" missing from tracker '\
220                     'home "%s"'%(reqd, tracker_home)
222         if self.trackers.has_key(tracker_home):
223             return imp.load_package(self.trackers[tracker_home],
224                 tracker_home)
225         # register all available backend modules
226         backends.list_backends()
227         self.number = self.number + 1
228         modname = '_roundup_tracker_%s'%self.number
229         self.trackers[tracker_home] = modname
231         # load the tracker
232         tracker = imp.load_package(modname, tracker_home)
234         # ensure the tracker has all the required bits
235         for required in 'open init Client MailGW'.split():
236             if not hasattr(tracker, required):
237                 raise TrackerError, \
238                     'Required tracker attribute "%s" missing'%required
240         # load and apply the config
241         tracker.config = configuration.CoreConfig(tracker_home)
242         tracker.dbinit.config = tracker.config
244         tracker.optimize = optimize
245         tracker.templates = templating.Templates(tracker.config["TEMPLATES"])
246         if optimize:
247             tracker.templates.precompileTemplates()
249         return tracker
251 OldStyleTrackers = OldStyleTrackers()
252 def open(tracker_home, optimize=0):
253     if os.path.exists(os.path.join(tracker_home, 'dbinit.py')):
254         # user should upgrade...
255         return OldStyleTrackers.open(tracker_home, optimize=optimize)
257     return Tracker(tracker_home, optimize=optimize)
259 # vim: set filetype=python sts=4 sw=4 et si :