Path: blob/main/crates/polars-utils/src/python_function.rs
6939 views
use pyo3::prelude::*;1#[cfg(feature = "serde")]2pub use serde_wrap::{3PYTHON3_VERSION, PySerializeWrap, SERDE_MAGIC_BYTE_MARK as PYTHON_SERDE_MAGIC_BYTE_MARK,4TrySerializeToBytes,5};67/// Wrapper around PyObject from pyo3 with additional trait impls.8#[derive(Debug)]9pub struct PythonObject(pub PyObject);10// Note: We have this because the struct itself used to be called `PythonFunction`, so it's11// referred to as such from a lot of places.12pub type PythonFunction = PythonObject;1314impl std::ops::Deref for PythonObject {15type Target = PyObject;1617fn deref(&self) -> &Self::Target {18&self.019}20}2122impl std::ops::DerefMut for PythonObject {23fn deref_mut(&mut self) -> &mut Self::Target {24&mut self.025}26}2728impl Clone for PythonObject {29fn clone(&self) -> Self {30Python::with_gil(|py| Self(self.0.clone_ref(py)))31}32}3334impl From<PyObject> for PythonObject {35fn from(value: PyObject) -> Self {36Self(value)37}38}3940impl<'py> pyo3::conversion::IntoPyObject<'py> for PythonObject {41type Target = PyAny;42type Output = Bound<'py, Self::Target>;43type Error = PyErr;4445fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {46Ok(self.0.into_bound(py))47}48}4950impl<'py> pyo3::conversion::IntoPyObject<'py> for &PythonObject {51type Target = PyAny;52type Output = Bound<'py, Self::Target>;53type Error = PyErr;5455fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {56Ok(self.0.bind(py).clone())57}58}5960impl Eq for PythonObject {}6162impl PartialEq for PythonObject {63fn eq(&self, other: &Self) -> bool {64Python::with_gil(|py| {65let eq = self.0.getattr(py, "__eq__").unwrap();66eq.call1(py, (other.0.clone_ref(py),))67.unwrap()68.extract::<bool>(py)69// equality can be not implemented, so default to false70.unwrap_or(false)71})72}73}7475#[cfg(feature = "dsl-schema")]76impl schemars::JsonSchema for PythonObject {77fn schema_name() -> String {78"PythonObject".to_owned()79}8081fn schema_id() -> std::borrow::Cow<'static, str> {82std::borrow::Cow::Borrowed(concat!(module_path!(), "::", "PythonObject"))83}8485fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {86Vec::<u8>::json_schema(generator)87}88}8990#[cfg(feature = "serde")]91mod _serde_impls {92use super::{PySerializeWrap, PythonObject, TrySerializeToBytes};93use crate::pl_serialize::deserialize_map_bytes;9495impl PythonObject {96pub fn serialize_with_pyversion<T, S>(97value: &T,98serializer: S,99) -> std::result::Result<S::Ok, S::Error>100where101T: AsRef<PythonObject>,102S: serde::ser::Serializer,103{104use serde::Serialize;105PySerializeWrap(value.as_ref()).serialize(serializer)106}107108pub fn deserialize_with_pyversion<'de, T, D>(d: D) -> Result<T, D::Error>109where110T: From<PythonObject>,111D: serde::de::Deserializer<'de>,112{113use serde::Deserialize;114let v: PySerializeWrap<PythonObject> = PySerializeWrap::deserialize(d)?;115116Ok(v.0.into())117}118}119120impl TrySerializeToBytes for PythonObject {121fn try_serialize_to_bytes(&self) -> polars_error::PolarsResult<Vec<u8>> {122let mut buf = Vec::new();123crate::pl_serialize::python_object_serialize(&self.0, &mut buf)?;124Ok(buf)125}126127fn try_deserialize_bytes(bytes: &[u8]) -> polars_error::PolarsResult<Self> {128crate::pl_serialize::python_object_deserialize(bytes).map(PythonObject)129}130}131132impl serde::Serialize for PythonObject {133fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>134where135S: serde::Serializer,136{137use serde::ser::Error;138let bytes = self139.try_serialize_to_bytes()140.map_err(|e| S::Error::custom(e.to_string()))?;141142Vec::<u8>::serialize(&bytes, serializer)143}144}145146impl<'a> serde::Deserialize<'a> for PythonObject {147fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>148where149D: serde::Deserializer<'a>,150{151use serde::de::Error;152deserialize_map_bytes(deserializer, |bytes| {153Self::try_deserialize_bytes(&bytes).map_err(|e| D::Error::custom(e.to_string()))154})?155}156}157}158159#[cfg(feature = "serde")]160mod serde_wrap {161use std::sync::LazyLock;162163use polars_error::PolarsResult;164165use crate::pl_serialize::deserialize_map_bytes;166167pub const SERDE_MAGIC_BYTE_MARK: &[u8] = "PLPYFN".as_bytes();168/// [minor, micro]169pub static PYTHON3_VERSION: LazyLock<[u8; 2]> = LazyLock::new(super::get_python3_version);170171/// Serializes a Python object without additional system metadata. This is intended to be used172/// together with `PySerializeWrap`, which attaches e.g. Python version metadata.173pub trait TrySerializeToBytes: Sized {174fn try_serialize_to_bytes(&self) -> PolarsResult<Vec<u8>>;175fn try_deserialize_bytes(bytes: &[u8]) -> PolarsResult<Self>;176}177178/// Serialization wrapper for T: TrySerializeToBytes that attaches Python179/// version metadata.180pub struct PySerializeWrap<T>(pub T);181182impl<T: TrySerializeToBytes> serde::Serialize for PySerializeWrap<&T> {183fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>184where185S: serde::Serializer,186{187use serde::ser::Error;188let dumped = self189.0190.try_serialize_to_bytes()191.map_err(|e| S::Error::custom(e.to_string()))?;192193serializer.serialize_bytes(dumped.as_slice())194}195}196197impl<'a, T: TrySerializeToBytes> serde::Deserialize<'a> for PySerializeWrap<T> {198fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>199where200D: serde::Deserializer<'a>,201{202use serde::de::Error;203204deserialize_map_bytes(deserializer, |bytes| {205T::try_deserialize_bytes(bytes.as_ref())206.map(Self)207.map_err(|e| D::Error::custom(e.to_string()))208})?209}210}211}212213/// Get the [minor, micro] Python3 version from the `sys` module.214fn get_python3_version() -> [u8; 2] {215Python::with_gil(|py| {216let version_info = PyModule::import(py, "sys")217.unwrap()218.getattr("version_info")219.unwrap();220221[222version_info.getattr("minor").unwrap().extract().unwrap(),223version_info.getattr("micro").unwrap().extract().unwrap(),224]225})226}227228229