chore+feat(home-assistant): update to pyo3 0.27 and update extraction errors, switch out SmolStr for Arc<str>, tighten up light service calls and implement some for notify, start implementing units of measurement like for power

This commit is contained in:
2026-01-07 02:10:03 -05:00
parent 97aef026b2
commit fa36b39e81
35 changed files with 1255 additions and 259 deletions

View File

@@ -28,7 +28,7 @@ impl<'py, Event: IntoPyObject<'py>> IntoPyObject<'py> for Context<Event> {
.bind(py);
let context_class = homeassistant_core.getattr("Context")?;
let context_class = context_class.downcast_into::<PyType>()?;
let context_class = context_class.cast_into::<PyType>()?;
let context_instance = context_class.call1((self.user_id, self.parent_id, self.id))?;

View File

@@ -1,18 +1,35 @@
use std::convert::Infallible;
use std::{convert::Infallible, sync::Arc};
use pyo3::{prelude::*, types::PyString};
use smol_str::SmolStr;
use pyo3::{exceptions::PyTypeError, prelude::*, types::PyString};
use snafu::{ResultExt, Snafu};
use ulid::Ulid;
#[derive(Debug, Clone)]
pub enum Id {
Ulid(Ulid),
Other(SmolStr),
Other(Arc<str>),
}
impl<'py> FromPyObject<'py> for Id {
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
let s = ob.extract::<String>()?;
#[derive(Debug, Snafu)]
pub enum ExtractIdError {
/// couldn't extract the given object as a string
ExtractStringError { source: PyErr },
}
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<'a, 'py> FromPyObject<'a, 'py> for Id {
type Error = ExtractIdError;
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
let s = ob.extract::<&str>().context(ExtractStringSnafu)?;
if let Ok(ulid) = s.parse() {
Ok(Id::Ulid(ulid))
@@ -22,6 +39,7 @@ impl<'py> FromPyObject<'py> for Id {
}
}
// TODO: replace with a derive(DisplayToPy) (analogous to serde_with::SerializeDisplay) once I make one
impl<'py> IntoPyObject<'py> for Id {
type Target = PyString;
@@ -32,7 +50,7 @@ impl<'py> IntoPyObject<'py> for Id {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match self {
Id::Ulid(ulid) => ulid.to_string().into_pyobject(py),
Id::Other(id) => id.as_str().into_pyobject(py),
Id::Other(id) => id.into_pyobject(py),
}
}
}

View File

@@ -1,6 +1,10 @@
use std::str::FromStr;
use pyo3::{exceptions::PyValueError, prelude::*};
use pyo3::{
exceptions::{PyException, PyTypeError, PyValueError},
prelude::*,
};
use snafu::{ResultExt, Snafu};
#[derive(Debug, Clone, strum::EnumString, strum::Display)]
#[strum(serialize_all = "UPPERCASE")]
@@ -9,12 +13,41 @@ pub enum EventOrigin {
Remote,
}
impl<'py> FromPyObject<'py> for EventOrigin {
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
let s = ob.str()?;
let s = s.extract()?;
let event_origin =
EventOrigin::from_str(s).map_err(|err| PyValueError::new_err(err.to_string()))?;
#[derive(Debug, Snafu)]
pub enum ExtractEventOriginError {
/// couldn't turn the object into a string with `str` (Python function)
ToStrError { source: PyErr },
/// after calling `str` on the object, it's somehow not extractable as a string?!
NotString { source: PyErr },
/// this is not an expected value for [`EventOrigin`]
UnexpectedValue {
source: <EventOrigin as FromStr>::Err,
},
}
impl From<ExtractEventOriginError> for PyErr {
fn from(error: ExtractEventOriginError) -> Self {
match &error {
ExtractEventOriginError::ToStrError { .. } => PyException::new_err(error.to_string()),
ExtractEventOriginError::NotString { .. } => PyTypeError::new_err(error.to_string()),
ExtractEventOriginError::UnexpectedValue { .. } => {
PyValueError::new_err(error.to_string())
}
}
}
}
impl<'a, 'py> FromPyObject<'a, 'py> for EventOrigin {
type Error = ExtractEventOriginError;
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
let s = ob.str().context(ToStrSnafu)?;
// TODO: could I go straight to trying to extract an &str without calling .str() first? if so then I could
// TODO: replace with a derive(PyFromStr) (analogous to serde_with::DeserializeFromStr) once I make one
let s = s.extract().context(NotStringSnafu)?;
let event_origin = EventOrigin::from_str(s).context(UnexpectedValueSnafu)?;
Ok(event_origin)
}

View File

@@ -1,21 +1,41 @@
use pyo3::exceptions::PyValueError;
use pyo3::exceptions::{PyTypeError, PyValueError};
use pyo3::prelude::*;
use snafu::{ResultExt, Snafu};
use crate::{entity_id::EntityId, state_object::StateObject};
#[derive(Debug, Clone)]
pub struct Type;
impl<'py> FromPyObject<'py> for Type {
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
let s = ob.extract::<&str>()?;
#[derive(Debug, Snafu)]
pub enum ExtractTypeError {
/// couldn't extract this object as a string
ExtractStringError { source: PyErr },
/// expected a string of value "state_changed", but got {actual}
UnexpectedValue { actual: String },
}
impl From<ExtractTypeError> for PyErr {
fn from(error: ExtractTypeError) -> Self {
match &error {
ExtractTypeError::ExtractStringError { .. } => PyTypeError::new_err(error.to_string()),
ExtractTypeError::UnexpectedValue { .. } => PyValueError::new_err(error.to_string()),
}
}
}
// TODO: replace with a derive(PyFromStrLiteral) / #[str = "state_changed"] once I learn how to make something like that and see about serde or strum integration or inspiration
impl<'a, 'py> FromPyObject<'a, 'py> for Type {
type Error = ExtractTypeError;
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(PyValueError::new_err(format!(
"expected a string of value 'state_changed', but got {s}"
)))
Err(ExtractTypeError::UnexpectedValue { actual: s.into() })
}
}
}