Skip to content

Commit 805daa2

Browse files
[3.14] Revert "gh-112068: C API: Add support of nullable arguments in PyArg_Parse (GH-121303)" (GH-136991) (#137006)
1 parent c328d14 commit 805daa2

File tree

12 files changed

+139
-317
lines changed

12 files changed

+139
-317
lines changed

Doc/c-api/arg.rst

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -113,18 +113,14 @@ There are three ways strings and buffers can be converted to C:
113113
``z`` (:class:`str` or ``None``) [const char \*]
114114
Like ``s``, but the Python object may also be ``None``, in which case the C
115115
pointer is set to ``NULL``.
116-
It is the same as ``s?`` with the C pointer was initialized to ``NULL``.
117116

118117
``z*`` (:class:`str`, :term:`bytes-like object` or ``None``) [Py_buffer]
119118
Like ``s*``, but the Python object may also be ``None``, in which case the
120119
``buf`` member of the :c:type:`Py_buffer` structure is set to ``NULL``.
121-
It is the same as ``s*?`` with the ``buf`` member of the :c:type:`Py_buffer`
122-
structure was initialized to ``NULL``.
123120

124121
``z#`` (:class:`str`, read-only :term:`bytes-like object` or ``None``) [const char \*, :c:type:`Py_ssize_t`]
125122
Like ``s#``, but the Python object may also be ``None``, in which case the C
126123
pointer is set to ``NULL``.
127-
It is the same as ``s#?`` with the C pointer was initialized to ``NULL``.
128124

129125
``y`` (read-only :term:`bytes-like object`) [const char \*]
130126
This format converts a bytes-like object to a C pointer to a
@@ -387,17 +383,6 @@ Other objects
387383
Non-tuple sequences are deprecated if *items* contains format units
388384
which store a borrowed buffer or a borrowed reference.
389385

390-
``unit?`` (anything or ``None``) [*matching-variable(s)*]
391-
``?`` modifies the behavior of the preceding format unit.
392-
The C variable(s) corresponding to that parameter should be initialized
393-
to their default value --- when the argument is ``None``,
394-
:c:func:`PyArg_ParseTuple` does not touch the contents of the corresponding
395-
C variable(s).
396-
If the argument is not ``None``, it is parsed according to the specified
397-
format unit.
398-
399-
.. versionadded:: 3.14
400-
401386
A few other characters have a meaning in a format string. These may not occur
402387
inside nested parentheses. They are:
403388

Doc/whatsnew/3.14.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2940,11 +2940,6 @@ New features
29402940
file.
29412941
(Contributed by Victor Stinner in :gh:`127350`.)
29422942

2943-
* Add support of nullable arguments in :c:func:`PyArg_ParseTuple` and
2944-
similar functions.
2945-
Adding ``?`` after any format unit makes ``None`` be accepted as a value.
2946-
(Contributed by Serhiy Storchaka in :gh:`112068`.)
2947-
29482943
* The ``k`` and ``K`` formats in :c:func:`PyArg_ParseTuple` and
29492944
similar functions now use :meth:`~object.__index__` if available,
29502945
like all other integer formats.

Lib/test/test_capi/test_getargs.py

Lines changed: 0 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,123 +1389,6 @@ def test_nested_sequence(self):
13891389
"argument 1 must be sequence of length 1, not 0"):
13901390
parse(([],), {}, '(' + f + ')', ['a'])
13911391

1392-
def test_specific_type_errors(self):
1393-
parse = _testcapi.parse_tuple_and_keywords
1394-
1395-
def check(format, arg, expected, got='list'):
1396-
errmsg = f'must be {expected}, not {got}'
1397-
with self.assertRaisesRegex(TypeError, errmsg):
1398-
parse((arg,), {}, format, ['a'])
1399-
1400-
check('k', [], 'int')
1401-
check('k?', [], 'int or None')
1402-
check('K', [], 'int')
1403-
check('K?', [], 'int or None')
1404-
check('c', [], 'a byte string of length 1')
1405-
check('c?', [], 'a byte string of length 1 or None')
1406-
check('c', b'abc', 'a byte string of length 1',
1407-
'a bytes object of length 3')
1408-
check('c?', b'abc', 'a byte string of length 1 or None',
1409-
'a bytes object of length 3')
1410-
check('c', bytearray(b'abc'), 'a byte string of length 1',
1411-
'a bytearray object of length 3')
1412-
check('c?', bytearray(b'abc'), 'a byte string of length 1 or None',
1413-
'a bytearray object of length 3')
1414-
check('C', [], 'a unicode character')
1415-
check('C?', [], 'a unicode character or None')
1416-
check('C', 'abc', 'a unicode character',
1417-
'a string of length 3')
1418-
check('C?', 'abc', 'a unicode character or None',
1419-
'a string of length 3')
1420-
check('s', [], 'str')
1421-
check('s?', [], 'str or None')
1422-
check('z', [], 'str or None')
1423-
check('z?', [], 'str or None')
1424-
check('es', [], 'str')
1425-
check('es?', [], 'str or None')
1426-
check('es#', [], 'str')
1427-
check('es#?', [], 'str or None')
1428-
check('et', [], 'str, bytes or bytearray')
1429-
check('et?', [], 'str, bytes, bytearray or None')
1430-
check('et#', [], 'str, bytes or bytearray')
1431-
check('et#?', [], 'str, bytes, bytearray or None')
1432-
check('w*', [], 'read-write bytes-like object')
1433-
check('w*?', [], 'read-write bytes-like object or None')
1434-
check('S', [], 'bytes')
1435-
check('S?', [], 'bytes or None')
1436-
check('U', [], 'str')
1437-
check('U?', [], 'str or None')
1438-
check('Y', [], 'bytearray')
1439-
check('Y?', [], 'bytearray or None')
1440-
check('(OO)', 42, '2-item tuple', 'int')
1441-
check('(OO)?', 42, '2-item tuple or None', 'int')
1442-
check('(OO)', (1, 2, 3), 'tuple of length 2', '3')
1443-
1444-
def test_nullable(self):
1445-
parse = _testcapi.parse_tuple_and_keywords
1446-
1447-
def check(format, arg, allows_none=False):
1448-
# Because some format units (such as y*) require cleanup,
1449-
# we force the parsing code to perform the cleanup by adding
1450-
# an argument that always fails.
1451-
# By checking for an exception, we ensure that the parsing
1452-
# of the first argument was successful.
1453-
self.assertRaises(OverflowError, parse,
1454-
(arg, 256), {}, format + '?b', ['a', 'b'])
1455-
self.assertRaises(OverflowError, parse,
1456-
(None, 256), {}, format + '?b', ['a', 'b'])
1457-
self.assertRaises(OverflowError, parse,
1458-
(arg, 256), {}, format + 'b', ['a', 'b'])
1459-
self.assertRaises(OverflowError if allows_none else TypeError, parse,
1460-
(None, 256), {}, format + 'b', ['a', 'b'])
1461-
1462-
check('b', 42)
1463-
check('B', 42)
1464-
check('h', 42)
1465-
check('H', 42)
1466-
check('i', 42)
1467-
check('I', 42)
1468-
check('n', 42)
1469-
check('l', 42)
1470-
check('k', 42)
1471-
check('L', 42)
1472-
check('K', 42)
1473-
check('f', 2.5)
1474-
check('d', 2.5)
1475-
check('D', 2.5j)
1476-
check('c', b'a')
1477-
check('C', 'a')
1478-
check('p', True, allows_none=True)
1479-
check('y', b'buffer')
1480-
check('y*', b'buffer')
1481-
check('y#', b'buffer')
1482-
check('s', 'string')
1483-
check('s*', 'string')
1484-
check('s#', 'string')
1485-
check('z', 'string', allows_none=True)
1486-
check('z*', 'string', allows_none=True)
1487-
check('z#', 'string', allows_none=True)
1488-
check('w*', bytearray(b'buffer'))
1489-
check('U', 'string')
1490-
check('S', b'bytes')
1491-
check('Y', bytearray(b'bytearray'))
1492-
check('O', object, allows_none=True)
1493-
1494-
check('(OO)', (1, 2))
1495-
self.assertEqual(parse((((1, 2), 3),), {}, '((OO)?O)', ['a']), (1, 2, 3))
1496-
self.assertEqual(parse(((None, 3),), {}, '((OO)?O)', ['a']), (NULL, NULL, 3))
1497-
self.assertEqual(parse((((1, 2), 3),), {}, '((OO)O)', ['a']), (1, 2, 3))
1498-
self.assertRaises(TypeError, parse, ((None, 3),), {}, '((OO)O)', ['a'])
1499-
1500-
parse((None,), {}, 'es?', ['a'])
1501-
parse((None,), {}, 'es#?', ['a'])
1502-
parse((None,), {}, 'et?', ['a'])
1503-
parse((None,), {}, 'et#?', ['a'])
1504-
parse((None,), {}, 'O!?', ['a'])
1505-
parse((None,), {}, 'O&?', ['a'])
1506-
1507-
# TODO: More tests for es?, es#?, et?, et#?, O!, O&
1508-
15091392
@unittest.skipIf(_testinternalcapi is None, 'needs _testinternalcapi')
15101393
def test_gh_119213(self):
15111394
rc, out, err = script_helper.assert_python_ok("-c", """if True:

Lib/test/test_mmap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ def test_tagname(self):
732732
m2.close()
733733
m1.close()
734734

735-
with self.assertRaisesRegex(TypeError, 'must be str or None'):
735+
with self.assertRaisesRegex(TypeError, 'tagname'):
736736
mmap.mmap(-1, 8, tagname=1)
737737

738738
@cpython_only

Misc/NEWS.d/3.14.0b1.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2012,7 +2012,7 @@ interpreter.
20122012
.. nonce: ofI5Fl
20132013
.. section: C API
20142014
2015-
Add support of nullable arguments in :c:func:`PyArg_Parse` and similar
2015+
[Reverted in :gh:`136991`] Add support of nullable arguments in :c:func:`PyArg_Parse` and similar
20162016
functions. Adding ``?`` after any format unit makes ``None`` be accepted as
20172017
a value.
20182018

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Revert support of nullable arguments in :c:func:`PyArg_Parse`.

Modules/_ctypes/_ctypes.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3918,7 +3918,9 @@ _validate_paramflags(ctypes_state *st, PyTypeObject *type, PyObject *paramflags)
39183918
PyObject *name = Py_None;
39193919
PyObject *defval;
39203920
PyObject *typ;
3921-
if (!PyArg_ParseTuple(item, "i|U?O", &flag, &name, &defval)) {
3921+
if (!PyArg_ParseTuple(item, "i|OO", &flag, &name, &defval) ||
3922+
!(name == Py_None || PyUnicode_Check(name)))
3923+
{
39223924
PyErr_SetString(PyExc_TypeError,
39233925
"paramflags must be a sequence of (int [,string [,value]]) tuples");
39243926
return 0;
@@ -3983,8 +3985,10 @@ PyCFuncPtr_FromDll(PyTypeObject *type, PyObject *args, PyObject *kwds)
39833985
void *handle;
39843986
PyObject *paramflags = NULL;
39853987

3986-
if (!PyArg_ParseTuple(args, "O|O?", &ftuple, &paramflags))
3988+
if (!PyArg_ParseTuple(args, "O|O", &ftuple, &paramflags))
39873989
return NULL;
3990+
if (paramflags == Py_None)
3991+
paramflags = NULL;
39883992

39893993
ftuple = PySequence_Tuple(ftuple);
39903994
if (!ftuple)
@@ -4116,8 +4120,10 @@ PyCFuncPtr_FromVtblIndex(PyTypeObject *type, PyObject *args, PyObject *kwds)
41164120
GUID *iid = NULL;
41174121
Py_ssize_t iid_len = 0;
41184122

4119-
if (!PyArg_ParseTuple(args, "is|O?z#", &index, &name, &paramflags, &iid, &iid_len))
4123+
if (!PyArg_ParseTuple(args, "is|Oz#", &index, &name, &paramflags, &iid, &iid_len))
41204124
return NULL;
4125+
if (paramflags == Py_None)
4126+
paramflags = NULL;
41214127

41224128
ctypes_state *st = get_module_state_by_def(Py_TYPE(type));
41234129
if (!_validate_paramflags(st, type, paramflags)) {

Modules/_interpretersmodule.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,11 +1415,14 @@ interp_get_config(PyObject *self, PyObject *args, PyObject *kwds)
14151415
PyObject *idobj = NULL;
14161416
int restricted = 0;
14171417
if (!PyArg_ParseTupleAndKeywords(args, kwds,
1418-
"O?|$p:get_config", kwlist,
1418+
"O|$p:get_config", kwlist,
14191419
&idobj, &restricted))
14201420
{
14211421
return NULL;
14221422
}
1423+
if (idobj == Py_None) {
1424+
idobj = NULL;
1425+
}
14231426

14241427
int reqready = 0;
14251428
PyInterpreterState *interp = \
@@ -1536,14 +1539,14 @@ capture_exception(PyObject *self, PyObject *args, PyObject *kwds)
15361539
static char *kwlist[] = {"exc", NULL};
15371540
PyObject *exc_arg = NULL;
15381541
if (!PyArg_ParseTupleAndKeywords(args, kwds,
1539-
"|O?:capture_exception", kwlist,
1542+
"|O:capture_exception", kwlist,
15401543
&exc_arg))
15411544
{
15421545
return NULL;
15431546
}
15441547

15451548
PyObject *exc = exc_arg;
1546-
if (exc == NULL) {
1549+
if (exc == NULL || exc == Py_None) {
15471550
exc = PyErr_GetRaisedException();
15481551
if (exc == NULL) {
15491552
Py_RETURN_NONE;

Modules/_json.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,16 +1228,23 @@ encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
12281228
static char *kwlist[] = {"markers", "default", "encoder", "indent", "key_separator", "item_separator", "sort_keys", "skipkeys", "allow_nan", NULL};
12291229

12301230
PyEncoderObject *s;
1231-
PyObject *markers = Py_None, *defaultfn, *encoder, *indent, *key_separator;
1231+
PyObject *markers, *defaultfn, *encoder, *indent, *key_separator;
12321232
PyObject *item_separator;
12331233
int sort_keys, skipkeys, allow_nan;
12341234

1235-
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!?OOOUUppp:make_encoder", kwlist,
1236-
&PyDict_Type, &markers, &defaultfn, &encoder, &indent,
1235+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOUUppp:make_encoder", kwlist,
1236+
&markers, &defaultfn, &encoder, &indent,
12371237
&key_separator, &item_separator,
12381238
&sort_keys, &skipkeys, &allow_nan))
12391239
return NULL;
12401240

1241+
if (markers != Py_None && !PyDict_Check(markers)) {
1242+
PyErr_Format(PyExc_TypeError,
1243+
"make_encoder() argument 1 must be dict or None, "
1244+
"not %.200s", Py_TYPE(markers)->tp_name);
1245+
return NULL;
1246+
}
1247+
12411248
s = (PyEncoderObject *)type->tp_alloc(type, 0);
12421249
if (s == NULL)
12431250
return NULL;

Modules/_threadmodule.c

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -667,12 +667,12 @@ PyThreadHandleObject_join(PyObject *op, PyObject *args)
667667
PyThreadHandleObject *self = PyThreadHandleObject_CAST(op);
668668

669669
PyObject *timeout_obj = NULL;
670-
if (!PyArg_ParseTuple(args, "|O?:join", &timeout_obj)) {
670+
if (!PyArg_ParseTuple(args, "|O:join", &timeout_obj)) {
671671
return NULL;
672672
}
673673

674674
PyTime_t timeout_ns = -1;
675-
if (timeout_obj != NULL) {
675+
if (timeout_obj != NULL && timeout_obj != Py_None) {
676676
if (_PyTime_FromSecondsObject(&timeout_ns, timeout_obj,
677677
_PyTime_ROUND_TIMEOUT) < 0) {
678678
return NULL;
@@ -1945,10 +1945,10 @@ thread_PyThread_start_joinable_thread(PyObject *module, PyObject *fargs,
19451945
PyObject *func = NULL;
19461946
int daemon = 1;
19471947
thread_module_state *state = get_thread_state(module);
1948-
PyObject *hobj = Py_None;
1948+
PyObject *hobj = NULL;
19491949
if (!PyArg_ParseTupleAndKeywords(fargs, fkwargs,
1950-
"O|O!?p:start_joinable_thread", keywords,
1951-
&func, state->thread_handle_type, &hobj, &daemon)) {
1950+
"O|Op:start_joinable_thread", keywords,
1951+
&func, &hobj, &daemon)) {
19521952
return NULL;
19531953
}
19541954

@@ -1958,6 +1958,14 @@ thread_PyThread_start_joinable_thread(PyObject *module, PyObject *fargs,
19581958
return NULL;
19591959
}
19601960

1961+
if (hobj == NULL) {
1962+
hobj = Py_None;
1963+
}
1964+
else if (hobj != Py_None && !Py_IS_TYPE(hobj, state->thread_handle_type)) {
1965+
PyErr_SetString(PyExc_TypeError, "'handle' must be a _ThreadHandle");
1966+
return NULL;
1967+
}
1968+
19611969
if (PySys_Audit("_thread.start_joinable_thread", "OiO", func, daemon,
19621970
hobj) < 0) {
19631971
return NULL;

0 commit comments

Comments
 (0)