feat: FromPyFromStr and ToStrToPy macros YAY

This commit is contained in:
J / Jacob Babich
2026-07-09 01:35:49 -04:00
parent c637ad2d76
commit 9a9cefee37
25 changed files with 343 additions and 394 deletions

View File

@@ -4,8 +4,11 @@ version = "0.1.0"
edition = "2021"
license = { workspace = true }
[features]
macros = ["dep:python-utils-macros"]
[dependencies]
derive_more = { workspace = true }
pyo3 = { workspace = true }
python-utils-macros = { path = "../python-utils-macros" }
python-utils-macros = { optional = true, path = "../python-utils-macros" }
snafu = { workspace = true }

View File

@@ -0,0 +1,73 @@
use std::{
fmt::{Debug, Display},
str::FromStr,
sync::Arc,
};
use pyo3::{
exceptions::{PyException, PyValueError},
Borrowed, FromPyObject, PyAny, PyErr,
};
#[derive(Debug, Clone, derive_more::FromStr)]
pub struct FromPyObjectViaParse<T>(pub T);
#[derive(Debug, Clone)]
pub enum ExtractPyObjectViaParseError<ParseError> {
/// 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<ParseError: Display> Display for ExtractPyObjectViaParseError<ParseError> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ExtractPyObjectViaParseError::ExtractStringError { source } => write!(f, "{}", source),
ExtractPyObjectViaParseError::ParseError { source } => write!(f, "{}", source),
}
}
}
impl<ParseError> std::error::Error for ExtractPyObjectViaParseError<ParseError> where
ExtractPyObjectViaParseError<ParseError>: Debug + Display
{
}
impl<ParseError> From<ExtractPyObjectViaParseError<ParseError>> for PyErr
where
ExtractPyObjectViaParseError<ParseError>: Display,
{
fn from(error: ExtractPyObjectViaParseError<ParseError>) -> 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,
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)
.map_err(|e| ExtractPyObjectViaParseError::ExtractStringError { source: e })?;
let t = s
.parse()
.map_err(|e| ExtractPyObjectViaParseError::ParseError { source: e })?;
Ok(FromPyObjectViaParse(t))
}
}

View File

@@ -0,0 +1,20 @@
use std::{convert::Infallible, fmt::Display};
use pyo3::{types::PyString, Bound, IntoPyObject, Python};
#[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)
}
}

View File

@@ -1,15 +1,18 @@
use std::{convert::Infallible, fmt::Display, str::FromStr, sync::Arc};
use pyo3::{
exceptions::{PyException, PyTypeError, PyValueError},
exceptions::{PyException, PyTypeError},
prelude::*,
types::PyString,
};
use snafu::{ResultExt, Snafu};
pub use python_utils_macros::PyFromStr;
#[cfg(feature = "macros")]
pub use python_utils_macros::{FromPyFromStr, ToStrToPy};
pub mod from_pyobject_via_parse;
pub mod into_pyobject_via_display;
pub mod none;
pub use from_pyobject_via_parse::{ExtractPyObjectViaParseError, FromPyObjectViaParse};
pub use into_pyobject_via_display::IntoPyObjectViaDisplay;
pub use none::IsNone;
/// Create a GIL-independent reference
@@ -83,70 +86,3 @@ pub fn validate_type_by_name(
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))
}
}