M8-R02A: reconcile DSMR runtime after source CRUD
This commit is contained in:
@@ -9,6 +9,7 @@ from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.routes.api.deps import require_csrf, require_session
|
||||
from app.config import get_settings
|
||||
from app.dependencies import get_db
|
||||
from app.integrations.meter_sources import SourceProfileError, list_source_profiles, sanitize_source_config
|
||||
from app.models.energy import DsmrReading, Meter
|
||||
@@ -23,6 +24,8 @@ from app.schemas.meter_source import (
|
||||
SourceProfileResponse, SourceProfilesResponse,
|
||||
)
|
||||
from app.services.auth import AuthenticatedSession
|
||||
from app.services.config_page import build_runtime_settings
|
||||
from app.services.dsmr_ingest import apply_dsmr_subscription
|
||||
from app.services.meter_sources import (
|
||||
BindingNotFoundError, ChannelNotFoundError, MeterNotFoundError,
|
||||
MeterSourceError, SourceDeleteRestrictedError, SourceNotFoundError, create_binding,
|
||||
@@ -34,14 +37,24 @@ from app.services.warmtelink_worker import warmtelink_worker_manager
|
||||
router = APIRouter(prefix="/api/energy", tags=["api-energy-meter-sources"])
|
||||
|
||||
|
||||
def _reconcile_warmtelink_after_commit() -> None:
|
||||
"""Runtime convergence is best-effort; the already committed API result wins."""
|
||||
def _reconcile_runtimes_after_commit(db: Session) -> None:
|
||||
"""Best-effort runtime convergence after a durable source CRUD commit."""
|
||||
try:
|
||||
warmtelink_worker_manager.reconcile()
|
||||
except Exception:
|
||||
# The manager records individual source failures itself. Do not turn a
|
||||
# successful durable create/update/delete into a misleading HTTP 500.
|
||||
return
|
||||
pass
|
||||
try:
|
||||
apply_dsmr_subscription(build_runtime_settings(db, get_settings()))
|
||||
except Exception:
|
||||
# DSMR owns independent source clients. Its failure must neither undo
|
||||
# durable CRUD nor prevent the WarmteLink manager from converging.
|
||||
pass
|
||||
finally:
|
||||
# DSMR health callbacks use short independent sessions. Make a CRUD
|
||||
# response observe any durable status change they just committed.
|
||||
db.expire_all()
|
||||
|
||||
|
||||
def _as_utc(value: datetime) -> datetime:
|
||||
@@ -130,9 +143,8 @@ def post_source(body: MeterSourceCreate, db: Session = Depends(get_db),
|
||||
try:
|
||||
source = create_source(db, name=body.name, kind=body.kind, config=body.config, enabled=body.enabled)
|
||||
db.commit()
|
||||
db.refresh(source)
|
||||
_reconcile_warmtelink_after_commit()
|
||||
return _source_response(source)
|
||||
_reconcile_runtimes_after_commit(db)
|
||||
return _source_response(_source_or_404(db, source.uuid))
|
||||
except (SourceProfileError, MeterSourceError) as exc:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
@@ -151,9 +163,8 @@ def patch_source(source_uuid: str, body: MeterSourcePatch, db: Session = Depends
|
||||
try:
|
||||
updated = update_source(db, source.id, name=body.name, enabled=body.enabled, config_patch=body.config)
|
||||
db.commit()
|
||||
db.refresh(updated)
|
||||
_reconcile_warmtelink_after_commit()
|
||||
return _source_response(updated)
|
||||
_reconcile_runtimes_after_commit(db)
|
||||
return _source_response(_source_or_404(db, updated.uuid))
|
||||
except (SourceProfileError, MeterSourceError) as exc:
|
||||
db.rollback()
|
||||
raise _binding_error(exc) from exc
|
||||
@@ -171,7 +182,7 @@ def remove_source(source_uuid: str, db: Session = Depends(get_db),
|
||||
try:
|
||||
delete_source(db, source.id)
|
||||
db.commit()
|
||||
_reconcile_warmtelink_after_commit()
|
||||
_reconcile_runtimes_after_commit(db)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
except SourceDeleteRestrictedError as exc:
|
||||
db.rollback()
|
||||
|
||||
Reference in New Issue
Block a user