Reference
Public API
- class timedb.TimeDBClient(ch_url: str | None = None)[source]
Bases:
object- read(*, series_ids: Sequence[int], retention: str | Sequence[str] | None = None, start_valid: datetime | None = None, end_valid: datetime | None = None, start_known: datetime | None = None, end_known: datetime | None = None, include_updates: bool = False, include_knowledge_time: bool = False, meta_source: PgEngineMeta | None = None) DataFrame[source]
Read values for
series_ids, returning a Polars DataFrame.By default this collapses to the latest value per
valid_time, the row with the largest(knowledge_time, change_time), and returnsseries_id, valid_time, value. Two flags widen it:include_knowledge_time=True: one row per(knowledge_time, valid_time), every forecast run side by side, addingknowledge_time.include_updates=True: the full correction chain on the winning run, addingchange_time,changed_byandannotation.
Setting both returns the complete 3-dimensional audit log.
retentionaccepts one tier or a sequence of tiers and prunes whole partitions.start_valid/end_validboundvalid_time;start_known/end_knownboundknowledge_time. All datetimes must be timezone-aware.meta_sourcetakes aPgEngineMetato have ClickHouse resolve the series set itself through a PostgreSQL engine table instead of receiving an explicit id array. energydb’s concurrent read path uses it.
- read_relative(*, series_ids: Sequence[int], retention: str | Sequence[str] | None = None, window_length: timedelta | None = None, issue_offset: timedelta | None = None, start_window: datetime | None = None, start_valid: datetime | None = None, end_valid: datetime | None = None, days_ahead: int | None = None, time_of_day: time | None = None, meta_source: PgEngineMeta | None = None) DataFrame[source]
Per-window cutoff read: for each window, the latest forecast issued at or before that window’s cutoff.
This is the “what forecast was available at decision time” read that backtests and day-ahead simulations need. Returns
series_id, valid_time, value.Two mutually exclusive parameter sets address the windows; mixing them raises
ValueError:Low-level:
window_lengthplusissue_offset(relative to each window start) andstart_window.Daily shorthand:
days_aheadplustime_of_day, giving fixed 1-day windows with a human-friendly cutoff.
start_valid/end_validbound the returned range,retentionprunes partitions, andmeta_sourcebehaves as inread(). All datetimes must be timezone-aware.
- read_run_series(*, series_id: int) list[int][source]
Return run_ids that touched a given series_id, latest first.
Data only: the
energydb.runsPG table hydrates the metadata.
- write(df: DataFrame | DataFrame, *, retention: str | None = None, knowledge_time: datetime | None = None, skip_unchanged: bool = False, unchanged_scope: Literal['valid_time', 'knowledge_time', 'auto'] = 'valid_time', knowledge_time_scoped_series: Collection[int] | None = None) WriteResult[source]
Write time-series rows into
series_valuesand theirrun_seriesmapping.dfmay be a Pandas or Polars frame. Required columns:series_id,valid_time,value. Optional columns get a per-batch default when absent:knowledge_time(this kwarg, elsedatetime.now(UTC)),change_time(now(UTC)),run_id(one client-generated UUID7 truncated to 63 bits),valid_time_end(the2200-01-01sentinel), andchanged_by/annotation(empty strings). Every timestamp column must be timezone-aware; naive values raiseValueError.retentionandknowledge_timemay be given as a kwarg or a column, never both.retentiondefaults to"forever"(no TTL); seeRETENTION_TIERSfor the valid tiers.With
skip_unchanged=True, rows whose latest stored(value, annotation, changed_by)already matches are dropped before the insert, at the cost of one bounded read-back.unchanged_scopepicks the comparison key:"valid_time"(default),"knowledge_time", or"auto", which applies the knowledge-time key to the ids inknowledge_time_scoped_seriesand the valid-time key to every other series. Any other scope paired withknowledge_time_scoped_seriesraises.Returns a
WriteResult: aNamedTuple(written, skipped)of row counts.
- timedb.RETENTION_TIERS = frozenset({'forever', 'long', 'medium', 'short'})
frozenset() -> empty frozenset object frozenset(iterable) -> frozenset object
Build an immutable unordered collection of unique elements.
- class timedb.WriteResult(written: int, skipped: int)[source]
Bases:
NamedTupleCounts returned by
write().skippedis always 0 unlessskip_unchangedwas set.
- timedb.UnchangedScope
The comparison key for
write(skip_unchanged=True):"valid_time","knowledge_time", or"auto"(per-series, driven byknowledge_time_scoped_series). alias ofLiteral[‘valid_time’, ‘knowledge_time’, ‘auto’]
- class timedb.PgEngineMeta(table: str, root_path: str | None = None, paths: tuple[str, ...] | None = None, node_uuids: tuple[str, ...] | None = None, edge_uuids: tuple[str, ...] | None = None, edge_triple: tuple[str, str, str] | None = None, edge_triples: tuple[tuple[str, str, str], ...] | None = None, data_type: str | tuple[str, ...] | None = None, name: str | tuple[str, ...] | None = None)[source]
Bases:
objectResolve the series_id set inside ClickHouse via a PostgreSQL engine table over the
series_metaview, instead of the caller passing an explicit id array. The read then filtersseries_idandretentionby subqueries over thismetaCTE rather than array parameters.Exactly one addressing field must be set:
root_path: a node subtree (the root itself + descendants, path-prefix match);paths: an exact set of node paths (path-routed manifests);node_uuids/edge_uuids: owner-uuid sets (uuid-routed manifests, edge scopes);edge_triple: one(from_path, to_path, edge_type)edge identity (edge scopes);edge_triples: a set of(from_path, to_path, edge_type)identities (triple-routed manifests), pushed down as three single-columnINfilters. Like the set-valueddata_type/namebelow, this resolves a cartesian superset of the requested triples; the caller trims against its exactly-resolved meta.
data_type/namenarrow the series set; each accepts a scalar (scope reads) or a set of values (manifests). Set-valued filters make the engine-resolved ids a superset (the cartesian of the sets). Every predicate here pushes down to PG as a single-column comparison, and the caller is expected to trim against its exactly-resolved meta.
Profiling helpers
A lightweight phase-timer used by the read/write paths. Useful when diagnosing slow queries or large bulk inserts.
Opt-in per-phase timing collector for TimeDB internal operations.
Disabled by default: zero overhead when disabled (no perf_counter calls,
no function calls in the hot path). Benchmark scripts activate it per-trial
to collect phase-level timing breakdowns.
Not thread-safe; designed for single-threaded benchmark use.
Usage:
from timedb import profiling
profiling.enable()
profiling.reset()
# ... run operation ...
phases = profiling.collect() # dict of phase -> elapsed seconds
profiling.disable()
Or, for hot-path instrumentation:
with profiling._phase(profiling.PHASE_EDB_RESOLVE):
...