diff --git a/helpers/merge_names.py b/helpers/merge_names.py
index b4628a9..a282df3 100644
--- a/helpers/merge_names.py
+++ b/helpers/merge_names.py
@@ -3,6 +3,8 @@
 import os
 import json
 
+import six
+
 
 FIXTURES = ['mongolian/fixtures/mongolian_names_list.json',
             'russian/fixtures/pagan_names_list.json',
@@ -27,8 +29,8 @@ def names_equal(name, original_name):
             if language not in original_languages:
                 continue
 
-            text = languages[language] if isinstance(languages[language], basestring) else languages[language][0]
-            original_text = original_languages[language] if isinstance(original_languages[language], basestring) else original_languages[language][0]
+            text = languages[language] if isinstance(languages[language], six.string_types) else languages[language][0]
+            original_text = original_languages[language] if isinstance(original_languages[language], six.string_types) else original_languages[language][0]
 
             if text == original_text:
                 return True
@@ -37,8 +39,8 @@ def names_equal(name, original_name):
 
 
 def merge_names(name, original_name):
-    for gender, languages in name['genders'].iteritems():
-        for language, data in languages.iteritems():
+    for gender, languages in six.iteritems(name['genders']):
+        for language, data in six.iteritems(languages):
             original_name['genders'][gender][language] = data
 
 
@@ -54,7 +56,7 @@ def pretty_dump(data):
     content = []
     content.append(u'{')
 
-    for key, value in data.iteritems():
+    for key, value in six.iteritems(data):
         if key != 'names':
             content.append(u'    "%s": %s,' % (key, json.dumps(value, ensure_ascii=False)))
 
diff --git a/pynames/exceptions.py b/pynames/exceptions.py
index 48d5de7..16bbfb2 100644
--- a/pynames/exceptions.py
+++ b/pynames/exceptions.py
@@ -1,5 +1,7 @@
 # coding: utf-8
 
+from __future__ import unicode_literals
+
 
 class PynamesError(Exception):
     MSG = None
@@ -9,7 +11,7 @@ class PynamesError(Exception):
 
 
 class NoDefaultNameValue(PynamesError):
-    MSG = u'Name: can not get default value for name with data: %(raw_data)r'
+    MSG = 'Name: can not get default value for name with data: %(raw_data)r'
 
 
 class FromListGeneratorError(PynamesError):
@@ -17,7 +19,7 @@ class FromListGeneratorError(PynamesError):
 
 
 class NoNamesLoadedFromListError(FromListGeneratorError):
-    MSG = u'no names loaded from "%(source)s"'
+    MSG = 'no names loaded from "%(source)s"'
 
 
 class FromTablesGeneratorError(PynamesError):
@@ -25,11 +27,11 @@ class FromTablesGeneratorError(PynamesError):
 
 
 class WrongTemplateStructureError(FromTablesGeneratorError):
-    MSG = u'wrong template structure - cannot choose template for genders %(genders)r with template source: "%(source)s"'
+    MSG = 'wrong template structure - cannot choose template for genders %(genders)r with template source: "%(source)s"'
 
 
 class NotEqualFormsLengths(FromTablesGeneratorError):
-    MSG = u'not equal forms lengths: [%(left)r] and [%(right)r]'
+    MSG = 'not equal forms lengths: [%(left)r] and [%(right)r]'
 
 
 class WrongCSVData(FromTablesGeneratorError):
diff --git a/pynames/from_list_generator.py b/pynames/from_list_generator.py
index 9875e38..996e214 100644
--- a/pynames/from_list_generator.py
+++ b/pynames/from_list_generator.py
@@ -1,4 +1,7 @@
 # coding: utf-8
+
+from __future__ import unicode_literals
+
 import json
 import random
 
diff --git a/pynames/from_tables_generator.py b/pynames/from_tables_generator.py
index 7b68475..8688ec8 100644
--- a/pynames/from_tables_generator.py
+++ b/pynames/from_tables_generator.py
@@ -1,11 +1,14 @@
 # coding: utf-8
 
+from __future__ import unicode_literals
+
 # python lib:
 import json
 import random
 from collections import Iterable
 
 # thirdparties:
+import six
 import unicodecsv
 
 # pynames:
@@ -39,28 +42,28 @@ class Template(object):
 
     @classmethod
     def merge_forms(cls, left, right):
-        if not isinstance(left, basestring):
-            if not isinstance(right, basestring):
+        if not isinstance(left, six.string_types):
+            if not isinstance(right, six.string_types):
                 if len(left) != len(right):
                     raise exceptions.NotEqualFormsLengths(left=left, right=right)
                 return [l+r for l, r in zip(left, right)]
             else:
                 return [l+right for l in left]
         else:
-            if not isinstance(right, basestring):
+            if not isinstance(right, six.string_types):
                 return [left+r for r in right]
             else:
                 return left + right
 
     def get_name(self, tables):
         languages = dict(
-            (lang, u'') for lang in self.languages
+            (lang, '') for lang in self.languages
         )
         for slug in self.template:
             record = random.choice(tables[slug])
             languages = {
                 lang: self.merge_forms(forms, record['languages'][lang])
-                for lang, forms in languages.iteritems()
+                for lang, forms in six.iteritems(languages)
             }
 
         genders = dict(
@@ -103,7 +106,7 @@ class FromTablesGenerator(BaseGenerator):
             raise NotImplementedError(error_msg)
 
         with file_adapter(source) as f:
-            data = json.load(f)
+            data = json.loads(f.read().decode('utf-8'))
             self.native_language = data['native_language']
             self.languages = set(data['languages'])
             self.full_forms_for_languages = set(data.get('full_forms_for_languages', set()))
@@ -153,7 +156,7 @@ class FromTablesGenerator(BaseGenerator):
         return name.get_for(gender, language)
 
     def test_names_consistency(self, test):
-        for table_name, table in self.tables.iteritems():
+        for table_name, table in six.iteritems(self.tables):
             for record in table:
                 test.assertEqual(set(record['languages'].keys()) & self.languages, self.languages)
 
diff --git a/pynames/names.py b/pynames/names.py
index 1fcf27d..d69a297 100644
--- a/pynames/names.py
+++ b/pynames/names.py
@@ -1,5 +1,9 @@
 # coding: utf-8
 
+from __future__ import unicode_literals
+
+import six
+
 from pynames.relations import GENDER, LANGUAGE
 from pynames import exceptions
 
@@ -20,7 +24,7 @@ class Name(object):
 
         forms = self.translations[gender][language]
 
-        if not isinstance(forms, basestring):
+        if not isinstance(forms, six.string_types):
             return forms[0]
 
         return forms
@@ -31,7 +35,7 @@ class Name(object):
 
         forms = self.translations[gender][language]
 
-        if not isinstance(forms, basestring):
+        if not isinstance(forms, six.string_types):
             return list(forms)
 
         return None
diff --git a/pynames/relations.py b/pynames/relations.py
index 3b2b5f3..f065488 100644
--- a/pynames/relations.py
+++ b/pynames/relations.py
@@ -1,5 +1,7 @@
 # coding: utf-8
 
+from __future__ import unicode_literals
+
 class GENDER:
     MALE = 'm'
     FEMALE = 'f'
diff --git a/pynames/utils.py b/pynames/utils.py
index b7865e8..0430487 100644
--- a/pynames/utils.py
+++ b/pynames/utils.py
@@ -1,5 +1,7 @@
 # coding: utf-8
 
+from __future__ import unicode_literals
+
 import contextlib
 import importlib
 import pkgutil
@@ -54,6 +56,6 @@ def file_adapter(file_or_path):
     if is_file(file_or_path):
         file_obj = file_or_path
     else:
-        file_obj = open(file_or_path)
+        file_obj = open(file_or_path, 'rb')
     yield file_obj
     file_obj.close()
diff --git a/setup.py b/setup.py
index a5e70ad..eb2a544 100644
--- a/setup.py
+++ b/setup.py
@@ -22,12 +22,14 @@ setuptools.setup(
 
         'Programming Language :: Python :: 2',
         'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.5',
 
         'Natural Language :: English',
         'Natural Language :: Russian'],
     keywords=['gamedev', 'game', 'game development', 'names', 'names generation'],
     packages=setuptools.find_packages(),
-    install_requires=['unicodecsv'],
+    install_requires=['six', 'unicodecsv'],
     include_package_data=True,
     test_suite = 'tests',
     )

Поддержка Python 3
diff --git a/pynames/tests/__init__.py b/pynames/tests/__init__.py
index 91bd147..e69de29 100644
--- a/pynames/tests/__init__.py
+++ b/pynames/tests/__init__.py
@@ -1,7 +0,0 @@
-# coding: utf-8
-
-from pynames.tests.test_name import *
-from pynames.tests.test_from_list_generator import *
-from pynames.tests.test_from_tables_generator import *
-from pynames.tests.test_generators import *
-from pynames.tests.test_utils import *
diff --git a/pynames/tests/test_from_list_generator.py b/pynames/tests/test_from_list_generator.py
index 35b7e10..3ffc28a 100644
--- a/pynames/tests/test_from_list_generator.py
+++ b/pynames/tests/test_from_list_generator.py
@@ -1,8 +1,12 @@
 # coding: utf-8
 
+from __future__ import unicode_literals
+
 import os
 import unittest
 
+from six.moves import xrange
+
 from pynames.relations import GENDER, LANGUAGE
 from pynames.from_list_generator import FromListGenerator
 
diff --git a/pynames/tests/test_from_tables_generator.py b/pynames/tests/test_from_tables_generator.py
index 52b07e7..b1aef3f 100644
--- a/pynames/tests/test_from_tables_generator.py
+++ b/pynames/tests/test_from_tables_generator.py
@@ -1,8 +1,13 @@
 # coding: utf-8
 
+from __future__ import unicode_literals
+
 import os
 import unittest
 
+import six
+from six.moves import xrange
+
 from pynames.relations import GENDER, LANGUAGE
 from pynames.from_tables_generator import FromTablesGenerator, FromCSVTablesGenerator
 
@@ -116,13 +121,9 @@ class TestFromCSVTablesGenerator(unittest.TestCase):
         csv_generator = self.TestCSVGenerator()
 
         for attr_name in ['native_language', 'languages', 'templates', 'tables']:
-            try:
-                json_attr = getattr(json_generator, attr_name)
-                csv_attr = getattr(csv_generator, attr_name)
-                if isinstance(json_attr, list):
-                    self.assertItemsEqual(csv_attr, json_attr)
-                else:
-                    self.assertEqual(csv_attr, json_attr)
-            except Exception:
-                from nose.tools import set_trace; set_trace()
-                raise
+            json_attr = getattr(json_generator, attr_name)
+            csv_attr = getattr(csv_generator, attr_name)
+            if isinstance(json_attr, list):
+                six.assertCountEqual(self, csv_attr, json_attr)
+            else:
+                self.assertEqual(csv_attr, json_attr)
diff --git a/pynames/tests/test_name.py b/pynames/tests/test_name.py
index 19182c0..0403736 100644
--- a/pynames/tests/test_name.py
+++ b/pynames/tests/test_name.py
@@ -1,5 +1,8 @@
 # coding: utf-8
 
+from __future__ import unicode_literals
+
+import six
 import unittest
 
 from pynames.relations import GENDER, LANGUAGE
@@ -10,7 +13,7 @@ class TestName(unittest.TestCase):
 
     def test_base(self):
         name = Name('ru', {'genders': {'m': {'ru': 'ru_name'}}})
-        self.assertEqual(unicode(name), 'ru_name')
+        self.assertEqual(six.text_type(name), 'ru_name')
         self.assertEqual(name.get_for(GENDER.MALE, LANGUAGE.RU), 'ru_name')
         self.assertEqual(name.get_for(GENDER.MALE), 'ru_name')
         self.assertEqual(name.get_forms_for(GENDER.MALE), None)
@@ -18,7 +21,7 @@ class TestName(unittest.TestCase):
     def test_genders(self):
         name = Name('ru', {'genders': {'m': {'ru': 'ru_m_name'},
                                        'f': {'ru': 'ru_f_name'}}})
-        self.assertEqual(unicode(name), 'ru_m_name')
+        self.assertEqual(six.text_type(name), 'ru_m_name')
         self.assertEqual(name.get_for(GENDER.MALE, LANGUAGE.RU), 'ru_m_name')
         self.assertEqual(name.get_for(GENDER.FEMALE, LANGUAGE.RU), 'ru_f_name')
 
@@ -27,7 +30,7 @@ class TestName(unittest.TestCase):
                                              'en': 'en_m_name'},
                                        'f': {'ru': 'ru_f_name',
                                              'en': 'en_f_name'}}})
-        self.assertEqual(unicode(name), 'ru_m_name')
+        self.assertEqual(six.text_type(name), 'ru_m_name')
         self.assertEqual(name.get_for(GENDER.MALE, LANGUAGE.RU), 'ru_m_name')
         self.assertEqual(name.get_for(GENDER.FEMALE, LANGUAGE.RU), 'ru_f_name')
         self.assertEqual(name.get_for(GENDER.MALE, LANGUAGE.EN), 'en_m_name')
diff --git a/pynames/tests/test_utils.py b/pynames/tests/test_utils.py
index acab40a..a62aa06 100644
--- a/pynames/tests/test_utils.py
+++ b/pynames/tests/test_utils.py
@@ -1,5 +1,7 @@
 # coding: utf-8
 
+from __future__ import unicode_literals
+
 import os
 import tempfile
 import unittest

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest six
apt-get update apt-get install -y gcc
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
exceptiongroup==1.2.2
iniconfig==2.1.0
packaging==24.2
pluggy==1.5.0
-e git+https://github.com/Tiendil/pynames.git@bc496f64be0da44db0882b74b54125ce2e5e556b#egg=Pynames
pytest==8.3.5
six==1.17.0
tomli==2.2.1
unicodecsv==0.14.1

name: pynames
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:
      - exceptiongroup==1.2.2
      - iniconfig==2.1.0
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - six==1.17.0
      - tomli==2.2.1
      - unicodecsv==0.14.1
prefix: /opt/conda/envs/pynames

pynames/tests/test_from_tables_generator.py::TestFromTablesGenerator::test_base pynames/tests/test_from_tables_generator.py::TestFromTablesGenerator::test_get_name__with_forms pynames/tests/test_from_tables_generator.py::TestFromTablesGenerator::test_get_name_simple pynames/tests/test_from_tables_generator.py::TestFromCSVTablesGenerator::test_init_state_equal pynames/tests/test_name.py::TestName::test_base pynames/tests/test_name.py::TestName::test_forms pynames/tests/test_name.py::TestName::test_genders pynames/tests/test_name.py::TestName::test_languages
pynames/tests/test_from_list_generator.py::TestFromListGenerator::test_base pynames/tests/test_from_list_generator.py::TestFromListGenerator::test_get_name__simple pynames/tests/test_from_list_generator.py::TestFromListGenerator::test_get_name__with_forms pynames/tests/test_from_list_generator.py::TestFromListGenerator::test_male_female_selection
pynames/tests/test_from_list_generator.py::TestFromListGenerator::test_not_derived pynames/tests/test_from_list_generator.py::TestFromListGenerator::test_wrong_path pynames/tests/test_from_tables_generator.py::TestFromTablesGenerator::test_male_female_selection pynames/tests/test_from_tables_generator.py::TestFromTablesGenerator::test_not_derived pynames/tests/test_from_tables_generator.py::TestFromTablesGenerator::test_wrong_path pynames/tests/test_utils.py::TestName::test_file_adapter pynames/tests/test_utils.py::TestName::test_is_file pynames/tests/test_utils.py::TestName::test_is_file_on_django_files
pynames/utils.py pynames/exceptions.py setup.py pynames/from_tables_generator.py pynames/relations.py pynames/from_list_generator.py pynames/names.py helpers/merge_names.py
pynames/utils.py pynames/exceptions.py setup.py pynames/from_tables_generator.py pynames/relations.py pynames/from_list_generator.py pynames/names.py helpers/merge_names.py