Repository: pandas-dev/pandas
Pull request: CLN: Get rid of PyArray_GetCastFunc
- [ ] closes #55996  (Replace xxxx with the GitHub issue number)
- [ ] [Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [ ] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [ ] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [ ] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.


Problem statement:
MAINT: Remove use of `PyArray_GetCastFunc`
`pandas` uses `PyArray_GetCastFunc` in the json reader (or writer).  As far as I can tell, it is used only for simple NumPy numeric types.

I want to remove this from NumPy, because for many years NumPy *never* uses those cast functions internally (OK, a few were never replaced but not most are unused).
From glancing at it, I think you can just add a few branches and cast manually.  If needed, I could create a new `PyArray_CastBuffer()` function, but I suspect that would just add overhead for something that is simple enough.

EDIT: I will add the note, that the `castfunc` never deals with unaligned or memory or byteswapping.  So this is really just a C cast.

Hints from issue discussion:
Yeah, I think we are just casting scalar dt64 to int64. I'll take a look.

Commit message: CLN: Get rid of PyArray_GetCastFunc

Patch to review:
diff --git a/pandas/_libs/src/vendored/ujson/python/objToJSON.c b/pandas/_libs/src/vendored/ujson/python/objToJSON.c
index 22ffec51c82f1..89500ac80d12f 100644
--- a/pandas/_libs/src/vendored/ujson/python/objToJSON.c
+++ b/pandas/_libs/src/vendored/ujson/python/objToJSON.c
@@ -1280,13 +1280,10 @@ char **NpyArr_encodeLabels(PyArrayObject *labels, PyObjectEncoder *enc,
     NPY_DATETIMEUNIT dateUnit = NPY_FR_ns;
     if (PyTypeNum_ISDATETIME(type_num)) {
       is_datetimelike = 1;
-      PyArray_VectorUnaryFunc *castfunc =
-          PyArray_GetCastFunc(PyArray_DescrFromType(type_num), NPY_INT64);
-      if (!castfunc) {
-        PyErr_Format(PyExc_ValueError, "Cannot cast numpy dtype %d to long",
-                     enc->npyType);
-      }
-      castfunc(dataptr, &i8date, 1, NULL, NULL);
+      PyObject *scalarItem = PyArray_ToScalar(dataptr, labels);
+      PyArray_CastScalarToCtype(scalarItem, &i8date,
+                                PyArray_DescrFromType(NPY_INT64));
+      Py_DECREF(scalarItem);
       dateUnit = get_datetime_metadata_from_dtype(dtype).base;
     } else if (PyDate_Check(item) || PyDelta_Check(item)) {
       is_datetimelike = 1;
@@ -1432,6 +1429,13 @@ void Object_beginTypeContext(JSOBJ _obj, JSONTypeContext *tc) {
                    enc->npyType);
     }
     castfunc(enc->npyValue, &longVal, 1, NULL, NULL);
+
+    PyObject *scalar =
+        PyArray_ToScalar(enc->npyValue, enc->npyCtxtPassthru->array);
+    PyArray_Scalar(enc->npyValue, PyArray_DescrFromType(enc->npyType), NULL);
+    PyArray_CastScalarToCtype(scalar, &longVal,
+                              PyArray_DescrFromType(NPY_INT64));
+    Py_DECREF(scalar);
     if (longVal == get_nat()) {
       tc->type = JT_NULL;
     } else {
@@ -1532,7 +1536,6 @@ void Object_beginTypeContext(JSOBJ _obj, JSONTypeContext *tc) {
 
     PyArray_Descr *outcode = PyArray_DescrFromType(NPY_INT64);
     PyArray_CastScalarToCtype(obj, &longVal, outcode);
-    Py_DECREF(outcode);
 
     if (enc->datetimeIso) {
       GET_TC(tc)->longValue = longVal;


Code review comments:
diff_hunk: @@ -1280,13 +1280,10 @@ char **NpyArr_encodeLabels(PyArrayObject *labels, PyObjectEncoder *enc,
     NPY_DATETIMEUNIT dateUnit = NPY_FR_ns;
     if (PyTypeNum_ISDATETIME(type_num)) {
       is_datetimelike = 1;
-      PyArray_VectorUnaryFunc *castfunc =
-          PyArray_GetCastFunc(PyArray_DescrFromType(type_num), NPY_INT64);
-      if (!castfunc) {
-        PyErr_Format(PyExc_ValueError, "Cannot cast numpy dtype %d to long",
-                     enc->npyType);
-      }
-      castfunc(dataptr, &i8date, 1, NULL, NULL);
+      PyObject *scalarItem = PyArray_ToScalar(dataptr, labels);
+      PyArray_CastScalarToCtype(scalarItem, &i8date,
+                                PyArray_DescrFromType(NPY_INT64));
+      Py_DECREF(scalarItem);
path: pandas/_libs/src/vendored/ujson/python/objToJSON.c
text: @user1:
Here, you can just use:
```
memcpy(&i8date, dataptr, sizeof(i8data));
```
I think, since you know this is an int64, as datetimes are int64 (I am unsure about alignment, thus the memcpy).

Same below?  (at last I would think so?)

@author:
Wow, this is even easier, thanks!

@user1:
Actually, I just realized the cast function doesn't handle alignment, so even `i8data = *(npy_int64 *)dataptr;` should work.