"Fossies" - the Fresh Open Source Software Archive

Member "roundup-2.0.0/roundup/instance.py" (3 Apr 2020, 12597 Bytes) of package /linux/www/roundup-2.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "instance.py": 1.6.1_vs_2.0.0.

    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 """Top-level tracker interface.
   20 
   21 Open a tracker with:
   22 
   23     >>> from roundup import instance
   24     >>> db = instance.open('path to tracker home')
   25 
   26 The "db" handle you get back is the tracker's hyperdb which has the interface
   27 described in `roundup.hyperdb.Database`.
   28 """
   29 __docformat__ = 'restructuredtext'
   30 
   31 try:
   32     import builtins
   33 except ImportError:
   34     import __builtin__ as builtins
   35 
   36 try:
   37     from collections.abc import Callable
   38 except ImportError:
   39     from collections import Callable
   40 
   41 import os
   42 import sys
   43 import warnings
   44 
   45 from roundup import configuration, mailgw
   46 from roundup import hyperdb, backends, actions
   47 from roundup.cgi import client, templating
   48 from roundup.cgi import actions as cgi_actions
   49 from roundup.exceptions import RoundupException
   50 
   51 
   52 class Tracker:
   53     def __init__(self, tracker_home, optimize=0):
   54         """New-style tracker instance constructor
   55 
   56         Parameters:
   57             tracker_home:
   58                 tracker home directory
   59             optimize:
   60                 if set, precompile html templates
   61 
   62         """
   63         self.tracker_home = tracker_home
   64         self.optimize = optimize
   65         # if set, call schema_hook after executing schema.py will get
   66         # same variables (in particular db) as schema.py main purpose is
   67         # for regression tests
   68         self.schema_hook = None
   69         self.config = configuration.CoreConfig(tracker_home)
   70         self.actions = {}
   71         self.cgi_actions = {}
   72         self.templating_utils = {}
   73 
   74         libdir = os.path.join(self.tracker_home, 'lib')
   75         self.libdir = os.path.isdir(libdir) and libdir or ''
   76 
   77         self.load_interfaces()
   78         self.templates = templating.get_loader(self.config["TEMPLATES"],
   79                                                self.config["TEMPLATE_ENGINE"])
   80 
   81         rdbms_backend = self.config.RDBMS_BACKEND
   82 
   83         # TODO: Remove in v1.7
   84         # Provide some backwards compatability for existing Roundup instances
   85         # that still define the backend type in 'db/backend_name' and warn the
   86         # users they need to update their config.ini
   87         if rdbms_backend == '':
   88             filename = os.path.join(self.config.DATABASE, 'backend_name')
   89             msg = """\n
   90 The 'backend_name' file is no longer used to configure the database backend
   91 used for the tracker.  Please read 'doc/upgrading.txt' to find out how to
   92 update your config.ini
   93 """
   94             try:
   95                 with open(filename) as backend_file:
   96                     rdbms_backend = backend_file.readline().strip()
   97 
   98                 with warnings.catch_warnings():
   99                     warnings.simplefilter("once", DeprecationWarning)
  100                     warnings.warn(msg, DeprecationWarning, stacklevel=2)
  101             except IOError:
  102                 pass
  103 
  104         self.backend = backends.get_backend(rdbms_backend)
  105 
  106         if self.optimize:
  107             self.templates.precompile()
  108             # initialize tracker extensions
  109             for extension in self.get_extensions('extensions'):
  110                 extension(self)
  111             # load database schema
  112             self.schema = self._compile('schema.py')
  113             # load database detectors
  114             self.detectors = self.get_extensions('detectors')
  115             # db_open is set to True after first open()
  116             self.db_open = 0
  117 
  118     def open(self, name=None):
  119         # load the database schema
  120         # we cannot skip this part even if self.optimize is set
  121         # because the schema has security settings that must be
  122         # applied to each database instance
  123         backend = self.backend
  124         env = {
  125             'Class': backend.Class,
  126             'FileClass': backend.FileClass,
  127             'IssueClass': backend.IssueClass,
  128             'String': hyperdb.String,
  129             'Password': hyperdb.Password,
  130             'Date': hyperdb.Date,
  131             'Link': hyperdb.Link,
  132             'Multilink': hyperdb.Multilink,
  133             'Interval': hyperdb.Interval,
  134             'Boolean': hyperdb.Boolean,
  135             'Number': hyperdb.Number,
  136             'Integer': hyperdb.Integer,
  137             'db': backend.Database(self.config, name)
  138         }
  139 
  140         if self.optimize:
  141             # execute preloaded schema object
  142             self._exec(self.schema, env)
  143             if isinstance(self.schema_hook, Callable):
  144                 self.schema_hook(**env)
  145             # use preloaded detectors
  146             detectors = self.detectors
  147         else:
  148             # execute the schema file
  149             self._execfile('schema.py', env)
  150             if isinstance(self.schema_hook, Callable):
  151                 self.schema_hook(**env)
  152             # reload extensions and detectors
  153             for extension in self.get_extensions('extensions'):
  154                 extension(self)
  155             detectors = self.get_extensions('detectors')
  156         db = env['db']
  157         db.tx_Source = None
  158 
  159         # apply the detectors
  160         for detector in detectors:
  161             detector(db)
  162         # if we are running in debug mode
  163         # or this is the first time the database is opened,
  164         # do database upgrade checks
  165         if not (self.optimize and self.db_open):
  166             # As a consistency check, ensure that every link property is
  167             # pointing at a defined class.  Otherwise, the schema is
  168             # internally inconsistent.  This is an important safety
  169             # measure as it protects against an accidental schema change
  170             # dropping a table while there are still links to the table;
  171             # once the table has been dropped, there is no way to get it
  172             # back, so it is important to drop it only if we are as sure
  173             # as possible that it is no longer needed.
  174             classes = db.getclasses()
  175             for classname in classes:
  176                 cl = db.getclass(classname)
  177                 for propname, prop in cl.getprops().items():
  178                     if not isinstance(prop, (hyperdb.Link,
  179                                              hyperdb.Multilink)):
  180                         continue
  181                     linkto = prop.classname
  182                     if linkto not in classes:
  183                         raise ValueError("property %s.%s links to "
  184                                          "non-existent class %s"
  185                                          % (classname, propname, linkto))
  186 
  187             db.post_init()
  188             self.db_open = 1
  189         return db
  190 
  191     def load_interfaces(self):
  192         """load interfaces.py (if any), initialize Client and MailGW attrs"""
  193         env = {}
  194         if os.path.isfile(os.path.join(self.tracker_home, 'interfaces.py')):
  195             self._execfile('interfaces.py', env)
  196         self.Client = env.get('Client', client.Client)
  197         self.MailGW = env.get('MailGW', mailgw.MailGW)
  198         self.TemplatingUtils = env.get('TemplatingUtils',
  199                                        templating.TemplatingUtils)
  200 
  201     def get_extensions(self, dirname):
  202         """Load python extensions
  203 
  204         Parameters:
  205             dirname:
  206                 extension directory name relative to tracker home
  207 
  208         Return value:
  209             list of init() functions for each extension
  210 
  211         """
  212         extensions = []
  213         dirpath = os.path.join(self.tracker_home, dirname)
  214         if os.path.isdir(dirpath):
  215             sys.path.insert(1, dirpath)
  216             for name in os.listdir(dirpath):
  217                 if not name.endswith('.py'):
  218                     continue
  219                 env = {}
  220                 self._execfile(os.path.join(dirname, name), env)
  221                 extensions.append(env['init'])
  222             sys.path.remove(dirpath)
  223         return extensions
  224 
  225     def init(self, adminpw, tx_Source=None):
  226         db = self.open('admin')
  227         db.tx_Source = tx_Source
  228         self._execfile('initial_data.py',
  229                        {'db': db, 'adminpw': adminpw,
  230                         'admin_email': self.config['ADMIN_EMAIL']})
  231         db.commit()
  232         db.close()
  233 
  234     def exists(self):
  235         return self.backend.db_exists(self.config)
  236 
  237     def nuke(self):
  238         self.backend.db_nuke(self.config)
  239 
  240     def _compile(self, fname):
  241         fname = os.path.join(self.tracker_home, fname)
  242         return compile(builtins.open(fname).read(), fname, 'exec')
  243 
  244     def _exec(self, obj, env):
  245         if self.libdir:
  246             sys.path.insert(1, self.libdir)
  247         exec(obj, env)
  248         if self.libdir:
  249             sys.path.remove(self.libdir)
  250         return env
  251 
  252     def _execfile(self, fname, env):
  253         self._exec(self._compile(fname), env)
  254 
  255     def registerAction(self, name, action):
  256 
  257         # The logic here is this:
  258         # * if `action` derives from actions.Action,
  259         #   it is executable as a generic action.
  260         # * if, moreover, it also derives from cgi.actions.Bridge,
  261         #   it may in addition be called via CGI
  262         # * in all other cases we register it as a CGI action, without
  263         #   any check (for backward compatibility).
  264         if issubclass(action, actions.Action):
  265             self.actions[name] = action
  266             if issubclass(action, cgi_actions.Bridge):
  267                 self.cgi_actions[name] = action
  268         else:
  269             self.cgi_actions[name] = action
  270 
  271     def registerUtil(self, name, function):
  272         self.templating_utils[name] = function
  273 
  274 
  275 class TrackerError(RoundupException):
  276     pass
  277 
  278 
  279 class OldStyleTrackers:
  280     def __init__(self):
  281         self.number = 0
  282         self.trackers = {}
  283 
  284     def open(self, tracker_home, optimize=0):
  285         """Open the tracker.
  286 
  287         Parameters:
  288             tracker_home:
  289                 tracker home directory
  290             optimize:
  291                 if set, precompile html templates
  292 
  293         Raise ValueError if the tracker home doesn't exist.
  294 
  295         """
  296         import imp
  297         # sanity check existence of tracker home
  298         if not os.path.exists(tracker_home):
  299             raise ValueError('no such directory: "%s"' % tracker_home)
  300 
  301         # sanity check tracker home contents
  302         for reqd in 'config dbinit select_db interfaces'.split():
  303             if not os.path.exists(os.path.join(tracker_home, '%s.py' % reqd)):
  304                 raise TrackerError('File "%s.py" missing from tracker '
  305                                    'home "%s"' % (reqd, tracker_home))
  306 
  307         if tracker_home in self.trackers:
  308             return imp.load_package(self.trackers[tracker_home],
  309                                     tracker_home)
  310         # register all available backend modules
  311         backends.list_backends()
  312         self.number = self.number + 1
  313         modname = '_roundup_tracker_%s' % self.number
  314         self.trackers[tracker_home] = modname
  315 
  316         # load the tracker
  317         tracker = imp.load_package(modname, tracker_home)
  318 
  319         # ensure the tracker has all the required bits
  320         for required in 'open init Client MailGW'.split():
  321             if not hasattr(tracker, required):
  322                 raise TrackerError('Required tracker attribute "%s" missing' %
  323                                    required)
  324 
  325         # load and apply the config
  326         tracker.config = configuration.CoreConfig(tracker_home)
  327         tracker.dbinit.config = tracker.config
  328 
  329         tracker.optimize = optimize
  330         tracker.templates = templating.get_loader(tracker.config["TEMPLATES"])
  331         if optimize:
  332             tracker.templates.precompile()
  333 
  334         return tracker
  335 
  336 
  337 OldStyleTrackers = OldStyleTrackers()
  338 
  339 
  340 def open(tracker_home, optimize=0):
  341     if os.path.exists(os.path.join(tracker_home, 'dbinit.py')):
  342         # user should upgrade...
  343         return OldStyleTrackers.open(tracker_home, optimize=optimize)
  344 
  345     return Tracker(tracker_home, optimize=optimize)
  346 
  347 # vim: set filetype=python sts=4 sw=4 et si :