##// END OF EJS Templates
pycompat: add maplist alias for old map behavior
Augie Fackler -
r31501:a1e40cee default
parent child Browse files
Show More
@@ -1,385 +1,387 b''
1 # pycompat.py - portability shim for python 3
1 # pycompat.py - portability shim for python 3
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 """Mercurial portability shim for python 3.
6 """Mercurial portability shim for python 3.
7
7
8 This contains aliases to hide python version-specific details from the core.
8 This contains aliases to hide python version-specific details from the core.
9 """
9 """
10
10
11 from __future__ import absolute_import
11 from __future__ import absolute_import
12
12
13 import getopt
13 import getopt
14 import os
14 import os
15 import shlex
15 import shlex
16 import sys
16 import sys
17
17
18 ispy3 = (sys.version_info[0] >= 3)
18 ispy3 = (sys.version_info[0] >= 3)
19
19
20 if not ispy3:
20 if not ispy3:
21 import cPickle as pickle
21 import cPickle as pickle
22 import httplib
22 import httplib
23 import Queue as _queue
23 import Queue as _queue
24 import SocketServer as socketserver
24 import SocketServer as socketserver
25 import urlparse
25 import urlparse
26 urlunquote = urlparse.unquote
26 urlunquote = urlparse.unquote
27 import xmlrpclib
27 import xmlrpclib
28 else:
28 else:
29 import http.client as httplib
29 import http.client as httplib
30 import pickle
30 import pickle
31 import queue as _queue
31 import queue as _queue
32 import socketserver
32 import socketserver
33 import urllib.parse as urlparse
33 import urllib.parse as urlparse
34 urlunquote = urlparse.unquote_to_bytes
34 urlunquote = urlparse.unquote_to_bytes
35 import xmlrpc.client as xmlrpclib
35 import xmlrpc.client as xmlrpclib
36
36
37 if ispy3:
37 if ispy3:
38 import builtins
38 import builtins
39 import functools
39 import functools
40 import io
40 import io
41 import struct
41 import struct
42
42
43 fsencode = os.fsencode
43 fsencode = os.fsencode
44 fsdecode = os.fsdecode
44 fsdecode = os.fsdecode
45 # A bytes version of os.name.
45 # A bytes version of os.name.
46 osname = os.name.encode('ascii')
46 osname = os.name.encode('ascii')
47 ospathsep = os.pathsep.encode('ascii')
47 ospathsep = os.pathsep.encode('ascii')
48 ossep = os.sep.encode('ascii')
48 ossep = os.sep.encode('ascii')
49 osaltsep = os.altsep
49 osaltsep = os.altsep
50 if osaltsep:
50 if osaltsep:
51 osaltsep = osaltsep.encode('ascii')
51 osaltsep = osaltsep.encode('ascii')
52 # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
52 # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
53 # returns bytes.
53 # returns bytes.
54 getcwd = os.getcwdb
54 getcwd = os.getcwdb
55 sysplatform = sys.platform.encode('ascii')
55 sysplatform = sys.platform.encode('ascii')
56 sysexecutable = sys.executable
56 sysexecutable = sys.executable
57 if sysexecutable:
57 if sysexecutable:
58 sysexecutable = os.fsencode(sysexecutable)
58 sysexecutable = os.fsencode(sysexecutable)
59 stringio = io.BytesIO
59 stringio = io.BytesIO
60 maplist = lambda *args: list(map(*args))
60
61
61 # TODO: .buffer might not exist if std streams were replaced; we'll need
62 # TODO: .buffer might not exist if std streams were replaced; we'll need
62 # a silly wrapper to make a bytes stream backed by a unicode one.
63 # a silly wrapper to make a bytes stream backed by a unicode one.
63 stdin = sys.stdin.buffer
64 stdin = sys.stdin.buffer
64 stdout = sys.stdout.buffer
65 stdout = sys.stdout.buffer
65 stderr = sys.stderr.buffer
66 stderr = sys.stderr.buffer
66
67
67 # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
68 # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
68 # we can use os.fsencode() to get back bytes argv.
69 # we can use os.fsencode() to get back bytes argv.
69 #
70 #
70 # https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55
71 # https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55
71 #
72 #
72 # TODO: On Windows, the native argv is wchar_t, so we'll need a different
73 # TODO: On Windows, the native argv is wchar_t, so we'll need a different
73 # workaround to simulate the Python 2 (i.e. ANSI Win32 API) behavior.
74 # workaround to simulate the Python 2 (i.e. ANSI Win32 API) behavior.
74 if getattr(sys, 'argv', None) is not None:
75 if getattr(sys, 'argv', None) is not None:
75 sysargv = list(map(os.fsencode, sys.argv))
76 sysargv = list(map(os.fsencode, sys.argv))
76
77
77 bytechr = struct.Struct('>B').pack
78 bytechr = struct.Struct('>B').pack
78
79
79 class bytestr(bytes):
80 class bytestr(bytes):
80 """A bytes which mostly acts as a Python 2 str
81 """A bytes which mostly acts as a Python 2 str
81
82
82 >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
83 >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
83 (b'', b'foo', b'ascii', b'1')
84 (b'', b'foo', b'ascii', b'1')
84 >>> s = bytestr(b'foo')
85 >>> s = bytestr(b'foo')
85 >>> assert s is bytestr(s)
86 >>> assert s is bytestr(s)
86
87
87 There's no implicit conversion from non-ascii str as its encoding is
88 There's no implicit conversion from non-ascii str as its encoding is
88 unknown:
89 unknown:
89
90
90 >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
91 >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
91 Traceback (most recent call last):
92 Traceback (most recent call last):
92 ...
93 ...
93 UnicodeEncodeError: ...
94 UnicodeEncodeError: ...
94
95
95 Comparison between bytestr and bytes should work:
96 Comparison between bytestr and bytes should work:
96
97
97 >>> assert bytestr(b'foo') == b'foo'
98 >>> assert bytestr(b'foo') == b'foo'
98 >>> assert b'foo' == bytestr(b'foo')
99 >>> assert b'foo' == bytestr(b'foo')
99 >>> assert b'f' in bytestr(b'foo')
100 >>> assert b'f' in bytestr(b'foo')
100 >>> assert bytestr(b'f') in b'foo'
101 >>> assert bytestr(b'f') in b'foo'
101
102
102 Sliced elements should be bytes, not integer:
103 Sliced elements should be bytes, not integer:
103
104
104 >>> s[1], s[:2]
105 >>> s[1], s[:2]
105 (b'o', b'fo')
106 (b'o', b'fo')
106 >>> list(s), list(reversed(s))
107 >>> list(s), list(reversed(s))
107 ([b'f', b'o', b'o'], [b'o', b'o', b'f'])
108 ([b'f', b'o', b'o'], [b'o', b'o', b'f'])
108
109
109 As bytestr type isn't propagated across operations, you need to cast
110 As bytestr type isn't propagated across operations, you need to cast
110 bytes to bytestr explicitly:
111 bytes to bytestr explicitly:
111
112
112 >>> s = bytestr(b'foo').upper()
113 >>> s = bytestr(b'foo').upper()
113 >>> t = bytestr(s)
114 >>> t = bytestr(s)
114 >>> s[0], t[0]
115 >>> s[0], t[0]
115 (70, b'F')
116 (70, b'F')
116
117
117 Be careful to not pass a bytestr object to a function which expects
118 Be careful to not pass a bytestr object to a function which expects
118 bytearray-like behavior.
119 bytearray-like behavior.
119
120
120 >>> t = bytes(t) # cast to bytes
121 >>> t = bytes(t) # cast to bytes
121 >>> assert type(t) is bytes
122 >>> assert type(t) is bytes
122 """
123 """
123
124
124 def __new__(cls, s=b''):
125 def __new__(cls, s=b''):
125 if isinstance(s, bytestr):
126 if isinstance(s, bytestr):
126 return s
127 return s
127 if not isinstance(s, (bytes, bytearray)):
128 if not isinstance(s, (bytes, bytearray)):
128 s = str(s).encode(u'ascii')
129 s = str(s).encode(u'ascii')
129 return bytes.__new__(cls, s)
130 return bytes.__new__(cls, s)
130
131
131 def __getitem__(self, key):
132 def __getitem__(self, key):
132 s = bytes.__getitem__(self, key)
133 s = bytes.__getitem__(self, key)
133 if not isinstance(s, bytes):
134 if not isinstance(s, bytes):
134 s = bytechr(s)
135 s = bytechr(s)
135 return s
136 return s
136
137
137 def __iter__(self):
138 def __iter__(self):
138 return iterbytestr(bytes.__iter__(self))
139 return iterbytestr(bytes.__iter__(self))
139
140
140 def iterbytestr(s):
141 def iterbytestr(s):
141 """Iterate bytes as if it were a str object of Python 2"""
142 """Iterate bytes as if it were a str object of Python 2"""
142 return map(bytechr, s)
143 return map(bytechr, s)
143
144
144 def sysstr(s):
145 def sysstr(s):
145 """Return a keyword str to be passed to Python functions such as
146 """Return a keyword str to be passed to Python functions such as
146 getattr() and str.encode()
147 getattr() and str.encode()
147
148
148 This never raises UnicodeDecodeError. Non-ascii characters are
149 This never raises UnicodeDecodeError. Non-ascii characters are
149 considered invalid and mapped to arbitrary but unique code points
150 considered invalid and mapped to arbitrary but unique code points
150 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
151 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
151 """
152 """
152 if isinstance(s, builtins.str):
153 if isinstance(s, builtins.str):
153 return s
154 return s
154 return s.decode(u'latin-1')
155 return s.decode(u'latin-1')
155
156
156 def _wrapattrfunc(f):
157 def _wrapattrfunc(f):
157 @functools.wraps(f)
158 @functools.wraps(f)
158 def w(object, name, *args):
159 def w(object, name, *args):
159 return f(object, sysstr(name), *args)
160 return f(object, sysstr(name), *args)
160 return w
161 return w
161
162
162 # these wrappers are automagically imported by hgloader
163 # these wrappers are automagically imported by hgloader
163 delattr = _wrapattrfunc(builtins.delattr)
164 delattr = _wrapattrfunc(builtins.delattr)
164 getattr = _wrapattrfunc(builtins.getattr)
165 getattr = _wrapattrfunc(builtins.getattr)
165 hasattr = _wrapattrfunc(builtins.hasattr)
166 hasattr = _wrapattrfunc(builtins.hasattr)
166 setattr = _wrapattrfunc(builtins.setattr)
167 setattr = _wrapattrfunc(builtins.setattr)
167 xrange = builtins.range
168 xrange = builtins.range
168
169
169 def open(name, mode='r', buffering=-1):
170 def open(name, mode='r', buffering=-1):
170 return builtins.open(name, sysstr(mode), buffering)
171 return builtins.open(name, sysstr(mode), buffering)
171
172
172 # getopt.getopt() on Python 3 deals with unicodes internally so we cannot
173 # getopt.getopt() on Python 3 deals with unicodes internally so we cannot
173 # pass bytes there. Passing unicodes will result in unicodes as return
174 # pass bytes there. Passing unicodes will result in unicodes as return
174 # values which we need to convert again to bytes.
175 # values which we need to convert again to bytes.
175 def getoptb(args, shortlist, namelist):
176 def getoptb(args, shortlist, namelist):
176 args = [a.decode('latin-1') for a in args]
177 args = [a.decode('latin-1') for a in args]
177 shortlist = shortlist.decode('latin-1')
178 shortlist = shortlist.decode('latin-1')
178 namelist = [a.decode('latin-1') for a in namelist]
179 namelist = [a.decode('latin-1') for a in namelist]
179 opts, args = getopt.getopt(args, shortlist, namelist)
180 opts, args = getopt.getopt(args, shortlist, namelist)
180 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1'))
181 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1'))
181 for a in opts]
182 for a in opts]
182 args = [a.encode('latin-1') for a in args]
183 args = [a.encode('latin-1') for a in args]
183 return opts, args
184 return opts, args
184
185
185 # keys of keyword arguments in Python need to be strings which are unicodes
186 # keys of keyword arguments in Python need to be strings which are unicodes
186 # Python 3. This function takes keyword arguments, convert the keys to str.
187 # Python 3. This function takes keyword arguments, convert the keys to str.
187 def strkwargs(dic):
188 def strkwargs(dic):
188 dic = dict((k.decode('latin-1'), v) for k, v in dic.iteritems())
189 dic = dict((k.decode('latin-1'), v) for k, v in dic.iteritems())
189 return dic
190 return dic
190
191
191 # keys of keyword arguments need to be unicode while passing into
192 # keys of keyword arguments need to be unicode while passing into
192 # a function. This function helps us to convert those keys back to bytes
193 # a function. This function helps us to convert those keys back to bytes
193 # again as we need to deal with bytes.
194 # again as we need to deal with bytes.
194 def byteskwargs(dic):
195 def byteskwargs(dic):
195 dic = dict((k.encode('latin-1'), v) for k, v in dic.iteritems())
196 dic = dict((k.encode('latin-1'), v) for k, v in dic.iteritems())
196 return dic
197 return dic
197
198
198 # shlex.split() accepts unicodes on Python 3. This function takes bytes
199 # shlex.split() accepts unicodes on Python 3. This function takes bytes
199 # argument, convert it into unicodes, pass into shlex.split(), convert the
200 # argument, convert it into unicodes, pass into shlex.split(), convert the
200 # returned value to bytes and return that.
201 # returned value to bytes and return that.
201 # TODO: handle shlex.shlex().
202 # TODO: handle shlex.shlex().
202 def shlexsplit(s):
203 def shlexsplit(s):
203 ret = shlex.split(s.decode('latin-1'))
204 ret = shlex.split(s.decode('latin-1'))
204 return [a.encode('latin-1') for a in ret]
205 return [a.encode('latin-1') for a in ret]
205
206
206 else:
207 else:
207 import cStringIO
208 import cStringIO
208
209
209 bytechr = chr
210 bytechr = chr
210 bytestr = str
211 bytestr = str
211 iterbytestr = iter
212 iterbytestr = iter
212
213
213 def sysstr(s):
214 def sysstr(s):
214 return s
215 return s
215
216
216 # Partial backport from os.py in Python 3, which only accepts bytes.
217 # Partial backport from os.py in Python 3, which only accepts bytes.
217 # In Python 2, our paths should only ever be bytes, a unicode path
218 # In Python 2, our paths should only ever be bytes, a unicode path
218 # indicates a bug.
219 # indicates a bug.
219 def fsencode(filename):
220 def fsencode(filename):
220 if isinstance(filename, str):
221 if isinstance(filename, str):
221 return filename
222 return filename
222 else:
223 else:
223 raise TypeError(
224 raise TypeError(
224 "expect str, not %s" % type(filename).__name__)
225 "expect str, not %s" % type(filename).__name__)
225
226
226 # In Python 2, fsdecode() has a very chance to receive bytes. So it's
227 # In Python 2, fsdecode() has a very chance to receive bytes. So it's
227 # better not to touch Python 2 part as it's already working fine.
228 # better not to touch Python 2 part as it's already working fine.
228 def fsdecode(filename):
229 def fsdecode(filename):
229 return filename
230 return filename
230
231
231 def getoptb(args, shortlist, namelist):
232 def getoptb(args, shortlist, namelist):
232 return getopt.getopt(args, shortlist, namelist)
233 return getopt.getopt(args, shortlist, namelist)
233
234
234 def strkwargs(dic):
235 def strkwargs(dic):
235 return dic
236 return dic
236
237
237 def byteskwargs(dic):
238 def byteskwargs(dic):
238 return dic
239 return dic
239
240
240 osname = os.name
241 osname = os.name
241 ospathsep = os.pathsep
242 ospathsep = os.pathsep
242 ossep = os.sep
243 ossep = os.sep
243 osaltsep = os.altsep
244 osaltsep = os.altsep
244 stdin = sys.stdin
245 stdin = sys.stdin
245 stdout = sys.stdout
246 stdout = sys.stdout
246 stderr = sys.stderr
247 stderr = sys.stderr
247 if getattr(sys, 'argv', None) is not None:
248 if getattr(sys, 'argv', None) is not None:
248 sysargv = sys.argv
249 sysargv = sys.argv
249 sysplatform = sys.platform
250 sysplatform = sys.platform
250 getcwd = os.getcwd
251 getcwd = os.getcwd
251 sysexecutable = sys.executable
252 sysexecutable = sys.executable
252 shlexsplit = shlex.split
253 shlexsplit = shlex.split
253 stringio = cStringIO.StringIO
254 stringio = cStringIO.StringIO
255 maplist = map
254
256
255 empty = _queue.Empty
257 empty = _queue.Empty
256 queue = _queue.Queue
258 queue = _queue.Queue
257
259
258 class _pycompatstub(object):
260 class _pycompatstub(object):
259 def __init__(self):
261 def __init__(self):
260 self._aliases = {}
262 self._aliases = {}
261
263
262 def _registeraliases(self, origin, items):
264 def _registeraliases(self, origin, items):
263 """Add items that will be populated at the first access"""
265 """Add items that will be populated at the first access"""
264 items = map(sysstr, items)
266 items = map(sysstr, items)
265 self._aliases.update(
267 self._aliases.update(
266 (item.replace(sysstr('_'), sysstr('')).lower(), (origin, item))
268 (item.replace(sysstr('_'), sysstr('')).lower(), (origin, item))
267 for item in items)
269 for item in items)
268
270
269 def __getattr__(self, name):
271 def __getattr__(self, name):
270 try:
272 try:
271 origin, item = self._aliases[name]
273 origin, item = self._aliases[name]
272 except KeyError:
274 except KeyError:
273 raise AttributeError(name)
275 raise AttributeError(name)
274 self.__dict__[name] = obj = getattr(origin, item)
276 self.__dict__[name] = obj = getattr(origin, item)
275 return obj
277 return obj
276
278
277 httpserver = _pycompatstub()
279 httpserver = _pycompatstub()
278 urlreq = _pycompatstub()
280 urlreq = _pycompatstub()
279 urlerr = _pycompatstub()
281 urlerr = _pycompatstub()
280 if not ispy3:
282 if not ispy3:
281 import BaseHTTPServer
283 import BaseHTTPServer
282 import CGIHTTPServer
284 import CGIHTTPServer
283 import SimpleHTTPServer
285 import SimpleHTTPServer
284 import urllib2
286 import urllib2
285 import urllib
287 import urllib
286 urlreq._registeraliases(urllib, (
288 urlreq._registeraliases(urllib, (
287 "addclosehook",
289 "addclosehook",
288 "addinfourl",
290 "addinfourl",
289 "ftpwrapper",
291 "ftpwrapper",
290 "pathname2url",
292 "pathname2url",
291 "quote",
293 "quote",
292 "splitattr",
294 "splitattr",
293 "splitpasswd",
295 "splitpasswd",
294 "splitport",
296 "splitport",
295 "splituser",
297 "splituser",
296 "unquote",
298 "unquote",
297 "url2pathname",
299 "url2pathname",
298 "urlencode",
300 "urlencode",
299 ))
301 ))
300 urlreq._registeraliases(urllib2, (
302 urlreq._registeraliases(urllib2, (
301 "AbstractHTTPHandler",
303 "AbstractHTTPHandler",
302 "BaseHandler",
304 "BaseHandler",
303 "build_opener",
305 "build_opener",
304 "FileHandler",
306 "FileHandler",
305 "FTPHandler",
307 "FTPHandler",
306 "HTTPBasicAuthHandler",
308 "HTTPBasicAuthHandler",
307 "HTTPDigestAuthHandler",
309 "HTTPDigestAuthHandler",
308 "HTTPHandler",
310 "HTTPHandler",
309 "HTTPPasswordMgrWithDefaultRealm",
311 "HTTPPasswordMgrWithDefaultRealm",
310 "HTTPSHandler",
312 "HTTPSHandler",
311 "install_opener",
313 "install_opener",
312 "ProxyHandler",
314 "ProxyHandler",
313 "Request",
315 "Request",
314 "urlopen",
316 "urlopen",
315 ))
317 ))
316 urlerr._registeraliases(urllib2, (
318 urlerr._registeraliases(urllib2, (
317 "HTTPError",
319 "HTTPError",
318 "URLError",
320 "URLError",
319 ))
321 ))
320 httpserver._registeraliases(BaseHTTPServer, (
322 httpserver._registeraliases(BaseHTTPServer, (
321 "HTTPServer",
323 "HTTPServer",
322 "BaseHTTPRequestHandler",
324 "BaseHTTPRequestHandler",
323 ))
325 ))
324 httpserver._registeraliases(SimpleHTTPServer, (
326 httpserver._registeraliases(SimpleHTTPServer, (
325 "SimpleHTTPRequestHandler",
327 "SimpleHTTPRequestHandler",
326 ))
328 ))
327 httpserver._registeraliases(CGIHTTPServer, (
329 httpserver._registeraliases(CGIHTTPServer, (
328 "CGIHTTPRequestHandler",
330 "CGIHTTPRequestHandler",
329 ))
331 ))
330
332
331 else:
333 else:
332 import urllib.parse
334 import urllib.parse
333 urlreq._registeraliases(urllib.parse, (
335 urlreq._registeraliases(urllib.parse, (
334 "splitattr",
336 "splitattr",
335 "splitpasswd",
337 "splitpasswd",
336 "splitport",
338 "splitport",
337 "splituser",
339 "splituser",
338 "unquote",
340 "unquote",
339 ))
341 ))
340 import urllib.request
342 import urllib.request
341 urlreq._registeraliases(urllib.request, (
343 urlreq._registeraliases(urllib.request, (
342 "AbstractHTTPHandler",
344 "AbstractHTTPHandler",
343 "BaseHandler",
345 "BaseHandler",
344 "build_opener",
346 "build_opener",
345 "FileHandler",
347 "FileHandler",
346 "FTPHandler",
348 "FTPHandler",
347 "ftpwrapper",
349 "ftpwrapper",
348 "HTTPHandler",
350 "HTTPHandler",
349 "HTTPSHandler",
351 "HTTPSHandler",
350 "install_opener",
352 "install_opener",
351 "pathname2url",
353 "pathname2url",
352 "HTTPBasicAuthHandler",
354 "HTTPBasicAuthHandler",
353 "HTTPDigestAuthHandler",
355 "HTTPDigestAuthHandler",
354 "HTTPPasswordMgrWithDefaultRealm",
356 "HTTPPasswordMgrWithDefaultRealm",
355 "ProxyHandler",
357 "ProxyHandler",
356 "Request",
358 "Request",
357 "url2pathname",
359 "url2pathname",
358 "urlopen",
360 "urlopen",
359 ))
361 ))
360 import urllib.response
362 import urllib.response
361 urlreq._registeraliases(urllib.response, (
363 urlreq._registeraliases(urllib.response, (
362 "addclosehook",
364 "addclosehook",
363 "addinfourl",
365 "addinfourl",
364 ))
366 ))
365 import urllib.error
367 import urllib.error
366 urlerr._registeraliases(urllib.error, (
368 urlerr._registeraliases(urllib.error, (
367 "HTTPError",
369 "HTTPError",
368 "URLError",
370 "URLError",
369 ))
371 ))
370 import http.server
372 import http.server
371 httpserver._registeraliases(http.server, (
373 httpserver._registeraliases(http.server, (
372 "HTTPServer",
374 "HTTPServer",
373 "BaseHTTPRequestHandler",
375 "BaseHTTPRequestHandler",
374 "SimpleHTTPRequestHandler",
376 "SimpleHTTPRequestHandler",
375 "CGIHTTPRequestHandler",
377 "CGIHTTPRequestHandler",
376 ))
378 ))
377
379
378 # urllib.parse.quote() accepts both str and bytes, decodes bytes
380 # urllib.parse.quote() accepts both str and bytes, decodes bytes
379 # (if necessary), and returns str. This is wonky. We provide a custom
381 # (if necessary), and returns str. This is wonky. We provide a custom
380 # implementation that only accepts bytes and emits bytes.
382 # implementation that only accepts bytes and emits bytes.
381 def quote(s, safe=r'/'):
383 def quote(s, safe=r'/'):
382 s = urllib.parse.quote_from_bytes(s, safe=safe)
384 s = urllib.parse.quote_from_bytes(s, safe=safe)
383 return s.encode('ascii', 'strict')
385 return s.encode('ascii', 'strict')
384
386
385 urlreq.quote = quote
387 urlreq.quote = quote
General Comments 0
You need to be logged in to leave comments. Login now