[Python-checkins] bpo-14935: Remove static state from the _csv module (GH-23224)

encukou webhook-mailer at python.org
Tue Dec 15 09:14:50 EST 2020


https://github.com/python/cpython/commit/6a02b384751dbc13979efc1185f0a7c1670dc349
commit: 6a02b384751dbc13979efc1185f0a7c1670dc349
branch: master
author: Petr Viktorin <encukou at gmail.com>
committer: encukou <encukou at gmail.com>
date: 2020-12-15T15:14:35+01:00
summary:

bpo-14935: Remove static state from the _csv module (GH-23224)

Uses code from: https://github.com/python/cpython/pull/16078

Co-authored-by: Marcel Plch <gmarcel.plch at gmail.com>
Co-authored-by: Eddie Elizondo <eduardo.elizondorueda at gmail.com>
Co-authored-by: Hai Shi <shihai1992 at gmail.com>

files:
A Misc/NEWS.d/next/Library/2020-10-20-23-28-55.bpo-1635741.Iyka3r.rst
M Modules/_csv.c

diff --git a/Misc/NEWS.d/next/Library/2020-10-20-23-28-55.bpo-1635741.Iyka3r.rst b/Misc/NEWS.d/next/Library/2020-10-20-23-28-55.bpo-1635741.Iyka3r.rst
new file mode 100644
index 0000000000000..e14e9a12c275d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-10-20-23-28-55.bpo-1635741.Iyka3r.rst
@@ -0,0 +1 @@
+Port the _csv module to the multi-phase initialization API (:pep:`489`).
diff --git a/Modules/_csv.c b/Modules/_csv.c
index 594f6c1472726..cade1ca9d47f0 100644
--- a/Modules/_csv.c
+++ b/Modules/_csv.c
@@ -18,9 +18,14 @@ module instead.
 typedef struct {
     PyObject *error_obj;   /* CSV exception */
     PyObject *dialects;   /* Dialect registry */
+    PyTypeObject *dialect_type;
+    PyTypeObject *reader_type;
+    PyTypeObject *writer_type;
     long field_limit;   /* max parsed field size */
 } _csvstate;
 
+static struct PyModuleDef _csvmodule;
+
 static inline _csvstate*
 get_csv_state(PyObject *module)
 {
@@ -30,31 +35,35 @@ get_csv_state(PyObject *module)
 }
 
 static int
-_csv_clear(PyObject *m)
+_csv_clear(PyObject *module)
 {
-    Py_CLEAR(get_csv_state(m)->error_obj);
-    Py_CLEAR(get_csv_state(m)->dialects);
+    _csvstate *module_state = PyModule_GetState(module);
+    Py_CLEAR(module_state->error_obj);
+    Py_CLEAR(module_state->dialects);
+    Py_CLEAR(module_state->dialect_type);
+    Py_CLEAR(module_state->reader_type);
+    Py_CLEAR(module_state->writer_type);
     return 0;
 }
 
 static int
-_csv_traverse(PyObject *m, visitproc visit, void *arg)
+_csv_traverse(PyObject *module, visitproc visit, void *arg)
 {
-    Py_VISIT(get_csv_state(m)->error_obj);
-    Py_VISIT(get_csv_state(m)->dialects);
+    _csvstate *module_state = PyModule_GetState(module);
+    Py_VISIT(module_state->error_obj);
+    Py_VISIT(module_state->dialects);
+    Py_VISIT(module_state->dialect_type);
+    Py_VISIT(module_state->reader_type);
+    Py_VISIT(module_state->writer_type);
     return 0;
 }
 
 static void
-_csv_free(void *m)
+_csv_free(void *module)
 {
-   _csv_clear((PyObject *)m);
+   _csv_clear((PyObject *)module);
 }
 
-static struct PyModuleDef _csvmodule;
-
-#define _csvstate_global ((_csvstate *)PyModule_GetState(PyState_FindModule(&_csvmodule)))
-
 typedef enum {
     START_RECORD, START_FIELD, ESCAPED_CHAR, IN_FIELD,
     IN_QUOTED_FIELD, ESCAPE_IN_QUOTED_FIELD, QUOTE_IN_QUOTED_FIELD,
@@ -92,8 +101,6 @@ typedef struct {
 
 } DialectObj;
 
-static PyTypeObject Dialect_Type;
-
 typedef struct {
     PyObject_HEAD
 
@@ -110,8 +117,6 @@ typedef struct {
     unsigned long line_num;     /* Source-file line number */
 } ReaderObj;
 
-static PyTypeObject Reader_Type;
-
 typedef struct {
     PyObject_HEAD
 
@@ -123,26 +128,27 @@ typedef struct {
     Py_ssize_t rec_size;        /* size of allocated record */
     Py_ssize_t rec_len;         /* length of record */
     int num_fields;             /* number of fields in record */
-} WriterObj;
 
-static PyTypeObject Writer_Type;
+    PyObject *error_obj;       /* cached error object */
+} WriterObj;
 
 /*
  * DIALECT class
  */
 
 static PyObject *
-get_dialect_from_registry(PyObject * name_obj)
+get_dialect_from_registry(PyObject *name_obj, _csvstate *module_state)
 {
     PyObject *dialect_obj;
 
-    dialect_obj = PyDict_GetItemWithError(_csvstate_global->dialects, name_obj);
+    dialect_obj = PyDict_GetItemWithError(module_state->dialects, name_obj);
     if (dialect_obj == NULL) {
         if (!PyErr_Occurred())
-            PyErr_Format(_csvstate_global->error_obj, "unknown dialect");
+            PyErr_Format(module_state->error_obj, "unknown dialect");
     }
     else
         Py_INCREF(dialect_obj);
+
     return dialect_obj;
 }
 
@@ -309,8 +315,16 @@ static PyGetSetDef Dialect_getsetlist[] = {
 static void
 Dialect_dealloc(DialectObj *self)
 {
-    Py_XDECREF(self->lineterminator);
-    Py_TYPE(self)->tp_free((PyObject *)self);
+    PyTypeObject *tp = Py_TYPE(self);
+    Py_CLEAR(self->lineterminator);
+    tp->tp_free((PyObject *)self);
+    Py_DECREF(tp);
+}
+
+static void
+Dialect_finalize(DialectObj *self)
+{
+    Py_CLEAR(self->lineterminator);
 }
 
 static char *dialect_kws[] = {
@@ -326,6 +340,22 @@ static char *dialect_kws[] = {
     NULL
 };
 
+static _csvstate *
+_csv_state_from_type(PyTypeObject *type, const char *name)
+{
+    PyObject *module = _PyType_GetModuleByDef(type, &_csvmodule);
+    if (module == NULL) {
+        return NULL;
+    }
+    _csvstate *module_state = PyModule_GetState(module);
+    if (module_state == NULL) {
+        PyErr_Format(PyExc_SystemError,
+                     "%s: No _csv module state found", name);
+        return NULL;
+    }
+    return module_state;
+}
+
 static PyObject *
 dialect_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
 {
@@ -354,16 +384,21 @@ dialect_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
                                      &strict))
         return NULL;
 
+    _csvstate *module_state = _csv_state_from_type(type, "dialect_new");
+    if (module_state == NULL) {
+        return NULL;
+    }
+
     if (dialect != NULL) {
         if (PyUnicode_Check(dialect)) {
-            dialect = get_dialect_from_registry(dialect);
+            dialect = get_dialect_from_registry(dialect, module_state);
             if (dialect == NULL)
                 return NULL;
         }
         else
             Py_INCREF(dialect);
         /* Can we reuse this instance? */
-        if (PyObject_TypeCheck(dialect, &Dialect_Type) &&
+        if (PyObject_TypeCheck(dialect, module_state->dialect_type) &&
             delimiter == NULL &&
             doublequote == NULL &&
             escapechar == NULL &&
@@ -377,7 +412,7 @@ dialect_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
 
     self = (DialectObj *)type->tp_alloc(type, 0);
     if (self == NULL) {
-        Py_XDECREF(dialect);
+        Py_CLEAR(dialect);
         return NULL;
     }
     self->lineterminator = NULL;
@@ -441,76 +476,69 @@ dialect_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
     ret = (PyObject *)self;
     Py_INCREF(self);
 err:
-    Py_XDECREF(self);
-    Py_XDECREF(dialect);
-    Py_XDECREF(delimiter);
-    Py_XDECREF(doublequote);
-    Py_XDECREF(escapechar);
-    Py_XDECREF(lineterminator);
-    Py_XDECREF(quotechar);
-    Py_XDECREF(quoting);
-    Py_XDECREF(skipinitialspace);
-    Py_XDECREF(strict);
+    Py_CLEAR(self);
+    Py_CLEAR(dialect);
+    Py_CLEAR(delimiter);
+    Py_CLEAR(doublequote);
+    Py_CLEAR(escapechar);
+    Py_CLEAR(lineterminator);
+    Py_CLEAR(quotechar);
+    Py_CLEAR(quoting);
+    Py_CLEAR(skipinitialspace);
+    Py_CLEAR(strict);
     return ret;
 }
 
+/* Since dialect is now a heap type, it inherits pickling method for
+ * protocol 0 and 1 from object, therefore it needs to be overriden */
+
+PyDoc_STRVAR(dialect_reduce_doc, "raises an exception to avoid pickling");
+
+static PyObject *
+Dialect_reduce(PyObject *self, PyObject *args) {
+    PyErr_Format(PyExc_TypeError,
+        "cannot pickle '%.100s' instances", _PyType_Name(Py_TYPE(self)));
+    return NULL;
+}
+
+static struct PyMethodDef dialect_methods[] = {
+    {"__reduce__", Dialect_reduce, METH_VARARGS, dialect_reduce_doc},
+    {"__reduce_ex__", Dialect_reduce, METH_VARARGS, dialect_reduce_doc},
+    {NULL, NULL}
+};
 
 PyDoc_STRVAR(Dialect_Type_doc,
 "CSV dialect\n"
 "\n"
 "The Dialect type records CSV parsing and generation options.\n");
 
-static PyTypeObject Dialect_Type = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    "_csv.Dialect",                         /* tp_name */
-    sizeof(DialectObj),                     /* tp_basicsize */
-    0,                                      /* tp_itemsize */
-    /*  methods  */
-    (destructor)Dialect_dealloc,            /* tp_dealloc */
-    0,                                      /* tp_vectorcall_offset */
-    (getattrfunc)0,                         /* tp_getattr */
-    (setattrfunc)0,                         /* tp_setattr */
-    0,                                      /* tp_as_async */
-    (reprfunc)0,                            /* tp_repr */
-    0,                                      /* tp_as_number */
-    0,                                      /* tp_as_sequence */
-    0,                                      /* tp_as_mapping */
-    (hashfunc)0,                            /* tp_hash */
-    (ternaryfunc)0,                         /* tp_call */
-    (reprfunc)0,                                /* tp_str */
-    0,                                      /* tp_getattro */
-    0,                                      /* tp_setattro */
-    0,                                      /* tp_as_buffer */
-    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
-    Dialect_Type_doc,                       /* tp_doc */
-    0,                                      /* tp_traverse */
-    0,                                      /* tp_clear */
-    0,                                      /* tp_richcompare */
-    0,                                      /* tp_weaklistoffset */
-    0,                                      /* tp_iter */
-    0,                                      /* tp_iternext */
-    0,                                          /* tp_methods */
-    Dialect_memberlist,                     /* tp_members */
-    Dialect_getsetlist,                     /* tp_getset */
-    0,                                          /* tp_base */
-    0,                                          /* tp_dict */
-    0,                                          /* tp_descr_get */
-    0,                                          /* tp_descr_set */
-    0,                                          /* tp_dictoffset */
-    0,                                          /* tp_init */
-    0,                                          /* tp_alloc */
-    dialect_new,                                /* tp_new */
-    0,                                          /* tp_free */
+static PyType_Slot Dialect_Type_slots[] = {
+    {Py_tp_doc, (char*)Dialect_Type_doc},
+    {Py_tp_members, Dialect_memberlist},
+    {Py_tp_getset, Dialect_getsetlist},
+    {Py_tp_new, dialect_new},
+    {Py_tp_methods, dialect_methods},
+    {Py_tp_finalize, Dialect_finalize},
+    {Py_tp_dealloc, Dialect_dealloc},
+    {0, NULL}
 };
 
+PyType_Spec Dialect_Type_spec = {
+    .name = "_csv.Dialect",
+    .basicsize = sizeof(DialectObj),
+    .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+    .slots = Dialect_Type_slots,
+};
+
+
 /*
  * Return an instance of the dialect type, given a Python instance or kwarg
  * description of the dialect
  */
 static PyObject *
-_call_dialect(PyObject *dialect_inst, PyObject *kwargs)
+_call_dialect(_csvstate *module_state, PyObject *dialect_inst, PyObject *kwargs)
 {
-    PyObject *type = (PyObject *)&Dialect_Type;
+    PyObject *type = (PyObject *)module_state->dialect_type;
     if (dialect_inst) {
         return PyObject_VectorcallDict(type, &dialect_inst, 1, kwargs);
     }
@@ -568,11 +596,12 @@ parse_grow_buff(ReaderObj *self)
 }
 
 static int
-parse_add_char(ReaderObj *self, Py_UCS4 c)
+parse_add_char(ReaderObj *self, _csvstate *module_state, Py_UCS4 c)
 {
-    if (self->field_len >= _csvstate_global->field_limit) {
-        PyErr_Format(_csvstate_global->error_obj, "field larger than field limit (%ld)",
-                     _csvstate_global->field_limit);
+    if (self->field_len >= module_state->field_limit) {
+        PyErr_Format(module_state->error_obj,
+                     "field larger than field limit (%ld)",
+                     module_state->field_limit);
         return -1;
     }
     if (self->field_len == self->field_size && !parse_grow_buff(self))
@@ -582,7 +611,7 @@ parse_add_char(ReaderObj *self, Py_UCS4 c)
 }
 
 static int
-parse_process_char(ReaderObj *self, Py_UCS4 c)
+parse_process_char(ReaderObj *self, _csvstate *module_state, Py_UCS4 c)
 {
     DialectObj *dialect = self->dialect;
 
@@ -628,7 +657,7 @@ parse_process_char(ReaderObj *self, Py_UCS4 c)
             /* begin new unquoted field */
             if (dialect->quoting == QUOTE_NONNUMERIC)
                 self->numeric_field = 1;
-            if (parse_add_char(self, c) < 0)
+            if (parse_add_char(self, module_state, c) < 0)
                 return -1;
             self->state = IN_FIELD;
         }
@@ -636,14 +665,14 @@ parse_process_char(ReaderObj *self, Py_UCS4 c)
 
     case ESCAPED_CHAR:
         if (c == '\n' || c=='\r') {
-            if (parse_add_char(self, c) < 0)
+            if (parse_add_char(self, module_state, c) < 0)
                 return -1;
             self->state = AFTER_ESCAPED_CRNL;
             break;
         }
         if (c == '\0')
             c = '\n';
-        if (parse_add_char(self, c) < 0)
+        if (parse_add_char(self, module_state, c) < 0)
             return -1;
         self->state = IN_FIELD;
         break;
@@ -673,7 +702,7 @@ parse_process_char(ReaderObj *self, Py_UCS4 c)
         }
         else {
             /* normal character - save in field */
-            if (parse_add_char(self, c) < 0)
+            if (parse_add_char(self, module_state, c) < 0)
                 return -1;
         }
         break;
@@ -699,7 +728,7 @@ parse_process_char(ReaderObj *self, Py_UCS4 c)
         }
         else {
             /* normal character - save in field */
-            if (parse_add_char(self, c) < 0)
+            if (parse_add_char(self, module_state, c) < 0)
                 return -1;
         }
         break;
@@ -707,7 +736,7 @@ parse_process_char(ReaderObj *self, Py_UCS4 c)
     case ESCAPE_IN_QUOTED_FIELD:
         if (c == '\0')
             c = '\n';
-        if (parse_add_char(self, c) < 0)
+        if (parse_add_char(self, module_state, c) < 0)
             return -1;
         self->state = IN_QUOTED_FIELD;
         break;
@@ -717,7 +746,7 @@ parse_process_char(ReaderObj *self, Py_UCS4 c)
         if (dialect->quoting != QUOTE_NONE &&
             c == dialect->quotechar) {
             /* save "" as " */
-            if (parse_add_char(self, c) < 0)
+            if (parse_add_char(self, module_state, c) < 0)
                 return -1;
             self->state = IN_QUOTED_FIELD;
         }
@@ -734,13 +763,13 @@ parse_process_char(ReaderObj *self, Py_UCS4 c)
             self->state = (c == '\0' ? START_RECORD : EAT_CRNL);
         }
         else if (!dialect->strict) {
-            if (parse_add_char(self, c) < 0)
+            if (parse_add_char(self, module_state, c) < 0)
                 return -1;
             self->state = IN_FIELD;
         }
         else {
             /* illegal */
-            PyErr_Format(_csvstate_global->error_obj, "'%c' expected after '%c'",
+            PyErr_Format(module_state->error_obj, "'%c' expected after '%c'",
                             dialect->delimiter,
                             dialect->quotechar);
             return -1;
@@ -753,7 +782,8 @@ parse_process_char(ReaderObj *self, Py_UCS4 c)
         else if (c == '\0')
             self->state = START_RECORD;
         else {
-            PyErr_Format(_csvstate_global->error_obj, "new-line character seen in unquoted field - do you need to open the file in universal-newline mode?");
+            PyErr_Format(module_state->error_obj,
+                         "new-line character seen in unquoted field - do you need to open the file in universal-newline mode?");
             return -1;
         }
         break;
@@ -784,6 +814,12 @@ Reader_iternext(ReaderObj *self)
     const void *data;
     PyObject *lineobj;
 
+    _csvstate *module_state = _csv_state_from_type(Py_TYPE(self),
+                                                   "Reader.__next__");
+    if (module_state == NULL) {
+        return NULL;
+    }
+
     if (parse_reset(self) < 0)
         return NULL;
     do {
@@ -793,7 +829,7 @@ Reader_iternext(ReaderObj *self)
             if (!PyErr_Occurred() && (self->field_len != 0 ||
                                       self->state == IN_QUOTED_FIELD)) {
                 if (self->dialect->strict)
-                    PyErr_SetString(_csvstate_global->error_obj,
+                    PyErr_SetString(module_state->error_obj,
                                     "unexpected end of data");
                 else if (parse_save_field(self) >= 0)
                     break;
@@ -801,7 +837,7 @@ Reader_iternext(ReaderObj *self)
             return NULL;
         }
         if (!PyUnicode_Check(lineobj)) {
-            PyErr_Format(_csvstate_global->error_obj,
+            PyErr_Format(module_state->error_obj,
                          "iterator should return strings, "
                          "not %.200s "
                          "(the file should be opened in text mode)",
@@ -823,18 +859,18 @@ Reader_iternext(ReaderObj *self)
             c = PyUnicode_READ(kind, data, pos);
             if (c == '\0') {
                 Py_DECREF(lineobj);
-                PyErr_Format(_csvstate_global->error_obj,
+                PyErr_Format(module_state->error_obj,
                              "line contains NUL");
                 goto err;
             }
-            if (parse_process_char(self, c) < 0) {
+            if (parse_process_char(self, module_state, c) < 0) {
                 Py_DECREF(lineobj);
                 goto err;
             }
             pos++;
         }
         Py_DECREF(lineobj);
-        if (parse_process_char(self, 0) < 0)
+        if (parse_process_char(self, module_state, 0) < 0)
             goto err;
     } while (self->state != START_RECORD);
 
@@ -847,13 +883,29 @@ Reader_iternext(ReaderObj *self)
 static void
 Reader_dealloc(ReaderObj *self)
 {
+    PyTypeObject *tp = Py_TYPE(self);
     PyObject_GC_UnTrack(self);
-    Py_XDECREF(self->dialect);
-    Py_XDECREF(self->input_iter);
-    Py_XDECREF(self->fields);
-    if (self->field != NULL)
+    Py_CLEAR(self->dialect);
+    Py_CLEAR(self->input_iter);
+    Py_CLEAR(self->fields);
+    if (self->field != NULL) {
         PyMem_Free(self->field);
+        self->field = NULL;
+    }
     PyObject_GC_Del(self);
+    Py_DECREF(tp);
+}
+
+static void
+Reader_finalize(ReaderObj *self)
+{
+    Py_CLEAR(self->dialect);
+    Py_CLEAR(self->input_iter);
+    Py_CLEAR(self->fields);
+    if (self->field != NULL) {
+        PyMem_Free(self->field);
+        self->field = NULL;
+    }
 }
 
 static int
@@ -893,47 +945,35 @@ static struct PyMemberDef Reader_memberlist[] = {
 };
 
 
-static PyTypeObject Reader_Type = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    "_csv.reader",                          /*tp_name*/
-    sizeof(ReaderObj),                      /*tp_basicsize*/
-    0,                                      /*tp_itemsize*/
-    /* methods */
-    (destructor)Reader_dealloc,             /*tp_dealloc*/
-    0,                                      /*tp_vectorcall_offset*/
-    (getattrfunc)0,                         /*tp_getattr*/
-    (setattrfunc)0,                         /*tp_setattr*/
-    0,                                      /*tp_as_async*/
-    (reprfunc)0,                            /*tp_repr*/
-    0,                                      /*tp_as_number*/
-    0,                                      /*tp_as_sequence*/
-    0,                                      /*tp_as_mapping*/
-    (hashfunc)0,                            /*tp_hash*/
-    (ternaryfunc)0,                         /*tp_call*/
-    (reprfunc)0,                                /*tp_str*/
-    0,                                      /*tp_getattro*/
-    0,                                      /*tp_setattro*/
-    0,                                      /*tp_as_buffer*/
-    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
-        Py_TPFLAGS_HAVE_GC,                     /*tp_flags*/
-    Reader_Type_doc,                        /*tp_doc*/
-    (traverseproc)Reader_traverse,          /*tp_traverse*/
-    (inquiry)Reader_clear,                  /*tp_clear*/
-    0,                                      /*tp_richcompare*/
-    0,                                      /*tp_weaklistoffset*/
-    PyObject_SelfIter,                          /*tp_iter*/
-    (getiterfunc)Reader_iternext,           /*tp_iternext*/
-    Reader_methods,                         /*tp_methods*/
-    Reader_memberlist,                      /*tp_members*/
-    0,                                      /*tp_getset*/
+static PyType_Slot Reader_Type_slots[] = {
+    {Py_tp_doc, (char*)Reader_Type_doc},
+    {Py_tp_traverse, Reader_traverse},
+    {Py_tp_clear, Reader_clear},
+    {Py_tp_iter, PyObject_SelfIter},
+    {Py_tp_iternext, Reader_iternext},
+    {Py_tp_methods, Reader_methods},
+    {Py_tp_members, Reader_memberlist},
+    {Py_tp_finalize, Reader_finalize},
+    {Py_tp_dealloc, Reader_dealloc},
+    {0, NULL}
+};
 
+PyType_Spec Reader_Type_spec = {
+    .name = "_csv.reader",
+    .basicsize = sizeof(ReaderObj),
+    .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
+    .slots = Reader_Type_slots
 };
 
+
 static PyObject *
 csv_reader(PyObject *module, PyObject *args, PyObject *keyword_args)
 {
     PyObject * iterator, * dialect = NULL;
-    ReaderObj * self = PyObject_GC_New(ReaderObj, &Reader_Type);
+    _csvstate *module_state = get_csv_state(module);
+    ReaderObj * self = PyObject_GC_New(
+        ReaderObj,
+        module_state->reader_type);
 
     if (!self)
         return NULL;
@@ -959,7 +999,8 @@ csv_reader(PyObject *module, PyObject *args, PyObject *keyword_args)
         Py_DECREF(self);
         return NULL;
     }
-    self->dialect = (DialectObj *)_call_dialect(dialect, keyword_args);
+    self->dialect = (DialectObj *)_call_dialect(module_state, dialect,
+                                                keyword_args);
     if (self->dialect == NULL) {
         Py_DECREF(self);
         return NULL;
@@ -1048,7 +1089,7 @@ join_append_data(WriterObj *self, unsigned int field_kind, const void *field_dat
             }
             if (want_escape) {
                 if (!dialect->escapechar) {
-                    PyErr_Format(_csvstate_global->error_obj,
+                    PyErr_Format(self->error_obj,
                                  "need to escape, but no escapechar set");
                     return -1;
                 }
@@ -1166,7 +1207,7 @@ csv_writerow(WriterObj *self, PyObject *seq)
     iter = PyObject_GetIter(seq);
     if (iter == NULL) {
         if (PyErr_ExceptionMatches(PyExc_TypeError)) {
-            PyErr_Format(_csvstate_global->error_obj,
+            PyErr_Format(self->error_obj,
                          "iterable expected, not %.200s",
                          Py_TYPE(seq)->tp_name);
         }
@@ -1223,7 +1264,7 @@ csv_writerow(WriterObj *self, PyObject *seq)
 
     if (self->num_fields > 0 && self->rec_len == 0) {
         if (dialect->quoting == QUOTE_NONE) {
-            PyErr_Format(_csvstate_global->error_obj,
+            PyErr_Format(self->error_obj,
                 "single empty field record must be quoted");
             return NULL;
         }
@@ -1292,22 +1333,12 @@ static struct PyMemberDef Writer_memberlist[] = {
     { NULL }
 };
 
-static void
-Writer_dealloc(WriterObj *self)
-{
-    PyObject_GC_UnTrack(self);
-    Py_XDECREF(self->dialect);
-    Py_XDECREF(self->write);
-    if (self->rec != NULL)
-        PyMem_Free(self->rec);
-    PyObject_GC_Del(self);
-}
-
 static int
 Writer_traverse(WriterObj *self, visitproc visit, void *arg)
 {
     Py_VISIT(self->dialect);
     Py_VISIT(self->write);
+    Py_VISIT(self->error_obj);
     return 0;
 }
 
@@ -1316,9 +1347,19 @@ Writer_clear(WriterObj *self)
 {
     Py_CLEAR(self->dialect);
     Py_CLEAR(self->write);
+    Py_CLEAR(self->error_obj);
     return 0;
 }
 
+static void
+Writer_finalize(WriterObj *self)
+{
+    Writer_clear(self);
+    if (self->rec != NULL) {
+        PyMem_Free(self->rec);
+    }
+}
+
 PyDoc_STRVAR(Writer_Type_doc,
 "CSV writer\n"
 "\n"
@@ -1326,46 +1367,30 @@ PyDoc_STRVAR(Writer_Type_doc,
 "in CSV format from sequence input.\n"
 );
 
-static PyTypeObject Writer_Type = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    "_csv.writer",                          /*tp_name*/
-    sizeof(WriterObj),                      /*tp_basicsize*/
-    0,                                      /*tp_itemsize*/
-    /* methods */
-    (destructor)Writer_dealloc,             /*tp_dealloc*/
-    0,                                      /*tp_vectorcall_offset*/
-    (getattrfunc)0,                         /*tp_getattr*/
-    (setattrfunc)0,                         /*tp_setattr*/
-    0,                                      /*tp_as_async*/
-    (reprfunc)0,                            /*tp_repr*/
-    0,                                      /*tp_as_number*/
-    0,                                      /*tp_as_sequence*/
-    0,                                      /*tp_as_mapping*/
-    (hashfunc)0,                            /*tp_hash*/
-    (ternaryfunc)0,                         /*tp_call*/
-    (reprfunc)0,                            /*tp_str*/
-    0,                                      /*tp_getattro*/
-    0,                                      /*tp_setattro*/
-    0,                                      /*tp_as_buffer*/
-    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
-        Py_TPFLAGS_HAVE_GC,                     /*tp_flags*/
-    Writer_Type_doc,
-    (traverseproc)Writer_traverse,          /*tp_traverse*/
-    (inquiry)Writer_clear,                  /*tp_clear*/
-    0,                                      /*tp_richcompare*/
-    0,                                      /*tp_weaklistoffset*/
-    (getiterfunc)0,                         /*tp_iter*/
-    (getiterfunc)0,                         /*tp_iternext*/
-    Writer_methods,                         /*tp_methods*/
-    Writer_memberlist,                      /*tp_members*/
-    0,                                      /*tp_getset*/
+static PyType_Slot Writer_Type_slots[] = {
+    {Py_tp_finalize, Writer_finalize},
+    {Py_tp_doc, (char*)Writer_Type_doc},
+    {Py_tp_traverse, Writer_traverse},
+    {Py_tp_clear, Writer_clear},
+    {Py_tp_methods, Writer_methods},
+    {Py_tp_members, Writer_memberlist},
+    {0, NULL}
+};
+
+PyType_Spec Writer_Type_spec = {
+    .name = "_csv.writer",
+    .basicsize = sizeof(WriterObj),
+    .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
+    .slots = Writer_Type_slots,
 };
 
+
 static PyObject *
 csv_writer(PyObject *module, PyObject *args, PyObject *keyword_args)
 {
     PyObject * output_file, * dialect = NULL;
-    WriterObj * self = PyObject_GC_New(WriterObj, &Writer_Type);
+    _csvstate *module_state = get_csv_state(module);
+    WriterObj * self = PyObject_GC_New(WriterObj, module_state->writer_type);
     _Py_IDENTIFIER(write);
 
     if (!self)
@@ -1379,6 +1404,8 @@ csv_writer(PyObject *module, PyObject *args, PyObject *keyword_args)
     self->rec_len = 0;
     self->num_fields = 0;
 
+    self->error_obj = Py_NewRef(module_state->error_obj);
+
     if (!PyArg_UnpackTuple(args, "", 1, 2, &output_file, &dialect)) {
         Py_DECREF(self);
         return NULL;
@@ -1393,7 +1420,8 @@ csv_writer(PyObject *module, PyObject *args, PyObject *keyword_args)
         Py_DECREF(self);
         return NULL;
     }
-    self->dialect = (DialectObj *)_call_dialect(dialect, keyword_args);
+    self->dialect = (DialectObj *)_call_dialect(module_state, dialect,
+                                                keyword_args);
     if (self->dialect == NULL) {
         Py_DECREF(self);
         return NULL;
@@ -1408,13 +1436,14 @@ csv_writer(PyObject *module, PyObject *args, PyObject *keyword_args)
 static PyObject *
 csv_list_dialects(PyObject *module, PyObject *args)
 {
-    return PyDict_Keys(_csvstate_global->dialects);
+    return PyDict_Keys(get_csv_state(module)->dialects);
 }
 
 static PyObject *
 csv_register_dialect(PyObject *module, PyObject *args, PyObject *kwargs)
 {
     PyObject *name_obj, *dialect_obj = NULL;
+    _csvstate *module_state = get_csv_state(module);
     PyObject *dialect;
 
     if (!PyArg_UnpackTuple(args, "", 1, 2, &name_obj, &dialect_obj))
@@ -1426,10 +1455,10 @@ csv_register_dialect(PyObject *module, PyObject *args, PyObject *kwargs)
     }
     if (PyUnicode_READY(name_obj) == -1)
         return NULL;
-    dialect = _call_dialect(dialect_obj, kwargs);
+    dialect = _call_dialect(module_state, dialect_obj, kwargs);
     if (dialect == NULL)
         return NULL;
-    if (PyDict_SetItem(_csvstate_global->dialects, name_obj, dialect) < 0) {
+    if (PyDict_SetItem(module_state->dialects, name_obj, dialect) < 0) {
         Py_DECREF(dialect);
         return NULL;
     }
@@ -1440,9 +1469,10 @@ csv_register_dialect(PyObject *module, PyObject *args, PyObject *kwargs)
 static PyObject *
 csv_unregister_dialect(PyObject *module, PyObject *name_obj)
 {
-    if (PyDict_DelItem(_csvstate_global->dialects, name_obj) < 0) {
+    _csvstate *module_state = get_csv_state(module);
+    if (PyDict_DelItem(module_state->dialects, name_obj) < 0) {
         if (PyErr_ExceptionMatches(PyExc_KeyError)) {
-            PyErr_Format(_csvstate_global->error_obj, "unknown dialect");
+            PyErr_Format(module_state->error_obj, "unknown dialect");
         }
         return NULL;
     }
@@ -1452,14 +1482,15 @@ csv_unregister_dialect(PyObject *module, PyObject *name_obj)
 static PyObject *
 csv_get_dialect(PyObject *module, PyObject *name_obj)
 {
-    return get_dialect_from_registry(name_obj);
+    return get_dialect_from_registry(name_obj, get_csv_state(module));
 }
 
 static PyObject *
 csv_field_size_limit(PyObject *module, PyObject *args)
 {
     PyObject *new_limit = NULL;
-    long old_limit = _csvstate_global->field_limit;
+    _csvstate *module_state = get_csv_state(module);
+    long old_limit = module_state->field_limit;
 
     if (!PyArg_UnpackTuple(args, "field_size_limit", 0, 1, &new_limit))
         return NULL;
@@ -1469,15 +1500,25 @@ csv_field_size_limit(PyObject *module, PyObject *args)
                          "limit must be an integer");
             return NULL;
         }
-        _csvstate_global->field_limit = PyLong_AsLong(new_limit);
-        if (_csvstate_global->field_limit == -1 && PyErr_Occurred()) {
-            _csvstate_global->field_limit = old_limit;
+        module_state->field_limit = PyLong_AsLong(new_limit);
+        if (module_state->field_limit == -1 && PyErr_Occurred()) {
+            module_state->field_limit = old_limit;
             return NULL;
         }
     }
     return PyLong_FromLong(old_limit);
 }
 
+static PyType_Slot error_slots[] = {
+    {0, NULL},
+};
+
+PyType_Spec error_spec = {
+    .name = "_csv.Error",
+    .flags = Py_TPFLAGS_DEFAULT,
+    .slots = error_slots,
+};
+
 /*
  * MODULE
  */
@@ -1610,68 +1651,89 @@ static struct PyMethodDef csv_methods[] = {
     { NULL, NULL }
 };
 
-static struct PyModuleDef _csvmodule = {
-    PyModuleDef_HEAD_INIT,
-    "_csv",
-    csv_module_doc,
-    sizeof(_csvstate),
-    csv_methods,
-    NULL,
-    _csv_traverse,
-    _csv_clear,
-    _csv_free
-};
-
-PyMODINIT_FUNC
-PyInit__csv(void)
-{
-    PyObject *module;
+static int
+csv_exec(PyObject *module) {
     const StyleDesc *style;
+    PyObject *temp;
+    _csvstate *module_state = get_csv_state(module);
 
-    if (PyType_Ready(&Reader_Type) < 0)
-        return NULL;
+    temp = PyType_FromModuleAndSpec(module, &Dialect_Type_spec, NULL);
+    module_state->dialect_type = (PyTypeObject *)temp;
+    if (PyModule_AddObjectRef(module, "Dialect", temp) < 0) {
+        return -1;
+    }
 
-    if (PyType_Ready(&Writer_Type) < 0)
-        return NULL;
+    temp = PyType_FromModuleAndSpec(module, &Reader_Type_spec, NULL);
+    module_state->reader_type = (PyTypeObject *)temp;
+    if (PyModule_AddObjectRef(module, "Reader", temp) < 0) {
+        return -1;
+    }
 
-    /* Create the module and add the functions */
-    module = PyModule_Create(&_csvmodule);
-    if (module == NULL)
-        return NULL;
+    temp = PyType_FromModuleAndSpec(module, &Writer_Type_spec, NULL);
+    module_state->writer_type = (PyTypeObject *)temp;
+    if (PyModule_AddObjectRef(module, "Writer", temp) < 0) {
+        return -1;
+    }
 
     /* Add version to the module. */
     if (PyModule_AddStringConstant(module, "__version__",
-                                   MODULE_VERSION) == -1)
-        return NULL;
+                                   MODULE_VERSION) == -1) {
+        return -1;
+    }
 
     /* Set the field limit */
-    get_csv_state(module)->field_limit = 128 * 1024;
-    /* Do I still need to add this var to the Module Dict? */
+    module_state->field_limit = 128 * 1024;
 
     /* Add _dialects dictionary */
-    get_csv_state(module)->dialects = PyDict_New();
-    if (get_csv_state(module)->dialects == NULL)
-        return NULL;
-    Py_INCREF(get_csv_state(module)->dialects);
-    if (PyModule_AddObject(module, "_dialects", get_csv_state(module)->dialects))
-        return NULL;
+    module_state->dialects = PyDict_New();
+    if (PyModule_AddObjectRef(module, "_dialects", module_state->dialects) < 0) {
+        return -1;
+    }
 
     /* Add quote styles into dictionary */
     for (style = quote_styles; style->name; style++) {
         if (PyModule_AddIntConstant(module, style->name,
                                     style->style) == -1)
-            return NULL;
+            return -1;
     }
 
-    if (PyModule_AddType(module, &Dialect_Type)) {
-        return NULL;
+    /* Add the CSV exception object to the module. */
+    PyObject *bases = PyTuple_Pack(1, PyExc_Exception);
+    if (bases == NULL) {
+        return -1;
+    }
+    module_state->error_obj = PyType_FromModuleAndSpec(module, &error_spec,
+                                                       bases);
+    Py_DECREF(bases);
+    if (module_state->error_obj == NULL) {
+        return -1;
+    }
+    if (PyModule_AddType(module, (PyTypeObject *)module_state->error_obj) != 0) {
+        return -1;
     }
 
-    /* Add the CSV exception object to the module. */
-    get_csv_state(module)->error_obj = PyErr_NewException("_csv.Error", NULL, NULL);
-    if (get_csv_state(module)->error_obj == NULL)
-        return NULL;
-    Py_INCREF(get_csv_state(module)->error_obj);
-    PyModule_AddObject(module, "Error", get_csv_state(module)->error_obj);
-    return module;
+    return 0;
+}
+
+static PyModuleDef_Slot csv_slots[] = {
+    {Py_mod_exec, csv_exec},
+    {0, NULL}
+};
+
+static struct PyModuleDef _csvmodule = {
+    PyModuleDef_HEAD_INIT,
+    "_csv",
+    csv_module_doc,
+    sizeof(_csvstate),
+    csv_methods,
+    csv_slots,
+    _csv_traverse,
+    _csv_clear,
+    _csv_free
+};
+
+PyMODINIT_FUNC
+PyInit__csv(void)
+{
+    return PyModuleDef_Init(&_csvmodule);
 }



More information about the Python-checkins mailing list