Skip to content

Commit 5a7cb18

Browse files
Introduce NumpyArray façade over py::array (#513)
Add a thin wrapper class `NumpyArray` (src/duckdb_py/include/duckdb_python/ numpy/numpy_array.hpp) whose single data member is a `py::array`. This is now the only spot in the codebase that names `py::array` as the underlying numpy-array representation, so a future migration to nanobind's `nb::ndarray` is localized to this one header. The façade exposes Data()/MutableData() (data buffer pointers), an Allocate() factory (dtype + count), a FromObject() factory, an `explicit NumpyArray(py::array)` constructor (a py::object argument implicitly converts via np.asarray semantics, matching prior behaviour), and GetArray() accessors for .attr(...) calls, iteration, resize, and handing the array back to Python. It is default-constructible, copyable, and movable. Route every direct py::array use through the façade: - numpy/raw_array_wrapper.{hpp,cpp}: member + Allocate/MutableData, resize via GetArray() - pandas/pandas_bind.hpp (RegisteredArray) and pandas/column/ pandas_numpy_column.hpp: members + constructors take NumpyArray - numpy/numpy_scan.cpp: scan helpers take NumpyArray&, .data() -> .Data() - numpy/numpy_bind.cpp, pandas/bind.cpp: construct NumpyArray instead of py::array; dtype attrs via GetArray() - numpy/array_wrapper.cpp (ToArray): move out / bool-check via GetArray() - pyconnection.cpp, python_replacement_scan.cpp: py::cast<py::array>(...) -> wrap the object in NumpyArray and use GetArray()
2 parents 676a023 + 3f89994 commit 5a7cb18

11 files changed

Lines changed: 135 additions & 47 deletions

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//===----------------------------------------------------------------------===//
2+
// DuckDB
3+
//
4+
// duckdb_python/numpy/numpy_array.hpp
5+
//
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#pragma once
10+
11+
#include "duckdb_python/pybind11/pybind_wrapper.hpp"
12+
#include "duckdb.hpp"
13+
14+
namespace duckdb {
15+
16+
//! Thin façade over pybind11's `py::array`.
17+
//!
18+
//! This class is the SINGLE place in the codebase that names `py::array` as the
19+
//! underlying numpy-array representation. A future migration to nanobind's
20+
//! `nb::ndarray` should only require changing the member type and the handful of
21+
//! small methods defined here -- every call site goes through this wrapper
22+
//! instead of touching `py::array` directly.
23+
//!
24+
//! For operations that don't (yet) have a first-class method on the façade
25+
//! (Python attribute access via `.attr(...)`, iteration, resizing, handing the
26+
//! array back to Python, ...) use `GetArray()` to reach the underlying object.
27+
class NumpyArray {
28+
public:
29+
NumpyArray() = default;
30+
//! Wrap an existing numpy array. A `py::object` argument is implicitly
31+
//! converted to a `py::array` (np.asarray semantics), matching the behaviour
32+
//! the call sites relied on before this façade existed.
33+
explicit NumpyArray(py::array arr) : array(std::move(arr)) {
34+
}
35+
36+
NumpyArray(NumpyArray &&) = default;
37+
NumpyArray &operator=(NumpyArray &&) = default;
38+
NumpyArray(const NumpyArray &) = default;
39+
NumpyArray &operator=(const NumpyArray &) = default;
40+
41+
public:
42+
//! Allocate a fresh, contiguous 1-D numpy array of `count` elements with the
43+
//! given dtype.
44+
static NumpyArray Allocate(const py::dtype &dtype, idx_t count) {
45+
return NumpyArray(py::array(py::dtype(dtype), count));
46+
}
47+
48+
//! Produce a numpy array from an arbitrary Python object (np.asarray semantics).
49+
static NumpyArray FromObject(py::object obj) {
50+
return NumpyArray(py::array(std::move(obj)));
51+
}
52+
53+
//! Read-only pointer to the underlying data buffer (wraps `py::array::data()`).
54+
const void *Data() const {
55+
return array.data();
56+
}
57+
58+
//! Mutable pointer to the underlying data buffer (wraps `py::array::mutable_data()`).
59+
void *MutableData() {
60+
return array.mutable_data();
61+
}
62+
63+
//! Access the underlying array, e.g. for `.attr(...)` calls, iteration, or to
64+
//! hand it back to Python.
65+
py::array &GetArray() {
66+
return array;
67+
}
68+
const py::array &GetArray() const {
69+
return array;
70+
}
71+
72+
private:
73+
//! The single data member -- the one spot that later becomes `nb::ndarray`.
74+
py::array array;
75+
};
76+
77+
} // namespace duckdb

src/duckdb_py/include/duckdb_python/numpy/raw_array_wrapper.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#pragma once
1010

1111
#include "duckdb_python/pybind11/pybind_wrapper.hpp"
12+
#include "duckdb_python/numpy/numpy_array.hpp"
1213
#include "duckdb.hpp"
1314

1415
namespace duckdb {
@@ -17,7 +18,7 @@ struct RawArrayWrapper {
1718

1819
explicit RawArrayWrapper(const LogicalType &type);
1920

20-
py::array array;
21+
NumpyArray array;
2122
data_ptr_t data;
2223
LogicalType type;
2324
idx_t type_width;

src/duckdb_py/include/duckdb_python/pandas/column/pandas_numpy_column.hpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@
22

33
#include "duckdb_python/pandas/pandas_column.hpp"
44
#include "duckdb_python/pybind11/pybind_wrapper.hpp"
5+
#include "duckdb_python/numpy/numpy_array.hpp"
56

67
namespace duckdb {
78

89
class PandasNumpyColumn : public PandasColumn {
910
public:
10-
PandasNumpyColumn(py::array array_p) : PandasColumn(PandasColumnBackend::NUMPY), array(std::move(array_p)) {
11-
D_ASSERT(py::hasattr(array, "strides"));
12-
stride = array.attr("strides").attr("__getitem__")(0).cast<idx_t>();
11+
PandasNumpyColumn(NumpyArray array_p) : PandasColumn(PandasColumnBackend::NUMPY), array(std::move(array_p)) {
12+
auto &arr = array.GetArray();
13+
D_ASSERT(py::hasattr(arr, "strides"));
14+
stride = arr.attr("strides").attr("__getitem__")(0).cast<idx_t>();
1315
}
1416

1517
public:
16-
py::array array;
18+
NumpyArray array;
1719
idx_t stride;
1820
};
1921

src/duckdb_py/include/duckdb_python/pandas/pandas_bind.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "duckdb_python/pybind11/pybind_wrapper.hpp"
44
#include "duckdb_python/pybind11/python_object_container.hpp"
55
#include "duckdb_python/numpy/numpy_type.hpp"
6+
#include "duckdb_python/numpy/numpy_array.hpp"
67
#include "duckdb/common/helper.hpp"
78
#include "duckdb_python/pandas/pandas_column.hpp"
89

@@ -11,9 +12,9 @@ namespace duckdb {
1112
class ClientContext;
1213

1314
struct RegisteredArray {
14-
explicit RegisteredArray(py::array numpy_array) : numpy_array(std::move(numpy_array)) {
15+
explicit RegisteredArray(NumpyArray numpy_array) : numpy_array(std::move(numpy_array)) {
1516
}
16-
py::array numpy_array;
17+
NumpyArray numpy_array;
1718
};
1819

1920
struct PandasColumnBindData {

src/duckdb_py/numpy/array_wrapper.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -739,15 +739,15 @@ void ArrayWrapper::Append(idx_t current_offset, Vector &input, idx_t source_size
739739
}
740740

741741
py::object ArrayWrapper::ToArray() const {
742-
D_ASSERT(data->array && mask->array);
742+
D_ASSERT(data->array.GetArray() && mask->array.GetArray());
743743
data->Resize(data->count);
744744
if (!requires_mask) {
745-
return std::move(data->array);
745+
return std::move(data->array.GetArray());
746746
}
747747
mask->Resize(mask->count);
748748
// construct numpy arrays from the data and the mask
749-
auto values = std::move(data->array);
750-
auto nullmask = std::move(mask->array);
749+
auto values = std::move(data->array.GetArray());
750+
auto nullmask = std::move(mask->array.GetArray());
751751

752752
// create masked array and return it
753753
auto masked_array = py::module::import("numpy.ma").attr("masked_array")(values, nullmask);

src/duckdb_py/numpy/numpy_bind.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "duckdb_python/numpy/numpy_bind.hpp"
22
#include "duckdb_python/numpy/array_wrapper.hpp"
3+
#include "duckdb_python/numpy/numpy_array.hpp"
34
#include "duckdb_python/pandas/pandas_analyzer.hpp"
45
#include "duckdb_python/pandas/column/pandas_numpy_column.hpp"
56
#include "duckdb_python/pandas/pandas_bind.hpp"
@@ -34,7 +35,7 @@ void NumpyBind::Bind(ClientContext &context, py::handle df, vector<PandasColumnB
3435
auto column = get_fun(df_columns[col_idx]);
3536

3637
if (bind_data.numpy_type.type == NumpyNullableType::FLOAT_16) {
37-
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(py::array(column.attr("astype")("float32")));
38+
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(NumpyArray(column.attr("astype")("float32")));
3839
bind_data.numpy_type.type = NumpyNullableType::FLOAT_32;
3940
duckdb_col_type = NumpyToLogicalType(bind_data.numpy_type);
4041
} else if (bind_data.numpy_type.type == NumpyNullableType::STRING) {
@@ -53,9 +54,9 @@ void NumpyBind::Bind(ClientContext &context, py::handle df, vector<PandasColumnB
5354
duckdb_col_type = LogicalType::ENUM(enum_entries_vec, size);
5455
auto pandas_col = uniq.attr("__getitem__")(1);
5556
bind_data.internal_categorical_type = string(py::str(pandas_col.attr("dtype")));
56-
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(pandas_col);
57+
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(NumpyArray(pandas_col));
5758
} else {
58-
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(column);
59+
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(NumpyArray(column));
5960
duckdb_col_type = NumpyToLogicalType(bind_data.numpy_type);
6061
}
6162

src/duckdb_py/numpy/numpy_scan.cpp

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414
#include "duckdb_python/numpy/numpy_type.hpp"
1515
#include "duckdb/function/scalar/nested_functions.hpp"
1616
#include "duckdb_python/numpy/numpy_scan.hpp"
17+
#include "duckdb_python/numpy/numpy_array.hpp"
1718
#include "duckdb_python/pandas/column/pandas_numpy_column.hpp"
1819

1920
namespace duckdb {
2021

2122
template <class T>
22-
void ScanNumpyColumn(py::array &numpy_col, idx_t stride, idx_t offset, Vector &out, idx_t count) {
23-
auto src_ptr = (T *)numpy_col.data();
23+
void ScanNumpyColumn(NumpyArray &numpy_col, idx_t stride, idx_t offset, Vector &out, idx_t count) {
24+
auto src_ptr = (T *)numpy_col.Data();
2425
if (stride == sizeof(T)) {
2526
FlatVector::SetData(out, data_ptr_cast(src_ptr + offset), count_t(count));
2627
} else {
@@ -32,8 +33,8 @@ void ScanNumpyColumn(py::array &numpy_col, idx_t stride, idx_t offset, Vector &o
3233
}
3334

3435
template <class T, class V>
35-
void ScanNumpyCategoryTemplated(py::array &column, idx_t offset, Vector &out, idx_t count) {
36-
auto src_ptr = (T *)column.data();
36+
void ScanNumpyCategoryTemplated(NumpyArray &column, idx_t offset, Vector &out, idx_t count) {
37+
auto src_ptr = (T *)column.Data();
3738
auto tgt_ptr = (V *)FlatVector::GetData(out);
3839
auto &tgt_mask = FlatVector::ValidityMutable(out);
3940
for (idx_t i = 0; i < count; i++) {
@@ -47,7 +48,7 @@ void ScanNumpyCategoryTemplated(py::array &column, idx_t offset, Vector &out, id
4748
}
4849

4950
template <class T>
50-
void ScanNumpyCategory(py::array &column, idx_t count, idx_t offset, Vector &out, string &src_type) {
51+
void ScanNumpyCategory(NumpyArray &column, idx_t count, idx_t offset, Vector &out, string &src_type) {
5152
if (src_type == "int8") {
5253
ScanNumpyCategoryTemplated<int8_t, T>(column, offset, out, count);
5354
} else if (src_type == "int16") {
@@ -63,7 +64,7 @@ void ScanNumpyCategory(py::array &column, idx_t count, idx_t offset, Vector &out
6364

6465
static void ApplyMask(PandasColumnBindData &bind_data, ValidityMask &validity, idx_t count, idx_t offset) {
6566
D_ASSERT(bind_data.mask);
66-
auto mask = reinterpret_cast<const bool *>(bind_data.mask->numpy_array.data());
67+
auto mask = reinterpret_cast<const bool *>(bind_data.mask->numpy_array.Data());
6768
for (idx_t i = 0; i < count; i++) {
6869
auto is_null = mask[offset + i];
6970
if (is_null) {
@@ -236,18 +237,18 @@ void NumpyScan::Scan(ClientContext &context, PandasColumnBindData &bind_data, id
236237
ScanNumpyMasked<int64_t>(bind_data, count, offset, out);
237238
break;
238239
case NumpyNullableType::FLOAT_32:
239-
ScanNumpyFpColumn<float>(bind_data, reinterpret_cast<const float *>(array.data()), numpy_col.stride, count,
240+
ScanNumpyFpColumn<float>(bind_data, reinterpret_cast<const float *>(array.Data()), numpy_col.stride, count,
240241
offset, out);
241242
break;
242243
case NumpyNullableType::FLOAT_64:
243-
ScanNumpyFpColumn<double>(bind_data, reinterpret_cast<const double *>(array.data()), numpy_col.stride, count,
244+
ScanNumpyFpColumn<double>(bind_data, reinterpret_cast<const double *>(array.Data()), numpy_col.stride, count,
244245
offset, out);
245246
break;
246247
case NumpyNullableType::DATETIME_NS:
247248
case NumpyNullableType::DATETIME_MS:
248249
case NumpyNullableType::DATETIME_US:
249250
case NumpyNullableType::DATETIME_S: {
250-
auto src_ptr = reinterpret_cast<const int64_t *>(array.data());
251+
auto src_ptr = reinterpret_cast<const int64_t *>(array.Data());
251252
auto tgt_ptr = FlatVector::GetDataMutable<timestamp_t>(out);
252253

253254
using timestamp_convert_func = std::function<timestamp_t(int64_t)>;
@@ -307,7 +308,7 @@ void NumpyScan::Scan(ClientContext &context, PandasColumnBindData &bind_data, id
307308
case NumpyNullableType::TIMEDELTA_US:
308309
case NumpyNullableType::TIMEDELTA_MS:
309310
case NumpyNullableType::TIMEDELTA_S: {
310-
auto src_ptr = reinterpret_cast<const int64_t *>(array.data());
311+
auto src_ptr = reinterpret_cast<const int64_t *>(array.Data());
311312
auto tgt_ptr = FlatVector::GetDataMutable<interval_t>(out);
312313
auto &mask = FlatVector::ValidityMutable(out);
313314

@@ -352,7 +353,7 @@ void NumpyScan::Scan(ClientContext &context, PandasColumnBindData &bind_data, id
352353
case NumpyNullableType::STRING:
353354
case NumpyNullableType::OBJECT: {
354355
// Get the source pointer of the numpy array
355-
auto src_ptr = (PyObject **)array.data(); // NOLINT
356+
auto src_ptr = (PyObject **)array.Data(); // NOLINT
356357
const bool is_object_col = bind_data.numpy_type.type == NumpyNullableType::OBJECT;
357358
if (is_object_col && out.GetType().id() != LogicalTypeId::VARCHAR) {
358359
//! We have determined the underlying logical type of this object column

src/duckdb_py/numpy/raw_array_wrapper.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,14 @@ string RawArrayWrapper::DuckDBToNumpyDtype(const LogicalType &type) {
151151
void RawArrayWrapper::Initialize(idx_t capacity) {
152152
string dtype = DuckDBToNumpyDtype(type);
153153

154-
array = py::array(py::dtype(dtype), capacity);
155-
data = data_ptr_cast(array.mutable_data());
154+
array = NumpyArray::Allocate(py::dtype(dtype), capacity);
155+
data = data_ptr_cast(array.MutableData());
156156
}
157157

158158
void RawArrayWrapper::Resize(idx_t new_capacity) {
159159
vector<py::ssize_t> new_shape {py::ssize_t(new_capacity)};
160-
array.resize(new_shape, false);
161-
data = data_ptr_cast(array.mutable_data());
160+
array.GetArray().resize(new_shape, false);
161+
data = data_ptr_cast(array.MutableData());
162162
}
163163

164164
} // namespace duckdb

src/duckdb_py/pandas/bind.cpp

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "duckdb_python/pandas/pandas_bind.hpp"
22
#include "duckdb_python/pandas/pandas_analyzer.hpp"
33
#include "duckdb_python/pandas/column/pandas_numpy_column.hpp"
4+
#include "duckdb_python/numpy/numpy_array.hpp"
45
#include "duckdb_python/pyconnection/pyconnection.hpp"
56

67
namespace duckdb {
@@ -53,19 +54,19 @@ static LogicalType BindColumn(ClientContext &context, PandasBindColumn &column_p
5354

5455
if (column_has_mask) {
5556
// masked object, fetch the internal data and mask array
56-
bind_data.mask = std::make_unique<RegisteredArray>(column.attr("array").attr("_mask"));
57+
bind_data.mask = std::make_unique<RegisteredArray>(NumpyArray(column.attr("array").attr("_mask")));
5758
}
5859

5960
if (bind_data.numpy_type.type == NumpyNullableType::CATEGORY) {
6061
// for category types, we create an ENUM type for string or use the converted numpy type for the rest
6162
D_ASSERT(py::hasattr(column, "cat"));
6263
D_ASSERT(py::hasattr(column.attr("cat"), "categories"));
63-
auto categories = py::array(column.attr("cat").attr("categories"));
64-
auto categories_pd_type = ConvertNumpyType(categories.attr("dtype"));
64+
NumpyArray categories(column.attr("cat").attr("categories"));
65+
auto categories_pd_type = ConvertNumpyType(categories.GetArray().attr("dtype"));
6566
if (categories_pd_type.type == NumpyNullableType::OBJECT) {
6667
// Let's hope the object type is a string.
6768
bind_data.numpy_type.type = NumpyNullableType::CATEGORY;
68-
vector<string> enum_entries = py::cast<vector<string>>(categories);
69+
vector<string> enum_entries = py::cast<vector<string>>(categories.GetArray());
6970
idx_t size = enum_entries.size();
7071
Vector enum_entries_vec(LogicalType::VARCHAR, size);
7172
auto enum_entries_ptr = FlatVector::GetDataMutable<string_t>(enum_entries_vec);
@@ -74,33 +75,33 @@ static LogicalType BindColumn(ClientContext &context, PandasBindColumn &column_p
7475
}
7576
D_ASSERT(py::hasattr(column.attr("cat"), "codes"));
7677
column_type = LogicalType::ENUM(enum_entries_vec, size);
77-
auto pandas_col = py::array(column.attr("cat").attr("codes"));
78-
bind_data.internal_categorical_type = string(py::str(pandas_col.attr("dtype")));
79-
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(pandas_col);
78+
NumpyArray pandas_col(column.attr("cat").attr("codes"));
79+
bind_data.internal_categorical_type = string(py::str(pandas_col.GetArray().attr("dtype")));
80+
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(std::move(pandas_col));
8081
} else {
81-
auto pandas_col = py::array(column.attr("to_numpy")());
82-
auto numpy_type = pandas_col.attr("dtype");
83-
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(pandas_col);
82+
NumpyArray pandas_col(column.attr("to_numpy")());
83+
auto numpy_type = pandas_col.GetArray().attr("dtype");
84+
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(std::move(pandas_col));
8485
// for category types (non-strings), we use the converted numpy type
8586
bind_data.numpy_type = ConvertNumpyType(numpy_type);
8687
column_type = NumpyToLogicalType(bind_data.numpy_type);
8788
}
8889
} else if (bind_data.numpy_type.type == NumpyNullableType::FLOAT_16) {
8990
auto pandas_array = column.attr("array");
90-
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(py::array(column.attr("to_numpy")("float32")));
91+
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(NumpyArray(column.attr("to_numpy")("float32")));
9192
bind_data.numpy_type.type = NumpyNullableType::FLOAT_32;
9293
column_type = NumpyToLogicalType(bind_data.numpy_type);
9394
} else {
9495
auto pandas_array = column.attr("array");
9596
if (py::hasattr(pandas_array, "_data")) {
9697
// This means we can access the numpy array directly
97-
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(column.attr("array").attr("_data"));
98+
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(NumpyArray(column.attr("array").attr("_data")));
9899
} else if (py::hasattr(pandas_array, "asi8")) {
99100
// This is a datetime object, has the option to get the array as int64_t's
100-
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(py::array(pandas_array.attr("asi8")));
101+
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(NumpyArray(pandas_array.attr("asi8")));
101102
} else {
102103
// Otherwise we have to get it through 'to_numpy()'
103-
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(py::array(column.attr("to_numpy")()));
104+
bind_data.pandas_col = std::make_unique<PandasNumpyColumn>(NumpyArray(column.attr("to_numpy")()));
104105
}
105106
column_type = NumpyToLogicalType(bind_data.numpy_type);
106107
}

src/duckdb_py/pyconnection.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "duckdb_python/pyresult.hpp"
2727
#include "duckdb_python/python_conversion.hpp"
2828
#include "duckdb_python/numpy/numpy_type.hpp"
29+
#include "duckdb_python/numpy/numpy_array.hpp"
2930
#include "duckdb_python/jupyter_progress_bar_display.hpp"
3031
#include "duckdb_python/pyfilesystem.hpp"
3132
#include "duckdb/parser/parsed_data/create_scalar_function_info.hpp"
@@ -2352,7 +2353,7 @@ bool IsValidNumpyDimensions(const py::handle &object, int &dim) {
23522353
if (!py::isinstance(object, import_cache.numpy.ndarray())) {
23532354
return false;
23542355
}
2355-
auto shape = (py::cast<py::array>(object)).attr("shape");
2356+
auto shape = NumpyArray(py::reinterpret_borrow<py::object>(object)).GetArray().attr("shape");
23562357
if (py::len(shape) != 1) {
23572358
return false;
23582359
}
@@ -2366,7 +2367,7 @@ NumpyObjectType DuckDBPyConnection::IsAcceptedNumpyObject(const py::object &obje
23662367
}
23672368
auto import_cache_ = ImportCache();
23682369
if (py::isinstance(object, import_cache_->numpy.ndarray())) {
2369-
auto len = py::len((py::cast<py::array>(object)).attr("shape"));
2370+
auto len = py::len(NumpyArray(object).GetArray().attr("shape"));
23702371
switch (len) {
23712372
case 1:
23722373
return NumpyObjectType::NDARRAY1D;

0 commit comments

Comments
 (0)