##// END OF EJS Templates
typing: add type hints to global variables in mercurial/pycompat.py...
Matt Harbison -
r50696:9cd32750 default
parent child Browse files
Show More
@@ -1,425 +1,426 b''
1 1 # pycompat.py - portability shim for python 3
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 """Mercurial portability shim for python 3.
7 7
8 8 This contains aliases to hide python version-specific details from the core.
9 9 """
10 10
11 11
12 12 import builtins
13 13 import codecs
14 14 import concurrent.futures as futures
15 15 import functools
16 16 import getopt
17 17 import http.client as httplib
18 18 import http.cookiejar as cookielib
19 19 import inspect
20 20 import io
21 21 import json
22 22 import os
23 23 import queue
24 24 import shlex
25 25 import socketserver
26 26 import struct
27 27 import sys
28 28 import tempfile
29 29 import xmlrpc.client as xmlrpclib
30 30
31 from typing import (
32 List,
33 Optional,
34 )
31 35
32 36 ispy3 = sys.version_info[0] >= 3
33 37 ispypy = '__pypy__' in sys.builtin_module_names
34 38 TYPE_CHECKING = False
35 39
36 40 if not globals(): # hide this from non-pytype users
37 41 import typing
38 42
39 43 TYPE_CHECKING = typing.TYPE_CHECKING
40 44
41 45
42 46 def future_set_exception_info(f, exc_info):
43 47 f.set_exception(exc_info[0])
44 48
45 49
46 50 FileNotFoundError = builtins.FileNotFoundError
47 51
48 52
49 53 def identity(a):
50 54 return a
51 55
52 56
53 57 def _rapply(f, xs):
54 58 if xs is None:
55 59 # assume None means non-value of optional data
56 60 return xs
57 61 if isinstance(xs, (list, set, tuple)):
58 62 return type(xs)(_rapply(f, x) for x in xs)
59 63 if isinstance(xs, dict):
60 64 return type(xs)((_rapply(f, k), _rapply(f, v)) for k, v in xs.items())
61 65 return f(xs)
62 66
63 67
64 68 def rapply(f, xs):
65 69 """Apply function recursively to every item preserving the data structure
66 70
67 71 >>> def f(x):
68 72 ... return 'f(%s)' % x
69 73 >>> rapply(f, None) is None
70 74 True
71 75 >>> rapply(f, 'a')
72 76 'f(a)'
73 77 >>> rapply(f, {'a'}) == {'f(a)'}
74 78 True
75 79 >>> rapply(f, ['a', 'b', None, {'c': 'd'}, []])
76 80 ['f(a)', 'f(b)', None, {'f(c)': 'f(d)'}, []]
77 81
78 82 >>> xs = [object()]
79 83 >>> rapply(identity, xs) is xs
80 84 True
81 85 """
82 86 if f is identity:
83 87 # fast path mainly for py2
84 88 return xs
85 89 return _rapply(f, xs)
86 90
87 91
88 92 if os.name == r'nt':
89 93 # MBCS (or ANSI) filesystem encoding must be used as before.
90 94 # Otherwise non-ASCII filenames in existing repositories would be
91 95 # corrupted.
92 96 # This must be set once prior to any fsencode/fsdecode calls.
93 97 sys._enablelegacywindowsfsencoding() # pytype: disable=module-attr
94 98
95 99 fsencode = os.fsencode
96 100 fsdecode = os.fsdecode
97 oscurdir = os.curdir.encode('ascii')
98 oslinesep = os.linesep.encode('ascii')
99 osname = os.name.encode('ascii')
100 ospathsep = os.pathsep.encode('ascii')
101 ospardir = os.pardir.encode('ascii')
102 ossep = os.sep.encode('ascii')
103 osaltsep = os.altsep
104 if osaltsep:
105 osaltsep = osaltsep.encode('ascii')
106 osdevnull = os.devnull.encode('ascii')
101 oscurdir: bytes = os.curdir.encode('ascii')
102 oslinesep: bytes = os.linesep.encode('ascii')
103 osname: bytes = os.name.encode('ascii')
104 ospathsep: bytes = os.pathsep.encode('ascii')
105 ospardir: bytes = os.pardir.encode('ascii')
106 ossep: bytes = os.sep.encode('ascii')
107 osaltsep: Optional[bytes] = os.altsep.encode('ascii') if os.altsep else None
108 osdevnull: bytes = os.devnull.encode('ascii')
107 109
108 sysplatform = sys.platform.encode('ascii')
109 sysexecutable = sys.executable
110 if sysexecutable:
111 sysexecutable = os.fsencode(sysexecutable)
110 sysplatform: bytes = sys.platform.encode('ascii')
111 sysexecutable: bytes = os.fsencode(sys.executable) if sys.executable else b''
112 112
113 113
114 114 def maplist(*args):
115 115 return list(map(*args))
116 116
117 117
118 118 def rangelist(*args):
119 119 return list(range(*args))
120 120
121 121
122 122 def ziplist(*args):
123 123 return list(zip(*args))
124 124
125 125
126 126 rawinput = input
127 127 getargspec = inspect.getfullargspec
128 128
129 129 long = int
130 130
131 131 if getattr(sys, 'argv', None) is not None:
132 132 # On POSIX, the char** argv array is converted to Python str using
133 133 # Py_DecodeLocale(). The inverse of this is Py_EncodeLocale(), which
134 134 # isn't directly callable from Python code. In practice, os.fsencode()
135 135 # can be used instead (this is recommended by Python's documentation
136 136 # for sys.argv).
137 137 #
138 138 # On Windows, the wchar_t **argv is passed into the interpreter as-is.
139 139 # Like POSIX, we need to emulate what Py_EncodeLocale() would do. But
140 140 # there's an additional wrinkle. What we really want to access is the
141 141 # ANSI codepage representation of the arguments, as this is what
142 142 # `int main()` would receive if Python 3 didn't define `int wmain()`
143 143 # (this is how Python 2 worked). To get that, we encode with the mbcs
144 144 # encoding, which will pass CP_ACP to the underlying Windows API to
145 145 # produce bytes.
146 sysargv: List[bytes] = []
146 147 if os.name == r'nt':
147 148 sysargv = [a.encode("mbcs", "ignore") for a in sys.argv]
148 149 else:
149 150 sysargv = [fsencode(a) for a in sys.argv]
150 151
151 152 bytechr = struct.Struct('>B').pack
152 153 byterepr = b'%r'.__mod__
153 154
154 155
155 156 class bytestr(bytes):
156 157 """A bytes which mostly acts as a Python 2 str
157 158
158 159 >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
159 160 ('', 'foo', 'ascii', '1')
160 161 >>> s = bytestr(b'foo')
161 162 >>> assert s is bytestr(s)
162 163
163 164 __bytes__() should be called if provided:
164 165
165 166 >>> class bytesable:
166 167 ... def __bytes__(self):
167 168 ... return b'bytes'
168 169 >>> bytestr(bytesable())
169 170 'bytes'
170 171
171 172 There's no implicit conversion from non-ascii str as its encoding is
172 173 unknown:
173 174
174 175 >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
175 176 Traceback (most recent call last):
176 177 ...
177 178 UnicodeEncodeError: ...
178 179
179 180 Comparison between bytestr and bytes should work:
180 181
181 182 >>> assert bytestr(b'foo') == b'foo'
182 183 >>> assert b'foo' == bytestr(b'foo')
183 184 >>> assert b'f' in bytestr(b'foo')
184 185 >>> assert bytestr(b'f') in b'foo'
185 186
186 187 Sliced elements should be bytes, not integer:
187 188
188 189 >>> s[1], s[:2]
189 190 (b'o', b'fo')
190 191 >>> list(s), list(reversed(s))
191 192 ([b'f', b'o', b'o'], [b'o', b'o', b'f'])
192 193
193 194 As bytestr type isn't propagated across operations, you need to cast
194 195 bytes to bytestr explicitly:
195 196
196 197 >>> s = bytestr(b'foo').upper()
197 198 >>> t = bytestr(s)
198 199 >>> s[0], t[0]
199 200 (70, b'F')
200 201
201 202 Be careful to not pass a bytestr object to a function which expects
202 203 bytearray-like behavior.
203 204
204 205 >>> t = bytes(t) # cast to bytes
205 206 >>> assert type(t) is bytes
206 207 """
207 208
208 209 # Trick pytype into not demanding Iterable[int] be passed to __new__(),
209 210 # since the appropriate bytes format is done internally.
210 211 #
211 212 # https://github.com/google/pytype/issues/500
212 213 if TYPE_CHECKING:
213 214
214 215 def __init__(self, s=b''):
215 216 pass
216 217
217 218 def __new__(cls, s=b''):
218 219 if isinstance(s, bytestr):
219 220 return s
220 221 if not isinstance(
221 222 s, (bytes, bytearray)
222 223 ) and not hasattr( # hasattr-py3-only
223 224 s, u'__bytes__'
224 225 ):
225 226 s = str(s).encode('ascii')
226 227 return bytes.__new__(cls, s)
227 228
228 229 def __getitem__(self, key):
229 230 s = bytes.__getitem__(self, key)
230 231 if not isinstance(s, bytes):
231 232 s = bytechr(s)
232 233 return s
233 234
234 235 def __iter__(self):
235 236 return iterbytestr(bytes.__iter__(self))
236 237
237 238 def __repr__(self):
238 239 return bytes.__repr__(self)[1:] # drop b''
239 240
240 241
241 242 def iterbytestr(s):
242 243 """Iterate bytes as if it were a str object of Python 2"""
243 244 return map(bytechr, s)
244 245
245 246
246 247 def maybebytestr(s):
247 248 """Promote bytes to bytestr"""
248 249 if isinstance(s, bytes):
249 250 return bytestr(s)
250 251 return s
251 252
252 253
253 254 def sysbytes(s):
254 255 """Convert an internal str (e.g. keyword, __doc__) back to bytes
255 256
256 257 This never raises UnicodeEncodeError, but only ASCII characters
257 258 can be round-trip by sysstr(sysbytes(s)).
258 259 """
259 260 if isinstance(s, bytes):
260 261 return s
261 262 return s.encode('utf-8')
262 263
263 264
264 265 def sysstr(s):
265 266 """Return a keyword str to be passed to Python functions such as
266 267 getattr() and str.encode()
267 268
268 269 This never raises UnicodeDecodeError. Non-ascii characters are
269 270 considered invalid and mapped to arbitrary but unique code points
270 271 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
271 272 """
272 273 if isinstance(s, builtins.str):
273 274 return s
274 275 return s.decode('latin-1')
275 276
276 277
277 278 def strurl(url):
278 279 """Converts a bytes url back to str"""
279 280 if isinstance(url, bytes):
280 281 return url.decode('ascii')
281 282 return url
282 283
283 284
284 285 def bytesurl(url):
285 286 """Converts a str url to bytes by encoding in ascii"""
286 287 if isinstance(url, str):
287 288 return url.encode('ascii')
288 289 return url
289 290
290 291
291 292 def raisewithtb(exc, tb):
292 293 """Raise exception with the given traceback"""
293 294 raise exc.with_traceback(tb)
294 295
295 296
296 297 def getdoc(obj):
297 298 """Get docstring as bytes; may be None so gettext() won't confuse it
298 299 with _('')"""
299 300 doc = getattr(obj, '__doc__', None)
300 301 if doc is None:
301 302 return doc
302 303 return sysbytes(doc)
303 304
304 305
305 306 def _wrapattrfunc(f):
306 307 @functools.wraps(f)
307 308 def w(object, name, *args):
308 309 return f(object, sysstr(name), *args)
309 310
310 311 return w
311 312
312 313
313 314 # these wrappers are automagically imported by hgloader
314 315 delattr = _wrapattrfunc(builtins.delattr)
315 316 getattr = _wrapattrfunc(builtins.getattr)
316 317 hasattr = _wrapattrfunc(builtins.hasattr)
317 318 setattr = _wrapattrfunc(builtins.setattr)
318 319 xrange = builtins.range
319 320 unicode = str
320 321
321 322
322 323 def open(name, mode=b'r', buffering=-1, encoding=None):
323 324 return builtins.open(name, sysstr(mode), buffering, encoding)
324 325
325 326
326 327 safehasattr = _wrapattrfunc(builtins.hasattr)
327 328
328 329
329 330 def _getoptbwrapper(orig, args, shortlist, namelist):
330 331 """
331 332 Takes bytes arguments, converts them to unicode, pass them to
332 333 getopt.getopt(), convert the returned values back to bytes and then
333 334 return them for Python 3 compatibility as getopt.getopt() don't accepts
334 335 bytes on Python 3.
335 336 """
336 337 args = [a.decode('latin-1') for a in args]
337 338 shortlist = shortlist.decode('latin-1')
338 339 namelist = [a.decode('latin-1') for a in namelist]
339 340 opts, args = orig(args, shortlist, namelist)
340 341 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1')) for a in opts]
341 342 args = [a.encode('latin-1') for a in args]
342 343 return opts, args
343 344
344 345
345 346 def strkwargs(dic):
346 347 """
347 348 Converts the keys of a python dictonary to str i.e. unicodes so that
348 349 they can be passed as keyword arguments as dictionaries with bytes keys
349 350 can't be passed as keyword arguments to functions on Python 3.
350 351 """
351 352 dic = {k.decode('latin-1'): v for k, v in dic.items()}
352 353 return dic
353 354
354 355
355 356 def byteskwargs(dic):
356 357 """
357 358 Converts keys of python dictionaries to bytes as they were converted to
358 359 str to pass that dictonary as a keyword argument on Python 3.
359 360 """
360 361 dic = {k.encode('latin-1'): v for k, v in dic.items()}
361 362 return dic
362 363
363 364
364 365 # TODO: handle shlex.shlex().
365 366 def shlexsplit(s, comments=False, posix=True):
366 367 """
367 368 Takes bytes argument, convert it to str i.e. unicodes, pass that into
368 369 shlex.split(), convert the returned value to bytes and return that for
369 370 Python 3 compatibility as shelx.split() don't accept bytes on Python 3.
370 371 """
371 372 ret = shlex.split(s.decode('latin-1'), comments, posix)
372 373 return [a.encode('latin-1') for a in ret]
373 374
374 375
375 376 iteritems = lambda x: x.items()
376 377 itervalues = lambda x: x.values()
377 378
378 379 json_loads = json.loads
379 380
380 isjython = sysplatform.startswith(b'java')
381 isjython: bool = sysplatform.startswith(b'java')
381 382
382 isdarwin = sysplatform.startswith(b'darwin')
383 islinux = sysplatform.startswith(b'linux')
384 isposix = osname == b'posix'
385 iswindows = osname == b'nt'
383 isdarwin: bool = sysplatform.startswith(b'darwin')
384 islinux: bool = sysplatform.startswith(b'linux')
385 isposix: bool = osname == b'posix'
386 iswindows: bool = osname == b'nt'
386 387
387 388
388 389 def getoptb(args, shortlist, namelist):
389 390 return _getoptbwrapper(getopt.getopt, args, shortlist, namelist)
390 391
391 392
392 393 def gnugetoptb(args, shortlist, namelist):
393 394 return _getoptbwrapper(getopt.gnu_getopt, args, shortlist, namelist)
394 395
395 396
396 397 def mkdtemp(suffix=b'', prefix=b'tmp', dir=None):
397 398 return tempfile.mkdtemp(suffix, prefix, dir)
398 399
399 400
400 401 # text=True is not supported; use util.from/tonativeeol() instead
401 402 def mkstemp(suffix=b'', prefix=b'tmp', dir=None):
402 403 return tempfile.mkstemp(suffix, prefix, dir)
403 404
404 405
405 406 # TemporaryFile does not support an "encoding=" argument on python2.
406 407 # This wrapper file are always open in byte mode.
407 408 def unnamedtempfile(mode=None, *args, **kwargs):
408 409 if mode is None:
409 410 mode = 'w+b'
410 411 else:
411 412 mode = sysstr(mode)
412 413 assert 'b' in mode
413 414 return tempfile.TemporaryFile(mode, *args, **kwargs)
414 415
415 416
416 417 # NamedTemporaryFile does not support an "encoding=" argument on python2.
417 418 # This wrapper file are always open in byte mode.
418 419 def namedtempfile(
419 420 mode=b'w+b', bufsize=-1, suffix=b'', prefix=b'tmp', dir=None, delete=True
420 421 ):
421 422 mode = sysstr(mode)
422 423 assert 'b' in mode
423 424 return tempfile.NamedTemporaryFile(
424 425 mode, bufsize, suffix=suffix, prefix=prefix, dir=dir, delete=delete
425 426 )
General Comments 0
You need to be logged in to leave comments. Login now