Skip to content

Commit c5e856a

Browse files
committed
Merge branch 'main' of https://github.com/python/cpython
2 parents b6b5767 + f5a7037 commit c5e856a

File tree

7 files changed

+155
-35
lines changed

7 files changed

+155
-35
lines changed

Doc/c-api/arg.rst

+17-2
Original file line numberDiff line numberDiff line change
@@ -357,11 +357,26 @@ Other objects
357357

358358
.. versionadded:: 3.3
359359

360-
``(items)`` (:class:`tuple`) [*matching-items*]
361-
The object must be a Python sequence whose length is the number of format units
360+
``(items)`` (sequence) [*matching-items*]
361+
The object must be a Python sequence (except :class:`str`, :class:`bytes`
362+
or :class:`bytearray`) whose length is the number of format units
362363
in *items*. The C arguments must correspond to the individual format units in
363364
*items*. Format units for sequences may be nested.
364365

366+
If *items* contains format units which store a :ref:`borrowed buffer
367+
<c-arg-borrowed-buffer>` (``s``, ``s#``, ``z``, ``z#``, ``y``, or ``y#``)
368+
or a :term:`borrowed reference` (``S``, ``Y``, ``U``, ``O``, or ``O!``),
369+
the object must be a Python tuple.
370+
The *converter* for the ``O&`` format unit in *items* must not store
371+
a borrowed buffer or a borrowed reference.
372+
373+
.. versionchanged:: next
374+
:class:`str` and :class:`bytearray` objects no longer accepted as a sequence.
375+
376+
.. deprecated:: next
377+
Non-tuple sequences are deprecated if *items* contains format units
378+
which store a borrowed buffer or a borrowed reference.
379+
365380
A few other characters have a meaning in a format string. These may not occur
366381
inside nested parentheses. They are:
367382

Doc/whatsnew/3.14.rst

+7
Original file line numberDiff line numberDiff line change
@@ -1938,6 +1938,13 @@ Deprecated
19381938
:c:macro:`!isfinite` available from :file:`math.h`
19391939
since C99. (Contributed by Sergey B Kirpichev in :gh:`119613`.)
19401940

1941+
* Non-tuple sequences are deprecated as argument for the ``(items)``
1942+
format unit in :c:func:`PyArg_ParseTuple` and other
1943+
:ref:`argument parsing <arg-parsing>` functions if *items* contains
1944+
format units which store a :ref:`borrowed buffer <c-arg-borrowed-buffer>`
1945+
or a :term:`borrowed reference`.
1946+
(Contributed by Serhiy Storchaka in :gh:`50333`.)
1947+
19411948
* The previously undocumented function :c:func:`PySequence_In` is :term:`soft deprecated`.
19421949
Use :c:func:`PySequence_Contains` instead.
19431950
(Contributed by Yuki Kobayashi in :gh:`127896`.)

Lib/mimetypes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,7 @@ def _main(args=None):
706706
if guess:
707707
return f"type: {guess} encoding: {encoding}"
708708
sys.exit(f"error: media type unknown for {gtype}")
709-
return parser.format_help()
709+
return help_text
710710

711711

712712
if __name__ == '__main__':

Lib/test/test_capi/test_getargs.py

+49-10
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@
6363

6464
NULL = None
6565

66+
class CustomError(Exception):
67+
pass
68+
6669
class Index:
6770
def __index__(self):
6871
return 99
@@ -586,13 +589,13 @@ def test_tuple(self):
586589
ret = getargs_tuple(1, (2, 3))
587590
self.assertEqual(ret, (1,2,3))
588591

589-
# make sure invalid tuple arguments are handled correctly
590-
class seq:
592+
# make sure invalid sequence arguments are handled correctly
593+
class TestSeq:
591594
def __len__(self):
592595
return 2
593596
def __getitem__(self, n):
594-
raise ValueError
595-
self.assertRaises(TypeError, getargs_tuple, 1, seq())
597+
raise CustomError
598+
self.assertRaises(CustomError, getargs_tuple, 1, TestSeq())
596599

597600
class Keywords_TestCase(unittest.TestCase):
598601
def test_kwargs(self):
@@ -1320,33 +1323,69 @@ def test_nonascii_keywords(self):
13201323
f"this function got an unexpected keyword argument '{name2}'"):
13211324
parse((), {name2: 1, name3: 2}, '|OO', [name, name3])
13221325

1323-
def test_nested_tuple(self):
1326+
def test_nested_sequence(self):
13241327
parse = _testcapi.parse_tuple_and_keywords
13251328

13261329
self.assertEqual(parse(((1, 2, 3),), {}, '(OOO)', ['a']), (1, 2, 3))
13271330
self.assertEqual(parse((1, (2, 3), 4), {}, 'O(OO)O', ['a', 'b', 'c']),
13281331
(1, 2, 3, 4))
13291332
parse(((1, 2, 3),), {}, '(iii)', ['a'])
1333+
parse(([1, 2, 3],), {}, '(iii)', ['a'])
13301334

13311335
with self.assertRaisesRegex(TypeError,
1332-
"argument 1 must be sequence of length 2, not 3"):
1336+
"argument 1 must be tuple of length 2, not 3"):
13331337
parse(((1, 2, 3),), {}, '(ii)', ['a'])
13341338
with self.assertRaisesRegex(TypeError,
1335-
"argument 1 must be sequence of length 2, not 1"):
1339+
"argument 1 must be tuple of length 2, not 1"):
13361340
parse(((1,),), {}, '(ii)', ['a'])
13371341
with self.assertRaisesRegex(TypeError,
1338-
"argument 1 must be 2-item sequence, not int"):
1342+
"argument 1 must be sequence of length 2, not 3"):
1343+
parse(([1, 2, 3],), {}, '(ii)', ['a'])
1344+
with self.assertRaisesRegex(TypeError,
1345+
"argument 1 must be sequence of length 2, not 1"):
1346+
parse(([1,],), {}, '(ii)', ['a'])
1347+
with self.assertRaisesRegex(TypeError,
1348+
"argument 1 must be 2-item tuple, not int"):
13391349
parse((1,), {}, '(ii)', ['a'])
13401350
with self.assertRaisesRegex(TypeError,
1341-
"argument 1 must be 2-item sequence, not bytes"):
1351+
"argument 1 must be 2-item tuple, not None$"):
1352+
parse((None,), {}, '(ii)', ['a'])
1353+
with self.assertRaisesRegex(TypeError,
1354+
"argument 1 must be 2-item tuple, not str"):
1355+
parse(('ab',), {}, '(CC)', ['a'])
1356+
with self.assertRaisesRegex(TypeError,
1357+
"argument 1 must be 2-item tuple, not bytes"):
13421358
parse((b'ab',), {}, '(ii)', ['a'])
1359+
with self.assertRaisesRegex(TypeError,
1360+
"argument 1 must be 2-item tuple, not bytearray"):
1361+
parse((bytearray(b'ab'),), {}, '(ii)', ['a'])
1362+
with self.assertRaisesRegex(TypeError,
1363+
"argument 1 must be 2-item tuple, not dict"):
1364+
parse(({},), {}, '(ii)', ['a'])
1365+
1366+
with self.assertWarnsRegex(DeprecationWarning,
1367+
"argument must be 3-item tuple, not list"):
1368+
self.assertEqual(parse(([1, 2, 3],), {}, '(OOO)', ['a']), (1, 2, 3))
1369+
with self.assertWarnsRegex(DeprecationWarning,
1370+
"argument must be 2-item tuple, not list"):
1371+
with self.assertRaisesRegex(TypeError,
1372+
"argument 1 must be tuple of length 2, not 3"):
1373+
parse(([1, 2, 3],), {}, '(OO)', ['a'])
1374+
with self.assertWarnsRegex(DeprecationWarning,
1375+
"argument must be 2-item tuple, not list"):
1376+
with self.assertRaisesRegex(TypeError,
1377+
"argument 1 must be tuple of length 2, not 1"):
1378+
parse(([1,],), {}, '(OO)', ['a'])
13431379

13441380
for f in 'es', 'et', 'es#', 'et#':
13451381
with self.assertRaises(LookupError): # empty encoding ""
13461382
parse((('a',),), {}, '(' + f + ')', ['a'])
13471383
with self.assertRaisesRegex(TypeError,
1348-
"argument 1 must be sequence of length 1, not 0"):
1384+
"argument 1 must be tuple of length 1, not 0"):
13491385
parse(((),), {}, '(' + f + ')', ['a'])
1386+
with self.assertRaisesRegex(TypeError,
1387+
"argument 1 must be sequence of length 1, not 0"):
1388+
parse(([],), {}, '(' + f + ')', ['a'])
13501389

13511390
@unittest.skipIf(_testinternalcapi is None, 'needs _testinternalcapi')
13521391
def test_gh_119213(self):

Lib/test/test_mimetypes.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,6 @@ def test_parse_args(self):
417417
self.assertFalse(args.lenient)
418418
self.assertEqual(args.type, ["foo.pic"])
419419

420-
421420
def test_invocation(self):
422421
for command, expected in [
423422
("-l -e image/jpg", ".jpg"),
@@ -426,14 +425,14 @@ def test_invocation(self):
426425
]:
427426
self.assertEqual(mimetypes._main(shlex.split(command)), expected)
428427

429-
430428
def test_invocation_error(self):
431429
for command, expected in [
432430
("-e image/jpg", "error: unknown type image/jpg"),
433-
("foo.pic", "error: media type unknown for foo.pic"),
431+
("foo.bar_ext", "error: media type unknown for foo.bar_ext"),
434432
]:
435-
with self.assertRaisesRegex(SystemExit, expected):
436-
mimetypes._main(shlex.split(command))
433+
with self.subTest(command=command):
434+
with self.assertRaisesRegex(SystemExit, expected):
435+
mimetypes._main(shlex.split(command))
437436

438437

439438
if __name__ == "__main__":
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Non-tuple sequences are deprecated as argument for the ``(items)`` format
2+
unit in :c:func:`PyArg_ParseTuple` and other :ref:`argument parsing
3+
<arg-parsing>` functions if *items* contains format units which store
4+
a :ref:`borrowed buffer <c-arg-borrowed-buffer>` or
5+
a :term:`borrowed reference`.

Python/getargs.c

+72-17
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,8 @@ converttuple(PyObject *arg, const char **p_format, va_list *p_va, int flags,
466466
const char *format = *p_format;
467467
int i;
468468
Py_ssize_t len;
469+
int istuple = PyTuple_Check(arg);
470+
int mustbetuple = istuple;
469471

470472
for (;;) {
471473
int c = *format++;
@@ -481,51 +483,104 @@ converttuple(PyObject *arg, const char **p_format, va_list *p_va, int flags,
481483
}
482484
else if (c == ':' || c == ';' || c == '\0')
483485
break;
484-
else if (level == 0 && Py_ISALPHA(c) && c != 'e')
485-
n++;
486+
else {
487+
if (level == 0 && Py_ISALPHA(c)) {
488+
n++;
489+
}
490+
if (c == 'e' && (*format == 's' || *format == 't')) {
491+
format++;
492+
continue;
493+
}
494+
if (!mustbetuple) {
495+
switch (c) {
496+
case 'y':
497+
case 's':
498+
case 'z':
499+
if (*format != '*') {
500+
mustbetuple = 1;
501+
}
502+
break;
503+
case 'S':
504+
case 'Y':
505+
case 'U':
506+
mustbetuple = 1;
507+
break;
508+
case 'O':
509+
if (*format != '&') {
510+
mustbetuple = 1;
511+
}
512+
break;
513+
}
514+
}
515+
}
486516
}
487517

488-
if (!PySequence_Check(arg) || PyBytes_Check(arg)) {
518+
if (istuple) {
519+
/* fallthrough */
520+
}
521+
else if (!PySequence_Check(arg) ||
522+
PyUnicode_Check(arg) || PyBytes_Check(arg) || PyByteArray_Check(arg))
523+
{
489524
levels[0] = 0;
490525
PyOS_snprintf(msgbuf, bufsize,
491-
"must be %d-item sequence, not %.50s",
526+
"must be %d-item tuple, not %.50s",
492527
n,
493528
arg == Py_None ? "None" : Py_TYPE(arg)->tp_name);
494529
return msgbuf;
495530
}
531+
else {
532+
if (mustbetuple) {
533+
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 0,
534+
"argument must be %d-item tuple, not %T", n, arg))
535+
{
536+
return msgbuf;
537+
}
538+
}
539+
len = PySequence_Size(arg);
540+
if (len != n) {
541+
levels[0] = 0;
542+
PyOS_snprintf(msgbuf, bufsize,
543+
"must be %s of length %d, not %zd",
544+
mustbetuple ? "tuple" : "sequence", n, len);
545+
return msgbuf;
546+
}
547+
arg = PySequence_Tuple(arg);
548+
if (arg == NULL) {
549+
return msgbuf;
550+
}
551+
}
496552

497-
len = PySequence_Size(arg);
553+
len = PyTuple_GET_SIZE(arg);
498554
if (len != n) {
499555
levels[0] = 0;
500556
PyOS_snprintf(msgbuf, bufsize,
501-
"must be sequence of length %d, not %zd",
557+
"must be tuple of length %d, not %zd",
502558
n, len);
559+
if (!istuple) {
560+
Py_DECREF(arg);
561+
}
503562
return msgbuf;
504563
}
505564

506565
format = *p_format;
507566
for (i = 0; i < n; i++) {
508567
const char *msg;
509-
PyObject *item;
510-
item = PySequence_GetItem(arg, i);
511-
if (item == NULL) {
512-
PyErr_Clear();
513-
levels[0] = i+1;
514-
levels[1] = 0;
515-
strncpy(msgbuf, "is not retrievable", bufsize);
516-
return msgbuf;
517-
}
568+
PyObject *item = PyTuple_GET_ITEM(arg, i);
518569
msg = convertitem(item, &format, p_va, flags, levels+1,
519570
msgbuf, bufsize, freelist);
520-
/* PySequence_GetItem calls tp->sq_item, which INCREFs */
521-
Py_XDECREF(item);
522571
if (msg != NULL) {
523572
levels[0] = i+1;
573+
if (!istuple) {
574+
Py_DECREF(arg);
575+
}
524576
return msg;
525577
}
526578
}
527579

528580
*p_format = format;
581+
if (!istuple) {
582+
Py_DECREF(arg);
583+
}
529584
return NULL;
530585
}
531586

0 commit comments

Comments
 (0)