diff --git a/packages/cubejs-backend-native/src/cross/clrepr_python.rs b/packages/cubejs-backend-native/src/cross/clrepr_python.rs index 9712206845cc9..9ac669cedf62d 100644 --- a/packages/cubejs-backend-native/src/cross/clrepr_python.rs +++ b/packages/cubejs-backend-native/src/cross/clrepr_python.rs @@ -1,9 +1,10 @@ use crate::cross::clrepr::CLReprObject; use crate::cross::{CLRepr, CLReprObjectKind, StringType}; -use pyo3::exceptions::{PyNotImplementedError, PyTypeError}; +use crate::python::utils::PyAnyHelpers; +use pyo3::exceptions::{PyException, PyNotImplementedError, PyTypeError}; use pyo3::types::{ - PyBool, PyComplex, PyDate, PyDict, PyFloat, PyFrame, PyFunction, PyInt, PyList, PySequence, - PySet, PyString, PyTraceback, PyTuple, + PyBool, PyComplex, PyDate, PyDateTime, PyDelta, PyDict, PyFloat, PyFrame, PyFrozenSet, + PyFunction, PyInt, PyList, PySequence, PySet, PyString, PyTraceback, PyTuple, }; use pyo3::{Py, PyAny, PyErr, PyObject, Python, ToPyObject}; @@ -12,7 +13,7 @@ pub enum PythonRef { PyObject(PyObject), PyFunction(Py), /// Special type to transfer functions through JavaScript - /// In JS it's an external object. It's not the same as Function. + /// In JS it's an external object. PyExternalFunction(Py), } @@ -87,10 +88,28 @@ impl CLRepr { return Err(PyErr::new::( "Unable to represent PyComplex type as CLR from Python".to_string(), )); + } else if v.get_type().is_subclass_of::()? { + return Err(PyErr::new::( + "Unable to represent PyDateTime type as CLR from Python".to_string(), + )); } else if v.get_type().is_subclass_of::()? { return Err(PyErr::new::( "Unable to represent PyDate type as CLR from Python".to_string(), )); + } else if v.get_type().is_subclass_of::()? { + let set = v.downcast::()?; + + return Err(PyErr::new::(format!( + "Unable to represent PyFrozenSet type as CLR from Python, value: {:?}", + set + ))); + } else if v.get_type().is_subclass_of::()? { + let exception = v.downcast::()?; + + return Err(PyErr::new::(format!( + "Unable to represent PyException type as CLR from Python, value: {:?}", + exception + ))); } else if v.get_type().is_subclass_of::()? { let frame = v.downcast::()?; @@ -105,17 +124,22 @@ impl CLRepr { "Unable to represent PyTraceback type as CLR from Python, value: {:?}", trb ))); - } else { - let is_sequence = unsafe { pyo3::ffi::PySequence_Check(v.as_ptr()) == 1 }; - if is_sequence { - let seq = v.downcast::()?; - - return Err(PyErr::new::(format!( - "Unable to represent PySequence type as CLR from Python, value: {:?}", - seq - ))); - } + } else if v.get_type().is_subclass_of::()? { + let delta = v.downcast::()?; + + return Err(PyErr::new::(format!( + "Unable to represent PyDelta type as CLR from Python, value: {:?}", + delta + ))); + } else if v.is_sequence()? { + let seq = v.downcast::()?; + return Err(PyErr::new::(format!( + "Unable to represent PySequence type as CLR from Python, value: {:?}", + seq + ))); + } else { + // Fallback to PyObject, it will lead to throw error in the JS side Self::PythonRef(PythonRef::PyObject(v.into())) }) } diff --git a/packages/cubejs-backend-native/src/python/runtime.rs b/packages/cubejs-backend-native/src/python/runtime.rs index ef62860f4d2d2..00f0ea55fdb24 100644 --- a/packages/cubejs-backend-native/src/python/runtime.rs +++ b/packages/cubejs-backend-native/src/python/runtime.rs @@ -1,5 +1,6 @@ use crate::cross::CLRepr; use crate::python::neon_py::*; +use crate::python::utils::PyAnyHelpers; use crate::tokio_runtime_node; use cubesql::CubeError; use log::{error, trace}; @@ -143,8 +144,7 @@ impl PyRuntime { let py_args = PyTuple::new(py, prep_tuple); let call_res = fun.call(py, py_args, py_kwargs)?; - let is_coroutine = unsafe { pyo3::ffi::PyCoro_CheckExact(call_res.as_ptr()) == 1 }; - if is_coroutine { + if call_res.is_coroutine()? { let fut = pyo3_asyncio::tokio::into_future(call_res.as_ref(py))?; Ok(PyScheduledFunResult::Poll(Box::pin(fut))) } else { diff --git a/packages/cubejs-backend-native/src/python/utils.rs b/packages/cubejs-backend-native/src/python/utils.rs index ea740a0422715..83b9c1f0996a9 100644 --- a/packages/cubejs-backend-native/src/python/utils.rs +++ b/packages/cubejs-backend-native/src/python/utils.rs @@ -1,7 +1,9 @@ use crate::cross::*; -use pyo3::exceptions::PyNotImplementedError; +use pyo3::exceptions::{PyNotImplementedError, PySystemError}; use pyo3::prelude::*; use pyo3::types::{PyFunction, PyString, PyTuple}; +use pyo3::{ffi, AsPyPointer}; +use std::ffi::c_int; pub fn python_fn_call_sync(py_fun: &Py, arguments: Vec) -> PyResult { Python::with_gil(|py| { @@ -15,8 +17,7 @@ pub fn python_fn_call_sync(py_fun: &Py, arguments: Vec) -> P let call_res = py_fun.call1(py, tuple)?; - let is_coroutine = unsafe { pyo3::ffi::PyCoro_CheckExact(call_res.as_ptr()) == 1 }; - if is_coroutine { + if call_res.is_coroutine()? { Err(PyErr::new::( "Calling function with async response is not supported", )) @@ -38,8 +39,7 @@ pub fn python_obj_call_sync(py_fun: &PyObject, arguments: Vec) -> PyResu let call_res = py_fun.call1(py, tuple)?; - let is_coroutine = unsafe { pyo3::ffi::PyCoro_CheckExact(call_res.as_ptr()) == 1 }; - if is_coroutine { + if call_res.is_coroutine()? { Err(PyErr::new::( "Calling object with async response is not supported", )) @@ -68,8 +68,7 @@ where let call_res = py_fun.call_method1(py, name, tuple)?; - let is_coroutine = unsafe { pyo3::ffi::PyCoro_CheckExact(call_res.as_ptr()) == 1 }; - if is_coroutine { + if call_res.is_coroutine()? { Err(PyErr::new::( "Calling object method with async response is not supported", )) @@ -78,3 +77,52 @@ where } }) } + +pub trait PyAnyHelpers { + fn is_sequence(&self) -> PyResult; + + fn is_coroutine(&self) -> PyResult; +} + +pub(crate) fn internal_error_on_minusone(result: c_int) -> PyResult<()> { + if result != -1 { + Ok(()) + } else { + Err(PyErr::new::( + "Error on call via ffi, result is -1", + )) + } +} + +impl PyAnyHelpers for T +where + T: AsPyPointer, +{ + fn is_sequence(&self) -> PyResult { + let ptr = self.as_ptr(); + if ptr.is_null() { + return Err(PyErr::new::( + "Unable to verify that object is sequence, because ptr is null", + )); + } + + let v = unsafe { ffi::PySequence_Check(ptr) }; + internal_error_on_minusone(v)?; + + Ok(v != 0) + } + + fn is_coroutine(&self) -> PyResult { + let ptr = self.as_ptr(); + if ptr.is_null() { + return Err(PyErr::new::( + "Unable to verify that object is coroutine, because ptr is null", + )); + } + + let v = unsafe { ffi::PyCoro_CheckExact(ptr) }; + internal_error_on_minusone(v)?; + + Ok(v != 0) + } +}