diff --git a/harness/tests/test_dwh_port_contract.py b/harness/tests/test_dwh_port_contract.py new file mode 100644 index 00000000..0cb64756 --- /dev/null +++ b/harness/tests/test_dwh_port_contract.py @@ -0,0 +1,47 @@ +from tht.execute import ExecResult, PlanSummary +from tht.mschema.models import PhysicalSchema +from tht.ports.dwh import ( + DwhAdapter, + DwhCapabilities, + DwhHealth, + UnsupportedCapability, +) + + +class FakeDwhAdapter: + capabilities = DwhCapabilities() + + def health(self) -> DwhHealth: + return DwhHealth(ok=True) + + def introspect(self) -> PhysicalSchema: + raise NotImplementedError + + def run_query(self, sql: str, *, limit: int | None = None) -> ExecResult: + raise NotImplementedError + + def explain(self, sql: str) -> PlanSummary: + raise NotImplementedError + + def sample_column(self, table: str, column: str, *, limit: int) -> list[object]: + raise NotImplementedError + + def distinct_values(self, table: str, column: str) -> list[object]: + raise NotImplementedError + + +def test_fake_adapter_satisfies_runtime_protocol(): + adapter = FakeDwhAdapter() + + assert isinstance(adapter, DwhAdapter) + assert adapter.capabilities.explain is True + assert adapter.health().ok is True + + +def test_contract_types_are_public_and_capabilities_are_immutable(): + capabilities = DwhCapabilities() + + assert capabilities.introspection is True + assert capabilities.sampling is True + assert capabilities.distinct_values is True + assert issubclass(UnsupportedCapability, Exception) diff --git a/harness/tht/ports/__init__.py b/harness/tht/ports/__init__.py new file mode 100644 index 00000000..ea5e7407 --- /dev/null +++ b/harness/tht/ports/__init__.py @@ -0,0 +1,15 @@ +"""Stable interfaces implemented by Thoth infrastructure adapters.""" + +from tht.ports.dwh import ( + DwhAdapter, + DwhCapabilities, + DwhHealth, + UnsupportedCapability, +) + +__all__ = [ + "DwhAdapter", + "DwhCapabilities", + "DwhHealth", + "UnsupportedCapability", +] diff --git a/harness/tht/ports/dwh.py b/harness/tht/ports/dwh.py new file mode 100644 index 00000000..c92181ed --- /dev/null +++ b/harness/tht/ports/dwh.py @@ -0,0 +1,43 @@ +"""Data-warehouse adapter contract.""" + +from dataclasses import dataclass +from typing import Protocol, runtime_checkable + +from tht.execute import ExecResult, PlanSummary +from tht.mschema.models import PhysicalSchema + + +@dataclass(frozen=True) +class DwhCapabilities: + introspection: bool = True + explain: bool = True + sampling: bool = True + distinct_values: bool = True + + +@dataclass(frozen=True) +class DwhHealth: + ok: bool + detail: str | None = None + + +class UnsupportedCapability(Exception): + """Raised when an adapter cannot provide an optional DWH operation.""" + + +@runtime_checkable +class DwhAdapter(Protocol): + @property + def capabilities(self) -> DwhCapabilities: ... + + def health(self) -> DwhHealth: ... + + def introspect(self) -> PhysicalSchema: ... + + def run_query(self, sql: str, *, limit: int | None = None) -> ExecResult: ... + + def explain(self, sql: str) -> PlanSummary: ... + + def sample_column(self, table: str, column: str, *, limit: int) -> list[object]: ... + + def distinct_values(self, table: str, column: str) -> list[object]: ...