##// END OF EJS Templates
pycompat: custom implementation of urllib.parse.quote()...
pycompat: custom implementation of urllib.parse.quote() urllib.parse.quote() accepts either str or bytes and returns str. There exists a urllib.parse.quote_from_bytes() which only accepts bytes. We should probably use that to retain strong typing and avoid surprises. In addition, since nearly all strings in Mercurial are bytes, we probably don't want quote() returning unicode. So, this patch implements a custom quote() that only accepts bytes and returns bytes. The quoted URL should only contain URL safe characters which is a strict subset of ASCII. So `.encode('ascii', 'strict')` should be safe.

File last commit:

r31400:fb1f7033 default
r31400:fb1f7033 default
Show More
pycompat.py
323 lines | 9.5 KiB | text/x-python | PythonLexer
timeless
pycompat: add empty and queue to handle py3 divergence...
r28818 # pycompat.py - portability shim for python 3
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""Mercurial portability shim for python 3.
This contains aliases to hide python version-specific details from the core.
"""
from __future__ import absolute_import
Pulkit Goyal
py3: make a bytes version of getopt.getopt()...
r30578 import getopt
Pulkit Goyal
py3: add a bytes version of os.name...
r30302 import os
Pulkit Goyal
py3: have a bytes version of shlex.split()...
r30678 import shlex
Pulkit Goyal
pycompat: make pycompat demandimport friendly...
r29584 import sys
Yuya Nishihara
pycompat: provide 'ispy3' constant...
r30030 ispy3 = (sys.version_info[0] >= 3)
if not ispy3:
Pulkit Goyal
py3: conditionalize cPickle import by adding in util...
r29324 import cPickle as pickle
Pulkit Goyal
py3: conditionalize httplib import...
r29455 import httplib
Pulkit Goyal
pycompat: make pycompat demandimport friendly...
r29584 import Queue as _queue
Pulkit Goyal
py3: conditionalize SocketServer import...
r29433 import SocketServer as socketserver
Pulkit Goyal
pycompat: make pycompat demandimport friendly...
r29584 import urlparse
Augie Fackler
pycompat: introduce an alias for urllib.unquote...
r30327 urlunquote = urlparse.unquote
Pulkit Goyal
py3: conditionalize xmlrpclib import...
r29432 import xmlrpclib
Pulkit Goyal
pycompat: make pycompat demandimport friendly...
r29584 else:
import http.client as httplib
import pickle
import queue as _queue
import socketserver
import urllib.parse as urlparse
Augie Fackler
pycompat: introduce an alias for urllib.unquote...
r30327 urlunquote = urlparse.unquote_to_bytes
Pulkit Goyal
py3: conditionalize xmlrpclib import...
r29432 import xmlrpc.client as xmlrpclib
Pulkit Goyal
py3: conditionalize the urlparse import...
r29431
Yuya Nishihara
pycompat: provide 'ispy3' constant...
r30030 if ispy3:
Yuya Nishihara
py3: move xrange alias next to import lines...
r29797 import builtins
Yuya Nishihara
py3: provide (del|get|has|set)attr wrappers that accepts bytes...
r29799 import functools
Yuya Nishihara
pycompat: move imports of cStringIO/io to where they are used...
r31372 import io
Martijn Pieters
py3: add an os.fsencode backport to ease path handling
r30119 fsencode = os.fsencode
Pulkit Goyal
py3: add os.fsdecode() as pycompat.fsdecode()...
r30300 fsdecode = os.fsdecode
Pulkit Goyal
py3: add a bytes version of os.name...
r30302 # A bytes version of os.name.
osname = os.name.encode('ascii')
Pulkit Goyal
py3: have pycompat.ospathsep and pycompat.ossep...
r30303 ospathsep = os.pathsep.encode('ascii')
ossep = os.sep.encode('ascii')
Pulkit Goyal
py3: have a bytes version of os.altsep...
r30623 osaltsep = os.altsep
if osaltsep:
osaltsep = osaltsep.encode('ascii')
Pulkit Goyal
py3: add os.getcwdb() to have bytes path...
r30500 # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
# returns bytes.
getcwd = os.getcwdb
Pulkit Goyal
py3: have a bytes version of sys.platform...
r30624 sysplatform = sys.platform.encode('ascii')
Pulkit Goyal
py3: have bytes version of sys.executable...
r30668 sysexecutable = sys.executable
if sysexecutable:
sysexecutable = os.fsencode(sysexecutable)
Pulkit Goyal
pycompat: default to BytesIO instead of StringIO
r31359 stringio = io.BytesIO
Yuya Nishihara
py3: document why os.fsencode() can be used to get back bytes argv...
r30334
Yuya Nishihara
py3: provide bytes stdin/out/err through util module...
r30472 # TODO: .buffer might not exist if std streams were replaced; we'll need
# a silly wrapper to make a bytes stream backed by a unicode one.
stdin = sys.stdin.buffer
stdout = sys.stdout.buffer
stderr = sys.stderr.buffer
Yuya Nishihara
py3: document why os.fsencode() can be used to get back bytes argv...
r30334 # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
# we can use os.fsencode() to get back bytes argv.
#
# https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55
#
# TODO: On Windows, the native argv is wchar_t, so we'll need a different
# workaround to simulate the Python 2 (i.e. ANSI Win32 API) behavior.
Augie Fackler
pycompat: verify sys.argv exists before forwarding it (issue5493)...
r31277 if getattr(sys, 'argv', None) is not None:
sysargv = list(map(os.fsencode, sys.argv))
Yuya Nishihara
py3: move xrange alias next to import lines...
r29797
Yuya Nishihara
py3: factor out bytechr() function...
r31253 def bytechr(i):
return bytes([i])
Yuya Nishihara
pycompat: add helper to iterate each char in bytes
r31382 def iterbytestr(s):
"""Iterate bytes as if it were a str object of Python 2"""
return iter(s[i:i + 1] for i in range(len(s)))
Yuya Nishihara
pycompat: extract function that converts attribute or encoding name to str...
r30032 def sysstr(s):
"""Return a keyword str to be passed to Python functions such as
getattr() and str.encode()
This never raises UnicodeDecodeError. Non-ascii characters are
considered invalid and mapped to arbitrary but unique code points
such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
"""
if isinstance(s, builtins.str):
return s
return s.decode(u'latin-1')
Yuya Nishihara
py3: provide (del|get|has|set)attr wrappers that accepts bytes...
r29799 def _wrapattrfunc(f):
@functools.wraps(f)
def w(object, name, *args):
Yuya Nishihara
pycompat: extract function that converts attribute or encoding name to str...
r30032 return f(object, sysstr(name), *args)
Yuya Nishihara
py3: provide (del|get|has|set)attr wrappers that accepts bytes...
r29799 return w
Yuya Nishihara
py3: import builtin wrappers automagically by code transformer...
r29800 # these wrappers are automagically imported by hgloader
Yuya Nishihara
py3: provide (del|get|has|set)attr wrappers that accepts bytes...
r29799 delattr = _wrapattrfunc(builtins.delattr)
getattr = _wrapattrfunc(builtins.getattr)
hasattr = _wrapattrfunc(builtins.hasattr)
setattr = _wrapattrfunc(builtins.setattr)
Yuya Nishihara
py3: import builtin wrappers automagically by code transformer...
r29800 xrange = builtins.range
Yuya Nishihara
py3: provide (del|get|has|set)attr wrappers that accepts bytes...
r29799
Pulkit Goyal
py3: add pycompat.open and replace open() calls...
r31149 def open(name, mode='r', buffering=-1):
return builtins.open(name, sysstr(mode), buffering)
Pulkit Goyal
py3: make a bytes version of getopt.getopt()...
r30578 # getopt.getopt() on Python 3 deals with unicodes internally so we cannot
# pass bytes there. Passing unicodes will result in unicodes as return
# values which we need to convert again to bytes.
def getoptb(args, shortlist, namelist):
args = [a.decode('latin-1') for a in args]
shortlist = shortlist.decode('latin-1')
namelist = [a.decode('latin-1') for a in namelist]
opts, args = getopt.getopt(args, shortlist, namelist)
opts = [(a[0].encode('latin-1'), a[1].encode('latin-1'))
for a in opts]
args = [a.encode('latin-1') for a in args]
return opts, args
Pulkit Goyal
py3: utility functions to convert keys of kwargs to bytes/unicodes...
r30579 # keys of keyword arguments in Python need to be strings which are unicodes
# Python 3. This function takes keyword arguments, convert the keys to str.
def strkwargs(dic):
dic = dict((k.decode('latin-1'), v) for k, v in dic.iteritems())
return dic
# keys of keyword arguments need to be unicode while passing into
# a function. This function helps us to convert those keys back to bytes
# again as we need to deal with bytes.
def byteskwargs(dic):
dic = dict((k.encode('latin-1'), v) for k, v in dic.iteritems())
return dic
Pulkit Goyal
py3: have a bytes version of shlex.split()...
r30678 # shlex.split() accepts unicodes on Python 3. This function takes bytes
# argument, convert it into unicodes, pass into shlex.split(), convert the
# returned value to bytes and return that.
# TODO: handle shlex.shlex().
def shlexsplit(s):
ret = shlex.split(s.decode('latin-1'))
return [a.encode('latin-1') for a in ret]
Yuya Nishihara
pycompat: extract function that converts attribute or encoding name to str...
r30032 else:
Yuya Nishihara
pycompat: move imports of cStringIO/io to where they are used...
r31372 import cStringIO
Yuya Nishihara
py3: factor out bytechr() function...
r31253 bytechr = chr
Yuya Nishihara
pycompat: add helper to iterate each char in bytes
r31382 iterbytestr = iter
Yuya Nishihara
py3: factor out bytechr() function...
r31253
Yuya Nishihara
pycompat: extract function that converts attribute or encoding name to str...
r30032 def sysstr(s):
return s
Martijn Pieters
pycompat: only accept a bytestring filepath in Python 2
r30133 # Partial backport from os.py in Python 3, which only accepts bytes.
# In Python 2, our paths should only ever be bytes, a unicode path
# indicates a bug.
def fsencode(filename):
if isinstance(filename, str):
return filename
Martijn Pieters
py3: add an os.fsencode backport to ease path handling
r30119 else:
Martijn Pieters
pycompat: only accept a bytestring filepath in Python 2
r30133 raise TypeError(
"expect str, not %s" % type(filename).__name__)
Martijn Pieters
py3: add an os.fsencode backport to ease path handling
r30119
Pulkit Goyal
py3: add os.fsdecode() as pycompat.fsdecode()...
r30300 # In Python 2, fsdecode() has a very chance to receive bytes. So it's
# better not to touch Python 2 part as it's already working fine.
def fsdecode(filename):
return filename
Pulkit Goyal
py3: make a bytes version of getopt.getopt()...
r30578 def getoptb(args, shortlist, namelist):
return getopt.getopt(args, shortlist, namelist)
Pulkit Goyal
py3: utility functions to convert keys of kwargs to bytes/unicodes...
r30579 def strkwargs(dic):
return dic
def byteskwargs(dic):
return dic
Pulkit Goyal
py3: add a bytes version of os.name...
r30302 osname = os.name
Pulkit Goyal
py3: have pycompat.ospathsep and pycompat.ossep...
r30303 ospathsep = os.pathsep
ossep = os.sep
Pulkit Goyal
py3: have a bytes version of os.altsep...
r30623 osaltsep = os.altsep
Yuya Nishihara
py3: provide bytes stdin/out/err through util module...
r30472 stdin = sys.stdin
stdout = sys.stdout
stderr = sys.stderr
Augie Fackler
pycompat: verify sys.argv exists before forwarding it (issue5493)...
r31277 if getattr(sys, 'argv', None) is not None:
sysargv = sys.argv
Pulkit Goyal
py3: have a bytes version of sys.platform...
r30624 sysplatform = sys.platform
Pulkit Goyal
py3: add os.getcwdb() to have bytes path...
r30500 getcwd = os.getcwd
Pulkit Goyal
py3: have bytes version of sys.executable...
r30668 sysexecutable = sys.executable
Pulkit Goyal
py3: have a bytes version of shlex.split()...
r30678 shlexsplit = shlex.split
Yuya Nishihara
pycompat: move imports of cStringIO/io to where they are used...
r31372 stringio = cStringIO.StringIO
Pulkit Goyal
py3: add a bytes version of os.name...
r30302
timeless
pycompat: add empty and queue to handle py3 divergence...
r28818 empty = _queue.Empty
queue = _queue.Queue
timeless
pycompat: alias xrange to range in py3
r28834
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882 class _pycompatstub(object):
Yuya Nishihara
pycompat: delay loading modules registered to stub...
r29801 def __init__(self):
self._aliases = {}
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882
Yuya Nishihara
pycompat: delay loading modules registered to stub...
r29801 def _registeraliases(self, origin, items):
"""Add items that will be populated at the first access"""
Augie Fackler
pycompat: when setting attrs, ensure we use sysstr...
r30086 items = map(sysstr, items)
self._aliases.update(
(item.replace(sysstr('_'), sysstr('')).lower(), (origin, item))
for item in items)
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882
Yuya Nishihara
pycompat: delay loading modules registered to stub...
r29801 def __getattr__(self, name):
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882 try:
Yuya Nishihara
pycompat: delay loading modules registered to stub...
r29801 origin, item = self._aliases[name]
except KeyError:
raise AttributeError(name)
self.__dict__[name] = obj = getattr(origin, item)
return obj
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882
Pulkit Goyal
py3: conditionalize BaseHTTPServer, SimpleHTTPServer and CGIHTTPServer import...
r29566 httpserver = _pycompatstub()
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882 urlreq = _pycompatstub()
urlerr = _pycompatstub()
Yuya Nishihara
pycompat: provide 'ispy3' constant...
r30030 if not ispy3:
Pulkit Goyal
py3: conditionalize BaseHTTPServer, SimpleHTTPServer and CGIHTTPServer import...
r29566 import BaseHTTPServer
import CGIHTTPServer
import SimpleHTTPServer
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882 import urllib2
import urllib
Yuya Nishihara
pycompat: delay loading modules registered to stub...
r29801 urlreq._registeraliases(urllib, (
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882 "addclosehook",
"addinfourl",
"ftpwrapper",
"pathname2url",
"quote",
"splitattr",
"splitpasswd",
"splitport",
"splituser",
"unquote",
"url2pathname",
"urlencode",
))
Yuya Nishihara
pycompat: delay loading modules registered to stub...
r29801 urlreq._registeraliases(urllib2, (
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882 "AbstractHTTPHandler",
"BaseHandler",
"build_opener",
"FileHandler",
"FTPHandler",
"HTTPBasicAuthHandler",
"HTTPDigestAuthHandler",
"HTTPHandler",
"HTTPPasswordMgrWithDefaultRealm",
"HTTPSHandler",
"install_opener",
"ProxyHandler",
"Request",
"urlopen",
))
Yuya Nishihara
pycompat: delay loading modules registered to stub...
r29801 urlerr._registeraliases(urllib2, (
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882 "HTTPError",
"URLError",
))
Yuya Nishihara
pycompat: delay loading modules registered to stub...
r29801 httpserver._registeraliases(BaseHTTPServer, (
Pulkit Goyal
py3: conditionalize BaseHTTPServer, SimpleHTTPServer and CGIHTTPServer import...
r29566 "HTTPServer",
"BaseHTTPRequestHandler",
))
Yuya Nishihara
pycompat: delay loading modules registered to stub...
r29801 httpserver._registeraliases(SimpleHTTPServer, (
Pulkit Goyal
py3: conditionalize BaseHTTPServer, SimpleHTTPServer and CGIHTTPServer import...
r29566 "SimpleHTTPRequestHandler",
))
Yuya Nishihara
pycompat: delay loading modules registered to stub...
r29801 httpserver._registeraliases(CGIHTTPServer, (
Pulkit Goyal
py3: conditionalize BaseHTTPServer, SimpleHTTPServer and CGIHTTPServer import...
r29566 "CGIHTTPRequestHandler",
))
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882
Yuya Nishihara
pycompat: delay loading modules registered to stub...
r29801 else:
Gregory Szorc
pycompat: alias urllib symbols directly...
r31399 import urllib.parse
urlreq._registeraliases(urllib.parse, (
"splitattr",
"splitpasswd",
"splitport",
"splituser",
"unquote",
))
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882 import urllib.request
Yuya Nishihara
pycompat: delay loading modules registered to stub...
r29801 urlreq._registeraliases(urllib.request, (
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882 "AbstractHTTPHandler",
"BaseHandler",
"build_opener",
"FileHandler",
"FTPHandler",
"ftpwrapper",
"HTTPHandler",
"HTTPSHandler",
"install_opener",
"pathname2url",
"HTTPBasicAuthHandler",
"HTTPDigestAuthHandler",
Gregory Szorc
pycompat: add HTTPPasswordMgrWithDefaultRealm to Python 3 block...
r29414 "HTTPPasswordMgrWithDefaultRealm",
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882 "ProxyHandler",
"Request",
"url2pathname",
"urlopen",
))
Gregory Szorc
pycompat: alias urllib symbols directly...
r31399 import urllib.response
urlreq._registeraliases(urllib.response, (
"addclosehook",
"addinfourl",
))
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882 import urllib.error
Yuya Nishihara
pycompat: delay loading modules registered to stub...
r29801 urlerr._registeraliases(urllib.error, (
timeless
pycompat: add util.urlerr util.urlreq classes for py3 compat...
r28882 "HTTPError",
"URLError",
))
Pulkit Goyal
py3: conditionalize BaseHTTPServer, SimpleHTTPServer and CGIHTTPServer import...
r29566 import http.server
Yuya Nishihara
pycompat: delay loading modules registered to stub...
r29801 httpserver._registeraliases(http.server, (
Pulkit Goyal
py3: conditionalize BaseHTTPServer, SimpleHTTPServer and CGIHTTPServer import...
r29566 "HTTPServer",
"BaseHTTPRequestHandler",
"SimpleHTTPRequestHandler",
"CGIHTTPRequestHandler",
))
Gregory Szorc
pycompat: custom implementation of urllib.parse.quote()...
r31400
# urllib.parse.quote() accepts both str and bytes, decodes bytes
# (if necessary), and returns str. This is wonky. We provide a custom
# implementation that only accepts bytes and emits bytes.
def quote(s, safe=r'/'):
s = urllib.parse.quote_from_bytes(s, safe=safe)
return s.encode('ascii', 'strict')
urlreq.quote = quote