• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

roundup-tracker / roundup / 25532790014

08 May 2026 02:11AM UTC coverage: 74.758% (-0.05%) from 74.811%
25532790014

push

github

rouilj
bug: issue2551405 - locale error causes 500 code uwsgi with no log

Changed exception handling in two places.

There is an inner try/except where I added the more specific
ConnectionAbortedError before the existing IOError
exception. ConnectionAbortedError should be what is returned (in
python 3) when writing on a closed connection caused by the client
going away. Unable to test though.

Then I set up IOError to log/report exception as it is
unexpected. This logs useful error for the issue.

In the outer try/except, I replaced outer IOError handler with
ConnectionAbortdError handler. Then I removed IOError handler and let
an IOError fall through the last resort exception handler.

For this specific translator issue, generating the html error message
also crashes because it is trying to translate it and translation
fails. So I wrap the cgitb.http call in try/except Exception and
logger.exception() the original exception so there is something useful
logged.

2 of 21 new or added lines in 1 file covered. (9.52%)

11 existing lines in 2 files now uncovered.

19180 of 25656 relevant lines covered (74.76%)

4.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.3
/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

19
"""Top-level tracker interface.
2✔
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'
6✔
30

31
try:
6✔
32
    import builtins
6✔
33
except ImportError:
×
34
    import __builtin__ as builtins
×
35

36
import os
6✔
37
import sys
6✔
38

39
from roundup import actions, backends, configuration, hyperdb, mailgw
6✔
40
from roundup.cgi import actions as cgi_actions
6✔
41
from roundup.cgi import client, templating
6✔
42
from roundup.exceptions import RoundupException
6✔
43

44

45
class Tracker:
6✔
46
    def __init__(self, tracker_home, optimize=0):
6✔
47
        """New-style tracker instance constructor
48

49
        Parameters:
50
            tracker_home:
51
                tracker home directory
52
            optimize:
53
                if set, precompile html templates
54

55
        """
56
        self.tracker_home = tracker_home
6✔
57
        self.optimize = optimize
6✔
58
        self.config = configuration.CoreConfig(tracker_home)
6✔
59
        self.actions = {}
6✔
60
        self.cgi_actions = {}
6✔
61
        self.templating_utils = {}
6✔
62

63
        libdir = os.path.join(self.tracker_home, 'lib')
6✔
64
        self.libdir = (os.path.isdir(libdir) and libdir) or ''
6✔
65

66
        self.load_interfaces()
6✔
67
        self.templates = templating.get_loader(self.config["TEMPLATES"],
6✔
68
                                               self.config["TEMPLATE_ENGINE"])
69

70
        rdbms_backend = self.config.RDBMS_BACKEND
6✔
71

72
        self.backend = backends.get_backend(rdbms_backend)
6✔
73

74
        if self.optimize:
6✔
75
            self.templates.precompile()
6✔
76
            # initialize tracker extensions
77
            for extension in self.get_extensions('extensions'):
6✔
78
                extension(self)
×
79
            # load database schema
80
            self.schema = self._compile('schema.py')
6✔
81
            # load database detectors
82
            self.detectors = self.get_extensions('detectors')
6✔
83
            # db_open is set to True after first open()
84
            self.db_open = 0
6✔
85

86
    def open(self, name=None):
6✔
87
        # load the database schema
88
        # we cannot skip this part even if self.optimize is set
89
        # because the schema has security settings that must be
90
        # applied to each database instance
91
        backend = self.backend
6✔
92
        env = {
6✔
93
            'Class': backend.Class,
94
            'FileClass': backend.FileClass,
95
            'IssueClass': backend.IssueClass,
96
            'String': hyperdb.String,
97
            'Password': hyperdb.Password,
98
            'Date': hyperdb.Date,
99
            'Link': hyperdb.Link,
100
            'Multilink': hyperdb.Multilink,
101
            'Interval': hyperdb.Interval,
102
            'Boolean': hyperdb.Boolean,
103
            'Number': hyperdb.Number,
104
            'Integer': hyperdb.Integer,
105
            'db': backend.Database(self.config, name)
106
        }
107

108
        if self.optimize:
6✔
109
            # execute preloaded schema object
110
            self._exec(self.schema, env)
6✔
111
            # use preloaded detectors
112
            detectors = self.detectors
6✔
113
        else:
114
            # execute the schema file
115
            self._execfile('schema.py', env)
6✔
116
            # reload extensions and detectors
117
            for extension in self.get_extensions('extensions'):
6✔
118
                extension(self)
×
119
            detectors = self.get_extensions('detectors')
6✔
120
        db = env['db']
6✔
121
        db.tx_Source = None
6✔
122
        # Useful for script when multiple open calls happen. Scripts have
123
        # to inject the i18n object, there is currently no support for this
124
        if hasattr(self, 'i18n'):
6✔
125
            db.i18n = self.i18n
×
126

127
        # apply the detectors
128
        for detector in detectors:
6✔
129
            detector(db)
6✔
130
        # if we are running in debug mode
131
        # or this is the first time the database is opened,
132
        # do database upgrade checks
133
        if not (self.optimize and self.db_open):
6✔
134
            # As a consistency check, ensure that every link property is
135
            # pointing at a defined class.  Otherwise, the schema is
136
            # internally inconsistent.  This is an important safety
137
            # measure as it protects against an accidental schema change
138
            # dropping a table while there are still links to the table;
139
            # once the table has been dropped, there is no way to get it
140
            # back, so it is important to drop it only if we are as sure
141
            # as possible that it is no longer needed.
142
            classes = db.getclasses()
6✔
143
            for classname in classes:
6✔
144
                cl = db.getclass(classname)
6✔
145
                for propname, prop in cl.getprops().items():
6✔
146
                    if not isinstance(prop, (hyperdb.Link,
6✔
147
                                             hyperdb.Multilink)):
148
                        continue
6✔
149
                    linkto = prop.classname
6✔
150
                    if linkto not in classes:
6✔
151
                        raise ValueError("property %s.%s links to "
×
152
                                         "non-existent class %s"
153
                                         % (classname, propname, linkto))
154

155
            self.db_open = 1
6✔
156
        # *Must* call post_init! It is not an error if called multiple times.
157
        db.post_init()
6✔
158
        return db
6✔
159

160
    def load_interfaces(self):
6✔
161
        """load interfaces.py (if any), initialize Client and MailGW attrs"""
162
        env = {}
6✔
163
        if os.path.isfile(os.path.join(self.tracker_home, 'interfaces.py')):
6✔
164
            self._execfile('interfaces.py', env)
×
165
        self.Client = env.get('Client', client.Client)
6✔
166
        self.MailGW = env.get('MailGW', mailgw.MailGW)
6✔
167
        self.TemplatingUtils = env.get('TemplatingUtils',
6✔
168
                                       templating.TemplatingUtils)
169

170
    def get_extensions(self, dirname):
6✔
171
        """Load python extensions
172

173
        Parameters:
174
            dirname:
175
                extension directory name relative to tracker home
176

177
        Return value:
178
            list of init() functions for each extension
179

180
        """
181
        extensions = []
6✔
182
        dirpath = os.path.join(self.tracker_home, dirname)
6✔
183
        if os.path.isdir(dirpath):
6✔
184
            sys.path.insert(1, dirpath)
6✔
185
            with os.scandir(dirpath) as ext_dir:
6✔
186
                for dir_entry in ext_dir:
6✔
187
                    name = dir_entry.name
6✔
188
                    if not name.endswith('.py'):
6✔
189
                        continue
6✔
190
                    env = {}  # reset to empty every cycle
6✔
191
                    try:
6✔
192
                        self._execfile(os.path.join(dirname, name), env)
6✔
UNCOV
193
                    except ImportError as e:
×
UNCOV
194
                        raise TrackerError(
×
195
                            "%(exception)s found when loading tracker extension: %(name)s" % {
196
                                "exception": e, "name": os.path.join(dirname, name)})
197
                    extensions.append(env['init'])
6✔
198
            sys.path.remove(dirpath)
6✔
199
        return extensions
6✔
200

201
    def init(self, adminpw, tx_Source=None):
6✔
202
        db = self.open('admin')
6✔
203
        db.tx_Source = tx_Source
6✔
204
        self._execfile('initial_data.py',
6✔
205
                       {'db': db, 'adminpw': adminpw,
206
                        'admin_email': self.config['ADMIN_EMAIL']})
207
        db.commit()
6✔
208
        db.close()
6✔
209

210
    def exists(self):
6✔
211
        return self.backend.db_exists(self.config)
6✔
212

213
    def nuke(self):
6✔
214
        self.backend.db_nuke(self.config)
6✔
215

216
    def _compile(self, fname):
6✔
217
        fname = os.path.join(self.tracker_home, fname)
6✔
218
        with builtins.open(fname) as fnamed:
6✔
219
            return compile(fnamed.read(), fname, 'exec')
6✔
220

221
    def _exec(self, obj, env):
6✔
222
        if self.libdir:
6✔
UNCOV
223
            sys.path.insert(1, self.libdir)
×
224
        exec(obj, env)
6✔
225
        if self.libdir:
6✔
UNCOV
226
            sys.path.remove(self.libdir)
×
227
        return env
6✔
228

229
    def _execfile(self, fname, env):
6✔
230
        self._exec(self._compile(fname), env)
6✔
231

232
    def registerAction(self, name, action):
6✔
233

234
        # The logic here is this:
235
        # * if `action` derives from actions.Action,
236
        #   it is executable as a generic action.
237
        # * if, moreover, it also derives from cgi.actions.Bridge,
238
        #   it may in addition be called via CGI
239
        # * in all other cases we register it as a CGI action, without
240
        #   any check (for backward compatibility).
241
        if issubclass(action, actions.Action):
6✔
UNCOV
242
            self.actions[name] = action
×
UNCOV
243
            if issubclass(action, cgi_actions.Bridge):
×
UNCOV
244
                self.cgi_actions[name] = action
×
245
        else:
246
            self.cgi_actions[name] = action
6✔
247

248
    def registerUtil(self, name, function):
6✔
249
        """Register a function that can be called using:
250
           `utils.<name>(...)`.
251

252
           The function is defined as:
253

254
               def function(...):
255

256
           If you need access to the client, database, form or other
257
           item, you have to pass it explicitly::
258

259
               utils.name(request.client, ...)
260

261
           If you need client access, consider using registerUtilMethod()
262
           instead.
263

264
        """
UNCOV
265
        self.templating_utils[name] = function
×
266

267
    def registerUtilMethod(self, name, function):
6✔
268
        """Register a method that can be called using:
269
           `utils.<name>(...)`.
270

271
           Unlike registerUtil, the method is defined as:
272

273
               def function(self, ...):
274

275
           `self` is a TemplatingUtils object. You can use self.client
276
           to access the client object for your request.
277
        """
UNCOV
278
        setattr(self.TemplatingUtils,
×
279
                name,
280
                function)
281

282

283
class TrackerError(RoundupException):
6✔
284
    pass
6✔
285

286

287
def open(tracker_home, optimize=0):
6✔
288
    if os.path.exists(os.path.join(tracker_home, 'dbinit.py')):
6✔
289
        # user should upgrade...
290
        raise TrackerError("Old style trackers using dbinit.py "
6✔
291
                           "are not supported after release 2.0")
292

293
    return Tracker(tracker_home, optimize=optimize)
6✔
294

295
# vim: set filetype=python sts=4 sw=4 et si :
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc