Repository: pandas-dev/pandas
Pull request #56355 (2023-12-06 04:10:17)

remove unused code from JSONtoObj
I always thought we did some kind of direct serialization to NumPy with the ujson reader, but as it turns out we do not. 

This must have all been started but at some point abandoned


Patch:
diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst
index d421b422f9..67b4052b38 100644
--- a/doc/source/whatsnew/v2.2.0.rst
+++ b/doc/source/whatsnew/v2.2.0.rst
@@ -358,19 +358,30 @@ Other API changes
 Deprecations
 ~~~~~~~~~~~~
 
-Deprecate aliases ``M``, ``SM``, ``BM``, ``CBM``, ``Q``, ``BQ``, ``Y``, and ``BY`` in favour of ``ME``, ``SME``, ``BME``, ``CBME``, ``QE``, ``BQE``, ``YE``,  and ``BYE`` for offsets
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Deprecate aliases ``M``, ``Q``, ``Y``, etc. in favour of ``ME``, ``QE``, ``YE``, etc. for offsets
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Deprecated the following frequency aliases (:issue:`9586`):
 
-- ``M`` (month end) has been renamed ``ME`` for offsets
-- ``SM`` (semi month end) has been renamed ``SME`` for offsets
-- ``BM`` (business month end) has been renamed ``BME`` for offsets
-- ``CBM`` (custom business month end) has been renamed ``CBME`` for offsets
-- ``Q`` (quarter end) has been renamed ``QE`` for offsets
-- ``BQ`` (business quarter end) has been renamed ``BQE`` for offsets
-- ``Y`` (year end) has been renamed ``YE`` for offsets
-- ``BY`` (business year end) has been renamed ``BYE`` for offsets
++-------------------------------+------------------+------------------+
+|offsets                        |deprecated aliases|new aliases       |
++===============================+==================+==================+
+|:class:`MonthEnd`              |      ``M``       |     ``ME``       |
++-------------------------------+------------------+------------------+
+|:class:`BusinessMonthEnd`      |      ``BM``      |     ``BME``      |
++-------------------------------+------------------+------------------+
+|:class:`SemiMonthEnd`          |      ``SM``      |     ``SME``      |
++-------------------------------+------------------+------------------+
+|:class:`CustomBusinessMonthEnd`|      ``CBM``     |     ``CBME``     |
++-------------------------------+------------------+------------------+
+|:class:`QuarterEnd`            |      ``Q``       |     ``QE``       |
++-------------------------------+------------------+------------------+
+|:class:`BQuarterEnd`           |      ``BQ``      |     ``BQE``      |
++-------------------------------+------------------+------------------+
+|:class:`YearEnd`               |      ``Y``       |     ``YE``       |
++-------------------------------+------------------+------------------+
+|:class:`BYearEnd`              |      ``BY``      |     ``BYE``      |
++-------------------------------+------------------+------------------+
 
 For example:
 
@@ -598,6 +609,7 @@ I/O
 - Bug in :func:`read_csv` where ``engine="python"`` did not respect ``chunksize`` arg when ``skiprows`` was specified. (:issue:`56323`)
 - Bug in :func:`read_csv` where ``engine="python"`` was causing a ``TypeError`` when a callable ``skiprows`` and a chunk size was specified. (:issue:`55677`)
 - Bug in :func:`read_csv` where ``on_bad_lines="warn"`` would write to ``stderr`` instead of raise a Python warning. This now yields a :class:`.errors.ParserWarning` (:issue:`54296`)
+- Bug in :func:`read_csv` with ``engine="pyarrow"`` where ``quotechar`` was ignored (:issue:`52266`)
 - Bug in :func:`read_csv` with ``engine="pyarrow"`` where ``usecols`` wasn't working with a csv with no headers (:issue:`54459`)
 - Bug in :func:`read_excel`, with ``engine="xlrd"`` (``xls`` files) erroring when file contains NaNs/Infs (:issue:`54564`)
 - Bug in :func:`read_json` not handling dtype conversion properly if ``infer_string`` is set (:issue:`56195`)
diff --git a/pandas/_libs/src/vendored/ujson/python/JSONtoObj.c b/pandas/_libs/src/vendored/ujson/python/JSONtoObj.c
index e7c58d498f..b7ee58c63a 100644
--- a/pandas/_libs/src/vendored/ujson/python/JSONtoObj.c
+++ b/pandas/_libs/src/vendored/ujson/python/JSONtoObj.c
@@ -38,43 +38,9 @@ https://www.opensource.apple.com/source/tcl/tcl-14/tcl/license.terms
 
 // Licence at LICENSES/ULTRAJSON_LICENSE
 
-#define PY_ARRAY_UNIQUE_SYMBOL UJSON_NUMPY
-#define NO_IMPORT_ARRAY
-#define PY_SSIZE_T_CLEAN
 #include "pandas/vendored/ujson/lib/ultrajson.h"
+#define PY_SSIZE_T_CLEAN
 #include <Python.h>
-#include <numpy/arrayobject.h>
-
-typedef struct __PyObjectDecoder {
-  JSONObjectDecoder dec;
-
-  void *npyarr;      // Numpy context buffer
-  void *npyarr_addr; // Ref to npyarr ptr to track DECREF calls
-} PyObjectDecoder;
-
-typedef struct __NpyArrContext {
-  PyObject *ret;
-  PyObject *labels[2];
-  PyArray_Dims shape;
-
-  PyObjectDecoder *dec;
-} NpyArrContext;
-
-// free the numpy context buffer
-void Npy_releaseContext(NpyArrContext *npyarr) {
-  if (npyarr) {
-    if (npyarr->shape.ptr) {
-      PyObject_Free(npyarr->shape.ptr);
-    }
-    if (npyarr->dec) {
-      npyarr->dec->npyarr = NULL;
-    }
-    Py_XDECREF(npyarr->labels[0]);
-    Py_XDECREF(npyarr->labels[1]);
-    Py_XDECREF(npyarr->ret);
-    PyObject_Free(npyarr);
-  }
-}
 
 static int Object_objectAddKey(void *prv, JSOBJ obj, JSOBJ name, JSOBJ value) {
   int ret = PyDict_SetItem(obj, name, value);
@@ -132,95 +98,62 @@ static JSOBJ Object_newDouble(void *prv, double value) {
 }
 
 static void Object_releaseObject(void *prv, JSOBJ obj, void *_decoder) {
-  PyObjectDecoder *decoder = (PyObjectDecoder *)_decoder;
-  if (obj != decoder->npyarr_addr) {
-    Py_XDECREF(((PyObject *)obj));
-  }
+  Py_XDECREF(((PyObject *)obj));
 }
 
-static char *g_kwlist[] = {"obj", "precise_float", "labelled", "dtype", NULL};
-
 PyObject *JSONToObj(PyObject *self, PyObject *args, PyObject *kwargs) {
-  PyObject *ret;
-  PyObject *sarg;
-  PyObject *arg;
-  PyObject *opreciseFloat = NULL;
-  JSONObjectDecoder *decoder;
-  PyObjectDecoder pyDecoder;
-  PyArray_Descr *dtype = NULL;
-  int labelled = 0;
-
-  JSONObjectDecoder dec = {
-      Object_newString,  Object_objectAddKey,  Object_arrayAddItem,
-      Object_newTrue,    Object_newFalse,      Object_newNull,
-      Object_newPosInf,  Object_newNegInf,     Object_newObject,
-      Object_endObject,  Object_newArray,      Object_endArray,
-      Object_newInteger, Object_newLong,       Object_newUnsignedLong,
-      Object_newDouble,  Object_releaseObject, PyObject_Malloc,
-      PyObject_Free,     PyObject_Realloc};
-
-  dec.preciseFloat = 0;
-  dec.prv = NULL;
-
-  pyDecoder.dec = dec;
-  pyDecoder.npyarr = NULL;
-  pyDecoder.npyarr_addr = NULL;
-
-  decoder = (JSONObjectDecoder *)&pyDecoder;
-
-  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OiiO&", g_kwlist, &arg,
-                                   &opreciseFloat, &labelled,
-                                   PyArray_DescrConverter2, &dtype)) {
-    Npy_releaseContext(pyDecoder.npyarr);
+  JSONObjectDecoder dec = {.newString = Object_newString,
+                           .objectAddKey = Object_objectAddKey,
+                           .arrayAddItem = Object_arrayAddItem,
+                           .newTrue = Object_newTrue,
+                           .newFalse = Object_newFalse,
+                           .newNull = Object_newNull,
+                           .newPosInf = Object_newPosInf,
+                           .newNegInf = Object_newNegInf,
+                           .newObject = Object_newObject,
+                           .endObject = Object_endObject,
+                           .newArray = Object_newArray,
+                           .endArray = Object_endArray,
+                           .newInt = Object_newInteger,
+                           .newLong = Object_newLong,
+                           .newUnsignedLong = Object_newUnsignedLong,
+                           .newDouble = Object_newDouble,
+                           .releaseObject = Object_releaseObject,
+                           .malloc = PyObject_Malloc,
+                           .free = PyObject_Free,
+                           .realloc = PyObject_Realloc,
+                           .errorStr = NULL,
+                           .errorOffset = NULL,
+                           .preciseFloat = 0,
+                           .prv = NULL};
+
+  char *kwlist[] = {"obj", "precise_float", NULL};
+  char *buf;
+  Py_ssize_t len;
+  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s#|b", kwlist, &buf, &len,
+                                   &dec.preciseFloat)) {
     return NULL;
   }
 
-  if (opreciseFloat && PyObject_IsTrue(opreciseFloat)) {
-    decoder->preciseFloat = 1;
-  }
-
-  if (PyBytes_Check(arg)) {
-    sarg = arg;
-  } else if (PyUnicode_Check(arg)) {
-    sarg = PyUnicode_AsUTF8String(arg);
-    if (sarg == NULL) {
-      // Exception raised above us by codec according to docs
-      return NULL;
-    }
-  } else {
-    PyErr_Format(PyExc_TypeError, "Expected 'str' or 'bytes'");
-    return NULL;
-  }
-
-  decoder->errorStr = NULL;
-  decoder->errorOffset = NULL;
-
-  ret = JSON_DecodeObject(decoder, PyBytes_AS_STRING(sarg),
-                          PyBytes_GET_SIZE(sarg));
-
-  if (sarg != arg) {
-    Py_DECREF(sarg);
-  }
+  PyObject *ret = JSON_DecodeObject(&dec, buf, len);
 
   if (PyErr_Occurred()) {
     if (ret) {
       Py_DECREF((PyObject *)ret);
     }
-    Npy_releaseContext(pyDecoder.npyarr);
     return NULL;
   }
 
-  if (decoder->errorStr) {
+  if (dec.errorStr) {
     /*
     FIXME: It's possible to give a much nicer error message here with actual
     failing element in input etc*/
 
-    PyErr_Format(PyExc_ValueError, "%s", decoder->errorStr);
+    PyErr_Format(PyExc_ValueError, "%s", dec.errorStr);
 
     if (ret) {
       Py_DECREF((PyObject *)ret);
     }
-    Npy_releaseContext(pyDecoder.npyarr);
 
     return NULL;
   }
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
index 3edfea4480..1630c2c319 100644
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -10339,6 +10339,14 @@ class DataFrame(NDFrame, OpsMixin):
         0  NaN  4
         1  5.0  5
 
+        It is also possible to use `map` with functions that are not
+        `lambda` functions:
+
+        >>> df.map(round, ndigits=1)
+             0    1
+        0  1.0  2.1
+        1  3.4  4.6
+
         Note that a vectorized version of `func` often exists, which will
         be much faster. You could square each number elementwise.
 
diff --git a/pandas/io/parsers/arrow_parser_wrapper.py b/pandas/io/parsers/arrow_parser_wrapper.py
index 037239e4ec..1c79392d54 100644
--- a/pandas/io/parsers/arrow_parser_wrapper.py
+++ b/pandas/io/parsers/arrow_parser_wrapper.py
@@ -65,6 +65,7 @@ class ArrowParserWrapper(ParserBase):
             "escapechar": "escape_char",
             "skip_blank_lines": "ignore_empty_lines",
             "decimal": "decimal_point",
+            "quotechar": "quote_char",
         }
         for pandas_name, pyarrow_name in mapping.items():
             if pandas_name in self.kwds and self.kwds.get(pandas_name) is not None:


Test patch:
diff --git a/pandas/tests/io/json/test_ujson.py b/pandas/tests/io/json/test_ujson.py
index 646f674593..56ea9ea625 100644
--- a/pandas/tests/io/json/test_ujson.py
+++ b/pandas/tests/io/json/test_ujson.py
@@ -585,7 +585,7 @@ class TestUltraJSONTests:
         assert ujson.ujson_loads(int_exp) == json.loads(int_exp)
 
     def test_loads_non_str_bytes_raises(self):
-        msg = "Expected 'str' or 'bytes'"
+        msg = "a bytes-like object is required, not 'NoneType'"
         with pytest.raises(TypeError, match=msg):
             ujson.ujson_loads(None)
 
diff --git a/pandas/tests/io/parser/test_quoting.py b/pandas/tests/io/parser/test_quoting.py
index 0a1ba0252f..a70b7e3389 100644
--- a/pandas/tests/io/parser/test_quoting.py
+++ b/pandas/tests/io/parser/test_quoting.py
@@ -66,7 +66,6 @@ def test_quote_char_basic(all_parsers):
     tm.assert_frame_equal(result, expected)
 
 
-@xfail_pyarrow  # ValueError: The 'quoting' option is not supported
 @pytest.mark.parametrize("quote_char", ["~", "*", "%", "$", "@", "P"])
 def test_quote_char_various(all_parsers, quote_char):
     parser = all_parsers