Skip to content

Commit 289a9e3

Browse files
committed
fix pybind type casters for enums
1 parent 0911fa4 commit 289a9e3

9 files changed

Lines changed: 180 additions & 172 deletions

File tree

src/duckdb_py/include/duckdb_python/pybind11/conversions/exception_handling_enum.hpp

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,67 +4,60 @@
44
#include "duckdb/common/exception.hpp"
55
#include "duckdb/common/string_util.hpp"
66

7-
using duckdb::InvalidInputException;
8-
using duckdb::string;
9-
using duckdb::StringUtil;
10-
117
namespace duckdb {
128

139
enum class PythonExceptionHandling : uint8_t { FORWARD_ERROR, RETURN_NULL };
1410

15-
} // namespace duckdb
16-
17-
using duckdb::PythonExceptionHandling;
18-
19-
namespace py = pybind11;
20-
21-
static PythonExceptionHandling PythonExceptionHandlingFromString(const string &type) {
11+
inline PythonExceptionHandling PythonExceptionHandlingFromString(const string &type) {
2212
auto ltype = StringUtil::Lower(type);
2313
if (ltype.empty() || ltype == "default") {
2414
return PythonExceptionHandling::FORWARD_ERROR;
25-
} else if (ltype == "return_null") {
15+
}
16+
if (ltype == "return_null") {
2617
return PythonExceptionHandling::RETURN_NULL;
27-
} else {
28-
throw InvalidInputException("'%s' is not a recognized type for 'exception_handling'", type);
2918
}
19+
throw InvalidInputException("'%s' is not a recognized type for 'exception_handling'", type);
3020
}
3121

32-
static PythonExceptionHandling PythonExceptionHandlingFromInteger(int64_t value) {
22+
inline PythonExceptionHandling PythonExceptionHandlingFromInteger(int64_t value) {
3323
if (value == 0) {
3424
return PythonExceptionHandling::FORWARD_ERROR;
35-
} else if (value == 1) {
25+
}
26+
if (value == 1) {
3627
return PythonExceptionHandling::RETURN_NULL;
37-
} else {
38-
throw InvalidInputException("'%d' is not a recognized type for 'exception_handling'", value);
3928
}
29+
throw InvalidInputException("'%d' is not a recognized type for 'exception_handling'", value);
4030
}
4131

32+
} // namespace duckdb
33+
4234
namespace PYBIND11_NAMESPACE {
4335
namespace detail {
4436

37+
//! See python_udf_type_enum.hpp for the rationale (composition over inheritance, umbrella visibility).
4538
template <>
46-
struct type_caster<PythonExceptionHandling> : public type_caster_base<PythonExceptionHandling> {
47-
using base = type_caster_base<PythonExceptionHandling>;
48-
PythonExceptionHandling tmp;
39+
struct type_caster<duckdb::PythonExceptionHandling> {
40+
PYBIND11_TYPE_CASTER(duckdb::PythonExceptionHandling, const_name("PythonExceptionHandling"));
4941

50-
public:
5142
bool load(handle src, bool convert) {
52-
if (base::load(src, convert)) {
43+
if (isinstance<str>(src)) {
44+
value = duckdb::PythonExceptionHandlingFromString(src.cast<std::string>());
5345
return true;
54-
} else if (py::isinstance<py::str>(src)) {
55-
tmp = PythonExceptionHandlingFromString(py::str(src));
56-
value = &tmp;
57-
return true;
58-
} else if (py::isinstance<py::int_>(src)) {
59-
tmp = PythonExceptionHandlingFromInteger(src.cast<int64_t>());
60-
value = &tmp;
46+
}
47+
if (isinstance<int_>(src)) {
48+
value = duckdb::PythonExceptionHandlingFromInteger(src.cast<int64_t>());
6149
return true;
6250
}
63-
return false;
51+
type_caster_base<duckdb::PythonExceptionHandling> base;
52+
if (!base.load(src, convert)) {
53+
return false;
54+
}
55+
value = *static_cast<duckdb::PythonExceptionHandling *>(base);
56+
return true;
6457
}
6558

66-
static handle cast(PythonExceptionHandling src, return_value_policy policy, handle parent) {
67-
return base::cast(src, policy, parent);
59+
static handle cast(duckdb::PythonExceptionHandling src, return_value_policy policy, handle parent) {
60+
return type_caster_base<duckdb::PythonExceptionHandling>::cast(src, policy, parent);
6861
}
6962
};
7063

src/duckdb_py/include/duckdb_python/pybind11/conversions/explain_enum.hpp

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,60 @@
55
#include "duckdb/common/exception.hpp"
66
#include "duckdb/common/string_util.hpp"
77

8-
using duckdb::ExplainType;
9-
using duckdb::InvalidInputException;
10-
using duckdb::string;
11-
using duckdb::StringUtil;
8+
namespace duckdb {
129

13-
namespace py = pybind11;
14-
15-
static ExplainType ExplainTypeFromString(const string &type) {
10+
inline ExplainType ExplainTypeFromString(const string &type) {
1611
auto ltype = StringUtil::Lower(type);
1712
if (ltype.empty() || ltype == "standard") {
1813
return ExplainType::EXPLAIN_STANDARD;
19-
} else if (ltype == "analyze") {
14+
}
15+
if (ltype == "analyze") {
2016
return ExplainType::EXPLAIN_ANALYZE;
21-
} else {
22-
throw InvalidInputException("Unrecognized type for 'explain'");
2317
}
18+
throw InvalidInputException("Unrecognized type for 'explain'");
2419
}
2520

26-
static ExplainType ExplainTypeFromInteger(int64_t value) {
21+
inline ExplainType ExplainTypeFromInteger(int64_t value) {
2722
if (value == 0) {
2823
return ExplainType::EXPLAIN_STANDARD;
29-
} else if (value == 1) {
24+
}
25+
if (value == 1) {
3026
return ExplainType::EXPLAIN_ANALYZE;
31-
} else {
32-
throw InvalidInputException("Unrecognized type for 'explain'");
3327
}
28+
throw InvalidInputException("Unrecognized type for 'explain'");
3429
}
3530

36-
//! Resolve a Python explain-type argument (ExplainType enum, str, or int) to an ExplainType.
37-
//! NOTE: deliberately NOT a pybind type_caster. A custom caster inheriting type_caster_base shadows the
38-
//! registered py::enum_ inconsistently across translation units - it ends up accepting str/int XOR the enum
39-
//! instance, never both, depending on which TU sees the specialization. Explicit dispatch at the call site is
40-
//! robust regardless of include order.
41-
static ExplainType ExplainTypeFromPython(const py::object &obj) {
42-
if (py::isinstance<py::str>(obj)) {
43-
return ExplainTypeFromString(py::str(obj));
31+
} // namespace duckdb
32+
33+
namespace PYBIND11_NAMESPACE {
34+
namespace detail {
35+
36+
//! See python_udf_type_enum.hpp for the rationale (composition over inheritance, umbrella visibility).
37+
template <>
38+
struct type_caster<duckdb::ExplainType> {
39+
PYBIND11_TYPE_CASTER(duckdb::ExplainType, const_name("ExplainType"));
40+
41+
bool load(handle src, bool convert) {
42+
if (isinstance<str>(src)) {
43+
value = duckdb::ExplainTypeFromString(src.cast<std::string>());
44+
return true;
45+
}
46+
if (isinstance<int_>(src)) {
47+
value = duckdb::ExplainTypeFromInteger(src.cast<int64_t>());
48+
return true;
49+
}
50+
type_caster_base<duckdb::ExplainType> base;
51+
if (!base.load(src, convert)) {
52+
return false;
53+
}
54+
value = *static_cast<duckdb::ExplainType *>(base);
55+
return true;
4456
}
45-
if (py::isinstance<py::int_>(obj)) {
46-
return ExplainTypeFromInteger(obj.cast<int64_t>());
57+
58+
static handle cast(duckdb::ExplainType src, return_value_policy policy, handle parent) {
59+
return type_caster_base<duckdb::ExplainType>::cast(src, policy, parent);
4760
}
48-
// Fall through to the registered py::enum_ caster (handles an actual ExplainType, throws otherwise).
49-
return obj.cast<ExplainType>();
50-
}
61+
};
62+
63+
} // namespace detail
64+
} // namespace PYBIND11_NAMESPACE

src/duckdb_py/include/duckdb_python/pybind11/conversions/null_handling_enum.hpp

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,60 +5,60 @@
55
#include "duckdb/common/exception.hpp"
66
#include "duckdb/common/string_util.hpp"
77

8-
using duckdb::FunctionNullHandling;
9-
using duckdb::InvalidInputException;
10-
using duckdb::string;
11-
using duckdb::StringUtil;
8+
namespace duckdb {
129

13-
namespace py = pybind11;
14-
15-
static FunctionNullHandling FunctionNullHandlingFromString(const string &type) {
10+
inline FunctionNullHandling FunctionNullHandlingFromString(const string &type) {
1611
auto ltype = StringUtil::Lower(type);
1712
if (ltype.empty() || ltype == "default") {
1813
return FunctionNullHandling::DEFAULT_NULL_HANDLING;
19-
} else if (ltype == "special") {
14+
}
15+
if (ltype == "special") {
2016
return FunctionNullHandling::SPECIAL_HANDLING;
21-
} else {
22-
throw InvalidInputException("'%s' is not a recognized type for 'null_handling'", type);
2317
}
18+
throw InvalidInputException("'%s' is not a recognized type for 'null_handling'", type);
2419
}
2520

26-
static FunctionNullHandling FunctionNullHandlingFromInteger(int64_t value) {
21+
inline FunctionNullHandling FunctionNullHandlingFromInteger(int64_t value) {
2722
if (value == 0) {
2823
return FunctionNullHandling::DEFAULT_NULL_HANDLING;
29-
} else if (value == 1) {
24+
}
25+
if (value == 1) {
3026
return FunctionNullHandling::SPECIAL_HANDLING;
31-
} else {
32-
throw InvalidInputException("'%d' is not a recognized type for 'null_handling'", value);
3327
}
28+
throw InvalidInputException("'%d' is not a recognized type for 'null_handling'", value);
3429
}
3530

31+
} // namespace duckdb
32+
3633
namespace PYBIND11_NAMESPACE {
3734
namespace detail {
3835

36+
//! See python_udf_type_enum.hpp for why this owns its value and delegates the enum case to a local base
37+
//! caster instead of inheriting type_caster_base. Must stay visible in every TU (included from
38+
//! pybind_wrapper.hpp).
3939
template <>
40-
struct type_caster<FunctionNullHandling> : public type_caster_base<FunctionNullHandling> {
41-
using base = type_caster_base<FunctionNullHandling>;
42-
FunctionNullHandling tmp;
40+
struct type_caster<duckdb::FunctionNullHandling> {
41+
PYBIND11_TYPE_CASTER(duckdb::FunctionNullHandling, const_name("FunctionNullHandling"));
4342

44-
public:
4543
bool load(handle src, bool convert) {
46-
if (base::load(src, convert)) {
44+
if (isinstance<str>(src)) {
45+
value = duckdb::FunctionNullHandlingFromString(src.cast<std::string>());
4746
return true;
48-
} else if (py::isinstance<py::str>(src)) {
49-
tmp = FunctionNullHandlingFromString(py::str(src));
50-
value = &tmp;
51-
return true;
52-
} else if (py::isinstance<py::int_>(src)) {
53-
tmp = FunctionNullHandlingFromInteger(src.cast<int64_t>());
54-
value = &tmp;
47+
}
48+
if (isinstance<int_>(src)) {
49+
value = duckdb::FunctionNullHandlingFromInteger(src.cast<int64_t>());
5550
return true;
5651
}
57-
return false;
52+
type_caster_base<duckdb::FunctionNullHandling> base;
53+
if (!base.load(src, convert)) {
54+
return false;
55+
}
56+
value = *static_cast<duckdb::FunctionNullHandling *>(base);
57+
return true;
5858
}
5959

60-
static handle cast(FunctionNullHandling src, return_value_policy policy, handle parent) {
61-
return base::cast(src, policy, parent);
60+
static handle cast(duckdb::FunctionNullHandling src, return_value_policy policy, handle parent) {
61+
return type_caster_base<duckdb::FunctionNullHandling>::cast(src, policy, parent);
6262
}
6363
};
6464

src/duckdb_py/include/duckdb_python/pybind11/conversions/python_csv_line_terminator_enum.hpp

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44
#include "duckdb/common/exception.hpp"
55
#include "duckdb/common/string_util.hpp"
66

7-
using duckdb::InvalidInputException;
8-
using duckdb::string;
9-
using duckdb::StringUtil;
10-
117
namespace duckdb {
128

139
struct PythonCSVLineTerminator {
@@ -45,32 +41,30 @@ struct PythonCSVLineTerminator {
4541

4642
} // namespace duckdb
4743

48-
using duckdb::PythonCSVLineTerminator;
49-
50-
namespace py = pybind11;
51-
5244
namespace PYBIND11_NAMESPACE {
5345
namespace detail {
5446

47+
//! See python_udf_type_enum.hpp for the rationale (composition over inheritance, umbrella visibility).
48+
//! Only a string or the enum itself are accepted (no integer form).
5549
template <>
56-
struct type_caster<PythonCSVLineTerminator::Type> : public type_caster_base<PythonCSVLineTerminator::Type> {
57-
using base = type_caster_base<PythonCSVLineTerminator::Type>;
58-
PythonCSVLineTerminator::Type tmp;
50+
struct type_caster<duckdb::PythonCSVLineTerminator::Type> {
51+
PYBIND11_TYPE_CASTER(duckdb::PythonCSVLineTerminator::Type, const_name("CSVLineTerminator"));
5952

60-
public:
6153
bool load(handle src, bool convert) {
62-
if (base::load(src, convert)) {
63-
return true;
64-
} else if (py::isinstance<py::str>(src)) {
65-
tmp = duckdb::PythonCSVLineTerminator::FromString(py::str(src));
66-
value = &tmp;
54+
if (isinstance<str>(src)) {
55+
value = duckdb::PythonCSVLineTerminator::FromString(src.cast<std::string>());
6756
return true;
6857
}
69-
return false;
58+
type_caster_base<duckdb::PythonCSVLineTerminator::Type> base;
59+
if (!base.load(src, convert)) {
60+
return false;
61+
}
62+
value = *static_cast<duckdb::PythonCSVLineTerminator::Type *>(base);
63+
return true;
7064
}
7165

72-
static handle cast(PythonCSVLineTerminator::Type src, return_value_policy policy, handle parent) {
73-
return base::cast(src, policy, parent);
66+
static handle cast(duckdb::PythonCSVLineTerminator::Type src, return_value_policy policy, handle parent) {
67+
return type_caster_base<duckdb::PythonCSVLineTerminator::Type>::cast(src, policy, parent);
7468
}
7569
};
7670

0 commit comments

Comments
 (0)