Skip to content

Commit c469d4c

Browse files
Issue 600362: Relocated parse_qs() and parse_qsl(), from the cgi module
to the urlparse one. Added a DeprecationWarning in the old module, it will be deprecated in the future. Docs and tests updated.
1 parent 849f79a commit c469d4c

File tree

7 files changed

+159
-131
lines changed

7 files changed

+159
-131
lines changed

Doc/library/cgi.rst

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -257,57 +257,26 @@ algorithms implemented in this module in other circumstances.
257257

258258
Parse a query in the environment or from a file (the file defaults to
259259
``sys.stdin``). The *keep_blank_values* and *strict_parsing* parameters are
260-
passed to :func:`parse_qs` unchanged.
260+
passed to :func:`urllib.parse.parse_qs` unchanged.
261261

262262

263263
.. function:: parse_qs(qs[, keep_blank_values[, strict_parsing]])
264264

265-
Parse a query string given as a string argument (data of type
266-
:mimetype:`application/x-www-form-urlencoded`). Data are returned as a
267-
dictionary. The dictionary keys are the unique query variable names and the
268-
values are lists of values for each name.
269-
270-
The optional argument *keep_blank_values* is a flag indicating whether blank
271-
values in URL encoded queries should be treated as blank strings. A true value
272-
indicates that blanks should be retained as blank strings. The default false
273-
value indicates that blank values are to be ignored and treated as if they were
274-
not included.
275-
276-
The optional argument *strict_parsing* is a flag indicating what to do with
277-
parsing errors. If false (the default), errors are silently ignored. If true,
278-
errors raise a :exc:`ValueError` exception.
279-
280-
Use the :func:`urllib.parse.urlencode` function to convert such dictionaries into
281-
query strings.
282-
265+
This function is deprecated in this module. Use :func:`urllib.parse.parse_qs`
266+
instead. It is maintained here only for backward compatiblity.
283267

284268
.. function:: parse_qsl(qs[, keep_blank_values[, strict_parsing]])
285269

286-
Parse a query string given as a string argument (data of type
287-
:mimetype:`application/x-www-form-urlencoded`). Data are returned as a list of
288-
name, value pairs.
289-
290-
The optional argument *keep_blank_values* is a flag indicating whether blank
291-
values in URL encoded queries should be treated as blank strings. A true value
292-
indicates that blanks should be retained as blank strings. The default false
293-
value indicates that blank values are to be ignored and treated as if they were
294-
not included.
295-
296-
The optional argument *strict_parsing* is a flag indicating what to do with
297-
parsing errors. If false (the default), errors are silently ignored. If true,
298-
errors raise a :exc:`ValueError` exception.
299-
300-
Use the :func:`urllib.parse.urlencode` function to convert such lists of pairs into
301-
query strings.
302-
270+
This function is deprecated in this module. Use :func:`urllib.parse.parse_qs`
271+
instead. It is maintained here only for backward compatiblity.
303272

304273
.. function:: parse_multipart(fp, pdict)
305274

306275
Parse input of type :mimetype:`multipart/form-data` (for file uploads).
307276
Arguments are *fp* for the input file and *pdict* for a dictionary containing
308277
other parameters in the :mailheader:`Content-Type` header.
309278

310-
Returns a dictionary just like :func:`parse_qs` keys are the field names, each
279+
Returns a dictionary just like :func:`urllib.parse.parse_qs` keys are the field names, each
311280
value is a list of values for that field. This is easy to use but not much good
312281
if you are expecting megabytes to be uploaded --- in that case, use the
313282
:class:`FieldStorage` class instead which is much more flexible.

Doc/library/urllib.parse.rst

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,47 @@ The :mod:`urllib.parse` module defines the following functions:
8989
object.
9090

9191

92+
.. function:: parse_qs(qs[, keep_blank_values[, strict_parsing]])
93+
94+
Parse a query string given as a string argument (data of type
95+
:mimetype:`application/x-www-form-urlencoded`). Data are returned as a
96+
dictionary. The dictionary keys are the unique query variable names and the
97+
values are lists of values for each name.
98+
99+
The optional argument *keep_blank_values* is a flag indicating whether blank
100+
values in URL encoded queries should be treated as blank strings. A true value
101+
indicates that blanks should be retained as blank strings. The default false
102+
value indicates that blank values are to be ignored and treated as if they were
103+
not included.
104+
105+
The optional argument *strict_parsing* is a flag indicating what to do with
106+
parsing errors. If false (the default), errors are silently ignored. If true,
107+
errors raise a :exc:`ValueError` exception.
108+
109+
Use the :func:`urllib.urlencode` function to convert such dictionaries into
110+
query strings.
111+
112+
113+
.. function:: parse_qsl(qs[, keep_blank_values[, strict_parsing]])
114+
115+
Parse a query string given as a string argument (data of type
116+
:mimetype:`application/x-www-form-urlencoded`). Data are returned as a list of
117+
name, value pairs.
118+
119+
The optional argument *keep_blank_values* is a flag indicating whether blank
120+
values in URL encoded queries should be treated as blank strings. A true value
121+
indicates that blanks should be retained as blank strings. The default false
122+
value indicates that blank values are to be ignored and treated as if they were
123+
not included.
124+
125+
The optional argument *strict_parsing* is a flag indicating what to do with
126+
parsing errors. If false (the default), errors are silently ignored. If true,
127+
errors raise a :exc:`ValueError` exception.
128+
129+
Use the :func:`urllib.parse.urlencode` function to convert such lists of pairs into
130+
query strings.
131+
132+
92133
.. function:: urlunparse(parts)
93134

94135
Construct a URL from a tuple as returned by ``urlparse()``. The *parts*
@@ -273,7 +314,7 @@ The :mod:`urllib.parse` module defines the following functions:
273314
of the sequence. When a sequence of two-element tuples is used as the *query*
274315
argument, the first element of each tuple is a key and the second is a value.
275316
The order of parameters in the encoded string will match the order of parameter
276-
tuples in the sequence. The :mod:`cgi` module provides the functions
317+
tuples in the sequence. This module provides the functions
277318
:func:`parse_qs` and :func:`parse_qsl` which are used to parse query strings
278319
into Python data structures.
279320

Lib/cgi.py

Lines changed: 17 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import os
3838
import urllib.parse
3939
import email.parser
40+
from warnings import warn
4041

4142
__all__ = ["MiniFieldStorage", "FieldStorage",
4243
"parse", "parse_qs", "parse_qsl", "parse_multipart",
@@ -153,75 +154,23 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
153154
else:
154155
qs = ""
155156
environ['QUERY_STRING'] = qs # XXX Shouldn't, really
156-
return parse_qs(qs, keep_blank_values, strict_parsing)
157+
return urllib.parse.parse_qs(qs, keep_blank_values, strict_parsing)
157158

158159

159-
def parse_qs(qs, keep_blank_values=0, strict_parsing=0):
160-
"""Parse a query given as a string argument.
161-
162-
Arguments:
160+
# parse query string function called from urlparse,
161+
# this is done in order to maintain backward compatiblity.
163162

164-
qs: URL-encoded query string to be parsed
165-
166-
keep_blank_values: flag indicating whether blank values in
167-
URL encoded queries should be treated as blank strings.
168-
A true value indicates that blanks should be retained as
169-
blank strings. The default false value indicates that
170-
blank values are to be ignored and treated as if they were
171-
not included.
172-
173-
strict_parsing: flag indicating what to do with parsing errors.
174-
If false (the default), errors are silently ignored.
175-
If true, errors raise a ValueError exception.
176-
"""
177-
dict = {}
178-
for name, value in parse_qsl(qs, keep_blank_values, strict_parsing):
179-
if name in dict:
180-
dict[name].append(value)
181-
else:
182-
dict[name] = [value]
183-
return dict
163+
def parse_qs(qs, keep_blank_values=0, strict_parsing=0):
164+
"""Parse a query given as a string argument."""
165+
warn("cgi.parse_qs is deprecated, use urllib.parse.parse_qs instead",
166+
DeprecationWarning)
167+
return urllib.parse.parse_qs(qs, keep_blank_values, strict_parsing)
184168

185169
def parse_qsl(qs, keep_blank_values=0, strict_parsing=0):
186-
"""Parse a query given as a string argument.
187-
188-
Arguments:
189-
190-
qs: URL-encoded query string to be parsed
191-
192-
keep_blank_values: flag indicating whether blank values in
193-
URL encoded queries should be treated as blank strings. A
194-
true value indicates that blanks should be retained as blank
195-
strings. The default false value indicates that blank values
196-
are to be ignored and treated as if they were not included.
197-
198-
strict_parsing: flag indicating what to do with parsing errors. If
199-
false (the default), errors are silently ignored. If true,
200-
errors raise a ValueError exception.
201-
202-
Returns a list, as G-d intended.
203-
"""
204-
pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
205-
r = []
206-
for name_value in pairs:
207-
if not name_value and not strict_parsing:
208-
continue
209-
nv = name_value.split('=', 1)
210-
if len(nv) != 2:
211-
if strict_parsing:
212-
raise ValueError("bad query field: %r" % (name_value,))
213-
# Handle case of a control-name with no equal sign
214-
if keep_blank_values:
215-
nv.append('')
216-
else:
217-
continue
218-
if len(nv[1]) or keep_blank_values:
219-
name = urllib.parse.unquote(nv[0].replace('+', ' '))
220-
value = urllib.parse.unquote(nv[1].replace('+', ' '))
221-
r.append((name, value))
222-
223-
return r
224-
170+
"""Parse a query given as a string argument."""
171+
warn("cgi.parse_qsl is deprecated, use urllib.parse.parse_qs instead",
172+
DeprecationWarning)
173+
return urllib.parse.parse_qsl(qs, keep_blank_values, strict_parsing)
225174

226175
def parse_multipart(fp, pdict):
227176
"""Parse multipart input.
@@ -624,8 +573,8 @@ def read_urlencoded(self):
624573
if self.qs_on_post:
625574
qs += '&' + self.qs_on_post
626575
self.list = list = []
627-
for key, value in parse_qsl(qs, self.keep_blank_values,
628-
self.strict_parsing):
576+
for key, value in urllib.parse.parse_qsl(qs, self.keep_blank_values,
577+
self.strict_parsing):
629578
list.append(MiniFieldStorage(key, value))
630579
self.skip_lines()
631580

@@ -638,8 +587,8 @@ def read_multi(self, environ, keep_blank_values, strict_parsing):
638587
raise ValueError('Invalid boundary in multipart form: %r' % (ib,))
639588
self.list = []
640589
if self.qs_on_post:
641-
for key, value in parse_qsl(self.qs_on_post, self.keep_blank_values,
642-
self.strict_parsing):
590+
for key, value in urllib.parse.parse_qsl(self.qs_on_post,
591+
self.keep_blank_values, self.strict_parsing):
643592
self.list.append(MiniFieldStorage(key, value))
644593
FieldStorageClass = None
645594

Lib/test/test_cgi.py

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -53,25 +53,6 @@ def do_test(buf, method):
5353
except Exception as err:
5454
return ComparableException(err)
5555

56-
# A list of test cases. Each test case is a a two-tuple that contains
57-
# a string with the query and a dictionary with the expected result.
58-
59-
parse_qsl_test_cases = [
60-
("", []),
61-
("&", []),
62-
("&&", []),
63-
("=", [('', '')]),
64-
("=a", [('', 'a')]),
65-
("a", [('a', '')]),
66-
("a=", [('a', '')]),
67-
("a=", [('a', '')]),
68-
("&a=b", [('a', 'b')]),
69-
("a=a+b&b=b+c", [('a', 'a b'), ('b', 'b c')]),
70-
("a=1&a=2", [('a', '1'), ('a', '2')]),
71-
("a=%26&b=%3D", [('a', '&'), ('b', '=')]),
72-
("a=%C3%BC&b=%CA%83", [('a', '\xfc'), ('b', '\u0283')]),
73-
]
74-
7556
parse_strict_test_cases = [
7657
("", ValueError("bad query field: ''")),
7758
("&", ValueError("bad query field: ''")),
@@ -141,11 +122,6 @@ def gen_result(data, environ):
141122

142123
class CgiTests(unittest.TestCase):
143124

144-
def test_qsl(self):
145-
for orig, expect in parse_qsl_test_cases:
146-
result = cgi.parse_qsl(orig, keep_blank_values=True)
147-
self.assertEqual(result, expect, "Error parsing %s" % repr(orig))
148-
149125
def test_strict(self):
150126
for orig, expect in parse_strict_test_cases:
151127
# Test basic parsing

Lib/test/test_urlparse.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,23 @@
88
RFC2396_BASE = "http://a/b/c/d;p?q"
99
RFC3986_BASE = "http://a/b/c/d;p?q"
1010

11+
# A list of test cases. Each test case is a a two-tuple that contains
12+
# a string with the query and a dictionary with the expected result.
13+
14+
parse_qsl_test_cases = [
15+
("", []),
16+
("&", []),
17+
("&&", []),
18+
("=", [('', '')]),
19+
("=a", [('', 'a')]),
20+
("a", [('a', '')]),
21+
("a=", [('a', '')]),
22+
("a=", [('a', '')]),
23+
("&a=b", [('a', 'b')]),
24+
("a=a+b&b=b+c", [('a', 'a b'), ('b', 'b c')]),
25+
("a=1&a=2", [('a', '1'), ('a', '2')]),
26+
]
27+
1128
class UrlParseTestCase(unittest.TestCase):
1229

1330
def checkRoundtrips(self, url, parsed, split):
@@ -61,6 +78,12 @@ def checkRoundtrips(self, url, parsed, split):
6178
self.assertEqual(result3.hostname, result.hostname)
6279
self.assertEqual(result3.port, result.port)
6380

81+
def test_qsl(self):
82+
for orig, expect in parse_qsl_test_cases:
83+
result = urllib.parse.parse_qsl(orig, keep_blank_values=True)
84+
self.assertEqual(result, expect, "Error parsing %s" % repr(orig))
85+
86+
6487
def test_roundtrips(self):
6588
testcases = [
6689
('file:///tmp/junk.txt',

Lib/urllib/parse.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import collections
99

1010
__all__ = ["urlparse", "urlunparse", "urljoin", "urldefrag",
11-
"urlsplit", "urlunsplit",
11+
"urlsplit", "urlunsplit", "parse_qs", "parse_qsl",
1212
"quote", "quote_plus", "quote_from_bytes",
1313
"unquote", "unquote_plus", "unquote_to_bytes"]
1414

@@ -329,6 +329,72 @@ def unquote(string, encoding='utf-8', errors='replace'):
329329
res[-1] = b''.join(pct_sequence).decode(encoding, errors)
330330
return ''.join(res)
331331

332+
def parse_qs(qs, keep_blank_values=0, strict_parsing=0):
333+
"""Parse a query given as a string argument.
334+
335+
Arguments:
336+
337+
qs: URL-encoded query string to be parsed
338+
339+
keep_blank_values: flag indicating whether blank values in
340+
URL encoded queries should be treated as blank strings.
341+
A true value indicates that blanks should be retained as
342+
blank strings. The default false value indicates that
343+
blank values are to be ignored and treated as if they were
344+
not included.
345+
346+
strict_parsing: flag indicating what to do with parsing errors.
347+
If false (the default), errors are silently ignored.
348+
If true, errors raise a ValueError exception.
349+
"""
350+
dict = {}
351+
for name, value in parse_qsl(qs, keep_blank_values, strict_parsing):
352+
if name in dict:
353+
dict[name].append(value)
354+
else:
355+
dict[name] = [value]
356+
return dict
357+
358+
def parse_qsl(qs, keep_blank_values=0, strict_parsing=0):
359+
"""Parse a query given as a string argument.
360+
361+
Arguments:
362+
363+
qs: URL-encoded query string to be parsed
364+
365+
keep_blank_values: flag indicating whether blank values in
366+
URL encoded queries should be treated as blank strings. A
367+
true value indicates that blanks should be retained as blank
368+
strings. The default false value indicates that blank values
369+
are to be ignored and treated as if they were not included.
370+
371+
strict_parsing: flag indicating what to do with parsing errors. If
372+
false (the default), errors are silently ignored. If true,
373+
errors raise a ValueError exception.
374+
375+
Returns a list, as G-d intended.
376+
"""
377+
pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
378+
r = []
379+
for name_value in pairs:
380+
if not name_value and not strict_parsing:
381+
continue
382+
nv = name_value.split('=', 1)
383+
if len(nv) != 2:
384+
if strict_parsing:
385+
raise ValueError("bad query field: %r" % (name_value,))
386+
# Handle case of a control-name with no equal sign
387+
if keep_blank_values:
388+
nv.append('')
389+
else:
390+
continue
391+
if len(nv[1]) or keep_blank_values:
392+
name = unquote(nv[0].replace('+', ' '))
393+
value = unquote(nv[1].replace('+', ' '))
394+
r.append((name, value))
395+
396+
return r
397+
332398
def unquote_plus(string, encoding='utf-8', errors='replace'):
333399
"""Like unquote(), but also replace plus signs by spaces, as required for
334400
unquoting HTML form values.

0 commit comments

Comments
 (0)