Source code for ezmsg.dashboard.backend.services.adapters

from __future__ import annotations

import dataclasses
import enum
from typing import Any, Mapping
from uuid import UUID

from ezmsg.core.graphmeta import (
    GraphSnapshot,
    ProcessProfilingSnapshot,
    ProfilingTraceStreamBatch,
    SettingsChangedEvent,
    SettingsSnapshotValue,
    TopologyChangedEvent,
)

from ..json_encoding import encode_float


def _json_safe(value: Any) -> Any:
    # Non-finite floats have no JSON literal; encode them before they reach
    # JSONResponse (which raises) or pydantic (which rewrites them to null).
    if isinstance(value, float):
        return encode_float(value)

    if value is None or isinstance(value, (str, int, bool)):
        return value

    if isinstance(value, UUID):
        return str(value)

    if isinstance(value, enum.Enum):
        return value.value

    if isinstance(value, bytes):
        # Keep the browser protocol free from pickled payloads.
        return None

    if dataclasses.is_dataclass(value):
        return {field.name: _json_safe(getattr(value, field.name)) for field in dataclasses.fields(value)}

    if isinstance(value, Mapping):
        return {str(_json_safe(k)): _json_safe(v) for k, v in value.items()}

    if isinstance(value, tuple) and hasattr(value, "_fields"):
        return {field_name: _json_safe(getattr(value, field_name)) for field_name in value._fields}

    if isinstance(value, (list, tuple, set)):
        return [_json_safe(item) for item in value]

    return value


[docs] def adapt_graph_snapshot(snapshot: GraphSnapshot) -> dict[str, Any]: edge_owners = [ { "edge": { "from_topic": edge.from_topic, "to_topic": edge.to_topic, }, "owner_session_ids": owners, } for edge, owners in snapshot.edge_owners.items() ] sessions = { session_id: { "edges": [ { "from_topic": edge.from_topic, "to_topic": edge.to_topic, } for edge in session.edges ], "metadata": _json_safe(session.metadata), } for session_id, session in snapshot.sessions.items() } processes = {str(process_id): _json_safe(process) for process_id, process in snapshot.processes.items()} return { "graph": _json_safe(snapshot.graph), "edge_owners": edge_owners, "sessions": sessions, "processes": processes, }
[docs] def adapt_settings_value(value: SettingsSnapshotValue) -> dict[str, Any]: return { "repr_value": _json_safe(value.repr_value), "structured_value": _json_safe(value.structured_value), "settings_schema": _json_safe(value.settings_schema), "serialized_present": value.serialized is not None, }
[docs] def adapt_settings_snapshot( snapshot: Mapping[str, SettingsSnapshotValue], ) -> dict[str, dict[str, Any]]: return {component_address: adapt_settings_value(value) for component_address, value in snapshot.items()}
[docs] def adapt_profiling_snapshot( snapshot: Mapping[UUID, ProcessProfilingSnapshot], ) -> dict[str, Any]: return {str(process_id): _json_safe(value) for process_id, value in snapshot.items()}
[docs] def adapt_topology_event(event: TopologyChangedEvent) -> dict[str, Any]: return { "seq": event.seq, "event_type": event.event_type.value, "timestamp": event.timestamp, "changed_topics": list(event.changed_topics), "source_session_id": event.source_session_id, "source_process_id": (str(event.source_process_id) if event.source_process_id is not None else None), }
[docs] def adapt_settings_event(event: SettingsChangedEvent) -> dict[str, Any]: return { "seq": event.seq, "event_type": event.event_type.value, "component_address": event.component_address, "timestamp": event.timestamp, "source_session_id": event.source_session_id, "source_process_id": (str(event.source_process_id) if event.source_process_id is not None else None), "value": adapt_settings_value(event.value), }
[docs] def adapt_profiling_trace_batch(batch: ProfilingTraceStreamBatch) -> dict[str, Any]: return { "timestamp": batch.timestamp, "batches": {str(process_id): _json_safe(data) for process_id, data in batch.batches.items()}, }