Skip to content

Commit fe4fb74

Browse files
committed
nanobind: DuckDBPyType::TryConvert helper to restore implicit type conversion (shared_ptr caster strips convert)
1 parent 9286f8c commit fe4fb74

6 files changed

Lines changed: 46 additions & 14 deletions

File tree

src/duckdb_py/include/duckdb_python/pytype.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ class DuckDBPyType : public std::enable_shared_from_this<DuckDBPyType> {
2828
public:
2929
static void Initialize(py::handle &m);
3030

31+
//! Convert a Python object (an existing DuckDBPyType, a type string, a Python type object such as `int`, or a
32+
//! dict describing a struct) into a DuckDBPyType. nanobind's shared_ptr type caster strips the implicit-convert
33+
//! flag, so a plain try_cast<shared_ptr<DuckDBPyType>> no longer triggers DuckDBPyType's registered implicit
34+
//! conversion; this routes non-DuckDBPyType objects through the registered Python constructor. Returns false
35+
//! (without throwing) when the object can't be converted.
36+
static bool TryConvert(const py::object &object, std::shared_ptr<DuckDBPyType> &result);
37+
3138
public:
3239
bool Equals(const std::shared_ptr<DuckDBPyType> &other) const;
3340
bool EqualsString(const string &type_str) const;

src/duckdb_py/native/python_conversion.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "duckdb_python/python_conversion.hpp"
22
#include "duckdb_python/pybind11/pybind_wrapper.hpp"
3+
#include "duckdb_python/pytype.hpp"
34

45
#include "duckdb_python/pyrelation.hpp"
56
#include "duckdb_python/pyconnection/pyconnection.hpp"
@@ -594,9 +595,9 @@ struct PythonValueConversion {
594595
case PythonObjectType::Value: {
595596
// Extract the internal object and the type from the Value instance
596597
auto object = ele.attr("object");
597-
auto type = ele.attr("type");
598+
py::object type = ele.attr("type");
598599
std::shared_ptr<DuckDBPyType> internal_type;
599-
if (!py::try_cast<std::shared_ptr<DuckDBPyType>>(type, internal_type)) {
600+
if (!DuckDBPyType::TryConvert(type, internal_type)) {
600601
string actual_type = py::cast<std::string>(py::str((type).type()));
601602
throw InvalidInputException("The 'type' of a Value should be of type DuckDBPyType, not '%s'",
602603
actual_type);

src/duckdb_py/pyconnection.cpp

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,12 +1241,19 @@ std::unique_ptr<DuckDBPyRelation> DuckDBPyConnection::ReadCSV(const py::object &
12411241
child_list_t<Value> struct_fields;
12421242
py::dict dtype_dict = py::cast<py::dict>(dtype);
12431243
for (auto kv : dtype_dict) { // nanobind dict iteration yields std::pair<handle,handle> by value
1244-
std::shared_ptr<DuckDBPyType> sql_type;
1245-
if (!py::try_cast(kv.second, sql_type)) {
1246-
struct_fields.emplace_back(py::cast<std::string>(py::str(kv.first)),
1247-
Value(py::cast<std::string>(py::str(kv.second))));
1244+
auto key = py::cast<std::string>(py::str(kv.first));
1245+
auto value_obj = py::borrow<py::object>(kv.second);
1246+
if (py::isinstance<py::str>(value_obj)) {
1247+
// A type string -- pass through for DuckDB to parse.
1248+
struct_fields.emplace_back(key, Value(py::cast<std::string>(value_obj)));
12481249
} else {
1249-
struct_fields.emplace_back(py::cast<std::string>(py::str(kv.first)), Value(sql_type->ToString()));
1250+
// A DuckDBPyType instance, or a Python type object (int/str/...). nanobind's shared_ptr caster
1251+
// strips the implicit-convert flag, so build the DuckDBPyType via its registered constructor.
1252+
if (!py::isinstance<DuckDBPyType>(value_obj)) {
1253+
value_obj = py::type<DuckDBPyType>()(value_obj);
1254+
}
1255+
auto sql_type = py::cast<std::shared_ptr<DuckDBPyType>>(value_obj);
1256+
struct_fields.emplace_back(key, Value(sql_type->ToString()));
12501257
}
12511258
}
12521259
auto dtype_struct = Value::STRUCT(std::move(struct_fields));
@@ -1255,11 +1262,12 @@ std::unique_ptr<DuckDBPyRelation> DuckDBPyConnection::ReadCSV(const py::object &
12551262
vector<Value> list_values;
12561263
py::list dtype_list = py::cast<py::list>(dtype);
12571264
for (auto child : dtype_list) {
1265+
auto child_obj = py::borrow<py::object>(child);
12581266
std::shared_ptr<DuckDBPyType> sql_type;
1259-
if (!py::try_cast(child, sql_type)) {
1260-
list_values.push_back(Value(py::cast<std::string>(child)));
1261-
} else {
1267+
if (!py::isinstance<py::str>(child_obj) && DuckDBPyType::TryConvert(child_obj, sql_type)) {
12621268
list_values.push_back(sql_type->ToString());
1269+
} else {
1270+
list_values.push_back(Value(py::cast<std::string>(py::str(child_obj))));
12631271
}
12641272
}
12651273
bind_parameters["dtypes"] = Value::LIST(LogicalType::VARCHAR, std::move(list_values));

src/duckdb_py/pyconnection/type_creation.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ static child_list_t<LogicalType> GetChildList(const py::object &container) {
2525
idx_t i = 1;
2626
for (auto item : fields) {
2727
std::shared_ptr<DuckDBPyType> pytype;
28-
if (!py::try_cast<std::shared_ptr<DuckDBPyType>>(item, pytype)) {
28+
if (!DuckDBPyType::TryConvert(py::borrow<py::object>(item), pytype)) {
2929
string actual_type = py::cast<std::string>(py::str((item).type()));
3030
throw InvalidInputException("object has to be a list of DuckDBPyType's, not '%s'", actual_type);
3131
}
@@ -39,7 +39,7 @@ static child_list_t<LogicalType> GetChildList(const py::object &container) {
3939
auto type_p = item.second;
4040
auto name = Identifier(py::cast<std::string>(name_p));
4141
std::shared_ptr<DuckDBPyType> pytype;
42-
if (!py::try_cast<std::shared_ptr<DuckDBPyType>>(type_p, pytype)) {
42+
if (!DuckDBPyType::TryConvert(py::borrow<py::object>(type_p), pytype)) {
4343
string actual_type = py::cast<std::string>(py::str((type_p).type()));
4444
throw InvalidInputException("object has to be a list of DuckDBPyType's, not '%s'", actual_type);
4545
}

src/duckdb_py/python_udf.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ struct PythonUDFData {
483483
auto empty = py::module_::import_("inspect").attr("Signature").attr("empty");
484484
if (!py::none().is(return_annotation) && !empty.is(return_annotation)) {
485485
std::shared_ptr<DuckDBPyType> pytype;
486-
if (py::try_cast<std::shared_ptr<DuckDBPyType>>(return_annotation, pytype)) {
486+
if (DuckDBPyType::TryConvert(py::borrow<py::object>(return_annotation), pytype)) {
487487
return_type = pytype->Type();
488488
}
489489
}
@@ -493,7 +493,7 @@ struct PythonUDFData {
493493
for (auto item : params) {
494494
auto value = item.second;
495495
std::shared_ptr<DuckDBPyType> pytype;
496-
if (py::try_cast<std::shared_ptr<DuckDBPyType>>(value.attr("annotation"), pytype)) {
496+
if (DuckDBPyType::TryConvert(py::borrow<py::object>(value.attr("annotation")), pytype)) {
497497
parameters.push_back(pytype->Type());
498498
} else {
499499
std::string kind = py::cast<std::string>(py::str(value.attr("kind")));

src/duckdb_py/typing/pytype.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,22 @@ static LogicalType FromObject(const py::object &object) {
328328
}
329329
}
330330

331+
bool DuckDBPyType::TryConvert(const py::object &object, std::shared_ptr<DuckDBPyType> &result) {
332+
if (py::isinstance<DuckDBPyType>(object)) {
333+
result = py::cast<std::shared_ptr<DuckDBPyType>>(object);
334+
return true;
335+
}
336+
try {
337+
// Construct via the registered DuckDBPyType type (DuckDBPyType(object)); this hits the same factories
338+
// that drive the implicit conversion, which nanobind's shared_ptr caster otherwise bypasses.
339+
py::object converted = py::type<DuckDBPyType>()(object);
340+
result = py::cast<std::shared_ptr<DuckDBPyType>>(converted);
341+
return true;
342+
} catch (...) {
343+
return false;
344+
}
345+
}
346+
331347
void DuckDBPyType::Initialize(py::handle &m) {
332348
auto type_module = py::class_<DuckDBPyType>(m, "DuckDBPyType");
333349

0 commit comments

Comments
 (0)