changeset 5378:35ea9b1efc14

Python 3 preparation: "raise" syntax. Changing "raise Exception, value" to "raise Exception(value)". Tool-assisted patch. Particular cases to check carefully are the one place in frontends/ZRoundup/ZRoundup.py where a string exception needed to be fixed, and the one in roundup/cgi/client.py involving raising an exception with a traceback (requires three-argument form of raise in Python 2, which as I understand it requires exec() to avoid a Python 3 syntax error).
author Joseph Myers <jsm@polyomino.org.uk>
date Tue, 24 Jul 2018 21:39:58 +0000
parents 12fe83f90f0d
children 17edccfe1755
files detectors/creator_resolution.py frontends/ZRoundup/ZRoundup.py frontends/ZRoundup/__init__.py roundup/backends/back_anydbm.py roundup/backends/back_mysql.py roundup/cgi/PageTemplates/Expressions.py roundup/cgi/PageTemplates/MultiMapping.py roundup/cgi/PageTemplates/PageTemplate.py roundup/cgi/PageTemplates/PythonExpr.py roundup/cgi/PageTemplates/TALES.py roundup/cgi/TAL/TALInterpreter.py roundup/cgi/ZTUtils/Batch.py roundup/cgi/ZTUtils/Iterator.py roundup/cgi/actions.py roundup/cgi/client.py roundup/cgi/engine_zopetal.py roundup/cgi/templating.py roundup/cgi/wsgi_handler.py roundup/date.py roundup/hyperdb.py roundup/instance.py roundup/mailgw.py roundup/password.py roundup/roundupdb.py roundup/scripts/roundup_server.py roundup/scripts/roundup_xmlrpc_server.py roundup/security.py roundup/xmlrpc.py scripts/imapServer.py share/roundup/templates/classic/detectors/userauditor.py share/roundup/templates/devel/detectors/userauditor.py share/roundup/templates/devel/extensions/timestamp.py share/roundup/templates/jinja2/detectors/userauditor.py share/roundup/templates/minimal/detectors/userauditor.py share/roundup/templates/responsive/detectors/userauditor.py share/roundup/templates/responsive/extensions/timestamp.py test/memorydb.py test/test_actions.py test/test_locking.py test/test_mailgw.py test/test_templating.py website/issues/detectors/newissuecopy.py website/issues/detectors/userauditor.py website/issues/extensions/timestamp.py
diffstat 44 files changed, 245 insertions(+), 249 deletions(-) [+]
line wrap: on
line diff
--- a/detectors/creator_resolution.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/detectors/creator_resolution.py	Tue Jul 24 21:39:58 2018 +0000
@@ -24,7 +24,7 @@
     if assignedto == creator:
         if db.getuid() != creator:
             name = db.user.get(creator, 'username')
-            raise Reject, 'Only the creator (%s) may close this issue'%name
+            raise Reject('Only the creator (%s) may close this issue'%name)
         return
 
     # set the assignedto and status
--- a/frontends/ZRoundup/ZRoundup.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/frontends/ZRoundup/ZRoundup.py	Tue Jul 24 21:39:58 2018 +0000
@@ -208,7 +208,7 @@
             client.main()
             return ''
         except client.NotFound:
-            raise 'NotFound', REQUEST.URL
+            raise Exception('NotFound ' + REQUEST.URL)
             pass
         except:
             import traceback
--- a/frontends/ZRoundup/__init__.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/frontends/ZRoundup/__init__.py	Tue Jul 24 21:39:58 2018 +0000
@@ -32,7 +32,7 @@
     if not os.path.exists(path):
         path = os.path.join(here, 'lib', 'python', 'Products', 'ZRoundup')
         if not os.path.exists(path):
-            raise ValueError, "Can't determine where ZRoundup is installed"
+            raise ValueError("Can't determine where ZRoundup is installed")
 
 # product initialisation
 from ZRoundup import ZRoundup, manage_addZRoundupForm, manage_addZRoundup
--- a/roundup/backends/back_anydbm.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/backends/back_anydbm.py	Tue Jul 24 21:39:58 2018 +0000
@@ -241,12 +241,12 @@
         """A convenient way of calling self.getclass(classname)."""
         if classname in self.classes:
             return self.classes[classname]
-        raise AttributeError, classname
+        raise AttributeError(classname)
 
     def addclass(self, cl):
         cn = cl.classname
         if cn in self.classes:
-            raise ValueError, cn
+            raise ValueError(cn)
         self.classes[cn] = cl
 
         # add default Edit and View permissions
@@ -1186,10 +1186,10 @@
             return propvalues
 
         if 'creation' in propvalues or 'activity' in propvalues:
-            raise KeyError, '"creation" and "activity" are reserved'
+            raise KeyError('"creation" and "activity" are reserved')
 
         if 'id' in propvalues:
-            raise KeyError, '"id" is reserved'
+            raise KeyError('"id" is reserved')
 
         if self.db.journaltag is None:
             raise hyperdb.DatabaseError(_('Database open read-only'))
--- a/roundup/backends/back_mysql.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/backends/back_mysql.py	Tue Jul 24 21:39:58 2018 +0000
@@ -184,7 +184,7 @@
                 raise
         except MySQLdb.ProgrammingError as message:
             if message[0] != ER.NO_SUCH_TABLE:
-                raise hyperdb.DatabaseError, message
+                raise hyperdb.DatabaseError(message)
             self.init_dbschema()
             self.sql("CREATE TABLE `schema` (`schema` TEXT) ENGINE=%s"%
                 self.mysql_backend)
@@ -660,7 +660,7 @@
         # then the snapshot has already been established.
         if e[0] == ER.DUP_ENTRY:
             key = propvalues[self.key]
-            raise ValueError, 'node with key "%s" exists' % key
+            raise ValueError('node with key "%s" exists' % key)
         # We don't know what this exception is; reraise it.
         raise
         
--- a/roundup/cgi/PageTemplates/Expressions.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/cgi/PageTemplates/Expressions.py	Tue Jul 24 21:39:58 2018 +0000
@@ -57,7 +57,7 @@
 def acquisition_security_filter(orig, inst, name, v, real_validate):
     if real_validate(orig, inst, name, v):
         return 1
-    raise Unauthorized, name
+    raise Unauthorized(name)
 
 def call_with_ns(f, ns, arg=1):
     if arg==2:
@@ -102,7 +102,7 @@
         self._path = path = path.strip().split('/')
         self._base = base = path.pop(0)
         if base and not _valid_name(base):
-            raise CompilerError, 'Invalid variable name "%s"' % base
+            raise CompilerError('Invalid variable name "%s"' % base)
         # Parse path
         self._dp = dp = []
         for i in range(len(path)):
@@ -220,7 +220,7 @@
                     exp = exp[m.end():]
                     m = _interp.search(exp)
                 if '$' in exp:
-                    raise CompilerError, (
+                    raise CompilerError(
                         '$ must be doubled or followed by a simple path')
                 parts.append(exp)
             expr = ''.join(parts)
@@ -308,7 +308,7 @@
             o = object[name]
             # Check access to the item.
             if not validate(object, object, name, o):
-                raise Unauthorized, name
+                raise Unauthorized(name)
             object = o
             continue
 
--- a/roundup/cgi/PageTemplates/MultiMapping.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/cgi/PageTemplates/MultiMapping.py	Tue Jul 24 21:39:58 2018 +0000
@@ -7,14 +7,14 @@
         for store in self.stores:
             if store.has_key(key):
                 return store[key]
-        raise KeyError, key
+        raise KeyError(key)
     _marker = []
     def get(self, key, default=_marker):
         for store in self.stores:
             if store.has_key(key):
                 return store[key]
         if default is self._marker:
-            raise KeyError, key
+            raise KeyError(key)
         return default
     def __len__(self):
         return reduce(operator.add, [len(x) for x in self.stores], 0)
--- a/roundup/cgi/PageTemplates/PageTemplate.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/cgi/PageTemplates/PageTemplate.py	Tue Jul 24 21:39:58 2018 +0000
@@ -81,7 +81,7 @@
         __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
 
         if self._v_errors:
-            raise PTRuntimeError, 'Page Template %s has errors.' % self.id
+            raise PTRuntimeError('Page Template %s has errors.' % self.id)
         output = self.StringIO()
         c = self.pt_getContext()
         c.update(extra_context)
@@ -119,13 +119,13 @@
             self._cook()
         __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
         if self._v_errors:
-            raise PTRuntimeError, 'Page Template %s has errors.' % self.id
+            raise PTRuntimeError('Page Template %s has errors.' % self.id)
         return self._v_macros
 
     def __getattr__(self, name):
         if name == 'macros':
             return self.pt_macros()
-        raise AttributeError, name
+        raise AttributeError(name)
 
     def pt_source_file(self):
         return None  # Unknown.
--- a/roundup/cgi/PageTemplates/PythonExpr.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/cgi/PageTemplates/PythonExpr.py	Tue Jul 24 21:39:58 2018 +0000
@@ -34,8 +34,8 @@
             exec 'def f():\n return %s\n' % expr.strip() in d
             self._f = d['f']
         except:
-            raise CompilerError, ('Python expression error:\n'
-                                  '%s: %s') % exc_info()[:2]
+            raise CompilerError(('Python expression error:\n'
+                                 '%s: %s') % exc_info()[:2])
         self._get_used_names()
 
     def _get_used_names(self):
--- a/roundup/cgi/PageTemplates/TALES.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/cgi/PageTemplates/TALES.py	Tue Jul 24 21:39:58 2018 +0000
@@ -112,10 +112,10 @@
 
     def registerType(self, name, handler):
         if not _valid_name(name):
-            raise RegistrationError, 'Invalid Expression type "%s".' % name
+            raise RegistrationError('Invalid Expression type "%s".' % name)
         types = self.types
         if types.has_key(name):
-            raise RegistrationError, (
+            raise RegistrationError(
                 'Multiple registrations for Expression type "%s".' %
                 name)
         types[name] = handler
@@ -134,7 +134,7 @@
         try:
             handler = self.types[type]
         except KeyError:
-            raise CompilerError, (
+            raise CompilerError(
                 'Unrecognized expression type "%s".' % type)
         return handler(type, expr, self)
 
--- a/roundup/cgi/TAL/TALInterpreter.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/cgi/TAL/TALInterpreter.py	Tue Jul 24 21:39:58 2018 +0000
@@ -755,4 +755,4 @@
 
 
 def _write_ValueError(s):
-    raise ValueError, "I/O operation on closed file"
+    raise ValueError("I/O operation on closed file")
--- a/roundup/cgi/ZTUtils/Batch.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/cgi/ZTUtils/Batch.py	Tue Jul 24 21:39:58 2018 +0000
@@ -86,10 +86,10 @@
 
     def __getitem__(self, index):
         if index < 0:
-            if index + self.end < self.first: raise IndexError, index
+            if index + self.end < self.first: raise IndexError(index)
             return self._sequence[index + self.end]
         
-        if index >= self.length: raise IndexError, index
+        if index >= self.length: raise IndexError(index)
         return self._sequence[index + self.first]
 
     def __len__(self):
--- a/roundup/cgi/ZTUtils/Iterator.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/cgi/ZTUtils/Iterator.py	Tue Jul 24 21:39:58 2018 +0000
@@ -38,7 +38,7 @@
         try:
             inner = getattr(self._inner, 'it_' + name)
         except AttributeError:
-            raise AttributeError, name
+            raise AttributeError(name)
         return inner(self)
 
     def next(self):
--- a/roundup/cgi/actions.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/cgi/actions.py	Tue Jul 24 21:39:58 2018 +0000
@@ -413,7 +413,7 @@
                     try:
                         float(self.form[key].value)
                     except ValueError:
-                        raise exceptions.FormError, "Invalid number: "+self.form[key].value
+                        raise exceptions.FormError("Invalid number: "+self.form[key].value)
                 elif isinstance(prop, hyperdb.Integer):
                     try:
                         val=self.form[key].value
@@ -422,7 +422,7 @@
                         else:
                             raise ValueError
                     except ValueError:
-                        raise exceptions.FormError, "Invalid integer: "+val
+                        raise exceptions.FormError("Invalid integer: "+val)
 
             self.form.value.append(cgi.MiniFieldStorage('@filter', key))
 
@@ -1159,7 +1159,7 @@
         # we don't the user gets an invalid CSRF token error
         # As above choose the home page since everybody can
         # see that.
-        raise exceptions.Redirect, self.base
+        raise exceptions.Redirect(self.base)
 
 class LoginAction(Action):
     def handle(self):
--- a/roundup/cgi/client.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/cgi/client.py	Tue Jul 24 21:39:58 2018 +0000
@@ -1028,7 +1028,7 @@
             if (config["WEB_CSRF_ENFORCE_HEADER_%s"%header] == 'required'
                     and "HTTP_%s"%header not in self.env):
                 logger.error(self._("csrf header %s required but missing for user%s."), header, current_user)
-                raise Unauthorised, self._("Missing header: %s")%header
+                raise Unauthorised(self._("Missing header: %s")%header)
                 
         # self.base always matches: ^https?://hostname
         enforce=config['WEB_CSRF_ENFORCE_HEADER_REFERER']
@@ -1039,7 +1039,7 @@
             if foundat != 0:
                 if enforce in ('required', 'yes'):
                     logger.error(self._("csrf Referer header check failed for user%s. Value=%s"), current_user, referer)
-                    raise Unauthorised, self._("Invalid Referer %s, %s")%(referer,self.base)
+                    raise Unauthorised(self._("Invalid Referer %s, %s")%(referer,self.base))
                 elif enforce == 'logfailure':
                     logger.warning(self._("csrf Referer header check failed for user%s. Value=%s"), current_user, referer)
             else:
@@ -1055,7 +1055,7 @@
             if foundat != 0:
                 if enforce in ('required', 'yes'):
                     logger.error(self._("csrf Origin header check failed for user%s. Value=%s"), current_user, origin)
-                    raise Unauthorised, self._("Invalid Origin %s"%origin)
+                    raise Unauthorised(self._("Invalid Origin %s"%origin))
                 elif enforce == 'logfailure':
                     logger.warning(self._("csrf Origin header check failed for user%s. Value=%s"), current_user, origin)
             else:
@@ -1070,7 +1070,7 @@
                 if foundat not in [4, 5]:
                     if enforce in ('required', 'yes'):
                         logger.error(self._("csrf X-FORWARDED-HOST header check failed for user%s. Value=%s"), current_user, host)
-                        raise Unauthorised, self._("Invalid X-FORWARDED-HOST %s")%host
+                        raise Unauthorised(self._("Invalid X-FORWARDED-HOST %s")%host)
                     elif enforce == 'logfailure':
                         logger.warning(self._("csrf X-FORWARDED-HOST header check failed for user%s. Value=%s"), current_user, host)
                 else:
@@ -1090,7 +1090,7 @@
                 if foundat not in [4, 5]:
                     if enforce in ('required', 'yes'):
                         logger.error(self._("csrf HOST header check failed for user%s. Value=%s"), current_user, host)
-                        raise Unauthorised, self._("Invalid HOST %s")%host
+                        raise Unauthorised(self._("Invalid HOST %s")%host)
                     elif enforce == 'logfailure':
                         logger.warning(self._("csrf HOST header check failed for user%s. Value=%s"), current_user, host)
                 else:
@@ -1099,7 +1099,7 @@
         enforce=config['WEB_CSRF_HEADER_MIN_COUNT']
         if header_pass < enforce:
             logger.error(self._("Csrf: unable to verify sufficient headers"))
-            raise UsageError, self._("Unable to verify sufficient headers")
+            raise UsageError(self._("Unable to verify sufficient headers"))
 
         enforce=config['WEB_CSRF_ENFORCE_HEADER_X-REQUESTED-WITH']
         if xmlrpc:
@@ -1113,7 +1113,7 @@
                 # see: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Protecting_REST_Services:_Use_of_Custom_Request_Headers
                 if 'HTTP_X-REQUESTED-WITH' not in self.env:
                     logger.error(self._("csrf X-REQUESTED-WITH xmlrpc required header check failed for user%s."), current_user)
-                    raise UsageError, self._("Required Header Missing")
+                    raise UsageError(self._("Required Header Missing"))
 
         # Expire old csrf tokens now so we don't use them.  These will
         # be committed after the otks.destroy below.  Note that the
@@ -1151,7 +1151,7 @@
         if key is None: # we do not have an @csrf token
             if enforce == 'required':
                 logger.error(self._("Required csrf field missing for user%s"), current_user)
-                raise UsageError, self._("Csrf token is missing.")
+                raise UsageError(self._("Csrf token is missing."))
             elif enforce == 'logfailure':
                     # FIXME include url
                     logger.warning(self._("csrf field not supplied by user%s"), current_user)
@@ -1203,7 +1203,7 @@
                 logger.error(
                     self._("Csrf mismatch user: current user %s != stored user %s, current session, stored session: %s,%s for key %s."),
                     current_user, nonce_user, current_session, nonce_session, key)
-                raise UsageError, self._("Invalid csrf token found: %s")%key
+                raise UsageError(self._("Invalid csrf token found: %s")%key)
             elif enforce == 'logfailure':
                 logger.warning(
                     self._("logged only: Csrf mismatch user: current user %s != stored user %s, current session, stored session: %s,%s for key %s."),
@@ -1213,7 +1213,7 @@
                 logger.error(
                     self._("Csrf mismatch user: current session %s != stored session %s, current user/stored user is: %s for key %s."),
                     current_session, nonce_session, current_user, key)
-                raise UsageError, self._("Invalid csrf session found: %s")%key
+                raise UsageError(self._("Invalid csrf session found: %s")%key)
             elif enforce == 'logfailure':
                     logger.warning(
                         self._("logged only: Csrf mismatch user: current session %s != stored session %s, current user/stored user is: %s for key %s."),
@@ -1685,7 +1685,10 @@
                 # receive an error message, and the adminstrator will
                 # receive a traceback, albeit with less information
                 # than the one we tried to generate above.
-                raise exc_info[0], exc_info[1], exc_info[2]
+                if sys.version_info[0] > 2:
+                    raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
+                else:
+                    exec('raise exc_info[0], exc_info[1], exc_info[2]')
 
     # these are the actions that are available
     actions = (
--- a/roundup/cgi/engine_zopetal.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/cgi/engine_zopetal.py	Tue Jul 24 21:39:58 2018 +0000
@@ -79,8 +79,7 @@
         __traceback_supplement__ = (PageTemplate.PageTemplateTracebackSupplement, self)
 
         if self._v_errors:
-            raise PageTemplate.PTRuntimeError, \
-                'Page Template %s has errors.'%self.id
+            raise PageTemplate.PTRuntimeError('Page Template %s has errors.'%self.id)
 
         # figure the context
         c = context(client, self, classname, request)
--- a/roundup/cgi/templating.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/cgi/templating.py	Tue Jul 24 21:39:58 2018 +0000
@@ -200,7 +200,7 @@
         try:
             return self.load(name)
         except NoTemplate as message:
-            raise KeyError, message
+            raise KeyError(message)
 
 class MultiLoader(LoaderBase):
     def __init__(self):
@@ -225,7 +225,7 @@
         try:
             return self.load(name)
         except NoTemplate as message:
-            raise KeyError, message
+            raise KeyError(message)
         
 
 class TemplateBase:
@@ -373,7 +373,7 @@
         try:
             return self[attr]
         except KeyError:
-            raise AttributeError, attr
+            raise AttributeError(attr)
 
     def classes(self):
         l = self._client.db.classes.keys()
@@ -563,7 +563,7 @@
         try:
             prop = self._props[item]
         except KeyError:
-            raise KeyError, 'No such property "%s" on %s'%(item, self.classname)
+            raise KeyError('No such property "%s" on %s'%(item, self.classname))
 
         # look up the correct HTMLProperty class
         for klass, htmlklass in propclasses:
@@ -574,14 +574,14 @@
                 value, self._anonymous)
 
         # no good
-        raise KeyError, item
+        raise KeyError(item)
 
     def __getattr__(self, attr):
         """ convenience access """
         try:
             return self[attr]
         except KeyError:
-            raise AttributeError, attr
+            raise AttributeError(attr)
 
     def designator(self):
         """ Return this class' designator (classname) """
@@ -879,7 +879,7 @@
         prop = self._props[items[0]]
 
         if has_rest and not isinstance(prop, (hyperdb.Link, hyperdb.Multilink)):
-            raise KeyError, item
+            raise KeyError(item)
 
         # get the value, handling missing values
         value = None
@@ -902,14 +902,14 @@
                 return htmlprop[items[1]]
             return htmlprop
 
-        raise KeyError, item
+        raise KeyError(item)
 
     def __getattr__(self, attr):
         """ convenience access to properties """
         try:
             return self[attr]
         except KeyError:
-            raise AttributeError, attr
+            raise AttributeError(attr)
 
     def designator(self):
         """Return this item's designator (classname + id)."""
@@ -966,7 +966,7 @@
                 try:
                     template = self._client.selectTemplate(classname, 'item')
                     if template.startswith('_generic.'):
-                        raise NoTemplate, 'not really...'
+                        raise NoTemplate('not really...')
                 except NoTemplate:
                     pass
                 else:
@@ -1029,7 +1029,7 @@
                             template = self._client.selectTemplate(classname,
                                'item')
                             if template.startswith('_generic.'):
-                                raise NoTemplate, 'not really...'
+                                raise NoTemplate('not really...')
                             hrefable = 1
                         except NoTemplate:
                             hrefable = 0
@@ -1883,9 +1883,9 @@
                 elif isinstance(default, DateHTMLProperty):
                     raw_value = default._value
                 else:
-                    raise ValueError, self._('default value for '
+                    raise ValueError(self._('default value for '
                         'DateHTMLProperty must be either DateHTMLProperty '
-                        'or string date representation.')
+                        'or string date representation.'))
         elif isinstance(value, str) or isinstance(value, unicode):
             # most likely erroneous input to be passed back to user
             if isinstance(value, unicode): value = value.encode('utf8')
@@ -2270,7 +2270,7 @@
 
     def __getattr__(self, attr):
         """ no extended attribute accesses make sense here """
-        raise AttributeError, attr
+        raise AttributeError(attr)
 
     def viewableGenerator(self, values):
         """Used to iterate over only the View'able items in a class."""
@@ -2999,11 +2999,11 @@
     # overwrite so we can late-instantiate the HTMLItem instance
     def __getitem__(self, index):
         if index < 0:
-            if index + self.end < self.first: raise IndexError, index
+            if index + self.end < self.first: raise IndexError(index)
             return self._sequence[index + self.end]
 
         if index >= self.length:
-            raise IndexError, index
+            raise IndexError(index)
 
         # move the last_item along - but only if the fetched index changes
         # (for some reason, index 0 is fetched twice)
@@ -3078,9 +3078,9 @@
         """Try the tracker's templating_utils."""
         if not hasattr(self.client.instance, 'templating_utils'):
             # backwards-compatibility
-            raise AttributeError, name
+            raise AttributeError(name)
         if not self.client.instance.templating_utils.has_key(name):
-            raise AttributeError, name
+            raise AttributeError(name)
         return self.client.instance.templating_utils[name]
 
     def keywords_expressions(self, request):
--- a/roundup/cgi/wsgi_handler.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/cgi/wsgi_handler.py	Tue Jul 24 21:39:58 2018 +0000
@@ -79,6 +79,6 @@
 
     def get_wfile(self):
         if self.__wfile is None:
-            raise ValueError, 'start_response() not called'
+            raise ValueError('start_response() not called')
         return self.__wfile
 
--- a/roundup/date.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/date.py	Tue Jul 24 21:39:58 2018 +0000
@@ -142,7 +142,7 @@
     elif tz in _tzoffsets:
         return SimpleTimezone(_tzoffsets[tz], tz)
     else:
-        raise KeyError, tz
+        raise KeyError(tz)
 
 def _utc_to_local(y,m,d,H,M,S,tz):
     TZ = get_timezone(tz)
@@ -349,7 +349,7 @@
             # making sure we match the precision of serialise()
             self.second = min(self.second, 59.999)
         except:
-            raise ValueError, 'Unknown spec %r' % (spec,)
+            raise ValueError('Unknown spec %r' % (spec,))
 
     def now(self):
         """ To be able to override for testing
@@ -373,9 +373,9 @@
         # not serialised data, try usual format
         m = date_re.match(spec)
         if m is None:
-            raise ValueError, self._('Not a date spec: %r '
+            raise ValueError(self._('Not a date spec: %r '
                 '("yyyy-mm-dd", "mm-dd", "HH:MM", "HH:MM:SS" or '
-                '"yyyy-mm-dd.HH:MM:SS.SSS")' % spec)
+                '"yyyy-mm-dd.HH:MM:SS.SSS")' % spec))
 
         info = m.groupdict()
 
@@ -448,9 +448,9 @@
             try:
                 self.applyInterval(Interval(info['o'], allowdate=0))
             except ValueError:
-                raise ValueError, self._('%r not a date / time spec '
+                raise ValueError(self._('%r not a date / time spec '
                     '"yyyy-mm-dd", "mm-dd", "HH:MM", "HH:MM:SS" or '
-                    '"yyyy-mm-dd.HH:MM:SS.SSS"')%(spec,)
+                    '"yyyy-mm-dd.HH:MM:SS.SSS"')%(spec,))
 
         if info.get('tz', None):
             tz     = info ['tz'].strip ()
@@ -784,9 +784,9 @@
         if not m:
             m = interval_re.match(spec)
             if not m:
-                raise ValueError, self._('Not an interval spec: "%s"'
+                raise ValueError(self._('Not an interval spec: "%s"'
                     ' ([+-] [#y] [#m] [#w] [#d] [[[H]H:MM]:SS] [date spec])'
-                    % spec)
+                    % spec))
         else:
             allowdate = 0
 
@@ -807,9 +807,9 @@
 
         # make sure it's valid
         if not valid and not info['D']:
-            raise ValueError, self._('Not an interval spec: "%s"'
+            raise ValueError(self._('Not an interval spec: "%s"'
                 ' ([+-] [#y] [#m] [#w] [#d] [[[H]H:MM]:SS])'
-                % spec)
+                % spec))
 
         if self.week:
             self.day = self.day + self.week*7
@@ -867,7 +867,7 @@
             i = fixTimeOverflow(i)
             return Interval(i, translator=self.translator)
         # nope, no idea what to do with this other...
-        raise TypeError, "Can't add %r"%other
+        raise TypeError("Can't add %r"%other)
 
     def __sub__(self, other):
         if isinstance(other, Date):
@@ -887,7 +887,7 @@
             i = fixTimeOverflow(i)
             return Interval(i, translator=self.translator)
         # nope, no idea what to do with this other...
-        raise TypeError, "Can't add %r"%other
+        raise TypeError("Can't add %r"%other)
 
     def __div__(self, other):
         """ Divide this interval by an int value.
@@ -898,13 +898,13 @@
         try:
             other = float(other)
         except TypeError:
-            raise ValueError, "Can only divide Intervals by numbers"
+            raise ValueError("Can only divide Intervals by numbers")
 
         y, m, d, H, M, S = (self.year, self.month, self.day,
             self.hour, self.minute, self.second)
         if y or m:
             if d or H or M or S:
-                raise ValueError, "Can't divide Interval with date and time"
+                raise ValueError("Can't divide Interval with date and time")
             months = self.year*12 + self.month
             months *= self.sign
 
@@ -1192,7 +1192,7 @@
                 self.from_value = Type(spec, **params)
                 self.to_value = Type(spec, add_granularity=True, **params)
             else:
-                raise ValueError, "Invalid range"
+                raise ValueError("Invalid range")
 
     def __str__(self):
         return "from %s to %s" % (self.from_value, self.to_value)
--- a/roundup/hyperdb.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/hyperdb.py	Tue Jul 24 21:39:58 2018 +0000
@@ -87,8 +87,7 @@
         try:
             return password.Password(encrypted=value, scheme=self.scheme, strict=True)
         except password.PasswordValueError as message:
-            raise HyperdbValueError, \
-                    _('property %s: %s')%(kw['propname'], message)
+            raise HyperdbValueError(_('property %s: %s')%(kw['propname'], message))
 
     def sort_repr (self, cls, val, name):
         if not val:
@@ -110,8 +109,8 @@
         try:
             value = date.Date(value, self.offset(db))
         except ValueError as message:
-            raise HyperdbValueError, _('property %s: %r is an invalid '\
-                'date (%s)')%(kw['propname'], value, message)
+            raise HyperdbValueError(_('property %s: %r is an invalid '\
+                'date (%s)')%(kw['propname'], value, message))
         return value
     def range_from_raw(self, value, db):
         """return Range value from given raw value with offset correction"""
@@ -127,8 +126,8 @@
         try:
             value = date.Interval(value)
         except ValueError as message:
-            raise HyperdbValueError, _('property %s: %r is an invalid '\
-                'date interval (%s)')%(kw['propname'], value, message)
+            raise HyperdbValueError(_('property %s: %r is an invalid '\
+                'date interval (%s)')%(kw['propname'], value, message))
         return value
     def sort_repr (self, cls, val, name):
         if not val:
@@ -257,8 +256,8 @@
                 try:
                     curvalue.remove(itemid)
                 except ValueError:
-                    raise HyperdbValueError, _('property %s: %r is not ' \
-                        'currently an element')%(propname, item)
+                    raise HyperdbValueError(_('property %s: %r is not ' \
+                        'currently an element')%(propname, item))
             else:
                 newvalue.append(itemid)
                 if itemid not in curvalue:
@@ -311,8 +310,8 @@
         try:
             value = float(value)
         except ValueError:
-            raise HyperdbValueError, _('property %s: %r is not a number')%(
-                kw['propname'], value)
+            raise HyperdbValueError(_('property %s: %r is not a number')%(
+                kw['propname'], value))
         return value
 
 class Integer(_Type):
@@ -322,8 +321,8 @@
         try:
             value = int(value)
         except ValueError:
-            raise HyperdbValueError, _('property %s: %r is not an integer')%(
-                kw['propname'], value)
+            raise HyperdbValueError(_('property %s: %r is not an integer')%(
+                kw['propname'], value))
         return value
 #
 # Support for splitting designators
@@ -335,7 +334,7 @@
     """
     m = dre.match(designator)
     if m is None:
-        raise DesignatorError, _('"%s" not a node designator')%designator
+        raise DesignatorError(_('"%s" not a node designator')%designator)
     return m.group(1), m.group(2)
 
 class Proptree(object):
@@ -830,7 +829,7 @@
         into something python can use more easily
     '''
     if not roles or not roles.strip():
-        raise StopIteration, "Empty roles given"
+        raise StopIteration("Empty roles given")
     for role in [x.lower().strip() for x in roles.split(',')]:
         yield role
 
@@ -854,8 +853,8 @@
         """
         for name in 'creation activity creator actor'.split():
             if properties.has_key(name):
-                raise ValueError, '"creation", "activity", "creator" and '\
-                    '"actor" are reserved'
+                raise ValueError('"creation", "activity", "creator" and '\
+                    '"actor" are reserved')
 
         self.classname = classname
         self.properties = properties
@@ -1160,7 +1159,7 @@
            resolution order.
         """
         if labelprop not in self.getprops():
-            raise ValueError, _("Not a property name: %s") % labelprop
+            raise ValueError(_("Not a property name: %s") % labelprop)
         self._labelprop = labelprop
 
     def setorderprop(self, orderprop):
@@ -1168,7 +1167,7 @@
            resolution order
         """
         if orderprop not in self.getprops():
-            raise ValueError, _("Not a property name: %s") % orderprop
+            raise ValueError(_("Not a property name: %s") % orderprop)
         self._orderprop = orderprop
 
     def getkey(self):
@@ -1567,11 +1566,11 @@
             try:
                 value = linkcl.lookup(value)
             except KeyError as message:
-                raise HyperdbValueError, _('property %s: %r is not a %s.')%(
-                    propname, value, prop.classname)
+                raise HyperdbValueError(_('property %s: %r is not a %s.')%(
+                    propname, value, prop.classname))
         else:
-            raise HyperdbValueError, _('you may only enter ID values '\
-                'for property %s')%propname
+            raise HyperdbValueError(_('you may only enter ID values '\
+                'for property %s')%propname)
     return value
 
 def fixNewlines(text):
@@ -1602,8 +1601,8 @@
     try:
         proptype =  properties[propname]
     except KeyError:
-        raise HyperdbValueError, _('%r is not a property of %s')%(propname,
-            klass.classname)
+        raise HyperdbValueError(_('%r is not a property of %s')%(propname,
+            klass.classname))
 
     # if we got a string, strip it now
     if isinstance(value, type('')):
@@ -1708,14 +1707,14 @@
             # exceptions should pass through untrapped
             pass
         # nope, no such attribute
-        raise AttributeError, str(value)
+        raise AttributeError(str(value))
     def __getitem__(self, name):
         return self.cl.get(self.nodeid, name)
     def __setattr__(self, name, value):
         try:
             return self.cl.set(self.nodeid, **{name: value})
         except KeyError as value:
-            raise AttributeError, str(value)
+            raise AttributeError(str(value))
     def __setitem__(self, name, value):
         self.cl.set(self.nodeid, **{name: value})
     def history(self, enforceperm=True, skipquiet=True):
--- a/roundup/instance.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/instance.py	Tue Jul 24 21:39:58 2018 +0000
@@ -168,8 +168,7 @@
                         continue
                     linkto = prop.classname
                     if linkto not in classes:
-                        raise ValueError, \
-                            ("property %s.%s links to non-existent class %s"
+                        raise ValueError("property %s.%s links to non-existent class %s"
                              % (classname, propname, linkto))
 
             db.post_init()
@@ -281,13 +280,13 @@
         import imp
         # sanity check existence of tracker home
         if not os.path.exists(tracker_home):
-            raise ValueError, 'no such directory: "%s"'%tracker_home
+            raise ValueError('no such directory: "%s"'%tracker_home)
 
         # sanity check tracker home contents
         for reqd in 'config dbinit select_db interfaces'.split():
             if not os.path.exists(os.path.join(tracker_home, '%s.py'%reqd)):
-                raise TrackerError, 'File "%s.py" missing from tracker '\
-                    'home "%s"'%(reqd, tracker_home)
+                raise TrackerError('File "%s.py" missing from tracker '\
+                    'home "%s"'%(reqd, tracker_home))
 
         if self.trackers.has_key(tracker_home):
             return imp.load_package(self.trackers[tracker_home],
@@ -304,8 +303,7 @@
         # ensure the tracker has all the required bits
         for required in 'open init Client MailGW'.split():
             if not hasattr(tracker, required):
-                raise TrackerError, \
-                    'Required tracker attribute "%s" missing'%required
+                raise TrackerError('Required tracker attribute "%s" missing'%required)
 
         # load and apply the config
         tracker.config = configuration.CoreConfig(tracker_home)
--- a/roundup/mailgw.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/mailgw.py	Tue Jul 24 21:39:58 2018 +0000
@@ -207,9 +207,9 @@
 
     # we couldn't find a key belonging to the author of the email
     if sigs:
-        raise MailUsageError, _("Message signed with unknown key: %s") % sig.fpr
+        raise MailUsageError(_("Message signed with unknown key: %s") % sig.fpr)
     elif not may_be_unsigned:
-        raise MailUsageError, _("Unsigned Message")
+        raise MailUsageError(_("Unsigned Message"))
 
 class Message(mimetools.Message):
     ''' subclass mimetools.Message so we can retrieve the parts of the
@@ -503,8 +503,7 @@
         # the message meets the RFC before we try to decrypt it.
         if hdr.getbody().strip() != 'Version: 1' \
            or hdr.gettype() != 'application/pgp-encrypted':
-            raise MailUsageError, \
-                _("Unknown multipart/encrypted version.")
+            raise MailUsageError(_("Unknown multipart/encrypted version."))
 
         context = pyme.core.Context()
         ciphertext = pyme.core.Data(msg.getbody())
@@ -513,7 +512,7 @@
         result = context.op_decrypt_verify(ciphertext, plaintext)
 
         if result:
-            raise MailUsageError, _("Unable to decrypt your message.")
+            raise MailUsageError(_("Unable to decrypt your message."))
 
         # we've decrypted it but that just means they used our public
         # key to send it to us. now check the signatures to see if it
@@ -542,8 +541,7 @@
         (msg, sig) = self.getparts()
 
         if sig.gettype() != 'application/pgp-signature':
-            raise MailUsageError, \
-                _("No PGP signature found in message.")
+            raise MailUsageError(_("No PGP signature found in message."))
 
         # msg.getbody() is skipping over some headers that are
         # required to be present for verification to succeed so
@@ -619,9 +617,9 @@
         ''' Check to see if the message contains a valid subject line
         '''
         if not self.subject:
-            raise MailUsageError, _("""
+            raise MailUsageError(_("""
 Emails to Roundup trackers must include a Subject: line!
-""")
+"""))
 
     def parse_subject(self):
         ''' Matches subjects like:
@@ -728,7 +726,7 @@
             classname = self.matches['classname']
 
         if not classname and self.has_prefix and self.pfxmode == 'strict':
-            raise MailUsageError, _("""
+            raise MailUsageError(_("""
 The message you sent to roundup did not contain a properly formed subject
 line. The subject must contain a class name or designator to indicate the
 'topic' of the message. For example:
@@ -740,7 +738,7 @@
         in the tracker.
 
 Subject was: '%(subject)s'
-""") % locals()
+""") % locals())
 
         # try to get the class specified - if "loose" or "none" then fall
         # back on the default
@@ -766,15 +764,15 @@
         if not self.cl:
             validname = ', '.join(self.db.getclasses())
             if classname:
-                raise MailUsageError, _("""
+                raise MailUsageError(_("""
 The class name you identified in the subject line ("%(classname)s") does
 not exist in the database.
 
 Valid class names are: %(validname)s
 Subject was: "%(subject)s"
-""") % locals()
+""") % locals())
             else:
-                raise MailUsageError, _("""
+                raise MailUsageError(_("""
 You did not identify a class name in the subject line and there is no
 default set for this tracker. The subject must contain a class name or
 designator to indicate the 'topic' of the message. For example:
@@ -786,7 +784,7 @@
         in the tracker.
 
 Subject was: '%(subject)s'
-""") % locals()
+""") % locals())
         # get the class properties
         self.properties = self.cl.getprops()
         
@@ -812,13 +810,13 @@
 
         # but we do need either a title or a nodeid...
         if nodeid is None and not title:
-            raise MailUsageError, _("""
+            raise MailUsageError(_("""
 I cannot match your message to a node in the database - you need to either
 supply a full designator (with number, eg "[issue123]") or keep the
 previous subject title intact so I can match that.
 
 Subject was: "%(subject)s"
-""") % locals()
+""") % locals())
 
         # If there's no nodeid, check to see if this is a followup and
         # maybe someone's responded to the initial mail that created an
@@ -845,12 +843,12 @@
         # if a nodeid was specified, make sure it's valid
         if nodeid is not None and not self.cl.hasnode(nodeid):
             if self.pfxmode == 'strict':
-                raise MailUsageError, _("""
+                raise MailUsageError(_("""
 The node specified by the designator in the subject of your message
 ("%(nodeid)s") does not exist.
 
 Subject was: "%(subject)s"
-""") % locals()
+""") % locals())
             else:
                 nodeid = None
         self.nodeid = nodeid
@@ -890,15 +888,15 @@
 
 ...before sending mail to the tracker.""" % locals()
 
-                raise Unauthorized, _("""
+                raise Unauthorized(_("""
 You are not a registered user.%(registration_info)s
 
 Unknown address: %(from_address)s
-""") % locals()
+""") % locals())
             else:
                 # we're registered and we're _still_ not allowed access
-                raise Unauthorized, _(
-                    'You are not permitted to access this tracker.')
+                raise Unauthorized(_(
+                    'You are not permitted to access this tracker.'))
         self.author = author
 
     def check_permissions(self):
@@ -908,15 +906,15 @@
         if self.nodeid:
             if not self.db.security.hasPermission('Edit', self.author,
                     self.classname, itemid=self.nodeid):
-                raise Unauthorized, _(
+                raise Unauthorized(_(
                     'You are not permitted to edit %(classname)s.'
-                    ) % self.__dict__
+                    ) % self.__dict__)
         else:
             if not self.db.security.hasPermission('Create', self.author,
                     self.classname):
-                raise Unauthorized, _(
+                raise Unauthorized(_(
                     'You are not permitted to create %(classname)s.'
-                    ) % self.__dict__
+                    ) % self.__dict__)
 
     def commit_and_reopen_as_author(self):
         ''' the author may have been created - make sure the change is
@@ -996,12 +994,12 @@
                 if errors:
                     if self.sfxmode == 'strict':
                         errors = '\n- '.join(map(str, errors))
-                        raise MailUsageError, _("""
+                        raise MailUsageError(_("""
 There were problems handling your subject line argument list:
 - %(errors)s
 
 Subject was: "%(subject)s"
-""") % locals()
+""") % locals())
                     else:
                         title += ' ' + argswhole
 
@@ -1048,9 +1046,9 @@
                 os.environ['GNUPGHOME'] = self.config.PGP_HOMEDIR
             if self.config.PGP_REQUIRE_INCOMING in ('encrypted', 'both') \
                 and pgp_role() and not self.message.pgp_encrypted():
-                raise MailUsageError, _(
+                raise MailUsageError(_(
                     "This tracker has been configured to require all email "
-                    "be PGP encrypted.")
+                    "be PGP encrypted."))
             if self.message.pgp_signed():
                 self.message.verify_signature(author_address)
                 # signature has been verified
@@ -1086,9 +1084,9 @@
                 # store the decrypted message      
                 self.message = message
             elif pgp_role():
-                raise MailUsageError, _("""
+                raise MailUsageError(_("""
 This tracker has been configured to require all email be PGP signed or
-encrypted.""")
+encrypted."""))
 
     def get_content_and_attachments(self):
         ''' get the attachments and first text part from the message
@@ -1116,8 +1114,8 @@
             for (name, mime_type, data) in self.attachments:
                 if not self.db.security.hasPermission('Create', self.author,
                     'file'):
-                    raise Unauthorized, _(
-                        'You are not permitted to create files.')
+                    raise Unauthorized(_(
+                        'You are not permitted to create files.'))
                 if not name:
                     name = "unnamed"
                 try:
@@ -1130,9 +1128,9 @@
             # allowed to attach the files to an existing node?
             if self.nodeid and not self.db.security.hasPermission('Edit',
                     self.author, self.classname, 'files'):
-                raise Unauthorized, _(
+                raise Unauthorized(_(
                     'You are not permitted to add files to %(classname)s.'
-                    ) % self.__dict__
+                    ) % self.__dict__)
 
             self.msg_props['files'] = files
             if self.nodeid:
@@ -1160,18 +1158,18 @@
                 self.classname, self.nodeid, self.config['MAIL_DOMAIN'])
         
         if self.content is None:
-            raise MailUsageError, _("""
+            raise MailUsageError(_("""
 Roundup requires the submission to be plain text. The message parser could
 not find a text/plain part to use.
-""")
+"""))
         # parse the body of the message, stripping out bits as appropriate
         summary, content = parseContent(self.content, config=self.config, is_new_issue = not bool(self.nodeid))
         content = content.strip()
 
         if content:
             if not self.db.security.hasPermission('Create', self.author, 'msg'):
-                raise Unauthorized, _(
-                    'You are not permitted to create messages.')
+                raise Unauthorized(_(
+                    'You are not permitted to create messages.'))
 
             try:
                 message_id = self.db.msg.create(author=self.author,
@@ -1186,9 +1184,9 @@
             # allowed to attach the message to the existing node?
             if self.nodeid and not self.db.security.hasPermission('Edit',
                     self.author, self.classname, 'messages'):
-                raise Unauthorized, _(
+                raise Unauthorized(_(
                     'You are not permitted to add messages to %(classname)s.'
-                    ) % self.__dict__
+                    ) % self.__dict__)
 
             if self.nodeid:
                 # add the message to the node's list
@@ -1209,25 +1207,25 @@
                 for prop in self.props.keys():
                     if not self.db.security.hasPermission('Edit', self.author,
                             classname, prop):
-                        raise Unauthorized, _('You are not permitted to edit '
+                        raise Unauthorized(_('You are not permitted to edit '
                             'property %(prop)s of class %(classname)s.'
-                            ) % locals()
+                            ) % locals())
                 self.cl.set(self.nodeid, **self.props)
             else:
                 # Check permissions for each property
                 for prop in self.props.keys():
                     if not self.db.security.hasPermission('Create', self.author,
                             classname, prop):
-                        raise Unauthorized, _('You are not permitted to set '
+                        raise Unauthorized(_('You are not permitted to set '
                             'property %(prop)s of class %(classname)s.'
-                            ) % locals()
+                            ) % locals())
                 self.nodeid = self.cl.create(**self.props)
         except (TypeError, IndexError, ValueError, exceptions.Reject) as message:
             self.mailgw.logger.exception("Rejecting email due to node creation error:")
-            raise MailUsageError, _("""
+            raise MailUsageError(_("""
 There was a problem with the message you sent:
    %(message)s
-""") % locals()
+""") % locals())
 
         return self.nodeid
 
@@ -1672,11 +1670,11 @@
             self.db.getclass(clsname)
         except KeyError:
             mailadmin = self.instance.config['ADMIN_EMAIL']
-            raise MailUsageError, _("""
+            raise MailUsageError(_("""
 The mail gateway is not properly set up. Please contact
 %(mailadmin)s and have them fix the incorrect class specified as:
   %(clsname)s
-""") % locals()
+""") % locals())
         
         if self.arguments:
             # The default type on the commandline is msg
@@ -1703,11 +1701,11 @@
 
                     if errors:
                         mailadmin = self.instance.config['ADMIN_EMAIL']
-                        raise MailUsageError, _("""
+                        raise MailUsageError(_("""
 The mail gateway is not properly set up. Please contact
 %(mailadmin)s and have them fix the incorrect properties:
   %(errors)s
-""") % locals()
+""") % locals())
                     allprops.update(props)
 
         return allprops
--- a/roundup/password.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/password.py	Tue Jul 24 21:39:58 2018 +0000
@@ -113,9 +113,9 @@
         #NOTE: pbkdf2 allows up to (2**31-1)*20 bytes,
         # but m2crypto has issues on some platforms above 40,
         # and such sizes aren't needed for a password hash anyways...
-        raise ValueError, "key length too large"
+        raise ValueError("key length too large")
     if rounds < 1:
-        raise ValueError, "rounds must be positive number"
+        raise ValueError("rounds must be positive number")
     return _pbkdf2(password, salt, rounds, keylen)
 
 class PasswordValueError(ValueError):
@@ -131,13 +131,13 @@
     try:
         rounds, salt, digest = pbkdf2.split("$")
     except ValueError:
-        raise PasswordValueError, "invalid PBKDF2 hash (wrong number of separators)"
+        raise PasswordValueError("invalid PBKDF2 hash (wrong number of separators)")
     if rounds.startswith("0"):
-        raise PasswordValueError, "invalid PBKDF2 hash (zero-padded rounds)"
+        raise PasswordValueError("invalid PBKDF2 hash (zero-padded rounds)")
     try:
         rounds = int(rounds)
     except ValueError:
-        raise PasswordValueError, "invalid PBKDF2 hash (invalid rounds)"
+        raise PasswordValueError("invalid PBKDF2 hash (invalid rounds)")
     raw_salt = h64decode(salt)
     return rounds, salt, raw_salt, digest
 
@@ -157,7 +157,7 @@
             else:
                 rounds = 10000
         if rounds < 1000:
-            raise PasswordValueError, "invalid PBKDF2 hash (rounds too low)"
+            raise PasswordValueError("invalid PBKDF2 hash (rounds too low)")
         raw_digest = pbkdf2(plaintext, raw_salt, rounds, 20)
         return "%d$%s$%s" % (rounds, salt, h64encode(raw_digest))
     elif scheme == 'SSHA':
@@ -184,7 +184,7 @@
     elif scheme == 'plaintext':
         s = plaintext
     else:
-        raise PasswordValueError, 'Unknown encryption scheme %r'%scheme
+        raise PasswordValueError('Unknown encryption scheme %r'%scheme)
     return s
 
 def generatePassword(length=12):
@@ -233,7 +233,7 @@
 
         # assume password is plaintext
         if self.password is None:
-            raise ValueError, 'Password not set'
+            raise ValueError('Password not set')
         return cmp(self.password, encodePassword(other, self.scheme,
             self.password or None))
 
@@ -301,7 +301,7 @@
             # currently plaintext - encrypt
             self.setPassword(encrypted, scheme, config=config)
         if strict and self.scheme not in self.known_schemes:
-            raise PasswordValueError, "Unknown encryption scheme: %r" % (self.scheme,)
+            raise PasswordValueError("Unknown encryption scheme: %r" % (self.scheme,))
 
     def setPassword(self, plaintext, scheme=None, config=None):
         """Sets encrypts plaintext."""
@@ -314,7 +314,7 @@
     def __str__(self):
         """Stringify the encrypted password for database storage."""
         if self.password is None:
-            raise ValueError, 'Password not set'
+            raise ValueError('Password not set')
         return '{%s}%s'%(self.scheme, self.password)
 
 def test():
--- a/roundup/roundupdb.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/roundupdb.py	Tue Jul 24 21:39:58 2018 +0000
@@ -385,7 +385,7 @@
                 keys.append(k)
             else:
                 msg = _('No key for "%(adr)s" in keyring')%locals()
-                raise MessageSendError, msg
+                raise MessageSendError(msg)
             ctx.op_keylist_end()
         ctx.op_encrypt(keys, 1, plain, cipher)
         cipher.seek(0,0)
--- a/roundup/scripts/roundup_server.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/scripts/roundup_server.py	Tue Jul 24 21:39:58 2018 +0000
@@ -479,7 +479,7 @@
     try:
         import grp
     except ImportError:
-        raise ValueError, _("Can't change groups - no grp module")
+        raise ValueError(_("Can't change groups - no grp module"))
     try:
         try:
             gid = int(group)
@@ -488,7 +488,7 @@
         else:
             grp.getgrgid(gid)
     except KeyError:
-        raise ValueError,_("Group %(group)s doesn't exist")%locals()
+        raise ValueError(_("Group %(group)s doesn't exist")%locals())
     os.setgid(gid)
 
 def setuid(user):
@@ -499,7 +499,7 @@
     if user is None:
         if os.getuid():
             return
-        raise ValueError, _("Can't run as root!")
+        raise ValueError(_("Can't run as root!"))
 
     if os.getuid():
         print(_('WARNING: ignoring "-u" argument, not root'))
@@ -508,7 +508,7 @@
     try:
         import pwd
     except ImportError:
-        raise ValueError, _("Can't change users - no pwd module")
+        raise ValueError(_("Can't change users - no pwd module"))
     try:
         try:
             uid = int(user)
@@ -517,7 +517,7 @@
         else:
             pwd.getpwuid(uid)
     except KeyError:
-        raise ValueError, _("User %(user)s doesn't exist")%locals()
+        raise ValueError(_("User %(user)s doesn't exist")%locals())
     os.setuid(uid)
 
 class TrackerHomeOption(configuration.FilePathOption):
@@ -725,9 +725,8 @@
             httpd = server_class(*args, **kwargs)
         except socket.error as e:
             if e[0] == errno.EADDRINUSE:
-                raise socket.error, \
-                    _("Unable to bind to port %s, port already in use.") \
-                    % self["PORT"]
+                raise socket.error(_("Unable to bind to port %s, port already in use.") \
+                    % self["PORT"])
             raise
         # change user and/or group
         setgid(self["GROUP"])
@@ -966,7 +965,7 @@
             try:
                 name, home = arg.split('=')
             except ValueError:
-                raise ValueError, _("Instances must be name=home")
+                raise ValueError(_("Instances must be name=home"))
             config.add_option(TrackerHomeOption(config, "trackers", name))
             config["TRACKERS_" + name.upper()] = home
 
--- a/roundup/scripts/roundup_xmlrpc_server.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/scripts/roundup_xmlrpc_server.py	Tue Jul 24 21:39:58 2018 +0000
@@ -77,12 +77,12 @@
             userid = db.user.lookup(username)
         except KeyError: # No such user
             db.close()
-            raise Unauthorised, 'Invalid user'
+            raise Unauthorised('Invalid user')
         stored = db.user.get(userid, 'password')
         if stored != password:
             # Wrong password
             db.close()
-            raise Unauthorised, 'Invalid user'
+            raise Unauthorised('Invalid user')
         db.setCurrentUser(username)
         return db
 
--- a/roundup/security.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/security.py	Tue Jul 24 21:39:58 2018 +0000
@@ -226,13 +226,13 @@
             Raise ValueError if there is no exact match.
         '''
         if permission not in self.permission:
-            raise ValueError, 'No permission "%s" defined'%permission
+            raise ValueError('No permission "%s" defined'%permission)
 
         if classname:
             try:
                 self.db.getclass(classname)
             except KeyError:
-                raise ValueError, 'No class "%s" defined'%classname
+                raise ValueError('No class "%s" defined'%classname)
 
         # look through all the permissions of the given name
         tester = Permission(permission, klass=classname, properties=properties,
@@ -241,8 +241,8 @@
         for perm in self.permission[permission]:
             if perm == tester:
                 return perm
-        raise ValueError, 'No permission "%s" defined for "%s"'%(permission,
-            classname)
+        raise ValueError('No permission "%s" defined for "%s"'%(permission,
+            classname))
 
     def hasPermission(self, permission, userid, classname=None,
             property=None, itemid=None):
@@ -275,7 +275,7 @@
 
         '''
         if itemid and classname is None:
-            raise ValueError, 'classname must accompany itemid'
+            raise ValueError('classname must accompany itemid')
         for rolename in self.db.user.get_roles(userid):
             if not rolename or (rolename not in self.role):
                 continue
--- a/roundup/xmlrpc.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/roundup/xmlrpc.py	Tue Jul 24 21:39:58 2018 +0000
@@ -39,12 +39,12 @@
         try :
             key, value = arg.split('=', 1)
         except ValueError :
-            raise UsageError, 'argument "%s" not propname=value'%arg
+            raise UsageError('argument "%s" not propname=value'%arg)
         if isinstance(key, unicode):
             try:
                 key = key.encode ('ascii')
             except UnicodeEncodeError:
-                raise UsageError, 'argument %r is no valid ascii keyword'%key
+                raise UsageError('argument %r is no valid ascii keyword'%key)
         if isinstance(value, unicode):
             value = value.encode('utf-8')
         if value:
@@ -52,7 +52,7 @@
                 props[key] = hyperdb.rawToHyperdb(db, cl, itemid,
                                                   key, value)
             except hyperdb.HyperdbValueError as message:
-                raise UsageError, message
+                raise UsageError(message)
         else:
             # If we're syncing a file the contents may not be None
             if key == 'content':
@@ -141,7 +141,7 @@
         # check for the key property
         key = cl.getkey()
         if key and not props.has_key(key):
-            raise UsageError, 'you must provide the "%s" property.'%key
+            raise UsageError('you must provide the "%s" property.'%key)
 
         for key in props:
             if not self.db.security.hasPermission('Create', self.db.getuid(),
--- a/scripts/imapServer.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/scripts/imapServer.py	Tue Jul 24 21:39:58 2018 +0000
@@ -61,20 +61,20 @@
             if not self.dbhome:
                 self.dbhome = raw_input('Tracker home: ')
                 if not os.path.exists(self.dbhome):
-                    raise ValueError, 'Invalid home address: ' \
-                        'directory "%s" does not exist.' % self.dbhome
+                    raise ValueError('Invalid home address: ' \
+                        'directory "%s" does not exist.' % self.dbhome)
 
             if not self.server:
                 self.server = raw_input('Server: ')
                 if not self.server:
-                    raise ValueError, 'No Servername supplied'
+                    raise ValueError('No Servername supplied')
                 protocol = raw_input('protocol [imaps]? ')
                 self.protocol = protocol
 
             if not self.username:
                 self.username = raw_input('Username: ')
                 if not self.username:
-                    raise ValueError, 'Invalid Username'
+                    raise ValueError('Invalid Username')
 
             if not self.password:
                 print('For server %s, user %s' % (self.server, self.username))
@@ -88,7 +88,7 @@
             #   # select the INBOX, whatever it is called
 
         except (KeyboardInterrupt, EOFError):
-            raise ValueError, 'Canceled by User'
+            raise ValueError('Canceled by User')
 
     def __str__(self):
         return 'Mailbox{ server:%(server)s, protocol:%(protocol)s, ' \
@@ -150,7 +150,7 @@
             user['password'] = mailbox.password
 
         if user['mailboxes'].has_key(mailbox.mailbox):
-            raise ValueError, 'Mailbox is already defined'
+            raise ValueError('Mailbox is already defined')
 
         user['mailboxes'][mailbox.mailbox] = mailbox.dbhome
 
@@ -191,7 +191,7 @@
                     elif protocol == 'imap':
                         serv = imaplib.IMAP4(server)
                     else:
-                        raise ValueError, 'Unknown protocol %s' % protocol
+                        raise ValueError('Unknown protocol %s' % protocol)
 
                     password = u_vals['password']
 
--- a/share/roundup/templates/classic/detectors/userauditor.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/share/roundup/templates/classic/detectors/userauditor.py	Tue Jul 24 21:39:58 2018 +0000
@@ -58,19 +58,19 @@
 
     for address in get_addresses(newvalues):
         if not valid_address(address):
-            raise ValueError, 'Email address syntax is invalid "%s"'%address
+            raise ValueError('Email address syntax is invalid "%s"'%address)
 
         check_main = db.user.stringFind(address=address)
         # make sure none of the alts are owned by anyone other than us (x!=nodeid)
         check_alts = [x for x in db.user.filter(None, {'alternate_addresses' : address}) if x != nodeid]
         if check_main or check_alts:
-            raise ValueError, 'Email address %s already in use' % address
+            raise ValueError('Email address %s already in use' % address)
 
     newroles = newvalues.get('roles')
     if newroles:
         for rolename in [r.lower().strip() for r in newroles.split(',')]:
             if rolename and not db.security.role.has_key(rolename):
-                raise ValueError, 'Role "%s" does not exist'%rolename
+                raise ValueError('Role "%s" does not exist'%rolename)
 
     tz = newvalues.get('timezone', None)
     if tz:
@@ -83,9 +83,9 @@
             dt = datetime.datetime.now()
             local = TZ.localize(dt).utctimetuple()
         except IOError:
-            raise ValueError, 'Timezone "%s" does not exist' % tz
+            raise ValueError('Timezone "%s" does not exist' % tz)
         except ValueError:
-            raise ValueError, 'Timezone "%s" exceeds valid range [-23...23]' % tz
+            raise ValueError('Timezone "%s" exceeds valid range [-23...23]' % tz)
 
 def init(db):
     # fire before changes are made
--- a/share/roundup/templates/devel/detectors/userauditor.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/share/roundup/templates/devel/detectors/userauditor.py	Tue Jul 24 21:39:58 2018 +0000
@@ -58,19 +58,19 @@
 
     for address in get_addresses(newvalues):
         if not valid_address(address):
-            raise ValueError, 'Email address syntax is invalid "%s"'%address
+            raise ValueError('Email address syntax is invalid "%s"'%address)
 
         check_main = db.user.stringFind(address=address)
         # make sure none of the alts are owned by anyone other than us (x!=nodeid)
         check_alts = [x for x in db.user.filter(None, {'alternate_addresses' : address}) if x != nodeid]
         if check_main or check_alts:
-            raise ValueError, 'Email address %s already in use' % address
+            raise ValueError('Email address %s already in use' % address)
 
     newroles = newvalues.get('roles')
     if newroles:
         for rolename in [r.lower().strip() for r in newroles.split(',')]:
             if rolename and not db.security.role.has_key(rolename):
-                raise ValueError, 'Role "%s" does not exist'%rolename
+                raise ValueError('Role "%s" does not exist'%rolename)
 
     tz = newvalues.get('timezone', None)
     if tz:
@@ -83,9 +83,9 @@
             dt = datetime.datetime.now()
             local = TZ.localize(dt).utctimetuple()
         except IOError:
-            raise ValueError, 'Timezone "%s" does not exist' % tz
+            raise ValueError('Timezone "%s" does not exist' % tz)
         except ValueError:
-            raise ValueError, 'Timezone "%s" exceeds valid range [-23...23]' % tz
+            raise ValueError('Timezone "%s" exceeds valid range [-23...23]' % tz)
 
 def init(db):
     # fire before changes are made
--- a/share/roundup/templates/devel/extensions/timestamp.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/share/roundup/templates/devel/extensions/timestamp.py	Tue Jul 24 21:39:58 2018 +0000
@@ -13,9 +13,9 @@
         try:
             created = unpack_timestamp(self.form['opaque'].value)
         except KeyError:
-            raise FormError, "somebody tampered with the form"
+            raise FormError("somebody tampered with the form")
         if time.time() - created < 4:
-            raise FormError, "responding to the form too quickly"
+            raise FormError("responding to the form too quickly")
         return True
 
 class TimestampedRegister(Timestamped, RegisterAction):
--- a/share/roundup/templates/jinja2/detectors/userauditor.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/share/roundup/templates/jinja2/detectors/userauditor.py	Tue Jul 24 21:39:58 2018 +0000
@@ -58,19 +58,19 @@
 
     for address in get_addresses(newvalues):
         if not valid_address(address):
-            raise ValueError, 'Email address syntax is invalid "%s"'%address
+            raise ValueError('Email address syntax is invalid "%s"'%address)
 
         check_main = db.user.stringFind(address=address)
         # make sure none of the alts are owned by anyone other than us (x!=nodeid)
         check_alts = [x for x in db.user.filter(None, {'alternate_addresses' : address}) if x != nodeid]
         if check_main or check_alts:
-            raise ValueError, 'Email address %s already in use' % address
+            raise ValueError('Email address %s already in use' % address)
 
     newroles = newvalues.get('roles')
     if newroles:
         for rolename in [r.lower().strip() for r in newroles.split(',')]:
             if rolename and not db.security.role.has_key(rolename):
-                raise ValueError, 'Role "%s" does not exist'%rolename
+                raise ValueError('Role "%s" does not exist'%rolename)
 
     tz = newvalues.get('timezone', None)
     if tz:
@@ -83,9 +83,9 @@
             dt = datetime.datetime.now()
             local = TZ.localize(dt).utctimetuple()
         except IOError:
-            raise ValueError, 'Timezone "%s" does not exist' % tz
+            raise ValueError('Timezone "%s" does not exist' % tz)
         except ValueError:
-            raise ValueError, 'Timezone "%s" exceeds valid range [-23...23]' % tz
+            raise ValueError('Timezone "%s" exceeds valid range [-23...23]' % tz)
 
 def init(db):
     # fire before changes are made
--- a/share/roundup/templates/minimal/detectors/userauditor.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/share/roundup/templates/minimal/detectors/userauditor.py	Tue Jul 24 21:39:58 2018 +0000
@@ -58,19 +58,19 @@
 
     for address in get_addresses(newvalues):
         if not valid_address(address):
-            raise ValueError, 'Email address syntax is invalid "%s"'%address
+            raise ValueError('Email address syntax is invalid "%s"'%address)
 
         check_main = db.user.stringFind(address=address)
         # make sure none of the alts are owned by anyone other than us (x!=nodeid)
         check_alts = [x for x in db.user.filter(None, {'alternate_addresses' : address}) if x != nodeid]
         if check_main or check_alts:
-            raise ValueError, 'Email address %s already in use' % address
+            raise ValueError('Email address %s already in use' % address)
 
     newroles = newvalues.get('roles')
     if newroles:
         for rolename in [r.lower().strip() for r in newroles.split(',')]:
             if rolename and not db.security.role.has_key(rolename):
-                raise ValueError, 'Role "%s" does not exist'%rolename
+                raise ValueError('Role "%s" does not exist'%rolename)
 
     tz = newvalues.get('timezone', None)
     if tz:
@@ -83,9 +83,9 @@
             dt = datetime.datetime.now()
             local = TZ.localize(dt).utctimetuple()
         except IOError:
-            raise ValueError, 'Timezone "%s" does not exist' % tz
+            raise ValueError('Timezone "%s" does not exist' % tz)
         except ValueError:
-            raise ValueError, 'Timezone "%s" exceeds valid range [-23...23]' % tz
+            raise ValueError('Timezone "%s" exceeds valid range [-23...23]' % tz)
 
 def init(db):
     # fire before changes are made
--- a/share/roundup/templates/responsive/detectors/userauditor.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/share/roundup/templates/responsive/detectors/userauditor.py	Tue Jul 24 21:39:58 2018 +0000
@@ -58,19 +58,19 @@
 
     for address in get_addresses(newvalues):
         if not valid_address(address):
-            raise ValueError, 'Email address syntax is invalid "%s"'%address
+            raise ValueError('Email address syntax is invalid "%s"'%address)
 
         check_main = db.user.stringFind(address=address)
         # make sure none of the alts are owned by anyone other than us (x!=nodeid)
         check_alts = [x for x in db.user.filter(None, {'alternate_addresses' : address}) if x != nodeid]
         if check_main or check_alts:
-            raise ValueError, 'Email address %s already in use' % address
+            raise ValueError('Email address %s already in use' % address)
 
     newroles = newvalues.get('roles')
     if newroles:
         for rolename in [r.lower().strip() for r in newroles.split(',')]:
             if rolename and not db.security.role.has_key(rolename):
-                raise ValueError, 'Role "%s" does not exist'%rolename
+                raise ValueError('Role "%s" does not exist'%rolename)
 
     tz = newvalues.get('timezone', None)
     if tz:
@@ -83,9 +83,9 @@
             dt = datetime.datetime.now()
             local = TZ.localize(dt).utctimetuple()
         except IOError:
-            raise ValueError, 'Timezone "%s" does not exist' % tz
+            raise ValueError('Timezone "%s" does not exist' % tz)
         except ValueError:
-            raise ValueError, 'Timezone "%s" exceeds valid range [-23...23]' % tz
+            raise ValueError('Timezone "%s" exceeds valid range [-23...23]' % tz)
 
 def init(db):
     # fire before changes are made
--- a/share/roundup/templates/responsive/extensions/timestamp.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/share/roundup/templates/responsive/extensions/timestamp.py	Tue Jul 24 21:39:58 2018 +0000
@@ -13,9 +13,9 @@
         try:
             created = unpack_timestamp(self.form['opaque'].value)
         except KeyError:
-            raise FormError, "somebody tampered with the form"
+            raise FormError("somebody tampered with the form")
         if time.time() - created < 4:
-            raise FormError, "responding to the form too quickly"
+            raise FormError("responding to the form too quickly")
         return True
 
 class TimestampedRegister(Timestamped, RegisterAction):
--- a/test/memorydb.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/test/memorydb.py	Tue Jul 24 21:39:58 2018 +0000
@@ -267,12 +267,12 @@
         """A convenient way of calling self.getclass(classname)."""
         if self.classes.has_key(classname):
             return self.classes[classname]
-        raise AttributeError, classname
+        raise AttributeError(classname)
 
     def addclass(self, cl):
         cn = cl.classname
         if self.classes.has_key(cn):
-            raise ValueError, cn
+            raise ValueError(cn)
         self.classes[cn] = cl
         if cn not in self.items:
             self.items[cn] = cldb()
@@ -300,7 +300,7 @@
         try:
             return self.classes[classname]
         except KeyError:
-            raise KeyError, 'There is no class called "%s"'%classname
+            raise KeyError('There is no class called "%s"'%classname)
 
     #
     # Class DBs
@@ -363,7 +363,7 @@
             res += self.journals.get(classname, {})[nodeid]
         except KeyError:
             if res: return res
-            raise IndexError, nodeid
+            raise IndexError(nodeid)
         return res
 
     def pack(self, pack_before):
--- a/test/test_actions.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/test/test_actions.py	Tue Jul 24 21:39:58 2018 +0000
@@ -45,7 +45,7 @@
                 excName = exception.__name__
             else:
                 excName = str(exception)
-            raise self.failureException, excName
+            raise self.failureException(excName)
 
     def testShowAction(self):
         self.client.base = 'BASE/'
@@ -254,7 +254,7 @@
                 excName = exception.__name__
             else:
                 excName = str(exception)
-            raise self.failureException, excName
+            raise self.failureException(excName)
 
     def assertLoginLeavesMessages(self, messages, username=None, password=None):
         if username is not None:
--- a/test/test_locking.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/test/test_locking.py	Tue Jul 24 21:39:58 2018 +0000
@@ -36,7 +36,7 @@
         except:
             pass
         else:
-            raise AssertionError, 'no exception'
+            raise AssertionError('no exception')
         release_lock(f)
         f = acquire_lock(self.path)
         release_lock(f)
--- a/test/test_mailgw.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/test/test_mailgw.py	Tue Jul 24 21:39:58 2018 +0000
@@ -145,7 +145,7 @@
 
             if res:
                 res.insert(0, 'Generated message not correct (diff follows, expected vs. actual):')
-                raise AssertionError, '\n'.join(res)
+                raise AssertionError('\n'.join(res))
 
     def compareStrings(self, s2, s1, replace={}):
         '''Note the reversal of s2 and s1 - difflib.SequenceMatcher wants
@@ -2571,7 +2571,7 @@
 """)
             assert not body_diff, body_diff
         else:
-            raise AssertionError, "Unauthorized not raised when handling mail"
+            raise AssertionError("Unauthorized not raised when handling mail")
 
         # Add Web Access role to anonymous, and try again to make sure
         # we get a "please register at:" message this time.
@@ -2594,7 +2594,7 @@
 """)
             assert not body_diff, body_diff
         else:
-            raise AssertionError, "Unauthorized not raised when handling mail"
+            raise AssertionError("Unauthorized not raised when handling mail")
 
         # Make sure list of users is the same as before.
         m = self.db.user.list()
--- a/test/test_templating.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/test/test_templating.py	Tue Jul 24 21:39:58 2018 +0000
@@ -72,7 +72,7 @@
             if key == 'ok':
                 return '1'
             if key == 'fail':
-                raise KeyError, 'fail'
+                raise KeyError('fail')
             return key
         db._db.classes = {'issue': MockNull(lookup=lookup)}
         prop = MockNull(classname='issue')
--- a/website/issues/detectors/newissuecopy.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/website/issues/detectors/newissuecopy.py	Tue Jul 24 21:39:58 2018 +0000
@@ -13,7 +13,7 @@
             cl.send_message(nodeid, msgid, change_note,
                 ['roundup-devel@lists.sourceforge.net'])
         except roundupdb.MessageSendError as message:
-            raise roundupdb.DetectorError, message
+            raise roundupdb.DetectorError(message)
 
 def init(db):
     db.issue.react('create', newissuecopy)
--- a/website/issues/detectors/userauditor.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/website/issues/detectors/userauditor.py	Tue Jul 24 21:39:58 2018 +0000
@@ -58,19 +58,19 @@
 
     for address in get_addresses(newvalues):
         if not valid_address(address):
-            raise ValueError, 'Email address syntax is invalid "%s"'%address
+            raise ValueError('Email address syntax is invalid "%s"'%address)
 
         check_main = db.user.stringFind(address=address)
         # make sure none of the alts are owned by anyone other than us (x!=nodeid)
         check_alts = [x for x in db.user.filter(None, {'alternate_addresses' : address}) if x != nodeid]
         if check_main or check_alts:
-            raise ValueError, 'Email address %s already in use' % address
+            raise ValueError('Email address %s already in use' % address)
 
     newroles = newvalues.get('roles')
     if newroles:
         for rolename in [r.lower().strip() for r in newroles.split(',')]:
             if rolename and not db.security.role.has_key(rolename):
-                raise ValueError, 'Role "%s" does not exist'%rolename
+                raise ValueError('Role "%s" does not exist'%rolename)
 
     tz = newvalues.get('timezone', None)
     if tz:
@@ -83,9 +83,9 @@
             dt = datetime.datetime.now()
             local = TZ.localize(dt).utctimetuple()
         except IOError:
-            raise ValueError, 'Timezone "%s" does not exist' % tz
+            raise ValueError('Timezone "%s" does not exist' % tz)
         except ValueError:
-            raise ValueError, 'Timezone "%s" exceeds valid range [-23...23]' % tz
+            raise ValueError('Timezone "%s" exceeds valid range [-23...23]' % tz)
 
 def init(db):
     # fire before changes are made
--- a/website/issues/extensions/timestamp.py	Tue Jul 24 21:36:02 2018 +0000
+++ b/website/issues/extensions/timestamp.py	Tue Jul 24 21:39:58 2018 +0000
@@ -13,9 +13,9 @@
         try:
             created = unpack_timestamp(self.form['opaque'].value)
         except KeyError:
-            raise FormError, "somebody tampered with the form"
+            raise FormError("somebody tampered with the form")
         if time.time() - created < 4:
-            raise FormError, "responding to the form too quickly"
+            raise FormError("responding to the form too quickly")
         return True
 
 class TimestampedRegister(Timestamped, RegisterAction):

Roundup Issue Tracker: http://roundup-tracker.org/