From 750b280fc3cc83986b1478d96d373ad0e4f77542 Mon Sep 17 00:00:00 2001
From: Dave Jones <dave@waveform.org.uk>
Date: Wed, 24 Feb 2021 21:29:45 +0000
Subject: [PATCH 1/2] Add a modern gitignore

---
 .gitignore | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)
 create mode 100644 .gitignore

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9904c03
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,27 @@
+# Python stuff
+*.py[cdo]
+
+# Vim stuff
+*.vim
+*.swp
+
+# Miscellaneous
+tags
+
+# Packages
+*.egg
+*.egg-info
+*.pyc
+*.whl
+dist
+build
+man
+
+# Unit test / coverage reports
+coverage
+.cache
+.coverage
+.tox
+.pytest_cache
+.env
+.eggs

From c035494c138f0cdc9c29106628769833de07ccb8 Mon Sep 17 00:00:00 2001
From: Dave Jones <dave@waveform.org.uk>
Date: Wed, 24 Feb 2021 22:41:48 +0000
Subject: [PATCH 2/2] Rough'n'ready py3 conversion

The result of a 2to3 run with a little manual fixing up afterwards
---
 dbsuite/astex.py                       | 24 +++++------
 dbsuite/converter.py                   | 36 ++++++++---------
 dbsuite/etree.py                       | 16 ++++----
 dbsuite/highlighters.py                |  4 +-
 dbsuite/instance.py                    |  2 +-
 dbsuite/main/__init__.py               | 11 +++---
 dbsuite/main/dbconvdoc.py              |  2 +-
 dbsuite/main/dbexec.py                 |  9 ++---
 dbsuite/main/dbgrepdoc.py              |  2 +-
 dbsuite/main/dbmakedoc.py              | 10 ++---
 dbsuite/parser.py                      | 17 ++++----
 dbsuite/plugins/__init__.py            | 26 ++++++------
 dbsuite/plugins/db2/__init__.py        |  8 ++--
 dbsuite/plugins/db2/luw/tokenizer.py   |  6 +--
 dbsuite/plugins/db2/zos/parser.py      | 10 ++---
 dbsuite/plugins/db2/zos/tokenizer.py   | 12 +++---
 dbsuite/plugins/html/__init__.py       |  2 +-
 dbsuite/plugins/html/document.py       | 55 +++++++++++---------------
 dbsuite/plugins/html/plain/document.py |  8 ++--
 dbsuite/plugins/html/w3/document.py    | 12 +++---
 dbsuite/plugins/odf/text/__init__.py   | 12 +++---
 dbsuite/plugins/sql/__init__.py        | 16 ++++----
 dbsuite/plugins/tex/__init__.py        |  2 +-
 dbsuite/plugins/tex/document.py        |  4 +-
 dbsuite/plugins/xml/input/__init__.py  | 13 +++---
 dbsuite/plugins/xml/output/__init__.py |  4 +-
 dbsuite/script.py                      | 38 ++++++++----------
 dbsuite/tokenizer.py                   | 14 +++----
 28 files changed, 178 insertions(+), 197 deletions(-)

diff --git a/dbsuite/astex.py b/dbsuite/astex.py
index 60b4fe7..85a3fd0 100644
--- a/dbsuite/astex.py
+++ b/dbsuite/astex.py
@@ -28,7 +28,7 @@
 import datetime
 
 
-tex_special = re.compile(ur'([#$%^&_{}~/\\\u00A0]|\.\.\.)')
+tex_special = re.compile(r'([#$%^&_{}~/\\\u00A0]|\.\.\.)')
 def escape_tex(s):
     def subfn(m):
         m = m.group(0)
@@ -146,7 +146,7 @@ def attrs(c):
             if hasattr(c, '__slots__'):
                 result = set(c.__slots__)
             elif hasattr(c, '__dict__'):
-                result = set(c.__dict__.iterkeys())
+                result = set(c.__dict__.keys())
             else:
                 result = set()
             for b in c.__bases__:
@@ -173,7 +173,7 @@ def format_value(name, value):
         content = self._content_xml()
         attrs = ''.join(
             ' %s="%s"' % (name, format_value(name, value))
-            for (name, value) in self._attrib().iteritems()
+            for (name, value) in self._attrib().items()
             if format_value(name, value) is not None
         )
         if content:
@@ -937,7 +937,7 @@ def _packages(self):
             yield 'ucs'
         yield ('inputenc', '[%s]' % self.encoding) # Encoding must be as early as possible
         options = []
-        if isinstance(self.paper_size, basestring):
+        if isinstance(self.paper_size, str):
             options.append('paper=%s' % self.paper_size)
         elif isinstance(self.paper_size, tuple):
             options.append('paper_size={%s,%s}' % self.paper_size)
@@ -1008,7 +1008,7 @@ def _preamble(self):
         yield format_cmd('documentclass', self.doc_class, '[%s]' % self.font_size)
         # Add all the packages, then deal with preamble options
         for package in self._packages():
-            if not isinstance(package, basestring):
+            if not isinstance(package, str):
                 package, options = package
             else:
                 options = ''
@@ -1039,13 +1039,13 @@ def _preamble(self):
         yield format_cmd('hypersetup', ',%'.join('\n    ' + option for option in options))
         # Define all the custom style commands and environments
         if self.commands:
-            for name, definition in self.commands.iteritems():
+            for name, definition in self.commands.items():
                 yield '%s[1]{%s}' % (
                     format_cmd('newcommand', '\\' + name),
                     tex(definition(TeXParam(1)))
                 )
         if self.environments:
-            for name, (prefix, suffix, preamble) in self.environments.iteritems():
+            for name, (prefix, suffix, preamble) in self.environments.items():
                 yield '%s\n%s%%\n{%s}%%\n{%s}' % (
                     preamble,
                     format_cmd('newenvironment', name),
@@ -1056,7 +1056,7 @@ def _preamble(self):
         # font calls in the styles have registered their colors before this
         # point)
         if self.colors:
-            for name, color in self.colors.iteritems():
+            for name, color in self.colors.items():
                 yield '%s{rgb}{%.2f,%.2f,%.2f}' % (
                     format_cmd('definecolor', name),
                     float((color & 0xFF0000) >> 16) // 0xFF,
@@ -1101,7 +1101,7 @@ def __tex__(self):
 # the factory class below. This isn't done in the initializer as the available
 # classes are not expected to change
 tag_map = dict(
-    (c.tag, c) for c in locals().itervalues()
+    (c.tag, c) for c in locals().values()
     if type(c) == type(object)
     and issubclass(c, TeXNode)
     and hasattr(c, 'tag')
@@ -1116,7 +1116,7 @@ def __init__(self):
 
     def _format(self, content):
         """Reformats content into a human-readable string"""
-        if isinstance(content, basestring):
+        if isinstance(content, str):
             # Strings (including unicode) are returned verbatim
             return content
         else:
@@ -1125,14 +1125,14 @@ def _format(self, content):
 
     def _append(self, node, contents):
         """Adds content (string, node, node-list, etc.) to a node"""
-        if isinstance(contents, basestring):
+        if isinstance(contents, str):
             if contents != '':
                 if len(node.children) == 0:
                     node.text += contents
                 else:
                     last = node.children[-1]
                     last.tail += contents
-        elif isinstance(contents, (int, long, bool, datetime.datetime, datetime.date, datetime.time)):
+        elif isinstance(contents, (int, bool, datetime.datetime, datetime.date, datetime.time)):
             # XXX This branch exists for optimization purposes only (the except
             # branch below is moderately expensive)
             self._append(node, self._format(contents))
diff --git a/dbsuite/converter.py b/dbsuite/converter.py
index 0f60419..a92b853 100644
--- a/dbsuite/converter.py
+++ b/dbsuite/converter.py
@@ -34,8 +34,8 @@
 import re
 import locale
 import logging
-from urllib2 import urlopen
-from urlparse import urljoin
+from urllib.request import urlopen
+from urllib.parse import urljoin
 
 from dbsuite.parser import quote_str, format_ident
 from dbsuite.etree import fromstring, tostring, iselement, Element, SubElement, indent
@@ -85,12 +85,12 @@ def convert_desc(elem):
     """
     if elem.tag == 'ul':
         result = ', '.join([
-            re.sub(ur'\.$', '', convert_desc(li))
+            re.sub(r'\.$', '', convert_desc(li))
             for li in elem.findall('li')
         ]) + '. '
     elif elem.tag == 'ol':
         result = ', '.join([
-            '%d. %s' % (ix, re.sub(ur'\.$', '', convert_desc(li)))
+            '%d. %s' % (ix, re.sub(r'\.$', '', convert_desc(li)))
             for (ix, li) in enumerate(elem.findall('li'))
         ]) + '. '
     elif elem.tag == 'span' and elem.attrib.get('id') == 'changed':
@@ -99,7 +99,7 @@ def convert_desc(elem):
         result = elem.text or ''
         for e in elem:
             result += convert_desc(e) + (e.tail or '')
-        result = re.sub(ur'\s+', ' ', result)
+        result = re.sub(r'\s+', ' ', result)
     return result.strip()
 
 
@@ -169,7 +169,7 @@ def __iter__(self):
                     # Strip all whitespace (newlines, space, etc.) - sometimes
                     # the docs include essentially erroneous whitespace to
                     # allow wrapping for really long column names
-                    column = re.sub(ur'\s', '', convert_name(column))
+                    column = re.sub(r'\s', '', convert_name(column))
                     # Workaround: DB2 9.5 and 9.7 catalog spelling error: the
                     # documentation lists SYSCAT.INDEXES.COLLECTSTATISTICS but
                     # the column in the actual view in the database is called
@@ -213,7 +213,7 @@ def _get_object_urls(self):
                 obj = re.sub(' catalog view$', '', anchor.text)
                 schema, obj = obj.split('.')
                 d[(schema, obj)] = url
-        for ((schema, obj), url) in sorted(d.iteritems()):
+        for ((schema, obj), url) in sorted(d.items()):
             yield (schema, obj, url)
 
     def _get_xml(self, url):
@@ -274,7 +274,7 @@ def __init__(self, xml):
         self.xml = xml
 
     def __iter__(self):
-        if isinstance(self.xml, basestring):
+        if isinstance(self.xml, str):
             root = fromstring(xml)
         elif hasattr(self.xml, 'read'):
             # Assume self.xml is a file-like object
@@ -333,9 +333,9 @@ def __iter__(self):
             prefix = ''
             maxlen = max(
                 len(format_ident(column))
-                for column in columns.iterkeys()
+                for column in columns.keys()
             )
-            for (column, desc) in sorted(columns.iteritems()):
+            for (column, desc) in sorted(columns.items()):
                 logging.debug('Generating SQL for column %s' % column)
                 if len(desc) > self.maxlen:
                     logging.warning('Description for column %s.%s.%s has been truncated' % (schema, obj, column))
@@ -389,9 +389,9 @@ def __iter__(self):
             prefix = ''
             maxlen = max(
                 len(quote_str(column)) + 1
-                for column in columns.iterkeys()
+                for column in columns.keys()
             )
-            for (column, desc) in sorted(columns.iteritems()):
+            for (column, desc) in sorted(columns.items()):
                 logging.debug('Generating SQL for column %s' % column)
                 yield '%s\t\t(%-*s CLOB(%s))\n' % (
                     prefix,
@@ -441,9 +441,9 @@ def __iter__(self):
             yield '\tREMARKS = CASE COLNAME\n'
             maxlen = max(
                 len(quote_str(column)) + 1
-                for column in columns.iterkeys()
+                for column in columns.keys()
             )
-            for (column, desc) in sorted(columns.iteritems()):
+            for (column, desc) in sorted(columns.items()):
                 logging.debug('Generating SQL for column %s' % column)
                 yield '\t\tWHEN %-*s THEN CLOB(%s)\n' % (
                     maxlen,
@@ -498,9 +498,9 @@ def __iter__(self):
             prefix = ''
             maxlen = max(
                 len(quote_str(column)) + 1
-                for column in columns.iterkeys()
+                for column in columns.keys()
             )
-            for (column, desc) in sorted(columns.iteritems()):
+            for (column, desc) in sorted(columns.items()):
                 logging.debug('Generating SQL for column %s' % column)
                 yield '%s\t(%s, %s, %-*s CLOB(%s))\n' % (
                     prefix,
@@ -554,7 +554,7 @@ def __iter__(self):
                 obj_elem.attrib['name'] = obj
                 objects[(schema, obj)] = obj_elem
             SubElement(obj_elem, 'description').text = desc
-            for (column, desc) in sorted(columns.iteritems()):
+            for (column, desc) in sorted(columns.items()):
                 col_elem = SubElement(obj_elem, 'column')
                 col_elem.attrib['name'] = column
                 col_elem.text = desc
@@ -562,7 +562,7 @@ def __iter__(self):
         yield '<?xml version="1.0" encoding="UTF-8" ?>\n'
         # Ensure the output is in UTF-8 encoding
         s = tostring(root)
-        if isinstance(s, unicode):
+        if isinstance(s, str):
             s = s.encode('UTF-8')
         yield s
 
diff --git a/dbsuite/etree.py b/dbsuite/etree.py
index e3cf3ee..84f33e3 100644
--- a/dbsuite/etree.py
+++ b/dbsuite/etree.py
@@ -156,7 +156,7 @@ def flatten(elem, include_tail=False):
 # display types by tag.
 html4_display = dict(
     (tag, display)
-    for (display, tags) in html4_display.iteritems()
+    for (display, tags) in html4_display.items()
     for tag in tags
 )
 
@@ -275,7 +275,7 @@ def _find(self, root, tagname, id=None):
 
     def _format(self, content):
         """Reformats content into a human-readable string"""
-        if isinstance(content, basestring):
+        if isinstance(content, str):
             # Strings (including unicode) are returned verbatim
             return content
         else:
@@ -284,7 +284,7 @@ def _format(self, content):
 
     def _append(self, node, contents):
         """Adds content (string, node, node-list, etc.) to a node"""
-        if isinstance(contents, basestring):
+        if isinstance(contents, str):
             if contents != '':
                 if len(node) == 0:
                     if node.text is None:
@@ -297,7 +297,7 @@ def _append(self, node, contents):
                         last.tail = contents
                     else:
                         last.tail += contents
-        elif isinstance(contents, (int, long, bool, datetime.datetime, datetime.date, datetime.time)):
+        elif isinstance(contents, (int, bool, datetime.datetime, datetime.date, datetime.time)):
             # This branch exists for optimization purposes only (the except
             # branch below is moderately expensive)
             self._append(node, self._format(contents))
@@ -316,7 +316,7 @@ def _element(self, _name, *contents, **attrs):
             _name = '{%s}%s' % (self._namespace, _name)
             attrs = dict(
                 ('{%s}%s' % (self._namespace, key), value)
-                for (key, value) in attrs.iteritems()
+                for (key, value) in attrs.items()
             )
         def conv(key, value):
             # This little utility routine is used to clean up attributes:
@@ -324,18 +324,18 @@ def conv(key, value):
             # all values are converted to strings, and trailing underscores are
             # removed from key names (convenience for names which are python
             # keywords)
-            if not isinstance(key, basestring):
+            if not isinstance(key, str):
                 key = str(key)
             else:
                 key = key.rstrip('_')
             if value is True:
                 value = key
-            elif not isinstance(value, basestring):
+            elif not isinstance(value, str):
                 value = str(value)
             return key, value
         e = Element(_name, dict(
             conv(key, value)
-            for key, value in attrs.iteritems()
+            for key, value in attrs.items()
             if value is not None and value is not False
         ))
         for content in contents:
diff --git a/dbsuite/highlighters.py b/dbsuite/highlighters.py
index 4bb47a9..e33294e 100644
--- a/dbsuite/highlighters.py
+++ b/dbsuite/highlighters.py
@@ -235,14 +235,14 @@ def excerpt(tokens):
             # SQL highlighted from the tokenized stream
             try:
                 tokens = self.formatter.parse(tokens)
-            except ParseTokenError, e:
+            except ParseTokenError as e:
                 logging.warning('While formatting %s' % excerpt(tokens))
                 logging.warning('error %s found at line %d, column %d' % (str(e), e.line, e.column))
         if tokens:
             if line_split:
                 return (
                     self.format_line(line + 1, (token for token in tokens if token.line == line + 1))
-                    for line in xrange(tokens[-1].line)
+                    for line in range(tokens[-1].line)
                 )
             else:
                 return (self.format_token(token) for token in tokens)
diff --git a/dbsuite/instance.py b/dbsuite/instance.py
index 2e2afd5..6f99d8b 100644
--- a/dbsuite/instance.py
+++ b/dbsuite/instance.py
@@ -89,7 +89,7 @@ def get_instance(name=None):
 
 def set_instance(instance):
     """Restore an instance from an earlier get_instance() call"""
-    for key, value in instance.iteritems():
+    for key, value in instance.items():
         if value is not None:
             os.environ[key] = value
         elif key in os.environ:
diff --git a/dbsuite/main/__init__.py b/dbsuite/main/__init__.py
index 7274823..69f789a 100644
--- a/dbsuite/main/__init__.py
+++ b/dbsuite/main/__init__.py
@@ -26,7 +26,7 @@
 import sys
 import os
 import optparse
-import ConfigParser
+import configparser
 import logging
 import locale
 import textwrap
@@ -36,6 +36,7 @@
 import dbsuite.plugins
 from dbsuite.termsize import terminal_size
 from dbsuite import __version__
+from functools import reduce
 
 
 # Use the user's default locale instead of C
@@ -145,7 +146,7 @@ def expand_args(self, args):
                                 result.append(resp_arg)
                             else:
                                 result.extend(self.glob_arg(resp_arg))
-                except IOError, e:
+                except IOError as e:
                     raise optparse.OptionValueError(str(e))
             else:
                 result.append(arg)
@@ -170,7 +171,7 @@ def handle(self, type, value, tb):
             # Just ignore system exit and keyboard interrupt errors (after all,
             # they're user generated)
             return 130
-        elif issubclass(type, (IOError, ConfigParser.Error, dbsuite.plugins.PluginError)):
+        elif issubclass(type, (IOError, configparser.Error, dbsuite.plugins.PluginError)):
             # For simple errors like IOError and PluginError just output the
             # message which should be sufficient for the end user (no need to
             # confuse them with a full stack trace)
@@ -210,7 +211,7 @@ def pprint(self, s, indent=None, initial_indent='', subsequent_indent=''):
         else:
             self.wrapper.initial_indent = indent
             self.wrapper.subsequent_indent = indent
-        if isinstance(s, basestring):
+        if isinstance(s, str):
             s = [s]
         first = True
         for para in s:
@@ -219,7 +220,7 @@ def pprint(self, s, indent=None, initial_indent='', subsequent_indent=''):
             else:
                 sys.stdout.write('\n')
             para = self.wrapper.fill(para)
-            if isinstance(para, unicode):
+            if isinstance(para, str):
                 para = para.encode(self.encoding)
             sys.stdout.write(para + '\n')
 
diff --git a/dbsuite/main/dbconvdoc.py b/dbsuite/main/dbconvdoc.py
index b8fb581..0e65637 100644
--- a/dbsuite/main/dbconvdoc.py
+++ b/dbsuite/main/dbconvdoc.py
@@ -106,7 +106,7 @@ def class_description(self, cls):
 
     def list_classes(self, header, classes):
         self.pprint(header)
-        for (key, cls) in sorted(classes.iteritems()):
+        for (key, cls) in sorted(classes.items()):
             self.pprint(key, indent=' '*4)
             self.pprint(self.class_summary(cls), indent=' '*8)
             self.pprint('')
diff --git a/dbsuite/main/dbexec.py b/dbsuite/main/dbexec.py
index a034f35..12f3a1f 100644
--- a/dbsuite/main/dbexec.py
+++ b/dbsuite/main/dbexec.py
@@ -27,12 +27,9 @@
 import sys
 import logging
 import re
-import ConfigParser
+import configparser
 import optparse
-try:
-    import cPickle as pickle
-except ImportError:
-    import pickle
+import pickle
 
 import dbsuite.script
 import dbsuite.tokenizer
@@ -40,7 +37,7 @@
 import dbsuite.main
 
 
-class MyConfigParser(ConfigParser.SafeConfigParser):
+class MyConfigParser(configparser.SafeConfigParser):
     """Tweaked version of SaveConfigParser that uses uppercase for keys"""
     def optionxform(self, optionstr):
         return optionstr.upper()
diff --git a/dbsuite/main/dbgrepdoc.py b/dbsuite/main/dbgrepdoc.py
index 02a61bb..935d213 100644
--- a/dbsuite/main/dbgrepdoc.py
+++ b/dbsuite/main/dbgrepdoc.py
@@ -72,7 +72,7 @@ def main(self, options, args):
             sql = sql_file.read()
             try:
                 sql = extractor.parse(sql, terminator=options.terminator)
-            except dbsuite.tokenizer.Error, e:
+            except dbsuite.tokenizer.Error as e:
                 logging.error('In file %s:' % sql_file.name)
                 logging.error(str(e))
                 rc = 3
diff --git a/dbsuite/main/dbmakedoc.py b/dbsuite/main/dbmakedoc.py
index 9a3294f..ba3a978 100644
--- a/dbsuite/main/dbmakedoc.py
+++ b/dbsuite/main/dbmakedoc.py
@@ -24,7 +24,7 @@
     )
 
 import optparse
-import ConfigParser
+import configparser
 import logging
 from operator import itemgetter
 
@@ -85,8 +85,8 @@ def process_config(self, config_file):
         "section-name" is the name of a section, and "plugin" is the
         constructed and configured plugin object.
         """
-        parser = ConfigParser.SafeConfigParser()
-        if isinstance(config_file, basestring):
+        parser = configparser.SafeConfigParser()
+        if isinstance(config_file, str):
             if not parser.read(config_file):
                 raise IOError('Failed to read configuration file "%s"' % config_file)
         elif hasattr(config_file, 'read'):
@@ -132,7 +132,7 @@ def test_config(self, config_files):
             (inputs, outputs) = self.process_config(config_file)
             for (section, plugin) in inputs + outputs:
                 logging.debug('Active configuration for section [%s]:' % section)
-                for name, value in plugin.options.iteritems():
+                for name, value in plugin.options.items():
                     logging.debug('%s=%s' % (name, repr(value)))
 
     def make_docs(self, config_files):
@@ -206,7 +206,7 @@ def help_plugin(self, plugin_name):
         self.pprint('')
         if hasattr(plugin, 'options'):
             self.pprint('Options:')
-            for (name, (default, desc, _)) in sorted(plugin.options.iteritems(), key=lambda(name, desc): name):
+            for (name, (default, desc, _)) in sorted(plugin.options.items(), key=lambda name_desc: name_desc[0]):
                 if default:
                     self.pprint('%s (default "%s")' % (name, default), indent=' '*4)
                 else:
diff --git a/dbsuite/parser.py b/dbsuite/parser.py
index 59c678b..4823373 100644
--- a/dbsuite/parser.py
+++ b/dbsuite/parser.py
@@ -37,7 +37,7 @@
 import sys
 import math
 from decimal import Decimal
-from itertools import tee, izip
+from itertools import tee
 
 from dbsuite.tokenizer import TokenTypes, Token, Error, TokenError, sql92_namechars, sql92_identchars
 
@@ -55,7 +55,6 @@
     'merge_whitespace',
     'strip_whitespace',
     'split_lines',
-    'Connection',
     'Error',
     'ParseError',
     'ParseBacktrack',
@@ -82,7 +81,7 @@
 ):
     TT.add(type, name)
 
-ctrlchars = re.compile(ur'([\x00-\x1F\x7F-\xFF]+)')
+ctrlchars = re.compile(r'([\x00-\x1F\x7F-\xFF]+)')
 def quote_str(s, quotechar="'"):
     """Quotes a string, doubling all quotation characters within it.
 
@@ -331,7 +330,7 @@ def merge_whitespace(tokens):
     space = ''
     line = col = 1
     # Iterate pairwise over the tokens
-    for last, token in izip(a, b):
+    for last, token in zip(a, b):
         if last.type == TT.WHITESPACE:
             if token.type == last.type:
                 space += token.source
@@ -378,7 +377,7 @@ def split_lines(tokens):
     for token in tokens:
         (type, value, source, line, column) = token
         while '\n' in source:
-            if isinstance(value, basestring) and '\n' in value:
+            if isinstance(value, str) and '\n' in value:
                 i = value.index('\n') + 1
                 new_value, value = value[:i], value[i:]
             else:
@@ -409,7 +408,7 @@ class ParseTokenError(TokenError, ParseError):
 
     def token_name(self, token):
         """Formats a token for display in an error message string"""
-        if isinstance(token, basestring):
+        if isinstance(token, str):
             return token
         elif isinstance(token, int):
             return TT.names[token]
@@ -833,7 +832,7 @@ def _cmp_tokens(self, token, template):
             TT.TERMINATOR:  (TT.STATEMENT,),
             TT.EOF:         (TT.STATEMENT,),
         }
-        if isinstance(template, basestring):
+        if isinstance(template, str):
             if token.type in (TT.KEYWORD, TT.OPERATOR) and token.value == template:
                 return token
             elif token.type == TT.IDENTIFIER and token.value == template and token.source[0] != '"':
@@ -985,8 +984,8 @@ def _match_sequence(self, templates, prespace=None, postspace=None, interspace=N
         self._save_state()
         # Build lists of prespace and postspace settings for each template and
         # zip them together in the loop
-        prespaces = [prespace] + [interspace for i in xrange(len(templates) - 1)]
-        postspaces = [interspace for i in xrange(len(templates) - 1)] + [postspace]
+        prespaces = [prespace] + [interspace for i in range(len(templates) - 1)]
+        postspaces = [interspace for i in range(len(templates) - 1)] + [postspace]
         for (template, prespace, postspace) in zip(templates, prespaces, postspaces):
             if not self._match(template, prespace, postspace):
                 self._restore_state()
diff --git a/dbsuite/plugins/__init__.py b/dbsuite/plugins/__init__.py
index bd875ab..ab1dbed 100644
--- a/dbsuite/plugins/__init__.py
+++ b/dbsuite/plugins/__init__.py
@@ -41,7 +41,7 @@
 import re
 import fnmatch
 from operator import attrgetter
-from itertools import chain, groupby, ifilter
+from itertools import chain, groupby
 
 import dbsuite.db
 from dbsuite.tuples import (
@@ -126,7 +126,7 @@ def __init__(self):
         self.options = {}
         # Construct some utility mappings for the database class conversions
         classes = [
-            cls for cls in dbsuite.db.__dict__.itervalues()
+            cls for cls in dbsuite.db.__dict__.values()
             if type(cls) == type(object)
             and issubclass(cls, dbsuite.db.DatabaseObject)
             and hasattr(cls, 'config_names')
@@ -153,7 +153,7 @@ def __init__(self):
         # Remove concrete classes (those with no children)
         self.__abstracts = dict(
             (base, classes)
-            for (base, classes) in self.__abstracts.iteritems()
+            for (base, classes) in self.__abstracts.items()
             if classes
         )
 
@@ -374,7 +374,7 @@ class names are accepted (see the "config_names" attribute of the
                 self.__names[name] for name in
                 self.convert_set(value, separator, subconvert=lambda x: x.lower())
             )
-        except KeyError, e:
+        except KeyError as e:
             raise PluginConfigurationError('Unknown database object type "%s"' % str(e))
         if abstract:
             return value
@@ -393,7 +393,7 @@ def configure(self, config):
         override this method they should call the inherited method and then
         test that the configuration is valid.
         """
-        for (name, (default, doc, convert)) in self.options.iteritems():
+        for (name, (default, doc, convert)) in self.options.items():
             value = config.get(name, default)
             # Note: Conversion is applied to defaults as well as explicitly
             # specified values (unless the default is None, which is passed
@@ -401,7 +401,7 @@ def configure(self, config):
             if convert is not None and value is not None:
                 try:
                     value = convert(value)
-                except Exception, e:
+                except Exception as e:
                     raise PluginConfigurationError('Error reading value for "%s": %s' % (name, str(e)))
             self.options[name] = value
 
@@ -1050,11 +1050,11 @@ def filter(self, items, key=None):
         if self.include:
             def include_predicate(item):
                 return any(fnmatch.fnmatchcase(key(item), pattern) for pattern in self.include)
-            result = ifilter(include_predicate, result)
+            result = filter(include_predicate, result)
         if self.exclude:
             def exclude_predicate(item):
                 return not any(fnmatch.fnmatchcase(key(item), pattern) for pattern in self.exclude)
-            result = ifilter(exclude_predicate, result)
+            result = filter(exclude_predicate, result)
         return result
 
     def fetch_some(self, cursor, count=10):
@@ -1132,7 +1132,7 @@ def relation_dependents(self):
         result = chain(
             (
                 RelationDep(*(relation + dep))
-                for (relation, deps) in self.relation_dependencies.iteritems()
+                for (relation, deps) in self.relation_dependencies.items()
                 for dep in deps
             ),
             (
@@ -1203,7 +1203,7 @@ def foreign_key_cols(self):
 
     @cachedproperty
     def parent_keys(self):
-        result = (key for (table, keys) in self.foreign_keys.iteritems() for key in keys)
+        result = (key for (table, keys) in self.foreign_keys.items() for key in keys)
         result = sorted(result, key=attrgetter('const_schema', 'const_table', 'const_name'))
         result = groupby(result, key=attrgetter('const_schema', 'const_table', 'const_name'))
         return dict((ConstraintRef(*ukey), list(fkeys)) for (ukey, fkeys) in result)
@@ -1259,7 +1259,7 @@ def trigger_dependencies(self):
     def trigger_dependents(self):
         result = (
             TriggerDep(*(trigger + dep))
-            for (trigger, deps) in self.trigger_dependencies.iteritems()
+            for (trigger, deps) in self.trigger_dependencies.items()
             for dep in deps
         )
         result = sorted(result, key=attrgetter('dep_schema', 'dep_name', 'trig_schema', 'trig_name'))
@@ -1323,7 +1323,7 @@ class definition) named InputPlugin or OutputPlugin. It yields a 2-tuple
         logging.debug('Attempting to import module/package %s' % mod_name)
         try:
             __import__(mod_name)
-        except ImportError, e:
+        except ImportError as e:
             logging.debug(str(e))
             continue
         try:
@@ -1351,7 +1351,7 @@ def load_plugin(name, root=None):
     if not mod_name in sys.modules:
         try:
             __import__(mod_name, level=0)
-        except ImportError, e:
+        except ImportError as e:
             raise PluginLoadError(e)
     try:
         module = sys.modules[mod_name]
diff --git a/dbsuite/plugins/db2/__init__.py b/dbsuite/plugins/db2/__init__.py
index 427aa68..919133d 100644
--- a/dbsuite/plugins/db2/__init__.py
+++ b/dbsuite/plugins/db2/__init__.py
@@ -116,9 +116,9 @@ def make_str(value):
     """
     if value is None:
         return None
-    elif isinstance(value, unicode):
+    elif isinstance(value, str):
         return value
-    elif isinstance(value, basestring):
+    elif isinstance(value, str):
         return decoder(value)[0]
     else:
         raise ValueError('Invalid string value %s' % repr(value))
@@ -131,7 +131,7 @@ def make_int(value):
     """
     if value is None:
         return None
-    elif isinstance(value, basestring):
+    elif isinstance(value, str):
         raise ValueError('Invalid integer value %s' % repr(value))
     else:
         return int(value)
@@ -154,7 +154,7 @@ def make_datetime(value):
         return value
     elif isinstance(value, datetime.date):
         return datetime.datetime.combine(value, datetime.time.min)
-    elif isinstance(value, basestring):
+    elif isinstance(value, str):
         return datetime.datetime(*([int(x) for x in re.match(r'(\d{4})-(\d{2})-(\d{2})[T -](\d{2})[:.](\d{2})[:.](\d{2})\.(\d{6})\d*', value).groups()]))
     elif hasattr(value, 'value') and isinstance(value.value, int):
         return datetime.datetime.fromtimestamp(value.value)
diff --git a/dbsuite/plugins/db2/luw/tokenizer.py b/dbsuite/plugins/db2/luw/tokenizer.py
index a08e6a2..42c3711 100644
--- a/dbsuite/plugins/db2/luw/tokenizer.py
+++ b/dbsuite/plugins/db2/luw/tokenizer.py
@@ -168,12 +168,12 @@ def utf8sub(match):
                     if match.group(1) == '\\':
                         return '\\'
                     elif match.group(1).startswith('+'):
-                        return unichr(int(match.group(1)[1:], 16))
+                        return chr(int(match.group(1)[1:], 16))
                     else:
-                        return unichr(int(match.group(1), 16))
+                        return chr(int(match.group(1), 16))
                 try:
                     s = self._extract_string(self.multiline_str)
-                except ValueError, e:
+                except ValueError as e:
                     self._add_token(TT.ERROR, str(e))
                 else:
                     self._add_token(TT.STRING, utf8re.sub(utf8sub, s.decode('UTF-8')))
diff --git a/dbsuite/plugins/db2/zos/parser.py b/dbsuite/plugins/db2/zos/parser.py
index f87003c..ee0b697 100644
--- a/dbsuite/plugins/db2/zos/parser.py
+++ b/dbsuite/plugins/db2/zos/parser.py
@@ -1917,7 +1917,7 @@ def _parse_column_constraint(self):
                 self._expect(TT.IDENTIFIER)
                 self._expect(')')
             t = ['DELETE', 'UPDATE']
-            for i in xrange(2):
+            for i in range(2):
                 if self._match('ON'):
                     t.remove(self._expect_one_of(t).value)
                     if self._match('NO'):
@@ -1991,7 +1991,7 @@ def _parse_table_constraint(self):
             self._parse_ident_list()
             self._expect(')')
             t = ['DELETE', 'UPDATE']
-            for i in xrange(2):
+            for i in range(2):
                 if self._match('ON'):
                     t.remove(self._expect_one_of(t).value)
                     if self._match('NO'):
@@ -2470,7 +2470,7 @@ def _parse_partition_boundary(self):
     def _parse_copy_options(self):
         """Parse copy options for CREATE TABLE... LIKE statements"""
         # XXX Tidy this up (shouldn't just be a 2-time loop)
-        for i in xrange(2):
+        for i in range(2):
             if self._match_one_of(['INCLUDING', 'EXCLUDING']):
                 if self._match('COLUMN'):
                     self._expect('DEFAULTS')
@@ -5144,7 +5144,7 @@ def _parse_create_workload_statement(self):
             # parse error if the first match fails
             try:
                 self._parse_connection_attributes()
-            except ParseError, e:
+            except ParseError as e:
                 if first:
                     raise e
             else:
@@ -7551,7 +7551,7 @@ def _parse_disconnect_command(self):
             self.current_connection = None
         else:
             t = self._expect_clp_string()
-            if isinstance(self.current_connection.database, basestring) and s.lower() == t.value.lower():
+            if isinstance(self.current_connection.database, str) and s.lower() == t.value.lower():
                 self.current_connection = None
 
     def _parse_drop_contact_command(self):
diff --git a/dbsuite/plugins/db2/zos/tokenizer.py b/dbsuite/plugins/db2/zos/tokenizer.py
index cf141b7..05363ac 100644
--- a/dbsuite/plugins/db2/zos/tokenizer.py
+++ b/dbsuite/plugins/db2/zos/tokenizer.py
@@ -109,8 +109,8 @@ def _handle_hexstring(self):
                 s = self._extract_string(False)
                 if len(s) % 2 != 0:
                     raise ValueError('Hex-string must have an even length')
-                s = ''.join(unichr(int(s[i:i + 2], 16)) for i in xrange(0, len(s), 2))
-            except ValueError, e:
+                s = ''.join(chr(int(s[i:i + 2], 16)) for i in range(0, len(s), 2))
+            except ValueError as e:
                 self._add_token(TT.ERROR, str(e))
             else:
                 self._add_token(TT.STRING, s)
@@ -125,8 +125,8 @@ def _handle_unihexstring(self):
                 s = self._extract_string(False)
                 if len(s) % 4 != 0:
                     raise ValueError('Unicode hex-string must have a length which is a multiple of 4')
-                s = ''.join(unichr(int(s[i:i + 4], 16)) for i in xrange(0, len(s), 4))
-            except ValueError, e:
+                s = ''.join(chr(int(s[i:i + 4], 16)) for i in range(0, len(s), 4))
+            except ValueError as e:
                 self._add_token(TT.ERROR, str(e))
             else:
                 self._add_token(TT.STRING, s)
@@ -141,8 +141,8 @@ def _handle_unistring(self):
                 # XXX Needs testing ... what exactly is the Info Center on
                 # about with these Graphic String literals (mixing DBCS in an
                 # SBCS/MBCS file?!)
-                s = unicode(self._extract_string(self.multiline_str))
-            except ValueError, e:
+                s = str(self._extract_string(self.multiline_str))
+            except ValueError as e:
                 self._add_token(TT.ERROR, str(e))
             else:
                 self._add_token(TT.STRING, s)
diff --git a/dbsuite/plugins/html/__init__.py b/dbsuite/plugins/html/__init__.py
index d2caf1c..78c5354 100644
--- a/dbsuite/plugins/html/__init__.py
+++ b/dbsuite/plugins/html/__init__.py
@@ -209,7 +209,7 @@ def build_site(self, database):
         })
         self.options = dict(self.options_templates)
         for option in self.substitute():
-            if isinstance(self.options[option], basestring):
+            if isinstance(self.options[option], str):
                 self.options[option] = Template(self.options[option]).safe_substitute(values)
         return self.site_class(database, self.options)
 
diff --git a/dbsuite/plugins/html/document.py b/dbsuite/plugins/html/document.py
index 5be7e03..1cab4f8 100644
--- a/dbsuite/plugins/html/document.py
+++ b/dbsuite/plugins/html/document.py
@@ -31,12 +31,13 @@
     )
 
 import sys
+import io
 import os
 import re
 import traceback
 import datetime
 import logging
-import urlparse
+import urllib.parse
 import threading
 from operator import attrgetter
 from pkg_resources import resource_stream, resource_string
@@ -64,15 +65,6 @@
     # if xapian is required but not present
     pass
 
-# Import the fastest StringIO implementation
-try:
-    from cStringIO import StringIO
-except ImportError:
-    try:
-        from StringIO import StringIO
-    except ImportError:
-        raise ImportError('unable to find a StringIO implementation')
-
 
 # Constants for HTML versions
 (
@@ -104,10 +96,10 @@ def _format(self, content):
         elif isinstance(content, bool):
             # Format booleans as Yes/No
             return ['No', 'Yes'][content]
-        elif isinstance(content, (int, long)):
+        elif isinstance(content, int):
             # Format integer number with , as a thousand separator
             s = str(content)
-            for i in xrange(len(s) - 3, 0, -3):
+            for i in range(len(s) - 3, 0, -3):
                 s = '%s,%s' % (s[:i], s[i:])
             return s
         elif isinstance(content, datetime.datetime):
@@ -677,7 +669,7 @@ def create_index_links(self):
         # Sort the list of database classes by name and filter out those which
         # have no index content
         dbclasses = [
-            dbclass for dbclass in sorted(self.index_docs.iterkeys(),
+            dbclass for dbclass in sorted(self.index_docs.keys(),
                 key=lambda dbclass: self.type_name(dbclass))
             if self.index_docs[dbclass]
         ]
@@ -695,7 +687,8 @@ def create_index_links(self):
         dbclass_parent = self.object_document(self.database)
         dbclass_prior = None
         for (dbclass, dbclass_doc) in zip(dbclasses, dbclass_docs):
-            letter_docs = sorted(self.index_docs[dbclass].itervalues(), key=attrgetter('letter'))
+            letter_docs = sorted(self.index_docs[dbclass].values(),
+                                 key=attrgetter('letter'))
             if letter_docs:
                 # Replace the URL of the class document with the URL of the
                 # first letter of the index. We can't do this at construction
@@ -969,7 +962,7 @@ def write(self):
                 logging.info('Writing documents and graphs')
             else:
                 logging.info('Writing documents')
-            self.write_single(self.urls.itervalues())
+            self.write_single(self.urls.values())
         else:
             # Writing documents with multiple threads is split into two phases:
             # writing graphs, and writing non-graphs. This avoids a race
@@ -982,12 +975,12 @@ def write(self):
             if self.diagrams:
                 logging.info('Writing graphs')
                 self.write_multi(
-                    doc for doc in self.urls.itervalues()
+                    doc for doc in self.urls.values()
                     if isinstance(doc, GraphDocument)
                 )
             logging.info('Writing documents')
             self.write_multi(
-                doc for doc in self.urls.itervalues()
+                doc for doc in self.urls.values()
                 if not isinstance(doc, GraphDocument)
             )
         if self.search:
@@ -998,7 +991,7 @@ def write(self):
                 xapian.DB_CREATE_OR_OVERWRITE)
             db.begin_transaction()
             try:
-                for doc in self.urls.itervalues():
+                for doc in self.urls.values():
                     if isinstance(doc, HTMLDocument) and doc.ftsdoc:
                         db.add_document(doc.ftsdoc)
                 db.commit_transaction()
@@ -1041,7 +1034,7 @@ def write_single(self, docs):
             doc = docs.pop()
             try:
                 doc.write()
-            except Exception, e:
+            except Exception as e:
                 # XXX Temporary hack to trace down errors
                 logging.error('While writing %s:' % doc.filename)
                 for line in traceback.format_exception(*sys.exc_info()):
@@ -1090,7 +1083,7 @@ def __thread_write(self):
             doc = self._documents_set.pop()
             try:
                 doc.write()
-            except Exception, e:
+            except Exception as e:
                 # XXX Temporary hack to trace down errors
                 logging.error('While writing %s:' % doc.filename)
                 for line in traceback.format_exception(*sys.exc_info()):
@@ -1107,7 +1100,7 @@ def __init__(self, site, url):
         self.mimetype = 'application/octet-stream'
         self.site = site
         self.url = url
-        self.absolute_url = urlparse.urljoin(self.site.base_url, url)
+        self.absolute_url = urllib.parse.urljoin(self.site.base_url, url)
         parts = [self.site.base_path] + self.url.split('/')
         self.filename = os.path.join(*parts)
         self.tag = self.site.tag
@@ -1129,8 +1122,8 @@ def serialize(self, content):
         types other than unicode (which is the only type this base
         implementation handles).
         """
-        if isinstance(content, basestring):
-            if isinstance(content, unicode):
+        if isinstance(content, str):
+            if isinstance(content, str):
                 return content.encode(self.site.encoding)
             else:
                 return content
@@ -1276,7 +1269,7 @@ def serialize(self, content):
             result = ['<?xml version="1.0" encoding="%s"?>' % self.site.encoding]
             if self.public_id and self.system_id:
                 result.append('<!DOCTYPE %s PUBLIC "%s" "%s">' % (content.tag, self.public_id, self.system_id))
-            content = unicode(tostring(content))
+            content = str(tostring(content))
             if self.entities:
                 # Dirty manual hack to convert non-XML entities (to support
                 # things like HTML)
@@ -1286,8 +1279,8 @@ def subfunc(match):
                     else:
                         return match.group()
                 entities_re = re.compile('[%s-%s]' % (
-                    unichr(min(self.entities.iterkeys())),
-                    unichr(max(self.entities.iterkeys()))
+                    chr(min(self.entities.keys())),
+                    chr(max(self.entities.keys()))
                 ))
                 content = entities_re.sub(subfunc, content)
             # Glue everything together as a unicode string and leave it to the
@@ -1587,7 +1580,7 @@ def generate_body(self):
         index = sorted(dict(
             (item1.name, [item2 for item2 in index if item1.name == item2.name])
             for item1 in index
-        ).iteritems(), key=lambda (name, _): name)
+        ).items(), key=lambda name__: name__[0])
         body.append(
             tag.dl(
                 ((
@@ -1648,7 +1641,7 @@ def _get_prior(self):
 
     def _get_next(self):
         result = super(HTMLObjectDocument, self)._get_next()
-        if not result and self.dbobject.next:
+        if not result and self.dbobject.__next__:
             result = self.site.object_document(self.dbobject.next)
             self.next = result
         return result
@@ -1735,7 +1728,7 @@ def serialize(self, content):
                 }[ext]
             except KeyError:
                 raise Exception('Unknown image extension "%s"' % ext)
-            result = StringIO()
+            result = io.StringIO()
             method(result)
             return result.getvalue()
         else:
@@ -1760,7 +1753,7 @@ def write_broken(self, msg):
     def write(self):
         try:
             super(GraphDocument, self).write()
-        except Exception, e:
+        except Exception as e:
             self.write_broken('An error occurred while writing image "%s": %s' % (self.filename, e))
 
     def link(self, *args, **kwargs):
@@ -1777,7 +1770,7 @@ def link(self, *args, **kwargs):
     def map(self):
         """Returns an Element containing the client-side image map."""
         assert self.usemap
-        f = StringIO()
+        f = io.StringIO()
         try:
             self.generate().to_map(f)
             result = fromstring(f.getvalue())
diff --git a/dbsuite/plugins/html/plain/document.py b/dbsuite/plugins/html/plain/document.py
index 0ccef73..4d6b985 100644
--- a/dbsuite/plugins/html/plain/document.py
+++ b/dbsuite/plugins/html/plain/document.py
@@ -119,7 +119,7 @@ def table(self, *content, **attrs):
                     table.attrib['id'],
                     ', '.join(
                         '%d: {sorter: %s}' % (index, sorter)
-                        for (index, sorter) in sorters.iteritems()
+                        for (index, sorter) in sorters.items()
                     )
                 ))
                 return (table, script)
@@ -468,7 +468,7 @@ def write(self):
                 if self.usemap:
                     try:
                         im = Image.open(self.filename)
-                    except IOError, e:
+                    except IOError as e:
                         raise Exception('Failed to open image "%s" for resizing: %s' % (self.filename, e))
                     (maxw, maxh) = self.site.max_graph_size
                     (w, h) = im.size
@@ -490,10 +490,10 @@ def write(self):
                                 im = im.convert('RGB').resize((neww, newh), Image.ANTIALIAS)
                             else:
                                 im = im.resize((neww, newh), Image.NEAREST)
-                        except Exception, e:
+                        except Exception as e:
                             raise Exception('Failed to resize image "%s" from (%dx%d) to (%dx%d): %s' % (self.filename, w, h, neww, newh, e))
                         im.save(self.filename)
-        except Exception, e:
+        except Exception as e:
             self.write_broken(str(e))
 
     def map(self):
diff --git a/dbsuite/plugins/html/w3/document.py b/dbsuite/plugins/html/w3/document.py
index 6b0c6f6..11bd711 100644
--- a/dbsuite/plugins/html/w3/document.py
+++ b/dbsuite/plugins/html/w3/document.py
@@ -144,7 +144,7 @@ def table(self, *content, **attrs):
                     table.attrib['id'],
                     ', '.join(
                         '%d: {sorter: %s}' % (index, sorter)
-                        for (index, sorter) in sorters.iteritems()
+                        for (index, sorter) in sorters.items()
                     )
                 ))
                 return (table, script)
@@ -703,12 +703,12 @@ def link(doc):
 
         # Generate links for all documents
         links = {}
-        for doc in set(self.site.urls.itervalues()):
+        for doc in set(self.site.urls.values()):
             if isinstance(doc, HTMLDocument):
                 links[doc] = link(doc)
         # Stitch together links into a single site map document
         root = self.tag.root()
-        queue = set(links.iterkeys())
+        queue = set(links.keys())
         while queue:
             doc = queue.pop()
             if doc.first:
@@ -799,7 +799,7 @@ def write(self):
                 if self.usemap:
                     try:
                         im = Image.open(self.filename)
-                    except IOError, e:
+                    except IOError as e:
                         raise Exception('failed to open image "%s" for resizing: %s' % (self.filename, e))
                     (maxw, maxh) = self.site.max_graph_size
                     (w, h) = im.size
@@ -821,10 +821,10 @@ def write(self):
                                 im = im.convert('RGB').resize((neww, newh), Image.ANTIALIAS)
                             else:
                                 im = im.resize((neww, newh), Image.NEAREST)
-                        except Exception, e:
+                        except Exception as e:
                             raise Exception('failed to resize image "%s" from (%dx%d) to (%dx%d): %s' % (self.filename, w, h, neww, newh, e))
                         im.save(self.filename)
-        except Exception, e:
+        except Exception as e:
             self.write_broken(str(e))
 
     def map(self):
diff --git a/dbsuite/plugins/odf/text/__init__.py b/dbsuite/plugins/odf/text/__init__.py
index 250ffe0..f4b508d 100644
--- a/dbsuite/plugins/odf/text/__init__.py
+++ b/dbsuite/plugins/odf/text/__init__.py
@@ -71,7 +71,7 @@ def __init__(self):
         # Update ElementTree's namespace map with the myriad namespaces used by
         # ODF (this isn't strictly necessary but it helps with debugging)
         _namespace_map.update(dict(
-            (value, name) for (name, value) in self._odf_namespaces.iteritems()
+            (value, name) for (name, value) in self._odf_namespaces.items()
         ))
 
     def _element(self, _name, *content, **attrs):
@@ -87,7 +87,7 @@ def ns_conv(name):
             return '{%s}%s' % (self._odf_namespaces[ns], name.replace('_', '-'))
         return super(ODFElementFactory, self)._element(ns_conv(_name), *content, **dict(
             (ns_conv(key), value)
-            for (key, value) in attrs.iteritems()
+            for (key, value) in attrs.items()
         ))
 
 
@@ -271,7 +271,7 @@ def generate_meta(self):
             ),
             office_version='1.1'
         )
-        doc = unicode(tostring(doc))
+        doc = str(tostring(doc))
         return doc.encode(self.encoding)
 
     def generate_mimetype(self):
@@ -283,7 +283,7 @@ def generate_settings(self):
             tag.office_settings(),
             office_version='1.1'
         )
-        doc = unicode(tostring(doc))
+        doc = str(tostring(doc))
         return doc.encode(self.encoding)
 
     def generate_styles(self):
@@ -453,7 +453,7 @@ def generate_styles(self):
             ),
             office_version='1.1'
         )
-        doc = unicode(tostring(doc))
+        doc = str(tostring(doc))
         return doc.encode(self.encoding)
 
     def generate_content(self):
@@ -472,7 +472,7 @@ def generate_content(self):
             ),
             office_version='1.1'
         )
-        doc = unicode(tostring(doc))
+        doc = str(tostring(doc))
         return doc.encode(self.encoding)
 
     def generate_index(self):
diff --git a/dbsuite/plugins/sql/__init__.py b/dbsuite/plugins/sql/__init__.py
index 0a0d707..4805260 100644
--- a/dbsuite/plugins/sql/__init__.py
+++ b/dbsuite/plugins/sql/__init__.py
@@ -123,7 +123,7 @@ def execute(self, database):
         self.find_cache = {}
         for dbobject in database:
             self.get_statement(dbobject)
-        s = unicode('\n\n'.join(stmt for stmt in self.script))
+        s = str('\n\n'.join(stmt for stmt in self.script))
         # Finally, write the document to disk
         logging.info('Writing output to "%s"' % self.options['filename'])
         f = open(self.options['filename'], 'w')
@@ -158,7 +158,7 @@ def get_predicates(self, predicates, qualifier=None):
             qualifier = ''
         return ' AND '.join(
             '%s%s = %s' % (qualifier, format_ident(key), quote_str(value))
-            for (key, value) in predicates.iteritems()
+            for (key, value) in predicates.items()
         )
 
     def get_statement(self, dbobject):
@@ -371,8 +371,8 @@ def merge(self, dbobject, table, predicates):
                 format_ident(table),
                 self.get_description(dbobject.description),
                 self.get_predicates(predicates, qualifier='T'),
-                ', '.join(format_ident(k) for k in predicates.iterkeys()),
-                ', '.join(quote_str(v) for v in predicates.itervalues()),
+                ', '.join(format_ident(k) for k in predicates.keys()),
+                ', '.join(quote_str(v) for v in predicates.values()),
                 self.terminator,
             )
 
@@ -411,8 +411,8 @@ def merge_table(self, o):
                 format_ident(self.schema),
                 field_comments,
                 self.get_predicates(predicates, qualifier='T'),
-                ', '.join(format_ident(k) for k in predicates.iterkeys()),
-                ', '.join(quote_str(v) for v in predicates.itervalues()),
+                ', '.join(format_ident(k) for k in predicates.keys()),
+                ', '.join(quote_str(v) for v in predicates.values()),
                 self.terminator,
             )
         else:
@@ -452,8 +452,8 @@ def merge_param_type(self, o, type):
                 format_ident(self.schema),
                 param_comments,
                 self.get_predicates(predicates),
-                ', '.join(format_ident(k) for k in predicates.iterkeys()),
-                ', '.join(quote_str(v) for v in predicates.itervalues()),
+                ', '.join(format_ident(k) for k in predicates.keys()),
+                ', '.join(quote_str(v) for v in predicates.values()),
                 type,
                 self.terminator,
             )
diff --git a/dbsuite/plugins/tex/__init__.py b/dbsuite/plugins/tex/__init__.py
index 6a2992a..d52910f 100644
--- a/dbsuite/plugins/tex/__init__.py
+++ b/dbsuite/plugins/tex/__init__.py
@@ -170,7 +170,7 @@ def execute(self, database):
         })
         self.options = dict(self.options_templates)
         for option in self.substitute():
-            if isinstance(self.options[option], basestring):
+            if isinstance(self.options[option], str):
                 self.options[option] = Template(self.options[option]).safe_substitute(values)
         doc = TeXDocumentation(database, self.options)
         doc.write()
diff --git a/dbsuite/plugins/tex/document.py b/dbsuite/plugins/tex/document.py
index 1786829..194e16c 100644
--- a/dbsuite/plugins/tex/document.py
+++ b/dbsuite/plugins/tex/document.py
@@ -267,10 +267,10 @@ def _format(self, content):
         elif isinstance(content, bool):
             # Format booleans as Yes/No
             return ['No', 'Yes'][content]
-        elif isinstance(content, (int, long)):
+        elif isinstance(content, int):
             # Format integer number with , as a thousand separator
             s = str(content)
-            for i in xrange(len(s) - 3, 0, -3):
+            for i in range(len(s) - 3, 0, -3):
                 s = '%s,%s' % (s[:i], s[i:])
             return s
         elif isinstance(content, datetime.datetime):
diff --git a/dbsuite/plugins/xml/input/__init__.py b/dbsuite/plugins/xml/input/__init__.py
index 9ea378e..17f1e15 100644
--- a/dbsuite/plugins/xml/input/__init__.py
+++ b/dbsuite/plugins/xml/input/__init__.py
@@ -65,13 +65,13 @@ def timestamp(value):
                     return datetime.datetime(int(yr), int(mth), int(day))
             else:
                 raise ValueError()
-        except ValueError, e:
+        except ValueError as e:
             raise ValueError('Invalid timestamp representation: %s' % value)
     else:
         return None
 
 def integer(value):
-    """Utility routine for converting a value into an integer/long object.
+    """Utility routine for converting a value into an integer object.
 
     This routine is used to transform values extracted from the XML document.
     If value is None, the result is None. Otherwise, the value is assumed to
@@ -79,11 +79,8 @@ def integer(value):
     suitable type.
     """
     if value is not None:
-        assert isinstance(value, basestring)
-        if len(value) < 10:
-            return int(value)
-        else:
-            return long(value)
+        assert isinstance(value, str)
+        return int(value)
     else:
         return None
 
@@ -125,7 +122,7 @@ def configure(self, config):
             raise dbsuite.plugins.PluginConfigurationError('The filename option must be specified')
         try:
             open(self.options['filename'], 'rb')
-        except Exception, e:
+        except Exception as e:
             raise dbsuite.plugins.PluginConfigurationError('Unable to open the specified file: %s' % str(e))
 
     def open(self):
diff --git a/dbsuite/plugins/xml/output/__init__.py b/dbsuite/plugins/xml/output/__init__.py
index ed508a5..431507a 100644
--- a/dbsuite/plugins/xml/output/__init__.py
+++ b/dbsuite/plugins/xml/output/__init__.py
@@ -94,7 +94,7 @@ def execute(self, database):
             self.make_element(db_object)
         # Stitch together the XML tree by adding each element to its parent
         logging.debug('Constructing element hierarchy')
-        for db_object, element in self.elements.iteritems():
+        for db_object, element in self.elements.items():
             if db_object.parent:
                 parent = self.elements[db_object.parent]
                 parent.append(element)
@@ -104,7 +104,7 @@ def execute(self, database):
         root = self.elements[database]
         if self.options['indent']:
             indent(root)
-        s = unicode(tostring(root))
+        s = str(tostring(root))
         s = '<?xml version="1.0" encoding="%s"?>\n%s' % (self.options['encoding'], s)
         # Check there aren't any silly characters (control characters / binary)
         # lurking in the unicode version. Most codecs will blindly pass these
diff --git a/dbsuite/script.py b/dbsuite/script.py
index a01c755..c364481 100644
--- a/dbsuite/script.py
+++ b/dbsuite/script.py
@@ -26,17 +26,11 @@
 import sys
 import re
 import os
+import io
 import logging
 import threading
 import subprocess
-try:
-    import cPickle as pickle
-except ImportError:
-    import pickle
-try:
-    from cStringIO import StringIO
-except ImportError:
-    from StringIO import StringIO
+import pickle
 from datetime import datetime
 from string import Template
 from time import sleep
@@ -242,7 +236,7 @@ def print_transfers(self, scripts=None):
                     dbs[(source, target)] = [f]
                 else:
                     dbs[(source, target)].append(f)
-        for ((source, target), files) in dbs.iteritems():
+        for ((source, target), files) in dbs.items():
             logging.info('%s -> %s:' % (
                 '(missing source)' if source is None else source.database,
                 '(missing consumer)' if target is None else target.database,
@@ -315,12 +309,12 @@ def test_connection(self, connection):
             try:
                 with open(os.devnull, 'w') as null:
                     subprocess.call([cmdline], stdout=null, stderr=null, shell=True)
-            except OSError, e:
+            except OSError as e:
                 pass
             try:
                 saved_instance = get_instance()
                 set_instance(get_instance(connection.instance))
-            except Exception, e:
+            except Exception as e:
                 raise ScriptRuntimeError(str(e))
         try:
             args = [
@@ -347,7 +341,7 @@ def test_connection(self, connection):
             sql += 'CONNECT RESET;\n'
             try:
                 output = p.communicate(sql)[0]
-            except Exception, e:
+            except Exception as e:
                 raise ScriptRuntimeError(str(e))
             if not (0 <= p.returncode < 4):
                 raise ScriptRuntimeError(clean_output(output))
@@ -367,7 +361,7 @@ def test_connections(self, scripts=None):
             try:
                 self.test_connection(connection)
                 logging.info('Test connection to database %s succeeded with username: %s' % (connection.database, connection.username))
-            except Exception, e:
+            except Exception as e:
                 logging.error('Test connection to database %s failed with username: %s. Error:' % (connection.database, connection.username))
                 logging.error(str(e))
                 result = False
@@ -407,7 +401,7 @@ def test_permissions(self, scripts=None):
                 if 'w' in filemodes[f]:
                     os.unlink(f)
                 logging.info('Test %s for %s succeeded' % (action, f))
-            except IOError, e:
+            except IOError as e:
                 logging.error('Unable to %s file %s. Error:' % (action, f))
                 logging.error(str(e))
                 filemodesok = False
@@ -443,7 +437,7 @@ def execute(self, scripts=None, debug=False):
                     if os.path.exists(filename):
                         try:
                             os.unlink(filename)
-                        except Exception, e:
+                        except Exception as e:
                             # If we can't delete a file, log the error but
                             # don't abort (and don't report an error via the
                             # exit code)
@@ -488,7 +482,7 @@ def __init__(
         self.autocommit = autocommit
         self.stoponerror = stoponerror
         self.deletefiles = deletefiles
-        if isinstance(sql_file, basestring):
+        if isinstance(sql_file, str):
             self.filename = sql_file
             sql_file = open(sql_file, 'rU')
         else:
@@ -699,8 +693,8 @@ def _exec_thread(self, debug=False):
             # Pickle ourselves and send the result to the subprocess
             s = pickle.dumps(self, protocol=pickle.HIGHEST_PROTOCOL)
             try:
-                output = StringIO(p.communicate(s)[0])
-            except Exception, e:
+                output = io.StringIO(p.communicate(s)[0])
+            except Exception as e:
                 logging.error('Script %s failed: %s' % (self.filename, str(e)))
             else:
                 # On Linux/UNIX, the returncode is modulus 256
@@ -850,7 +844,7 @@ def _exec_statement(self, statement):
                 close_fds=not IS_WINDOWS
             )
             output = p.communicate(sql)[0]
-        except Exception, e:
+        except Exception as e:
             logging.error('Failed to execute at line %d of script %s: %s' % (statement[0].line, self.filename, str(e)))
             return (8, self._on_states.get(ErrorState))
         else:
@@ -870,7 +864,7 @@ def _exec_statement(self, statement):
                 elif not (0 <= p.returncode < 4):
                     on_state = self._on_states.get(ErrorState)
             # Check whether the output matches any regexes set by ON statements
-            for (condition, action) in self._on_states.iteritems():
+            for (condition, action) in self._on_states.items():
                 if hasattr(condition, 'search'):
                     matched = False
                     for line in output:
@@ -975,7 +969,7 @@ def _exec_on_statement(self, statement):
             else:
                 self._on_states[state] = OnState(statement[0].line, delay, retry_mode, retry_count, action)
             logging.info('ON statement processed successfully')
-        except Exception, e:
+        except Exception as e:
             logging.error('Statement at line %d of script %s produced the following error:' % (statement[0].line, self.filename))
             logging.error(str(e))
             return (8, self._on_states.get(ErrorState))
@@ -989,7 +983,7 @@ def _exec_instance_statement(self, statement):
             # structure ['INSTANCE', instance-name, ';']
             statement = [t for t in statement if t.type not in (TT.WHITESPACE, TT.COMMENT)]
             set_instance(get_instance(statement[1].value))
-        except Exception, e:
+        except Exception as e:
             logging.error('Statement at line %d of script %s produced the following error:' % (statement[0].line, self.filename))
             logging.error(str(e))
             return (8, self._on_states.get(ErrorState))
diff --git a/dbsuite/tokenizer.py b/dbsuite/tokenizer.py
index e4698b7..3ce41a2 100644
--- a/dbsuite/tokenizer.py
+++ b/dbsuite/tokenizer.py
@@ -450,7 +450,7 @@ def parse(self, sql, terminator=';', line_split=False):
             for token in self._tokens:
                 (type, value, source, line, column) = token
                 while '\n' in source:
-                    if isinstance(value, basestring) and '\n' in value:
+                    if isinstance(value, str) and '\n' in value:
                         i = value.index('\n') + 1
                         newvalue, value = value[:i], value[i:]
                     else:
@@ -725,7 +725,7 @@ def _handle_apos(self):
         """Parses single quote characters (') in the source."""
         try:
             self._add_token(TT.STRING, self._extract_string(self.multiline_str))
-        except ValueError, e:
+        except ValueError as e:
             self._add_token(TT.ERROR, str(e))
 
     def _handle_asterisk(self):
@@ -755,7 +755,7 @@ def _handle_colon(self):
         if self._char in ['"', "'"]:
             try:
                 self._add_token(TT.PARAMETER, self._extract_string(False))
-            except ValueError, e:
+            except ValueError as e:
                 self._add_token(TT.ERROR, str(e))
         else:
             self._mark()
@@ -776,7 +776,7 @@ def _handle_digit(self):
         """Parses numeric digits (0..9) in the source."""
         self._mark()
         self._next()
-        convert = long
+        convert = int
         valid = set('0123456789Ee.')
         while self._char in valid:
             if self._char == '.':
@@ -790,7 +790,7 @@ def _handle_digit(self):
             self._next()
         try:
             self._add_token(TT.NUMBER, convert(self._marked_chars))
-        except ValueError, e:
+        except ValueError as e:
             self._add_token(TT.ERROR, str(e))
 
     def _handle_equal(self):
@@ -880,7 +880,7 @@ def _handle_quote(self):
                 self._add_token(TT.LABEL, ident)
             else:
                 self._add_token(TT.IDENTIFIER, ident)
-        except ValueError, e:
+        except ValueError as e:
             self._add_token(ERROR, str(e))
 
     def _handle_semicolon(self):
@@ -920,7 +920,7 @@ def _handle_slash(self):
                             break
                     else:
                         self._next()
-            except ValueError, e:
+            except ValueError as e:
                 self._add_token(TT.ERROR, str(e))
         else:
             self._add_token(TT.OPERATOR, '/')
