Source code for parq_blockmodel.visualization.asset_selector

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, Mapping


[docs] @dataclass(frozen=True, slots=True) class PbmAsset: path: Path name: str levels: tuple[tuple[str, str], ...] @property def level_map(self) -> dict[str, str]: return dict(self.levels)
[docs] class HivePbmCatalog: """Read-only hive-style PBM asset catalog."""
[docs] def __init__(self, assets: Iterable[PbmAsset]) -> None: normalized_assets = [ PbmAsset( path=asset.path.resolve(), name=asset.name, levels=tuple((str(k), str(v)) for k, v in asset.levels), ) for asset in assets ] if not normalized_assets: raise ValueError("HivePbmCatalog requires at least one PBM asset.") self.assets = tuple( sorted( normalized_assets, key=lambda asset: ( tuple(asset.levels), asset.name, str(asset.path), ), ) ) seen_keys: list[str] = [] for asset in self.assets: for key, _ in asset.levels: if key not in seen_keys: seen_keys.append(key) self.level_keys = tuple(seen_keys) self._assets_by_path = {str(asset.path): asset for asset in self.assets}
@classmethod def discover(cls, root_path: str | Path) -> "HivePbmCatalog": root = Path(root_path).resolve() assets: list[PbmAsset] = [] for pbm_path in sorted(root.rglob("*.pbm")): relative_parent = pbm_path.parent.relative_to(root) levels: list[tuple[str, str]] = [] for part in relative_parent.parts: if "=" not in part: continue key, value = part.split("=", 1) if not key or not value: continue levels.append((key, value)) assets.append(PbmAsset(path=pbm_path, name=pbm_path.stem, levels=tuple(levels))) if not assets: raise FileNotFoundError(f"No .pbm files found under: {root}") return cls(assets) def find_by_path(self, pbm_path: str | Path) -> PbmAsset | None: resolved = Path(pbm_path).resolve() return self._assets_by_path.get(str(resolved)) def level_options(self, key: str, selections: Mapping[str, str] | None = None) -> list[str]: if key not in self.level_keys: return [] level_index = self.level_keys.index(key) constrained_keys = self.level_keys[:level_index] filtered_assets = self._filter_assets(selections=selections, keys=constrained_keys) values = [] for asset in filtered_assets: level_map = asset.level_map if key in level_map: values.append(level_map[key]) return sorted(set(values)) def pbm_name_options(self, selections: Mapping[str, str] | None = None) -> list[str]: names = [asset.name for asset in self._filter_assets(selections=selections)] return sorted(set(names)) def select_asset(self, selections: Mapping[str, str], name: str) -> PbmAsset: candidates = [ asset for asset in self._filter_assets(selections=selections) if asset.name == name ] if not candidates: raise LookupError( "No PBM asset matches the provided hive selection and name " f"(name={name}, selections={dict(selections)})." ) if len(candidates) > 1: paths = ", ".join(str(asset.path) for asset in candidates) raise LookupError( f"PBM name '{name}' is not unique for selections {dict(selections)}. " f"Candidates: {paths}" ) return candidates[0] def _filter_assets( self, *, selections: Mapping[str, str] | None = None, keys: tuple[str, ...] | None = None, ) -> list[PbmAsset]: if not selections: return list(self.assets) constrained_keys = keys if keys is not None else tuple(selections.keys()) filtered: list[PbmAsset] = [] for asset in self.assets: level_map = asset.level_map is_match = True for key in constrained_keys: expected = selections.get(key) if expected in (None, ""): continue if level_map.get(key) != expected: is_match = False break if is_match: filtered.append(asset) return filtered