chore+feat(python-utils): update to pyo3 0.27, introduce helpers like FromPyObjectViaParse and IntoPyObjectViaDisplay before I make macros that can complement or replace them
This commit is contained in:
@@ -5,4 +5,6 @@ edition = "2021"
|
|||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
derive_more = { workspace = true }
|
||||||
pyo3 = { workspace = true }
|
pyo3 = { workspace = true }
|
||||||
|
snafu = { workspace = true }
|
||||||
|
|||||||
@@ -1,45 +1,150 @@
|
|||||||
use std::convert::Infallible;
|
use std::{convert::Infallible, fmt::Display, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
use pyo3::{exceptions::PyTypeError, prelude::*, types::PyNone};
|
use pyo3::{
|
||||||
|
exceptions::{PyException, PyTypeError, PyValueError},
|
||||||
|
prelude::*,
|
||||||
|
types::PyString,
|
||||||
|
};
|
||||||
|
use snafu::{ResultExt, Snafu};
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
pub mod none;
|
||||||
pub struct IsNone;
|
pub use none::IsNone;
|
||||||
|
|
||||||
impl<'py> FromPyObject<'py> for IsNone {
|
|
||||||
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
|
|
||||||
ob.downcast::<PyNone>()?;
|
|
||||||
Ok(IsNone)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'py> IntoPyObject<'py> for IsNone {
|
|
||||||
type Target = PyNone;
|
|
||||||
|
|
||||||
type Output = Borrowed<'py, 'py, Self::Target>;
|
|
||||||
|
|
||||||
type Error = Infallible;
|
|
||||||
|
|
||||||
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
|
|
||||||
Ok(PyNone::get(py))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a GIL-independent reference
|
/// Create a GIL-independent reference
|
||||||
pub fn detach<T>(bound: &Bound<T>) -> Py<T> {
|
pub fn detach<T>(borrowed: Borrowed<'_, '_, T>) -> Py<T> {
|
||||||
let py = bound.py();
|
let py = borrowed.py();
|
||||||
bound.as_unbound().clone_ref(py)
|
borrowed.as_unbound().clone_ref(py)
|
||||||
|
}
|
||||||
|
/// Create a GIL-independent reference
|
||||||
|
pub fn detach_bound<T>(bound: &Bound<T>) -> Py<T> {
|
||||||
|
detach(bound.as_borrowed())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_type_by_name(bound: &Bound<PyAny>, expected_type_name: &str) -> PyResult<()> {
|
#[derive(Debug, Snafu)]
|
||||||
|
pub enum TypeByNameValidationError {
|
||||||
|
/// error getting the type name of this object
|
||||||
|
GetTypeNameError { source: PyErr },
|
||||||
|
/// error extracting the (successfully retrieved) type name as an [`&str`]
|
||||||
|
ExtractTypeNameError { source: PyErr },
|
||||||
|
|
||||||
|
/// error getting the fully qualified type name of this object
|
||||||
|
GetFullyQualifiedTypeNameError { source: PyErr },
|
||||||
|
/// error extracting the (successfully retrieved) fully qualified type name as an [`&str`]
|
||||||
|
ExtractFullyQualifiedTypeNameError { source: PyErr },
|
||||||
|
|
||||||
|
/// expected an instance of {expected} but got an instance of {actual}
|
||||||
|
UnexpectedType { expected: String, actual: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TypeByNameValidationError> for PyErr {
|
||||||
|
fn from(error: TypeByNameValidationError) -> Self {
|
||||||
|
match &error {
|
||||||
|
TypeByNameValidationError::GetTypeNameError { .. } => {
|
||||||
|
PyException::new_err(error.to_string())
|
||||||
|
}
|
||||||
|
TypeByNameValidationError::ExtractTypeNameError { .. } => {
|
||||||
|
PyException::new_err(error.to_string())
|
||||||
|
}
|
||||||
|
TypeByNameValidationError::GetFullyQualifiedTypeNameError { .. } => {
|
||||||
|
PyException::new_err(error.to_string())
|
||||||
|
}
|
||||||
|
TypeByNameValidationError::ExtractFullyQualifiedTypeNameError { .. } => {
|
||||||
|
PyException::new_err(error.to_string())
|
||||||
|
}
|
||||||
|
TypeByNameValidationError::UnexpectedType { .. } => {
|
||||||
|
PyTypeError::new_err(error.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_type_by_name(
|
||||||
|
bound: &Bound<PyAny>,
|
||||||
|
expected_type_name: &str,
|
||||||
|
) -> Result<(), TypeByNameValidationError> {
|
||||||
let py_type = bound.get_type();
|
let py_type = bound.get_type();
|
||||||
let type_name = py_type.name()?;
|
let type_name = py_type.name().context(GetTypeNameSnafu)?;
|
||||||
let type_name = type_name.to_str()?;
|
let type_name = type_name.to_str().context(ExtractTypeNameSnafu)?;
|
||||||
|
|
||||||
if type_name != expected_type_name {
|
if type_name != expected_type_name {
|
||||||
let fully_qualified_type_name = py_type.fully_qualified_name()?;
|
let fully_qualified_type_name = py_type
|
||||||
let fully_qualified_type_name = fully_qualified_type_name.to_str()?;
|
.fully_qualified_name()
|
||||||
return Err(PyTypeError::new_err(format!("expected an instance of {expected_type_name} but got an instance of {fully_qualified_type_name}")));
|
.context(GetFullyQualifiedTypeNameSnafu)?;
|
||||||
|
let fully_qualified_type_name = fully_qualified_type_name
|
||||||
|
.to_str()
|
||||||
|
.context(ExtractFullyQualifiedTypeNameSnafu)?;
|
||||||
|
return Err(TypeByNameValidationError::UnexpectedType {
|
||||||
|
expected: expected_type_name.to_owned(),
|
||||||
|
actual: fully_qualified_type_name.to_owned(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, derive_more::Display)]
|
||||||
|
pub struct IntoPyObjectViaDisplay<T>(pub T);
|
||||||
|
|
||||||
|
impl<'py, T> IntoPyObject<'py> for IntoPyObjectViaDisplay<T>
|
||||||
|
where
|
||||||
|
T: Display,
|
||||||
|
{
|
||||||
|
type Target = PyString;
|
||||||
|
type Output = Bound<'py, Self::Target>;
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
|
||||||
|
let s = self.to_string();
|
||||||
|
s.into_pyobject(py)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, derive_more::FromStr)]
|
||||||
|
pub struct FromPyObjectViaParse<T>(pub T);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Snafu)]
|
||||||
|
pub enum ExtractPyObjectViaParseError<ParseError>
|
||||||
|
where
|
||||||
|
ParseError: 'static + snafu::Error,
|
||||||
|
{
|
||||||
|
/// couldn't extract the object as a string
|
||||||
|
ExtractStringError { source: Arc<PyErr> },
|
||||||
|
|
||||||
|
/// couldn't parse the string as an instance of this Rust type
|
||||||
|
ParseError { source: ParseError },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> From<ExtractPyObjectViaParseError<E>> for PyErr
|
||||||
|
where
|
||||||
|
E: 'static + snafu::Error,
|
||||||
|
{
|
||||||
|
fn from(error: ExtractPyObjectViaParseError<E>) -> Self {
|
||||||
|
match &error {
|
||||||
|
ExtractPyObjectViaParseError::ExtractStringError { .. } => {
|
||||||
|
PyException::new_err(error.to_string())
|
||||||
|
}
|
||||||
|
ExtractPyObjectViaParseError::ParseError { .. } => {
|
||||||
|
PyValueError::new_err(error.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'py, T> FromPyObject<'a, 'py> for FromPyObjectViaParse<T>
|
||||||
|
where
|
||||||
|
T: FromStr,
|
||||||
|
<T as FromStr>::Err: 'static + snafu::Error,
|
||||||
|
PyErr: From<ExtractPyObjectViaParseError<<T as FromStr>::Err>>,
|
||||||
|
{
|
||||||
|
type Error = ExtractPyObjectViaParseError<<T as FromStr>::Err>;
|
||||||
|
|
||||||
|
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
|
||||||
|
let s = obj
|
||||||
|
.extract::<&str>()
|
||||||
|
.map_err(Arc::new)
|
||||||
|
.context(ExtractStringSnafu)?;
|
||||||
|
let t = T::from_str(s).context(ParseSnafu)?;
|
||||||
|
|
||||||
|
Ok(FromPyObjectViaParse(t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
27
python-utils/src/none.rs
Normal file
27
python-utils/src/none.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
use std::convert::Infallible;
|
||||||
|
|
||||||
|
use pyo3::{types::PyNone, Borrowed, FromPyObject, IntoPyObject, PyAny, PyErr, Python};
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct IsNone;
|
||||||
|
|
||||||
|
impl<'a, 'py> FromPyObject<'a, 'py> for IsNone {
|
||||||
|
type Error = PyErr;
|
||||||
|
|
||||||
|
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
|
||||||
|
ob.cast::<PyNone>()?;
|
||||||
|
Ok(IsNone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'py> IntoPyObject<'py> for IsNone {
|
||||||
|
type Target = PyNone;
|
||||||
|
|
||||||
|
type Output = Borrowed<'py, 'py, Self::Target>;
|
||||||
|
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
|
||||||
|
Ok(PyNone::get(py))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user