Source code for ursa.store.config

"""Pydantic config models for the Ursa object-store layer.

A YAML config maps named roles (`default`, `raw`, `catalog`,
`polaris_cache`, ...) to backend configs. M1 ships R2 + Local; S3 / GCS
land as additive discriminator values once they have a real backend
implementation.

Credentials never appear in YAML. The `r2` backend resolves credentials
at construction time via `constellation_utils.secrets.r2_*()`. The YAML
selects *which* of those four accessors to call via the `creds` field;
the bucket name rides along on the resolved `R2Secrets` object so the
config never names buckets either — the cred selector IS the bucket
selector.
"""

from __future__ import annotations

from pathlib import Path
from typing import Annotated, Literal

from pydantic import BaseModel, ConfigDict, Field

__all__ = [
    "R2StoreConfig",
    "LocalStoreConfig",
    "ObjectStoreConfig",
    "UrsaConfig",
    "R2Creds",
]


# The four credential accessors exposed by constellation_utils.secrets.
# Each one resolves to a 1Password item that is bucket-scoped, so the
# choice of cred name fully determines which bucket the store reads/writes.
R2Creds = Literal["raw_rw", "raw_ro", "assets_rw", "assets_ro"]


[docs] class _Base(BaseModel): model_config = ConfigDict(frozen=True, extra="forbid")
[docs] class R2StoreConfig(_Base): """Cloudflare R2 backend, S3-protocol via obstore. `creds` selects the constellation-utils accessor (and therefore the bucket — `*_rw` accessors return RW credentials, `*_ro` return RO). The bucket name comes from the resolved credential object; we never repeat it in YAML. Mapping (see `docs/source/architecture.md`): - `assets_rw` -> `constellation-assets[-test]` RW (Ursa-managed data) - `assets_ro` -> `constellation-assets[-test]` RO (downstream readers) - `raw_rw` -> `constellation-data[-test]` RW (data-engine uploader) - `raw_ro` -> `constellation-data[-test]` RO (Ursa reading raw) """ backend: Literal["r2"] creds: R2Creds prefix: str = ""
[docs] class LocalStoreConfig(_Base): """Local filesystem backend backed by `obstore.store.LocalStore`. `root` is combined with `prefix` and resolved on construction; the `LocalStore` then confines all keys under that directory. """ backend: Literal["local"] root: Path prefix: str = ""
ObjectStoreConfig = Annotated[ R2StoreConfig | LocalStoreConfig, Field(discriminator="backend"), ]
[docs] class UrsaConfig(_Base): """Top-level config. `stores` maps role names to backend configs. `get_store(role)` defaults `role` to `"default"`; configs that omit a `default` entry must pass `role=` explicitly to `get_store()`. """ stores: dict[str, ObjectStoreConfig]