diff --git a/pandas/core/arrays/arrow/array.py b/pandas/core/arrays/arrow/array.py index 2313e28950de7..353da80e27464 100644 --- a/pandas/core/arrays/arrow/array.py +++ b/pandas/core/arrays/arrow/array.py @@ -2091,7 +2091,10 @@ def _dt_round( return self._round_temporally("round", freq, ambiguous, nonexistent) def _dt_to_pydatetime(self): - return np.array(self._pa_array.to_pylist(), dtype=object) + data = self._pa_array.to_pylist() + if self._dtype.pyarrow_dtype.unit == "ns": + data = [ts.to_pydatetime(warn=False) for ts in data] + return np.array(data, dtype=object) def _dt_tz_localize( self, diff --git a/pandas/io/sql.py b/pandas/io/sql.py index ec04a9ce81d92..044fd9806d921 100644 --- a/pandas/io/sql.py +++ b/pandas/io/sql.py @@ -964,14 +964,16 @@ def insert_data(self) -> tuple[list[str], list[np.ndarray]]: data_list: list[np.ndarray] = [None] * ncols # type: ignore[list-item] for i, (_, ser) in enumerate(temp.items()): - vals = ser._values - if vals.dtype.kind == "M": - d = vals.to_pydatetime() - elif vals.dtype.kind == "m": + if ser.dtype.kind == "M": + d = ser.dt.to_pydatetime() + elif ser.dtype.kind == "m": + vals = ser._values + if isinstance(vals, ArrowExtensionArray): + vals = vals.to_numpy(dtype=np.dtype("m8[ns]")) # store as integers, see GH#6921, GH#7076 d = vals.view("i8").astype(object) else: - d = vals.astype(object) + d = ser._values.astype(object) assert isinstance(d, np.ndarray), type(d) diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index 1cdff54cd6df6..a740cb91850d3 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -2271,6 +2271,7 @@ def test_dt_to_pydatetime(): result = ser.dt.to_pydatetime() expected = np.array(data, dtype=object) tm.assert_numpy_array_equal(result, expected) + assert all(type(res) is datetime for res in result) expected = ser.astype("datetime64[ns]").dt.to_pydatetime() tm.assert_numpy_array_equal(result, expected) diff --git a/pandas/tests/io/test_sql.py b/pandas/tests/io/test_sql.py index 3d79d483038ee..1bfc5cf0c3178 100644 --- a/pandas/tests/io/test_sql.py +++ b/pandas/tests/io/test_sql.py @@ -24,6 +24,7 @@ date, datetime, time, + timedelta, ) from io import StringIO from pathlib import Path @@ -549,6 +550,26 @@ def test_dataframe_to_sql(conn, test_frame1, request): test_frame1.to_sql("test", conn, if_exists="append", index=False) +@pytest.mark.db +@pytest.mark.parametrize("conn", all_connectable) +def test_dataframe_to_sql_arrow_dtypes(conn, request): + # GH 52046 + pytest.importorskip("pyarrow") + df = DataFrame( + { + "int": pd.array([1], dtype="int8[pyarrow]"), + "datetime": pd.array( + [datetime(2023, 1, 1)], dtype="timestamp[ns][pyarrow]" + ), + "timedelta": pd.array([timedelta(1)], dtype="duration[ns][pyarrow]"), + "string": pd.array(["a"], dtype="string[pyarrow]"), + } + ) + conn = request.getfixturevalue(conn) + with tm.assert_produces_warning(UserWarning, match="the 'timedelta'"): + df.to_sql("test_arrow", conn, if_exists="replace", index=False) + + @pytest.mark.db @pytest.mark.parametrize("conn", all_connectable) @pytest.mark.parametrize("method", [None, "multi"])