##// END OF EJS Templates
pycompat: alias urlreq.unquote to unquote_to_bytes...
Gregory Szorc -
r31566:c6df6a23 default
parent child Browse files
Show More
@@ -1,387 +1,391 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 maplist = lambda *args: list(map(*args))
61
61
62 # 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
63 # 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.
64 stdin = sys.stdin.buffer
64 stdin = sys.stdin.buffer
65 stdout = sys.stdout.buffer
65 stdout = sys.stdout.buffer
66 stderr = sys.stderr.buffer
66 stderr = sys.stderr.buffer
67
67
68 # 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,
69 # we can use os.fsencode() to get back bytes argv.
69 # we can use os.fsencode() to get back bytes argv.
70 #
70 #
71 # 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
72 #
72 #
73 # 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
74 # 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.
75 if getattr(sys, 'argv', None) is not None:
75 if getattr(sys, 'argv', None) is not None:
76 sysargv = list(map(os.fsencode, sys.argv))
76 sysargv = list(map(os.fsencode, sys.argv))
77
77
78 bytechr = struct.Struct('>B').pack
78 bytechr = struct.Struct('>B').pack
79
79
80 class bytestr(bytes):
80 class bytestr(bytes):
81 """A bytes which mostly acts as a Python 2 str
81 """A bytes which mostly acts as a Python 2 str
82
82
83 >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
83 >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
84 (b'', b'foo', b'ascii', b'1')
84 (b'', b'foo', b'ascii', b'1')
85 >>> s = bytestr(b'foo')
85 >>> s = bytestr(b'foo')
86 >>> assert s is bytestr(s)
86 >>> assert s is bytestr(s)
87
87
88 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
89 unknown:
89 unknown:
90
90
91 >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
91 >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
92 Traceback (most recent call last):
92 Traceback (most recent call last):
93 ...
93 ...
94 UnicodeEncodeError: ...
94 UnicodeEncodeError: ...
95
95
96 Comparison between bytestr and bytes should work:
96 Comparison between bytestr and bytes should work:
97
97
98 >>> assert bytestr(b'foo') == b'foo'
98 >>> assert bytestr(b'foo') == b'foo'
99 >>> assert b'foo' == bytestr(b'foo')
99 >>> assert b'foo' == bytestr(b'foo')
100 >>> assert b'f' in bytestr(b'foo')
100 >>> assert b'f' in bytestr(b'foo')
101 >>> assert bytestr(b'f') in b'foo'
101 >>> assert bytestr(b'f') in b'foo'
102
102
103 Sliced elements should be bytes, not integer:
103 Sliced elements should be bytes, not integer:
104
104
105 >>> s[1], s[:2]
105 >>> s[1], s[:2]
106 (b'o', b'fo')
106 (b'o', b'fo')
107 >>> list(s), list(reversed(s))
107 >>> list(s), list(reversed(s))
108 ([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'])
109
109
110 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
111 bytes to bytestr explicitly:
111 bytes to bytestr explicitly:
112
112
113 >>> s = bytestr(b'foo').upper()
113 >>> s = bytestr(b'foo').upper()
114 >>> t = bytestr(s)
114 >>> t = bytestr(s)
115 >>> s[0], t[0]
115 >>> s[0], t[0]
116 (70, b'F')
116 (70, b'F')
117
117
118 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
119 bytearray-like behavior.
119 bytearray-like behavior.
120
120
121 >>> t = bytes(t) # cast to bytes
121 >>> t = bytes(t) # cast to bytes
122 >>> assert type(t) is bytes
122 >>> assert type(t) is bytes
123 """
123 """
124
124
125 def __new__(cls, s=b''):
125 def __new__(cls, s=b''):
126 if isinstance(s, bytestr):
126 if isinstance(s, bytestr):
127 return s
127 return s
128 if not isinstance(s, (bytes, bytearray)):
128 if not isinstance(s, (bytes, bytearray)):
129 s = str(s).encode(u'ascii')
129 s = str(s).encode(u'ascii')
130 return bytes.__new__(cls, s)
130 return bytes.__new__(cls, s)
131
131
132 def __getitem__(self, key):
132 def __getitem__(self, key):
133 s = bytes.__getitem__(self, key)
133 s = bytes.__getitem__(self, key)
134 if not isinstance(s, bytes):
134 if not isinstance(s, bytes):
135 s = bytechr(s)
135 s = bytechr(s)
136 return s
136 return s
137
137
138 def __iter__(self):
138 def __iter__(self):
139 return iterbytestr(bytes.__iter__(self))
139 return iterbytestr(bytes.__iter__(self))
140
140
141 def iterbytestr(s):
141 def iterbytestr(s):
142 """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"""
143 return map(bytechr, s)
143 return map(bytechr, s)
144
144
145 def sysstr(s):
145 def sysstr(s):
146 """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
147 getattr() and str.encode()
147 getattr() and str.encode()
148
148
149 This never raises UnicodeDecodeError. Non-ascii characters are
149 This never raises UnicodeDecodeError. Non-ascii characters are
150 considered invalid and mapped to arbitrary but unique code points
150 considered invalid and mapped to arbitrary but unique code points
151 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
151 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
152 """
152 """
153 if isinstance(s, builtins.str):
153 if isinstance(s, builtins.str):
154 return s
154 return s
155 return s.decode(u'latin-1')
155 return s.decode(u'latin-1')
156
156
157 def _wrapattrfunc(f):
157 def _wrapattrfunc(f):
158 @functools.wraps(f)
158 @functools.wraps(f)
159 def w(object, name, *args):
159 def w(object, name, *args):
160 return f(object, sysstr(name), *args)
160 return f(object, sysstr(name), *args)
161 return w
161 return w
162
162
163 # these wrappers are automagically imported by hgloader
163 # these wrappers are automagically imported by hgloader
164 delattr = _wrapattrfunc(builtins.delattr)
164 delattr = _wrapattrfunc(builtins.delattr)
165 getattr = _wrapattrfunc(builtins.getattr)
165 getattr = _wrapattrfunc(builtins.getattr)
166 hasattr = _wrapattrfunc(builtins.hasattr)
166 hasattr = _wrapattrfunc(builtins.hasattr)
167 setattr = _wrapattrfunc(builtins.setattr)
167 setattr = _wrapattrfunc(builtins.setattr)
168 xrange = builtins.range
168 xrange = builtins.range
169
169
170 def open(name, mode='r', buffering=-1):
170 def open(name, mode='r', buffering=-1):
171 return builtins.open(name, sysstr(mode), buffering)
171 return builtins.open(name, sysstr(mode), buffering)
172
172
173 # 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
174 # pass bytes there. Passing unicodes will result in unicodes as return
174 # pass bytes there. Passing unicodes will result in unicodes as return
175 # values which we need to convert again to bytes.
175 # values which we need to convert again to bytes.
176 def getoptb(args, shortlist, namelist):
176 def getoptb(args, shortlist, namelist):
177 args = [a.decode('latin-1') for a in args]
177 args = [a.decode('latin-1') for a in args]
178 shortlist = shortlist.decode('latin-1')
178 shortlist = shortlist.decode('latin-1')
179 namelist = [a.decode('latin-1') for a in namelist]
179 namelist = [a.decode('latin-1') for a in namelist]
180 opts, args = getopt.getopt(args, shortlist, namelist)
180 opts, args = getopt.getopt(args, shortlist, namelist)
181 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1'))
181 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1'))
182 for a in opts]
182 for a in opts]
183 args = [a.encode('latin-1') for a in args]
183 args = [a.encode('latin-1') for a in args]
184 return opts, args
184 return opts, args
185
185
186 # 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
187 # 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.
188 def strkwargs(dic):
188 def strkwargs(dic):
189 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())
190 return dic
190 return dic
191
191
192 # keys of keyword arguments need to be unicode while passing into
192 # keys of keyword arguments need to be unicode while passing into
193 # 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
194 # again as we need to deal with bytes.
194 # again as we need to deal with bytes.
195 def byteskwargs(dic):
195 def byteskwargs(dic):
196 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())
197 return dic
197 return dic
198
198
199 # shlex.split() accepts unicodes on Python 3. This function takes bytes
199 # shlex.split() accepts unicodes on Python 3. This function takes bytes
200 # argument, convert it into unicodes, pass into shlex.split(), convert the
200 # argument, convert it into unicodes, pass into shlex.split(), convert the
201 # returned value to bytes and return that.
201 # returned value to bytes and return that.
202 # TODO: handle shlex.shlex().
202 # TODO: handle shlex.shlex().
203 def shlexsplit(s):
203 def shlexsplit(s):
204 ret = shlex.split(s.decode('latin-1'))
204 ret = shlex.split(s.decode('latin-1'))
205 return [a.encode('latin-1') for a in ret]
205 return [a.encode('latin-1') for a in ret]
206
206
207 else:
207 else:
208 import cStringIO
208 import cStringIO
209
209
210 bytechr = chr
210 bytechr = chr
211 bytestr = str
211 bytestr = str
212 iterbytestr = iter
212 iterbytestr = iter
213
213
214 def sysstr(s):
214 def sysstr(s):
215 return s
215 return s
216
216
217 # 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.
218 # 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
219 # indicates a bug.
219 # indicates a bug.
220 def fsencode(filename):
220 def fsencode(filename):
221 if isinstance(filename, str):
221 if isinstance(filename, str):
222 return filename
222 return filename
223 else:
223 else:
224 raise TypeError(
224 raise TypeError(
225 "expect str, not %s" % type(filename).__name__)
225 "expect str, not %s" % type(filename).__name__)
226
226
227 # 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
228 # 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.
229 def fsdecode(filename):
229 def fsdecode(filename):
230 return filename
230 return filename
231
231
232 def getoptb(args, shortlist, namelist):
232 def getoptb(args, shortlist, namelist):
233 return getopt.getopt(args, shortlist, namelist)
233 return getopt.getopt(args, shortlist, namelist)
234
234
235 def strkwargs(dic):
235 def strkwargs(dic):
236 return dic
236 return dic
237
237
238 def byteskwargs(dic):
238 def byteskwargs(dic):
239 return dic
239 return dic
240
240
241 osname = os.name
241 osname = os.name
242 ospathsep = os.pathsep
242 ospathsep = os.pathsep
243 ossep = os.sep
243 ossep = os.sep
244 osaltsep = os.altsep
244 osaltsep = os.altsep
245 stdin = sys.stdin
245 stdin = sys.stdin
246 stdout = sys.stdout
246 stdout = sys.stdout
247 stderr = sys.stderr
247 stderr = sys.stderr
248 if getattr(sys, 'argv', None) is not None:
248 if getattr(sys, 'argv', None) is not None:
249 sysargv = sys.argv
249 sysargv = sys.argv
250 sysplatform = sys.platform
250 sysplatform = sys.platform
251 getcwd = os.getcwd
251 getcwd = os.getcwd
252 sysexecutable = sys.executable
252 sysexecutable = sys.executable
253 shlexsplit = shlex.split
253 shlexsplit = shlex.split
254 stringio = cStringIO.StringIO
254 stringio = cStringIO.StringIO
255 maplist = map
255 maplist = map
256
256
257 empty = _queue.Empty
257 empty = _queue.Empty
258 queue = _queue.Queue
258 queue = _queue.Queue
259
259
260 class _pycompatstub(object):
260 class _pycompatstub(object):
261 def __init__(self):
261 def __init__(self):
262 self._aliases = {}
262 self._aliases = {}
263
263
264 def _registeraliases(self, origin, items):
264 def _registeraliases(self, origin, items):
265 """Add items that will be populated at the first access"""
265 """Add items that will be populated at the first access"""
266 items = map(sysstr, items)
266 items = map(sysstr, items)
267 self._aliases.update(
267 self._aliases.update(
268 (item.replace(sysstr('_'), sysstr('')).lower(), (origin, item))
268 (item.replace(sysstr('_'), sysstr('')).lower(), (origin, item))
269 for item in items)
269 for item in items)
270
270
271 def _registeralias(self, origin, attr, name):
272 """Alias ``origin``.``attr`` as ``name``"""
273 self._aliases[sysstr(name)] = (origin, sysstr(attr))
274
271 def __getattr__(self, name):
275 def __getattr__(self, name):
272 try:
276 try:
273 origin, item = self._aliases[name]
277 origin, item = self._aliases[name]
274 except KeyError:
278 except KeyError:
275 raise AttributeError(name)
279 raise AttributeError(name)
276 self.__dict__[name] = obj = getattr(origin, item)
280 self.__dict__[name] = obj = getattr(origin, item)
277 return obj
281 return obj
278
282
279 httpserver = _pycompatstub()
283 httpserver = _pycompatstub()
280 urlreq = _pycompatstub()
284 urlreq = _pycompatstub()
281 urlerr = _pycompatstub()
285 urlerr = _pycompatstub()
282 if not ispy3:
286 if not ispy3:
283 import BaseHTTPServer
287 import BaseHTTPServer
284 import CGIHTTPServer
288 import CGIHTTPServer
285 import SimpleHTTPServer
289 import SimpleHTTPServer
286 import urllib2
290 import urllib2
287 import urllib
291 import urllib
288 urlreq._registeraliases(urllib, (
292 urlreq._registeraliases(urllib, (
289 "addclosehook",
293 "addclosehook",
290 "addinfourl",
294 "addinfourl",
291 "ftpwrapper",
295 "ftpwrapper",
292 "pathname2url",
296 "pathname2url",
293 "quote",
297 "quote",
294 "splitattr",
298 "splitattr",
295 "splitpasswd",
299 "splitpasswd",
296 "splitport",
300 "splitport",
297 "splituser",
301 "splituser",
298 "unquote",
302 "unquote",
299 "url2pathname",
303 "url2pathname",
300 "urlencode",
304 "urlencode",
301 ))
305 ))
302 urlreq._registeraliases(urllib2, (
306 urlreq._registeraliases(urllib2, (
303 "AbstractHTTPHandler",
307 "AbstractHTTPHandler",
304 "BaseHandler",
308 "BaseHandler",
305 "build_opener",
309 "build_opener",
306 "FileHandler",
310 "FileHandler",
307 "FTPHandler",
311 "FTPHandler",
308 "HTTPBasicAuthHandler",
312 "HTTPBasicAuthHandler",
309 "HTTPDigestAuthHandler",
313 "HTTPDigestAuthHandler",
310 "HTTPHandler",
314 "HTTPHandler",
311 "HTTPPasswordMgrWithDefaultRealm",
315 "HTTPPasswordMgrWithDefaultRealm",
312 "HTTPSHandler",
316 "HTTPSHandler",
313 "install_opener",
317 "install_opener",
314 "ProxyHandler",
318 "ProxyHandler",
315 "Request",
319 "Request",
316 "urlopen",
320 "urlopen",
317 ))
321 ))
318 urlerr._registeraliases(urllib2, (
322 urlerr._registeraliases(urllib2, (
319 "HTTPError",
323 "HTTPError",
320 "URLError",
324 "URLError",
321 ))
325 ))
322 httpserver._registeraliases(BaseHTTPServer, (
326 httpserver._registeraliases(BaseHTTPServer, (
323 "HTTPServer",
327 "HTTPServer",
324 "BaseHTTPRequestHandler",
328 "BaseHTTPRequestHandler",
325 ))
329 ))
326 httpserver._registeraliases(SimpleHTTPServer, (
330 httpserver._registeraliases(SimpleHTTPServer, (
327 "SimpleHTTPRequestHandler",
331 "SimpleHTTPRequestHandler",
328 ))
332 ))
329 httpserver._registeraliases(CGIHTTPServer, (
333 httpserver._registeraliases(CGIHTTPServer, (
330 "CGIHTTPRequestHandler",
334 "CGIHTTPRequestHandler",
331 ))
335 ))
332
336
333 else:
337 else:
334 import urllib.parse
338 import urllib.parse
335 urlreq._registeraliases(urllib.parse, (
339 urlreq._registeraliases(urllib.parse, (
336 "splitattr",
340 "splitattr",
337 "splitpasswd",
341 "splitpasswd",
338 "splitport",
342 "splitport",
339 "splituser",
343 "splituser",
340 "unquote",
341 ))
344 ))
345 urlreq._registeralias(urllib.parse, "unquote_to_bytes", "unquote")
342 import urllib.request
346 import urllib.request
343 urlreq._registeraliases(urllib.request, (
347 urlreq._registeraliases(urllib.request, (
344 "AbstractHTTPHandler",
348 "AbstractHTTPHandler",
345 "BaseHandler",
349 "BaseHandler",
346 "build_opener",
350 "build_opener",
347 "FileHandler",
351 "FileHandler",
348 "FTPHandler",
352 "FTPHandler",
349 "ftpwrapper",
353 "ftpwrapper",
350 "HTTPHandler",
354 "HTTPHandler",
351 "HTTPSHandler",
355 "HTTPSHandler",
352 "install_opener",
356 "install_opener",
353 "pathname2url",
357 "pathname2url",
354 "HTTPBasicAuthHandler",
358 "HTTPBasicAuthHandler",
355 "HTTPDigestAuthHandler",
359 "HTTPDigestAuthHandler",
356 "HTTPPasswordMgrWithDefaultRealm",
360 "HTTPPasswordMgrWithDefaultRealm",
357 "ProxyHandler",
361 "ProxyHandler",
358 "Request",
362 "Request",
359 "url2pathname",
363 "url2pathname",
360 "urlopen",
364 "urlopen",
361 ))
365 ))
362 import urllib.response
366 import urllib.response
363 urlreq._registeraliases(urllib.response, (
367 urlreq._registeraliases(urllib.response, (
364 "addclosehook",
368 "addclosehook",
365 "addinfourl",
369 "addinfourl",
366 ))
370 ))
367 import urllib.error
371 import urllib.error
368 urlerr._registeraliases(urllib.error, (
372 urlerr._registeraliases(urllib.error, (
369 "HTTPError",
373 "HTTPError",
370 "URLError",
374 "URLError",
371 ))
375 ))
372 import http.server
376 import http.server
373 httpserver._registeraliases(http.server, (
377 httpserver._registeraliases(http.server, (
374 "HTTPServer",
378 "HTTPServer",
375 "BaseHTTPRequestHandler",
379 "BaseHTTPRequestHandler",
376 "SimpleHTTPRequestHandler",
380 "SimpleHTTPRequestHandler",
377 "CGIHTTPRequestHandler",
381 "CGIHTTPRequestHandler",
378 ))
382 ))
379
383
380 # urllib.parse.quote() accepts both str and bytes, decodes bytes
384 # urllib.parse.quote() accepts both str and bytes, decodes bytes
381 # (if necessary), and returns str. This is wonky. We provide a custom
385 # (if necessary), and returns str. This is wonky. We provide a custom
382 # implementation that only accepts bytes and emits bytes.
386 # implementation that only accepts bytes and emits bytes.
383 def quote(s, safe=r'/'):
387 def quote(s, safe=r'/'):
384 s = urllib.parse.quote_from_bytes(s, safe=safe)
388 s = urllib.parse.quote_from_bytes(s, safe=safe)
385 return s.encode('ascii', 'strict')
389 return s.encode('ascii', 'strict')
386
390
387 urlreq.quote = quote
391 urlreq.quote = quote
General Comments 0
You need to be logged in to leave comments. Login now