From b7699fb102adc17afca11fda3cb632d414f4c8f1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 12 Apr 2024 18:36:39 +0300 Subject: [PATCH 1/3] C API: Add support of nullable arguments in PyArg_Parse --- Modules/_ctypes/_ctypes.c | 11 ++-- Modules/_json.c | 13 ++--- Modules/_threadmodule.c | 14 ++--- Modules/mmapmodule.c | 3 +- Python/getargs.c | 107 +++++++++++++++++++++++--------------- 5 files changed, 76 insertions(+), 72 deletions(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 1ff108a39320cf..7d8d550d0cfc7c 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -3620,8 +3620,7 @@ _validate_paramflags(ctypes_state *st, PyTypeObject *type, PyObject *paramflags) PyObject *name = Py_None; PyObject *defval; PyObject *typ; - if (!PyArg_ParseTuple(item, "i|OO", &flag, &name, &defval) || - !(name == Py_None || PyUnicode_Check(name))) + if (!PyArg_ParseTuple(item, "i|?UO", &flag, &name, &defval)) { PyErr_SetString(PyExc_TypeError, "paramflags must be a sequence of (int [,string [,value]]) tuples"); @@ -3686,10 +3685,8 @@ PyCFuncPtr_FromDll(PyTypeObject *type, PyObject *args, PyObject *kwds) void *handle; PyObject *paramflags = NULL; - if (!PyArg_ParseTuple(args, "O|O", &ftuple, ¶mflags)) + if (!PyArg_ParseTuple(args, "O|?O", &ftuple, ¶mflags)) return NULL; - if (paramflags == Py_None) - paramflags = NULL; ftuple = PySequence_Tuple(ftuple); if (!ftuple) @@ -3804,10 +3801,8 @@ PyCFuncPtr_FromVtblIndex(PyTypeObject *type, PyObject *args, PyObject *kwds) GUID *iid = NULL; Py_ssize_t iid_len = 0; - if (!PyArg_ParseTuple(args, "is|Oz#", &index, &name, ¶mflags, &iid, &iid_len)) + if (!PyArg_ParseTuple(args, "is|?Oz#", &index, &name, ¶mflags, &iid, &iid_len)) return NULL; - if (paramflags == Py_None) - paramflags = NULL; ctypes_state *st = get_module_state_by_def(Py_TYPE(type)); if (!_validate_paramflags(st, type, paramflags)) { diff --git a/Modules/_json.c b/Modules/_json.c index c7fe1561bb1018..f53e461bc8e5bb 100644 --- a/Modules/_json.c +++ b/Modules/_json.c @@ -1208,23 +1208,16 @@ encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds) static char *kwlist[] = {"markers", "default", "encoder", "indent", "key_separator", "item_separator", "sort_keys", "skipkeys", "allow_nan", NULL}; PyEncoderObject *s; - PyObject *markers, *defaultfn, *encoder, *indent, *key_separator; + PyObject *markers = Py_None, *defaultfn, *encoder, *indent, *key_separator; PyObject *item_separator; int sort_keys, skipkeys, allow_nan; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOUUppp:make_encoder", kwlist, - &markers, &defaultfn, &encoder, &indent, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "?O!OOOUUppp:make_encoder", kwlist, + &PyDict_Type, &markers, &defaultfn, &encoder, &indent, &key_separator, &item_separator, &sort_keys, &skipkeys, &allow_nan)) return NULL; - if (markers != Py_None && !PyDict_Check(markers)) { - PyErr_Format(PyExc_TypeError, - "make_encoder() argument 1 must be dict or None, " - "not %.200s", Py_TYPE(markers)->tp_name); - return NULL; - } - s = (PyEncoderObject *)type->tp_alloc(type, 0); if (s == NULL) return NULL; diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 39d309729d88b8..8b8896059bebe2 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1878,10 +1878,10 @@ thread_PyThread_start_joinable_thread(PyObject *module, PyObject *fargs, PyObject *func = NULL; int daemon = 1; thread_module_state *state = get_thread_state(module); - PyObject *hobj = NULL; + PyObject *hobj = Py_None; if (!PyArg_ParseTupleAndKeywords(fargs, fkwargs, - "O|Op:start_joinable_thread", keywords, - &func, &hobj, &daemon)) { + "O|?O!p:start_joinable_thread", keywords, + &func, state->thread_handle_type, &hobj, &daemon)) { return NULL; } @@ -1891,14 +1891,6 @@ thread_PyThread_start_joinable_thread(PyObject *module, PyObject *fargs, return NULL; } - if (hobj == NULL) { - hobj = Py_None; - } - else if (hobj != Py_None && !Py_IS_TYPE(hobj, state->thread_handle_type)) { - PyErr_SetString(PyExc_TypeError, "'handle' must be a _ThreadHandle"); - return NULL; - } - if (PySys_Audit("_thread.start_joinable_thread", "OiO", func, daemon, hobj) < 0) { return NULL; diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index 99a85e9e49ad47..d034b64e2cc11a 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -23,7 +23,6 @@ #endif #include -#include "pycore_abstract.h" // _Py_convert_optional_to_ssize_t() #include "pycore_bytesobject.h" // _PyBytes_Find() #include "pycore_fileutils.h" // _Py_stat_struct @@ -512,7 +511,7 @@ mmap_read_method(mmap_object *self, Py_ssize_t num_bytes = PY_SSIZE_T_MAX, remaining; CHECK_VALID(NULL); - if (!PyArg_ParseTuple(args, "|O&:read", _Py_convert_optional_to_ssize_t, &num_bytes)) + if (!PyArg_ParseTuple(args, "|?n:read", &num_bytes)) return NULL; CHECK_VALID(NULL); diff --git a/Python/getargs.c b/Python/getargs.c index b96ce3a22dae7c..7d1ad6fb710154 100644 --- a/Python/getargs.c +++ b/Python/getargs.c @@ -21,6 +21,7 @@ PyAPI_FUNC(int) _PyArg_VaParseTupleAndKeywords_SizeT(PyObject *, PyObject *, const char *, const char * const *, va_list); #define FLAG_COMPAT 1 +#define FLAG_NULLABLE 4 typedef int (*destr_t)(PyObject *, void *); @@ -487,8 +488,9 @@ converttuple(PyObject *arg, const char **p_format, va_list *p_va, int flags, if (!PySequence_Check(arg) || PyBytes_Check(arg)) { levels[0] = 0; PyOS_snprintf(msgbuf, bufsize, - "must be %d-item sequence, not %.50s", + "must be %d-item sequence%s, not %.50s", n, + (flags & FLAG_NULLABLE) ? " or None" : "", arg == Py_None ? "None" : Py_TYPE(arg)->tp_name); return msgbuf; } @@ -502,6 +504,7 @@ converttuple(PyObject *arg, const char **p_format, va_list *p_va, int flags, return msgbuf; } + flags &= ~FLAG_NULLABLE; format = *p_format; for (i = 0; i < n; i++) { const char *msg; @@ -538,6 +541,20 @@ convertitem(PyObject *arg, const char **p_format, va_list *p_va, int flags, const char *msg; const char *format = *p_format; + if (*format == '?') { + format++; + if (arg == Py_None) { + msg = skipitem(&format, p_va, flags); + if (msg == NULL) { + *p_format = format; + } + else { + levels[0] = 0; + } + return msg; + } + flags |= FLAG_NULLABLE; + } if (*format == '(' /* ')' */) { format++; msg = converttuple(arg, &format, p_va, flags, levels, msgbuf, @@ -573,7 +590,7 @@ _PyArg_BadArgument(const char *fname, const char *displayname, } static const char * -converterr(const char *expected, PyObject *arg, char *msgbuf, size_t bufsize) +converterr(int flags, const char *expected, PyObject *arg, char *msgbuf, size_t bufsize) { assert(expected != NULL); assert(arg != NULL); @@ -583,20 +600,25 @@ converterr(const char *expected, PyObject *arg, char *msgbuf, size_t bufsize) } else { PyOS_snprintf(msgbuf, bufsize, - "must be %.50s, not %.50s", expected, + "must be %.50s%s, not %.50s", expected, + ((flags & FLAG_NULLABLE) && !strstr(expected, " or None")) + ? " or None" : "", arg == Py_None ? "None" : Py_TYPE(arg)->tp_name); } return msgbuf; } static const char * -convertcharerr(const char *expected, const char *what, Py_ssize_t size, +convertcharerr(int flags, const char *expected, const char *what, Py_ssize_t size, char *msgbuf, size_t bufsize) { assert(expected != NULL); PyOS_snprintf(msgbuf, bufsize, - "must be %.50s, not %.50s of length %zd", - expected, what, size); + "must be %.50s%s, not %.50s of length %zd", + expected, + ((flags & FLAG_NULLABLE) && !strstr(expected, " or None")) + ? " or None" : "", + what, size); return msgbuf; } @@ -747,7 +769,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, if (PyLong_Check(arg)) ival = PyLong_AsUnsignedLongMask(arg); else - return converterr("int", arg, msgbuf, bufsize); + return converterr(flags, "int", arg, msgbuf, bufsize); *p = ival; break; } @@ -768,7 +790,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, if (PyLong_Check(arg)) ival = PyLong_AsUnsignedLongLongMask(arg); else - return converterr("int", arg, msgbuf, bufsize); + return converterr(flags, "int", arg, msgbuf, bufsize); *p = ival; break; } @@ -808,7 +830,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, char *p = va_arg(*p_va, char *); if (PyBytes_Check(arg)) { if (PyBytes_GET_SIZE(arg) != 1) { - return convertcharerr("a byte string of length 1", + return convertcharerr(flags, "a byte string of length 1", "a bytes object", PyBytes_GET_SIZE(arg), msgbuf, bufsize); } @@ -816,14 +838,14 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, } else if (PyByteArray_Check(arg)) { if (PyByteArray_GET_SIZE(arg) != 1) { - return convertcharerr("a byte string of length 1", + return convertcharerr(flags, "a byte string of length 1", "a bytearray object", PyByteArray_GET_SIZE(arg), msgbuf, bufsize); } *p = PyByteArray_AS_STRING(arg)[0]; } else - return converterr("a byte string of length 1", arg, msgbuf, bufsize); + return converterr(flags, "a byte string of length 1", arg, msgbuf, bufsize); break; } @@ -833,10 +855,10 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, const void *data; if (!PyUnicode_Check(arg)) - return converterr("a unicode character", arg, msgbuf, bufsize); + return converterr(flags, "a unicode character", arg, msgbuf, bufsize); if (PyUnicode_GET_LENGTH(arg) != 1) { - return convertcharerr("a unicode character", + return convertcharerr(flags, "a unicode character", "a string", PyUnicode_GET_LENGTH(arg), msgbuf, bufsize); } @@ -868,18 +890,18 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, Py_ssize_t count; if (*format == '*') { if (getbuffer(arg, (Py_buffer*)p, &buf) < 0) - return converterr(buf, arg, msgbuf, bufsize); + return converterr(flags, buf, arg, msgbuf, bufsize); format++; if (addcleanup(p, freelist, cleanup_buffer)) { return converterr( - "(cleanup problem)", + flags, "(cleanup problem)", arg, msgbuf, bufsize); } break; } count = convertbuffer(arg, (const void **)p, &buf); if (count < 0) - return converterr(buf, arg, msgbuf, bufsize); + return converterr(flags, buf, arg, msgbuf, bufsize); if (*format == '#') { Py_ssize_t *psize = va_arg(*p_va, Py_ssize_t*); *psize = count; @@ -906,18 +928,18 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, Py_ssize_t len; sarg = PyUnicode_AsUTF8AndSize(arg, &len); if (sarg == NULL) - return converterr(CONV_UNICODE, + return converterr(flags, CONV_UNICODE, arg, msgbuf, bufsize); PyBuffer_FillInfo(p, arg, (void *)sarg, len, 1, 0); } else { /* any bytes-like object */ const char *buf; if (getbuffer(arg, p, &buf) < 0) - return converterr(buf, arg, msgbuf, bufsize); + return converterr(flags, buf, arg, msgbuf, bufsize); } if (addcleanup(p, freelist, cleanup_buffer)) { return converterr( - "(cleanup problem)", + flags, "(cleanup problem)", arg, msgbuf, bufsize); } format++; @@ -934,7 +956,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, Py_ssize_t len; sarg = PyUnicode_AsUTF8AndSize(arg, &len); if (sarg == NULL) - return converterr(CONV_UNICODE, + return converterr(flags, CONV_UNICODE, arg, msgbuf, bufsize); *p = sarg; *psize = len; @@ -944,7 +966,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, const char *buf; Py_ssize_t count = convertbuffer(arg, p, &buf); if (count < 0) - return converterr(buf, arg, msgbuf, bufsize); + return converterr(flags, buf, arg, msgbuf, bufsize); *psize = count; } format++; @@ -959,7 +981,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, else if (PyUnicode_Check(arg)) { sarg = PyUnicode_AsUTF8AndSize(arg, &len); if (sarg == NULL) - return converterr(CONV_UNICODE, + return converterr(flags, CONV_UNICODE, arg, msgbuf, bufsize); if (strlen(sarg) != (size_t)len) { PyErr_SetString(PyExc_ValueError, "embedded null character"); @@ -968,7 +990,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, *p = sarg; } else - return converterr(c == 'z' ? "str or None" : "str", + return converterr(flags, c == 'z' ? "str or None" : "str", arg, msgbuf, bufsize); } break; @@ -997,12 +1019,12 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, recode_strings = 0; else return converterr( - "(unknown parser marker combination)", + flags, "(unknown parser marker combination)", arg, msgbuf, bufsize); buffer = (char **)va_arg(*p_va, char **); format++; if (buffer == NULL) - return converterr("(buffer is NULL)", + return converterr(flags, "(buffer is NULL)", arg, msgbuf, bufsize); /* Encode object */ @@ -1024,7 +1046,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, encoding, NULL); if (s == NULL) - return converterr("(encoding failed)", + return converterr(flags, "(encoding failed)", arg, msgbuf, bufsize); assert(PyBytes_Check(s)); size = PyBytes_GET_SIZE(s); @@ -1034,7 +1056,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, } else { return converterr( - recode_strings ? "str" : "str, bytes or bytearray", + flags, recode_strings ? "str" : "str, bytes or bytearray", arg, msgbuf, bufsize); } @@ -1067,7 +1089,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, if (psize == NULL) { Py_DECREF(s); return converterr( - "(buffer_len is NULL)", + flags, "(buffer_len is NULL)", arg, msgbuf, bufsize); } if (*buffer == NULL) { @@ -1080,7 +1102,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, if (addcleanup(buffer, freelist, cleanup_ptr)) { Py_DECREF(s); return converterr( - "(cleanup problem)", + flags, "(cleanup problem)", arg, msgbuf, bufsize); } } else { @@ -1114,7 +1136,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, if ((Py_ssize_t)strlen(ptr) != size) { Py_DECREF(s); return converterr( - "encoded string without null bytes", + flags, "encoded string without null bytes", arg, msgbuf, bufsize); } *buffer = PyMem_NEW(char, size + 1); @@ -1125,7 +1147,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, } if (addcleanup(buffer, freelist, cleanup_ptr)) { Py_DECREF(s); - return converterr("(cleanup problem)", + return converterr(flags, "(cleanup problem)", arg, msgbuf, bufsize); } memcpy(*buffer, ptr, size+1); @@ -1139,7 +1161,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, if (PyBytes_Check(arg)) *p = arg; else - return converterr("bytes", arg, msgbuf, bufsize); + return converterr(flags, "bytes", arg, msgbuf, bufsize); break; } @@ -1148,7 +1170,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, if (PyByteArray_Check(arg)) *p = arg; else - return converterr("bytearray", arg, msgbuf, bufsize); + return converterr(flags, "bytearray", arg, msgbuf, bufsize); break; } @@ -1158,7 +1180,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, *p = arg; } else - return converterr("str", arg, msgbuf, bufsize); + return converterr(flags, "str", arg, msgbuf, bufsize); break; } @@ -1172,7 +1194,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, if (PyType_IsSubtype(Py_TYPE(arg), type)) *p = arg; else - return converterr(type->tp_name, arg, msgbuf, bufsize); + return converterr(flags, type->tp_name, arg, msgbuf, bufsize); } else if (*format == '&') { @@ -1182,11 +1204,11 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, int res; format++; if (! (res = (*convert)(arg, addr))) - return converterr("(unspecified)", + return converterr(flags, "(unspecified)", arg, msgbuf, bufsize); if (res == Py_CLEANUP_SUPPORTED && addcleanup(addr, freelist, convert) == -1) - return converterr("(cleanup problem)", + return converterr(flags, "(cleanup problem)", arg, msgbuf, bufsize); } else { @@ -1202,7 +1224,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, if (*format != '*') return converterr( - "(invalid use of 'w' format character)", + flags, "(invalid use of 'w' format character)", arg, msgbuf, bufsize); format++; @@ -1211,20 +1233,20 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, result is C-contiguous with format 'B'. */ if (PyObject_GetBuffer(arg, (Py_buffer*)p, PyBUF_WRITABLE) < 0) { PyErr_Clear(); - return converterr("read-write bytes-like object", + return converterr(flags, "read-write bytes-like object", arg, msgbuf, bufsize); } assert(PyBuffer_IsContiguous((Py_buffer *)p, 'C')); if (addcleanup(p, freelist, cleanup_buffer)) { return converterr( - "(cleanup problem)", + flags, "(cleanup problem)", arg, msgbuf, bufsize); } break; } default: - return converterr("(impossible)", arg, msgbuf, bufsize); + return converterr(flags, "(impossible)", arg, msgbuf, bufsize); } @@ -2630,6 +2652,9 @@ skipitem(const char **p_format, va_list *p_va, int flags) const char *format = *p_format; char c = *format++; + if (c == '?') { + c = *format++; + } switch (c) { /* From 9eda73e762e65e39cc2695e8c147fbb4afa078a8 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 3 Jul 2024 10:08:28 +0300 Subject: [PATCH 2/3] Add tests. --- Lib/test/test_capi/test_getargs.py | 65 ++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/Lib/test/test_capi/test_getargs.py b/Lib/test/test_capi/test_getargs.py index 232aa2a80025dc..8cbaa841983700 100644 --- a/Lib/test/test_capi/test_getargs.py +++ b/Lib/test/test_capi/test_getargs.py @@ -1352,6 +1352,71 @@ def test_nested_tuple(self): "argument 1 must be sequence of length 1, not 0"): parse(((),), {}, '(' + f + ')', ['a']) + def test_nullable(self): + parse = _testcapi.parse_tuple_and_keywords + + def check(format, arg, allows_none=False): + # Because some format units (such as y*) require cleanup, + # we force the parsing code to perform the cleanup by adding + # an argument that always fails. + # By checking for an exception, we ensure that the parsing + # of the first argument was successful. + self.assertRaises(OverflowError, parse, + (arg, 256), {}, '?' + format + 'b', ['a', 'b']) + self.assertRaises(OverflowError, parse, + (None, 256), {}, '?' + format + 'b', ['a', 'b']) + self.assertRaises(OverflowError, parse, + (arg, 256), {}, format + 'b', ['a', 'b']) + self.assertRaises(OverflowError if allows_none else TypeError, parse, + (None, 256), {}, format + 'b', ['a', 'b']) + + check('b', 42) + check('B', 42) + check('h', 42) + check('H', 42) + check('i', 42) + check('I', 42) + check('n', 42) + check('l', 42) + check('k', 42) + check('L', 42) + check('K', 42) + check('f', 2.5) + check('d', 2.5) + check('D', 2.5j) + check('c', b'a') + check('C', 'a') + check('p', True, allows_none=True) + check('y', b'buffer') + check('y*', b'buffer') + check('y#', b'buffer') + check('s', 'string') + check('s*', 'string') + check('s#', 'string') + check('z', 'string', allows_none=True) + check('z*', 'string', allows_none=True) + check('z#', 'string', allows_none=True) + check('w*', bytearray(b'buffer')) + check('U', 'string') + check('S', b'bytes') + check('Y', bytearray(b'bytearray')) + check('O', object, allows_none=True) + + check('(OO)', (1, 2)) + self.assertEqual(parse((((1, 2), 3),), {}, '(?(OO)O)', ['a']), (1, 2, 3)) + self.assertEqual(parse(((None, 3),), {}, '(?(OO)O)', ['a']), (NULL, NULL, 3)) + self.assertEqual(parse((((1, 2), 3),), {}, '((OO)O)', ['a']), (1, 2, 3)) + self.assertRaises(TypeError, parse, ((None, 3),), {}, '((OO)O)', ['a']) + + parse((None,), {}, '?es', ['a']) + parse((None,), {}, '?es#', ['a']) + parse((None,), {}, '?et', ['a']) + parse((None,), {}, '?et#', ['a']) + parse((None,), {}, '?O!', ['a']) + parse((None,), {}, '?O&', ['a']) + + # TODO: More tests for es?, es#?, et?, et#?, O!, O& + @unittest.skipIf(_testinternalcapi is None, 'needs _testinternalcapi') def test_gh_119213(self): rc, out, err = script_helper.assert_python_ok("-c", """if True: From 2707238bb78cd0235e0e7d059e5b63079c8e94ce Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 3 Jul 2024 13:43:41 +0300 Subject: [PATCH 3/3] Add more tests. --- Lib/test/test_capi/test_getargs.py | 52 ++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/Lib/test/test_capi/test_getargs.py b/Lib/test/test_capi/test_getargs.py index 8cbaa841983700..ba6be36ef35c8c 100644 --- a/Lib/test/test_capi/test_getargs.py +++ b/Lib/test/test_capi/test_getargs.py @@ -1352,6 +1352,58 @@ def test_nested_tuple(self): "argument 1 must be sequence of length 1, not 0"): parse(((),), {}, '(' + f + ')', ['a']) + def test_specific_type_errors(self): + parse = _testcapi.parse_tuple_and_keywords + + def check(format, arg, expected, got='list'): + errmsg = f'must be {expected}, not {got}' + with self.assertRaisesRegex(TypeError, errmsg): + parse((arg,), {}, format, ['a']) + + check('k', [], 'int') + check('?k', [], 'int or None') + check('K', [], 'int') + check('?K', [], 'int or None') + check('c', [], 'a byte string of length 1') + check('?c', [], 'a byte string of length 1 or None') + check('c', b'abc', 'a byte string of length 1', + 'a bytes object of length 3') + check('?c', b'abc', 'a byte string of length 1 or None', + 'a bytes object of length 3') + check('c', bytearray(b'abc'), 'a byte string of length 1', + 'a bytearray object of length 3') + check('?c', bytearray(b'abc'), 'a byte string of length 1 or None', + 'a bytearray object of length 3') + check('C', [], 'a unicode character') + check('?C', [], 'a unicode character or None') + check('C', 'abc', 'a unicode character', + 'a string of length 3') + check('?C', 'abc', 'a unicode character or None', + 'a string of length 3') + check('s', [], 'str') + check('?s', [], 'str or None') + check('z', [], 'str or None') + check('?z', [], 'str or None') + check('es', [], 'str') + check('?es', [], 'str or None') + check('es#', [], 'str') + check('?es#', [], 'str or None') + check('et', [], 'str, bytes or bytearray') + check('?et', [], 'str, bytes or bytearray or None') + check('et#', [], 'str, bytes or bytearray') + check('?et#', [], 'str, bytes or bytearray or None') + check('w*', [], 'read-write bytes-like object') + check('?w*', [], 'read-write bytes-like object or None') + check('S', [], 'bytes') + check('?S', [], 'bytes or None') + check('U', [], 'str') + check('?U', [], 'str or None') + check('Y', [], 'bytearray') + check('?Y', [], 'bytearray or None') + check('(OO)', 42, '2-item sequence', 'int') + check('?(OO)', 42, '2-item sequence or None', 'int') + check('(OO)', (1, 2, 3), 'sequence of length 2', '3') + def test_nullable(self): parse = _testcapi.parse_tuple_and_keywords pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy