Reflective Data Catalog
Reflective's unified Python interface for accessing SAI (Stratospheric Aerosol Injection) climate model data across cloud providers (S3, GCS, Azure, Cloudflare R2), for use on the Reflective Cloud Hub and anywhere else you run Python.
Every dataset is registered in a single packaged YAML catalog and loaded through one consistent xarray-based interface for browsing, searching, and loading SRM-related datasets. All available datasets can be seen here with more information in the Reflective Cloud Hub documentation. We've also included an example Jupyter Notebook showing how to use the tool.
The public ARISE sources work with no credentials at all; sources on the private Reflective hub bucket require AWS credentials (available automatically on the Reflective Cloud Hub). See the credentials matrix below.
Installation
pip install reflective-data-catalog
Optional extras add the Google Cloud CMIP6/GeoMIP catalog (via intake-esm) and ESGF access (via intake-esgf; needs Python >= 3.12) — neither is required for the core catalog:
pip install "reflective-data-catalog[esm]" # catalog.esm / catalog.geomip_cloud
pip install "reflective-data-catalog[esgf]" # catalog.esgf
pip install "reflective-data-catalog[esm,esgf]"
For development:
git clone https://github.com/ReflectiveCloud/reflective-data-catalog.git
cd reflective-data-catalog
pip install -e ".[dev]"
pre-commit install
This installs a pre-commit hook that automatically runs Ruff linting (with auto-fix) and formatting on every commit.
Quick Start
from reflective_data_catalog import ReflectiveCatalog
rdc = ReflectiveCatalog()
# Public data — works with no credentials at all
ds = rdc.arise_sai_15(variable="TREFHT", time_frequency="month_1").to_dask()
# Public Zarr stores on Cloudflare R2 — also no credentials. Tables and
# realms are Zarr groups; variables are selected from the opened dataset —
# list_variables() enumerates them (list_variables(table="day") aggregates
# across the day/* groups; add realm= to pin one group).
ds = rdc.cesm2_waccm_g6_1p5k_hilla(table="Amon", realm="atmos_3d", ensemble="r1").to_dask()
temperature = ds["T"]
# Load into memory instead of lazily
ds = rdc.miroc_es2h_g6_1p5k_sai(table="Mon", ensemble="r01").read()
surface_temp = ds["SurfT"]
Credentials
Three access classes cover every source in the catalog:
| Access class | Credentials needed | Sources |
|---|---|---|
| Public (anonymous) | None — works out of the box | The CESM2-WACCM and MIROC-ES2H entries (public Cloudflare R2 Zarr stores: cesm2_waccm_g6_1p5k_hilla, cesm2_waccm_g6_1p5k_sai, cesm2_waccm_ssp245, miroc_es2h_g6_1p5k_hilla, miroc_es2h_g6_1p5k_sai) and the ARISE entries (arise_sai_15, arise_15_cesm2_waccm_ssp245, ukesm1_arise_sai, ukesm1_arise_cmip6) |
| Reflective hub (private S3 bucket) | AWS credentials via environment variables (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY) or ~/.aws; provided automatically on the Reflective Cloud Hub |
UKESM1.1, E3SMv3, cesm2_waccm_historical, and GAUSS entries |
Cloudflare R2 S3 API (r2:// URLs) |
CLOUDFLARE_R2_ACCOUNT_ID (or CLOUDFLARE_ACCOUNT_ID) environment variable, plus R2 access keys |
No shipped entry needs this — the public R2 stores are served over plain HTTPS; support is built in for future private R2 entries |
Missing credentials raise a MissingCredentialsError that names the missing configuration (for example the CLOUDFLARE_R2_ACCOUNT_ID environment variable) rather than a provider stack trace.
Available Sources
All 27 sources come from the packaged catalog file (src/reflective_data_catalog/data-catalog.yaml). Entries marked experimental are still being populated or verified — see docs/migration-matrix.md for the authoritative stability listing.
| Source | Model | Experiment | Driver | Access | Stability |
|---|---|---|---|---|---|
ukesm1_g6_1p5k_hilla |
UKESM1.1 | G6-1.5K-HiLLA | netcdf | Hub | stable |
cesm2_waccm_g6_1p5k_hilla |
CESM2-WACCM | G6-1.5K-HiLLA | zarr | Public | stable |
e3smv3_g6_1p5k_hilla |
E3SMv3 | G6-1.5K-HiLLA | netcdf | Hub | stable |
miroc_es2h_g6_1p5k_hilla |
MIROC-ES2H | G6-1.5K-HiLLA (variant=) |
zarr | Public | stable |
ukesm1_ssp245 |
UKESM1.1 | SSP2-4.5 reference | netcdf | Hub | stable |
e3smv3_ssp245 |
E3SMv3 | SSP2-4.5 reference | netcdf | Hub | stable |
cesm2_waccm_historical |
CESM2-WACCM | Historical (POP ocean) | netcdf | Hub | stable |
cesm2_waccm_ssp245 |
CESM2-WACCM | SSP2-4.5 (POP ocean) | zarr | Public | stable |
miroc_es2h_g6_1p5k_sai |
MIROC-ES2H | G6-1.5K-SAI (variant=) |
zarr | Public | stable |
ukesm1_g6_1p5k_sai |
UKESM1.1 | G6-1.5K-SAI | zarr | Hub | experimental |
cesm2_waccm_g6_1p5k_sai |
CESM2-WACCM | G6-1.5K-SAI | zarr | Public | stable |
e3smv3_g6_1p5k_sai |
E3SMv3 | G6-1.5K-SAI | zarr | Hub | experimental |
cesm2_waccm6_gauss_historical |
CESM2-WACCM | GAUSS | zarr | Hub | experimental |
arise_sai_15 |
CESM2-WACCM | ARISE-SAI-1.5 | netcdf | Public | stable |
arise_15_cesm2_waccm_ssp245 |
CESM2-WACCM | SSP2-4.5 (ARISE reference) | netcdf | Public | stable |
ukesm1_arise_sai |
UKESM1.0 | ARISE-SAI-1.5 | netcdf | Public | stable |
ukesm1_arise_cmip6 |
UKESM1.0 | SSP2-4.5 (CMIP6 ScenarioMIP) | netcdf | Public | stable |
simulator_cesm2_waccm_ma_0p5k_sai |
CESM2-WACCM-MA | 0.5K-SAI (simulator inputs) | zarr | Public | stable |
simulator_cesm2_waccm_ma_1p0k_sai |
CESM2-WACCM-MA | 1.0K-SAI (simulator inputs) | zarr | Public | stable |
simulator_cesm2_waccm_ma_1p5k_sai |
CESM2-WACCM-MA | 1.5K-SAI (simulator inputs) | zarr | Public | stable |
simulator_cesm2_waccm_ma_baseline |
CESM2-WACCM-MA | baseline (simulator inputs) | zarr | Public | stable |
simulator_cesm2_waccm_ma_historical |
CESM2-WACCM-MA | historical (simulator inputs) | zarr | Public | stable |
simulator_miroc_es2h_g6_0p5k_sai |
MIROC-ES2H | G6-0.5K-SAI (simulator inputs) | zarr | Public | stable |
simulator_miroc_es2h_g6_1p5k_sai |
MIROC-ES2H | G6-1.5K-SAI (simulator inputs) | zarr | Public | stable |
simulator_miroc_es2h_baseline |
MIROC-ES2H | baseline (simulator inputs) | zarr | Public | stable |
simulator_miroc_es2h_historical |
MIROC-ES2H | historical (simulator inputs) | zarr | Public | stable |
simulator_miroc_es2h_ssp245 |
MIROC-ES2H | SSP2-4.5 (simulator inputs) | zarr | Public | stable |
The ten simulator_* entries are the public simulator-input stores (simulator-inputs/ on the R2 bucket): grouped Zarr stores of monthly (CESM-MA also daily) 2D atmosphere fields with table/realm groups (Mon/atmos_2d default, plus atmos_2d_derived and atmos_2d_tasminmax) and a 3-member ensemble selected exactly like the other public CESM/MIROC entries.
Usage
Selecting Parameters
Every source takes the canonical keyword arguments ensemble, table, and variable, plus per-source extras such as variant, realm, time_frequency, or version. CMIP6-style aliases are accepted permanently: ensemble_member/member_id → ensemble, table_id → table, variable_id → variable.
# Canonical kwargs (UKESM HiLLA is NetCDF: UM stream tables + a time segment)
ds = rdc.ukesm1_g6_1p5k_hilla(
variable="ua", table="ap4", time="AERmon", ensemble="r12i1p1f2"
).to_dask()
# Aliases work identically
ds = rdc.ukesm1_g6_1p5k_hilla(
variable_id="ua", table_id="ap4", time="AERmon", member_id="r12i1p1f2"
).to_dask()
# Typos raise a TypeError listing the valid parameters — nothing loads silently
rdc.cesm2_waccm_ssp245(varaible="SALT")
# TypeError: cesm2_waccm_ssp245: unknown parameter(s) ['varaible']. ...
The CESM2-WACCM and MIROC-ES2H entries open grouped public Zarr stores: table and realm select the Zarr group, the ensemble is a dataset dimension (ensemble="all" keeps every member), and variables are picked from the opened dataset. The MIROC entries additionally take variant= — 'baseline' selects the SSP2-4.5 reference store (the default on the HiLLA entry); the experiment name selects the experiment store:
# SSP2-4.5 reference outputs (the default variant on the HiLLA entry)
ds = rdc.miroc_es2h_g6_1p5k_hilla(table="Mon", ensemble="r01").to_dask()
surface_temp = ds["SurfT"]
# The experiments themselves (the HiLLA experiment store uses
# member-suffixed realm groups — use .discover() to list them)
ds = rdc.miroc_es2h_g6_1p5k_hilla(
variant="G6-1.5K-HiLLA", table="Amon", realm="atmos_2d_r03"
).to_dask()
ds = rdc.miroc_es2h_g6_1p5k_sai().to_dask() # variant='G6-1.5K-SAI' default
# The full ten-member ensemble as one dataset
ds = rdc.miroc_es2h_g6_1p5k_sai(ensemble="all").to_dask()
Discovering Available Data
Catalog-level listing and search return structured records; pass verbose=True for a printed summary:
records = rdc.list_sources() # structured records for every source
rdc.list_sources(verbose=True) # human-readable printout
for rec in rdc.search(term="ukesm"):
print(rec["name"], rec["kind"]) # every result carries its kind
rdc.list_tags() # tags across catalog entries
rdc.get_parameters("arise_sai_15") # {name, driver, description, parameters}
src = rdc.get_source("arise_sai_15") # string-keyed access to any entry
rdc.help() # overview help text
Each source scans cloud storage to report what actually exists (an empty scan returns [] with a warning — never the parameter defaults):
source = rdc.ukesm1_g6_1p5k_hilla()
source.list_variables() # observed variables (optionally per ensemble/table)
source.list_ensembles()
source.list_tables()
source.discover() # dict summary: url + ensembles + tables + variables
Errors
The package raises a typed exception hierarchy: CatalogError (bad or unsupported catalog file), SourceNotFoundError (unknown source name; subclasses AttributeError so hasattr and tab-completion keep working), DataNotFoundError (a rendered URL matched no data, or matched it impurely), and MissingCredentialsError (names the missing credential). Calls that use pre-1.0 parameter vocabulary raise errors that carry the old→new guidance from the migration matrix.
Google Cloud CMIP6 / GeoMIP ([esm] extra)
With pip install "reflective-data-catalog[esm]", access cloud-optimized Zarr data from the Google Cloud CMIP6 catalog:
# Search and load in one step
datasets = rdc.esm.load(
experiment_id=["G6sulfur", "ssp245", "ssp585"],
variable_id="tas",
table_id="Amon",
require_all_on=["source_id", "institution_id"],
)
# Or use the GeoMIP convenience helper
datasets = rdc.geomip_cloud.load_ensemble(
experiments=["G6sulfur", "ssp245", "ssp585"],
variable="tas",
)
# Quick single-experiment load
ds_dict = rdc.geomip_cloud.g6sulfur(variable="tas")
# Explore what's available
rdc.geomip_cloud.list_models()
rdc.geomip_cloud.list_variables(experiment_id="G6sulfur")
rdc.geomip_cloud.summary()
# Advanced: direct search then load
subset = rdc.esm.search(
experiment_id="G6sulfur",
variable_id=["tas", "pr"],
table_id="Amon",
)
datasets = subset.to_dataset_dict()
ESGF Data ([esgf] extra)
With pip install "reflective-data-catalog[esgf]", the catalog also provides access to ESGF (Earth System Grid Federation) data:
ds = rdc.esgf.geomip.g6sulfur(model="UKESM1-0-LL", variable="tas")
Migrating from 0.x
Mid-analysis and need the old behavior right now? Pin below 1.0:
pip install "reflective-data-catalog<1"
v1.0 replaces the pre-1.0 dual registration system (flexible source configs plus an intake catalog) with a single packaged YAML catalog and a self-parsed loader. The 8 documented source names are unchanged. What did change:
- One registration mechanism. All sources live in
data-catalog.yaml; the flexible-sources system (FlexibleSourceConfig,reflective_data.py) is gone.intakeis no longer a runtime dependency —intake-esm/intake-esgfmoved behind the[esm]/[esgf]extras. - Canonical kwargs plus permanent aliases.
ensemble/table/variableare canonical;ensemble_member,member_id,table_id, andvariable_idare accepted forever. - Typos now error. Unknown keyword arguments raise
TypeErrorlisting the valid parameters. Previously they were silently ignored and the defaults loaded. - MIROC uses
variant=. The old separate MIROC SSP2-4.5 sources were absorbed into the two MIROC entries asvariant='baseline'(the default on the HiLLA entry). - Both UKESM hub entries (
ukesm1_ssp245,ukesm1_g6_1p5k_hilla) stay NetCDF in their pre-1.0 stream/time layouts — UM streamtable=values (ap4..onm), thetime=parameter, and the old defaults all keep working unchanged. - Parameter value vocabularies changed with the Zarr switches (CESM/MIROC). Old table values (e.g.
table='AMON') no longer match the Zarr groups (Amon), and variables are selected from the opened dataset instead of the path. Old values are never silently translated — they raise an error carrying the old→new mapping. The NetCDF-preserving entries (UKESM, E3SM,cesm2_waccm_historical) keep their old vocabularies; E3SM variables stay E3SM-native (T,TREFHT— there is notas). - Structured results and typed errors.
list_sources()/list_tags()/search()return records (printing behindverbose=True);get_source_config()is replaced byget_source()andget_parameters(); unknown sources raiseSourceNotFoundError.
The full old→new table — source by source, kwarg by kwarg, default by default — is in docs/migration-matrix.md, rendered from the machine-readable migration_matrix.yaml that ships inside the package.
Running Tests
Run the full test suite:
pytest
Run with coverage report:
pytest --cov=reflective_data_catalog --cov-report=term-missing
Run a specific test file:
pytest tests/test_catalog.py
pytest tests/test_catalog_entries.py # instantiates every shipped catalog entry
Tests use the real packaged catalog and mock storage I/O only — no network access or cloud credentials are required.
Requirements
Python >= 3.11 for the core catalog; the [esgf] extra needs Python >= 3.12 (intake-esgf dropped 3.11). Runtime dependencies (and the esm, esgf, and dev extras) are declared in pyproject.toml — that file is the single source of truth for versions.
License
Apache 2.0
Release files for reflective-data-catalog 1.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| reflective_data_catalog-1.0.1.tar.gz | 120.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| reflective_data_catalog-1.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 170.6 kB
Release files / reflective_data_catalog-1.0.1.tar.gz
| Download URL | reflective_data_catalog-1.0.1.tar.gz |
|---|---|
| Size | 120.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
03bc3dcc7f37ae95d451b5bf243ad1ffa3c09b7929b9c99f9ef9b132996c783a
|
|
BLAKE2b-256 checksum How to use checksums |
7cf941d5c9844dee95a2a5d7086a6a87bc510695fd97b981df8784de619c72c5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency logRelease files / reflective_data_catalog-1.0.1-py3-none-any.whl
| Download URL | reflective_data_catalog-1.0.1-py3-none-any.whl |
|---|---|
| Size | 49.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4ce87be6b60221be2072a5b96e99556ded69f66e1be3093e97df927fd1807fae
|
|
BLAKE2b-256 checksum How to use checksums |
d4fd2e6db7f5fefa4559062195a4cd131c4ff0d57a214cb5e4cd6eefe9b1f502
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency log