feat: FromPyFromStr and ToStrToPy macros YAY
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
|
.history
|
||||||
/target
|
/target
|
||||||
|
|||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1745,6 +1745,8 @@ name = "python-utils-macros"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"python-utils-macros-impl",
|
"python-utils-macros-impl",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -26,11 +26,14 @@ derive_more = "2.1.0"
|
|||||||
ext-trait = "2.0.1"
|
ext-trait = "2.0.1"
|
||||||
mitsein = "0.8"
|
mitsein = "0.8"
|
||||||
palette = "0.7"
|
palette = "0.7"
|
||||||
|
proc-macro2 = "1.0.106"
|
||||||
pyo3 = "0.27"
|
pyo3 = "0.27"
|
||||||
pyo3-async-runtimes = "0.27"
|
pyo3-async-runtimes = "0.27"
|
||||||
|
quote = "1.0.46"
|
||||||
serde = "1.0.228"
|
serde = "1.0.228"
|
||||||
snafu = "0.8.9"
|
snafu = "0.8.9"
|
||||||
strum = "0.27.2"
|
strum = "0.27.2"
|
||||||
|
syn = "2.0.118"
|
||||||
tokio = "1.48.0"
|
tokio = "1.48.0"
|
||||||
tracing = "0.1.43"
|
tracing = "0.1.43"
|
||||||
typed-builder = "0.22"
|
typed-builder = "0.22"
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ async fn real_main(
|
|||||||
async {
|
async {
|
||||||
let jacob_phone_id = "galaxy_s21_ultra_1";
|
let jacob_phone_id = "galaxy_s21_ultra_1";
|
||||||
let jacob_phone_object_id = ObjectId::from_str(jacob_phone_id).unwrap();
|
let jacob_phone_object_id = ObjectId::from_str(jacob_phone_id).unwrap();
|
||||||
|
|
||||||
let services = Python::attach(|py| home_assistant.services(py)).unwrap();
|
let services = Python::attach(|py| home_assistant.services(py)).unwrap();
|
||||||
|
|
||||||
let mut interval = interval(Duration::from_secs(15));
|
let mut interval = interval(Duration::from_secs(15));
|
||||||
@@ -163,7 +163,7 @@ async fn real_main(
|
|||||||
// filter: command::DoNotDisturbFilter::Off,
|
// filter: command::DoNotDisturbFilter::Off,
|
||||||
// }, context, target, false).await;
|
// }, context, target, false).await;
|
||||||
// dbg!(turn_off_result);
|
// dbg!(turn_off_result);
|
||||||
|
|
||||||
// let instant = interval.tick().await;
|
// let instant = interval.tick().await;
|
||||||
// let context: Option<Context<()>> = None;
|
// let context: Option<Context<()>> = None;
|
||||||
// let target: Option<()> = None;
|
// let target: Option<()> = None;
|
||||||
@@ -172,7 +172,7 @@ async fn real_main(
|
|||||||
// filter: command::DoNotDisturbFilter::AlarmsOnly,
|
// filter: command::DoNotDisturbFilter::AlarmsOnly,
|
||||||
// }, context, target, false).await;
|
// }, context, target, false).await;
|
||||||
// dbg!(alarms_only_result);
|
// dbg!(alarms_only_result);
|
||||||
|
|
||||||
// let instant = interval.tick().await;
|
// let instant = interval.tick().await;
|
||||||
// let context: Option<Context<()>> = None;
|
// let context: Option<Context<()>> = None;
|
||||||
// let target: Option<()> = None;
|
// let target: Option<()> = None;
|
||||||
@@ -181,7 +181,7 @@ async fn real_main(
|
|||||||
// filter: command::DoNotDisturbFilter::PriorityOnly,
|
// filter: command::DoNotDisturbFilter::PriorityOnly,
|
||||||
// }, context, target, false).await;
|
// }, context, target, false).await;
|
||||||
// dbg!(priority_only_result);
|
// dbg!(priority_only_result);
|
||||||
|
|
||||||
// let instant = interval.tick().await;
|
// let instant = interval.tick().await;
|
||||||
// let context: Option<Context<()>> = None;
|
// let context: Option<Context<()>> = None;
|
||||||
// let target: Option<()> = None;
|
// let target: Option<()> = None;
|
||||||
@@ -192,7 +192,8 @@ async fn real_main(
|
|||||||
// dbg!(total_silence_result);
|
// dbg!(total_silence_result);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
).0
|
)
|
||||||
|
.0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ once_cell = "1.21.3"
|
|||||||
protocol = { path = "../protocol" }
|
protocol = { path = "../protocol" }
|
||||||
pyo3 = { workspace = true }
|
pyo3 = { workspace = true }
|
||||||
pyo3-async-runtimes = { workspace = true, features = ["tokio-runtime"] }
|
pyo3-async-runtimes = { workspace = true, features = ["tokio-runtime"] }
|
||||||
python-utils = { path = "../python-utils" }
|
python-utils = { path = "../python-utils", features = ["macros"] }
|
||||||
snafu = { workspace = true }
|
snafu = { workspace = true }
|
||||||
strum = { workspace = true, features = ["derive"] }
|
strum = { workspace = true, features = ["derive"] }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
use std::{convert::Infallible, fmt::Display, str::FromStr};
|
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||||
|
|
||||||
use pyo3::{
|
|
||||||
exceptions::{PyException, PyValueError},
|
|
||||||
prelude::*,
|
|
||||||
types::PyString,
|
|
||||||
};
|
|
||||||
use snafu::{ResultExt, Snafu};
|
use snafu::{ResultExt, Snafu};
|
||||||
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
domain::Domain,
|
domain::Domain,
|
||||||
object_id::{ObjectId, ObjectIdParsingError},
|
object_id::{ObjectId, ObjectIdParsingError},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, FromPyFromStr, ToStrToPy)]
|
||||||
pub struct EntityId(pub Domain, pub ObjectId);
|
pub struct EntityId(pub Domain, pub ObjectId);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Snafu)]
|
#[derive(Debug, Clone, Snafu)]
|
||||||
@@ -47,47 +42,3 @@ impl Display for EntityId {
|
|||||||
write!(f, "{domain}.{object_id}")
|
write!(f, "{domain}.{object_id}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
|
||||||
pub enum ExtractEntityIdError {
|
|
||||||
/// couldn't extract the object as a string
|
|
||||||
ExtractStringError { source: PyErr },
|
|
||||||
|
|
||||||
/// couldn't parse the string as an [`EntityId`]
|
|
||||||
ParseError { source: EntityIdParsingError },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ExtractEntityIdError> for PyErr {
|
|
||||||
fn from(error: ExtractEntityIdError) -> Self {
|
|
||||||
match &error {
|
|
||||||
ExtractEntityIdError::ExtractStringError { .. } => {
|
|
||||||
PyException::new_err(error.to_string())
|
|
||||||
}
|
|
||||||
ExtractEntityIdError::ParseError { .. } => PyValueError::new_err(error.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: replace with a derive(PyFromStr) (analogous to serde_with::DeserializeFromStr) once I make one
|
|
||||||
impl<'a, 'py> FromPyObject<'a, 'py> for EntityId {
|
|
||||||
type Error = ExtractEntityIdError;
|
|
||||||
|
|
||||||
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
|
|
||||||
let s = ob.extract().context(ExtractStringSnafu)?;
|
|
||||||
let entity_id = EntityId::from_str(s).context(ParseSnafu)?;
|
|
||||||
|
|
||||||
Ok(entity_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: replace with a derive(DisplayToPy) (analogous to serde_with::SerializeDisplay) once I make one
|
|
||||||
impl<'py> IntoPyObject<'py> for EntityId {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,36 +1,28 @@
|
|||||||
use std::{convert::Infallible, sync::Arc};
|
use std::{convert::Infallible, fmt::Display, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
use pyo3::{exceptions::PyTypeError, prelude::*, types::PyString};
|
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||||
use snafu::{ResultExt, Snafu};
|
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, FromPyFromStr, ToStrToPy)]
|
||||||
pub enum Id {
|
pub enum Id {
|
||||||
Ulid(Ulid),
|
Ulid(Ulid),
|
||||||
Other(Arc<str>),
|
Other(Arc<str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
impl From<String> for Id {
|
||||||
pub enum ExtractIdError {
|
fn from(s: String) -> Self {
|
||||||
/// couldn't extract the given object as a string
|
if let Ok(ulid) = s.parse() {
|
||||||
ExtractStringError { source: PyErr },
|
Id::Ulid(ulid)
|
||||||
}
|
} else {
|
||||||
|
Id::Other(s.into())
|
||||||
impl From<ExtractIdError> for PyErr {
|
|
||||||
fn from(error: ExtractIdError) -> Self {
|
|
||||||
match &error {
|
|
||||||
ExtractIdError::ExtractStringError { .. } => PyTypeError::new_err(error.to_string()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replace with a derive(PyFromStr) (analogous to serde_with::DeserializeFromStr) once I make one
|
impl FromStr for Id {
|
||||||
impl<'a, 'py> FromPyObject<'a, 'py> for Id {
|
type Err = Infallible;
|
||||||
type Error = ExtractIdError;
|
|
||||||
|
|
||||||
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
|
|
||||||
let s = ob.extract::<&str>().context(ExtractStringSnafu)?;
|
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
if let Ok(ulid) = s.parse() {
|
if let Ok(ulid) = s.parse() {
|
||||||
Ok(Id::Ulid(ulid))
|
Ok(Id::Ulid(ulid))
|
||||||
} else {
|
} else {
|
||||||
@@ -39,18 +31,11 @@ impl<'a, 'py> FromPyObject<'a, 'py> for Id {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replace with a derive(DisplayToPy) (analogous to serde_with::SerializeDisplay) once I make one
|
impl Display for Id {
|
||||||
impl<'py> IntoPyObject<'py> for Id {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
type Target = PyString;
|
|
||||||
|
|
||||||
type Output = Bound<'py, Self::Target>;
|
|
||||||
|
|
||||||
type Error = Infallible;
|
|
||||||
|
|
||||||
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
|
|
||||||
match self {
|
match self {
|
||||||
Id::Ulid(ulid) => ulid.to_string().into_pyobject(py),
|
Id::Ulid(ulid) => write!(f, "{ulid}"),
|
||||||
Id::Other(id) => id.into_pyobject(py),
|
Id::Other(other) => write!(f, "{other}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,39 @@
|
|||||||
use pyo3::exceptions::{PyTypeError, PyValueError};
|
use std::fmt::Display;
|
||||||
use pyo3::prelude::*;
|
use std::str::FromStr;
|
||||||
use snafu::{ResultExt, Snafu};
|
|
||||||
|
use pyo3::FromPyObject;
|
||||||
|
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||||
|
use snafu::Snafu;
|
||||||
|
|
||||||
use crate::{entity_id::EntityId, state_object::StateObject};
|
use crate::{entity_id::EntityId, state_object::StateObject};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
// TODO: replace with a derive(PyFromStrLiteral) / #[literal = "state_changed"] once I learn how to make something like that and see about serde or strum integration or inspiration
|
||||||
|
#[derive(Debug, Clone, FromPyFromStr, ToStrToPy)]
|
||||||
pub struct Type;
|
pub struct Type;
|
||||||
|
|
||||||
|
/// expected a string of value "state_changed", but got {actual}
|
||||||
#[derive(Debug, Snafu)]
|
#[derive(Debug, Snafu)]
|
||||||
pub enum ExtractTypeError {
|
pub struct ParseTypeError {
|
||||||
/// couldn't extract this object as a string
|
actual: String,
|
||||||
ExtractStringError { source: PyErr },
|
|
||||||
|
|
||||||
/// expected a string of value "state_changed", but got {actual}
|
|
||||||
UnexpectedValue { actual: String },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ExtractTypeError> for PyErr {
|
impl FromStr for Type {
|
||||||
fn from(error: ExtractTypeError) -> Self {
|
type Err = ParseTypeError;
|
||||||
match &error {
|
|
||||||
ExtractTypeError::ExtractStringError { .. } => PyTypeError::new_err(error.to_string()),
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
ExtractTypeError::UnexpectedValue { .. } => PyValueError::new_err(error.to_string()),
|
if s == "state_changed" {
|
||||||
|
Ok(Self)
|
||||||
|
} else {
|
||||||
|
Err(ParseTypeError {
|
||||||
|
actual: s.to_owned(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replace with a derive(PyFromStrLiteral) / #[literal = "state_changed"] once I learn how to make something like that and see about serde or strum integration or inspiration
|
impl Display for Type {
|
||||||
impl<'a, 'py> FromPyObject<'a, 'py> for Type {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
type Error = ExtractTypeError;
|
write!(f, "state_changed")
|
||||||
|
|
||||||
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
|
|
||||||
let s = ob.extract::<&str>().context(ExtractStringSnafu)?;
|
|
||||||
|
|
||||||
if s == "state_changed" {
|
|
||||||
Ok(Type)
|
|
||||||
} else {
|
|
||||||
Err(ExtractTypeError::UnexpectedValue { actual: s.into() })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
use std::{convert::Infallible, str::FromStr};
|
use std::{convert::Infallible, str::FromStr};
|
||||||
|
|
||||||
use pyo3::{types::{PyDict, PyString, PyDictMethods}, Bound, IntoPyObject, Python, PyErr};
|
use pyo3::{
|
||||||
use python_utils::IntoPyObjectViaDisplay;
|
types::{PyDict, PyDictMethods, PyString},
|
||||||
|
Bound, IntoPyObject, PyErr, Python,
|
||||||
|
};
|
||||||
|
use python_utils::{FromPyFromStr, IntoPyObjectViaDisplay, ToStrToPy};
|
||||||
use snafu::Snafu;
|
use snafu::Snafu;
|
||||||
use strum::EnumString;
|
use strum::EnumString;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@@ -84,7 +87,7 @@ impl TryFrom<String> for NonSpecialMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// How much of a notification is visible on the lock screen
|
/// How much of a notification is visible on the lock screen
|
||||||
#[derive(Debug, Clone, Default, EnumString, strum::Display)]
|
#[derive(Debug, Clone, Default, EnumString, strum::Display, FromPyFromStr, ToStrToPy)]
|
||||||
#[strum(serialize_all = "snake_case")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum Visibility {
|
pub enum Visibility {
|
||||||
/// always show all notification content
|
/// always show all notification content
|
||||||
@@ -98,38 +101,14 @@ pub enum Visibility {
|
|||||||
Secret,
|
Secret,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replace with a derive(DisplayToPy) (analogous to serde_with::SerializeDisplay) once I make one
|
#[derive(Debug, Clone, EnumString, strum::Display, FromPyFromStr, ToStrToPy)]
|
||||||
impl<'py> IntoPyObject<'py> for Visibility {
|
|
||||||
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, EnumString, strum::Display)]
|
|
||||||
pub enum Behavior {
|
pub enum Behavior {
|
||||||
/// prompt for text to return with the event
|
/// prompt for text to return with the event
|
||||||
#[strum(serialize = "textInput")]
|
#[strum(serialize = "textInput")]
|
||||||
TextInput,
|
TextInput,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replace with a derive(DisplayToPy) (analogous to serde_with::SerializeDisplay) once I make one
|
#[derive(Debug, Clone, Default, EnumString, strum::Display, FromPyFromStr, ToStrToPy)]
|
||||||
impl<'py> IntoPyObject<'py> for Behavior {
|
|
||||||
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, Default, EnumString, strum::Display)]
|
|
||||||
#[strum(serialize_all = "camelCase")]
|
#[strum(serialize_all = "camelCase")]
|
||||||
pub enum ActivationMode {
|
pub enum ActivationMode {
|
||||||
/// launch the app when tapped
|
/// launch the app when tapped
|
||||||
@@ -139,18 +118,6 @@ pub enum ActivationMode {
|
|||||||
Background,
|
Background,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replace with a derive(DisplayToPy) (analogous to serde_with::SerializeDisplay) once I make one
|
|
||||||
impl<'py> IntoPyObject<'py> for ActivationMode {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: better typed versions like `CallNumber` or `OpenWebpage` where Action: From<CallNumber> and Action: From<OpenWebPage>
|
// TODO: better typed versions like `CallNumber` or `OpenWebpage` where Action: From<CallNumber> and Action: From<OpenWebPage>
|
||||||
#[derive(Debug, Clone, IntoPyObject, typed_builder::TypedBuilder)]
|
#[derive(Debug, Clone, IntoPyObject, typed_builder::TypedBuilder)]
|
||||||
#[builder(field_defaults(default, setter(strip_option(fallback_suffix = "_option"))))]
|
#[builder(field_defaults(default, setter(strip_option(fallback_suffix = "_option"))))]
|
||||||
@@ -190,7 +157,7 @@ pub struct Action {
|
|||||||
pub icon: Option<String>,
|
pub icon: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, EnumString, strum::Display)]
|
#[derive(Debug, Clone, Default, EnumString, strum::Display, FromPyFromStr, ToStrToPy)]
|
||||||
#[strum(serialize_all = "snake_case", suffix = "_stream")]
|
#[strum(serialize_all = "snake_case", suffix = "_stream")]
|
||||||
pub enum MediaStream {
|
pub enum MediaStream {
|
||||||
Alarm,
|
Alarm,
|
||||||
@@ -203,18 +170,6 @@ pub enum MediaStream {
|
|||||||
System,
|
System,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replace with a derive(DisplayToPy) (analogous to serde_with::SerializeDisplay) once I make one
|
|
||||||
impl<'py> IntoPyObject<'py> for MediaStream {
|
|
||||||
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, Default, Clone, typed_builder::TypedBuilder)]
|
#[derive(Debug, Default, Clone, typed_builder::TypedBuilder)]
|
||||||
#[builder(field_defaults(default, setter(strip_option(fallback_suffix = "_option"))))]
|
#[builder(field_defaults(default, setter(strip_option(fallback_suffix = "_option"))))]
|
||||||
pub struct NotifyMobileAppServiceDataData {
|
pub struct NotifyMobileAppServiceDataData {
|
||||||
@@ -232,7 +187,13 @@ impl<'py> IntoPyObject<'py> for NotifyMobileAppServiceDataData {
|
|||||||
type Error = PyErr;
|
type Error = PyErr;
|
||||||
|
|
||||||
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
|
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
|
||||||
let NotifyMobileAppServiceDataData { actions, command, media_stream, tts_text, visibility } = self;
|
let NotifyMobileAppServiceDataData {
|
||||||
|
actions,
|
||||||
|
command,
|
||||||
|
media_stream,
|
||||||
|
tts_text,
|
||||||
|
visibility,
|
||||||
|
} = self;
|
||||||
|
|
||||||
let dict = PyDict::new(py);
|
let dict = PyDict::new(py);
|
||||||
|
|
||||||
@@ -279,7 +240,11 @@ impl<'py> IntoPyObject<'py> for NotifyMobileAppServiceData {
|
|||||||
type Error = PyErr;
|
type Error = PyErr;
|
||||||
|
|
||||||
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
|
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
|
||||||
let NotifyMobileAppServiceData { message, title, data } = self;
|
let NotifyMobileAppServiceData {
|
||||||
|
message,
|
||||||
|
title,
|
||||||
|
data,
|
||||||
|
} = self;
|
||||||
|
|
||||||
let dict = PyDict::new(py);
|
let dict = PyDict::new(py);
|
||||||
|
|
||||||
@@ -293,4 +258,4 @@ impl<'py> IntoPyObject<'py> for NotifyMobileAppServiceData {
|
|||||||
|
|
||||||
Ok(dict)
|
Ok(dict)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use mitsein::vec1::Vec1;
|
use mitsein::vec1::Vec1;
|
||||||
use pyo3::{types::PyAnyMethods, IntoPyObject, Python};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
object_id::ObjectId,
|
object_id::ObjectId,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{future::Future, str::FromStr, sync::Arc};
|
use std::{future::Future, sync::Arc};
|
||||||
|
|
||||||
use emitter_and_signal::{Signal, SignalExt};
|
use emitter_and_signal::{Signal, SignalExt};
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use std::{str::FromStr, sync::Arc};
|
use std::{str::FromStr, sync::Arc};
|
||||||
|
|
||||||
use pyo3::{exceptions::PyValueError, PyErr};
|
use pyo3::{exceptions::PyValueError, PyErr};
|
||||||
|
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||||
use snafu::Snafu;
|
use snafu::Snafu;
|
||||||
|
|
||||||
// TODO: derive(PyFromStr) (analogous to serde_with::DeserializeFromStr) once I make one
|
#[derive(Debug, Clone, derive_more::Display, FromPyFromStr, ToStrToPy)]
|
||||||
#[derive(Debug, Clone, derive_more::Display)]
|
|
||||||
pub struct Slug(Arc<str>);
|
pub struct Slug(Arc<str>);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Snafu)]
|
#[derive(Debug, Clone, Snafu)]
|
||||||
|
|||||||
@@ -1,55 +1,12 @@
|
|||||||
use std::str::FromStr;
|
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||||
|
|
||||||
use pyo3::{
|
|
||||||
exceptions::{PyException, PyValueError},
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
use snafu::{ResultExt, Snafu};
|
|
||||||
use strum::EnumString;
|
use strum::EnumString;
|
||||||
|
|
||||||
/// A state in Home Assistant that is known to represent an error of some kind:
|
/// A state in Home Assistant that is known to represent an error of some kind:
|
||||||
/// * `unavailable` (the device is likely offline or unreachable from the Home Assistant instance)
|
/// * `unavailable` (the device is likely offline or unreachable from the Home Assistant instance)
|
||||||
/// * `unknown` (I don't know how to explain this one)
|
/// * `unknown` (I don't know how to explain this one)
|
||||||
#[derive(Debug, Clone, EnumString, strum::Display)]
|
#[derive(Debug, Clone, EnumString, strum::Display, FromPyFromStr, ToStrToPy)]
|
||||||
#[strum(serialize_all = "snake_case")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum ErrorState {
|
pub enum ErrorState {
|
||||||
Unavailable,
|
Unavailable,
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
|
||||||
pub enum ExtractErrorStateError {
|
|
||||||
/// couldn't extract the object as a string
|
|
||||||
ExtractStringError { source: PyErr },
|
|
||||||
|
|
||||||
/// the string had an unexpected value
|
|
||||||
UnexpectedValue {
|
|
||||||
source: <ErrorState as FromStr>::Err,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ExtractErrorStateError> for PyErr {
|
|
||||||
fn from(error: ExtractErrorStateError) -> Self {
|
|
||||||
match &error {
|
|
||||||
ExtractErrorStateError::ExtractStringError { .. } => {
|
|
||||||
PyException::new_err(error.to_string())
|
|
||||||
}
|
|
||||||
ExtractErrorStateError::UnexpectedValue { .. } => {
|
|
||||||
PyValueError::new_err(error.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: replace with a derive(PyFromStr) (analogous to serde_with::DeserializeFromStr) once I make one
|
|
||||||
impl<'a, 'py> FromPyObject<'a, 'py> for ErrorState {
|
|
||||||
type Error = ExtractErrorStateError;
|
|
||||||
|
|
||||||
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
|
|
||||||
let s = ob.extract::<String>().context(ExtractStringSnafu)?;
|
|
||||||
|
|
||||||
let state = ErrorState::from_str(&s).context(UnexpectedValueSnafu)?;
|
|
||||||
|
|
||||||
Ok(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::{convert::Infallible, str::FromStr};
|
use std::{convert::Infallible, str::FromStr};
|
||||||
|
|
||||||
use pyo3::{exceptions::PyException, prelude::*};
|
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||||
use snafu::{ResultExt, Snafu};
|
|
||||||
|
|
||||||
pub mod error_state;
|
pub mod error_state;
|
||||||
pub mod unexpected_state;
|
pub mod unexpected_state;
|
||||||
@@ -9,13 +8,30 @@ pub mod unexpected_state;
|
|||||||
pub use error_state::ErrorState;
|
pub use error_state::ErrorState;
|
||||||
pub use unexpected_state::UnexpectedState;
|
pub use unexpected_state::UnexpectedState;
|
||||||
|
|
||||||
#[derive(Debug, Clone, derive_more::Display)]
|
#[derive(Debug, Clone, derive_more::Display, FromPyFromStr, ToStrToPy)]
|
||||||
pub enum HomeAssistantState<State> {
|
pub enum HomeAssistantState<State> {
|
||||||
Ok(State),
|
Ok(State),
|
||||||
Err(ErrorState),
|
Err(ErrorState),
|
||||||
UnexpectedErr(UnexpectedState),
|
UnexpectedErr(UnexpectedState),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<State> From<String> for HomeAssistantState<State>
|
||||||
|
where
|
||||||
|
State: FromStr,
|
||||||
|
{
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
if let Ok(ok) = State::from_str(&s) {
|
||||||
|
return HomeAssistantState::Ok(ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(error) = ErrorState::from_str(&s) {
|
||||||
|
return HomeAssistantState::Err(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
HomeAssistantState::UnexpectedErr(UnexpectedState(s.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<State: FromStr> FromStr for HomeAssistantState<State> {
|
impl<State: FromStr> FromStr for HomeAssistantState<State> {
|
||||||
type Err = Infallible;
|
type Err = Infallible;
|
||||||
|
|
||||||
@@ -31,34 +47,3 @@ impl<State: FromStr> FromStr for HomeAssistantState<State> {
|
|||||||
Ok(HomeAssistantState::UnexpectedErr(UnexpectedState(s.into())))
|
Ok(HomeAssistantState::UnexpectedErr(UnexpectedState(s.into())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
|
||||||
pub enum ExtractHomeAssistantStateError {
|
|
||||||
/// couldn't extract the object as a string
|
|
||||||
ExtractStringError { source: PyErr },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ExtractHomeAssistantStateError> for PyErr {
|
|
||||||
fn from(error: ExtractHomeAssistantStateError) -> Self {
|
|
||||||
match &error {
|
|
||||||
ExtractHomeAssistantStateError::ExtractStringError { .. } => {
|
|
||||||
PyException::new_err(error.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: replace with a derive(PyFromStr) (analogous to serde_with::DeserializeFromStr) once I make one
|
|
||||||
impl<'a, 'py, State: FromStr + FromPyObject<'a, 'py>> FromPyObject<'a, 'py>
|
|
||||||
for HomeAssistantState<State>
|
|
||||||
{
|
|
||||||
type Error = ExtractHomeAssistantStateError;
|
|
||||||
|
|
||||||
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
|
|
||||||
let s = ob.extract::<&str>().context(ExtractStringSnafu)?;
|
|
||||||
|
|
||||||
let Ok(state) = s.parse();
|
|
||||||
|
|
||||||
Ok(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use pyo3::{
|
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||||
exceptions::{PyException, PyValueError},
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
use snafu::{ResultExt, Snafu};
|
|
||||||
use strum::EnumString;
|
use strum::EnumString;
|
||||||
use uom::{
|
use uom::{
|
||||||
si::{
|
si::{
|
||||||
@@ -18,7 +14,7 @@ use uom::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Power units
|
/// Power units
|
||||||
#[derive(Debug, Clone, Copy, EnumString, strum::Display)]
|
#[derive(Debug, Clone, Copy, EnumString, strum::Display, FromPyFromStr, ToStrToPy)]
|
||||||
#[strum(serialize_all = "snake_case")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum UnitOfMeasurement {
|
pub enum UnitOfMeasurement {
|
||||||
#[strum(serialize = "mW")]
|
#[strum(serialize = "mW")]
|
||||||
@@ -37,42 +33,6 @@ pub enum UnitOfMeasurement {
|
|||||||
BtuPerhour,
|
BtuPerhour,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
|
||||||
pub enum ExtractUnitOfMeasurementError {
|
|
||||||
/// couldn't extract the object as a string
|
|
||||||
ExtractStringError { source: PyErr },
|
|
||||||
|
|
||||||
/// couldn't parse the string as a [`UnitOfMeasurement`]
|
|
||||||
ParseError {
|
|
||||||
source: <UnitOfMeasurement as FromStr>::Err,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ExtractUnitOfMeasurementError> for PyErr {
|
|
||||||
fn from(error: ExtractUnitOfMeasurementError) -> Self {
|
|
||||||
match &error {
|
|
||||||
ExtractUnitOfMeasurementError::ExtractStringError { .. } => {
|
|
||||||
PyException::new_err(error.to_string())
|
|
||||||
}
|
|
||||||
ExtractUnitOfMeasurementError::ParseError { .. } => {
|
|
||||||
PyValueError::new_err(error.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: replace with a derive(PyFromStr) (analogous to serde_with::DeserializeFromStr) once I make one
|
|
||||||
impl<'a, 'py> FromPyObject<'a, 'py> for UnitOfMeasurement {
|
|
||||||
type Error = ExtractUnitOfMeasurementError;
|
|
||||||
|
|
||||||
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
|
|
||||||
let s = obj.extract().context(ExtractStringSnafu)?;
|
|
||||||
let unit_of_measurement = UnitOfMeasurement::from_str(s).context(ParseSnafu)?;
|
|
||||||
|
|
||||||
Ok(unit_of_measurement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UnitOfMeasurement {
|
impl UnitOfMeasurement {
|
||||||
pub fn into_uom<V>(&self, amount: V) -> Power<V>
|
pub fn into_uom<V>(&self, amount: V) -> Power<V>
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = "1.0.106"
|
proc-macro2 = { workspace = true }
|
||||||
quote = "1.0.46"
|
quote = { workspace = true }
|
||||||
syn = "2.0.118"
|
syn = { workspace = true }
|
||||||
|
|||||||
56
python-utils-macros-impl/src/from_py_from_str.rs
Normal file
56
python-utils-macros-impl/src/from_py_from_str.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{ToTokens, quote};
|
||||||
|
use syn::{
|
||||||
|
DeriveInput, GenericParam,
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
parse_quote,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct FromPyFromStr {
|
||||||
|
derive_input: DeriveInput,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for FromPyFromStr {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let derive_input = input.parse()?;
|
||||||
|
|
||||||
|
let this = Self { derive_input };
|
||||||
|
|
||||||
|
Ok(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for FromPyFromStr {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let name = &self.derive_input.ident;
|
||||||
|
|
||||||
|
let (_, ty_generics, where_clause) = self.derive_input.generics.split_for_impl();
|
||||||
|
let mut generics = self.derive_input.generics.clone();
|
||||||
|
|
||||||
|
let a_lifetime: GenericParam = parse_quote!('a);
|
||||||
|
let py_lifetime: GenericParam = parse_quote!('py);
|
||||||
|
|
||||||
|
generics.params.push(a_lifetime.clone());
|
||||||
|
generics.params.push(py_lifetime.clone());
|
||||||
|
|
||||||
|
let (impl_generics, _, _) = generics.split_for_impl();
|
||||||
|
|
||||||
|
let output = quote! {
|
||||||
|
impl #impl_generics ::pyo3::conversion::FromPyObject<#a_lifetime, #py_lifetime> for #name #ty_generics #where_clause
|
||||||
|
where
|
||||||
|
#name #ty_generics: ::std::str::FromStr,
|
||||||
|
<#name #ty_generics as ::std::str::FromStr>::Err: ::std::fmt::Display,
|
||||||
|
{
|
||||||
|
type Error = ::python_utils::ExtractPyObjectViaParseError<<#name #ty_generics as ::std::str::FromStr>::Err>;
|
||||||
|
|
||||||
|
fn extract(obj: ::pyo3::Borrowed<#a_lifetime, #py_lifetime, ::pyo3::types::PyAny>) -> ::core::result::Result<Self, Self::Error> {
|
||||||
|
::python_utils::FromPyObjectViaParse::extract(obj).map(|wrapper| wrapper.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dbg!(&output.to_string());
|
||||||
|
|
||||||
|
tokens.extend(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,5 @@
|
|||||||
use proc_macro2::TokenStream;
|
mod from_py_from_str;
|
||||||
use quote::quote;
|
mod to_str_to_py;
|
||||||
use syn::{DeriveInput, parse_macro_input};
|
|
||||||
|
|
||||||
pub fn py_from_str(input: TokenStream) -> TokenStream {
|
pub use from_py_from_str::FromPyFromStr;
|
||||||
let derive_input: DeriveInput = parse_macro_input!(input);
|
pub use to_str_to_py::ToStrToPy;
|
||||||
|
|
||||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
|
||||||
|
|
||||||
let expanded = quote! {
|
|
||||||
impl #impl_generics MyTrait for #name #ty_generics #where_clause {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
expanded
|
|
||||||
}
|
|
||||||
|
|||||||
56
python-utils-macros-impl/src/to_str_to_py.rs
Normal file
56
python-utils-macros-impl/src/to_str_to_py.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{ToTokens, quote};
|
||||||
|
use syn::{
|
||||||
|
DeriveInput, GenericParam,
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
parse_quote,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ToStrToPy {
|
||||||
|
derive_input: DeriveInput,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for ToStrToPy {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let derive_input = input.parse()?;
|
||||||
|
|
||||||
|
let this = Self { derive_input };
|
||||||
|
|
||||||
|
Ok(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for ToStrToPy {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let name = &self.derive_input.ident;
|
||||||
|
|
||||||
|
let (_, ty_generics, where_clause) = self.derive_input.generics.split_for_impl();
|
||||||
|
let mut generics = self.derive_input.generics.clone();
|
||||||
|
|
||||||
|
let py_lifetime: GenericParam = parse_quote!('py);
|
||||||
|
|
||||||
|
generics.params.push(py_lifetime.clone());
|
||||||
|
|
||||||
|
let (impl_generics, _, _) = generics.split_for_impl();
|
||||||
|
|
||||||
|
let output = quote! {
|
||||||
|
impl #impl_generics ::pyo3::conversion::IntoPyObject<#py_lifetime> for #name #ty_generics #where_clause
|
||||||
|
where
|
||||||
|
#name #ty_generics: ::std::fmt::Display
|
||||||
|
{
|
||||||
|
type Target = ::pyo3::types::PyString;
|
||||||
|
type Output = ::pyo3::Bound<#py_lifetime, Self::Target>;
|
||||||
|
type Error = ::std::convert::Infallible;
|
||||||
|
|
||||||
|
fn into_pyobject(self, py: ::pyo3::Python<#py_lifetime>) -> ::core::result::Result<Self::Output, Self::Error> {
|
||||||
|
let s = ::std::string::ToString::to_string(&self);
|
||||||
|
::pyo3::conversion::IntoPyObject::into_pyobject(s, py)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dbg!(&output.to_string());
|
||||||
|
|
||||||
|
tokens.extend(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,3 +9,5 @@ proc-macro = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
python-utils-macros-impl = { path = "../python-utils-macros-impl" }
|
python-utils-macros-impl = { path = "../python-utils-macros-impl" }
|
||||||
|
quote = { workspace = true }
|
||||||
|
syn = { workspace = true }
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
use proc_macro::proc_macro_derive;
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::parse_macro_input;
|
||||||
|
|
||||||
#[proc_macro_derive(PyFromStr)]
|
#[proc_macro_derive(FromPyFromStr)]
|
||||||
pub fn py_from_str(input: TokenStream) -> TokenStream {
|
pub fn from_py_from_str(input: TokenStream) -> TokenStream {
|
||||||
python_utils_macros_impl::py_from_str(input.into()).into()
|
let item: python_utils_macros_impl::FromPyFromStr = parse_macro_input!(input);
|
||||||
|
quote! { #item }.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(ToStrToPy)]
|
||||||
|
pub fn to_str_to_py(input: TokenStream) -> TokenStream {
|
||||||
|
let item: python_utils_macros_impl::ToStrToPy = parse_macro_input!(input);
|
||||||
|
quote! { #item }.into()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
macros = ["dep:python-utils-macros"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
derive_more = { workspace = true }
|
derive_more = { workspace = true }
|
||||||
pyo3 = { workspace = true }
|
pyo3 = { workspace = true }
|
||||||
python-utils-macros = { path = "../python-utils-macros" }
|
python-utils-macros = { optional = true, path = "../python-utils-macros" }
|
||||||
snafu = { workspace = true }
|
snafu = { workspace = true }
|
||||||
|
|||||||
73
python-utils/src/from_pyobject_via_parse.rs
Normal file
73
python-utils/src/from_pyobject_via_parse.rs
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
20
python-utils/src/into_pyobject_via_display.rs
Normal file
20
python-utils/src/into_pyobject_via_display.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
use std::{convert::Infallible, fmt::Display, str::FromStr, sync::Arc};
|
|
||||||
|
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
exceptions::{PyException, PyTypeError, PyValueError},
|
exceptions::{PyException, PyTypeError},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
types::PyString,
|
|
||||||
};
|
};
|
||||||
use snafu::{ResultExt, Snafu};
|
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 mod none;
|
||||||
|
|
||||||
|
pub use from_pyobject_via_parse::{ExtractPyObjectViaParseError, FromPyObjectViaParse};
|
||||||
|
pub use into_pyobject_via_display::IntoPyObjectViaDisplay;
|
||||||
pub use none::IsNone;
|
pub use none::IsNone;
|
||||||
|
|
||||||
/// Create a GIL-independent reference
|
/// Create a GIL-independent reference
|
||||||
@@ -83,70 +86,3 @@ pub fn validate_type_by_name(
|
|||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user