feat: add temporal context and contextual help
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import or_
|
||||
|
||||
from govoplan_core.core.temporal import (
|
||||
TemporalContextError,
|
||||
TemporalDataContext,
|
||||
current_temporal_data_context,
|
||||
)
|
||||
|
||||
|
||||
def apply_temporal_revision_filter(
|
||||
query: Any,
|
||||
model: type[Any],
|
||||
*,
|
||||
context: TemporalDataContext | None = None,
|
||||
valid_from: str | None = "valid_from",
|
||||
valid_to: str | None = "valid_to",
|
||||
recorded_at: str | None = "recorded_at",
|
||||
superseded_at: str | None = "superseded_at",
|
||||
) -> Any:
|
||||
"""Apply latest/as-recorded and valid-time clauses to a revision query."""
|
||||
|
||||
resolved = context or current_temporal_data_context()
|
||||
clauses: list[Any] = []
|
||||
|
||||
superseded_column = _optional_column(model, superseded_at)
|
||||
recorded_column = _optional_column(model, recorded_at)
|
||||
if resolved.recorded_at is None:
|
||||
if superseded_column is not None:
|
||||
clauses.append(superseded_column.is_(None))
|
||||
else:
|
||||
if recorded_column is None or superseded_column is None:
|
||||
raise TemporalContextError(
|
||||
f"{model.__name__} does not expose recorded/superseded revision time."
|
||||
)
|
||||
clauses.extend(
|
||||
(
|
||||
recorded_column <= resolved.recorded_at,
|
||||
or_(
|
||||
superseded_column.is_(None),
|
||||
superseded_column > resolved.recorded_at,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
instant = resolved.validity_instant
|
||||
if instant is not None:
|
||||
valid_from_column = _optional_column(model, valid_from)
|
||||
valid_to_column = _optional_column(model, valid_to)
|
||||
if valid_from_column is not None:
|
||||
clauses.append(
|
||||
or_(valid_from_column.is_(None), valid_from_column <= instant)
|
||||
)
|
||||
if valid_to_column is not None:
|
||||
clauses.append(or_(valid_to_column.is_(None), valid_to_column > instant))
|
||||
|
||||
return query.filter(*clauses) if clauses else query
|
||||
|
||||
|
||||
def _optional_column(model: type[Any], name: str | None) -> Any | None:
|
||||
if name is None:
|
||||
return None
|
||||
column = getattr(model, name, None)
|
||||
if column is None:
|
||||
raise TemporalContextError(
|
||||
f"{model.__name__} has no temporal column named {name!r}."
|
||||
)
|
||||
return column
|
||||
|
||||
|
||||
__all__ = ["apply_temporal_revision_filter"]
|
||||
Reference in New Issue
Block a user