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

Skip to content

gh-110815: Support non-ASCII keyword names in PyArg_ParseTupleAndKeywords() #110816

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions Doc/c-api/arg.rst
Original file line number Diff line number Diff line change
Expand Up @@ -416,8 +416,10 @@ API Functions
.. c:function:: int PyArg_ParseTupleAndKeywords(PyObject *args, PyObject *kw, const char *format, char *keywords[], ...)

Parse the parameters of a function that takes both positional and keyword
parameters into local variables. The *keywords* argument is a
``NULL``-terminated array of keyword parameter names. Empty names denote
parameters into local variables.
The *keywords* argument is a ``NULL``-terminated array of keyword parameter
names specified as null-terminated ASCII or UTF-8 encoded C strings.
Empty names denote
:ref:`positional-only parameters <positional-only_parameter>`.
Returns true on success; on failure, it returns false and raises the
appropriate exception.
Expand All @@ -426,6 +428,9 @@ API Functions
Added support for :ref:`positional-only parameters
<positional-only_parameter>`.

.. versionchanged:: 3.13
Added support for non-ASCII keyword parameter names.


.. c:function:: int PyArg_VaParseTupleAndKeywords(PyObject *args, PyObject *kw, const char *format, char *keywords[], va_list vargs)

Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,10 @@ New Features
but pass event arguments as a Python :class:`tuple` object.
(Contributed by Victor Stinner in :gh:`85283`.)

* :c:func:`PyArg_ParseTupleAndKeywords` now supports non-ASCII keyword
parameter names.
(Contributed by Serhiy Storchaka in :gh:`110815`.)

Porting to Python 3.13
----------------------

Expand Down
51 changes: 51 additions & 0 deletions Lib/test/test_capi/test_getargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1235,6 +1235,57 @@ def test_positional_only(self):
with self.assertRaisesRegex(SystemError, 'Empty keyword'):
parse((1,), {}, 'O|OO', ['', 'a', ''])

def test_nonascii_keywords(self):
parse = _testcapi.parse_tuple_and_keywords

for name in ('a', 'ä', 'ŷ', '㷷', '𐀀'):
with self.subTest(name=name):
self.assertEqual(parse((), {name: 1}, 'O', [name]), (1,))
self.assertEqual(parse((), {}, '|O', [name]), (NULL,))
with self.assertRaisesRegex(TypeError,
f"function missing required argument '{name}'"):
parse((), {}, 'O', [name])
with self.assertRaisesRegex(TypeError,
fr"argument for function given by name \('{name}'\) "
fr"and position \(1\)"):
parse((1,), {name: 2}, 'O|O', [name, 'b'])
with self.assertRaisesRegex(TypeError,
f"'{name}' is an invalid keyword argument"):
parse((), {name: 1}, '|O', ['b'])
with self.assertRaisesRegex(TypeError,
"'b' is an invalid keyword argument"):
parse((), {'b': 1}, '|O', [name])

invalid = name.encode() + (name.encode()[:-1] or b'\x80')
self.assertEqual(parse((), {}, '|O', [invalid]), (NULL,))
self.assertEqual(parse((1,), {'b': 2}, 'O|O', [invalid, 'b']),
(1, 2))
with self.assertRaisesRegex(TypeError,
f"function missing required argument '{name}\ufffd'"):
parse((), {}, 'O', [invalid])
with self.assertRaisesRegex(UnicodeDecodeError,
f"'utf-8' codec can't decode bytes? "):
parse((), {'b': 1}, '|OO', [invalid, 'b'])
with self.assertRaisesRegex(UnicodeDecodeError,
f"'utf-8' codec can't decode bytes? "):
parse((), {'b': 1}, '|O', [invalid])

for name2 in ('b', 'ë', 'ĉ', 'Ɐ', '𐀁'):
with self.subTest(name2=name2):
with self.assertRaisesRegex(TypeError,
f"'{name2}' is an invalid keyword argument"):
parse((), {name2: 1}, '|O', [name])

name2 = name.encode().decode('latin1')
if name2 != name:
with self.assertRaisesRegex(TypeError,
f"'{name2}' is an invalid keyword argument"):
parse((), {name2: 1}, '|O', [name])
name3 = name + '3'
with self.assertRaisesRegex(TypeError,
f"'{name2}' is an invalid keyword argument"):
parse((), {name2: 1, name3: 2}, '|OO', [name, name3])


class Test_testcapi(unittest.TestCase):
locals().update((name, getattr(_testcapi, name))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support non-ASCII keyword names in :c:func:`PyArg_ParseTupleAndKeywords`.
2 changes: 1 addition & 1 deletion Python/getargs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1729,7 +1729,7 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
return cleanreturn(0, &freelist);
}
for (i = pos; i < len; i++) {
if (_PyUnicode_EqualToASCIIString(key, kwlist[i])) {
if (PyUnicode_EqualToUTF8(key, kwlist[i])) {
match = 1;
break;
}
Expand Down