Mercurial > public > mercurial-scm > hg
comparison mercurial/pycompat.py @ 49807: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 | 55d45d0de4e7 |
children | b900f40c343e |
comparison
equal
deleted
inserted
replaced
49806:9eb69fa5a783 | 49807:c5a06cc37401 |
---|---|
27 import sys | 27 import sys |
28 import tempfile | 28 import tempfile |
29 import xmlrpc.client as xmlrpclib | 29 import xmlrpc.client as xmlrpclib |
30 | 30 |
31 from typing import ( | 31 from typing import ( |
32 Any, | |
33 AnyStr, | |
34 BinaryIO, | |
35 Dict, | |
32 Iterable, | 36 Iterable, |
33 Iterator, | 37 Iterator, |
34 List, | 38 List, |
39 Mapping, | |
40 NoReturn, | |
35 Optional, | 41 Optional, |
42 Sequence, | |
43 Tuple, | |
36 Type, | 44 Type, |
37 TypeVar, | 45 TypeVar, |
46 cast, | |
47 overload, | |
38 ) | 48 ) |
39 | 49 |
40 ispy3 = sys.version_info[0] >= 3 | 50 ispy3 = sys.version_info[0] >= 3 |
41 ispypy = '__pypy__' in sys.builtin_module_names | 51 ispypy = '__pypy__' in sys.builtin_module_names |
42 TYPE_CHECKING = False | 52 TYPE_CHECKING = False |
44 if not globals(): # hide this from non-pytype users | 54 if not globals(): # hide this from non-pytype users |
45 import typing | 55 import typing |
46 | 56 |
47 TYPE_CHECKING = typing.TYPE_CHECKING | 57 TYPE_CHECKING = typing.TYPE_CHECKING |
48 | 58 |
59 _GetOptResult = Tuple[List[Tuple[bytes, bytes]], List[bytes]] | |
60 _T0 = TypeVar('_T0') | |
49 _Tbytestr = TypeVar('_Tbytestr', bound='bytestr') | 61 _Tbytestr = TypeVar('_Tbytestr', bound='bytestr') |
50 | 62 |
51 | 63 |
52 def future_set_exception_info(f, exc_info): | 64 def future_set_exception_info(f, exc_info): |
53 f.set_exception(exc_info[0]) | 65 f.set_exception(exc_info[0]) |
54 | 66 |
55 | 67 |
56 FileNotFoundError = builtins.FileNotFoundError | 68 FileNotFoundError = builtins.FileNotFoundError |
57 | 69 |
58 | 70 |
59 def identity(a): | 71 def identity(a: _T0) -> _T0: |
60 return a | 72 return a |
61 | 73 |
62 | 74 |
63 def _rapply(f, xs): | 75 def _rapply(f, xs): |
64 if xs is None: | 76 if xs is None: |
248 def iterbytestr(s: Iterable[int]) -> Iterator[bytes]: | 260 def iterbytestr(s: Iterable[int]) -> Iterator[bytes]: |
249 """Iterate bytes as if it were a str object of Python 2""" | 261 """Iterate bytes as if it were a str object of Python 2""" |
250 return map(bytechr, s) | 262 return map(bytechr, s) |
251 | 263 |
252 | 264 |
265 if TYPE_CHECKING: | |
266 | |
267 @overload | |
268 def maybebytestr(s: bytes) -> bytestr: | |
269 ... | |
270 | |
271 @overload | |
272 def maybebytestr(s: _T0) -> _T0: | |
273 ... | |
274 | |
275 | |
253 def maybebytestr(s): | 276 def maybebytestr(s): |
254 """Promote bytes to bytestr""" | 277 """Promote bytes to bytestr""" |
255 if isinstance(s, bytes): | 278 if isinstance(s, bytes): |
256 return bytestr(s) | 279 return bytestr(s) |
257 return s | 280 return s |
258 | 281 |
259 | 282 |
260 def sysbytes(s): | 283 def sysbytes(s: AnyStr) -> bytes: |
261 """Convert an internal str (e.g. keyword, __doc__) back to bytes | 284 """Convert an internal str (e.g. keyword, __doc__) back to bytes |
262 | 285 |
263 This never raises UnicodeEncodeError, but only ASCII characters | 286 This never raises UnicodeEncodeError, but only ASCII characters |
264 can be round-trip by sysstr(sysbytes(s)). | 287 can be round-trip by sysstr(sysbytes(s)). |
265 """ | 288 """ |
266 if isinstance(s, bytes): | 289 if isinstance(s, bytes): |
267 return s | 290 return s |
268 return s.encode('utf-8') | 291 return s.encode('utf-8') |
269 | 292 |
270 | 293 |
271 def sysstr(s): | 294 def sysstr(s: AnyStr) -> str: |
272 """Return a keyword str to be passed to Python functions such as | 295 """Return a keyword str to be passed to Python functions such as |
273 getattr() and str.encode() | 296 getattr() and str.encode() |
274 | 297 |
275 This never raises UnicodeDecodeError. Non-ascii characters are | 298 This never raises UnicodeDecodeError. Non-ascii characters are |
276 considered invalid and mapped to arbitrary but unique code points | 299 considered invalid and mapped to arbitrary but unique code points |
279 if isinstance(s, builtins.str): | 302 if isinstance(s, builtins.str): |
280 return s | 303 return s |
281 return s.decode('latin-1') | 304 return s.decode('latin-1') |
282 | 305 |
283 | 306 |
284 def strurl(url): | 307 def strurl(url: AnyStr) -> str: |
285 """Converts a bytes url back to str""" | 308 """Converts a bytes url back to str""" |
286 if isinstance(url, bytes): | 309 if isinstance(url, bytes): |
287 return url.decode('ascii') | 310 return url.decode('ascii') |
288 return url | 311 return url |
289 | 312 |
290 | 313 |
291 def bytesurl(url): | 314 def bytesurl(url: AnyStr) -> bytes: |
292 """Converts a str url to bytes by encoding in ascii""" | 315 """Converts a str url to bytes by encoding in ascii""" |
293 if isinstance(url, str): | 316 if isinstance(url, str): |
294 return url.encode('ascii') | 317 return url.encode('ascii') |
295 return url | 318 return url |
296 | 319 |
297 | 320 |
298 def raisewithtb(exc, tb): | 321 def raisewithtb(exc: BaseException, tb) -> NoReturn: |
299 """Raise exception with the given traceback""" | 322 """Raise exception with the given traceback""" |
300 raise exc.with_traceback(tb) | 323 raise exc.with_traceback(tb) |
301 | 324 |
302 | 325 |
303 def getdoc(obj): | 326 def getdoc(obj: object) -> Optional[bytes]: |
304 """Get docstring as bytes; may be None so gettext() won't confuse it | 327 """Get docstring as bytes; may be None so gettext() won't confuse it |
305 with _('')""" | 328 with _('')""" |
306 doc = builtins.getattr(obj, '__doc__', None) | 329 doc = builtins.getattr(obj, '__doc__', None) |
307 if doc is None: | 330 if doc is None: |
308 return doc | 331 return doc |
324 setattr = _wrapattrfunc(builtins.setattr) | 347 setattr = _wrapattrfunc(builtins.setattr) |
325 xrange = builtins.range | 348 xrange = builtins.range |
326 unicode = str | 349 unicode = str |
327 | 350 |
328 | 351 |
329 def open(name, mode=b'r', buffering=-1, encoding=None): | 352 def open( |
353 name, | |
354 mode: AnyStr = b'r', | |
355 buffering: int = -1, | |
356 encoding: Optional[str] = None, | |
357 ) -> Any: | |
358 # TODO: assert binary mode, and cast result to BinaryIO? | |
330 return builtins.open(name, sysstr(mode), buffering, encoding) | 359 return builtins.open(name, sysstr(mode), buffering, encoding) |
331 | 360 |
332 | 361 |
333 safehasattr = _wrapattrfunc(builtins.hasattr) | 362 safehasattr = _wrapattrfunc(builtins.hasattr) |
334 | 363 |
335 | 364 |
336 def _getoptbwrapper(orig, args, shortlist, namelist): | 365 def _getoptbwrapper( |
366 orig, args: Sequence[bytes], shortlist: bytes, namelist: Sequence[bytes] | |
367 ) -> _GetOptResult: | |
337 """ | 368 """ |
338 Takes bytes arguments, converts them to unicode, pass them to | 369 Takes bytes arguments, converts them to unicode, pass them to |
339 getopt.getopt(), convert the returned values back to bytes and then | 370 getopt.getopt(), convert the returned values back to bytes and then |
340 return them for Python 3 compatibility as getopt.getopt() don't accepts | 371 return them for Python 3 compatibility as getopt.getopt() don't accepts |
341 bytes on Python 3. | 372 bytes on Python 3. |
347 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1')) for a in opts] | 378 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1')) for a in opts] |
348 args = [a.encode('latin-1') for a in args] | 379 args = [a.encode('latin-1') for a in args] |
349 return opts, args | 380 return opts, args |
350 | 381 |
351 | 382 |
352 def strkwargs(dic): | 383 def strkwargs(dic: Mapping[bytes, _T0]) -> Dict[str, _T0]: |
353 """ | 384 """ |
354 Converts the keys of a python dictonary to str i.e. unicodes so that | 385 Converts the keys of a python dictonary to str i.e. unicodes so that |
355 they can be passed as keyword arguments as dictionaries with bytes keys | 386 they can be passed as keyword arguments as dictionaries with bytes keys |
356 can't be passed as keyword arguments to functions on Python 3. | 387 can't be passed as keyword arguments to functions on Python 3. |
357 """ | 388 """ |
358 dic = {k.decode('latin-1'): v for k, v in dic.items()} | 389 dic = {k.decode('latin-1'): v for k, v in dic.items()} |
359 return dic | 390 return dic |
360 | 391 |
361 | 392 |
362 def byteskwargs(dic): | 393 def byteskwargs(dic: Mapping[str, _T0]) -> Dict[bytes, _T0]: |
363 """ | 394 """ |
364 Converts keys of python dictionaries to bytes as they were converted to | 395 Converts keys of python dictionaries to bytes as they were converted to |
365 str to pass that dictonary as a keyword argument on Python 3. | 396 str to pass that dictonary as a keyword argument on Python 3. |
366 """ | 397 """ |
367 dic = {k.encode('latin-1'): v for k, v in dic.items()} | 398 dic = {k.encode('latin-1'): v for k, v in dic.items()} |
368 return dic | 399 return dic |
369 | 400 |
370 | 401 |
371 # TODO: handle shlex.shlex(). | 402 # TODO: handle shlex.shlex(). |
372 def shlexsplit(s, comments=False, posix=True): | 403 def shlexsplit( |
404 s: bytes, comments: bool = False, posix: bool = True | |
405 ) -> List[bytes]: | |
373 """ | 406 """ |
374 Takes bytes argument, convert it to str i.e. unicodes, pass that into | 407 Takes bytes argument, convert it to str i.e. unicodes, pass that into |
375 shlex.split(), convert the returned value to bytes and return that for | 408 shlex.split(), convert the returned value to bytes and return that for |
376 Python 3 compatibility as shelx.split() don't accept bytes on Python 3. | 409 Python 3 compatibility as shelx.split() don't accept bytes on Python 3. |
377 """ | 410 """ |
390 islinux: bool = sysplatform.startswith(b'linux') | 423 islinux: bool = sysplatform.startswith(b'linux') |
391 isposix: bool = osname == b'posix' | 424 isposix: bool = osname == b'posix' |
392 iswindows: bool = osname == b'nt' | 425 iswindows: bool = osname == b'nt' |
393 | 426 |
394 | 427 |
395 def getoptb(args, shortlist, namelist): | 428 def getoptb( |
429 args: Sequence[bytes], shortlist: bytes, namelist: Sequence[bytes] | |
430 ) -> _GetOptResult: | |
396 return _getoptbwrapper(getopt.getopt, args, shortlist, namelist) | 431 return _getoptbwrapper(getopt.getopt, args, shortlist, namelist) |
397 | 432 |
398 | 433 |
399 def gnugetoptb(args, shortlist, namelist): | 434 def gnugetoptb( |
435 args: Sequence[bytes], shortlist: bytes, namelist: Sequence[bytes] | |
436 ) -> _GetOptResult: | |
400 return _getoptbwrapper(getopt.gnu_getopt, args, shortlist, namelist) | 437 return _getoptbwrapper(getopt.gnu_getopt, args, shortlist, namelist) |
401 | 438 |
402 | 439 |
403 def mkdtemp(suffix=b'', prefix=b'tmp', dir=None): | 440 def mkdtemp( |
441 suffix: bytes = b'', prefix: bytes = b'tmp', dir: Optional[bytes] = None | |
442 ) -> bytes: | |
404 return tempfile.mkdtemp(suffix, prefix, dir) | 443 return tempfile.mkdtemp(suffix, prefix, dir) |
405 | 444 |
406 | 445 |
407 # text=True is not supported; use util.from/tonativeeol() instead | 446 # text=True is not supported; use util.from/tonativeeol() instead |
408 def mkstemp(suffix=b'', prefix=b'tmp', dir=None): | 447 def mkstemp( |
448 suffix: bytes = b'', prefix: bytes = b'tmp', dir: Optional[bytes] = None | |
449 ) -> Tuple[int, bytes]: | |
409 return tempfile.mkstemp(suffix, prefix, dir) | 450 return tempfile.mkstemp(suffix, prefix, dir) |
410 | 451 |
411 | 452 |
412 # TemporaryFile does not support an "encoding=" argument on python2. | 453 # TemporaryFile does not support an "encoding=" argument on python2. |
413 # This wrapper file are always open in byte mode. | 454 # This wrapper file are always open in byte mode. |
414 def unnamedtempfile(mode=None, *args, **kwargs): | 455 def unnamedtempfile(mode: Optional[bytes] = None, *args, **kwargs) -> BinaryIO: |
415 if mode is None: | 456 if mode is None: |
416 mode = 'w+b' | 457 mode = 'w+b' |
417 else: | 458 else: |
418 mode = sysstr(mode) | 459 mode = sysstr(mode) |
419 assert 'b' in mode | 460 assert 'b' in mode |
420 return tempfile.TemporaryFile(mode, *args, **kwargs) | 461 return cast(BinaryIO, tempfile.TemporaryFile(mode, *args, **kwargs)) |
421 | 462 |
422 | 463 |
423 # NamedTemporaryFile does not support an "encoding=" argument on python2. | 464 # NamedTemporaryFile does not support an "encoding=" argument on python2. |
424 # This wrapper file are always open in byte mode. | 465 # This wrapper file are always open in byte mode. |
425 def namedtempfile( | 466 def namedtempfile( |
426 mode=b'w+b', bufsize=-1, suffix=b'', prefix=b'tmp', dir=None, delete=True | 467 mode: bytes = b'w+b', |
468 bufsize: int = -1, | |
469 suffix: bytes = b'', | |
470 prefix: bytes = b'tmp', | |
471 dir: Optional[bytes] = None, | |
472 delete: bool = True, | |
427 ): | 473 ): |
428 mode = sysstr(mode) | 474 mode = sysstr(mode) |
429 assert 'b' in mode | 475 assert 'b' in mode |
430 return tempfile.NamedTemporaryFile( | 476 return tempfile.NamedTemporaryFile( |
431 mode, bufsize, suffix=suffix, prefix=prefix, dir=dir, delete=delete | 477 mode, bufsize, suffix=suffix, prefix=prefix, dir=dir, delete=delete |