Source code for aleph.toolkit.partitions

"""Helpers for monthly RANGE-partitioned tables on a TIMESTAMPTZ column.

Used by:
* the alembic migration that creates crn_metrics / ccn_metrics
* the metrics_partition cron job that maintains them (create next month,
  drop past-cutoff)

Keeping the naming and bounds logic in one place means migration-time
partitions and cron-created partitions follow identical conventions.
"""

import datetime as dt
from typing import List, Tuple


[docs] def month_floor(d: dt.datetime) -> dt.datetime: """First instant of d's month, UTC.""" return dt.datetime(d.year, d.month, 1, tzinfo=dt.timezone.utc)
[docs] def add_months(d: dt.datetime, months: int) -> dt.datetime: """Shift d by N calendar months (positive or negative). Snaps to the first day of the resulting month.""" total = d.month - 1 + months year = d.year + total // 12 month = total % 12 + 1 return dt.datetime(year, month, 1, tzinfo=dt.timezone.utc)
[docs] def monthly_bounds( start: dt.datetime, end_exclusive: dt.datetime ) -> List[Tuple[dt.datetime, dt.datetime]]: """List of [lower, upper) month-aligned ranges from start to end_exclusive. Both arguments should already be at month boundaries.""" bounds: List[Tuple[dt.datetime, dt.datetime]] = [] cursor = start while cursor < end_exclusive: upper = add_months(cursor, 1) bounds.append((cursor, upper)) cursor = upper return bounds
[docs] def partition_name(table: str, lower: dt.datetime) -> str: """Naming convention: <table>_YYYY_MM, e.g. crn_metrics_2026_05.""" return f"{table}_{lower.strftime('%Y_%m')}"
[docs] def ts_literal(d: dt.datetime) -> str: """TIMESTAMPTZ literal suitable for inlining into DDL.""" return d.strftime("%Y-%m-%d %H:%M:%S%z")