Skip to content

feat: unified data interface (ADR 0001) — hydrodatasource alignment - #14

Merged
OuyangWenyu merged 44 commits into
mainfrom
feat/unified-data-interface
Aug 15, 2026
Merged

OuyangWenyu merged 44 commits into
mainfrom
feat/unified-data-interface

Conversation

@OuyangWenyu

Copy link
Copy Markdown
Contributor

Summary

Align hydrodatasource with the unified data path resolution contract (ADR 0001), reusing hydrodataset as the resolver engine and exposing a one-line open_dataset() factory for downstream projects.

  • Add hydrodatasource/configs/data_resolver.py as a thin wrapper: injects HDS READER_ALIASES + HDS_DATASETS, delegates to hydrodataset
  • Migrate all 11 reader classes to URI-only constructors (uri= positional)
  • Add open_dataset(dataset_id) — resolves path + instantiates reader in one call
  • Add _with_hds_extras() helper; resolve_data_path now uses ResolverContext API
  • Re-export open_dataset, ResolverContext, READER_ALIASES, resolve_data_path from package root
  • Remove library-level datasets.yml; in-code HDS_DATASETS is Layer 2, user YAML is Layer 3 override
  • Lazy-load legacy config globals; remove minio/postgres coupling

Related

Usage (after merge)

from hydrodatasource import open_dataset

ds = open_dataset(songliao_event)   # hydrodatasource dataset
ds = open_dataset(camels_us)          # hydrodataset dataset (same entry point)

Test plan

  • uv run pytest tests/test_data_resolver.py tests/test_package_exports.py — 37 passed
  • uv run pytest tests/test_reader_uri_constructors.py — URI constructor tests
  • CI green on this PR
  • Manual smoke: open_dataset(songliao_event) with valid ~/hydro_setting.yml

Slmtdhy and others added 30 commits November 5, 2024 16:23
…rsion logic; optimize the selection logic of long-term data sets
…urce reader classes

- Register all HydroData subclasses: floodevent, selfmade, longterm, forecast,
  station, tghydro, gages, grdc, rainfall, crd, rsvrinflow
- Implement resolve_data_path() following same contract as hydromodel ADR 0001
- Merge hydrodataset's READER_ALIASES (37 entries) for a combined 44+ registry
- Re-export DatasetResolutionError from hydrodataset
- Add comprehensive tests (19 tests covering aliases, errors, local/cloud resolution)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add tool.uv.sources pointing to local hydrodataset for development
- Include spec document for unified data interface alignment

Co-Authored-By: Claude <noreply@anthropic.com>
- Register songliao_event → floodevent reader with relative path
- Follows ADR 0001 format: dataset id → reader alias + relative path

Co-Authored-By: Claude <noreply@anthropic.com>
- Remove postgres globals (POSTGRES_PARAM, PS, get_postgres_connection)
- Remove minio clients unused externally (RO, S3, MC)
- Remove unused constants (STATION_BUCKET, STATION_OBJECT, GRID_INTERIM_BUCKET)
- Remove minio/postgres validation from read_setting()
- Add bridge: translate new storage.* format → legacy local_data_path keys
- Add pure-function APIs: get_local_root(), get_cache_dir(), get_storage_config()
- Delegate to hydrodataset.configs.settings for new-format resolution

Co-Authored-By: Claude <noreply@anthropic.com>
- HydroData base: accept optional uri kwarg, bypass os.path.join()
- All subclasses pass uri through to super().__init__()
- Fix FloodEventDatasource: remove leftover download=False kwarg
- Add pathlib.Path import to data_source.py
- All classes verified via inspect.signature check

Co-Authored-By: Claude <noreply@anthropic.com>
…r from package

Co-Authored-By: Claude <noreply@anthropic.com>
… exports

- test_reader_uri_constructors.py: 27 tests covering HydroData base,
  Pattern A (7 subclasses via inspect.signature), Pattern B (4 subclasses
  via mock), and SelfMadeHydroDataset real instantiation with tmp_path
- test_config_api.py: 17 tests for read_setting validation,
  get_local_root/get_cache_dir/get_storage_config APIs, backward-compat
  globals, and format bridging
- test_package_exports.py: 7 tests verifying READER_ALIASES,
  resolve_data_path, and DatasetResolutionError are importable from
  hydrodatasource top-level package, with merged alias counts

All 70 new tests (51 new + 19 existing test_data_resolver.py) pass GREEN.
…/return bugs

- data_resolver.py: drop private _load_registry import, use in-memory
  _HDS_DATASETS + extra_registry_dicts injection. Add local_root passthrough.
  Remove _load_yaml/_validate_relative_path/parent.parent.parent path chain.
  Net reduction: 164 → 118 lines (-28%).

- floodevent.py: skip os.path.exists with self.head != 'minio' guard for
  S3/cloud URIs. Fix return {} → return None (2 sites) to avoid empty dict
  passing is-not-None gate.

- grdc.py/reservoir_datasets.py/rainfall_reader.py/rsvr_inflow_reader.py:
  add self.dataset_name in Pattern B readers that bypass super().__init__().

- tests: add test_resolve_with_local_root_override, TestS3UriPathCheck,
  test_pattern_b_readers_have_dataset_name (4 parametrized). Fix mock
  paths for get_local_root/get_storage_config to hydrodataset namespace.
- config.py: replace import-time _init_settings() call with module
  __getattr__ lazy loading. SETTING/CACHE_DIR/LOCAL_DATA_PATH/FS/MINIO_PARAM
  are now initialized on first access, eliminating import-time I/O and S3
  connection attempts.

- CACHE_DIR now delegates to hydrodataset's get_cache_dir(), ensuring
  a single canonical cache path across repos. Removes the divergence
  where CACHE_DIR and get_cache_dir() returned different directories.

- _init_settings() cleans stale __dict__ entries (left by monkeypatch
  teardowns) to keep __getattr__ resolution correct across test runs.

- tests: add test_cache_dir_consistent_with_get_cache_dir
OuyangWenyu and others added 14 commits June 23, 2026 18:33
…reader classes

- Replace (data_path, dataset_name) with single uri parameter in HydroData base
- All 11 reader classes now accept uri (positional for Pattern B, keyword for Pattern A)
- Remove legacy kwargs (data_path, dataset_name, data_folder) from all constructors
- Add S3 URI guard: skip os.path.exists() when head='minio' in FloodEventDatasource
- Add dataset_name auto-derivation from URI basename in HydroData.__init__
- Lazy-load config globals: rename LOCAL_DATA_PATH to LOCAL_ROOT, add warnings
- Add data_resolver.py thin wrapper over hydrodataset public API with extra_registry_dicts
- Add datasets.yml registry with songliao_event → floodevent mapping
- Export READER_ALIASES, resolve_data_path, DatasetResolutionError from package
- Add 74 tests covering URI constructors, config API, data resolver, and package exports
- Align CACHE_DIR with hydrodataset defaults

Closes #13
…er config

Datasets registry lives in _HDS_DATASETS (in-code, Layer 2 of 3-layer cascade).
configs/datasets.yml is a Layer 3 user override — the library does not ship one.

Changes:
- Delete configs/datasets.yml (redundant with _HDS_DATASETS)
- Update data_resolver.py comments to document the 3-layer architecture
- Add tests: resolution without YAML, _HDS_DATASETS entry verification

All 69 relevant tests pass.
Make the dataset registry dict a public name since downstream code
may need to inspect or extend it. Add __all__ to explicitly control
what's exported from the data_resolver module.
…ontext API

Expose a thin open_dataset factory that injects HDS registry/aliases so callers can instantiate readers in one line, and migrate resolve_data_path to the shared ctx-based contract.

Co-authored-by: Cursor <cursoragent@cursor.com>
Read attributes from either attributes.csv (preferred, backward compatible)
or a NetCDF file in the attributes/ dir, unifying csv/nc and local/minio via
a single _read_attributes_df() helper. basins.shp becomes optional in
cache_attributes_xrdataset: fall back to the 'area' column from attributes
when no shape file is present.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On a fresh machine the resolved CACHE_DIR (e.g. ~/.cache/hydrodataset) may not
exist yet; h5netcdf/os.listdir do not create it, so caching attributes/
timeseries raised FileNotFoundError. Create the dir once at CACHE_DIR
resolution with makedirs(exist_ok=True).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Data source hardening:
- require uri in HydroData, reject legacy data_path/dataset_name/data_folder kwargs
- preserve zero-padded basin_id via dtype passthrough in access_fs
- fix streamflow_unit mapping '1MS'->'1M'; add GLOBAL_DIR to LongTermDataset
- floodevent: filter on 'basin' coord (read side), not gage_id
- data_resolver: source=None falls back to storage.default_source
- config: warn when hydro_setting.yml lacks a 'storage' section
- remove dead scripts/example_tg_usage.py

hydroutils 0.2.0 migration (hydro_log module removed):
- replace @hydro_logger decorator with stdlib logging in gages, rainfall_reader,
  rainfall_cleaner, rsvr_inflow_cleaner; fixes latent .info() call on the
  decorator function in gages.prepare_usgs_data
- bump dependency to hydroutils>=0.2.0

Tests:
- add regression tests (test_fix_*, test_hydroutils_compat)
- fix CACHE_DIR isolation and sys.modules teardown pollution
- de-hardcode package export count
Cleaner base:
- initialize origin_df/processed_df; read_data loads CSV with utf-8/gbk fallback
- fix chained-assignment warning in streamflow anomaly_process via .loc
- waterlevel: fix if/elif so moving_grad no longer warns; remove logger_level dead param

Error handling:
- streamflow_cleaner: drop try/except that masked KeyError as NameError (remove WTF print)
- utils: minio_file_list propagates FS errors (graceful [] when S3 unconfigured)
- utils: is_minio_folder normalizes s3:// prefix, handles empty dirs, propagates real errors

Data source:
- HydroData rejects empty uri; streamflow_unit adds 8D mapping and tolerates time_unit=None
- remove 3 dead cargo-cult import pint_xarray lines (fixed full-suite pint flaky test)
- dmca_esr: remove debug prints; scripts: migrate station_dataset_usage to uri=

grdc:
- replace assert with explicit ValueError (safe under -O); numeric (not lexicographic) id ordering
- remove __main__ block that triggered full cache build; drop orphan get_local_root import

Dead code (net -616 lines):
- remove 11 unused utils unit-conversion helpers + 9 orphaned imports
- delete unreferenced reader/minio_api.py module
- remove orphan _rsvr_rolling_window_abrupt_abnormal_rm and deprecated voronoi functions
- strip commented-out debug lines and garbage log call in rainfall_cleaner

Tests:
- add 8 regression test files (cleaner, error handling, nits, pint imports, unit mapping,
  dead code, suggestions, grdc ordering)
- fix data_source one_day_dataset fixture and relax brittle compat assertion

Deps: uv.lock synced for all extras (dev tooling)
…>=0.3.0

README (en/zh):
- describe the unified URI-only design and hydrodataset compatibility (drop internal ADR framing)
- add open_dataset() quick start; URI-only SelfMadeHydroDataset construction
- 11 reader alias table; storage.* config; directory structure with intermediate/stations/8D

docs/:
- reader: open_dataset/resolve_data_path as the main entry, URI-only examples, full alias table
- configs: storage.* unified format + default_source + resolution flow
- api: add data_resolver section and all reader classes
- cleaner: note the functional Cleaner base and correct algorithm names
- remove spec-unified-data-interface.md (design now lives in data_resolver.py docstring)

pyproject: hydrodataset>=0.2.0 -> >=0.3.0 (configs.settings and configs.data_resolver
were introduced in hydrodataset 0.3; the old floor allowed installing an incompatible 0.2.x)
The [tool.uv.sources] table points hydrodataset at a local editable path
(../hydrodataset), which does not exist in CI runners. Add --no-sources so
CI resolves hydrodataset>=0.3.0 from PyPI; local dev keeps the editable path.
uv run also reads [tool.uv.sources]; add --no-sources so it resolves the
project from the already-synced PyPI environment instead of the local
hydrodataset path that does not exist on CI runners.
netCDF4 1.7.2+ wheels bundle HDF5 1.14.6 which is incompatible with the
h5py 3.15.x wheels, causing 'NetCDF: HDF error' when writing with to_netcdf
on Linux CI. Per Unidata/netcdf4-python#1438, h5py 3.14.x is compatible with
netCDF4 1.7.3.
tomllib is Python 3.11+; fall back to the tomli backport on 3.10 and add
tomli as a conditional dev dependency so the pyproject assertion test
collects on Python 3.10 (project supports 3.10+).
config: inject a storage default when the storage section is missing (S4);
correct the lazy-load docstring to note module-level binds still trigger init (I1)

data_resolver: docstring examples use registered dataset ids (S1); document
that resolution requires a storage root and raises DatasetResolutionError
without config (S2)

data_source: add NetCDF attributes fallback for S3 datasets, guarding the
unconfigured-S3 case (S3); drop the dead download param from LongTermDataset
(S6); remove stale data_path comment (N1)

utils: guard is_minio_folder against an unconfigured S3 filesystem (S5)

floodevent: fix stray spaces in the time-range f-string (N2)

readers: remove dead pathlib imports left by the migration (I2)

tests: decouple the package-export count floor from hydrodataset's registry
(N4); add regression tests for review cleanup, suggestions, and S3 fallback
@OuyangWenyu
OuyangWenyu merged commit 9bf7e80 into main Aug 15, 2026
7 checks passed
@OuyangWenyu
OuyangWenyu deleted the feat/unified-data-interface branch August 15, 2026 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants