Mercurial > hg-stable
changeset 49902:c5a06cc37401
typing: add type hints to most mercurial/pycompat.py functions
The `rapply` methods are left out because it's not `rapply(f, xs: _T0) -> _T0`
as I first thought- it's used somewhere to walk a collection and convert between
bytes and str.
Also, the `open()` call is partially untyped because I'm not sure what its
purpose is at this point- both the name and mode can be either bytes or str as
it is currently constituted. It might make sense to assert that the file is
being opened in binary mode (like `namedtempfile()`) and cast the result to
`BinaryIO`, but that shouldn't be smuggled in with these other changes. The
return is currently typed as `Any` because something suddenly got smarter and a
few uses in util.py (like readfile()) suddenly think it returns `IO[str]`
instead of `IO[bytes]` (BinaryIO), and it flags the type mismatch there.
author | Matt Harbison <matt_harbison@yahoo.com> |
---|---|
date | Thu, 15 Dec 2022 01:05:27 -0500 |
parents | 9eb69fa5a783 |
children | 7a4143428db7 |
files | mercurial/pycompat.py |
diffstat | 1 files changed, 65 insertions(+), 19 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/pycompat.py Wed Dec 14 22:27:22 2022 -0500 +++ b/mercurial/pycompat.py Thu Dec 15 01:05:27 2022 -0500 @@ -29,12 +29,22 @@ import xmlrpc.client as xmlrpclib from typing import ( + Any, + AnyStr, + BinaryIO, + Dict, Iterable, Iterator, List, + Mapping, + NoReturn, Optional, + Sequence, + Tuple, Type, TypeVar, + cast, + overload, ) ispy3 = sys.version_info[0] >= 3 @@ -46,6 +56,8 @@ TYPE_CHECKING = typing.TYPE_CHECKING +_GetOptResult = Tuple[List[Tuple[bytes, bytes]], List[bytes]] +_T0 = TypeVar('_T0') _Tbytestr = TypeVar('_Tbytestr', bound='bytestr') @@ -56,7 +68,7 @@ FileNotFoundError = builtins.FileNotFoundError -def identity(a): +def identity(a: _T0) -> _T0: return a @@ -250,6 +262,17 @@ return map(bytechr, s) +if TYPE_CHECKING: + + @overload + def maybebytestr(s: bytes) -> bytestr: + ... + + @overload + def maybebytestr(s: _T0) -> _T0: + ... + + def maybebytestr(s): """Promote bytes to bytestr""" if isinstance(s, bytes): @@ -257,7 +280,7 @@ return s -def sysbytes(s): +def sysbytes(s: AnyStr) -> bytes: """Convert an internal str (e.g. keyword, __doc__) back to bytes This never raises UnicodeEncodeError, but only ASCII characters @@ -268,7 +291,7 @@ return s.encode('utf-8') -def sysstr(s): +def sysstr(s: AnyStr) -> str: """Return a keyword str to be passed to Python functions such as getattr() and str.encode() @@ -281,26 +304,26 @@ return s.decode('latin-1') -def strurl(url): +def strurl(url: AnyStr) -> str: """Converts a bytes url back to str""" if isinstance(url, bytes): return url.decode('ascii') return url -def bytesurl(url): +def bytesurl(url: AnyStr) -> bytes: """Converts a str url to bytes by encoding in ascii""" if isinstance(url, str): return url.encode('ascii') return url -def raisewithtb(exc, tb): +def raisewithtb(exc: BaseException, tb) -> NoReturn: """Raise exception with the given traceback""" raise exc.with_traceback(tb) -def getdoc(obj): +def getdoc(obj: object) -> Optional[bytes]: """Get docstring as bytes; may be None so gettext() won't confuse it with _('')""" doc = builtins.getattr(obj, '__doc__', None) @@ -326,14 +349,22 @@ unicode = str -def open(name, mode=b'r', buffering=-1, encoding=None): +def open( + name, + mode: AnyStr = b'r', + buffering: int = -1, + encoding: Optional[str] = None, +) -> Any: + # TODO: assert binary mode, and cast result to BinaryIO? return builtins.open(name, sysstr(mode), buffering, encoding) safehasattr = _wrapattrfunc(builtins.hasattr) -def _getoptbwrapper(orig, args, shortlist, namelist): +def _getoptbwrapper( + orig, args: Sequence[bytes], shortlist: bytes, namelist: Sequence[bytes] +) -> _GetOptResult: """ Takes bytes arguments, converts them to unicode, pass them to getopt.getopt(), convert the returned values back to bytes and then @@ -349,7 +380,7 @@ return opts, args -def strkwargs(dic): +def strkwargs(dic: Mapping[bytes, _T0]) -> Dict[str, _T0]: """ 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 @@ -359,7 +390,7 @@ return dic -def byteskwargs(dic): +def byteskwargs(dic: Mapping[str, _T0]) -> Dict[bytes, _T0]: """ Converts keys of python dictionaries to bytes as they were converted to str to pass that dictonary as a keyword argument on Python 3. @@ -369,7 +400,9 @@ # TODO: handle shlex.shlex(). -def shlexsplit(s, comments=False, posix=True): +def shlexsplit( + s: bytes, comments: bool = False, posix: bool = True +) -> List[bytes]: """ 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 @@ -392,38 +425,51 @@ iswindows: bool = osname == b'nt' -def getoptb(args, shortlist, namelist): +def getoptb( + args: Sequence[bytes], shortlist: bytes, namelist: Sequence[bytes] +) -> _GetOptResult: return _getoptbwrapper(getopt.getopt, args, shortlist, namelist) -def gnugetoptb(args, shortlist, namelist): +def gnugetoptb( + args: Sequence[bytes], shortlist: bytes, namelist: Sequence[bytes] +) -> _GetOptResult: return _getoptbwrapper(getopt.gnu_getopt, args, shortlist, namelist) -def mkdtemp(suffix=b'', prefix=b'tmp', dir=None): +def mkdtemp( + suffix: bytes = b'', prefix: bytes = b'tmp', dir: Optional[bytes] = None +) -> bytes: return tempfile.mkdtemp(suffix, prefix, dir) # text=True is not supported; use util.from/tonativeeol() instead -def mkstemp(suffix=b'', prefix=b'tmp', dir=None): +def mkstemp( + suffix: bytes = b'', prefix: bytes = b'tmp', dir: Optional[bytes] = None +) -> Tuple[int, bytes]: return tempfile.mkstemp(suffix, prefix, dir) # TemporaryFile does not support an "encoding=" argument on python2. # This wrapper file are always open in byte mode. -def unnamedtempfile(mode=None, *args, **kwargs): +def unnamedtempfile(mode: Optional[bytes] = None, *args, **kwargs) -> BinaryIO: if mode is None: mode = 'w+b' else: mode = sysstr(mode) assert 'b' in mode - return tempfile.TemporaryFile(mode, *args, **kwargs) + return cast(BinaryIO, tempfile.TemporaryFile(mode, *args, **kwargs)) # NamedTemporaryFile does not support an "encoding=" argument on python2. # This wrapper file are always open in byte mode. def namedtempfile( - mode=b'w+b', bufsize=-1, suffix=b'', prefix=b'tmp', dir=None, delete=True + mode: bytes = b'w+b', + bufsize: int = -1, + suffix: bytes = b'', + prefix: bytes = b'tmp', + dir: Optional[bytes] = None, + delete: bool = True, ): mode = sysstr(mode) assert 'b' in mode