diff --git a/examples/example.py b/examples/example.py
index e879042..cac8202 100644
--- a/examples/example.py
+++ b/examples/example.py
@@ -13,9 +13,9 @@ sys.path.append(parent_path)
 
 from itolapi import Itol, ItolExport
 
-print 'Running example itol and itolexport script'
-print ''
-print 'Creating the upload params'
+print('Running example itol and itolexport script')
+print('')
+print('Creating the upload params')
 
 #Create the Itol class
 test = Itol.Itol()
@@ -33,24 +33,24 @@ test.add_variable('dataset1Type','multibar')
 # Check parameters
 test.print_variables()
 #Submit the tree
-print ''
-print 'Uploading the tree.  This may take some time depending on how large the tree is and how much load there is on the itol server'
+print('')
+print('Uploading the tree.  This may take some time depending on how large the tree is and how much load there is on the itol server')
 good_upload = test.upload()
 if good_upload == False:
-    print 'There was an error:'+test.comm.upload_output
+    print('There was an error:'+test.comm.upload_output)
     sys.exit(1)
 
 #Read the tree ID
-print 'Tree ID: '+str(test.comm.tree_id)
+print('Tree ID: '+str(test.comm.tree_id))
 
 #Read the iTOL API return statement
-print 'iTOL output: '+str(test.comm.upload_output)
+print('iTOL output: '+str(test.comm.upload_output))
 
 #Website to be redirected to iTOL tree
-print 'Tree Web Page URL: '+test.get_webpage()
+print('Tree Web Page URL: '+test.get_webpage())
 
 # Warnings associated with the upload
-print 'Warnings: '+str(test.comm.warnings)
+print('Warnings: '+str(test.comm.warnings))
 
 
 # Export a pre-made tree to pdf
@@ -59,13 +59,13 @@ itol_exporter.set_export_param_value('tree','18793532031912684633930')
 itol_exporter.set_export_param_value('format', 'pdf')
 itol_exporter.set_export_param_value('datasetList','dataset1')
 #itol_exporter.export('example_pdf.pdf')
-#print 'exported tree to ',export_location
+#print('exported tree to ',export_location)
 
 # Export the tree above to pdf
-print 'Exporting to pdf'
+print('Exporting to pdf')
 itol_exporter = test.get_itol_export()
 export_location = 'example_pdf.pdf'
 itol_exporter.set_export_param_value('format', 'pdf')
 itol_exporter.set_export_param_value('datasetList','dataset1')
 itol_exporter.export('example_pdf.pdf')
-print 'exported tree to ',export_location
+print('exported tree to ',export_location)
diff --git a/examples/failures/fatal.py b/examples/failures/fatal.py
index 59a2ff7..66b3e07 100644
--- a/examples/failures/fatal.py
+++ b/examples/failures/fatal.py
@@ -34,6 +34,6 @@ itol_o.add_variable('dataset1PieRadiusMin','20')
 itol_o.add_variable('dataset1BarSizeMax','20')
 itol_o.print_variables()
 good_upload = itol_o.upload()
-print 'Tree Web Page URL:\n'+itol_o.get_webpage() + '\n'
-print 'Warnings:'
-print itol_o.comm.warnings
+print('Tree Web Page URL:\n'+itol_o.get_webpage() + '\n')
+print('Warnings:')
+print(itol_o.comm.warnings)
diff --git a/examples/failures/warnings.py b/examples/failures/warnings.py
index 66a56f0..c38d816 100644
--- a/examples/failures/warnings.py
+++ b/examples/failures/warnings.py
@@ -34,6 +34,6 @@ itol_o.add_variable('dataset1PieRadiusMin','20')
 itol_o.add_variable('dataset1BarSizeMax','20')
 itol_o.print_variables()
 good_upload = itol_o.upload()
-print 'Tree Web Page URL:\n'+itol_o.get_webpage() + '\n'
-print 'Warnings:'
-print itol_o.comm.warnings
+print('Tree Web Page URL:\n'+itol_o.get_webpage() + '\n')
+print('Warnings:')
+print(itol_o.comm.warnings)
diff --git a/itolapi/Comm.py b/itolapi/Comm.py
index 6afe3c1..ab10513 100644
--- a/itolapi/Comm.py
+++ b/itolapi/Comm.py
@@ -2,6 +2,8 @@
 This file is for communication between this API and iTOL servers
 This also processes and stores information returned from the server
 """
+from __future__ import unicode_literals
+
 import requests
 
 
@@ -29,7 +31,7 @@ class Comm:
         new_params = {}
         files = {}
         for k,v in params.items():
-            if isinstance(v, file):
+            if hasattr(v, 'read'):
                 files[k] = v
             else:
                 new_params[k] = v
diff --git a/itolapi/Itol.py b/itolapi/Itol.py
index e771ce8..216c930 100644
--- a/itolapi/Itol.py
+++ b/itolapi/Itol.py
@@ -1,12 +1,20 @@
 """
 This is the main file for the iTOL API
 """
+from __future__ import unicode_literals
+
 import argparse
 import sys
 import os
 
 from itolapi import Comm, ItolExport
 
+
+try:
+    basestring
+except NameError:
+    basestring = str
+
 class Itol:
     """
     This class handles the main itol functionality
@@ -18,7 +26,6 @@ class Itol:
         self.variables = dict()
         self.comm = Comm.Comm()
 
-
     def add_variable(self, variable_name, variable_value):
         """
         Add a variable and its value to this upload.  This function includes
@@ -26,9 +33,9 @@ class Itol:
         modifying the variables dictionary
         """
         # Variable checking
-        if not isinstance(variable_name, str):
+        if not isinstance(variable_name, basestring):
             raise TypeError('variable name is not a string')
-        if not isinstance(variable_value, str):
+        if not isinstance(variable_value, basestring):
             raise TypeError('variable value should be a string')
         if self.is_file(variable_name):
             if not os.path.isfile(variable_value):
@@ -67,7 +74,7 @@ class Itol:
         Get the web page where you can download the Itol tree
         """
         webpage = "http://itol.embl.de/external.cgi?tree="+\
-            str(self.comm.tree_id)+"&restore_saved=1"
+            bytes(self.comm.tree_id)+"&restore_saved=1"
         return webpage
 
     def get_itol_export(self):
@@ -85,15 +92,15 @@ class Itol:
         """
         for variable_name, variable_value in self.variables.items():
             if isinstance(variable_value, file):
-                print variable_name+': '+variable_value.name
+                print(variable_name+': '+variable_value.name)
             else:
-                print variable_name+': '+variable_value
+                print(variable_name+': '+variable_value)
 
     def delete_variable(self, variable_name):
         """
         Remove a variable from the dictionary of set variables
         """
-        if self.variables.has_key(variable_name):
+        if variable_name in self.variables:
             del self.variables[variable_name]
 
 
@@ -107,4 +114,4 @@ if __name__ == "__main__":
     itol_upload = Itol()
     itol_upload.add_variable('treeFile', tree_file)
     itol_upload.upload()
-    print itol_upload.get_webpage()
+    print(itol_upload.get_webpage())
diff --git a/itolapi/ItolExport.py b/itolapi/ItolExport.py
index c98c23c..e44422b 100644
--- a/itolapi/ItolExport.py
+++ b/itolapi/ItolExport.py
@@ -1,6 +1,8 @@
 """
 This is the main file for exporting of trees created by iTOL
 """
+from __future__ import unicode_literals
+
 import argparse
 import sys
 
@@ -72,7 +74,7 @@ if __name__ == "__main__":
         itol_exporter.set_export_param_value('datasetList', \
             'dataset1,dataset2,dataset3,dataset4,dataset5,\
             dataset6,dataset7,dataset8,dataset9,dataset10')
-    print 'Exporting tree from server....'
+    print('Exporting tree from server....')
     itol_exporter.export(args.file_location)
-    print 'Exported to ', args.file_location
+    print('Exported to ', args.file_location)
     sys.exit(0)
diff --git a/setup.py b/setup.py
index 6071357..8531d3e 100644
--- a/setup.py
+++ b/setup.py
@@ -21,7 +21,8 @@ setup(name='itolapi',
       scripts=['itolapi/Itol.py', 'itolapi/ItolExport.py'],
       test_suite="tests",
       tests_require=[
-          'mock>=1.0.1'
+          'mock>=1.0.1',
+          'tox',
       ],
       classifiers=[
           'Development Status :: 5 - Production/Stable',
@@ -30,7 +31,10 @@ setup(name='itolapi',
           'Intended Audience :: Science/Research',
           'License :: OSI Approved :: MIT License',
           'Natural Language :: English',
-          'Programming Language :: Python :: 2 :: Only',
+          'Programming Language :: Python',
+          'Programming Language :: Python :: 2.7',
+          'Programming Language :: Python :: 3',
+          'Programming Language :: Python :: 3.4',
           'Topic :: Scientific/Engineering :: Bio-Informatics',
       ],
 )
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..99d2280
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,12 @@
+# Tox (http://tox.testrun.org/) is a tool for running tests
+# in multiple virtualenvs. This configuration file will run the
+# test suite on all supported python versions. To use it, "pip install tox"
+# and then run "tox" from this directory.
+
+[tox]
+envlist = py27, py34
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+    mock

Support Python 3
diff --git a/tests/comm.py b/tests/comm.py
index 3096f64..b4f5e65 100644
--- a/tests/comm.py
+++ b/tests/comm.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
 from mock import MagicMock, patch
 import tempfile
 import unittest
@@ -6,10 +7,16 @@ from itolapi import Comm
 
 
 class PullOutFilesTest(unittest.TestCase):
+    def setUp(self):
+        self.tempfile = tempfile.TemporaryFile()
+
+    def tearDown(self):
+        self.tempfile.close()
+
     def test_pull_out_files(self):
         params = {}
         params['asdf'] = 'qwer'
-        params['zxcv'] = tempfile.TemporaryFile()
+        params['zxcv'] = self.tempfile
         new_params, files = Comm.Comm.pull_out_files(params)
         self.assertEqual(new_params, {'asdf': 'qwer'})
         self.assertEqual(files, {'zxcv': params['zxcv']})
@@ -17,7 +24,7 @@ class PullOutFilesTest(unittest.TestCase):
     def test_doesnt_modify_params(self):
         params = {}
         params['asdf'] = 'qwer'
-        params['zxcv'] = tempfile.TemporaryFile()
+        params['zxcv'] = self.tempfile
         Comm.Comm.pull_out_files(params)
         self.assertTrue('asdf' in params)
         self.assertTrue('zxcv' in params)
@@ -25,12 +32,16 @@ class PullOutFilesTest(unittest.TestCase):
 
 class UploadTreeTest(unittest.TestCase):
     def setUp(self):
+        self.tempfile = tempfile.TemporaryFile()
         self.comm = Comm.Comm()
         self.params = {'asdf': 'qwer'}
-        self.files = {'zxcv': tempfile.TemporaryFile()}
+        self.files = {'zxcv': self.tempfile}
         self.all_params = self.params.copy()
         self.all_params.update(self.files)
 
+    def tearDown(self):
+        self.tempfile.close()
+
     @patch('itolapi.Comm.Comm.pull_out_files')
     @patch('itolapi.Comm.requests')
     @patch('itolapi.Comm.Comm.parse_upload')
diff --git a/tests/itol.py b/tests/itol.py
index f685ddb..6ec2abd 100644
--- a/tests/itol.py
+++ b/tests/itol.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
 from mock import MagicMock, patch
 import unittest
 
diff --git a/tests/itolexport.py b/tests/itolexport.py
index d1af1d9..ee2d5b0 100644
--- a/tests/itolexport.py
+++ b/tests/itolexport.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
 from mock import MagicMock, patch
 import unittest
 

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-mock mock
apt-get update apt-get install -y gcc
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
certifi==2025.1.31
charset-normalizer==3.4.1
exceptiongroup==1.2.2
idna==3.10
iniconfig==2.1.0
-e git+https://github.com/albertyw/itolapi.git@18ab55c5526ef6c8f3285af9363fb8e0e22f2bf1#egg=itolapi
mock==5.2.0
packaging==24.2
pluggy==1.5.0
pytest==8.3.5
pytest-mock==3.14.0
requests==2.32.3
tomli==2.2.1
urllib3==2.3.0

name: itolapi
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - exceptiongroup==1.2.2
      - idna==3.10
      - iniconfig==2.1.0
      - mock==5.2.0
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - pytest-mock==3.14.0
      - requests==2.32.3
      - tomli==2.2.1
      - urllib3==2.3.0
prefix: /opt/conda/envs/itolapi

tests/comm.py::PullOutFilesTest::test_doesnt_modify_params tests/comm.py::PullOutFilesTest::test_pull_out_files tests/comm.py::UploadTreeTest::test_upload_tree tests/comm.py::ParseUploadTest::test_fatal tests/comm.py::ParseUploadTest::test_successful tests/comm.py::ParseUploadTest::test_successful_warnings tests/comm.py::ExportImageTest::test_export_image tests/itol.py::ItolTest::test_add_variable tests/itol.py::ItolTest::test_bad_upload tests/itol.py::ItolTest::test_checks_file_variable tests/itol.py::ItolTest::test_checks_variable_name tests/itol.py::ItolTest::test_checks_variable_value tests/itol.py::ItolTest::test_delete_variables tests/itol.py::ItolTest::test_get_itol_export tests/itol.py::ItolTest::test_good_upload tests/itol.py::ItolTest::test_initializes tests/itol.py::ItolTest::test_is_file tests/itol.py::ItolTest::test_print_variables tests/itolexport.py::ItolTest::test_add_export_param_dict tests/itolexport.py::ItolTest::test_export tests/itolexport.py::ItolTest::test_get_export_params tests/itolexport.py::ItolTest::test_set_export_param_value
itolapi/Itol.py examples/failures/fatal.py examples/example.py setup.py itolapi/Comm.py itolapi/ItolExport.py examples/failures/warnings.py tox.ini
itolapi/Itol.py examples/failures/fatal.py examples/example.py setup.py itolapi/Comm.py itolapi/ItolExport.py examples/failures/warnings.py tox.ini