harden delivery pipeline and production runtime
This commit is contained in:
@@ -31,10 +31,13 @@ class HaClient:
|
||||
"Content-Type": "application/json",
|
||||
})
|
||||
|
||||
def close(self) -> None:
|
||||
self._session.close()
|
||||
|
||||
def list_entities(self) -> list[dict[str, object]]:
|
||||
try:
|
||||
response = self._session.get(
|
||||
f"{self._settings.url}/api/states",
|
||||
f"{self._settings.url.rstrip('/')}/api/states",
|
||||
timeout=self._settings.timeout_seconds,
|
||||
)
|
||||
except requests.Timeout as exc:
|
||||
@@ -71,4 +74,4 @@ class HaClient:
|
||||
"Antwort von Home Assistant hat unerwartetes Format."
|
||||
)
|
||||
|
||||
return payload
|
||||
return payload
|
||||
|
||||
@@ -15,9 +15,10 @@ class HaReader:
|
||||
entities = self._client.list_entities()
|
||||
summaries: list[HaEntitySummary] = []
|
||||
for item in entities:
|
||||
entity_id = item.get("entity_id", "")
|
||||
if "." not in entity_id:
|
||||
raw_entity_id = item.get("entity_id")
|
||||
if not isinstance(raw_entity_id, str) or "." not in raw_entity_id:
|
||||
continue
|
||||
entity_id = raw_entity_id
|
||||
domain = entity_id.split(".", 1)[0]
|
||||
raw_attributes = item.get("attributes") or {}
|
||||
attributes: dict[str, Any] = raw_attributes if isinstance(raw_attributes, dict) else {}
|
||||
|
||||
17
app/main.py
17
app/main.py
@@ -1,4 +1,6 @@
|
||||
from contextlib import asynccontextmanager
|
||||
from collections.abc import AsyncIterator
|
||||
from typing import cast
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
@@ -7,23 +9,30 @@ from app.config import load_settings
|
||||
from app.core.exception_handlers import register_exception_handlers
|
||||
from app.ha.client import HaClient, HaClientSettings
|
||||
from app.ha.reader import HaReader
|
||||
from app.ml.registry.model_registry import ModelRegistry
|
||||
from backend.routes.ml import init_ml_routes
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI): # type: ignore[no-untyped-def]
|
||||
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
settings = app.state.settings
|
||||
client: HaClient | None = None
|
||||
app.state.registry = ModelRegistry(settings.model_store)
|
||||
if hasattr(app.state, "ha_reader"):
|
||||
del app.state.ha_reader
|
||||
if settings.ha_configured:
|
||||
client = HaClient(
|
||||
settings=HaClientSettings(
|
||||
url=settings.ha_url,
|
||||
token=settings.ha_token,
|
||||
url=cast(str, settings.ha_url),
|
||||
token=cast(str, settings.ha_token),
|
||||
)
|
||||
)
|
||||
app.state.ha_reader = HaReader(client=client)
|
||||
yield
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if client is not None:
|
||||
client.close()
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
"""Machine-Learning-Grundbausteine für SillyHome Next."""
|
||||
__all__ = ["FeatureStore", "FeatureVector"]
|
||||
__all__ = ["FeatureStore", "FeatureVector", "TrainedArtifact", "TrainingPipeline"]
|
||||
from app.ml.feature_store import FeatureStore, FeatureVector
|
||||
from app.ml.training import TrainedArtifact, TrainingPipeline
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Iterable
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -28,4 +28,4 @@ class FeatureStore:
|
||||
return series[-1] if series else None
|
||||
|
||||
def all(self) -> list[FeatureVector]:
|
||||
return [vector for vectors in self._vectors.values() for vector in vectors]
|
||||
return [vector for vectors in self._vectors.values() for vector in vectors]
|
||||
|
||||
@@ -23,8 +23,8 @@ class ModelRegistry:
|
||||
|
||||
def register(self, artifact: TrainedArtifact) -> TrainedArtifact:
|
||||
self._validate_artifact_id(artifact.artifact_id)
|
||||
self._artifacts[artifact.artifact_id] = artifact
|
||||
self._persist(artifact)
|
||||
self._artifacts[artifact.artifact_id] = artifact
|
||||
return artifact
|
||||
|
||||
def load_artifact(self, artifact_id: str) -> TrainedArtifact:
|
||||
|
||||
@@ -2,9 +2,8 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Sequence
|
||||
|
||||
from app.ml.feature_store import FeatureVector, FeatureStore
|
||||
from app.ml.feature_store import FeatureStore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -34,4 +33,4 @@ class TrainingPipeline:
|
||||
def export(self, artifact_id: str) -> TrainedArtifact:
|
||||
if artifact_id not in self._artifacts:
|
||||
raise KeyError(f"Artifact '{artifact_id}' nicht gefunden.")
|
||||
return self._artifacts[artifact_id]
|
||||
return self._artifacts[artifact_id]
|
||||
|
||||
Reference in New Issue
Block a user