Mercurial > hg
changeset 48871:79009cca491e
pycompat: remove large Python 2 block
We no longer support Python 2. So we can delete its compatibility
code and remove the conditional and dedent the Python 3 code.
In order to make the linter happy, we had to inline imports in the
stanza at the top of the file.
Differential Revision: https://phab.mercurial-scm.org/D12250
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Thu, 03 Mar 2022 07:59:42 -0800 |
parents | df56e6bd37f6 |
children | 968b29a5a7fc |
files | mercurial/pycompat.py |
diffstat | 1 files changed, 286 insertions(+), 342 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/pycompat.py Tue Mar 01 20:29:03 2022 -0800 +++ b/mercurial/pycompat.py Thu Mar 03 07:59:42 2022 -0800 @@ -11,21 +11,26 @@ from __future__ import absolute_import import builtins +import codecs import concurrent.futures as futures +import functools import getopt import http.client as httplib import http.cookiejar as cookielib import inspect +import io import json import os import pickle import queue import shlex import socketserver +import struct import sys import tempfile import xmlrpc.client as xmlrpclib + ispy3 = sys.version_info[0] >= 3 ispypy = '__pypy__' in sys.builtin_module_names TYPE_CHECKING = False @@ -82,401 +87,340 @@ return _rapply(f, xs) -if ispy3: - import builtins - import codecs - import functools - import io - import struct - - if os.name == r'nt' and sys.version_info >= (3, 6): - # MBCS (or ANSI) filesystem encoding must be used as before. - # Otherwise non-ASCII filenames in existing repositories would be - # corrupted. - # This must be set once prior to any fsencode/fsdecode calls. - sys._enablelegacywindowsfsencoding() # pytype: disable=module-attr +if os.name == r'nt' and sys.version_info >= (3, 6): + # MBCS (or ANSI) filesystem encoding must be used as before. + # Otherwise non-ASCII filenames in existing repositories would be + # corrupted. + # This must be set once prior to any fsencode/fsdecode calls. + sys._enablelegacywindowsfsencoding() # pytype: disable=module-attr - fsencode = os.fsencode - fsdecode = os.fsdecode - oscurdir = os.curdir.encode('ascii') - oslinesep = os.linesep.encode('ascii') - osname = os.name.encode('ascii') - ospathsep = os.pathsep.encode('ascii') - ospardir = os.pardir.encode('ascii') - ossep = os.sep.encode('ascii') - osaltsep = os.altsep - if osaltsep: - osaltsep = osaltsep.encode('ascii') - osdevnull = os.devnull.encode('ascii') +fsencode = os.fsencode +fsdecode = os.fsdecode +oscurdir = os.curdir.encode('ascii') +oslinesep = os.linesep.encode('ascii') +osname = os.name.encode('ascii') +ospathsep = os.pathsep.encode('ascii') +ospardir = os.pardir.encode('ascii') +ossep = os.sep.encode('ascii') +osaltsep = os.altsep +if osaltsep: + osaltsep = osaltsep.encode('ascii') +osdevnull = os.devnull.encode('ascii') - sysplatform = sys.platform.encode('ascii') - sysexecutable = sys.executable - if sysexecutable: - sysexecutable = os.fsencode(sysexecutable) - bytesio = io.BytesIO - # TODO deprecate stringio name, as it is a lie on Python 3. - stringio = bytesio +sysplatform = sys.platform.encode('ascii') +sysexecutable = sys.executable +if sysexecutable: + sysexecutable = os.fsencode(sysexecutable) +bytesio = io.BytesIO +# TODO deprecate stringio name, as it is a lie on Python 3. +stringio = bytesio - def maplist(*args): - return list(map(*args)) + +def maplist(*args): + return list(map(*args)) - def rangelist(*args): - return list(range(*args)) + +def rangelist(*args): + return list(range(*args)) - def ziplist(*args): - return list(zip(*args)) + +def ziplist(*args): + return list(zip(*args)) + - rawinput = input - getargspec = inspect.getfullargspec +rawinput = input +getargspec = inspect.getfullargspec - long = int +long = int - if getattr(sys, 'argv', None) is not None: - # On POSIX, the char** argv array is converted to Python str using - # Py_DecodeLocale(). The inverse of this is Py_EncodeLocale(), which - # isn't directly callable from Python code. In practice, os.fsencode() - # can be used instead (this is recommended by Python's documentation - # for sys.argv). - # - # On Windows, the wchar_t **argv is passed into the interpreter as-is. - # Like POSIX, we need to emulate what Py_EncodeLocale() would do. But - # there's an additional wrinkle. What we really want to access is the - # ANSI codepage representation of the arguments, as this is what - # `int main()` would receive if Python 3 didn't define `int wmain()` - # (this is how Python 2 worked). To get that, we encode with the mbcs - # encoding, which will pass CP_ACP to the underlying Windows API to - # produce bytes. - if os.name == r'nt': - sysargv = [a.encode("mbcs", "ignore") for a in sys.argv] - else: - sysargv = [fsencode(a) for a in sys.argv] +if getattr(sys, 'argv', None) is not None: + # On POSIX, the char** argv array is converted to Python str using + # Py_DecodeLocale(). The inverse of this is Py_EncodeLocale(), which + # isn't directly callable from Python code. In practice, os.fsencode() + # can be used instead (this is recommended by Python's documentation + # for sys.argv). + # + # On Windows, the wchar_t **argv is passed into the interpreter as-is. + # Like POSIX, we need to emulate what Py_EncodeLocale() would do. But + # there's an additional wrinkle. What we really want to access is the + # ANSI codepage representation of the arguments, as this is what + # `int main()` would receive if Python 3 didn't define `int wmain()` + # (this is how Python 2 worked). To get that, we encode with the mbcs + # encoding, which will pass CP_ACP to the underlying Windows API to + # produce bytes. + if os.name == r'nt': + sysargv = [a.encode("mbcs", "ignore") for a in sys.argv] + else: + sysargv = [fsencode(a) for a in sys.argv] - bytechr = struct.Struct('>B').pack - byterepr = b'%r'.__mod__ - - class bytestr(bytes): - """A bytes which mostly acts as a Python 2 str +bytechr = struct.Struct('>B').pack +byterepr = b'%r'.__mod__ - >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1) - ('', 'foo', 'ascii', '1') - >>> s = bytestr(b'foo') - >>> assert s is bytestr(s) - __bytes__() should be called if provided: +class bytestr(bytes): + """A bytes which mostly acts as a Python 2 str - >>> class bytesable(object): - ... def __bytes__(self): - ... return b'bytes' - >>> bytestr(bytesable()) - 'bytes' + >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1) + ('', 'foo', 'ascii', '1') + >>> s = bytestr(b'foo') + >>> assert s is bytestr(s) + + __bytes__() should be called if provided: - There's no implicit conversion from non-ascii str as its encoding is - unknown: + >>> class bytesable(object): + ... def __bytes__(self): + ... return b'bytes' + >>> bytestr(bytesable()) + 'bytes' - >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS - Traceback (most recent call last): - ... - UnicodeEncodeError: ... - - Comparison between bytestr and bytes should work: + There's no implicit conversion from non-ascii str as its encoding is + unknown: - >>> assert bytestr(b'foo') == b'foo' - >>> assert b'foo' == bytestr(b'foo') - >>> assert b'f' in bytestr(b'foo') - >>> assert bytestr(b'f') in b'foo' + >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + UnicodeEncodeError: ... - Sliced elements should be bytes, not integer: + Comparison between bytestr and bytes should work: - >>> s[1], s[:2] - (b'o', b'fo') - >>> list(s), list(reversed(s)) - ([b'f', b'o', b'o'], [b'o', b'o', b'f']) - - As bytestr type isn't propagated across operations, you need to cast - bytes to bytestr explicitly: + >>> assert bytestr(b'foo') == b'foo' + >>> assert b'foo' == bytestr(b'foo') + >>> assert b'f' in bytestr(b'foo') + >>> assert bytestr(b'f') in b'foo' - >>> s = bytestr(b'foo').upper() - >>> t = bytestr(s) - >>> s[0], t[0] - (70, b'F') + Sliced elements should be bytes, not integer: - Be careful to not pass a bytestr object to a function which expects - bytearray-like behavior. + >>> s[1], s[:2] + (b'o', b'fo') + >>> list(s), list(reversed(s)) + ([b'f', b'o', b'o'], [b'o', b'o', b'f']) - >>> t = bytes(t) # cast to bytes - >>> assert type(t) is bytes - """ + As bytestr type isn't propagated across operations, you need to cast + bytes to bytestr explicitly: - # Trick pytype into not demanding Iterable[int] be passed to __new__(), - # since the appropriate bytes format is done internally. - # - # https://github.com/google/pytype/issues/500 - if TYPE_CHECKING: + >>> s = bytestr(b'foo').upper() + >>> t = bytestr(s) + >>> s[0], t[0] + (70, b'F') - def __init__(self, s=b''): - pass + Be careful to not pass a bytestr object to a function which expects + bytearray-like behavior. + + >>> t = bytes(t) # cast to bytes + >>> assert type(t) is bytes + """ - def __new__(cls, s=b''): - if isinstance(s, bytestr): - return s - if not isinstance( - s, (bytes, bytearray) - ) and not hasattr( # hasattr-py3-only - s, u'__bytes__' - ): - s = str(s).encode('ascii') - return bytes.__new__(cls, s) + # Trick pytype into not demanding Iterable[int] be passed to __new__(), + # since the appropriate bytes format is done internally. + # + # https://github.com/google/pytype/issues/500 + if TYPE_CHECKING: - def __getitem__(self, key): - s = bytes.__getitem__(self, key) - if not isinstance(s, bytes): - s = bytechr(s) + def __init__(self, s=b''): + pass + + def __new__(cls, s=b''): + if isinstance(s, bytestr): return s - - def __iter__(self): - return iterbytestr(bytes.__iter__(self)) - - def __repr__(self): - return bytes.__repr__(self)[1:] # drop b'' + if not isinstance( + s, (bytes, bytearray) + ) and not hasattr( # hasattr-py3-only + s, u'__bytes__' + ): + s = str(s).encode('ascii') + return bytes.__new__(cls, s) - def iterbytestr(s): - """Iterate bytes as if it were a str object of Python 2""" - return map(bytechr, s) - - def maybebytestr(s): - """Promote bytes to bytestr""" - if isinstance(s, bytes): - return bytestr(s) + def __getitem__(self, key): + s = bytes.__getitem__(self, key) + if not isinstance(s, bytes): + s = bytechr(s) return s - def sysbytes(s): - """Convert an internal str (e.g. keyword, __doc__) back to bytes + def __iter__(self): + return iterbytestr(bytes.__iter__(self)) + + def __repr__(self): + return bytes.__repr__(self)[1:] # drop b'' + - This never raises UnicodeEncodeError, but only ASCII characters - can be round-trip by sysstr(sysbytes(s)). - """ - if isinstance(s, bytes): - return s - return s.encode('utf-8') +def iterbytestr(s): + """Iterate bytes as if it were a str object of Python 2""" + return map(bytechr, s) + - def sysstr(s): - """Return a keyword str to be passed to Python functions such as - getattr() and str.encode() +def maybebytestr(s): + """Promote bytes to bytestr""" + if isinstance(s, bytes): + return bytestr(s) + return s + - 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('latin-1') +def sysbytes(s): + """Convert an internal str (e.g. keyword, __doc__) back to bytes + + This never raises UnicodeEncodeError, but only ASCII characters + can be round-trip by sysstr(sysbytes(s)). + """ + if isinstance(s, bytes): + return s + return s.encode('utf-8') + - def strurl(url): - """Converts a bytes url back to str""" - if isinstance(url, bytes): - return url.decode('ascii') - return url +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('latin-1') + - def bytesurl(url): - """Converts a str url to bytes by encoding in ascii""" - if isinstance(url, str): - return url.encode('ascii') - return url +def strurl(url): + """Converts a bytes url back to str""" + if isinstance(url, bytes): + return url.decode('ascii') + return url + - def raisewithtb(exc, tb): - """Raise exception with the given traceback""" - raise exc.with_traceback(tb) +def bytesurl(url): + """Converts a str url to bytes by encoding in ascii""" + if isinstance(url, str): + return url.encode('ascii') + return url - def getdoc(obj): - """Get docstring as bytes; may be None so gettext() won't confuse it - with _('')""" - doc = getattr(obj, '__doc__', None) - if doc is None: - return doc - return sysbytes(doc) + +def raisewithtb(exc, tb): + """Raise exception with the given traceback""" + raise exc.with_traceback(tb) + - def _wrapattrfunc(f): - @functools.wraps(f) - def w(object, name, *args): - return f(object, sysstr(name), *args) +def getdoc(obj): + """Get docstring as bytes; may be None so gettext() won't confuse it + with _('')""" + doc = getattr(obj, '__doc__', None) + if doc is None: + return doc + return sysbytes(doc) - return w + +def _wrapattrfunc(f): + @functools.wraps(f) + def w(object, name, *args): + return f(object, sysstr(name), *args) - # these wrappers are automagically imported by hgloader - delattr = _wrapattrfunc(builtins.delattr) - getattr = _wrapattrfunc(builtins.getattr) - hasattr = _wrapattrfunc(builtins.hasattr) - setattr = _wrapattrfunc(builtins.setattr) - xrange = builtins.range - unicode = str + return w + - def open(name, mode=b'r', buffering=-1, encoding=None): - return builtins.open(name, sysstr(mode), buffering, encoding) +# these wrappers are automagically imported by hgloader +delattr = _wrapattrfunc(builtins.delattr) +getattr = _wrapattrfunc(builtins.getattr) +hasattr = _wrapattrfunc(builtins.hasattr) +setattr = _wrapattrfunc(builtins.setattr) +xrange = builtins.range +unicode = str - safehasattr = _wrapattrfunc(builtins.hasattr) + +def open(name, mode=b'r', buffering=-1, encoding=None): + return builtins.open(name, sysstr(mode), buffering, encoding) + - def _getoptbwrapper(orig, args, shortlist, namelist): - """ - Takes bytes arguments, converts them to unicode, pass them to - getopt.getopt(), convert the returned values back to bytes and then - return them for Python 3 compatibility as getopt.getopt() don't accepts - bytes on Python 3. - """ - 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 = orig(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 +safehasattr = _wrapattrfunc(builtins.hasattr) + + +def _getoptbwrapper(orig, args, shortlist, namelist): + """ + Takes bytes arguments, converts them to unicode, pass them to + getopt.getopt(), convert the returned values back to bytes and then + return them for Python 3 compatibility as getopt.getopt() don't accepts + bytes on Python 3. + """ + 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 = orig(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 + - def strkwargs(dic): - """ - Converts the keys of a python dictonary to str i.e. unicodes so that - they can be passed as keyword arguments as dictionaries with bytes keys - can't be passed as keyword arguments to functions on Python 3. - """ - dic = {k.decode('latin-1'): v for k, v in dic.items()} - return dic +def strkwargs(dic): + """ + Converts the keys of a python dictonary to str i.e. unicodes so that + they can be passed as keyword arguments as dictionaries with bytes keys + can't be passed as keyword arguments to functions on Python 3. + """ + dic = {k.decode('latin-1'): v for k, v in dic.items()} + return dic - def byteskwargs(dic): - """ - Converts keys of python dictionaries to bytes as they were converted to - str to pass that dictonary as a keyword argument on Python 3. - """ - dic = {k.encode('latin-1'): v for k, v in dic.items()} - return dic - # TODO: handle shlex.shlex(). - def shlexsplit(s, comments=False, posix=True): - """ - Takes bytes argument, convert it to str i.e. unicodes, pass that into - shlex.split(), convert the returned value to bytes and return that for - Python 3 compatibility as shelx.split() don't accept bytes on Python 3. - """ - ret = shlex.split(s.decode('latin-1'), comments, posix) - return [a.encode('latin-1') for a in ret] +def byteskwargs(dic): + """ + Converts keys of python dictionaries to bytes as they were converted to + str to pass that dictonary as a keyword argument on Python 3. + """ + dic = {k.encode('latin-1'): v for k, v in dic.items()} + return dic + - iteritems = lambda x: x.items() - itervalues = lambda x: x.values() +# TODO: handle shlex.shlex(). +def shlexsplit(s, comments=False, posix=True): + """ + Takes bytes argument, convert it to str i.e. unicodes, pass that into + shlex.split(), convert the returned value to bytes and return that for + Python 3 compatibility as shelx.split() don't accept bytes on Python 3. + """ + ret = shlex.split(s.decode('latin-1'), comments, posix) + return [a.encode('latin-1') for a in ret] - # Python 3.5's json.load and json.loads require str. We polyfill its - # code for detecting encoding from bytes. - if sys.version_info[0:2] < (3, 6): - def _detect_encoding(b): - bstartswith = b.startswith - if bstartswith((codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE)): - return 'utf-32' - if bstartswith((codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE)): - return 'utf-16' - if bstartswith(codecs.BOM_UTF8): - return 'utf-8-sig' +iteritems = lambda x: x.items() +itervalues = lambda x: x.values() + +# Python 3.5's json.load and json.loads require str. We polyfill its +# code for detecting encoding from bytes. +if sys.version_info[0:2] < (3, 6): + + def _detect_encoding(b): + bstartswith = b.startswith + if bstartswith((codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE)): + return 'utf-32' + if bstartswith((codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE)): + return 'utf-16' + if bstartswith(codecs.BOM_UTF8): + return 'utf-8-sig' - if len(b) >= 4: - if not b[0]: - # 00 00 -- -- - utf-32-be - # 00 XX -- -- - utf-16-be - return 'utf-16-be' if b[1] else 'utf-32-be' - if not b[1]: - # XX 00 00 00 - utf-32-le - # XX 00 00 XX - utf-16-le - # XX 00 XX -- - utf-16-le - return 'utf-16-le' if b[2] or b[3] else 'utf-32-le' - elif len(b) == 2: - if not b[0]: - # 00 XX - utf-16-be - return 'utf-16-be' - if not b[1]: - # XX 00 - utf-16-le - return 'utf-16-le' - # default - return 'utf-8' + if len(b) >= 4: + if not b[0]: + # 00 00 -- -- - utf-32-be + # 00 XX -- -- - utf-16-be + return 'utf-16-be' if b[1] else 'utf-32-be' + if not b[1]: + # XX 00 00 00 - utf-32-le + # XX 00 00 XX - utf-16-le + # XX 00 XX -- - utf-16-le + return 'utf-16-le' if b[2] or b[3] else 'utf-32-le' + elif len(b) == 2: + if not b[0]: + # 00 XX - utf-16-be + return 'utf-16-be' + if not b[1]: + # XX 00 - utf-16-le + return 'utf-16-le' + # default + return 'utf-8' - def json_loads(s, *args, **kwargs): - if isinstance(s, (bytes, bytearray)): - s = s.decode(_detect_encoding(s), 'surrogatepass') + def json_loads(s, *args, **kwargs): + if isinstance(s, (bytes, bytearray)): + s = s.decode(_detect_encoding(s), 'surrogatepass') - return json.loads(s, *args, **kwargs) + return json.loads(s, *args, **kwargs) - else: - json_loads = json.loads else: - import cStringIO - - xrange = xrange - unicode = unicode - bytechr = chr - byterepr = repr - bytestr = str - iterbytestr = iter - maybebytestr = identity - sysbytes = identity - sysstr = identity - strurl = identity - bytesurl = identity - open = open - delattr = delattr - getattr = getattr - hasattr = hasattr - setattr = setattr - - # this can't be parsed on Python 3 - exec(b'def raisewithtb(exc, tb):\n raise exc, None, tb\n') - - def fsencode(filename): - """ - 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. - """ - if isinstance(filename, str): - return filename - else: - raise TypeError("expect str, not %s" % type(filename).__name__) - - # 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. - fsdecode = identity - - def getdoc(obj): - return getattr(obj, '__doc__', None) - - _notset = object() - - def safehasattr(thing, attr): - return getattr(thing, attr, _notset) is not _notset - - def _getoptbwrapper(orig, args, shortlist, namelist): - return orig(args, shortlist, namelist) - - strkwargs = identity - byteskwargs = identity - - oscurdir = os.curdir - oslinesep = os.linesep - osname = os.name - ospathsep = os.pathsep - ospardir = os.pardir - ossep = os.sep - osaltsep = os.altsep - osdevnull = os.devnull - long = long - if getattr(sys, 'argv', None) is not None: - sysargv = sys.argv - sysplatform = sys.platform - sysexecutable = sys.executable - shlexsplit = shlex.split - bytesio = cStringIO.StringIO - stringio = bytesio - maplist = map - rangelist = range - ziplist = zip - rawinput = raw_input - getargspec = inspect.getargspec - iteritems = lambda x: x.iteritems() - itervalues = lambda x: x.itervalues() json_loads = json.loads isjython = sysplatform.startswith(b'java')