From a6014dc2ea5ed59dc6714bf7c02f2aa8d91d1784 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 22 Aug 2026 10:35:47 +0300 Subject: [PATCH] gh-155496: Use Argument Clinic for more functions of the _csv module --- Modules/_csv.c | 190 ++++++++++++++++++------------- Modules/clinic/_csv.c.h | 246 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 353 insertions(+), 83 deletions(-) diff --git a/Modules/_csv.c b/Modules/_csv.c index 6af66c3f09a03b1..34548b943856931 100644 --- a/Modules/_csv.c +++ b/Modules/_csv.c @@ -21,10 +21,9 @@ module instead. /*[clinic input] module _csv +class _csv.Dialect "DialectObj *" "clinic_state()->dialect_type" [clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=385118b71aa43706]*/ - -#include "clinic/_csv.c.h" +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=042fcc14c7d541cc]*/ #define NOT_SET ((Py_UCS4)-1) #define EOL ((Py_UCS4)-2) @@ -125,6 +124,8 @@ typedef struct { bool nonascii_special; } DialectObj; +#include "clinic/_csv.c.h" + typedef struct { PyObject_HEAD @@ -631,43 +632,62 @@ dialect_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) /* Since dialect is now a heap type, it inherits pickling method for * protocol 0 and 1 from object, therefore it needs to be overridden */ -PyDoc_STRVAR(dialect_reduce_doc, "raises an exception to avoid pickling"); +/*[clinic input] +_csv.Dialect.__reduce__ + +Raise an exception to avoid pickling. +[clinic start generated code]*/ static PyObject * -Dialect_reduce(PyObject *self, PyObject *args) { +_csv_Dialect___reduce___impl(DialectObj *self) +/*[clinic end generated code: output=f728b34af509ed69 input=e46419b46279e480]*/ +{ PyErr_Format(PyExc_TypeError, "cannot pickle '%.100s' instances", _PyType_Name(Py_TYPE(self))); return NULL; } -PyDoc_STRVAR(dialect_replace_doc, -"__replace__($self, /, **changes)\n" -"--\n" -"\n" -"Return a copy of the dialect with the specified options replaced."); +/*[clinic input] +_csv.Dialect.__reduce_ex__ + + protocol: object + / + +Raise an exception to avoid pickling. +[clinic start generated code]*/ static PyObject * -Dialect_replace(PyObject *self, PyObject *args, PyObject *kwargs) +_csv_Dialect___reduce_ex___impl(DialectObj *self, PyObject *protocol) +/*[clinic end generated code: output=d45dec397da6575a input=c6ebd579e8959040]*/ +{ + return _csv_Dialect___reduce___impl(self); +} + +/*[clinic input] +_csv.Dialect.__replace__ + + **changes: dict + +Return a copy of the dialect with the specified options replaced. +[clinic start generated code]*/ + +static PyObject * +_csv_Dialect___replace___impl(DialectObj *self, PyObject *changes) +/*[clinic end generated code: output=8f692c2c63a61b50 input=154beb565fbabd4c]*/ { - if (PyTuple_GET_SIZE(args) != 0) { - PyErr_SetString(PyExc_TypeError, - "__replace__() takes no positional arguments"); - return NULL; - } PyObject *newargs = PyTuple_Pack(1, self); if (newargs == NULL) { return NULL; } - PyObject *result = dialect_new(Py_TYPE(self), newargs, kwargs); + PyObject *result = dialect_new(Py_TYPE(self), newargs, changes); Py_DECREF(newargs); return result; } static struct PyMethodDef dialect_methods[] = { - {"__reduce__", Dialect_reduce, METH_VARARGS, dialect_reduce_doc}, - {"__reduce_ex__", Dialect_reduce, METH_VARARGS, dialect_reduce_doc}, - {"__replace__", _PyCFunction_CAST(Dialect_replace), - METH_VARARGS | METH_KEYWORDS, dialect_replace_doc}, + _CSV_DIALECT___REDUCE___METHODDEF + _CSV_DIALECT___REDUCE_EX___METHODDEF + _CSV_DIALECT___REPLACE___METHODDEF {NULL, NULL} }; @@ -1162,10 +1182,31 @@ PyType_Spec Reader_Type_spec = { }; +/*[clinic input] +_csv.reader + + iterable: object + dialect: object(c_default='NULL') = 'excel' + / + **fmtparams: dict + +Return a reader object that will process lines from the given iterable. + +The "iterable" argument can be any object that returns a line +of input for each iteration, such as a file object or a list. The +optional "dialect" argument defines a CSV dialect. The function +also accepts optional keyword arguments which override settings +provided by the dialect. + +The returned object is an iterator. Each iteration returns a row +of the CSV file (which can span multiple input lines). +[clinic start generated code]*/ + static PyObject * -csv_reader(PyObject *module, PyObject *args, PyObject *keyword_args) +_csv_reader_impl(PyObject *module, PyObject *iterable, PyObject *dialect, + PyObject *fmtparams) +/*[clinic end generated code: output=c7033323f4e82fae input=330c6d58878e33b7]*/ { - PyObject * iterator, * dialect = NULL; _csvstate *module_state = get_csv_state(module); ReaderObj * self = PyObject_GC_New( ReaderObj, @@ -1186,17 +1227,13 @@ csv_reader(PyObject *module, PyObject *args, PyObject *keyword_args) return NULL; } - if (!PyArg_UnpackTuple(args, "reader", 1, 2, &iterator, &dialect)) { - Py_DECREF(self); - return NULL; - } - self->input_iter = PyObject_GetIter(iterator); + self->input_iter = PyObject_GetIter(iterable); if (self->input_iter == NULL) { Py_DECREF(self); return NULL; } self->dialect = (DialectObj *)_call_dialect(module_state, dialect, - keyword_args); + fmtparams); if (self->dialect == NULL) { Py_DECREF(self); return NULL; @@ -1624,10 +1661,27 @@ PyType_Spec Writer_Type_spec = { }; +/*[clinic input] +_csv.writer + + fileobj as output_file: object + dialect: object(c_default='NULL') = 'excel' + / + **fmtparams: dict + +Return a writer object writing user data to the given file object. + +The "fileobj" argument can be any object that supports the file API. +The optional "dialect" argument defines a CSV dialect. The function +also accepts optional keyword arguments which override settings +provided by the dialect. +[clinic start generated code]*/ + static PyObject * -csv_writer(PyObject *module, PyObject *args, PyObject *keyword_args) +_csv_writer_impl(PyObject *module, PyObject *output_file, PyObject *dialect, + PyObject *fmtparams) +/*[clinic end generated code: output=3f57919e03cca475 input=18d396d2aa8138f0]*/ { - PyObject * output_file, * dialect = NULL; _csvstate *module_state = get_csv_state(module); WriterObj * self = PyObject_GC_New(WriterObj, module_state->writer_type); @@ -1644,10 +1698,6 @@ csv_writer(PyObject *module, PyObject *args, PyObject *keyword_args) self->error_obj = Py_NewRef(module_state->error_obj); - if (!PyArg_UnpackTuple(args, "writer", 1, 2, &output_file, &dialect)) { - Py_DECREF(self); - return NULL; - } if (PyObject_GetOptionalAttr(output_file, module_state->str_write, &self->write) < 0) { @@ -1661,7 +1711,7 @@ csv_writer(PyObject *module, PyObject *args, PyObject *keyword_args) return NULL; } self->dialect = (DialectObj *)_call_dialect(module_state, dialect, - keyword_args); + fmtparams); if (self->dialect == NULL) { Py_DECREF(self); return NULL; @@ -1687,21 +1737,36 @@ _csv_list_dialects_impl(PyObject *module) return PyDict_Keys(get_csv_state(module)->dialects); } +/*[clinic input] +_csv.register_dialect + + name as name_obj: object + dialect as dialect_obj: object(c_default='NULL') = 'excel' + / + **fmtparams: dict + +Create a mapping from a string name to a CVS dialect. + +The optional "dialect" argument specifies the base dialect instance +or the name of the registered dialect. The function also accepts +optional keyword arguments which override settings provided by the +dialect. +[clinic start generated code]*/ + static PyObject * -csv_register_dialect(PyObject *module, PyObject *args, PyObject *kwargs) +_csv_register_dialect_impl(PyObject *module, PyObject *name_obj, + PyObject *dialect_obj, PyObject *fmtparams) +/*[clinic end generated code: output=b00b54de5b950472 input=64e9180e18d88a97]*/ { - PyObject *name_obj, *dialect_obj = NULL; _csvstate *module_state = get_csv_state(module); PyObject *dialect; - if (!PyArg_UnpackTuple(args, "register_dialect", 1, 2, &name_obj, &dialect_obj)) - return NULL; if (!PyUnicode_Check(name_obj)) { PyErr_SetString(PyExc_TypeError, "dialect name must be a string"); return NULL; } - dialect = _call_dialect(module_state, dialect_obj, kwargs); + dialect = _call_dialect(module_state, dialect_obj, fmtparams); if (dialect == NULL) return NULL; if (PyDict_SetItem(module_state->dialects, name_obj, dialect) < 0) { @@ -1800,47 +1865,10 @@ PyType_Spec error_spec = { PyDoc_STRVAR(csv_module_doc, "CSV parsing and writing.\n"); -PyDoc_STRVAR(csv_reader_doc, -"reader($module, iterable, /, dialect='excel', **fmtparams)\n" -"--\n\n" -"Return a reader object that will process lines from the given iterable.\n" -"\n" -"The \"iterable\" argument can be any object that returns a line\n" -"of input for each iteration, such as a file object or a list. The\n" -"optional \"dialect\" argument defines a CSV dialect. The function\n" -"also accepts optional keyword arguments which override settings\n" -"provided by the dialect.\n" -"\n" -"The returned object is an iterator. Each iteration returns a row\n" -"of the CSV file (which can span multiple input lines).\n"); - -PyDoc_STRVAR(csv_writer_doc, -"writer($module, fileobj, /, dialect='excel', **fmtparams)\n" -"--\n\n" -"Return a writer object that will write user data on the given file object.\n" -"\n" -"The \"fileobj\" argument can be any object that supports the file API.\n" -"The optional \"dialect\" argument defines a CSV dialect. The function\n" -"also accepts optional keyword arguments which override settings\n" -"provided by the dialect.\n"); - -PyDoc_STRVAR(csv_register_dialect_doc, -"register_dialect($module, name, /, dialect='excel', **fmtparams)\n" -"--\n\n" -"Create a mapping from a string name to a CVS dialect.\n" -"\n" -"The optional \"dialect\" argument specifies the base dialect instance\n" -"or the name of the registered dialect. The function also accepts\n" -"optional keyword arguments which override settings provided by the\n" -"dialect.\n"); - static struct PyMethodDef csv_methods[] = { - { "reader", _PyCFunction_CAST(csv_reader), - METH_VARARGS | METH_KEYWORDS, csv_reader_doc}, - { "writer", _PyCFunction_CAST(csv_writer), - METH_VARARGS | METH_KEYWORDS, csv_writer_doc}, - { "register_dialect", _PyCFunction_CAST(csv_register_dialect), - METH_VARARGS | METH_KEYWORDS, csv_register_dialect_doc}, + _CSV_READER_METHODDEF + _CSV_WRITER_METHODDEF + _CSV_REGISTER_DIALECT_METHODDEF _CSV_LIST_DIALECTS_METHODDEF _CSV_UNREGISTER_DIALECT_METHODDEF _CSV_GET_DIALECT_METHODDEF diff --git a/Modules/clinic/_csv.c.h b/Modules/clinic/_csv.c.h index b8dd8ac35fac599..b25f30893224cd4 100644 --- a/Modules/clinic/_csv.c.h +++ b/Modules/clinic/_csv.c.h @@ -6,7 +6,196 @@ preserve # include "pycore_gc.h" // PyGC_Head # include "pycore_runtime.h" // _Py_ID() #endif -#include "pycore_modsupport.h" // _PyArg_UnpackKeywords() +#include "pycore_modsupport.h" // _PyArg_NoPositional() + +PyDoc_STRVAR(_csv_Dialect___reduce____doc__, +"__reduce__($self, /)\n" +"--\n" +"\n" +"Raise an exception to avoid pickling."); + +#define _CSV_DIALECT___REDUCE___METHODDEF \ + {"__reduce__", (PyCFunction)_csv_Dialect___reduce__, METH_NOARGS, _csv_Dialect___reduce____doc__}, + +static PyObject * +_csv_Dialect___reduce___impl(DialectObj *self); + +static PyObject * +_csv_Dialect___reduce__(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return _csv_Dialect___reduce___impl((DialectObj *)self); +} + +PyDoc_STRVAR(_csv_Dialect___reduce_ex____doc__, +"__reduce_ex__($self, protocol, /)\n" +"--\n" +"\n" +"Raise an exception to avoid pickling."); + +#define _CSV_DIALECT___REDUCE_EX___METHODDEF \ + {"__reduce_ex__", (PyCFunction)_csv_Dialect___reduce_ex__, METH_O, _csv_Dialect___reduce_ex____doc__}, + +static PyObject * +_csv_Dialect___reduce_ex___impl(DialectObj *self, PyObject *protocol); + +static PyObject * +_csv_Dialect___reduce_ex__(PyObject *self, PyObject *protocol) +{ + PyObject *return_value = NULL; + + return_value = _csv_Dialect___reduce_ex___impl((DialectObj *)self, protocol); + + return return_value; +} + +PyDoc_STRVAR(_csv_Dialect___replace____doc__, +"__replace__($self, /, **changes)\n" +"--\n" +"\n" +"Return a copy of the dialect with the specified options replaced."); + +#define _CSV_DIALECT___REPLACE___METHODDEF \ + {"__replace__", _PyCFunction_CAST(_csv_Dialect___replace__), METH_VARARGS|METH_KEYWORDS, _csv_Dialect___replace____doc__}, + +static PyObject * +_csv_Dialect___replace___impl(DialectObj *self, PyObject *changes); + +static PyObject * +_csv_Dialect___replace__(PyObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + PyObject *changes = NULL; + + if (!_PyArg_NoPositional("__replace__", args)) { + goto exit; + } + if (kwargs == NULL) { + changes = PyDict_New(); + if (changes == NULL) { + goto exit; + } + } + else { + changes = Py_NewRef(kwargs); + } + return_value = _csv_Dialect___replace___impl((DialectObj *)self, changes); + +exit: + /* Cleanup for changes */ + Py_XDECREF(changes); + + return return_value; +} + +PyDoc_STRVAR(_csv_reader__doc__, +"reader($module, iterable, dialect=\'excel\', /, **fmtparams)\n" +"--\n" +"\n" +"Return a reader object that will process lines from the given iterable.\n" +"\n" +"The \"iterable\" argument can be any object that returns a line\n" +"of input for each iteration, such as a file object or a list. The\n" +"optional \"dialect\" argument defines a CSV dialect. The function\n" +"also accepts optional keyword arguments which override settings\n" +"provided by the dialect.\n" +"\n" +"The returned object is an iterator. Each iteration returns a row\n" +"of the CSV file (which can span multiple input lines)."); + +#define _CSV_READER_METHODDEF \ + {"reader", _PyCFunction_CAST(_csv_reader), METH_VARARGS|METH_KEYWORDS, _csv_reader__doc__}, + +static PyObject * +_csv_reader_impl(PyObject *module, PyObject *iterable, PyObject *dialect, + PyObject *fmtparams); + +static PyObject * +_csv_reader(PyObject *module, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + PyObject *iterable; + PyObject *dialect = NULL; + PyObject *fmtparams = NULL; + + if (!_PyArg_CheckPositional("reader", PyTuple_GET_SIZE(args), 1, 2)) { + goto exit; + } + iterable = PyTuple_GET_ITEM(args, 0); + if (PyTuple_GET_SIZE(args) < 2) { + goto skip_optional; + } + dialect = PyTuple_GET_ITEM(args, 1); +skip_optional: + if (kwargs == NULL) { + fmtparams = PyDict_New(); + if (fmtparams == NULL) { + goto exit; + } + } + else { + fmtparams = Py_NewRef(kwargs); + } + return_value = _csv_reader_impl(module, iterable, dialect, fmtparams); + +exit: + /* Cleanup for fmtparams */ + Py_XDECREF(fmtparams); + + return return_value; +} + +PyDoc_STRVAR(_csv_writer__doc__, +"writer($module, fileobj, dialect=\'excel\', /, **fmtparams)\n" +"--\n" +"\n" +"Return a writer object writing user data to the given file object.\n" +"\n" +"The \"fileobj\" argument can be any object that supports the file API.\n" +"The optional \"dialect\" argument defines a CSV dialect. The function\n" +"also accepts optional keyword arguments which override settings\n" +"provided by the dialect."); + +#define _CSV_WRITER_METHODDEF \ + {"writer", _PyCFunction_CAST(_csv_writer), METH_VARARGS|METH_KEYWORDS, _csv_writer__doc__}, + +static PyObject * +_csv_writer_impl(PyObject *module, PyObject *output_file, PyObject *dialect, + PyObject *fmtparams); + +static PyObject * +_csv_writer(PyObject *module, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + PyObject *output_file; + PyObject *dialect = NULL; + PyObject *fmtparams = NULL; + + if (!_PyArg_CheckPositional("writer", PyTuple_GET_SIZE(args), 1, 2)) { + goto exit; + } + output_file = PyTuple_GET_ITEM(args, 0); + if (PyTuple_GET_SIZE(args) < 2) { + goto skip_optional; + } + dialect = PyTuple_GET_ITEM(args, 1); +skip_optional: + if (kwargs == NULL) { + fmtparams = PyDict_New(); + if (fmtparams == NULL) { + goto exit; + } + } + else { + fmtparams = Py_NewRef(kwargs); + } + return_value = _csv_writer_impl(module, output_file, dialect, fmtparams); + +exit: + /* Cleanup for fmtparams */ + Py_XDECREF(fmtparams); + + return return_value; +} PyDoc_STRVAR(_csv_list_dialects__doc__, "list_dialects($module, /)\n" @@ -26,6 +215,59 @@ _csv_list_dialects(PyObject *module, PyObject *Py_UNUSED(ignored)) return _csv_list_dialects_impl(module); } +PyDoc_STRVAR(_csv_register_dialect__doc__, +"register_dialect($module, name, dialect=\'excel\', /, **fmtparams)\n" +"--\n" +"\n" +"Create a mapping from a string name to a CVS dialect.\n" +"\n" +"The optional \"dialect\" argument specifies the base dialect instance\n" +"or the name of the registered dialect. The function also accepts\n" +"optional keyword arguments which override settings provided by the\n" +"dialect."); + +#define _CSV_REGISTER_DIALECT_METHODDEF \ + {"register_dialect", _PyCFunction_CAST(_csv_register_dialect), METH_VARARGS|METH_KEYWORDS, _csv_register_dialect__doc__}, + +static PyObject * +_csv_register_dialect_impl(PyObject *module, PyObject *name_obj, + PyObject *dialect_obj, PyObject *fmtparams); + +static PyObject * +_csv_register_dialect(PyObject *module, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + PyObject *name_obj; + PyObject *dialect_obj = NULL; + PyObject *fmtparams = NULL; + + if (!_PyArg_CheckPositional("register_dialect", PyTuple_GET_SIZE(args), 1, 2)) { + goto exit; + } + name_obj = PyTuple_GET_ITEM(args, 0); + if (PyTuple_GET_SIZE(args) < 2) { + goto skip_optional; + } + dialect_obj = PyTuple_GET_ITEM(args, 1); +skip_optional: + if (kwargs == NULL) { + fmtparams = PyDict_New(); + if (fmtparams == NULL) { + goto exit; + } + } + else { + fmtparams = Py_NewRef(kwargs); + } + return_value = _csv_register_dialect_impl(module, name_obj, dialect_obj, fmtparams); + +exit: + /* Cleanup for fmtparams */ + Py_XDECREF(fmtparams); + + return return_value; +} + PyDoc_STRVAR(_csv_unregister_dialect__doc__, "unregister_dialect($module, /, name)\n" "--\n" @@ -207,4 +449,4 @@ _csv_field_size_limit(PyObject *module, PyObject *const *args, Py_ssize_t nargs, exit: return return_value; } -/*[clinic end generated code: output=ed77cb69fad9f3b4 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=1a90b8a8ed82497d input=a9049054013a1b77]*/