Skip to content

climate_ref.doctor #

Health checks for a Climate-REF deployment.

These look for the conditions that make a solve quietly do the wrong thing rather than fail. These include reference data that no diagnostic can reach, reference data that is missing so its diagnostics never run, and datasets whose files cover the same period twice.

diagnose examines a deployment and returns a DoctorReport, which is everything a caller needs to display one.

check declares a new check, a function taking a DoctorContext and returning Findings.

DoctorContext #

The deployment being checked.

Providers and catalogs are loaded lazily so a check that does not need them does not pay for them, and so a failure to load one provider does not stop the other checks.

Source code in packages/climate-ref/src/climate_ref/doctor/context.py
@define
class DoctorContext:
    """
    The deployment being checked.

    Providers and catalogs are loaded lazily so a check that does not need them does not pay for them,
    and so a failure to load one provider does not stop the other checks.
    """

    config: Config | None
    database: Database | None
    _providers: list[DiagnosticProvider] | None = field(default=None, alias="_providers")
    _catalogs: dict[SourceDatasetType, pd.DataFrame] = field(factory=dict, alias="_catalogs")

    @classmethod
    def from_catalogs(
        cls,
        catalogs: dict[SourceDatasetType, pd.DataFrame],
        providers: Iterable[DiagnosticProvider],
    ) -> "DoctorContext":
        """
        Build a context from catalogs already in hand, with no database behind it.

        Source types absent from ``catalogs`` are treated as having nothing ingested,
        so every check can run without reaching for a database that is not there.

        Parameters
        ----------
        catalogs
            The catalogs to check, keyed by source type.
        providers
            The providers to treat as enabled.

        Returns
        -------
        :
            A context backed by nothing but the supplied catalogs and providers.
        """
        complete = {
            source_type: catalogs.get(source_type, EMPTY_CATALOG) for source_type in SourceDatasetType
        }
        return cls(config=None, database=None, _providers=list(providers), _catalogs=complete)

    @property
    def providers(self) -> list[DiagnosticProvider]:
        """The diagnostic providers this deployment has enabled."""
        if self._providers is None:
            from climate_ref.provider_registry import ProviderRegistry  # noqa: PLC0415

            if self.config is None or self.database is None:
                raise ValueError("This context has no configuration to load providers from")
            registry = ProviderRegistry.build_from_config(
                self.config, self.database, configure=False, register=False
            )
            self._providers = list(registry.providers)
        return self._providers

    def catalog(self, source_type: SourceDatasetType) -> pd.DataFrame:
        """
        Load the ingested catalog for a source type, one row per file.

        Parameters
        ----------
        source_type
            The source type to load.

        Returns
        -------
        :
            The catalog, or an empty frame when nothing of that type has been ingested.
        """
        if source_type not in self._catalogs:
            if self.database is None:
                raise ValueError("This context has no database to load a catalog from")
            adapter = get_dataset_adapter(source_type.value)
            self._catalogs[source_type] = adapter.load_catalog(self.database)
        return self._catalogs[source_type]

providers property #

The diagnostic providers this deployment has enabled.

catalog(source_type) #

Load the ingested catalog for a source type, one row per file.

Parameters:

Name Type Description Default
source_type SourceDatasetType

The source type to load.

required

Returns:

Type Description
DataFrame

The catalog, or an empty frame when nothing of that type has been ingested.

Source code in packages/climate-ref/src/climate_ref/doctor/context.py
def catalog(self, source_type: SourceDatasetType) -> pd.DataFrame:
    """
    Load the ingested catalog for a source type, one row per file.

    Parameters
    ----------
    source_type
        The source type to load.

    Returns
    -------
    :
        The catalog, or an empty frame when nothing of that type has been ingested.
    """
    if source_type not in self._catalogs:
        if self.database is None:
            raise ValueError("This context has no database to load a catalog from")
        adapter = get_dataset_adapter(source_type.value)
        self._catalogs[source_type] = adapter.load_catalog(self.database)
    return self._catalogs[source_type]

from_catalogs(catalogs, providers) classmethod #

Build a context from catalogs already in hand, with no database behind it.

Source types absent from catalogs are treated as having nothing ingested, so every check can run without reaching for a database that is not there.

Parameters:

Name Type Description Default
catalogs dict[SourceDatasetType, DataFrame]

The catalogs to check, keyed by source type.

required
providers Iterable[DiagnosticProvider]

The providers to treat as enabled.

required

Returns:

Type Description
DoctorContext

A context backed by nothing but the supplied catalogs and providers.

Source code in packages/climate-ref/src/climate_ref/doctor/context.py
@classmethod
def from_catalogs(
    cls,
    catalogs: dict[SourceDatasetType, pd.DataFrame],
    providers: Iterable[DiagnosticProvider],
) -> "DoctorContext":
    """
    Build a context from catalogs already in hand, with no database behind it.

    Source types absent from ``catalogs`` are treated as having nothing ingested,
    so every check can run without reaching for a database that is not there.

    Parameters
    ----------
    catalogs
        The catalogs to check, keyed by source type.
    providers
        The providers to treat as enabled.

    Returns
    -------
    :
        A context backed by nothing but the supplied catalogs and providers.
    """
    complete = {
        source_type: catalogs.get(source_type, EMPTY_CATALOG) for source_type in SourceDatasetType
    }
    return cls(config=None, database=None, _providers=list(providers), _catalogs=complete)

DoctorReport #

What the checks found, and the deployment they ran against.

Source code in packages/climate-ref/src/climate_ref/doctor/report.py
@frozen
class DoctorReport:
    """
    What the checks found, and the deployment they ran against.
    """

    findings: tuple[Finding, ...]
    """Everything the checks found, worst first, then by the check that produced them."""

    check_count: int
    """How many checks ran, including those that found nothing."""

    environment: dict[str, dict[str, str]] | None = None
    """
    A description of the deployment as sections of ``name: value`` pairs, or ``None``.

    This is deliberately an untyped blob of data.
    It is used as context for a bug report and the shape may change at any time.

    Sensitive values have been redacted.
    """

    @property
    def worst_severity(self) -> Severity | None:
        """The most serious severity found, or ``None`` when nothing was found."""
        return worst_severity(self.findings)

check_count instance-attribute #

How many checks ran, including those that found nothing.

environment = None class-attribute instance-attribute #

A description of the deployment as sections of name: value pairs, or None.

This is deliberately an untyped blob of data. It is used as context for a bug report and the shape may change at any time.

Sensitive values have been redacted.

findings instance-attribute #

Everything the checks found, worst first, then by the check that produced them.

worst_severity property #

The most serious severity found, or None when nothing was found.

Finding #

One problem found by a check.

Source code in packages/climate-ref/src/climate_ref/doctor/findings.py
@frozen
class Finding:
    """
    One problem found by a check.
    """

    severity: Severity
    """How much it matters."""

    summary: str
    """One line stating what is wrong."""

    detail: str = ""
    """Optional further explanation of this finding alone."""

    remedy: str = ""
    """
    Optional instruction for fixing it.

    Findings that share a remedy are reported under it once rather than repeating it,
    so keep the wording free of anything specific to one finding.
    """

    command: str = ""
    """
    Optional command that carries out the remedy.

    Held apart from ``remedy`` so it can be printed unwrapped and stay pasteable.
    """

    check: str = ""
    """
    Slug of the check that produced it, e.g. ``duplicate-coverage``.

    A check does not set this itself.
    The runner stamps it from the check's registration, so the slug has one definition.
    """

check = '' class-attribute instance-attribute #

Slug of the check that produced it, e.g. duplicate-coverage.

A check does not set this itself. The runner stamps it from the check's registration, so the slug has one definition.

command = '' class-attribute instance-attribute #

Optional command that carries out the remedy.

Held apart from remedy so it can be printed unwrapped and stay pasteable.

detail = '' class-attribute instance-attribute #

Optional further explanation of this finding alone.

remedy = '' class-attribute instance-attribute #

Optional instruction for fixing it.

Findings that share a remedy are reported under it once rather than repeating it, so keep the wording free of anything specific to one finding.

severity instance-attribute #

How much it matters.

summary instance-attribute #

One line stating what is wrong.

Severity #

Bases: StrEnum

How much a finding matters. Declared worst-first for reporting.

Source code in packages/climate-ref/src/climate_ref/doctor/findings.py
class Severity(StrEnum):
    """How much a finding matters. Declared worst-first for reporting."""

    ERROR = "error"
    """Results computed in this state are wrong."""

    WARNING = "warning"
    """Something the deployment probably did not intend, but results remain valid."""

    INFO = "info"
    """Worth knowing, no action required."""

ERROR = 'error' class-attribute instance-attribute #

Results computed in this state are wrong.

INFO = 'info' class-attribute instance-attribute #

Worth knowing, no action required.

WARNING = 'warning' class-attribute instance-attribute #

Something the deployment probably did not intend, but results remain valid.

check(slug, description) #

Declare a function as a doctor check.

The decorated function is returned unchanged, so it stays directly callable in tests.

Parameters:

Name Type Description Default
slug str

Stable identifier for the check, in kebab-case.

required
description str

One line describing what the check looks for.

required

Returns:

Type Description
Callable[[CheckFunction], CheckFunction]

A decorator that registers the function and returns it.

Source code in packages/climate-ref/src/climate_ref/doctor/registry.py
def check(slug: str, description: str) -> Callable[[CheckFunction], CheckFunction]:
    """
    Declare a function as a doctor check.

    The decorated function is returned unchanged, so it stays directly callable in tests.

    Parameters
    ----------
    slug
        Stable identifier for the check, in kebab-case.
    description
        One line describing what the check looks for.

    Returns
    -------
    :
        A decorator that registers the function and returns it.
    """

    def decorate(func: CheckFunction) -> CheckFunction:
        register_check(RegisteredCheck(slug=slug, description=description, func=func))
        return func

    return decorate

diagnose(context, *, environment=False) #

Examine a REF deployment and generate a report.

Every check runs. One that raises becomes a finding rather than stopping the rest, so a broken check cannot make the deployment look healthy.

Parameters:

Name Type Description Default
context DoctorContext

The deployment to examine.

required
environment bool

Whether to describe the deployment as well as check it.

False

Returns:

Type Description
DoctorReport

What the checks found, and the deployment they ran against.

Source code in packages/climate-ref/src/climate_ref/doctor/report.py
def diagnose(context: DoctorContext, *, environment: bool = False) -> DoctorReport:
    """
    Examine a REF deployment and generate a report.

    Every check runs.
    One that raises becomes a finding rather than stopping the rest,
    so a broken check cannot make the deployment look healthy.

    Parameters
    ----------
    context
        The deployment to examine.
    environment
        Whether to describe the deployment as well as check it.

    Returns
    -------
    :
        What the checks found, and the deployment they ran against.
    """
    return DoctorReport(
        findings=tuple(run_checks(context)),
        check_count=len(iter_checks()),
        environment=collect_environment(context) if environment else None,
    )

iter_checks() #

Every check available to this deployment, built-in first, then each plugin's.

Returns:

Type Description
tuple[RegisteredCheck, ...]

The registered checks, ordered by source and then by registration order.

Source code in packages/climate-ref/src/climate_ref/doctor/registry.py
def iter_checks() -> tuple[RegisteredCheck, ...]:
    """
    Every check available to this deployment, built-in first, then each plugin's.

    Returns
    -------
    :
        The registered checks, ordered by source and then by registration order.
    """
    load_plugin_checks()
    built_in = [c for c in _REGISTRY.values() if c.source == BUILT_IN]
    plugin = [c for c in _REGISTRY.values() if c.source != BUILT_IN]
    return tuple(built_in + plugin)

worst_severity(findings) #

Return the most serious severity present, or None when there are no findings.

Parameters:

Name Type Description Default
findings Sequence[Finding]

The findings to inspect.

required

Returns:

Type Description
Severity | None

The worst severity present, or None when findings is empty.

Source code in packages/climate-ref/src/climate_ref/doctor/findings.py
def worst_severity(findings: Sequence[Finding]) -> Severity | None:
    """
    Return the most serious severity present, or ``None`` when there are no findings.

    Parameters
    ----------
    findings
        The findings to inspect.

    Returns
    -------
    :
        The worst severity present, or ``None`` when ``findings`` is empty.
    """
    for severity in SEVERITY_ORDER:
        if any(finding.severity == severity for finding in findings):
            return severity
    return None

sub-packages#

Sub-package Description
checks The checks that ship with climate_ref.
context The deployment a check runs against.
environment A description of the deployment, to accompany the findings.
findings What a check reports, and how serious it is.
registry The set of checks that ref doctor runs.
report What examining a deployment produced.