Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7284e0e

Browse files
gh-110815: Support non-ASCII keyword names in PyArg_ParseTupleAndKeywords() (GH-110816)
It already mostly worked, except in the case when invalid keyword argument with non-ASCII name was passed to function with non-ASCII parameter names. Then it crashed in the debug mode.
1 parent ce298a1 commit 7284e0e

File tree

5 files changed

+64
-3
lines changed

5 files changed

+64
-3
lines changed

Doc/c-api/arg.rst

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -416,8 +416,10 @@ API Functions
416416
.. c:function:: int PyArg_ParseTupleAndKeywords(PyObject *args, PyObject *kw, const char *format, char *keywords[], ...)
417417
418418
Parse the parameters of a function that takes both positional and keyword
419-
parameters into local variables. The *keywords* argument is a
420-
``NULL``-terminated array of keyword parameter names. Empty names denote
419+
parameters into local variables.
420+
The *keywords* argument is a ``NULL``-terminated array of keyword parameter
421+
names specified as null-terminated ASCII or UTF-8 encoded C strings.
422+
Empty names denote
421423
:ref:`positional-only parameters <positional-only_parameter>`.
422424
Returns true on success; on failure, it returns false and raises the
423425
appropriate exception.
@@ -426,6 +428,9 @@ API Functions
426428
Added support for :ref:`positional-only parameters
427429
<positional-only_parameter>`.
428430
431+
.. versionchanged:: 3.13
432+
Added support for non-ASCII keyword parameter names.
433+
429434
430435
.. c:function:: int PyArg_VaParseTupleAndKeywords(PyObject *args, PyObject *kw, const char *format, char *keywords[], va_list vargs)
431436

Doc/whatsnew/3.13.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,10 @@ New Features
10451045
but pass event arguments as a Python :class:`tuple` object.
10461046
(Contributed by Victor Stinner in :gh:`85283`.)
10471047

1048+
* :c:func:`PyArg_ParseTupleAndKeywords` now supports non-ASCII keyword
1049+
parameter names.
1050+
(Contributed by Serhiy Storchaka in :gh:`110815`.)
1051+
10481052
Porting to Python 3.13
10491053
----------------------
10501054

Lib/test/test_capi/test_getargs.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,6 +1235,57 @@ def test_positional_only(self):
12351235
with self.assertRaisesRegex(SystemError, 'Empty keyword'):
12361236
parse((1,), {}, 'O|OO', ['', 'a', ''])
12371237

1238+
def test_nonascii_keywords(self):
1239+
parse = _testcapi.parse_tuple_and_keywords
1240+
1241+
for name in ('a', 'ä', 'ŷ', '㷷', '𐀀'):
1242+
with self.subTest(name=name):
1243+
self.assertEqual(parse((), {name: 1}, 'O', [name]), (1,))
1244+
self.assertEqual(parse((), {}, '|O', [name]), (NULL,))
1245+
with self.assertRaisesRegex(TypeError,
1246+
f"function missing required argument '{name}'"):
1247+
parse((), {}, 'O', [name])
1248+
with self.assertRaisesRegex(TypeError,
1249+
fr"argument for function given by name \('{name}'\) "
1250+
fr"and position \(1\)"):
1251+
parse((1,), {name: 2}, 'O|O', [name, 'b'])
1252+
with self.assertRaisesRegex(TypeError,
1253+
f"'{name}' is an invalid keyword argument"):
1254+
parse((), {name: 1}, '|O', ['b'])
1255+
with self.assertRaisesRegex(TypeError,
1256+
"'b' is an invalid keyword argument"):
1257+
parse((), {'b': 1}, '|O', [name])
1258+
1259+
invalid = name.encode() + (name.encode()[:-1] or b'\x80')
1260+
self.assertEqual(parse((), {}, '|O', [invalid]), (NULL,))
1261+
self.assertEqual(parse((1,), {'b': 2}, 'O|O', [invalid, 'b']),
1262+
(1, 2))
1263+
with self.assertRaisesRegex(TypeError,
1264+
f"function missing required argument '{name}\ufffd'"):
1265+
parse((), {}, 'O', [invalid])
1266+
with self.assertRaisesRegex(UnicodeDecodeError,
1267+
f"'utf-8' codec can't decode bytes? "):
1268+
parse((), {'b': 1}, '|OO', [invalid, 'b'])
1269+
with self.assertRaisesRegex(UnicodeDecodeError,
1270+
f"'utf-8' codec can't decode bytes? "):
1271+
parse((), {'b': 1}, '|O', [invalid])
1272+
1273+
for name2 in ('b', 'ë', 'ĉ', 'Ɐ', '𐀁'):
1274+
with self.subTest(name2=name2):
1275+
with self.assertRaisesRegex(TypeError,
1276+
f"'{name2}' is an invalid keyword argument"):
1277+
parse((), {name2: 1}, '|O', [name])
1278+
1279+
name2 = name.encode().decode('latin1')
1280+
if name2 != name:
1281+
with self.assertRaisesRegex(TypeError,
1282+
f"'{name2}' is an invalid keyword argument"):
1283+
parse((), {name2: 1}, '|O', [name])
1284+
name3 = name + '3'
1285+
with self.assertRaisesRegex(TypeError,
1286+
f"'{name2}' is an invalid keyword argument"):
1287+
parse((), {name2: 1, name3: 2}, '|OO', [name, name3])
1288+
12381289

12391290
class Test_testcapi(unittest.TestCase):
12401291
locals().update((name, getattr(_testcapi, name))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support non-ASCII keyword names in :c:func:`PyArg_ParseTupleAndKeywords`.

Python/getargs.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1729,7 +1729,7 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
17291729
return cleanreturn(0, &freelist);
17301730
}
17311731
for (i = pos; i < len; i++) {
1732-
if (_PyUnicode_EqualToASCIIString(key, kwlist[i])) {
1732+
if (PyUnicode_EqualToUTF8(key, kwlist[i])) {
17331733
match = 1;
17341734
break;
17351735
}

0 commit comments

Comments
 (0)