From 68a290599df82b6d96b7ebe876280fb45fa1f638 Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Thu, 13 Aug 2026 02:37:48 +0000 Subject: [PATCH 1/2] docs: rewrite the API surface page around symbols rather than modules The page listed modules, so a provider author reading it could not tell which names within one were covered, nor that some live in a submodule rather than the package root. It now names each symbol, its kind and its tier, and the provider-author guidance points at symbols to match. --- docs/api-surface.md | 339 ++++++++++++++++----------- docs/versioning-and-compatibility.md | 4 +- 2 files changed, 205 insertions(+), 138 deletions(-) diff --git a/docs/api-surface.md b/docs/api-surface.md index 53d1d86da..901f5c00c 100644 --- a/docs/api-surface.md +++ b/docs/api-surface.md @@ -1,98 +1,102 @@ -# climate-ref-core Public API Surface +# climate-ref-core public API surface -This document catalogues the public API of `climate-ref-core` that diagnostic -provider packages depend on. Any breaking change to these interfaces requires -a major version bump and a migration guide. +This page lists what a diagnostic provider package is allowed to import from `climate-ref-core`, and what it can rely on staying put. +Breaking any of the stable interfaces below requires a major version bump and a migration guide. -## Stability Tiers +## Stability tiers -| Tier | Meaning | -| --------------- | ------------------------------------------------------ | -| **Stable** | Covered by semver; breaking changes require major bump | -| **Provisional** | May change in minor releases with deprecation notice | -| **Internal** | Prefixed with `_`; no stability guarantee | +| Tier | Meaning | +| --------------- | ----------------------------- | +| **Stable** | Covered by semver. | +| **Provisional** | May change in a minor release | +| **Internal** | No guarantees at all | ---- +Breaking changes to `Stable` feature require a major bump and may include deprecation messages. +Any `_`-prefixed names are also assumed to be Internal and may change at any time. ## `climate_ref_core.diagnostics` (Stable) -The primary module providers interact with. +The module providers spend most of their time in. + +| Symbol | Kind | Description | +| ----------------------- | ---------------- | -------------------------------------------------------------------- | +| `Diagnostic` | Class | Base class all diagnostics subclass | +| `AbstractDiagnostic` | Protocol | The same interface, for code that only needs to type-check it | +| `CommandLineDiagnostic` | Class | Base for diagnostics that shell out to a CLI tool | +| `DataRequirement` | Class (attrs) | The input datasets a diagnostic needs, and how to group them | +| `ExecutionDefinition` | Class (attrs) | One execution: a diagnostic plus the exact datasets it will run on | +| `ExecutionResult` | Class (attrs) | What an execution produced, its CMEC bundles and its `resource_usage` | +| `ensure_relative_path` | Function | Make a path relative to a root directory | +| `SeriesDefinition` | Class (Pydantic) | Declares a 1-d array output, with its index and dimensions | +| `FileDefinition` | Class (Pydantic) | Declares an output file, with its dimensions | -| Symbol | Kind | Description | -| ----------------------- | ------------- | ----------------------------------------------------- | -| `Diagnostic` | Class | Base class all diagnostics must subclass | -| `AbstractDiagnostic` | Protocol | Protocol defining the diagnostic interface | -| `CommandLineDiagnostic` | Class | Base for diagnostics that shell out to CLI tools | -| `DataRequirement` | Class (attrs) | Declares what datasets a diagnostic needs | -| `ExecutionDefinition` | Class (attrs) | Immutable description of a single execution | -| `ExecutionResult` | Class (attrs) | Result of running a diagnostic, with `resource_usage` | -| `ensure_relative_path` | Function | Resolve a path relative to root_directory | +`SeriesDefinition` and `FileDefinition` are re-exported from +[`climate_ref_core.metric_values.typing`](#climate_ref_coremetric_values-stable); +either import path works. -### Key extension points +### Extension points - Override `Diagnostic.run(definition) -> ExecutionResult` - Override `Diagnostic.build_execution_result(definition) -> ExecutionResult` -- Set `Diagnostic.data_requirements`, `facets`, `slug`, `name` -- Set `Diagnostic.test_data_spec` for test case support - ---- +- Set `Diagnostic.data_requirements`, `facets`, `slug` and `name` +- Set `Diagnostic.series` and `Diagnostic.files` to declare the metric values and files a run emits +- Bump `Diagnostic.version` whenever results change enough to need recomputation. + The value is append-only: always increment, never reuse a number. +- Set `Diagnostic.reconstruction_inputs` for output globs to keep beyond what the CMEC bundle references +- Set `Diagnostic.test_data_spec` to opt in to test case support ## `climate_ref_core.providers` (Stable) -| Symbol | Kind | Description | -| ------------------------------- | -------- | ----------------------------------------- | -| `DiagnosticProvider` | Class | Registry for diagnostics from one package | -| `CommandLineDiagnosticProvider` | Class | Provider that executes CLI commands | -| `CondaDiagnosticProvider` | Class | Provider that manages a conda environment | -| `import_provider` | Function | Import a provider by fully qualified name | +| Symbol | Kind | Description | +| ------------------------------- | -------- | -------------------------------------------------------- | +| `DiagnosticProvider` | Class | Registers and runs the diagnostics of one package | +| `CommandLineDiagnosticProvider` | Class | Provider whose diagnostics run as command line calls | +| `CondaDiagnosticProvider` | Class | As above, inside a conda environment the provider manages | +| `import_provider` | Function | Import a provider from a fully qualified name | -### Key extension points +### Extension points - Override `DiagnosticProvider.configure(config)` for provider-level setup -- Override `setup_environment(config)`, `fetch_data(config)`, `ingest_data(config, db)` +- Override `setup_environment(config)`, `fetch_data(config)` and `ingest_data(config, db)` - Override `validate_setup(config) -> bool` -- Call `provider.register(diagnostic)` to add diagnostics - ---- +- Call `provider.register(diagnostic)` to add a diagnostic ## `climate_ref_core.datasets` (Stable) -| Symbol | Kind | Description | -| ---------------------------- | ------------- | ------------------------------------------------------ | -| `SourceDatasetType` | Enum | Enum of source types (CMIP6, obs4MIPs, PMPClimatology) | -| `DatasetCollection` | Class (attrs) | A collection of datasets of one source type | -| `ExecutionDatasetCollection` | Class (attrs) | Multi-source-type dataset bundle | -| `FacetFilter` | Class (attrs) | Filter datasets by facet values | -| `Selector` | TypeAlias | `tuple[tuple[str, str], ...]` | - ---- +| Symbol | Kind | Description | +| ---------------------------- | ------------- | ----------------------------------------------------------- | +| `SourceDatasetType` | Enum | Supported source types (CMIP6, obs4MIPs, PMPClimatology, …) | +| `DatasetCollection` | Class (attrs) | The datasets of one source type needed for an execution | +| `ExecutionDatasetCollection` | Class | All of an execution's datasets, keyed by source type | +| `FacetFilter` | Class (attrs) | A filter applied to a data catalog | +| `Selector` | TypeAlias | `tuple[tuple[str, str], ...]` | ## `climate_ref_core.constraints` (Stable) -| Symbol | Kind | Description | -| ----------------------------- | ------------- | ------------------------------------------- | -| `GroupConstraint` | Protocol | Interface for dataset grouping constraints | -| `RequireFacets` | Class (attrs) | Require specific facet values | -| `IgnoreFacets` | Class (attrs) | Exclude datasets matching facets | -| `AddSupplementaryDataset` | Class (attrs) | Attach supplementary data to groups | -| `RequireTimerange` | Class (attrs) | Require a minimum time range | -| `RequireContiguousTimerange` | Class (attrs) | Require contiguous time coverage | -| `RequireOverlappingTimerange` | Class (attrs) | Require overlapping time ranges | -| `SelectParentExperiment` | Class (attrs) | Add parent experiment data | -| `PartialDateTime` | Class | Partial datetime for time range constraints | -| `apply_constraint` | Function | Apply a constraint to grouped data | - ---- +Constraints run over each candidate group of datasets during the solve, and either +reshape the group or reject it. + +| Symbol | Kind | Description | +| ----------------------------- | ------------- | -------------------------------------------------------- | +| `GroupConstraint` | Protocol | The interface the constraints below implement | +| `RequireFacets` | Class (attrs) | Reject groups missing the given facet values | +| `IgnoreFacets` | Class (attrs) | Drop datasets matching the given facet values | +| `AddSupplementaryDataset` | Class (attrs) | Pull in a cell measure or ancillary variable | +| `RequireTimerange` | Class (attrs) | Reject groups that don't cover a given time range | +| `RequireContiguousTimerange` | Class (attrs) | Reject groups with gaps in time | +| `RequireOverlappingTimerange` | Class (attrs) | Reject groups whose datasets don't overlap in time | +| `AddParentDataset` | Class (attrs) | Pull in a dataset's parent experiment | +| `SelectFirstMember` | Class (attrs) | Keep a single ensemble member per group | +| `PartialDateTime` | Class | An underspecified date, for comparing against timeranges | +| `apply_constraint` | Function | Apply one constraint to a group | ## `climate_ref_core.executor` (Stable) -| Symbol | Kind | Description | -| --------------------- | -------- | ------------------------------------------ | -| `Executor` | Protocol | Interface for execution backends | -| `execute_locally` | Function | Run a diagnostic in the current process | -| `import_executor_cls` | Function | Import an executor by fully qualified name | - ---- +| Symbol | Kind | Description | +| --------------------- | -------- | -------------------------------------------------- | +| `Executor` | Protocol | Runs executions asynchronously; local, Celery, HPC | +| `execute_locally` | Function | Run one execution in the current process | +| `import_executor_cls` | Function | Import an executor from a fully qualified name | ## `climate_ref_core.resources` (Provisional) @@ -111,106 +115,167 @@ and defaults to `None`, so a provider that never sets it stays valid. ## `climate_ref_core.dataset_registry` (Stable) -| Symbol | Kind | Description | -| -------------------------- | -------- | ---------------------------------- | -| `DatasetRegistryManager` | Class | Manages named pooch registries | -| `dataset_registry_manager` | Instance | Singleton registry manager | -| `fetch_all_files` | Function | Download all files from a registry | -| `validate_registry_cache` | Function | Verify cached file checksums | -| `DATASET_URL` | Constant | Base URL for dataset downloads | +Registries describe reference data that isn't published through ESGF. + +| Symbol | Kind | Description | +| --------------------------- | -------- | ------------------------------------------------------------- | +| `DatasetRegistryManager` | Class | Holds the named pooch registries | +| `dataset_registry_manager` | Instance | The process-wide manager; providers register into this | +| `RegistryEntry` | Class | One registry plus the metadata needed to ingest it | +| `RegistryUseCase` | Enum | Whether a registry is catalog-ingestable or fetch-only | +| `fetch_all_files` | Function | Download a registry's files into an output directory | +| `validate_registry_cache` | Function | Check that cached files are present and match their checksums | +| `resolve_cache_dir` | Function | Locate the cache directory a registry uses | +| `iter_reference_registries` | Function | Yield `(registry, source_type)` for the ingestable registries | +| `DATASET_URL` | Constant | Base URL the registries download from | + +## `climate_ref_core.testing` (Provisional) + +| Symbol | Kind | Description | +| -------------------------- | ------------- | ---------------------------------------------------------- | +| `TestCase` | Class (attrs) | One test case for a diagnostic | +| `TestDataSpecification` | Class (attrs) | The test cases a diagnostic declares, via `test_data_spec` | +| `TestCasePaths` | Class (attrs) | Resolves where a test case's data lives | +| `collect_test_case_params` | Function | Diagnostic/test-case pairs for `pytest.mark.parametrize` | +| `load_datasets_from_yaml` | Function | Read an `ExecutionDatasetCollection` from YAML | +| `save_datasets_to_yaml` | Function | Write an `ExecutionDatasetCollection` to YAML | ---- +## `climate_ref_core.exceptions` (Stable) -## `climate_ref_core.testing` (Stable) +| Symbol | Kind | Description | +| ---------------------------- | ----- | ----------------------------------------------- | +| `RefException` | Class | Base class for every exception below | +| `InvalidExecutorException` | Class | An executor could not be imported or configured | +| `InvalidProviderException` | Class | A provider could not be imported or configured | +| `InvalidDiagnosticException` | Class | A diagnostic failed validation at registration | +| `ConstraintNotSatisfied` | Class | A group did not satisfy a constraint | +| `ResultValidationError` | Class | An execution's output failed validation | +| `ExecutionError` | Class | An execution failed | +| `DiagnosticError` | Class | The diagnostic's own computation raised | +| `TestCaseError` | Class | Base class for the test case errors below | +| `TestCaseNotFoundError` | Class | No test case by that name | +| `NoTestDataSpecError` | Class | The diagnostic declares no `test_data_spec` | +| `DatasetResolutionError` | Class | A test case's datasets could not be resolved | -| Symbol | Kind | Description | -| -------------------------- | ------------- | ----------------------------------------------- | -| `TestCase` | Class (attrs) | A single test case definition | -| `TestDataSpecification` | Class (attrs) | Collection of test cases for a diagnostic | -| `TestCasePaths` | Class (attrs) | Path resolver for test case data | -| `collect_test_case_params` | Function | Collect pytest parametrize params from provider | -| `load_datasets_from_yaml` | Function | Load ExecutionDatasetCollection from YAML | -| `save_datasets_to_yaml` | Function | Save ExecutionDatasetCollection to YAML | +## `climate_ref_core.pycmec` (Stable) ---- +Models for the CMEC bundle formats. The package root is empty; import from the submodules. -## `climate_ref_core.exceptions` (Stable) +### `climate_ref_core.pycmec.metric` -| Symbol | Kind | Description | -| ---------------------------- | ----- | --------------------------------- | -| `RefException` | Class | Base exception for all REF errors | -| `InvalidExecutorException` | Class | Invalid executor configuration | -| `InvalidProviderException` | Class | Invalid provider configuration | -| `InvalidDiagnosticException` | Class | Invalid diagnostic registration | -| `ConstraintNotSatisfied` | Class | Dataset constraint not met | -| `ResultValidationError` | Class | Result validation failure | -| `ExecutionError` | Class | Execution failure | -| `DiagnosticError` | Class | Diagnostic runtime error | -| `TestCaseError` | Class | Base test case error | -| `TestCaseNotFoundError` | Class | Test case not found | -| `NoTestDataSpecError` | Class | Diagnostic has no test data spec | -| `DatasetResolutionError` | Class | Dataset resolution failure | +| Symbol | Kind | Description | +| ------------------- | ---------------- | ---------------------------------------------- | +| `CMECMetric` | Class (Pydantic) | A CMEC metric bundle | +| `MetricCV` | Enum | The bundle's controlled vocabulary of keys | +| `MetricDimensions` | Class (Pydantic) | The bundle's `DIMENSIONS` object | +| `MetricResults` | Class (Pydantic) | The bundle's `RESULTS` object | +| `remove_dimensions` | Function | Strip dimensions from a raw bundle | ---- +### `climate_ref_core.pycmec.output` -## `climate_ref_core.pycmec` (Stable) +| Symbol | Kind | Description | +| ------------------ | ---------------- | ------------------------------------------ | +| `CMECOutput` | Class (Pydantic) | A CMEC output bundle | +| `OutputCV` | Enum | The bundle's controlled vocabulary of keys | +| `OutputProvenance` | Class (Pydantic) | The bundle's provenance object | -| Symbol | Kind | Description | -| ---------------------- | ---------------- | -------------------------- | -| `CMECMetric` | Class (Pydantic) | CMEC metric bundle model | -| `CMECOutput` | Class (Pydantic) | CMEC output bundle model | -| `ControlledVocabulary` | Class (Pydantic) | CMEC controlled vocabulary | +### `climate_ref_core.pycmec.controlled_vocabulary` ---- +| Symbol | Kind | Description | +| ---------------- | ------------- | ------------------------------------------------------ | +| `CV` | Class (attrs) | The dimensions and values executions are validated against | +| `Dimension` | Class (attrs) | One dimension within the vocabulary | +| `DimensionValue` | Class (attrs) | One value a dimension permits | ## `climate_ref_core.esgf` (Provisional) -| Symbol | Kind | Description | -| --------------------- | ------------- | ---------------------------------------- | -| `ESGFRequest` | Class (attrs) | ESGF data request specification | -| `CMIP6Request` | Class (attrs) | CMIP6-specific ESGF request | -| `CMIP7Request` | Class (attrs) | CMIP7-specific ESGF request | -| `Obs4MIPsRequest` | Class (attrs) | obs4MIPs-specific ESGF request | -| `ESGFDataFetcher` | Class | Fetch data from ESGF | -| `ESGFRequestRegistry` | Class | Registry of ESGF requests per diagnostic | +| Symbol | Kind | Description | +| ------------------------ | -------- | ------------------------------------------------------- | +| `ESGFRequest` | Protocol | What a dataset request has to provide | +| `CMIP6Request` | Class | A CMIP6 request | +| `CMIP7Request` | Class | A CMIP7 request | +| `Obs4MIPsRequest` | Class | An obs4MIPs request | +| `RegistryRequest` | Class | A request served from a pooch registry, not from ESGF | +| `ESGFFetcher` | Class | Fetches the requested datasets and returns their paths | +| `IntakeESGFMixin` | Mixin | Gives a request class its intake-esgf search | +| `enable_ceda_solr_index` | Function | Add the CEDA Solr index to the ESGF search indices | ---- +## `climate_ref_core.metric_values` (Stable) + +| Symbol | Kind | Description | +| ------------------- | ---------------- | -------------------------------------------------------- | +| `SeriesMetricValue` | Class (Pydantic) | A 1-d array with its index and dimensions | +| `ScalarMetricValue` | Class (Pydantic) | A single value with its dimensions | +| `MetricValueKind` | TypeAlias | `Literal["model", "reference"]` | + +### `climate_ref_core.metric_values.typing` + +| Symbol | Kind | Description | +| ------------------ | ---------------- | ---------------------------------------------------------- | +| `SeriesDefinition` | Class (Pydantic) | Declares a series a diagnostic emits, ahead of running it | +| `FileDefinition` | Class (Pydantic) | Declares a file a diagnostic emits, ahead of running it | + +Both are also re-exported from +[`climate_ref_core.diagnostics`](#climate_ref_corediagnostics-stable). ## `climate_ref_core.source_types` (Stable) -| Symbol | Kind | Description | -| ------------------- | ---- | ------------------------------------------------------ | -| `SourceDatasetType` | Enum | Canonical source type enum (re-exported from datasets) | +| Symbol | Kind | Description | +| ------------------- | --------- | ----------------------------- | +| `SourceDatasetType` | Enum | Supported source types | +| `Selector` | TypeAlias | `tuple[tuple[str, str], ...]` | ---- +Both are defined here and re-exported by `climate_ref_core.datasets`. +Import from either. ## `climate_ref_core.logging` (Stable) -| Symbol | Kind | Description | -| -------------------- | --------------- | -------------------------------------- | -| `add_log_handler` | Function | Add a loguru handler | -| `remove_log_handler` | Function | Remove the default handler | -| `redirect_logs` | Context manager | Redirect logs to file during execution | -| `capture_logging` | Function | Capture stdlib logging into loguru | +| Symbol | Kind | Description | +| -------------------- | --------------- | ----------------------------------------------- | +| `add_log_handler` | Function | Add a sink to the loguru logger | +| `remove_log_handler` | Function | Remove the default handler | +| `redirect_logs` | Context manager | Send log output to a file for the duration | +| `capture_logging` | Function | Route stdlib `logging` into loguru | ---- +## `climate_ref_core.esmvaltool_reference` (Internal) -## `climate_ref_core.env` (Internal) +The path conventions ESMValTool uses for observational and reanalysis data. -| Symbol | Kind | Description | -| ------------------------- | -------- | ----------------------- | -| `env` | Instance | Environs Env instance | -| `get_available_cpu_count` | Function | Get available CPU count | +| Symbol | Kind | Description | +| ---------------------- | ---------- | ------------------------------------------------- | +| `ReferenceFacets` | NamedTuple | The metadata a reference file's path encodes | +| `parse_reference_path` | Function | Read that metadata off a path | +| `drs_relative_parts` | Function | Split a path into its DRS-relative components | +| `tier_from_segment` | Function | Read the tier number out of a `TierN` directory | +| `PROJECT_ANCHORS` | Constant | The directory names that anchor a reference tree | ---- +## `climate_ref_core.cmip6_to_cmip7` (Internal) + +Presents CMIP6 data under CMIP7 conventions. +Providers generally only need `get_dreq_entry`; the rest serves the REF's own ingestion +and will move as CMIP7 settles. + +| Symbol | Kind | Description | +| ------------------------------ | -------- | ------------------------------------------------------- | +| `get_dreq_entry` | Function | Look up a variable in the Data Request by compound name | +| `convert_cmip6_to_cmip7_attrs` | Function | Convert CMIP6 global attributes to CMIP7 | +| `convert_cmip6_dataset` | Function | Convert a whole dataset in memory | +| `CMIP7Metadata` | Class | The CMIP7 attributes a conversion produces | + +## `climate_ref_core.env` (Internal) + +| Symbol | Kind | Description | +| ------------------------- | -------- | ---------------------------------------------------- | +| `env` | Instance | The environs reader the REF reads its settings from | +| `get_available_cpu_count` | Function | CPU count, respecting cgroup limits | -## Entry Point Contract +## Entry point contract -Providers register via `pyproject.toml`: +Providers register themselves in `pyproject.toml`: ```toml [project.entry-points."climate-ref.providers"] my_provider = "my_package:provider" ``` -The `provider` attribute must be a `DiagnosticProvider` instance. +The attribute named on the right must be a `DiagnosticProvider` instance. diff --git a/docs/versioning-and-compatibility.md b/docs/versioning-and-compatibility.md index 1a4b89c4a..15e921799 100644 --- a/docs/versioning-and-compatibility.md +++ b/docs/versioning-and-compatibility.md @@ -127,7 +127,9 @@ but is useful for auditing which provider release produced a given result. ### For provider authors - Pin `climate-ref-core` with a compatible release constraint: `>=X.Y.0, Date: Thu, 13 Aug 2026 15:48:47 +1000 Subject: [PATCH 2/2] docs: add changelog fragment for the API surface rewrite --- changelog/867.docs.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/867.docs.md diff --git a/changelog/867.docs.md b/changelog/867.docs.md new file mode 100644 index 000000000..377a03d9a --- /dev/null +++ b/changelog/867.docs.md @@ -0,0 +1,3 @@ +Rewrote the API surface page so each section lists the symbols a provider may import, with its kind and a one-line description, +rather than describing the module in prose. +The extension points a provider overrides are now listed explicitly for `Diagnostic` and `DiagnosticProvider`.