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

@@ -1,33 +1,64 @@
use super::{event::context::context::Context, service::IntoServiceCall};
use pyo3::prelude::*;
use python_utils::{detach, validate_type_by_name};
use pyo3::{
exceptions::{PyException, PyTypeError},
prelude::*,
};
use python_utils::{detach, validate_type_by_name, TypeByNameValidationError};
use snafu::{ResultExt, Snafu};
#[derive(Debug)]
pub struct ServiceRegistry(Py<PyAny>);
impl<'py> FromPyObject<'py> for ServiceRegistry {
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
impl<'a, 'py> FromPyObject<'a, 'py> for ServiceRegistry {
type Error = TypeByNameValidationError;
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
// region: Validation
validate_type_by_name(ob, "ServiceRegistry")?;
validate_type_by_name(&ob, "ServiceRegistry")?;
// endregion: Validation
Ok(Self(detach(ob)))
}
}
#[derive(Debug, Snafu)]
pub enum CallServiceError {
/// couldn't successfully call `async_call` and turn it into a `Future`
CallIntoFutureError { source: PyErr },
/// couldn't await the `Future` from the `async_call`
AwaitFutureError { source: PyErr },
/// couldn't extract the service response as the requested type
ExtractServiceResponseError { source: PyErr },
}
impl From<CallServiceError> for PyErr {
fn from(error: CallServiceError) -> Self {
match &error {
CallServiceError::CallIntoFutureError { .. } => PyException::new_err(error.to_string()),
CallServiceError::AwaitFutureError { .. } => PyException::new_err(error.to_string()),
CallServiceError::ExtractServiceResponseError { .. } => {
PyTypeError::new_err(error.to_string())
}
}
}
}
impl ServiceRegistry {
pub async fn call_service<
'a,
ServiceData: for<'py> IntoPyObject<'py>,
Target: for<'py> IntoPyObject<'py>,
Event: for<'py> IntoPyObject<'py>,
ServiceResponse: for<'py> FromPyObject<'py>,
ServiceResponse: 'static + for<'py> FromPyObjectOwned<'py>,
>(
&self,
&'a self,
service_call: impl IntoServiceCall<ServiceData = ServiceData>,
context: Option<Context<Event>>,
target: Option<Target>,
return_response: bool,
) -> PyResult<ServiceResponse> {
) -> Result<ServiceResponse, CallServiceError> {
let (domain, service, service_data) = service_call.into_service_call();
let blocking = true;
@@ -42,13 +73,21 @@ impl ServiceRegistry {
return_response,
);
let future = Python::with_gil::<_, PyResult<_>>(|py| {
let future = Python::attach::<_, PyResult<_>>(|py| {
let service_registry = self.0.bind(py);
let awaitable = service_registry.call_method("async_call", args, None)?;
pyo3_async_runtimes::tokio::into_future(awaitable)
})
.context(CallIntoFutureSnafu)?;
let service_response = future.await.context(AwaitFutureSnafu)?;
let service_response = Python::attach(move |py| {
service_response
.extract(py)
.map_err(Into::into)
.context(ExtractServiceResponseSnafu)
})?;
let service_response = future.await?;
Python::with_gil(|py| service_response.extract(py))
Ok(service_response)
}
}