reflective-data-catalog 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. reflective_data_catalog-0.0.1/.coverage +0 -0
  2. reflective_data_catalog-0.0.1/.github/workflows/build-and-push.yml +79 -0
  3. reflective_data_catalog-0.0.1/.github/workflows/tests.yml +46 -0
  4. reflective_data_catalog-0.0.1/.gitignore +56 -0
  5. reflective_data_catalog-0.0.1/.pre-commit-config.yaml +9 -0
  6. reflective_data_catalog-0.0.1/CLAUDE.md +78 -0
  7. reflective_data_catalog-0.0.1/CONTRIBUTING.md +163 -0
  8. reflective_data_catalog-0.0.1/Example.ipynb +408 -0
  9. reflective_data_catalog-0.0.1/LICENSE +202 -0
  10. reflective_data_catalog-0.0.1/PKG-INFO +208 -0
  11. reflective_data_catalog-0.0.1/README.md +176 -0
  12. reflective_data_catalog-0.0.1/pyproject.toml +155 -0
  13. reflective_data_catalog-0.0.1/setup.cfg +4 -0
  14. reflective_data_catalog-0.0.1/src/reflective_data_catalog/__init__.py +20 -0
  15. reflective_data_catalog-0.0.1/src/reflective_data_catalog/data-catalog.yaml +645 -0
  16. reflective_data_catalog-0.0.1/src/reflective_data_catalog/esgf.py +433 -0
  17. reflective_data_catalog-0.0.1/src/reflective_data_catalog/esm.py +558 -0
  18. reflective_data_catalog-0.0.1/src/reflective_data_catalog/flexible_sources.py +1006 -0
  19. reflective_data_catalog-0.0.1/src/reflective_data_catalog/help_text.py +95 -0
  20. reflective_data_catalog-0.0.1/src/reflective_data_catalog/main.py +1409 -0
  21. reflective_data_catalog-0.0.1/src/reflective_data_catalog/py.typed +0 -0
  22. reflective_data_catalog-0.0.1/src/reflective_data_catalog/reflective_data.py +159 -0
  23. reflective_data_catalog-0.0.1/src/reflective_data_catalog/storage.py +362 -0
  24. reflective_data_catalog-0.0.1/src/reflective_data_catalog.egg-info/PKG-INFO +208 -0
  25. reflective_data_catalog-0.0.1/src/reflective_data_catalog.egg-info/SOURCES.txt +33 -0
  26. reflective_data_catalog-0.0.1/src/reflective_data_catalog.egg-info/dependency_links.txt +1 -0
  27. reflective_data_catalog-0.0.1/src/reflective_data_catalog.egg-info/requires.txt +11 -0
  28. reflective_data_catalog-0.0.1/src/reflective_data_catalog.egg-info/top_level.txt +1 -0
  29. reflective_data_catalog-0.0.1/tests/__init__.py +0 -0
  30. reflective_data_catalog-0.0.1/tests/conftest.py +270 -0
  31. reflective_data_catalog-0.0.1/tests/test_catalog.py +529 -0
  32. reflective_data_catalog-0.0.1/tests/test_esgf.py +250 -0
  33. reflective_data_catalog-0.0.1/tests/test_esm.py +208 -0
  34. reflective_data_catalog-0.0.1/tests/test_flexible_sources.py +455 -0
  35. reflective_data_catalog-0.0.1/tests/test_storage.py +373 -0
Binary file
@@ -0,0 +1,79 @@
1
+ name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI
2
+
3
+ on: push
4
+
5
+ jobs:
6
+ build:
7
+ name: Build distribution 📦
8
+ runs-on: ubuntu-latest
9
+
10
+ steps:
11
+ - uses: actions/checkout@v6
12
+ with:
13
+ persist-credentials: false
14
+ fetch-depth: 0 # full history so setuptools-scm can compute the version
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v6
17
+ with:
18
+ python-version: "3.x"
19
+
20
+ - name: Install pypa/build
21
+ run: >-
22
+ python3 -m
23
+ pip install
24
+ build
25
+ --user
26
+ - name: Build a binary wheel and a source tarball
27
+ run: python3 -m build
28
+ - name: Store the distribution packages
29
+ uses: actions/upload-artifact@v5
30
+ with:
31
+ name: python-package-distributions
32
+ path: dist/
33
+
34
+ publish-to-pypi:
35
+ name: >-
36
+ Publish Python 🐍 distribution 📦 to PyPI
37
+ if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
38
+ needs:
39
+ - build
40
+ runs-on: ubuntu-latest
41
+ environment:
42
+ name: pypi
43
+ url: https://pypi.org/p/reflective-data-catalog
44
+ permissions:
45
+ id-token: write # IMPORTANT: mandatory for trusted publishing
46
+
47
+ steps:
48
+ - name: Download all the dists
49
+ uses: actions/download-artifact@v6
50
+ with:
51
+ name: python-package-distributions
52
+ path: dist/
53
+ - name: Publish distribution 📦 to PyPI
54
+ uses: pypa/gh-action-pypi-publish@release/v1
55
+
56
+ publish-to-testpypi:
57
+ name: Publish Python 🐍 distribution 📦 to TestPyPI
58
+ needs:
59
+ - build
60
+ runs-on: ubuntu-latest
61
+
62
+ environment:
63
+ name: testpypi
64
+ url: https://test.pypi.org/p/reflective-data-catalog
65
+
66
+ permissions:
67
+ id-token: write # IMPORTANT: mandatory for trusted publishing
68
+
69
+ steps:
70
+ - name: Download all the dists
71
+ uses: actions/download-artifact@v6
72
+ with:
73
+ name: python-package-distributions
74
+ path: dist/
75
+ - name: Publish distribution 📦 to TestPyPI
76
+ uses: pypa/gh-action-pypi-publish@release/v1
77
+ with:
78
+ repository-url: https://test.pypi.org/legacy/
79
+ verbose: true
@@ -0,0 +1,46 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["*"]
7
+ pull_request:
8
+ branches: [main]
9
+
10
+ jobs:
11
+ test:
12
+ name: Tests (Python ${{ matrix.python-version }})
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ python-version: ["3.11", "3.12", "3.13"]
18
+
19
+ steps:
20
+ - uses: actions/checkout@v6
21
+
22
+ - name: Set up Python ${{ matrix.python-version }}
23
+ uses: actions/setup-python@v6
24
+ with:
25
+ python-version: ${{ matrix.python-version }}
26
+
27
+ - name: Install dependencies
28
+ run: |
29
+ python -m pip install --upgrade pip
30
+ pip install -e ".[dev]"
31
+
32
+ - name: Lint with Ruff
33
+ run: |
34
+ ruff check .
35
+ ruff format --check .
36
+
37
+ - name: Run tests with coverage
38
+ run: |
39
+ pytest --cov=reflective_data_catalog --cov-report=term-missing --cov-report=xml
40
+
41
+ - name: Upload coverage to Codecov
42
+ if: matrix.python-version == '3.12'
43
+ uses: codecov/codecov-action@v5
44
+ with:
45
+ files: ./coverage.xml
46
+ fail_ci_if_error: false
@@ -0,0 +1,56 @@
1
+ # Python bytecode
2
+ **/__pycache__/
3
+ *.py[cod]
4
+
5
+ # Virtual environments
6
+ venv/
7
+ env/
8
+ .venv/
9
+
10
+ # Environment variables
11
+ .env
12
+
13
+ # IDE settings
14
+ .vscode/
15
+ .idea/
16
+
17
+ # OS generated files
18
+ .DS_Store
19
+ Thumbs.db
20
+
21
+ # Data files
22
+ *.joblib
23
+ *.nc
24
+ dump.rdb
25
+ *.npy
26
+ *.pkl
27
+ *.csv
28
+
29
+ # Python packages
30
+ *.egg-info/
31
+ *.egg
32
+ *.eggs
33
+ *.eggs.zip
34
+ *.eggs.tar.gz
35
+ *.eggs.tar.bz2
36
+ *.eggs.tar.xz
37
+ *.eggs.tar.lzma
38
+ *.eggs.tar.lz
39
+ *.eggs.tar.zst
40
+ *.eggs.tar.zstd
41
+ *.eggs.tar.zstd.tar.gz
42
+
43
+ # Jupyter notebooks cruft
44
+ **/.ipynb_checkpoints/
45
+
46
+ # Ruff cache
47
+ .ruff_cache/
48
+
49
+ # Pytest cache
50
+ .pytest_cache/
51
+
52
+ # Pytest coverage
53
+ .coverage
54
+ coverage.xml
55
+ htmlcov/
56
+ .coverage.*
@@ -0,0 +1,9 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.8.4
4
+ hooks:
5
+ # Run the linter
6
+ - id: ruff
7
+ args: [--fix]
8
+ # Run the formatter
9
+ - id: ruff-format
@@ -0,0 +1,78 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with this codebase.
4
+
5
+ ## Project Overview
6
+
7
+ Reflective Data Catalog is a pip-installable Python package that provides a unified interface for accessing SAI (Stratospheric Aerosol Injection) climate model data stored in cloud storage (S3, GCS, Azure, Cloudflare R2). It wraps multiple climate model sources (CESM2-WACCM, MIROC-ES2H, UKESM1, E3SMv3) behind a consistent API.
8
+
9
+ ## Project Structure
10
+
11
+ - `src/reflective_data_catalog/` — Package source (src layout)
12
+ - `main.py` — `ReflectiveCatalog` class, the main entry point
13
+ - `flexibleSources.py` — `FlexibleSourceConfig`, `FlexibleSource`, `SourceDiscovery`, `FlexibleSourceRegistry` classes
14
+ - `reflective_data.py` — `DEFAULT_FLEXIBLE_SOURCES` list of all source configurations
15
+ - `storage.py` — `CloudFileSystem` class wrapping obstore for multi-cloud access
16
+ - `esgf.py` — `ESGFHelper` for ESGF data access
17
+ - `esm.py` — `ESMCatalog` and `GeoMIPCloudHelper` for Google Cloud CMIP6 catalog via intake-esm
18
+ - `help_text.py` — Help text utilities
19
+ - `tests/` — Unit tests (pytest)
20
+ - `conftest.py` — Shared fixtures and mocks
21
+ - `test_flexible_sources.py` — FlexibleSourceConfig, Registry, Discovery
22
+ - `test_storage.py` — CloudFileSystem (including R2)
23
+ - `test_esgf.py` — ESGFHelper (mocked)
24
+ - `test_esm.py` — ESMCatalog, GeoMIPCloudHelper (mocked)
25
+ - `test_catalog.py` — ReflectiveCatalog integration
26
+ - `pyproject.toml` — Package metadata, dependencies, and Ruff config
27
+ - `.github/workflows/tests.yml` — CI: runs tests and coverage on push/PR
28
+
29
+ ## Key Architecture
30
+
31
+ - **`FlexibleSourceConfig`** is a frozen dataclass that defines how to build cloud storage paths and filename patterns for each data source. It supports placeholders: `{base}`, `{ensemble}`, `{table_path}`, `{variable}`, `{variant}`, `{time}`, `{ensemble_id}`.
32
+ - **`FlexibleSource`** wraps a config and provides `.to_dask()`, `.read()`, `.list_variables()`, `.list_ensembles()`, `.list_tables()`, and `.discover()`.
33
+ - **`SourceDiscovery`** handles cloud storage scanning via `obstore` (through `CloudFileSystem`) to discover available data.
34
+ - **`CloudFileSystem`** wraps `obstore` to provide a unified API (glob, ls, exists, open) across S3, GCS, Azure, Cloudflare R2, and other cloud providers. It auto-detects the provider from the URL scheme (`s3://`, `gs://`, `az://`, `r2://`). R2 support uses `S3Store` with the Cloudflare endpoint.
35
+ - **`ESMCatalog`** provides intake-esm access to the Google Cloud CMIP6 catalog (search, load, list experiments/models/variables).
36
+ - **`GeoMIPCloudHelper`** wraps ESMCatalog with GeoMIP-specific convenience methods (g6sulfur, g6solar, load_ensemble).
37
+ - **`ReflectiveCatalog`** uses `__getattr__` to dynamically expose registered sources (e.g., `catalog.cesm2_waccm_ssp245()`). Also exposes `catalog.esm` and `catalog.geomip_cloud` for cloud-optimized Zarr access.
38
+ - Sources are registered from `DEFAULT_FLEXIBLE_SOURCES` in `reflective_data.py`.
39
+
40
+ ## Common Commands
41
+
42
+ ```bash
43
+ # Install in development mode
44
+ pip install -e ".[dev]"
45
+
46
+ # Lint
47
+ ruff check .
48
+
49
+ # Auto-fix lint issues
50
+ ruff check --fix .
51
+
52
+ # Format
53
+ ruff format .
54
+
55
+ # Run tests
56
+ pytest
57
+
58
+ # Run tests with coverage
59
+ pytest --cov=reflective_data_catalog --cov-report=term-missing
60
+ ```
61
+
62
+ ## Code Style
63
+
64
+ - Uses Ruff for linting and formatting (configured in `pyproject.toml`)
65
+ - Line length: 88 characters
66
+ - Double quotes for strings
67
+ - All imports use relative paths within the package (e.g., `from .flexibleSources import ...`)
68
+
69
+ ## Important Notes
70
+
71
+ - Cloud storage access uses `obstore` via the `CloudFileSystem` wrapper — auto-detects provider from URL scheme (`s3://`, `gs://`, `az://`, `r2://`).
72
+ - Cloudflare R2 uses `r2://` URLs and requires an account ID via `r2_account_id` kwarg or `CLOUDFLARE_R2_ACCOUNT_ID` / `CLOUDFLARE_ACCOUNT_ID` env var.
73
+ - AWS credentials are needed for S3 access; similarly for GCS and Azure.
74
+ - Google Cloud CMIP6/GeoMIP data is accessible via `catalog.esm` and `catalog.geomip_cloud` (requires `intake-esm`).
75
+ - Some sources use `ensemble_mapping` to convert ensemble names to IDs in filenames (e.g., `r1` → `001` for CESM2).
76
+ - MIROC sources use a `variant` parameter to distinguish between file types (e.g., `baseline` vs `G6-1.5K-SAI`).
77
+ - UKESM1 and E3SMv3 sources have `{variable}` in the directory pattern, not just the filename.
78
+ - All external services are mocked in tests — no network or cloud credentials needed to run `pytest`.
@@ -0,0 +1,163 @@
1
+ # Contributing to Reflective Data Catalog
2
+
3
+ Thank you for your interest in contributing! This document provides guidelines for contributing to the project.
4
+
5
+ ## Getting Started
6
+
7
+ 1. Fork and clone the repository:
8
+
9
+ ```bash
10
+ git clone https://github.com/ReflectiveCloud/reflective-data-catalog.git
11
+ cd reflective-data-catalog
12
+ ```
13
+
14
+ 2. Install in development mode:
15
+
16
+ ```bash
17
+ pip install -e ".[dev]"
18
+ ```
19
+
20
+ ## Development Workflow
21
+
22
+ 1. Create a branch for your work:
23
+
24
+ ```bash
25
+ git checkout -b feature/your-feature-name
26
+ ```
27
+
28
+ 2. Make your changes and ensure they pass linting and tests.
29
+
30
+ 3. Commit your changes with a clear message:
31
+
32
+ ```bash
33
+ git commit -m "Add brief description of change"
34
+ ```
35
+
36
+ 4. Push and open a pull request.
37
+
38
+ ## Code Style
39
+
40
+ This project uses [Ruff](https://docs.astral.sh/ruff/) for linting and formatting. Run both before submitting:
41
+
42
+ ```bash
43
+ ruff check .
44
+ ruff format .
45
+ ```
46
+
47
+ Auto-fix lint issues where possible:
48
+
49
+ ```bash
50
+ ruff check --fix .
51
+ ```
52
+
53
+ Key style rules:
54
+
55
+ - Line length: 88 characters
56
+ - Double quotes for strings
57
+ - Space indentation (no tabs)
58
+ - Imports sorted with isort rules
59
+
60
+ ## Running Tests
61
+
62
+ Run the full test suite:
63
+
64
+ ```bash
65
+ pytest
66
+ ```
67
+
68
+ Run with coverage report:
69
+
70
+ ```bash
71
+ pytest --cov=reflective_data_catalog --cov-report=term-missing
72
+ ```
73
+
74
+ Run a specific test file or test:
75
+
76
+ ```bash
77
+ pytest tests/test_flexible_sources.py
78
+ pytest tests/test_catalog.py::TestSearch::test_search_by_term
79
+ ```
80
+
81
+ All external services (S3, ESGF, intake-esm) are mocked in the test suite — no network access or cloud credentials are needed.
82
+
83
+ ### Writing Tests
84
+
85
+ - Tests live in `tests/` and use [pytest](https://docs.pytest.org/).
86
+ - Shared fixtures and mocks are in `tests/conftest.py`.
87
+ - Mock any cloud or network calls; tests must run offline.
88
+ - Aim for one test file per source module (e.g. `test_storage.py` for `storage.py`).
89
+
90
+ ## Adding a New Data Source
91
+
92
+ To add a new flexible data source, create a `FlexibleSourceConfig` entry in `src/reflective_data_catalog/reflective_data.py`:
93
+
94
+ ```python
95
+ FlexibleSourceConfig(
96
+ name='model_experiment', # Snake-case identifier
97
+ base='s3://bucket/path/to/data', # Cloud storage base URL (s3://, gs://, az://, r2://)
98
+ pattern='{base}/{ensemble}/{table_path}', # Directory pattern
99
+ filename_pattern='{variable}.*.nc', # Filename pattern
100
+ table_mapping={'Amon': 'Amon'}, # Table name mapping
101
+ default_table='Amon',
102
+ default_variable='tas',
103
+ default_ensemble='r1i1p1f1',
104
+ driver='netcdf',
105
+ description='Model experiment description'
106
+ )
107
+ ```
108
+
109
+ The source will automatically be available as `catalog.model_experiment()`.
110
+
111
+ ### Configuration Fields
112
+
113
+ | Field | Description |
114
+ |-------|-------------|
115
+ | `name` | Unique snake_case identifier |
116
+ | `base` | Cloud storage base URL (`s3://`, `gs://`, `az://`, `r2://`) |
117
+ | `pattern` | Directory path template with `{base}`, `{ensemble}`, `{table_path}`, `{variable}`, `{time}` placeholders |
118
+ | `filename_pattern` | Filename template with `{variable}`, `{ensemble}`, `{variant}`, `{ensemble_id}`, `{time}`, and `*` wildcards |
119
+ | `table_mapping` | Dict mapping table names to S3 directory names |
120
+ | `ensemble_mapping` | Optional dict mapping ensemble names to filename IDs (e.g. `{'r1': '001'}`) |
121
+ | `default_table` | Default table when none is specified |
122
+ | `default_variable` | Default variable when none is specified |
123
+ | `default_ensemble` | Default ensemble member |
124
+ | `default_variant` | Optional default variant (e.g. `'baseline'`) |
125
+ | `default_time` | Optional default time/frequency (e.g. `'AERmon'`) |
126
+ | `driver` | `'netcdf'` or `'zarr'` |
127
+ | `combine_files` | How to combine multi-file sources: `'by_coords'`, `'nested'`, or `'first'` |
128
+ | `concat_dim` | Dimension to concatenate along (default: `'time'`) |
129
+ | `description` | Human-readable description |
130
+
131
+ ## Project Structure
132
+
133
+ ```
134
+ src/reflective_data_catalog/
135
+ ├── __init__.py # Package exports
136
+ ├── main.py # ReflectiveCatalog class
137
+ ├── flexible_sources.py # FlexibleSourceConfig, FlexibleSource, SourceDiscovery
138
+ ├── reflective_data.py # Default source configurations
139
+ ├── esgf.py # ESGF data access helper
140
+ ├── esm.py # intake-esm Google Cloud CMIP6/GeoMIP access
141
+ ├── storage.py # CloudFileSystem (obstore wrapper)
142
+ └── help_text.py # Help text utilities
143
+ tests/
144
+ ├── conftest.py # Shared fixtures and mocks
145
+ ├── test_flexible_sources.py # FlexibleSourceConfig, Registry, Discovery
146
+ ├── test_storage.py # CloudFileSystem
147
+ ├── test_esgf.py # ESGFHelper (mocked)
148
+ ├── test_esm.py # ESMCatalog, GeoMIPCloudHelper (mocked)
149
+ └── test_catalog.py # ReflectiveCatalog integration
150
+ ```
151
+
152
+ ## Reporting Issues
153
+
154
+ When reporting a bug, please include:
155
+
156
+ - Python version
157
+ - Package version
158
+ - Steps to reproduce the issue
159
+ - Full error traceback
160
+
161
+ ## License
162
+
163
+ By contributing, you agree that your contributions will be licensed under the Apache 2.0 license.