Skip to content

Session

session

Persistent, bounded decoded-cube reuse for successive trajectory queries.

WeatherSession

Reuse decoded weather across successive point queries, without extrapolation.

source supplies a fixed set of fields. Optional fields selects a subset through its select_fields capability (currently GridSource); unsupported sources must be configured with their own field selection. vertical is required: choose "linear" or "log" in pressure.

The LRU budget bounds retained array payload, including coordinates, not Python overhead or temporary cubes held by the active enrichment call. Calls on one session are serialized; its normal source I/O stays parallel. Treat a session as a snapshot: call :meth:clear to refresh decoded mutable forecasts or ERA5T. The existing on-disk byte cache retains its own rules. Do not mutate the source configuration while using the session.

Source code in packages/barograph-met/src/baro/met/session.py
class WeatherSession:
    """Reuse decoded weather across successive point queries, without extrapolation.

    ``source`` supplies a fixed set of fields. Optional ``fields`` selects a
    subset through its ``select_fields`` capability (currently ``GridSource``);
    unsupported sources must be configured with their own field selection.
    ``vertical`` is required: choose ``"linear"`` or ``"log"`` in pressure.

    The LRU budget bounds retained array payload, including coordinates, not
    Python overhead or temporary cubes held by the active enrichment call.
    Calls on one session are serialized; its normal source I/O stays parallel.
    Treat a session as a snapshot: call :meth:`clear` to refresh decoded mutable
    forecasts or ERA5T. The existing on-disk byte cache retains its own rules.
    Do not mutate the source configuration while using the session.
    """

    def __init__(  # noqa: PLR0913 - explicit public sampling configuration
        self,
        source: Source,
        *,
        vertical: VerticalMethod,
        fields: Sequence[str] | None = None,
        max_cache_bytes: int = 256 * 1024 * 1024,
        cache_dir: str | Path | None = None,
        domain: Domain | None = None,
        margin_deg: float = 0.5,
        level_margin: int = 1,
        io_workers: int = 16,
        prefetch: int = 2,
        surface_pressure: SurfacePressureMode = "actual",
        on_uncovered: Literal["warn", "raise", "flag"] = "raise",
    ) -> None:
        if not isinstance(max_cache_bytes, int) or max_cache_bytes < 0:
            raise ValueError("max_cache_bytes must be a nonnegative integer")
        if vertical not in ("linear", "log"):
            raise ValueError("vertical must be 'linear' or 'log'")
        if fields is not None:
            select = getattr(source, "select_fields", None)
            if select is None:
                raise TypeError("source does not support select_fields; configure its fields first")
            source = select(fields)
        self._source = _DecodedSource(source, max_cache_bytes)
        self._lock = threading.Lock()
        self._enricher = Enricher(
            self._source,
            cache_dir=cache_dir,
            domain=domain,
            margin_deg=margin_deg,
            level_margin=level_margin,
            io_workers=io_workers,
            prefetch=prefetch,
            vertical=vertical,
            surface_pressure=surface_pressure,
            on_uncovered=on_uncovered,
        )

    def sample(
        self, time_utc: ArrayLike, lat: ArrayLike, lon: ArrayLike, *, pressure_pa: ArrayLike
    ) -> EnrichResult:
        """Sample 1-D points using nanosecond time and explicit pressure in Pa.

        Missing coverage retains Enricher flags; callers must check ``valid``
        before interpreting missing meteorology as a physical disappearance.
        Time interpolation divides integer timedelta differences, never floating
        epoch hours, preserving subsecond weights at modern dates.
        """
        with self._lock:
            before = self.cache_info()
            result = self._enricher.enrich(time_utc, lat, lon, pressure_pa=pressure_pa)
            after = self.cache_info()
            result.stats.update(
                decoded_cache_hits=after["hits"] - before["hits"],
                decoded_cache_misses=after["misses"] - before["misses"],
                decoded_cache_bytes=after["bytes"],
            )
            return result

    def cache_info(self) -> dict[str, int]:
        """Retained array bytes, entry count and lifetime LRU counters."""
        with self._source.lock:
            return {
                "bytes": self._source.bytes,
                "entries": len(self._source.entries),
                "hits": self._source.hits,
                "misses": self._source.misses,
                "evictions": self._source.evictions,
                "max_bytes": self._source.budget,
            }

    def clear(self) -> None:
        """Release decoded arrays after any active call; preserve lifetime counters."""
        with self._lock, self._source.lock:
            self._source.entries.clear()
            self._source.bytes = 0
cache_info()

Retained array bytes, entry count and lifetime LRU counters.

Source code in packages/barograph-met/src/baro/met/session.py
def cache_info(self) -> dict[str, int]:
    """Retained array bytes, entry count and lifetime LRU counters."""
    with self._source.lock:
        return {
            "bytes": self._source.bytes,
            "entries": len(self._source.entries),
            "hits": self._source.hits,
            "misses": self._source.misses,
            "evictions": self._source.evictions,
            "max_bytes": self._source.budget,
        }
clear()

Release decoded arrays after any active call; preserve lifetime counters.

Source code in packages/barograph-met/src/baro/met/session.py
def clear(self) -> None:
    """Release decoded arrays after any active call; preserve lifetime counters."""
    with self._lock, self._source.lock:
        self._source.entries.clear()
        self._source.bytes = 0
sample(time_utc, lat, lon, *, pressure_pa)

Sample 1-D points using nanosecond time and explicit pressure in Pa.

Missing coverage retains Enricher flags; callers must check valid before interpreting missing meteorology as a physical disappearance. Time interpolation divides integer timedelta differences, never floating epoch hours, preserving subsecond weights at modern dates.

Source code in packages/barograph-met/src/baro/met/session.py
def sample(
    self, time_utc: ArrayLike, lat: ArrayLike, lon: ArrayLike, *, pressure_pa: ArrayLike
) -> EnrichResult:
    """Sample 1-D points using nanosecond time and explicit pressure in Pa.

    Missing coverage retains Enricher flags; callers must check ``valid``
    before interpreting missing meteorology as a physical disappearance.
    Time interpolation divides integer timedelta differences, never floating
    epoch hours, preserving subsecond weights at modern dates.
    """
    with self._lock:
        before = self.cache_info()
        result = self._enricher.enrich(time_utc, lat, lon, pressure_pa=pressure_pa)
        after = self.cache_info()
        result.stats.update(
            decoded_cache_hits=after["hits"] - before["hits"],
            decoded_cache_misses=after["misses"] - before["misses"],
            decoded_cache_bytes=after["bytes"],
        )
        return result