diff --git a/indra/lib/python/indra/ipc/llmessage.py b/indra/lib/python/indra/ipc/llmessage.py
index 91fb36b72c62b4979c9ca1cde2f3f4ab00fc2833..663e2d9c63a532ec9a5c2a7d7b7ad71e7201120f 100755
--- a/indra/lib/python/indra/ipc/llmessage.py
+++ b/indra/lib/python/indra/ipc/llmessage.py
@@ -26,8 +26,8 @@
 $/LicenseInfo$
 """
 
-from compatibility import Incompatible, Older, Newer, Same
-from tokenstream import TokenStream
+from .compatibility import Incompatible, Older, Newer, Same
+from .tokenstream import TokenStream
 
 ###
 ### Message Template
@@ -42,8 +42,8 @@ def addMessage(self, m):
     
     def compatibleWithBase(self, base):
         messagenames = (
-              frozenset(self.messages.keys())
-            | frozenset(base.messages.keys())
+              frozenset(list(self.messages.keys()))
+            | frozenset(list(base.messages.keys()))
             )
             
         compatibility = Same()
@@ -142,7 +142,7 @@ def compatibleWithBase(self, base):
         baselen = len(base.blocks)
         samelen = min(selflen, baselen)
             
-        for i in xrange(0, samelen):
+        for i in range(0, samelen):
             selfblock = self.blocks[i]
             baseblock = base.blocks[i]
             
@@ -196,7 +196,7 @@ def compatibleWithBase(self, base):
         selflen = len(self.variables)
         baselen = len(base.variables)
         
-        for i in xrange(0, min(selflen, baselen)):
+        for i in range(0, min(selflen, baselen)):
             selfvar = self.variables[i]
             basevar = base.variables[i]
             
diff --git a/indra/lib/python/indra/ipc/tokenstream.py b/indra/lib/python/indra/ipc/tokenstream.py
index b96f26d3ffdc033d20677ee3e5ff98cabe8dff12..ab97e94846514c1015539696639e087ceecf46c3 100755
--- a/indra/lib/python/indra/ipc/tokenstream.py
+++ b/indra/lib/python/indra/ipc/tokenstream.py
@@ -60,7 +60,7 @@ def __str__(self):
         return "line %d: %s @ ... %s" % (
             self.line, self.reason, self._contextString())
 
-    def __nonzero__(self):
+    def __bool__(self):
         return False
 
 
diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py
index 013ba4798ce668a0c65263d1ef73f66bf0e036a8..cc961e268a54f8e0dcac0d7ce8515928e66dfe24 100755
--- a/indra/lib/python/indra/util/llmanifest.py
+++ b/indra/lib/python/indra/util/llmanifest.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python3
 """\
 @file llmanifest.py
 @author Ryan Williams
@@ -28,7 +29,7 @@
 """
 
 from collections import namedtuple, defaultdict
-import commands
+import subprocess
 import errno
 import filecmp
 import fnmatch
@@ -70,9 +71,9 @@ def proper_windows_path(path, current_platform = sys.platform):
     path = path.strip()
     drive_letter = None
     rel = None
-    match = re.match("/cygdrive/([a-z])/(.*)", path)
+    match = re.match(r"/cygdrive/([a-z])/(.*)", path)
     if not match:
-        match = re.match('([a-zA-Z]):\\\(.*)', path)
+        match = re.match(r'([a-zA-Z]):\\\(.*)', path)
     if not match:
         return None         # not an absolute path
     drive_letter = match.group(1)
@@ -162,20 +163,20 @@ def get_default_platform(dummy):
 
 def usage(arguments, srctree=""):
     nd = {'name':sys.argv[0]}
-    print """Usage:
+    print("""Usage:
     %(name)s [options] [destdir]
     Options:
-    """ % nd
+    """ % nd)
     for arg in arguments:
         default = arg['default']
         if hasattr(default, '__call__'):
             default = "(computed value) \"" + str(default(srctree)) + '"'
         elif default is not None:
             default = '"' + default + '"'
-        print "\t--%s        Default: %s\n\t%s\n" % (
+        print("\t--%s        Default: %s\n\t%s\n" % (
             arg['name'],
             default,
-            arg['description'] % nd)
+            arg['description'] % nd))
 
 def main(extra=[]):
 ##  print ' '.join((("'%s'" % item) if ' ' in item else item)
@@ -200,10 +201,10 @@ def main(extra=[]):
     for k in 'artwork build dest source'.split():
         args[k] = os.path.normpath(args[k])
 
-    print "Source tree:", args['source']
-    print "Artwork tree:", args['artwork']
-    print "Build tree:", args['build']
-    print "Destination tree:", args['dest']
+    print("Source tree:", args['source'])
+    print("Artwork tree:", args['artwork'])
+    print("Build tree:", args['build'])
+    print("Destination tree:", args['dest'])
 
     # early out for help
     if 'help' in args:
@@ -226,7 +227,7 @@ def main(extra=[]):
             vf = open(args['versionfile'], 'r')
             args['version'] = vf.read().strip().split('.')
         except:
-            print "Unable to read versionfile '%s'" % args['versionfile']
+            print("Unable to read versionfile '%s'" % args['versionfile'])
             raise
 
     # unspecified, default, and agni are default
@@ -238,7 +239,7 @@ def main(extra=[]):
 
     # debugging
     for opt in args:
-        print "Option:", opt, "=", args[opt]
+        print("Option:", opt, "=", args[opt])
 
     # pass in sourceid as an argument now instead of an environment variable
     args['sourceid'] = os.environ.get("sourceid", "")
@@ -246,18 +247,18 @@ def main(extra=[]):
     # Build base package.
     touch = args.get('touch')
     if touch:
-        print '================ Creating base package'
+        print('================ Creating base package')
     else:
-        print '================ Starting base copy'
+        print('================ Starting base copy')
     wm = LLManifest.for_platform(args['platform'], args.get('arch'))(args)
     wm.do(*args['actions'])
     # Store package file for later if making touched file.
     base_package_file = ""
     if touch:
-        print '================ Created base package ', wm.package_file
+        print('================ Created base package ', wm.package_file)
         base_package_file = "" + wm.package_file
     else:
-        print '================ Finished base copy'
+        print('================ Finished base copy')
 
     # handle multiple packages if set
     # ''.split() produces empty list
@@ -284,39 +285,38 @@ def main(extra=[]):
             args['sourceid']       = os.environ.get(package_id + "_sourceid")
             args['dest'] = base_dest_template.format(package_id)
             if touch:
-                print '================ Creating additional package for "', package_id, '" in ', args['dest']
+                print('================ Creating additional package for "', package_id, '" in ', args['dest'])
             else:
-                print '================ Starting additional copy for "', package_id, '" in ', args['dest']
+                print('================ Starting additional copy for "', package_id, '" in ', args['dest'])
             try:
                 wm = LLManifest.for_platform(args['platform'], args.get('arch'))(args)
                 wm.do(*args['actions'])
             except Exception as err:
                 sys.exit(str(err))
             if touch:
-                print '================ Created additional package ', wm.package_file, ' for ', package_id
+                print('================ Created additional package ', wm.package_file, ' for ', package_id)
                 with open(base_touch_template.format(package_id), 'w') as fp:
                     fp.write('set package_file=%s\n' % wm.package_file)
             else:
-                print '================ Finished additional copy "', package_id, '" in ', args['dest']
+                print('================ Finished additional copy "', package_id, '" in ', args['dest'])
     # Write out the package file in this format, so that it can easily be called
     # and used in a .bat file - yeah, it sucks, but this is the simplest...
     if touch:
         with open(touch, 'w') as fp:
             fp.write('set package_file=%s\n' % base_package_file)
-        print 'touched', touch
+        print('touched', touch)
     return 0
 
 class LLManifestRegistry(type):
     def __init__(cls, name, bases, dct):
         super(LLManifestRegistry, cls).__init__(name, bases, dct)
-        match = re.match("(\w+)Manifest", name)
+        match = re.match(r"(\w+)Manifest", name)
         if match:
            cls.manifests[match.group(1).lower()] = cls
 
 MissingFile = namedtuple("MissingFile", ("pattern", "tried"))
 
-class LLManifest(object):
-    __metaclass__ = LLManifestRegistry
+class LLManifest(object, metaclass=LLManifestRegistry):
     manifests = {}
     def for_platform(self, platform, arch = None):
         if arch:
@@ -408,8 +408,8 @@ def prefix(self, src='', build='', dst='', src_dst=None):
     def display_stacks(self):
         width = 1 + max(len(stack) for stack in self.PrefixManager.stacks)
         for stack in self.PrefixManager.stacks:
-            print "{} {}".format((stack + ':').ljust(width),
-                                 os.path.join(*getattr(self, stack)))
+            print("{} {}".format((stack + ':').ljust(width),
+                                 os.path.join(*getattr(self, stack))))
 
     class PrefixManager(object):
         # stack attributes we manage in this LLManifest (sub)class
@@ -426,7 +426,7 @@ def __init__(self, manifest):
             self.prevlen = { stack: len(getattr(self.manifest, stack)) - 1
                              for stack in self.stacks }
 
-        def __nonzero__(self):
+        def __bool__(self):
             # If the caller wrote:
             # if self.prefix(...):
             # then a value of this class had better evaluate as 'True'.
@@ -471,7 +471,7 @@ def end_prefix(self, descr=None):
         build = self.build_prefix.pop()
         dst = self.dst_prefix.pop()
         if descr and not(src == descr or build == descr or dst == descr):
-            raise ValueError, "End prefix '" + descr + "' didn't match '" +src+ "' or '" +dst + "'"
+            raise ValueError("End prefix '" + descr + "' didn't match '" +src+ "' or '" +dst + "'")
 
     def get_src_prefix(self):
         """ Returns the current source prefix."""
@@ -538,7 +538,7 @@ def run_command(self, command):
         Runs an external command.  
         Raises ManifestError exception if the command returns a nonzero status.
         """
-        print "Running command:", command
+        print("Running command:", command)
         sys.stdout.flush()
         try:
             subprocess.check_call(command)
@@ -551,7 +551,7 @@ def created_path(self, path):
           a) verify that you really have created it
           b) schedule it for cleanup"""
         if not os.path.exists(path):
-            raise ManifestError, "Should be something at path " + path
+            raise ManifestError("Should be something at path " + path)
         self.created_paths.append(path)
 
     def put_in_file(self, contents, dst, src=None):
@@ -591,7 +591,7 @@ def copy_action(self, src, dst):
             self.created_paths.append(dst)
             self.ccopymumble(src, dst)
         else:
-            print "Doesn't exist:", src
+            print("Doesn't exist:", src)
 
     def package_action(self, src, dst):
         pass
@@ -609,8 +609,8 @@ def finish(self):
         # file error until all were resolved. This way permits the developer
         # to resolve them all at once.
         if self.missing:
-            print '*' * 72
-            print "Missing files:"
+            print('*' * 72)
+            print("Missing files:")
             # Instead of just dumping each missing file and all the places we
             # looked for it, group by common sets of places we looked. Use a
             # set to store the 'tried' directories, to avoid mismatches due to
@@ -622,12 +622,12 @@ def finish(self):
             # Now dump all the patterns sought in each group of 'tried'
             # directories.
             for tried, patterns in organize.items():
-                print "  Could not find in:"
+                print("  Could not find in:")
                 for dir in sorted(tried):
-                    print "    %s" % dir
+                    print("    %s" % dir)
                 for pattern in sorted(patterns):
-                    print "      %s" % pattern
-            print '*' * 72
+                    print("      %s" % pattern)
+            print('*' * 72)
             raise MissingError('%s patterns could not be found' % len(self.missing))
 
     def copy_finish(self):
@@ -640,7 +640,7 @@ def unpacked_finish(self):
         unpacked_file_name = "unpacked_%(plat)s_%(vers)s.tar" % {
             'plat':self.args['platform'],
             'vers':'_'.join(self.args['version'])}
-        print "Creating unpacked file:", unpacked_file_name
+        print("Creating unpacked file:", unpacked_file_name)
         # could add a gz here but that doubles the time it takes to do this step
         tf = tarfile.open(self.src_path_of(unpacked_file_name), 'w:')
         # add the entire installation package, at the very top level
@@ -651,7 +651,7 @@ def cleanup_finish(self):
         """ Delete paths that were specified to have been created by this script"""
         for c in self.created_paths:
             # *TODO is this gonna be useful?
-            print "Cleaning up " + c
+            print("Cleaning up " + c)
 
     def process_either(self, src, dst):
         # If it's a real directory, recurse through it --
@@ -700,7 +700,7 @@ def includes(self, src, dst):
     def remove(self, *paths):
         for path in paths:
             if os.path.exists(path):
-                print "Removing path", path
+                print("Removing path", path)
                 if os.path.isdir(path):
                     shutil.rmtree(path)
                 else:
@@ -762,7 +762,7 @@ def ccopytree(self, src, dst):
             except (IOError, os.error) as why:
                 errors.append((srcname, dstname, why))
         if errors:
-            raise ManifestError, errors
+            raise ManifestError(errors)
 
 
     def cmakedirs(self, path):
@@ -800,11 +800,11 @@ def contents_of_tar(self, src_tar, dst_dir):
 
     def wildcard_regex(self, src_glob, dst_glob):
         src_re = re.escape(src_glob)
-        src_re = src_re.replace('\*', '([-a-zA-Z0-9._ ]*)')
+        src_re = src_re.replace(r'\*', r'([-a-zA-Z0-9._ ]*)')
         dst_temp = dst_glob
         i = 1
         while dst_temp.count("*") > 0:
-            dst_temp = dst_temp.replace('*', '\g<' + str(i) + '>', 1)
+            dst_temp = dst_temp.replace(r'*', r'\g<' + str(i) + '>', 1)
             i = i+1
         return re.compile(src_re), dst_temp
 
@@ -881,7 +881,7 @@ def try_path(src):
             # assigned! Even if it was, though, we can be sure it is 0.
             return 0
 
-        print "%d files" % count
+        print("%d files" % count)
 
         # Let caller check whether we processed as many files as expected. In
         # particular, let caller notice 0.
@@ -910,10 +910,10 @@ def path_optional(self, src, dst=None):
             added = [os.path.relpath(d, self.get_dst_prefix())
                      for s, d in self.file_list[oldlen:]]
         except (ManifestError, MissingError) as err:
-            print >> sys.stderr, "Warning: "+err.msg
+            print("Warning: %s" % err.msg, file=sys.stderr)
             added = []
         if not added:
-            print "Skipping %s" % dst
+            print("Skipping %s" % dst)
         return added
 
     def do(self, *actions):
diff --git a/indra/lib/python/indra/util/test_win32_manifest.py b/indra/lib/python/indra/util/test_win32_manifest.py
index 9863b97778a68f086e435e6884edbf51ca2752e1..98faef9bf9e0f7072ecaa8dbe6372a72737deb1a 100755
--- a/indra/lib/python/indra/util/test_win32_manifest.py
+++ b/indra/lib/python/indra/util/test_win32_manifest.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 """\
 @file test_win32_manifest.py
 @brief Test an assembly binding version and uniqueness in a windows dll or exe.  
@@ -44,10 +44,10 @@ class NoMatchingAssemblyException(AssemblyTestException):
     pass
 
 def get_HKLM_registry_value(key_str, value_str):
-    import _winreg
-    reg = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
-    key = _winreg.OpenKey(reg, key_str)
-    value = _winreg.QueryValueEx(key, value_str)[0]
+    import winreg
+    reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
+    key = winreg.OpenKey(reg, key_str)
+    value = winreg.QueryValueEx(key, value_str)[0]
     #print 'Found: %s' % value
     return value
         
@@ -62,13 +62,13 @@ def find_vc_dir():
                       (product, version))
             try:
                 return get_HKLM_registry_value(key_str, value_str)
-            except WindowsError, err:
+            except WindowsError as err:
                 x64_key_str = (r'SOFTWARE\Wow6432Node\Microsoft\VisualStudio\%s\Setup\VS' %
                         version)
                 try:
                     return get_HKLM_registry_value(x64_key_str, value_str)
                 except:
-                    print >> sys.stderr, "Didn't find MS %s version %s " % (product,version)
+                    print("Didn't find MS %s version %s " % (product,version), file=sys.stderr)
         
     raise
 
@@ -78,7 +78,7 @@ def find_mt_path():
     return mt_path
     
 def test_assembly_binding(src_filename, assembly_name, assembly_ver):
-    print "checking %s dependency %s..." % (src_filename, assembly_name)
+    print("checking %s dependency %s..." % (src_filename, assembly_name))
 
     (tmp_file_fd, tmp_file_name) = tempfile.mkstemp(suffix='.xml')
     tmp_file = os.fdopen(tmp_file_fd)
@@ -89,10 +89,10 @@ def test_assembly_binding(src_filename, assembly_name, assembly_ver):
     if os.path.splitext(src_filename)[1].lower() == ".dll":
        resource_id = ";#2"
     system_call = '%s -nologo -inputresource:%s%s -out:%s > NUL' % (mt_path, src_filename, resource_id, tmp_file_name)
-    print "Executing: %s" % system_call
+    print("Executing: %s" % system_call)
     mt_result = os.system(system_call)
     if mt_result == 31:
-        print "No manifest found in %s" % src_filename
+        print("No manifest found in %s" % src_filename)
         raise NoManifestException()
 
     manifest_dom = parse(tmp_file_name)
@@ -104,30 +104,30 @@ def test_assembly_binding(src_filename, assembly_name, assembly_ver):
             versions.append(node.getAttribute('version'))
 
     if len(versions) == 0:
-        print "No matching assemblies found in %s" % src_filename
+        print("No matching assemblies found in %s" % src_filename)
         raise NoMatchingAssemblyException()
         
     elif len(versions) > 1:
-        print "Multiple bindings to %s found:" % assembly_name
-        print versions
-        print 
+        print("Multiple bindings to %s found:" % assembly_name)
+        print(versions)
+        print() 
         raise MultipleBindingsException(versions)
 
     elif versions[0] != assembly_ver:
-        print "Unexpected version found for %s:" % assembly_name
-        print "Wanted %s, found %s" % (assembly_ver, versions[0])
-        print
+        print("Unexpected version found for %s:" % assembly_name)
+        print("Wanted %s, found %s" % (assembly_ver, versions[0]))
+        print()
         raise UnexpectedVersionException(assembly_ver, versions[0])
             
     os.remove(tmp_file_name)
     
-    print "SUCCESS: %s OK!" % src_filename
-    print
+    print("SUCCESS: %s OK!" % src_filename)
+    print()
   
 if __name__ == '__main__':
 
-    print
-    print "Running test_win32_manifest.py..."
+    print()
+    print("Running test_win32_manifest.py...")
     
     usage = 'test_win32_manfest <srcFileName> <assemblyName> <assemblyVersion>'
 
@@ -136,9 +136,9 @@ def test_assembly_binding(src_filename, assembly_name, assembly_ver):
         assembly_name = sys.argv[2]
         assembly_ver = sys.argv[3]
     except:
-        print "Usage:"
-        print usage
-        print
+        print("Usage:")
+        print(usage)
+        print()
         raise
     
     test_assembly_binding(src_filename, assembly_name, assembly_ver)
diff --git a/scripts/packages-formatter.py b/scripts/packages-formatter.py
index b1eef3c7211cdba715bad43c890ffa04ccc4bf58..dafa0bf53b7bb687ce88c46416ba6d589849cdae 100755
--- a/scripts/packages-formatter.py
+++ b/scripts/packages-formatter.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 """\
 This module formats the package version and copyright information for the
 viewer and its dependent packages.
@@ -37,8 +37,11 @@
 args = parser.parse_args()
 
 _autobuild=os.getenv('AUTOBUILD', 'autobuild')
+_autobuild_env=os.environ.copy()
+# Coerce stdout encoding to utf-8 as cygwin's will be detected as cp1252 otherwise.
+_autobuild_env["PYTHONIOENCODING"] = "utf-8"
 
-pkg_line=re.compile('^([\w-]+):\s+(.*)$')
+pkg_line=re.compile(r'^([\w-]+):\s+(.*)$')
 
 def autobuild(*args):
     """
@@ -50,7 +53,7 @@ def autobuild(*args):
     try:
         child = subprocess.Popen(command,
                                  stdin=None, stdout=subprocess.PIPE,
-                                 universal_newlines=True)
+                                 universal_newlines=True, env=_autobuild_env)
     except OSError as err:
         if err.errno != errno.ENOENT:
             # Don't attempt to interpret anything but ENOENT
@@ -110,20 +113,20 @@ def add_info(key, pkg, lines):
                 break
 
 # Now that we've run through all of both outputs -- are there duplicates?
-if any(pkgs for pkgs in dups.values()):
-    for key, pkgs in dups.items():
+if any(pkgs for pkgs in list(dups.values())):
+    for key, pkgs in list(dups.items()):
         if pkgs:
-            print >>sys.stderr, "Duplicate %s for %s" % (key, ", ".join(pkgs))
+            print("Duplicate %s for %s" % (key, ", ".join(pkgs)), file=sys.stderr)
     sys.exit(1)
 
-print "%s %s" % (args.channel, args.version)
-print viewer_copyright
+print("%s %s" % (args.channel, args.version))
+print(viewer_copyright)
 version = list(info['versions'].items())
 version.sort()
 for pkg, pkg_version in version:
-    print ': '.join([pkg, pkg_version])
+    print(': '.join([pkg, pkg_version]))
     try:
-        print info['copyrights'][pkg]
+        print(info['copyrights'][pkg])
     except KeyError:
         sys.exit("No copyright for %s" % pkg)
-    print
+    print()
diff --git a/scripts/setup-path.py b/scripts/setup-path.py
index ce83d815bf8d0ac3c743d8848e715da48d2b29bb..427d1195200cd5eeeb76ca0ef968789200832e2c 100755
--- a/scripts/setup-path.py
+++ b/scripts/setup-path.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 """\
 @file setup-path.py
 @brief Get the python library directory in the path, so we don't have
diff --git a/scripts/template_verifier.py b/scripts/template_verifier.py
index f13f012ffa2923592d23e1233159751ebdc224e2..d1086f03b40ccc37583bb4fa7470ed7ed1fa6697 100755
--- a/scripts/template_verifier.py
+++ b/scripts/template_verifier.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 """\
 @file template_verifier.py
 @brief Message template compatibility verifier.
@@ -58,16 +58,15 @@ def add_indra_lib_path():
                 sys.path.insert(0, dir)
             break
     else:
-        print >>sys.stderr, "This script is not inside a valid installation."
+        print("This script is not inside a valid installation.", file=sys.stderr)
         sys.exit(1)
 
 add_indra_lib_path()
 
 import optparse
 import os
-import urllib
+import urllib.request, urllib.parse, urllib.error
 import hashlib
-import certifi
 
 from indra.ipc import compatibility
 from indra.ipc import tokenstream
@@ -91,7 +90,7 @@ def getstatusoutput(command):
 
 
 def die(msg):
-    print >>sys.stderr, msg
+    print(msg, file=sys.stderr)
     sys.exit(1)
 
 MESSAGE_TEMPLATE = 'message_template.msg'
@@ -107,7 +106,7 @@ def retry(times, function, *args, **kwargs):
     for i in range(times):
         try:
             return function(*args, **kwargs)
-        except Exception, e:
+        except Exception as e:
             if i == times - 1:
                 raise e  # we retried all the times we could
 
@@ -139,10 +138,14 @@ def fetch(url):
     if url.startswith('file://'):
         # just open the file directly because urllib is dumb about these things
         file_name = url[len('file://'):]
-        return open(file_name).read()
+        with open(file_name, 'rb') as f:
+            return f.read()
     else:
-        # *FIX: this doesn't throw an exception for a 404, and oddly enough the sl.com 404 page actually gets parsed successfully
-        return ''.join(urllib.urlopen(url, cafile = certifi.where()).readlines())   
+        with urllib.request.urlopen(url) as res:
+            body = res.read()
+            if res.status > 299:
+                sys.exit("ERROR: Unable to download %s. HTTP status %d.\n%s" % (url, res.status, body.decode("utf-8")))
+            return body
 
 def cache_master(master_url):
     """Using the url for the master, updates the local cache, and returns an url to the local cache."""
@@ -154,23 +157,22 @@ def cache_master(master_url):
         and time.time() - os.path.getmtime(master_cache) < MAX_MASTER_AGE):
         return master_cache_url  # our cache is fresh
     # new master doesn't exist or isn't fresh
-    print "Refreshing master cache from %s" % master_url
+    print("Refreshing master cache from %s" % master_url)
     def get_and_test_master():
         new_master_contents = fetch(master_url)
-        llmessage.parseTemplateString(new_master_contents)
+        llmessage.parseTemplateString(new_master_contents.decode("utf-8"))
         return new_master_contents
     try:
         new_master_contents = retry(3, get_and_test_master)
-    except IOError, e:
+    except IOError as e:
         # the refresh failed, so we should just soldier on
-        print "WARNING: unable to download new master, probably due to network error.  Your message template compatibility may be suspect."
-        print "Cause: %s" % e
+        print("WARNING: unable to download new master, probably due to network error.  Your message template compatibility may be suspect.")
+        print("Cause: %s" % e)
         return master_cache_url
     try:
         tmpname = '%s.%d' % (master_cache, os.getpid())
-        mc = open(tmpname, 'wb')
-        mc.write(new_master_contents)
-        mc.close()
+        with open(tmpname, "wb") as mc:
+            mc.write(new_master_contents)
         try:
             os.rename(tmpname, master_cache)
         except OSError:
@@ -181,9 +183,9 @@ def get_and_test_master():
             # a single day.
             os.unlink(master_cache)
             os.rename(tmpname, master_cache)
-    except IOError, e:
-        print "WARNING: Unable to write master message template to %s, proceeding without cache." % master_cache
-        print "Cause: %s" % e
+    except IOError as e:
+        print("WARNING: Unable to write master message template to %s, proceeding without cache." % master_cache)
+        print("Cause: %s" % e)
         return master_url
     return master_cache_url
 
@@ -247,16 +249,16 @@ def run(sysargs):
     # both current and master supplied in positional params
     if len(args) == 2:
         master_filename, current_filename = args
-        print "master:", master_filename
-        print "current:", current_filename
+        print("master:", master_filename)
+        print("current:", current_filename)
         master_url = 'file://%s' % master_filename
         current_url = 'file://%s' % current_filename
     # only current supplied in positional param
     elif len(args) == 1:
         master_url = None
         current_filename = args[0]
-        print "master:", options.master_url 
-        print "current:", current_filename
+        print("master:", options.master_url) 
+        print("current:", current_filename)
         current_url = 'file://%s' % current_filename
     # nothing specified, use defaults for everything
     elif len(args) == 0:
@@ -270,8 +272,8 @@ def run(sysargs):
         
     if current_url is None:
         current_filename = local_template_filename()
-        print "master:", options.master_url
-        print "current:", current_filename
+        print("master:", options.master_url)
+        print("current:", current_filename)
         current_url = 'file://%s' % current_filename
 
     # retrieve the contents of the local template
@@ -282,42 +284,42 @@ def run(sysargs):
         sha_url = "%s.sha256" % current_url
         current_sha = fetch(sha_url)
         if hexdigest == current_sha:
-            print "Message template SHA_256 has not changed."
+            print("Message template SHA_256 has not changed.")
             sys.exit(0)
 
     # and check for syntax
-    current_parsed = llmessage.parseTemplateString(current)
+    current_parsed = llmessage.parseTemplateString(current.decode("utf-8"))
 
     if options.cache_master:
         # optionally return a url to a locally-cached master so we don't hit the network all the time
         master_url = cache_master(master_url)
 
     def parse_master_url():
-        master = fetch(master_url)
+        master = fetch(master_url).decode("utf-8")
         return llmessage.parseTemplateString(master)
     try:
         master_parsed = retry(3, parse_master_url)
-    except (IOError, tokenstream.ParseError), e:
+    except (IOError, tokenstream.ParseError) as e:
         if options.mode == 'production':
             raise e
         else:
-            print "WARNING: problems retrieving the master from %s."  % master_url
-            print "Syntax-checking the local template ONLY, no compatibility check is being run."
-            print "Cause: %s\n\n" % e
+            print("WARNING: problems retrieving the master from %s."  % master_url)
+            print("Syntax-checking the local template ONLY, no compatibility check is being run.")
+            print("Cause: %s\n\n" % e)
             return 0
         
     acceptable, compat = compare(
         master_parsed, current_parsed, options.mode)
 
     def explain(header, compat):
-        print header
+        print(header)
         # indent compatibility explanation
-        print '\n\t'.join(compat.explain().split('\n'))
+        print('\n\t'.join(compat.explain().split('\n')))
 
     if acceptable:
         explain("--- PASS ---", compat)
         if options.force_verification == False:
-            print "Updating sha256 to %s" % hexdigest
+            print("Updating sha256 to %s" % hexdigest)
             sha_filename = "%s.sha256" % current_filename
             sha_file = open(sha_filename, 'w')
             sha_file.write(hexdigest)