models.monitor¶
Pydantic boundary models for the monitor subsystem.
Two seams:
MetricPoint— the in-memory series element (replaces the old(ts, value, meta)3-tuple inMetricStore.series). It is anOttoModel(extra='forbid') because otto is the only thing that builds it: the live append path usesmodel_construct(no validation, hot loop) and the import path usesmodel_validate.MetricRecord/EventRecord/LogEventRecord— flat records at the JSON--fileand SQLite import/export boundary. These read historical, external data, so they are deliberately lenient (extra='ignore', viaRowModel): an unknown column from a newer schema is dropped, not rejected, exactly as the old.get()/[]parsing did. Field names follow the JSON spelling; avalidation_aliasalso accepts the SQLite column spelling (ts/end_ts) so one model validates both seams.
Leaf isolation: this module imports only otto.models.base, pydantic, and
the stdlib — no runtime or otto.monitor edge — so it stays a pure leaf inside
the models package.
-
class otto.models.monitor.MetricPoint(*, ts: datetime, value: float, meta: dict[str, Any] | None =
None)¶ Bases:
OttoModelA single charted sample: timestamp, numeric value, optional hover meta.
Replaces the
(datetime, float, dict | None)tuple stored per series. Consumers read.ts/.value/.metainstead of unpacking.
-
class otto.models.monitor.ChartSpec(*, label: str, y_title: str, unit: str, command: str, chart: str, interval: float | None =
None)¶ Bases:
OttoModelOne dashboard chart descriptor served by
/api/meta.The declarative contract the frontend renders from; TS types are generated from this schema in Phase 2.
-
class otto.models.monitor.TabSpec(*, id: str, label: str, metrics: list[str], kind: 'charts' | 'table' =
'charts', columns: list[str] | None =None)¶ Bases:
OttoModelOne dashboard tab descriptor served by
/api/meta.The declarative contract the frontend renders from; TS types are generated from this schema in Phase 2.
kind="table"tabs render an event table (schema incolumns) instead of charts, and carrymetrics=[].
-
class otto.models.monitor.MonitorMeta(*, hosts: list[str], live: bool, metrics: list[ChartSpec], tabs: list[TabSpec], interval: float | None =
None)¶ Bases:
OttoModelThe typed
/api/metapayload: hosts, chart specs, and tab layout.The declarative contract the frontend renders from; TS types are generated from this schema in Phase 2.
intervalis the global collection interval in seconds —Noneuntilrun()has recorded one (a collector that has not started live collection), or always for historical (loaded from--file/--db) data.
- class otto.models.monitor.RowModel¶
Bases:
BaseModelLenient base for historical-data import/export rows.
Unlike
OttoModel(extra='forbid', which exists to turn a config typo into an error), data read-back is tolerant: an unexpected key/column from a newer schema is ignored rather than rejected. This matches the pre-pydantic.get()/[]parsing and keeps older otto builds able to import exports written by newer ones.
-
class otto.models.monitor.MetricRecord(*, timestamp: datetime, host: str =
'', label: str, value: float, meta: dict[str, Any] | None =None)¶ Bases:
RowModelOne
metricsrow at the JSON / SQLite import-export boundary.The JSON
--fileformat spells the time keytimestamp; the SQLitemetricstable column ists. Thevalidation_aliasaccepts both, so a single model validates either seam.hostis optional for the pre-host-column schema;metarides only in JSON (the DB has no meta column). Exporting withmodel_dump(mode='json', exclude_none=True)emits the JSON spelling and omitsmetawhenNone(host=''is still emitted — empty string is notNone).
-
class otto.models.monitor.EventRecord(*, id: int | None =
None, timestamp: datetime, end_timestamp: datetime | None =None, label: str ='', source: str ='manual', color: str ='#888888', dash: str ='dash')¶ Bases:
RowModelOne
eventsrow at the JSON / SQLite import boundary.Mirrors the
MonitorEventfields. Used to validate external event data before constructing the (unchanged, mutable)MonitorEventdataclass — event export staysMonitorEvent.to_dict().timestampis required (a row without one is skipped, as before); everything else defaults.idisNonewhen absent so the collector can assign its running id.
- class otto.models.monitor.LogEventRecord(*, timestamp: ~datetime.datetime, host: str = '', tab: str = '', fields: dict[str, str] = <factory>)¶
Bases:
RowModelOne
log_eventsrow at the JSON / SQLite import-export boundary.Mirrors the parser-emitted
LogEventplus the host/tab the collector attaches. The JSON--fileformat spells the time keytimestamp; the SQLite column ists(itsfieldscolumn is JSON-decoded by the loader before validation).