Patchwork [5,of,8,v2] python3: handle urllib/urllib2 refactoring

login
register
mail settings
Submitter timeless@mozdev.org
Date March 30, 2016, 4:33 a.m.
Message ID <1f99c982266f5a3f2d78.1459312400@waste.org>
Download mbox | patch
Permalink /patch/14167/
State Accepted
Headers show

Comments

timeless@mozdev.org - March 30, 2016, 4:33 a.m.
# HG changeset patch
# User timeless <timeless@mozdev.org>
# Date 1459202612 0
#      Mon Mar 28 22:03:32 2016 +0000
# Node ID 1f99c982266f5a3f2d78734ccab2c7ac993a3e6d
# Parent  b2205d49ee6f9a8c6ce8316bce8fd25e5d593775
python3: handle urllib/urllib2 refactoring

Patch

diff --git a/hgext/acl.py b/hgext/acl.py
--- a/hgext/acl.py
+++ b/hgext/acl.py
@@ -194,7 +194,13 @@ 
 from __future__ import absolute_import
 
 import getpass
-import urllib
+
+try:
+    import urllib
+    unquote = urllib.unquote
+except AttributeError:
+    import urllib.request
+    unquote = urllib.request.unquote
 
 from mercurial.i18n import _
 from mercurial import (
@@ -287,7 +293,7 @@ 
     if source == 'serve' and 'url' in kwargs:
         url = kwargs['url'].split(':')
         if url[0] == 'remote' and url[1].startswith('http'):
-            user = urllib.unquote(url[3])
+            user = unquote(url[3])
 
     if user is None:
         user = getpass.getuser()
diff --git a/hgext/convert/subversion.py b/hgext/convert/subversion.py
--- a/hgext/convert/subversion.py
+++ b/hgext/convert/subversion.py
@@ -9,10 +9,25 @@ 
 import re
 import sys
 import tempfile
-import urllib
-import urllib2
 import xml.dom.minidom
 
+try:
+    import urllib2
+    import urllib
+    httperror = urllib2.HTTPError
+    build_opener = urllib2.build_opener
+    quote = urllib.quote
+    unquote = urllib.unquote
+    url2pathname = urllib.url2pathname
+except ImportError:
+    import urllib.request
+    import urllib.error
+    httperror = urllib.error.HTTPError
+    build_opener = urllib.request.build_opener
+    quote = urllib.request.quote
+    unquote = urllib.request.unquote
+    url2pathname = urllib.request.url2pathname
+
 from mercurial import (
     encoding,
     error,
@@ -87,7 +102,7 @@ 
         mod = '/' + parts[1]
     return uuid, mod, revnum
 
-def quote(s):
+def svnquote(s):
     # As of svn 1.7, many svn calls expect "canonical" paths. In
     # theory, we should call svn.core.*canonicalize() on all paths
     # before passing them to the API.  Instead, we assume the base url
@@ -95,7 +110,7 @@ 
     # so we can extend it safely with new components. The "safe"
     # characters were taken from the "svn_uri__char_validity" table in
     # libsvn_subr/path.c.
-    return urllib.quote(s, "!$&'()*+,-./:=@_~")
+    return quote(s, "!$&'()*+,-./:=@_~")
 
 def geturl(path):
     try:
@@ -110,7 +125,7 @@ 
         # Module URL is later compared with the repository URL returned
         # by svn API, which is UTF-8.
         path = encoding.tolocal(path)
-        path = 'file://%s' % quote(path)
+        path = 'file://%s' % svnquote(path)
     return svn.core.svn_path_canonicalize(path)
 
 def optrev(number):
@@ -237,7 +252,7 @@ 
         opener = urllib2.build_opener()
         rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path))
         data = rsp.read()
-    except urllib2.HTTPError as inst:
+    except httperror as inst:
         if inst.code != 404:
             # Except for 404 we cannot know for sure this is not an svn repo
             ui.warn(_('svn: cannot probe remote repository, assume it could '
@@ -261,7 +276,7 @@ 
             if (os.name == 'nt' and path[:1] == '/' and path[1:2].isalpha()
                 and path[2:6].lower() == '%3a/'):
                 path = path[:2] + ':/' + path[6:]
-            path = urllib.url2pathname(path)
+            path = url2pathname(path)
     except ValueError:
         proto = 'file'
         path = os.path.abspath(url)
@@ -331,7 +346,7 @@ 
             self.baseurl = svn.ra.get_repos_root(self.ra)
             # Module is either empty or a repository path starting with
             # a slash and not ending with a slash.
-            self.module = urllib.unquote(self.url[len(self.baseurl):])
+            self.module = unquote(self.url[len(self.baseurl):])
             self.prevmodule = None
             self.rootmodule = self.module
             self.commits = {}
@@ -395,7 +410,7 @@ 
 
     def exists(self, path, optrev):
         try:
-            svn.client.ls(self.url.rstrip('/') + '/' + quote(path),
+            svn.client.ls(self.url.rstrip('/') + '/' + svnquote(path),
                                  optrev, False, self.ctx)
             return True
         except svn.core.SubversionException:
@@ -447,7 +462,7 @@ 
         # Check if branches bring a few more heads to the list
         if branches:
             rpath = self.url.strip('/')
-            branchnames = svn.client.ls(rpath + '/' + quote(branches),
+            branchnames = svn.client.ls(rpath + '/' + svnquote(branches),
                                         rev, False, self.ctx)
             for branch in sorted(branchnames):
                 module = '%s/%s/%s' % (oldmodule, branches, branch)
@@ -481,7 +496,7 @@ 
         if full or not parents:
             # Perform a full checkout on roots
             uuid, module, revnum = revsplit(rev)
-            entries = svn.client.ls(self.baseurl + quote(module),
+            entries = svn.client.ls(self.baseurl + svnquote(module),
                                     optrev(revnum), True, self.ctx)
             files = [n for n, e in entries.iteritems()
                      if e.kind == svn.core.svn_node_file]
@@ -729,7 +744,7 @@ 
         """Reparent the svn transport and return the previous parent."""
         if self.prevmodule == module:
             return module
-        svnurl = self.baseurl + quote(module)
+        svnurl = self.baseurl + svnquote(module)
         prevmodule = self.prevmodule
         if prevmodule is None:
             prevmodule = ''
@@ -1012,7 +1027,7 @@ 
         """Enumerate all files in path at revnum, recursively."""
         path = path.strip('/')
         pool = svn.core.Pool()
-        rpath = '/'.join([self.baseurl, quote(path)]).strip('/')
+        rpath = '/'.join([self.baseurl, svnquote(path)]).strip('/')
         entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool)
         if path:
             path += '/'
diff --git a/hgext/largefiles/proto.py b/hgext/largefiles/proto.py
--- a/hgext/largefiles/proto.py
+++ b/hgext/largefiles/proto.py
@@ -4,9 +4,15 @@ 
 # GNU General Public License version 2 or any later version.
 
 import os
-import urllib2
 import re
 
+try:
+    import urllib2
+    httperror = urllib2.HTTPError
+except ImportError:
+    import urllib.error
+    httperror = urllib.error.HTTPError
+
 from mercurial import error, httppeer, util, wireproto
 from mercurial.i18n import _
 
@@ -140,7 +146,7 @@ 
             yield result, f
             try:
                 yield int(f.value)
-            except (ValueError, urllib2.HTTPError):
+            except (ValueError, httperror):
                 # If the server returns anything but an integer followed by a
                 # newline, newline, it's not speaking our language; if we get
                 # an HTTP error, we can't be sure the largefile is present;
diff --git a/hgext/largefiles/remotestore.py b/hgext/largefiles/remotestore.py
--- a/hgext/largefiles/remotestore.py
+++ b/hgext/largefiles/remotestore.py
@@ -6,7 +6,14 @@ 
 
 '''remote largefile store; the base class for wirestore'''
 
-import urllib2
+try:
+    import urllib2
+    httperror = urllib2.HTTPError
+    urlerror = urllib2.URLError
+except ImportError:
+    import urllib.error
+    httperror = urllib.error.HTTPError
+    urlerror = urllib.error.URLError
 
 from mercurial import util, wireproto, error
 from mercurial.i18n import _
@@ -49,11 +56,11 @@ 
     def _getfile(self, tmpfile, filename, hash):
         try:
             chunks = self._get(hash)
-        except urllib2.HTTPError as e:
+        except httperror as e:
             # 401s get converted to error.Aborts; everything else is fine being
             # turned into a StoreError
             raise basestore.StoreError(filename, hash, self.url, str(e))
-        except urllib2.URLError as e:
+        except urlerror as e:
             # This usually indicates a connection problem, so don't
             # keep trying with the other files... they will probably
             # all fail too.
diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -152,7 +152,15 @@ 
 import string
 import struct
 import sys
-import urllib
+
+try:
+    import urllib
+    quote = urllib.quote
+    unquote = urllib.unquote
+except AttributeError:
+    import urllib.request
+    quote = urllib.request.quote
+    unquote = urllib.request.unquote
 
 from .i18n import _
 from . import (
@@ -467,8 +475,8 @@ 
     chunks = []
     for ca in sorted(caps):
         vals = caps[ca]
-        ca = urllib.quote(ca)
-        vals = [urllib.quote(v) for v in vals]
+        ca = quote(ca)
+        vals = [quote(v) for v in vals]
         if vals:
             ca = "%s=%s" % (ca, ','.join(vals))
         chunks.append(ca)
@@ -570,9 +578,9 @@ 
         """return a encoded version of all stream parameters"""
         blocks = []
         for par, value in self._params:
-            par = urllib.quote(par)
+            par = quote(par)
             if value is not None:
-                value = urllib.quote(value)
+                value = quote(value)
                 par = '%s=%s' % (par, value)
             blocks.append(par)
         return ' '.join(blocks)
diff --git a/mercurial/byterange.py b/mercurial/byterange.py
--- a/mercurial/byterange.py
+++ b/mercurial/byterange.py
@@ -26,22 +26,45 @@ 
 import re
 import socket
 import stat
-import urllib
-import urllib2
 
-addclosehook = urllib.addclosehook
-addinfourl = urllib.addinfourl
-splitattr = urllib.splitattr
-splitpasswd = urllib.splitpasswd
-splitport = urllib.splitport
-splituser = urllib.splituser
-unquote = urllib.unquote
+try:
+    import urllib2
+    import urllib
+    basehandler = urllib2.BaseHandler
+    filehandler = urllib2.FileHandler
+    ftphandler = urllib2.FTPHandler
+    ftpwrapper = urllib.ftpwrapper
+    urlerror = urllib2.URLError
+    addclosehook = urllib.addclosehook
+    addinfourl = urllib.addinfourl
+    splitattr = urllib.splitattr
+    splitpasswd = urllib.splitpasswd
+    splitport = urllib.splitport
+    splituser = urllib.splituser
+    unquote = urllib.unquote
+    url2pathname = urllib.url2pathname
+except ImportError:
+    import urllib.request
+    import urllib.error
+    basehandler = urllib.request.BaseHandler
+    filehandler = urllib.request.FileHandler
+    ftphandler = urllib.request.FTPHandler
+    ftpwrapper = urllib.request.ftpwrapper
+    urlerror = urllib.error.URLError
+    addclosehook = urllib.request.addclosehook
+    addinfourl = urllib.request.addinfourl
+    splitattr = urllib.request.splitattr
+    splitpasswd = urllib.request.splitpasswd
+    splitport = urllib.request.splitport
+    splituser = urllib.request.splituser
+    unquote = urllib.request.unquote
+    url2pathname = urllib.request.url2pathname
 
 class RangeError(IOError):
     """Error raised when an unsatisfiable range is requested."""
     pass
 
-class HTTPRangeHandler(urllib2.BaseHandler):
+class HTTPRangeHandler(basehandler):
     """Handler that enables HTTP Range headers.
 
     This was extremely simple. The Range header is a HTTP feature to
@@ -67,7 +90,7 @@ 
 
     def http_error_206(self, req, fp, code, msg, hdrs):
         # 206 Partial Content Response
-        r = urllib.addinfourl(fp, hdrs, req.get_full_url())
+        r = addinfourl(fp, hdrs, req.get_full_url())
         r.code = code
         r.msg = msg
         return r
@@ -204,7 +227,7 @@ 
                 raise RangeError('Requested Range Not Satisfiable')
             pos += bufsize
 
-class FileRangeHandler(urllib2.FileHandler):
+class FileRangeHandler(filehandler):
     """FileHandler subclass that adds Range support.
     This class handles Range headers exactly like an HTTP
     server would.
@@ -212,15 +235,15 @@ 
     def open_local_file(self, req):
         host = req.get_host()
         file = req.get_selector()
-        localfile = urllib.url2pathname(file)
+        localfile = url2pathname(file)
         stats = os.stat(localfile)
         size = stats[stat.ST_SIZE]
         modified = email.Utils.formatdate(stats[stat.ST_MTIME])
         mtype = mimetypes.guess_type(file)[0]
         if host:
-            host, port = urllib.splitport(host)
+            host, port = splitport(host)
             if port or socket.gethostbyname(host) not in self.get_names():
-                raise urllib2.URLError('file not on local host')
+                raise urlerror('file not on local host')
         fo = open(localfile,'rb')
         brange = req.headers.get('Range', None)
         brange = range_header_to_tuple(brange)
@@ -236,7 +259,7 @@ 
         headers = email.message_from_string(
             'Content-Type: %s\nContent-Length: %d\nLast-Modified: %s\n' %
             (mtype or 'text/plain', size, modified))
-        return urllib.addinfourl(fo, headers, 'file:'+file)
+        return addinfourl(fo, headers, 'file:'+file)
 
 
 # FTP Range Support
@@ -246,7 +269,7 @@ 
 # follows:
 # -- range support modifications start/end here
 
-class FTPRangeHandler(urllib2.FTPHandler):
+class FTPRangeHandler(ftphandler):
     def ftp_open(self, req):
         host = req.get_host()
         if not host:
@@ -270,7 +293,7 @@ 
         try:
             host = socket.gethostbyname(host)
         except socket.error as msg:
-            raise urllib2.URLError(msg)
+            raise urlerror(msg)
         path, attrs = splitattr(req.get_selector())
         dirs = path.split('/')
         dirs = map(unquote, dirs)
@@ -331,10 +354,10 @@ 
             raise IOError('ftp error', msg)
 
     def connect_ftp(self, user, passwd, host, port, dirs):
-        fw = ftpwrapper(user, passwd, host, port, dirs)
+        fw = ftpwrapperrest(user, passwd, host, port, dirs)
         return fw
 
-class ftpwrapper(urllib.ftpwrapper):
+class ftpwrapperrest(ftpwrapper):
     # range support note:
     # this ftpwrapper code is copied directly from
     # urllib. The only enhancement is to add the rest
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -8,8 +8,20 @@ 
 from __future__ import absolute_import
 
 import errno
-import urllib
-import urllib2
+
+try:
+    import urllib2
+    import urllib
+    httperror = urllib2.HTTPError
+    urlerror = urllib2.URLError
+    quote = urllib.quote
+    unquote = urllib.unquote
+except ImportError:
+    import urllib.error
+    httperror = urllib.error.HTTPError
+    urlerror = urllib.error.URLError
+    quote = urllib.request.quote
+    unquote = urllib.request.unquote
 
 from .i18n import _
 from .node import (
@@ -97,8 +109,8 @@ 
                       'missing "=" in parameter: %s') % p)
 
             key, value = p.split('=', 1)
-            key = urllib.unquote(key)
-            value = urllib.unquote(value)
+            key = unquote(key)
+            value = unquote(value)
             params[key] = value
 
         return version, params
@@ -236,7 +248,7 @@ 
     elif isinstance(b, streamclone.streamcloneapplier):
         requirements = streamclone.readbundle1header(fh)[2]
         params = 'requirements=%s' % ','.join(sorted(requirements))
-        return 'none-packed1;%s' % urllib.quote(params)
+        return 'none-packed1;%s' % quote(params)
     else:
         raise error.Abort(_('unknown bundle type: %s') % b)
 
@@ -1469,7 +1481,7 @@ 
     """return a set with appropriate options to use bundle20 during getbundle"""
     caps = set(['HG20'])
     capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
-    caps.add('bundle2=' + urllib.quote(capsblob))
+    caps.add('bundle2=' + quote(capsblob))
     return caps
 
 # List of names of steps to perform for a bundle2 for getbundle, order matters.
@@ -1535,7 +1547,7 @@ 
     b2caps = {}
     for bcaps in bundlecaps:
         if bcaps.startswith('bundle2='):
-            blob = urllib.unquote(bcaps[len('bundle2='):])
+            blob = unquote(bcaps[len('bundle2='):])
             b2caps.update(bundle2.decodecaps(blob))
     bundler = bundle2.bundle20(repo.ui, b2caps)
 
@@ -1804,8 +1816,8 @@ 
         attrs = {'URL': fields[0]}
         for rawattr in fields[1:]:
             key, value = rawattr.split('=', 1)
-            key = urllib.unquote(key)
-            value = urllib.unquote(value)
+            key = unquote(key)
+            value = unquote(value)
             attrs[key] = value
 
             # Parse BUNDLESPEC into components. This makes client-side
diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py
--- a/mercurial/hgweb/protocol.py
+++ b/mercurial/hgweb/protocol.py
@@ -8,7 +8,6 @@ 
 from __future__ import absolute_import
 
 import cgi
-import urllib
 import zlib
 
 try:
@@ -16,6 +15,13 @@ 
 except ImportError:
     import io
 
+try:
+    import urllib
+    quote = urllib.quote
+except AttributeError:
+    import urllib.request
+    quote = urllib.request.quote
+
 from .common import (
     HTTP_OK,
 )
@@ -86,8 +92,8 @@ 
     def _client(self):
         return 'remote:%s:%s:%s' % (
             self.req.env.get('wsgi.url_scheme') or 'http',
-            urllib.quote(self.req.env.get('REMOTE_HOST', '')),
-            urllib.quote(self.req.env.get('REMOTE_USER', '')))
+            quote(self.req.env.get('REMOTE_HOST', '')),
+            quote(self.req.env.get('REMOTE_USER', '')))
 
 def iscmd(cmd):
     return cmd in wireproto.commands
diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -15,7 +15,13 @@ 
 import socket
 import sys
 import traceback
-import urllib
+
+try:
+    import urllib
+    unquote = urllib.unquote
+except AttributeError:
+    import urllib.request
+    unquote = urllib.request.unquote
 
 from ..i18n import _
 
@@ -38,7 +44,7 @@ 
         path, query = uri.split('?', 1)
     else:
         path, query = uri, ''
-    return urllib.unquote(path), query
+    return unquote(path), query
 
 class _error_logger(object):
     def __init__(self, handler):
diff --git a/mercurial/httpconnection.py b/mercurial/httpconnection.py
--- a/mercurial/httpconnection.py
+++ b/mercurial/httpconnection.py
@@ -13,8 +13,23 @@ 
 import logging
 import os
 import socket
-import urllib
-import urllib2
+
+try:
+    import urllib2
+    import urllib
+    httphandler = urllib2.HTTPHandler
+    httpshandler = urllib2.HTTPSHandler
+    abstracthttphandler = urllib2.AbstractHTTPHandler
+    addinfourl = urllib.addinfourl
+    urlerror = urllib2.URLError
+except ImportError:
+    import urllib.request
+    import urllib.error
+    httphandler = urllib.request.HTTPHandler
+    httpshandler = urllib.request.HTTPSHandler
+    abstracthttphandler = urllib.request.AbstractHTTPHandler
+    addinfourl = urllib.request.addinfourl
+    urlerror = urllib.error.URLError
 
 from .i18n import _
 from . import (
@@ -123,10 +138,10 @@ 
 # Subclass BOTH of these because otherwise urllib2 "helpfully"
 # reinserts them since it notices we don't include any subclasses of
 # them.
-class http2handler(urllib2.HTTPHandler, urllib2.HTTPSHandler):
+class http2handler(httphandler, httpshandler):
     def __init__(self, ui, pwmgr):
         global _configuredlogging
-        urllib2.AbstractHTTPHandler.__init__(self)
+        abstracthttphandler.__init__(self)
         self.ui = ui
         self.pwmgr = pwmgr
         self._connections = {}
@@ -187,7 +202,7 @@ 
             proxy = None
 
         if not host:
-            raise urllib2.URLError('no host given')
+            raise urlerror('no host given')
 
         connkey = use_ssl, host, proxy
         allconns = self._connections.get(connkey, [])
@@ -217,13 +232,13 @@ 
             h.request(req.get_method(), path, req.data, headers)
             r = h.getresponse()
         except socket.error as err: # XXX what error?
-            raise urllib2.URLError(err)
+            raise urlerror(err)
 
         # Pick apart the HTTPResponse object to get the addinfourl
         # object initialized properly.
         r.recv = r.read
 
-        resp = urllib.addinfourl(r, r.headers, req.get_full_url())
+        resp = addinfourl(r, r.headers, req.get_full_url())
         resp.code = r.status
         resp.msg = r.reason
         return resp
diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -13,10 +13,22 @@ 
 import os
 import socket
 import tempfile
-import urllib
-import urllib2
 import zlib
 
+try:
+    import urllib2
+    import urllib
+    request = urllib2.Request
+    httperror = urllib2.HTTPError
+    urlencode = urllib.urlencode
+except ImportError:
+    import urllib.request
+    import urllib.error
+    import urllib.parse
+    request = urllib.request.Request
+    httperror = urllib.error.HTTPError
+    urlencode = urllib.parse.urlencode
+
 from .i18n import _
 from .node import nullid
 from . import (
@@ -59,7 +71,7 @@ 
         self.ui.debug('using %s\n' % self._url)
 
         self.urlopener = url.opener(ui, authinfo)
-        self.requestbuilder = urllib2.Request
+        self.requestbuilder = request
 
     def __del__(self):
         if self.urlopener:
@@ -105,7 +117,7 @@ 
         # object rather than a basestring
         canmungedata = not data or isinstance(data, basestring)
         if postargsok and canmungedata:
-            strargs = urllib.urlencode(sorted(args.items()))
+            strargs = urlencode(sorted(args.items()))
             if strargs:
                 if not data:
                     data = strargs
@@ -119,7 +131,7 @@ 
                     headersize = int(httpheader.split(',', 1)[0])
             if headersize > 0:
                 # The headers can typically carry more data than the URL.
-                encargs = urllib.urlencode(sorted(args.items()))
+                encargs = urlencode(sorted(args.items()))
                 headerfmt = 'X-HgArg-%s'
                 contentlen = headersize - len(headerfmt % '000' + ': \r\n')
                 headernum = 0
@@ -132,7 +144,7 @@ 
                 headers['Vary'] = ','.join(varyheaders)
             else:
                 q += sorted(args.items())
-        qs = '?%s' % urllib.urlencode(q)
+        qs = '?%s' % urlencode(q)
         cu = "%s%s" % (self._url, qs)
         size = 0
         if util.safehasattr(data, 'length'):
@@ -150,7 +162,7 @@ 
             req.add_unredirected_header('Content-Length', '%d' % size)
         try:
             resp = self.urlopener.open(req)
-        except urllib2.HTTPError as inst:
+        except httperror as inst:
             if inst.code == 401:
                 raise error.Abort(_('authorization failed'))
             raise
diff --git a/mercurial/keepalive.py b/mercurial/keepalive.py
--- a/mercurial/keepalive.py
+++ b/mercurial/keepalive.py
@@ -25,13 +25,23 @@ 
 
 """An HTTP handler for urllib2 that supports HTTP 1.1 and keepalive.
 
->>> import urllib2
+>>> try:
+...     import urllib2
+...     build_opener = urllib2.build_opener
+...     install_opener = urllib2.install_opener
+...     urlopen = urllib2.urlopen
+... except ImportError:
+...     import urllib.request
+...     build_opener = urllib.request.build_opener
+...     install_opener = urllib.request.install_opener
+...     urlopen = urllib.request.urlopen
+
 >>> from keepalive import HTTPHandler
 >>> keepalive_handler = HTTPHandler()
->>> opener = urllib2.build_opener(keepalive_handler)
->>> urllib2.install_opener(opener)
+>>> opener = build_opener(keepalive_handler)
+>>> install_opener(opener)
 >>>
->>> fo = urllib2.urlopen('http://www.python.org')
+>>> fo = urlopen('http://www.python.org')
 
 If a connection to a given host is requested, and all of the existing
 connections are still in use, another connection will be opened.  If
@@ -114,7 +124,22 @@ 
 import socket
 import sys
 import thread
-import urllib2
+
+try:
+    import urllib2
+    build_opener = urllib2.build_opener
+    install_opener = urllib2.install_opener
+    urlopen = urllib2.urlopen
+    httphandler = urllib2.HTTPHandler
+    urlerror = urllib2.URLError
+except ImportError:
+    import urllib.request
+    import urllib.error
+    build_opener = urllib.request.build_opener
+    install_opener = urllib.request.install_opener
+    urlopen = urllib.request.urlopen
+    httphandler = urllib.request.HTTPHandler
+    urlerror = urllib.error.URLError
 
 DEBUG = None
 
@@ -227,7 +252,7 @@ 
     def do_open(self, http_class, req):
         host = req.get_host()
         if not host:
-            raise urllib2.URLError('no host given')
+            raise urlerror('no host given')
 
         try:
             h = self._cm.get_ready_conn(host)
@@ -254,7 +279,7 @@ 
                 self._start_transaction(h, req)
                 r = h.getresponse()
         except (socket.error, httplib.HTTPException) as err:
-            raise urllib2.URLError(err)
+            raise urlerror(err)
 
         # if not a persistent connection, don't try to reuse it
         if r.will_close:
@@ -346,14 +371,14 @@ 
             else:
                 h.putrequest('GET', req.get_selector(), **skipheaders)
         except socket.error as err:
-            raise urllib2.URLError(err)
+            raise urlerror(err)
         for k, v in headers.items():
             h.putheader(k, v)
         h.endheaders()
         if req.has_data():
             h.send(data)
 
-class HTTPHandler(KeepAliveHandler, urllib2.HTTPHandler):
+class HTTPHandler(KeepAliveHandler, httphandler):
     pass
 
 class HTTPResponse(httplib.HTTPResponse):
@@ -593,14 +618,14 @@ 
     global HANDLE_ERRORS
     orig = HANDLE_ERRORS
     keepalive_handler = HTTPHandler()
-    opener = urllib2.build_opener(keepalive_handler)
-    urllib2.install_opener(opener)
+    opener = build_opener(keepalive_handler)
+    install_opener(opener)
     pos = {0: 'off', 1: 'on'}
     for i in (0, 1):
         print("  fancy error handling %s (HANDLE_ERRORS = %i)" % (pos[i], i))
         HANDLE_ERRORS = i
         try:
-            fo = urllib2.urlopen(url)
+            fo = urlopen(url)
             fo.read()
             fo.close()
             try:
@@ -623,25 +648,25 @@ 
     format = '%25s: %s'
 
     # first fetch the file with the normal http handler
-    opener = urllib2.build_opener()
-    urllib2.install_opener(opener)
-    fo = urllib2.urlopen(url)
+    opener = build_opener()
+    install_opener(opener)
+    fo = urlopen(url)
     foo = fo.read()
     fo.close()
     m = md5(foo)
     print(format % ('normal urllib', m.hexdigest()))
 
     # now install the keepalive handler and try again
-    opener = urllib2.build_opener(HTTPHandler())
-    urllib2.install_opener(opener)
+    opener = build_opener(HTTPHandler())
+    install_opener(opener)
 
-    fo = urllib2.urlopen(url)
+    fo = urlopen(url)
     foo = fo.read()
     fo.close()
     m = md5(foo)
     print(format % ('keepalive read', m.hexdigest()))
 
-    fo = urllib2.urlopen(url)
+    fo = urlopen(url)
     foo = ''
     while True:
         f = fo.readline()
@@ -657,15 +682,15 @@ 
 
     sys.stdout.write('  first using the normal urllib handlers')
     # first use normal opener
-    opener = urllib2.build_opener()
-    urllib2.install_opener(opener)
+    opener = build_opener()
+    install_opener(opener)
     t1 = fetch(N, url)
     print('  TIME: %.3f s' % t1)
 
     sys.stdout.write('  now using the keepalive handler       ')
     # now install the keepalive handler and try again
-    opener = urllib2.build_opener(HTTPHandler())
-    urllib2.install_opener(opener)
+    opener = build_opener(HTTPHandler())
+    install_opener(opener)
     t2 = fetch(N, url)
     print('  TIME: %.3f s' % t2)
     print('  improvement factor: %.2f' % (t1 / t2))
@@ -677,7 +702,7 @@ 
     for i in range(N):
         if delay and i > 0:
             time.sleep(delay)
-        fo = urllib2.urlopen(url)
+        fo = urlopen(url)
         foo = fo.read()
         fo.close()
         lens.append(len(foo))
@@ -700,7 +725,7 @@ 
         info = warning = error = debug
     DEBUG = FakeLogger()
     print("  fetching the file to establish a connection")
-    fo = urllib2.urlopen(url)
+    fo = urlopen(url)
     data1 = fo.read()
     fo.close()
 
@@ -714,7 +739,7 @@ 
     sys.stderr.write('\r')
 
     print("  fetching the file a second time")
-    fo = urllib2.urlopen(url)
+    fo = urlopen(url)
     data2 = fo.read()
     fo.close()
 
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -12,9 +12,15 @@ 
 import os
 import random
 import time
-import urllib
 import weakref
 
+try:
+    import urllib
+    quote = urllib.quote
+except AttributeError:
+    import urllib.request
+    quote = urllib.request.quote
+
 from .i18n import _
 from .node import (
     hex,
@@ -366,7 +372,7 @@ 
         if self.ui.configbool('experimental', 'bundle2-advertise', True):
             caps = set(caps)
             capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
-            caps.add('bundle2=' + urllib.quote(capsblob))
+            caps.add('bundle2=' + quote(capsblob))
         return caps
 
     def _applyopenerreqs(self):
diff --git a/mercurial/statichttprepo.py b/mercurial/statichttprepo.py
--- a/mercurial/statichttprepo.py
+++ b/mercurial/statichttprepo.py
@@ -11,8 +11,21 @@ 
 
 import errno
 import os
-import urllib
-import urllib2
+
+try:
+    import urllib2
+    import urllib
+    request = urllib2.Request
+    httperror = urllib2.HTTPError
+    quote = urllib.quote
+    urlerror = urllib2.URLError
+except ImportError:
+    import urllib.request
+    import urllib.error
+    request = urllib.request.Request
+    httperror = urllib.error.HTTPError
+    quote = urllib.request.quote
+    urlerror = urllib.error.URLError
 
 from .i18n import _
 from . import (
@@ -45,7 +58,7 @@ 
     def seek(self, pos):
         self.pos = pos
     def read(self, bytes=None):
-        req = urllib2.Request(self.url)
+        req = request(self.url)
         end = ''
         if bytes:
             end = self.pos + bytes - 1
@@ -56,10 +69,10 @@ 
             f = self.opener.open(req)
             data = f.read()
             code = f.code
-        except urllib2.HTTPError as inst:
+        except httperror as inst:
             num = inst.code == 404 and errno.ENOENT or None
             raise IOError(num, inst)
-        except urllib2.URLError as inst:
+        except urlerror as inst:
             raise IOError(None, inst.reason[1])
 
         if code == 200:
@@ -92,7 +105,7 @@ 
         def __call__(self, path, mode='r', *args, **kw):
             if mode not in ('r', 'rb'):
                 raise IOError('Permission denied')
-            f = "/".join((self.base, urllib.quote(path)))
+            f = "/".join((self.base, quote(path)))
             return httprangereader(f, urlopener)
 
         def join(self, path):
diff --git a/mercurial/templatefilters.py b/mercurial/templatefilters.py
--- a/mercurial/templatefilters.py
+++ b/mercurial/templatefilters.py
@@ -11,7 +11,13 @@ 
 import os
 import re
 import time
-import urllib
+
+try:
+    import urllib
+    quote = urllib.quote
+except AttributeError:
+    import urllib.request
+    quote = urllib.request.quote
 
 from . import (
     encoding,
@@ -269,7 +275,7 @@ 
     Forward slashes are escaped twice to prevent web servers from prematurely
     unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
     """
-    return urllib.quote(text, safe='/@').replace('/', '%252F')
+    return quote(text, safe='/@').replace('/', '%252F')
 
 def rfc3339date(text):
     """:rfc3339date: Date. Returns a date using the Internet date format
@@ -342,7 +348,7 @@ 
     """:urlescape: Any text. Escapes all "special" characters. For example,
     "foo bar" becomes "foo%20bar".
     """
-    return urllib.quote(text)
+    return quote(text)
 
 def userfilter(text):
     """:user: Any text. Returns a short representation of a user name or email
diff --git a/mercurial/url.py b/mercurial/url.py
--- a/mercurial/url.py
+++ b/mercurial/url.py
@@ -13,8 +13,39 @@ 
 import httplib
 import os
 import socket
-import urllib
-import urllib2
+
+try:
+    import urllib2
+    import urllib
+    build_opener = urllib2.build_opener
+    pathname2url = urllib.pathname2url
+    pyhttpbasicauthhandler = urllib2.HTTPBasicAuthHandler
+    pyhttpdigestauthhandler = urllib2.HTTPDigestAuthHandler
+    pyhttppasswordmgrwithdefaultrealm = urllib2.HTTPPasswordMgrWithDefaultRealm
+    pyproxyhandler = urllib2.ProxyHandler
+
+    try:
+        pyhttpshandler = urllib2.HTTPSHandler
+        has_https = True
+    except AttributeError:
+        has_https = False
+
+except ImportError:
+    import urllib.request
+    import urllib.error
+    build_opener = urllib.request.build_opener
+    pathname2url = urllib.request.pathname2url
+    pyhttpbasicauthhandler = urllib.request.HTTPBasicAuthHandler
+    pyhttpdigestauthhandler = urllib.request.HTTPDigestAuthHandler
+    pyhttppasswordmgrwithdefaultrealm = \
+        urllib.request.HTTPPasswordMgrWithDefaultRealm
+    pyproxyhandler = urllib.request.ProxyHandler
+
+    try:
+        pyhttpshandler = urllib.request.HTTPSHandler
+        has_https = True
+    except AttributeError:
+        has_https = False
 
 try:
     import cStringIO as io
@@ -30,13 +61,13 @@ 
     util,
 )
 
-class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
+class passwordmgr(pyhttppasswordmgrwithdefaultrealm):
     def __init__(self, ui):
-        urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
+        pyhttppasswordmgrwithdefaultrealm.__init__(self)
         self.ui = ui
 
     def find_user_password(self, realm, authuri):
-        authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
+        authinfo = pyhttppasswordmgrwithdefaultrealm.find_user_password(
             self, realm, authuri)
         user, passwd = authinfo
         if user and passwd:
@@ -76,10 +107,10 @@ 
         self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
 
     def find_stored_password(self, authuri):
-        return urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
+        return pyhttppasswordmgrwithdefaultrealm.find_user_password(
             self, None, authuri)
 
-class proxyhandler(urllib2.ProxyHandler):
+class proxyhandler(pyproxyhandler):
     def __init__(self, ui):
         proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
         # XXX proxyauthinfo = None
@@ -125,7 +156,7 @@ 
                 except OSError:
                     pass
 
-        urllib2.ProxyHandler.__init__(self, proxies)
+        pyproxyhandler.__init__(self, proxies)
         self.ui = ui
 
     def proxy_open(self, req, proxy, type_):
@@ -138,7 +169,7 @@ 
             if e.startswith('.') and host.endswith(e[1:]):
                 return None
 
-        return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
+        return pyproxyhandler.proxy_open(self, req, proxy, type_)
 
 def _gen_sendfile(orgsend):
     def _sendfile(self, data):
@@ -152,7 +183,6 @@ 
             orgsend(self, data)
     return _sendfile
 
-has_https = util.safehasattr(urllib2, 'HTTPSHandler')
 if has_https:
     try:
         _create_connection = socket.create_connection
@@ -361,10 +391,10 @@ 
                 **sslutil.sslkwargs(self.ui, host))
             sslutil.validator(self.ui, host)(self.sock)
 
-    class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
+    class httpshandler(keepalive.KeepAliveHandler, pyhttpshandler):
         def __init__(self, ui):
             keepalive.KeepAliveHandler.__init__(self)
-            urllib2.HTTPSHandler.__init__(self)
+            pyhttpshandler.__init__(self)
             self.ui = ui
             self.pwmgr = passwordmgr(self.ui)
 
@@ -407,9 +437,9 @@ 
             conn.ui = self.ui
             return conn
 
-class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
+class httpdigestauthhandler(pyhttpdigestauthhandler):
     def __init__(self, *args, **kwargs):
-        urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
+        pyhttpdigestauthhandler.__init__(self, *args, **kwargs)
         self.retried_req = None
 
     def reset_retry_count(self):
@@ -423,13 +453,13 @@ 
         if req is not self.retried_req:
             self.retried_req = req
             self.retried = 0
-        return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
+        return pyhttpdigestauthhandler.http_error_auth_reqed(
                     self, auth_header, host, req, headers)
 
-class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
+class httpbasicauthhandler(pyhttpbasicauthhandler):
     def __init__(self, *args, **kwargs):
         self.auth = None
-        urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
+        pyhttpbasicauthhandler.__init__(self, *args, **kwargs)
         self.retried_req = None
 
     def http_request(self, request):
@@ -455,7 +485,7 @@ 
         if req is not self.retried_req:
             self.retried_req = req
             self.retried = 0
-        return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
+        return pyhttpbasicauthhandler.http_error_auth_reqed(
                         self, auth_header, host, req, headers)
 
     def retry_http_basic_auth(self, host, req, realm):
@@ -498,7 +528,7 @@ 
     handlers.extend((httpbasicauthhandler(passmgr),
                      httpdigestauthhandler(passmgr)))
     handlers.extend([h(ui, passmgr) for h in handlerfuncs])
-    opener = urllib2.build_opener(*handlers)
+    opener = build_opener(*handlers)
 
     # 1.0 here is the _protocol_ version
     opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
@@ -512,6 +542,6 @@ 
         url_, authinfo = u.authinfo()
     else:
         path = util.normpath(os.path.abspath(url_))
-        url_ = 'file://' + urllib.pathname2url(path)
+        url_ = 'file://' + pathname2url(path)
         authinfo = None
     return opener(ui, authinfo).open(url_, data)
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -34,9 +34,15 @@ 
 import textwrap
 import time
 import traceback
-import urllib
 import zlib
 
+try:
+    import urllib
+    quote = urllib.quote
+except AttributeError:
+    import urllib.request
+    quote = urllib.request.quote
+
 from . import (
     encoding,
     error,
@@ -2368,30 +2374,30 @@ 
             if hasdriveletter(self.path):
                 s += '/'
         if self.user:
-            s += urllib.quote(self.user, safe=self._safechars)
+            s += quote(self.user, safe=self._safechars)
         if self.passwd:
-            s += ':' + urllib.quote(self.passwd, safe=self._safechars)
+            s += ':' + quote(self.passwd, safe=self._safechars)
         if self.user or self.passwd:
             s += '@'
         if self.host:
             if not (self.host.startswith('[') and self.host.endswith(']')):
-                s += urllib.quote(self.host)
+                s += quote(self.host)
             else:
                 s += self.host
         if self.port:
-            s += ':' + urllib.quote(self.port)
+            s += ':' + quote(self.port)
         if self.host:
             s += '/'
         if self.path:
             # TODO: similar to the query string, we should not unescape the
             # path when we store it, the path might contain '%2f' = '/',
             # which we should *not* escape.
-            s += urllib.quote(self.path, safe=self._safepchars)
+            s += quote(self.path, safe=self._safepchars)
         if self.query:
             # we store the query in escaped form.
             s += '?' + self.query
         if self.fragment is not None:
-            s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
+            s += '#' + quote(self.fragment, safe=self._safepchars)
         return s
 
     def authinfo(self):
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -11,7 +11,15 @@ 
 import os
 import sys
 import tempfile
-import urllib
+
+try:
+    import urllib
+    quote = urllib.quote
+    unquote = urllib.unquote
+except AttributeError:
+    import urllib.request
+    quote = urllib.request.quote
+    unquote = urllib.request.unquote
 
 from .i18n import _
 from .node import (
@@ -287,7 +295,7 @@ 
             branchmap = {}
             for branchpart in d.splitlines():
                 branchname, branchheads = branchpart.split(' ', 1)
-                branchname = encoding.tolocal(urllib.unquote(branchname))
+                branchname = encoding.tolocal(unquote(branchname))
                 branchheads = decodelist(branchheads)
                 branchmap[branchname] = branchheads
             yield branchmap
@@ -632,7 +640,7 @@ 
     branchmap = repo.branchmap()
     heads = []
     for branch, nodes in branchmap.iteritems():
-        branchname = urllib.quote(encoding.fromlocal(branch))
+        branchname = quote(encoding.fromlocal(branch))
         branchnodes = encodelist(nodes)
         heads.append('%s %s' % (branchname, branchnodes))
     return '\n'.join(heads)
@@ -684,7 +692,7 @@ 
             caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
     if repo.ui.configbool('experimental', 'bundle2-advertise', True):
         capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
-        caps.append('bundle2=' + urllib.quote(capsblob))
+        caps.append('bundle2=' + quote(capsblob))
     caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
     caps.append(
         'httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen', 1024))
diff --git a/tests/test-check-py3-compat.t b/tests/test-check-py3-compat.t
--- a/tests/test-check-py3-compat.t
+++ b/tests/test-check-py3-compat.t
@@ -165,8 +165,8 @@ 
   hgext/largefiles/lfutil.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   hgext/largefiles/localstore.py: error importing module: <ImportError> No module named 'lfutil' (line *) (glob)
   hgext/largefiles/overrides.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
-  hgext/largefiles/proto.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
-  hgext/largefiles/remotestore.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
+  hgext/largefiles/proto.py: error importing: <ImportError> No module named 'httplib' (error at httppeer.py:*) (glob)
+  hgext/largefiles/remotestore.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   hgext/largefiles/reposetup.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   hgext/largefiles/uisetup.py: error importing module: <SyntaxError> invalid syntax (archival.py, line *) (line *) (glob)
   hgext/largefiles/wirestore.py: error importing module: <ImportError> No module named 'lfutil' (line *) (glob)
@@ -189,7 +189,6 @@ 
   mercurial/branchmap.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/bundle*.py: invalid syntax: invalid syntax (<unknown>, line *) (glob)
   mercurial/bundlerepo.py: error importing module: <SyntaxError> invalid syntax (bundle*.py, line *) (line *) (glob)
-  mercurial/byterange.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
   mercurial/changegroup.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/changelog.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/cmdutil.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
@@ -203,7 +202,7 @@ 
   mercurial/dirstate.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/discovery.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/dispatch.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
-  mercurial/exchange.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
+  mercurial/exchange.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/extensions.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/filelog.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/filemerge.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
@@ -223,7 +222,7 @@ 
   mercurial/hgweb/wsgicgi.py: error importing module: <SystemError> Parent module 'mercurial.hgweb' not loaded, cannot perform relative import (line *) (glob)
   mercurial/hook.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/httpclient/_readers.py: error importing module: <ImportError> No module named 'httplib' (line *) (glob)
-  mercurial/httpconnection.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
+  mercurial/httpconnection.py: error importing: <ImportError> No module named 'httplib' (error at __init__.py:*) (glob)
   mercurial/httppeer.py: error importing module: <ImportError> No module named 'httplib' (line *) (glob)
   mercurial/keepalive.py: error importing module: <ImportError> No module named 'httplib' (line *) (glob)
   mercurial/localrepo.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
@@ -254,7 +253,7 @@ 
   mercurial/sshpeer.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
   mercurial/sshserver.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
   mercurial/sslutil.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
-  mercurial/statichttprepo.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
+  mercurial/statichttprepo.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
   mercurial/store.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
   mercurial/streamclone.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
   mercurial/subrepo.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
diff --git a/tests/test-hgweb-auth.py b/tests/test-hgweb-auth.py
--- a/tests/test-hgweb-auth.py
+++ b/tests/test-hgweb-auth.py
@@ -1,5 +1,12 @@ 
+try:
+    import urllib2
+    httppasswordmgrwithdefaultrealm = urllib2.HTTPPasswordMgrWithDefaultRealm
+except AttributeError:
+    import urllib.request
+    httppasswordmgrwithdefaultrealm = \
+        urllib.request.HTTPPasswordMgrWithDefaultRealm
+
 from mercurial import demandimport; demandimport.enable()
-import urllib2
 from mercurial import ui, util
 from mercurial import url
 from mercurial.error import Abort
@@ -99,7 +106,7 @@ 
 
 def testauthinfo(fullurl, authurl):
     print 'URIs:', fullurl, authurl
-    pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
+    pm = httppasswordmgrwithdefaultrealm()
     pm.add_password(*util.url(fullurl).authinfo()[1])
     print pm.find_user_password('test', authurl)