chore: extract python_utils and home-assistant to their own crates

This commit is contained in:
2025-04-21 21:26:14 -04:00
parent e4fd9844cc
commit f8b269b6ce
36 changed files with 76 additions and 40 deletions

View File

@@ -0,0 +1,39 @@
use super::id::Id;
use once_cell::sync::OnceCell;
use pyo3::{prelude::*, types::PyType};
/// The context that triggered something.
#[derive(Debug, FromPyObject)]
pub struct Context<Event> {
pub id: Id,
pub user_id: Option<String>,
pub parent_id: Option<String>,
/// In order to prevent cycles, the user must decide to pass [`Py<PyAny>`] for the `Event` type here
/// or for the `Context` type in [`Event`]
pub origin_event: Event,
}
impl<'py, Event: IntoPyObject<'py>> IntoPyObject<'py> for Context<Event> {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
static HOMEASSISTANT_CORE: OnceCell<Py<PyModule>> = OnceCell::new();
let homeassistant_core = HOMEASSISTANT_CORE
.get_or_try_init(|| Result::<_, PyErr>::Ok(py.import("homeassistant.core")?.unbind()))?
.bind(py);
let context_class = homeassistant_core.getattr("Context")?;
let context_class = context_class.downcast_into::<PyType>()?;
let context_instance = context_class.call1((self.user_id, self.parent_id, self.id))?;
context_instance.setattr("origin_event", self.origin_event)?;
Ok(context_instance)
}
}

View File

@@ -0,0 +1,38 @@
use std::convert::Infallible;
use pyo3::{prelude::*, types::PyString};
use smol_str::SmolStr;
use ulid::Ulid;
#[derive(Debug, Clone)]
pub enum Id {
Ulid(Ulid),
Other(SmolStr),
}
impl<'py> FromPyObject<'py> for Id {
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
let s = ob.extract::<String>()?;
if let Ok(ulid) = s.parse() {
Ok(Id::Ulid(ulid))
} else {
Ok(Id::Other(s.into()))
}
}
}
impl<'py> IntoPyObject<'py> for Id {
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 {
Id::Ulid(ulid) => ulid.to_string().into_pyobject(py),
Id::Other(id) => id.as_str().into_pyobject(py),
}
}
}

View File

@@ -0,0 +1,2 @@
pub mod context;
pub mod id;

View File

@@ -0,0 +1,27 @@
use chrono::{DateTime, Utc};
use pyo3::prelude::*;
use super::event_origin::EventOrigin;
/// Representation of an event within the bus.
#[derive(Debug, FromPyObject)]
pub struct Event<Type, Data, Context> {
pub event_type: Type,
pub data: Data,
pub origin: EventOrigin,
/// In order to prevent cycles, the user must decide to pass [`Py<PyAny>`] for the `Context` type here
/// or for the `Event` type in [`Context`]
pub context: Context,
time_fired_timestamp: f64,
}
impl<Type, Data, Context> Event<Type, Data, Context> {
pub fn time_fired(&self) -> Option<DateTime<Utc>> {
const NANOS_PER_SEC: i32 = 1_000_000_000;
let secs = self.time_fired_timestamp as i64;
let nsecs = (self.time_fired_timestamp.fract() * (NANOS_PER_SEC as f64)) as u32;
DateTime::from_timestamp(secs, nsecs)
}
}

View File

@@ -0,0 +1,21 @@
use std::str::FromStr;
use pyo3::{exceptions::PyValueError, prelude::*};
#[derive(Debug, Clone, strum::EnumString, strum::Display)]
#[strum(serialize_all = "UPPERCASE")]
pub enum EventOrigin {
Local,
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()))?;
Ok(event_origin)
}
}

View File

@@ -0,0 +1,4 @@
pub mod context;
pub mod event;
pub mod event_origin;
pub mod specific;

View File

@@ -0,0 +1 @@
pub mod state_changed;

View File

@@ -0,0 +1,58 @@
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
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>()?;
if s == "state_changed" {
Ok(Type)
} else {
Err(PyValueError::new_err(format!(
"expected a string of value 'state_changed', but got {s}"
)))
}
}
}
#[derive(Debug, FromPyObject)]
#[pyo3(from_item_all)]
pub struct Data<
OldState,
OldAttributes,
OldStateContextEvent,
NewState,
NewAttributes,
NewStateContextEvent,
> {
pub entity_id: EntityId,
pub old_state: Option<StateObject<OldState, OldAttributes, OldStateContextEvent>>,
pub new_state: Option<StateObject<NewState, NewAttributes, NewStateContextEvent>>,
}
/// A state changed event is fired when on state write the state is changed.
pub type Event<
OldState,
OldAttributes,
OldStateContextEvent,
NewState,
NewAttributes,
NewStateContextEvent,
Context,
> = super::super::event::Event<
Type,
Data<
OldState,
OldAttributes,
OldStateContextEvent,
NewState,
NewAttributes,
NewStateContextEvent,
>,
Context,
>;