Bug 1425399 - Added python 3 support to mozprofile. r=wlach
☠☠ backed out by a2a8d7ccac40 ☠ ☠
authorVedant Chakravadhanula <vedantc98@gmail.com>
Wed, 20 Dec 2017 14:49:20 +0530
changeset 451440 2bc8e60fb35622ce330525e15f7170c52317c878
parent 451439 38a8726fbde26796847294962cc9edd369091fee
child 451441 570db0492055ac9bd86af8c13cbdb16148578ff9
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswlach
bugs1425399
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1425399 - Added python 3 support to mozprofile. r=wlach MozReview-Commit-ID: 9iAFA3JYagG
testing/mozbase/mozprofile/mozprofile/addons.py
testing/mozbase/mozprofile/mozprofile/permissions.py
testing/mozbase/mozprofile/mozprofile/prefs.py
testing/mozbase/mozprofile/setup.py
testing/mozbase/mozprofile/tests/server_locations.py
testing/mozbase/mozprofile/tests/test_addons.py
--- a/testing/mozbase/mozprofile/mozprofile/addons.py
+++ b/testing/mozbase/mozprofile/mozprofile/addons.py
@@ -4,22 +4,22 @@
 
 from __future__ import absolute_import
 
 import json
 import os
 import sys
 import shutil
 import tempfile
-import urllib2
 import zipfile
 import hashlib
 from xml.dom import minidom
 
 from six import reraise
+from six.moves.urllib import request
 
 import mozfile
 from mozlog.unstructured import getLogger
 
 # Needed for the AMO's rest API -
 # https://developer.mozilla.org/en/addons.mozilla.org_%28AMO%29_API_Developers%27_Guide/The_generic_AMO_API
 AMO_API_VERSION = "1.5"
 _SALT = os.urandom(32).encode('hex')
@@ -108,17 +108,17 @@ class AddonManager(object):
     def download(self, url, target_folder=None):
         """
         Downloads an add-on from the specified URL to the target folder
 
         :param url: URL of the add-on (XPI file)
         :param target_folder: Folder to store the XPI file in
 
         """
-        response = urllib2.urlopen(url)
+        response = request.urlopen(url)
         fd, path = tempfile.mkstemp(suffix='.xpi')
         os.write(fd, response.read())
         os.close(fd)
 
         if not self.is_addon(path):
             mozfile.remove(path)
             raise AddonFormatError('Not a valid add-on: %s' % url)
 
@@ -172,24 +172,24 @@ class AddonManager(object):
         Installs all types of addons
 
         :param addons: a list of addon paths to install
         :param manifest: a list of addon manifests to install
         """
 
         # install addon paths
         if addons:
-            if isinstance(addons, basestring):
+            if isinstance(addons, str):
                 addons = [addons]
             for addon in set(addons):
                 self.install_from_path(addon)
 
         # install addon manifests
         if manifests:
-            if isinstance(manifests, basestring):
+            if isinstance(manifests, str):
                 manifests = [manifests]
             for manifest in manifests:
                 self.install_from_manifest(manifest)
 
     def install_from_manifest(self, filepath):
         """
         Installs addons from a manifest
         :param filepath: path to the manifest of addons to install
@@ -230,17 +230,17 @@ class AddonManager(object):
     def get_amo_install_path(self, query):
         """
         Get the addon xpi install path for the specified AMO query.
 
         :param query: query-documentation_
 
         .. _query-documentation: https://developer.mozilla.org/en/addons.mozilla.org_%28AMO%29_API_Developers%27_Guide/The_generic_AMO_API # noqa
         """
-        response = urllib2.urlopen(query)
+        response = request.urlopen(query)
         dom = minidom.parseString(response.read())
         for node in dom.getElementsByTagName('install')[0].childNodes:
             if node.nodeType == node.TEXT_NODE:
                 return node.data
 
     @classmethod
     def _gen_iid(cls, addon_path):
         hash = hashlib.sha1(_SALT)
@@ -348,17 +348,17 @@ class AddonManager(object):
                     # Remove the namespace prefix from the tag for comparison
                     entry = node.nodeName.replace(em, "")
                     if entry in details.keys():
                         details.update({entry: get_text(node)})
             except Exception as e:
                 reraise(AddonFormatError(str(e)), None, sys.exc_info()[2])
 
         # turn unpack into a true/false value
-        if isinstance(details['unpack'], basestring):
+        if isinstance(details['unpack'], str):
             details['unpack'] = details['unpack'].lower() == 'true'
 
         # If no ID is set, the add-on is invalid
         if details.get('id') is None and not is_webext:
             raise AddonFormatError('Add-on id could not be found.')
 
         return details
 
--- a/testing/mozbase/mozprofile/mozprofile/permissions.py
+++ b/testing/mozbase/mozprofile/mozprofile/permissions.py
@@ -7,17 +7,18 @@
 add permissions to the profile
 """
 
 from __future__ import absolute_import
 
 import codecs
 import os
 import sqlite3
-import urlparse
+
+from six.moves.urllib import parse
 
 __all__ = ['MissingPrimaryLocationError', 'MultiplePrimaryLocationsError',
            'DEFAULT_PORTS', 'DuplicateLocationError', 'BadPortLocationError',
            'LocationsSyntaxError', 'Location', 'ServerLocations',
            'Permissions']
 
 # http://hg.mozilla.org/mozilla-central/file/b871dfb2186f/build/automation.py.in#l28
 DEFAULT_PORTS = {'http': '8888',
@@ -134,17 +135,17 @@ class ServerLocations(object):
                 raise MultiplePrimaryLocationsError()
             self.hasPrimary = True
 
         self._locations.append(location)
         if self.add_callback and not suppress_callback:
             self.add_callback([location])
 
     def add_host(self, host, port='80', scheme='http', options='privileged'):
-        if isinstance(options, basestring):
+        if isinstance(options, str):
             options = options.split(',')
         self.add(Location(scheme, host, port, options))
 
     def read(self, filename, check_for_primary=True):
         """
         Reads the file and adds all valid locations to the ``self._locations`` array.
 
         :param filename: in the format of server-locations.txt_
@@ -177,17 +178,17 @@ class ServerLocations(object):
                 options = options.split(',')
             except ValueError:
                 server = line
                 options = []
 
             # parse the server url
             if '://' not in server:
                 server = 'http://' + server
-            scheme, netloc, path, query, fragment = urlparse.urlsplit(server)
+            scheme, netloc, path, query, fragment = parse.urlsplit(server)
             # get the host and port
             try:
                 host, port = netloc.rsplit(':', 1)
             except ValueError:
                 host = netloc
                 port = DEFAULT_PORTS.get(scheme, '80')
 
             try:
--- a/testing/mozbase/mozprofile/mozprofile/prefs.py
+++ b/testing/mozbase/mozprofile/mozprofile/prefs.py
@@ -6,18 +6,19 @@
 user preferences
 """
 from __future__ import absolute_import, print_function
 
 import json
 import mozfile
 import os
 import tokenize
-from ConfigParser import SafeConfigParser as ConfigParser
-from StringIO import StringIO
+
+from six.moves.configparser import SafeConfigParser as ConfigParser
+from six import StringIO
 
 __all__ = ('PreferencesReadError', 'Preferences')
 
 
 class PreferencesReadError(Exception):
     """read error for prefrences files"""
 
 
@@ -59,17 +60,17 @@ class Preferences(object):
         what type the preference value is, as natively it is a string
 
         - integers will get cast to integers
         - true/false will get cast to True/False
         - anything enclosed in single quotes will be treated as a string
           with the ''s removed from both sides
         """
 
-        if not isinstance(value, basestring):
+        if not isinstance(value, str):
             return value  # no op
         quote = "'"
         if value == 'true':
             return True
         if value == 'false':
             return False
         try:
             return int(value)
@@ -141,17 +142,17 @@ class Preferences(object):
         if isinstance(prefs, list):
             if [i for i in prefs if type(i) != list or len(i) != 2]:
                 raise PreferencesReadError("Malformed preferences: %s" % path)
             values = [i[1] for i in prefs]
         elif isinstance(prefs, dict):
             values = prefs.values()
         else:
             raise PreferencesReadError("Malformed preferences: %s" % path)
-        types = (bool, basestring, int)
+        types = (bool, str, int)
         if [i for i in values if not [isinstance(i, j) for j in types]]:
             raise PreferencesReadError("Only bool, string, and int values allowed")
         return prefs
 
     @classmethod
     def read_prefs(cls, path, pref_setter='user_pref', interpolation=None):
         """
         Read preferences from (e.g.) prefs.js
@@ -181,42 +182,42 @@ class Preferences(object):
             if token[0] == tokenize.COMMENT:
                 continue
             processed_tokens.append(token[:2])  # [:2] gets around http://bugs.python.org/issue9974
         string = tokenize.untokenize(processed_tokens)
 
         retval = []
 
         def pref(a, b):
-            if interpolation and isinstance(b, basestring):
+            if interpolation and isinstance(b, str):
                 b = b.format(**interpolation)
             retval.append((a, b))
         lines = [i.strip().rstrip(';') for i in string.split('\n') if i.strip()]
 
         _globals = {'retval': retval, 'true': True, 'false': False}
         _globals[pref_setter] = pref
         for line in lines:
             try:
                 eval(line, _globals, {})
             except SyntaxError:
                 print(line)
                 raise
 
         # de-magic the marker
         for index, (key, value) in enumerate(retval):
-            if isinstance(value, basestring) and marker in value:
+            if isinstance(value, str) and marker in value:
                 retval[index] = (key, value.replace(marker, '//'))
 
         return retval
 
     @classmethod
     def write(cls, _file, prefs, pref_string='user_pref(%s, %s);'):
         """write preferences to a file"""
 
-        if isinstance(_file, basestring):
+        if isinstance(_file, str):
             f = file(_file, 'a')
         else:
             f = _file
 
         if isinstance(prefs, dict):
             # order doesn't matter
             prefs = prefs.items()
 
@@ -224,10 +225,10 @@ class Preferences(object):
         _prefs = [(json.dumps(k), json.dumps(v))
                   for k, v in prefs]
 
         # write the preferences
         for _pref in _prefs:
             print(pref_string % _pref, file=f)
 
         # close the file if opened internally
-        if isinstance(_file, basestring):
+        if isinstance(_file, str):
             f.close()
--- a/testing/mozbase/mozprofile/setup.py
+++ b/testing/mozbase/mozprofile/setup.py
@@ -1,38 +1,35 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import
 
-import sys
 from setuptools import setup
 
 PACKAGE_NAME = 'mozprofile'
 PACKAGE_VERSION = '0.29'
 
-# we only support python 2 right now
-assert sys.version_info[0] == 2
-
 deps = ['mozfile >= 1.0',
         'mozlog >= 3.0',
         'six >= 1.10.0'
         ]
 
 setup(name=PACKAGE_NAME,
       version=PACKAGE_VERSION,
       description="Library to create and modify Mozilla application profiles",
       long_description="see https://firefox-source-docs.mozilla.org/mozbase/index.html",
       classifiers=['Environment :: Console',
                    'Intended Audience :: Developers',
                    'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)',
                    'Natural Language :: English',
                    'Operating System :: OS Independent',
-                   'Programming Language :: Python',
+                   'Programming Language :: Python :: 2.7',
+                   'Programming Language :: Python :: 3',
                    'Topic :: Software Development :: Libraries :: Python Modules',
                    ],
       keywords='mozilla',
       author='Mozilla Automation and Tools team',
       author_email='tools@lists.mozilla.org',
       url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase',
       license='MPL 2.0',
       packages=['mozprofile'],
--- a/testing/mozbase/mozprofile/tests/server_locations.py
+++ b/testing/mozbase/mozprofile/tests/server_locations.py
@@ -60,31 +60,31 @@ http://example.org:80           privileg
         f = self.create_temp_file(self.locations)
 
         # read the locations
         locations = ServerLocations(f.name)
 
         # ensure that they're what we expect
         self.assertEqual(len(locations), 6)
         i = iter(locations)
-        self.compare_location(i.next(), 'http', 'mochi.test', '8888',
+        self.compare_location(next(i), 'http', 'mochi.test', '8888',
                               ['primary', 'privileged'])
-        self.compare_location(i.next(), 'http', '127.0.0.1', '80',
+        self.compare_location(next(i), 'http', '127.0.0.1', '80',
                               ['privileged'])
-        self.compare_location(i.next(), 'http', '127.0.0.1', '8888',
+        self.compare_location(next(i), 'http', '127.0.0.1', '8888',
                               ['privileged'])
-        self.compare_location(i.next(), 'https', 'test', '80', ['privileged'])
-        self.compare_location(i.next(), 'http', 'example.org', '80',
+        self.compare_location(next(i), 'https', 'test', '80', ['privileged'])
+        self.compare_location(next(i), 'http', 'example.org', '80',
                               ['privileged'])
-        self.compare_location(i.next(), 'http', 'test1.example.org', '8888',
+        self.compare_location(next(i), 'http', 'test1.example.org', '8888',
                               ['privileged'])
 
         locations.add_host('mozilla.org')
         self.assertEqual(len(locations), 7)
-        self.compare_location(i.next(), 'http', 'mozilla.org', '80',
+        self.compare_location(next(i), 'http', 'mozilla.org', '80',
                               ['privileged'])
 
         # test some errors
         self.assertRaises(MultiplePrimaryLocationsError, locations.add_host,
                           'primary.test', options='primary')
 
         # We no longer throw these DuplicateLocation Error
         try:
--- a/testing/mozbase/mozprofile/tests/test_addons.py
+++ b/testing/mozbase/mozprofile/tests/test_addons.py
@@ -5,28 +5,28 @@
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import
 
 import os
 import shutil
 import tempfile
 import unittest
-import urllib2
 import zipfile
 
 import mozunit
 
 from manifestparser import ManifestParser
 import mozfile
 import mozhttpd
 import mozlog.unstructured as mozlog
 import mozprofile
 
 from addon_stubs import generate_addon, generate_manifest
+from six.moves.urllib import error
 
 
 here = os.path.dirname(os.path.abspath(__file__))
 
 
 class TestAddonsManager(unittest.TestCase):
     """ Class to test mozprofile.addons.AddonManager """
 
@@ -99,17 +99,17 @@ class TestAddonsManager(unittest.TestCas
         # Download an invalid add-on to a special folder
         addon = server.get_url() + 'invalid.xpi'
         self.assertRaises(mozprofile.addons.AddonFormatError,
                           self.am.download, addon, self.tmpdir)
         self.assertEqual(os.listdir(self.tmpdir), [])
 
         # Download from an invalid URL
         addon = server.get_url() + 'not_existent.xpi'
-        self.assertRaises(urllib2.HTTPError,
+        self.assertRaises(error.HTTPError,
                           self.am.download, addon, self.tmpdir)
         self.assertEqual(os.listdir(self.tmpdir), [])
 
         # Download from an invalid URL
         addon = 'not_existent.xpi'
         self.assertRaises(ValueError,
                           self.am.download, addon, self.tmpdir)
         self.assertEqual(os.listdir(self.tmpdir), [])
@@ -161,17 +161,17 @@ class TestAddonsManager(unittest.TestCas
 
         # Generate installer stubs and install them
         for ext in ['test-addon-1@mozilla.org', 'test-addon-2@mozilla.org']:
             temp_addon = generate_addon(ext, path=self.tmpdir)
             addons_to_install.append(self.am.addon_details(temp_addon)['id'])
             self.am.install_from_path(temp_addon)
 
         # Generate a list of addons installed in the profile
-        addons_installed = [unicode(x[:-len('.xpi')]) for x in os.listdir(os.path.join(
+        addons_installed = [str(x[:-len('.xpi')]) for x in os.listdir(os.path.join(
                             self.profile.profile, 'extensions', 'staged'))]
         self.assertEqual(addons_to_install.sort(), addons_installed.sort())
 
     def test_install_from_path_folder(self):
         # Generate installer stubs for all possible types of addons
         addons = []
         addons.append(generate_addon('test-addon-1@mozilla.org',
                                      path=self.tmpdir))
@@ -326,17 +326,17 @@ class TestAddonsManager(unittest.TestCas
         m.read(temp_manifest)
         addons = m.get()
 
         # Obtain details of addons to install from the manifest
         addons_to_install = [self.am.addon_details(x['path']).get('id') for x in addons]
 
         self.am.install_from_manifest(temp_manifest)
         # Generate a list of addons installed in the profile
-        addons_installed = [unicode(x[:-len('.xpi')]) for x in os.listdir(os.path.join(
+        addons_installed = [str(x[:-len('.xpi')]) for x in os.listdir(os.path.join(
                             self.profile.profile, 'extensions', 'staged'))]
         self.assertEqual(addons_installed.sort(), addons_to_install.sort())
 
         # Cleanup the temporary addon and manifest directories
         mozfile.rmtree(os.path.dirname(temp_manifest))
 
     def test_addon_details(self):
         # Generate installer stubs for a valid and invalid add-on manifest
@@ -366,27 +366,27 @@ class TestAddonsManager(unittest.TestCas
                           self.am.addon_details, addon_path)
 
     @unittest.skip("Bug 900154")
     def test_clean_addons(self):
         addon_one = generate_addon('test-addon-1@mozilla.org')
         addon_two = generate_addon('test-addon-2@mozilla.org')
 
         self.am.install_addons(addon_one)
-        installed_addons = [unicode(x[:-len('.xpi')]) for x in os.listdir(os.path.join(
+        installed_addons = [str(x[:-len('.xpi')]) for x in os.listdir(os.path.join(
                             self.profile.profile, 'extensions', 'staged'))]
 
         # Create a new profile based on an existing profile
         # Install an extra addon in the new profile
         # Cleanup addons
         duplicate_profile = mozprofile.profile.Profile(profile=self.profile.profile,
                                                        addons=addon_two)
         duplicate_profile.addon_manager.clean()
 
-        addons_after_cleanup = [unicode(x[:-len('.xpi')]) for x in os.listdir(os.path.join(
+        addons_after_cleanup = [str(x[:-len('.xpi')]) for x in os.listdir(os.path.join(
                                 duplicate_profile.profile, 'extensions', 'staged'))]
         # New addons installed should be removed by clean_addons()
         self.assertEqual(installed_addons, addons_after_cleanup)
 
     def test_noclean(self):
         """test `restore=True/False` functionality"""
 
         server = mozhttpd.MozHttpd(docroot=os.path.join(here, 'addons'))