easysnowdata 0.3.1__tar.gz → 0.3.2__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.
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/CHANGELOG.md +18 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/CITATION.cff +2 -2
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/PKG-INFO +1 -1
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/REVAMP_PLAN.md +6 -4
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/_version.py +2 -2
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/archive.py +133 -42
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/pyproject.toml +1 -1
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/stations/test_archive.py +89 -8
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/.editorconfig +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/.gitignore +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/.pre-commit-config.yaml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/LICENSE +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/POTENTIAL_DATA_PRODUCTS_SOURCES_AND_EXAMPLES.md +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/README.md +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/WATCHLIST.toml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/_gdal.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/aoi.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/auth/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/auth/_base.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/auth/earthdata.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/auth/earthengine.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/auth/nve.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/auth/planet.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/auth/planetary_computer.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/catalog/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/catalog/_access.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/catalog/_models.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/catalog/_registry.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/catalog/health.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/catalog/pages.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/climate/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/climate/era5.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/climate/koppen_geiger.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/config.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/hydro/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/hydro/basins.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/land/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/land/forest_cover.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/land/landcover.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/land/nlcd.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/optical/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/optical/hls.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/optical/planetscope.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/optical/sentinel2.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/plotting/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/processing/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/processing/categorical.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/processing/contract.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/processing/masks.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/processing/optical.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/processing/sar.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/processing/snow.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/processing/wateryear.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/providers/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/providers/earthdata.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/providers/gee.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/providers/planet.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/providers/raster_http.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/providers/stac.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/providers/vector_http.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/providers/zarr_cloud.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/sar/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/sar/sentinel1.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/snow/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/snow/modis.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/snow/mountain_snow_mask.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/snow/snodas.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/snow/snow_classification.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/snow/ucla_sr.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/snow/viirs.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/_adapter.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/_catalog.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/_frames.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/clients/README.md +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/clients/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/clients/_common.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/clients/awdb/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/clients/awdb/awdb_client.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/clients/cdec/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/clients/cdec/cdec_client.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/clients/databc/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/clients/databc/databc_client.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/clients/nve/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/clients/nve/nve_client.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/clients/yukon/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/clients/yukon/yukon_client.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/networks.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/temporal.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/terrain/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/terrain/chili.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/terrain/dem.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/scripts/check_data_sources.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/scripts/feedstock_requirements.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/scripts/make_montage.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/scripts/report_health.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/scripts/update_readme_status.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/scripts/watch.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/cassettes/test_recorded/test_cmr_lpcloud_hls_search_is_open.yaml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/cassettes/test_recorded/test_earth_search_sentinel2_search_with_time.yaml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/cassettes/test_recorded/test_health_first_byte_probe_replays.yaml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/cassettes/test_recorded/test_health_stac_probe_replays.yaml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/cassettes/test_recorded/test_planetary_computer_dem_search.yaml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/conftest.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/fixtures/make_fixtures.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/static/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/static/cassettes/test_land_landcover/TestSearchRecorded.test_search_filters_by_version.yaml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/static/cassettes/test_terrain_dem/TestSearchRecorded.test_search_on_earth_search.yaml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/static/cassettes/test_terrain_dem/TestSearchRecorded.test_search_returns_tiles.yaml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/static/conftest.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/static/make_fixtures.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/static/test_hydro_basins.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/static/test_land_forest_cover.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/static/test_land_landcover.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/static/test_land_nlcd.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/static/test_snow_classification.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/static/test_snow_mountain_snow_mask.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/static/test_terrain_chili.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/static/test_terrain_dem.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/static/test_theme_common.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/stations/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/stations/conftest.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/stations/fixtures/make_fixtures.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/stations/test_adapter.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/stations/test_adapter_offline.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/stations/test_awdb_client.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/stations/test_cdec_client.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/stations/test_clients_offline.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/stations/test_databc_client.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/stations/test_nve_client.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/stations/test_yukon_client.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_aoi.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_auth.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_catalog.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_config.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_credentials.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_docs_pages.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_easysnowdata.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_edges.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_feedstock_requirements.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_health_reporting.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_montage.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_plotting.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_processing.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_providers.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_recorded.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_temporal.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/test_watch.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/__init__.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/cassettes/test_hls/test_search_cmr_lpcloud_is_open_and_labels_products.yaml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/cassettes/test_hls/test_search_planetary_computer_mirror.yaml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/cassettes/test_sentinel1/test_search_opera_rtc_is_open.yaml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/cassettes/test_sentinel1/test_search_planetary_computer.yaml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/cassettes/test_sentinel2/test_search_earth_search_collection_1.yaml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/cassettes/test_sentinel2/test_search_planetary_computer.yaml +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/conftest.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/fixtures/make_fixtures.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/test_contract.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/test_era5.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/test_hls.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/test_koppen_geiger.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/test_modis.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/test_planetscope.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/test_sentinel1.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/test_sentinel2.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/test_snodas.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/test_ucla_sr.py +0 -0
- {easysnowdata-0.3.1 → easysnowdata-0.3.2}/tests/timeseries/test_viirs.py +0 -0
|
@@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
8
|
<!-- insertion marker -->
|
|
9
|
+
## [v0.3.1](https://github.com/egagli/easysnowdata/releases/tag/v0.3.1) - 2026-09-22
|
|
10
|
+
|
|
11
|
+
<small>[Compare with v0.2.0](https://github.com/egagli/easysnowdata/compare/v0.2.0...v0.3.1)</small>
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- fix(planetscope): make a real order work end to end; gallery renders a real scene ([bd25c45](https://github.com/egagli/easysnowdata/commit/bd25c45c4212db5b1a5e212dec2fe295a9f1f5d6) by egagli).
|
|
16
|
+
- fix(archive): open a scheme-less store path as a local store ([d71b0ee](https://github.com/egagli/easysnowdata/commit/d71b0ee753cef56ec9fdc0dc248b85fda202b5b5) by Eric Gagliano).
|
|
17
|
+
- fix(sar): never fuse Sentinel-1 geometries — one raster per relative orbit ([c9fbe53](https://github.com/egagli/easysnowdata/commit/c9fbe53433a9ed8a36edadca1f72cafe67b387d8) by egagli).
|
|
18
|
+
- fix(hls): a failed band read raises instead of filling the Dataset with nodata ([101d97f](https://github.com/egagli/easysnowdata/commit/101d97f5aa482ed74c4185486dbc8a3710c4fb26) by egagli).
|
|
19
|
+
- fix(auth): log in at URS with the netrc for GDAL reads whenever a password exists ([1b7a1a1](https://github.com/egagli/easysnowdata/commit/1b7a1a13a66f3e71288967e41dcc61723dac5922) by egagli).
|
|
20
|
+
- fix(sar): drop the nominal Sentinel-1 heading and the constant 39° incidence angle ([736499e](https://github.com/egagli/easysnowdata/commit/736499e0fcfdef3c1f4bfdc9ea33c5417198806a) by egagli).
|
|
21
|
+
- fix(snow): Terra-only MODIS mosaics from Planetary Computer; real NSIDC-0768 file names ([8bbbdbd](https://github.com/egagli/easysnowdata/commit/8bbbdbdbe6d6c11411c867b07f5d578a672233ec) by egagli).
|
|
22
|
+
- fix(sar): incidence angle from each track's geometry, with the right sign; OPERA reads work ([d12dc9f](https://github.com/egagli/easysnowdata/commit/d12dc9f37684c9dfb73287c08e0c2472f11254f3) by egagli).
|
|
23
|
+
- fix(health): probe Planet through the SDK, send a User-Agent on first-byte probes ([e5aeb82](https://github.com/egagli/easysnowdata/commit/e5aeb82fc28802f3539aaea69c93e3d185b32553) by egagli).
|
|
24
|
+
- fix(stations): finish the transfer — format the clients, relabel the probes, refresh the archive ([fb1bf3e](https://github.com/egagli/easysnowdata/commit/fb1bf3e191e27ee7453e23830a99bd6c85731016) by egagli).
|
|
25
|
+
- fix(deprecation): the shims promised removal in the release they shipped in ([24e93b5](https://github.com/egagli/easysnowdata/commit/24e93b549e928cb6cfabd103b8ae40f9d1581b07) by egagli).
|
|
26
|
+
|
|
9
27
|
## [v0.2.0](https://github.com/egagli/easysnowdata/releases/tag/v0.2.0) - 2026-09-17
|
|
10
28
|
|
|
11
29
|
<small>[Compare with v0.0.26](https://github.com/egagli/easysnowdata/compare/v0.0.26...v0.2.0)</small>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: easysnowdata
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: A Python package to easily retrieve data relevant to snow science
|
|
5
5
|
Project-URL: Homepage, https://github.com/egagli/easysnowdata
|
|
6
6
|
Project-URL: Documentation, https://egagli.github.io/easysnowdata
|
|
@@ -912,10 +912,12 @@ that artefact (its `docs/STORAGE.md` and DESIGN.md §6.5). `easysnowdata`
|
|
|
912
912
|
reads the inventory over HTTPS at call time; `archive.load()` reads the
|
|
913
913
|
store by default, fetching only the chunks a request touches (one water year
|
|
914
914
|
of every station 0.7 MB, one station's whole record 0.5 MB, against the
|
|
915
|
-
27 MB bundle), and falls back to the
|
|
916
|
-
when the store cannot be read, so a Pages
|
|
917
|
-
|
|
918
|
-
|
|
915
|
+
27 MB bundle), and falls back with a warning to the bundle attached to that
|
|
916
|
+
repo's latest snapshot release when the store cannot be read, so a Pages
|
|
917
|
+
outage degrades to a possibly-stale snapshot rather than failing (Releases
|
|
918
|
+
are served from outside Pages). The daily-committed bundle on `main` was
|
|
919
|
+
retired once 0.3.2 carried this fallback; 0.2.x and 0.3.1 read that file and
|
|
920
|
+
must upgrade.
|
|
919
921
|
|
|
920
922
|
**What `global_snow_networks` still owes this migration** is tracked in that
|
|
921
923
|
repo as [`docs/EASYSNOWDATA_MIGRATION.md`](https://github.com/egagli/global_snow_networks/blob/main/docs/EASYSNOWDATA_MIGRATION.md):
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 3,
|
|
21
|
+
__version__ = version = '0.3.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 2)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -16,10 +16,12 @@ artefacts that this module reads (§9 step 3):
|
|
|
16
16
|
range requests, so a query fetches the chunks it touches — well under a
|
|
17
17
|
megabyte for either of those — instead of the whole bundle. The default
|
|
18
18
|
route.
|
|
19
|
-
``
|
|
20
|
-
One ``date,wteq_cm,snwd_cm`` CSV per daily-or-better station, bundled
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
``all_station_csvs.tar.xz`` on that repo's latest GitHub Release
|
|
20
|
+
One ``date,wteq_cm,snwd_cm`` CSV per daily-or-better station, bundled:
|
|
21
|
+
the periodic, DOI'd snapshot (a few times a year), about 28 MB and
|
|
22
|
+
whole-file only. The fallback when Pages is down, since Releases are
|
|
23
|
+
served from elsewhere — and the route to use when a result should be
|
|
24
|
+
pinned to a citable snapshot rather than to today's archive.
|
|
23
25
|
|
|
24
26
|
::
|
|
25
27
|
|
|
@@ -64,10 +66,12 @@ __all__ = [
|
|
|
64
66
|
"MANIFEST_URL",
|
|
65
67
|
"PAGES_BASE",
|
|
66
68
|
"PRODUCT",
|
|
69
|
+
"RELEASES_API",
|
|
67
70
|
"REPO",
|
|
68
71
|
"ZARR_URLS",
|
|
69
72
|
"csv_url",
|
|
70
73
|
"inventory",
|
|
74
|
+
"latest_snapshot",
|
|
71
75
|
"load",
|
|
72
76
|
"manifest",
|
|
73
77
|
"network_of",
|
|
@@ -77,7 +81,15 @@ _logger = logging.getLogger(__name__)
|
|
|
77
81
|
|
|
78
82
|
REPO = "https://github.com/egagli/global_snow_networks"
|
|
79
83
|
INVENTORY_URL = f"{REPO}/raw/main/all_snow_stations.geojson"
|
|
80
|
-
|
|
84
|
+
#: The bundled CSVs of the latest snapshot release. Fixed-name asset, so this
|
|
85
|
+
#: URL always resolves to the newest release; :func:`latest_snapshot` asks the
|
|
86
|
+
#: API for the tag and date behind it. Until 2026-09 the bundle was a file
|
|
87
|
+
#: committed daily on ``main``; that stopped once the Pages store took over
|
|
88
|
+
#: the daily role (that repo's docs/STORAGE.md §3).
|
|
89
|
+
ARCHIVE_URL = f"{REPO}/releases/latest/download/all_station_csvs.tar.xz"
|
|
90
|
+
RELEASES_API = (
|
|
91
|
+
"https://api.github.com/repos/egagli/global_snow_networks/releases/latest"
|
|
92
|
+
)
|
|
81
93
|
CSV_BASE = (
|
|
82
94
|
"https://raw.githubusercontent.com/egagli/global_snow_networks/main/data/stations/"
|
|
83
95
|
)
|
|
@@ -100,9 +112,10 @@ _BY_STATION_LIMIT = 64
|
|
|
100
112
|
#: output contract keeps the two-letter names the CSV columns abbreviate.
|
|
101
113
|
_STORE_COLUMNS = {"swe": "wteq_cm", "snow_depth": "snwd_cm"}
|
|
102
114
|
|
|
103
|
-
#: How long a downloaded bundle is trusted before it is fetched again
|
|
104
|
-
#:
|
|
105
|
-
#:
|
|
115
|
+
#: How long a downloaded bundle is trusted before it is fetched again, when
|
|
116
|
+
#: the release it came from could not be identified (the API was unreachable)
|
|
117
|
+
#: and the cache file therefore cannot be named after its tag. A tag-named
|
|
118
|
+
#: file is immutable and never re-fetched.
|
|
106
119
|
ARCHIVE_MAX_AGE = 24 * 3600
|
|
107
120
|
|
|
108
121
|
#: Archive CSV column -> (standardized type, units).
|
|
@@ -269,6 +282,31 @@ def _fetch_json(url: str) -> dict[str, Any]:
|
|
|
269
282
|
return response.json()
|
|
270
283
|
|
|
271
284
|
|
|
285
|
+
def latest_snapshot() -> dict[str, Any]:
|
|
286
|
+
"""The newest snapshot release of ``global_snow_networks``.
|
|
287
|
+
|
|
288
|
+
``{"tag", "published_at", "html_url", "tarball_url"}`` from the GitHub
|
|
289
|
+
releases API — the tag is what a Zenodo version DOI corresponds to, so
|
|
290
|
+
this is what to cite when a result came from the bundle. ``tarball_url``
|
|
291
|
+
is the bundled-CSV asset; ``None`` when the release carries none.
|
|
292
|
+
"""
|
|
293
|
+
release = _fetch_json(RELEASES_API)
|
|
294
|
+
assets = release.get("assets") or []
|
|
295
|
+
tarball = None
|
|
296
|
+
for asset in assets:
|
|
297
|
+
name = str(asset.get("name", ""))
|
|
298
|
+
if name.startswith("all_station_csvs") and name.endswith(".tar.xz"):
|
|
299
|
+
tarball = asset.get("browser_download_url")
|
|
300
|
+
if name == "all_station_csvs.tar.xz":
|
|
301
|
+
break
|
|
302
|
+
return {
|
|
303
|
+
"tag": str(release.get("tag_name") or ""),
|
|
304
|
+
"published_at": str(release.get("published_at") or "")[:10],
|
|
305
|
+
"html_url": str(release.get("html_url") or ""),
|
|
306
|
+
"tarball_url": tarball,
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
|
|
272
310
|
# ── reading the data ─────────────────────────────────────────────────────────
|
|
273
311
|
|
|
274
312
|
|
|
@@ -278,20 +316,53 @@ def _parse_csv(text: str, code: str) -> pd.DataFrame:
|
|
|
278
316
|
return frame.set_index("date").sort_index()
|
|
279
317
|
|
|
280
318
|
|
|
281
|
-
def
|
|
282
|
-
"""
|
|
319
|
+
def _snapshot_tarball() -> tuple[str, dict[str, Any]]:
|
|
320
|
+
"""URL of the latest snapshot's bundle, and what is known about the release.
|
|
321
|
+
|
|
322
|
+
When the releases API cannot be reached the fixed-name download URL is
|
|
323
|
+
used blind — it still resolves to the newest release — and the returned
|
|
324
|
+
metadata is empty.
|
|
325
|
+
"""
|
|
326
|
+
try:
|
|
327
|
+
info = latest_snapshot()
|
|
328
|
+
except Exception as exc: # noqa: BLE001 — the download URL works without it
|
|
329
|
+
_logger.warning(
|
|
330
|
+
"Could not read the latest snapshot release (%s); downloading the "
|
|
331
|
+
"bundle without knowing its tag.",
|
|
332
|
+
exc,
|
|
333
|
+
)
|
|
334
|
+
return ARCHIVE_URL, {}
|
|
335
|
+
return info.get("tarball_url") or ARCHIVE_URL, info
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _from_tarball(
|
|
339
|
+
codes: set[str] | None,
|
|
340
|
+
) -> tuple[dict[str, pd.DataFrame], dict[str, Any]]:
|
|
341
|
+
"""Every wanted station's CSV, out of the latest snapshot's bundle.
|
|
283
342
|
|
|
284
|
-
Downloaded into the package cache by pooch
|
|
285
|
-
|
|
286
|
-
|
|
343
|
+
Downloaded into the package cache by pooch. A bundle whose release tag is
|
|
344
|
+
known is cached under that tag and never re-fetched; one downloaded blind
|
|
345
|
+
is re-fetched after :data:`ARCHIVE_MAX_AGE`. ``EASYSNOWDATA_CACHE_DIR``
|
|
346
|
+
moves the cache root. Returns the frames and the release metadata.
|
|
287
347
|
"""
|
|
348
|
+
url, info = _snapshot_tarball()
|
|
349
|
+
tag = info.get("tag")
|
|
288
350
|
path = providers.raster_http.fetch(
|
|
289
|
-
|
|
290
|
-
"all_station_csvs.tar.xz",
|
|
351
|
+
url,
|
|
352
|
+
f"all_station_csvs-{tag}.tar.xz" if tag else "all_station_csvs.tar.xz",
|
|
291
353
|
subdir="stations",
|
|
292
|
-
max_age=ARCHIVE_MAX_AGE,
|
|
354
|
+
max_age=None if tag else ARCHIVE_MAX_AGE,
|
|
293
355
|
)
|
|
294
|
-
|
|
356
|
+
if tag:
|
|
357
|
+
_logger.info(
|
|
358
|
+
"Reading the bundled station archive of snapshot %s (%s) at %s; the "
|
|
359
|
+
"daily archive on Pages may be ahead of it.",
|
|
360
|
+
tag,
|
|
361
|
+
info.get("published_at") or "date unknown",
|
|
362
|
+
path,
|
|
363
|
+
)
|
|
364
|
+
else:
|
|
365
|
+
_logger.info("Reading the bundled station archive at %s", path)
|
|
295
366
|
frames: dict[str, pd.DataFrame] = {}
|
|
296
367
|
with tarfile.open(path, mode="r:xz") as tar:
|
|
297
368
|
for member in tar:
|
|
@@ -305,7 +376,7 @@ def _from_tarball(codes: set[str] | None) -> dict[str, pd.DataFrame]:
|
|
|
305
376
|
continue
|
|
306
377
|
frames[code] = _parse_csv(handle.read().decode("utf-8"), code)
|
|
307
378
|
_logger.info("Read %d station CSVs from the archive", len(frames))
|
|
308
|
-
return frames
|
|
379
|
+
return frames, {**info, "tarball_url": url}
|
|
309
380
|
|
|
310
381
|
|
|
311
382
|
def _from_csvs(codes: list[str]) -> dict[str, pd.DataFrame]:
|
|
@@ -420,7 +491,7 @@ def load(
|
|
|
420
491
|
time
|
|
421
492
|
Any form :func:`easysnowdata.temporal.parse_time` accepts. Each
|
|
422
493
|
station's series is cut to the window before the dense grid is built,
|
|
423
|
-
so a narrow window is cheap —
|
|
494
|
+
so a narrow window is cheap — on the bundle route it is still downloaded
|
|
424
495
|
whole, because that is how it is published.
|
|
425
496
|
networks
|
|
426
497
|
Keep only these networks.
|
|
@@ -432,12 +503,15 @@ def load(
|
|
|
432
503
|
every station is one or two chunks). Its ``time`` axis is complete
|
|
433
504
|
and daily between the first and last observation, and its values are
|
|
434
505
|
stored as float32 (returned as float64), so they agree with the CSV
|
|
435
|
-
routes to float32 precision. When the store
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
``"github-tarball"`` downloads the
|
|
439
|
-
|
|
440
|
-
|
|
506
|
+
routes to float32 precision. When the store cannot be read and no
|
|
507
|
+
*source* was asked for, the snapshot route below is used instead,
|
|
508
|
+
with a warning naming the snapshot.
|
|
509
|
+
``"github-tarball"`` downloads the ~28 MB bundle of CSVs attached to
|
|
510
|
+
that repo's **latest snapshot release** — a few times a year, each
|
|
511
|
+
with a Zenodo DOI — and reads every wanted CSV out of it. Ask for it
|
|
512
|
+
when a result should be pinned to a citable snapshot; the Dataset's
|
|
513
|
+
``snapshot_tag`` and ``snapshot_published_at`` say which one. Its
|
|
514
|
+
``time`` axis holds only days on which some station observed.
|
|
441
515
|
``"github-csv"`` fetches one CSV per station instead, which needs
|
|
442
516
|
neither a store nor a temporary file.
|
|
443
517
|
hemisphere
|
|
@@ -500,19 +574,24 @@ def load(
|
|
|
500
574
|
if source is not None:
|
|
501
575
|
raise
|
|
502
576
|
# The store is a build artefact of a Pages deploy; the bundle is
|
|
503
|
-
# a
|
|
504
|
-
#
|
|
505
|
-
#
|
|
577
|
+
# a GitHub Release asset, served from elsewhere. So a Pages outage
|
|
578
|
+
# (or a deploy that has not happened yet) degrades to the latest
|
|
579
|
+
# snapshot — possibly months behind, and the warning says so —
|
|
580
|
+
# rather than to an error (REVAMP_PLAN §9.3).
|
|
581
|
+
src = product.source("github-tarball")
|
|
582
|
+
frames, snapshot = _from_tarball(set(codes) if codes is not None else None)
|
|
506
583
|
_logger.warning(
|
|
507
|
-
"Could not read the chunked archive at %s (%s);
|
|
508
|
-
"
|
|
584
|
+
"Could not read the chunked archive at %s (%s); read the bundled "
|
|
585
|
+
"CSVs of snapshot %s (%s) instead, which may be behind the daily "
|
|
586
|
+
"archive.",
|
|
509
587
|
ZARR_URLS[layout],
|
|
510
588
|
exc,
|
|
589
|
+
snapshot.get("tag") or "unknown",
|
|
590
|
+
snapshot.get("published_at") or "date unknown",
|
|
511
591
|
)
|
|
512
|
-
src = product.source("github-tarball")
|
|
513
|
-
frames = _from_tarball(set(codes) if codes is not None else None)
|
|
514
592
|
ds = _to_dataset(frames, wanted_types, codes, inv, hemisphere, window)
|
|
515
|
-
source_url =
|
|
593
|
+
source_url = snapshot["tarball_url"]
|
|
594
|
+
ds.attrs.update(_snapshot_attrs(snapshot))
|
|
516
595
|
else:
|
|
517
596
|
ds = _finish(grid, inv, hemisphere)
|
|
518
597
|
source_url = ZARR_URLS[layout]
|
|
@@ -526,15 +605,25 @@ def load(
|
|
|
526
605
|
ds = _to_dataset(frames, wanted_types, codes, inv, hemisphere, window)
|
|
527
606
|
source_url = CSV_BASE
|
|
528
607
|
else:
|
|
529
|
-
frames = _from_tarball(set(codes) if codes is not None else None)
|
|
608
|
+
frames, snapshot = _from_tarball(set(codes) if codes is not None else None)
|
|
530
609
|
ds = _to_dataset(frames, wanted_types, codes, inv, hemisphere, window)
|
|
531
|
-
source_url =
|
|
610
|
+
source_url = snapshot["tarball_url"]
|
|
611
|
+
ds.attrs.update(_snapshot_attrs(snapshot))
|
|
532
612
|
|
|
533
613
|
ds.attrs.update(contract.provenance(product, src, source_url=source_url))
|
|
534
614
|
ds.attrs["interval"] = "daily"
|
|
535
615
|
return ds
|
|
536
616
|
|
|
537
617
|
|
|
618
|
+
def _snapshot_attrs(snapshot: dict[str, Any]) -> dict[str, str]:
|
|
619
|
+
"""Which snapshot release a bundle-route Dataset came from, for its attrs."""
|
|
620
|
+
return {
|
|
621
|
+
"snapshot_tag": str(snapshot.get("tag") or ""),
|
|
622
|
+
"snapshot_published_at": str(snapshot.get("published_at") or ""),
|
|
623
|
+
"snapshot_url": str(snapshot.get("html_url") or ""),
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
|
|
538
627
|
def _types(variables: Any) -> list[str]:
|
|
539
628
|
known = {type_name for type_name, _ in COLUMNS.values()}
|
|
540
629
|
if variables is None:
|
|
@@ -640,7 +729,8 @@ PRODUCT = Product(
|
|
|
640
729
|
"pre-downloaded and published by global_snow_networks: a normalized "
|
|
641
730
|
"station inventory as GeoJSON, a chunked Zarr store on its Pages site "
|
|
642
731
|
"that a query reads only the touched chunks of, and one CSV per "
|
|
643
|
-
"station bundled into a single ~28 MB archive
|
|
732
|
+
"station bundled into a single ~28 MB archive on each snapshot release. "
|
|
733
|
+
"The fast path for "
|
|
644
734
|
"'everything daily' without hitting five APIs, refreshed daily. Most "
|
|
645
735
|
"stations start around 1980; "
|
|
646
736
|
"a few long snow courses reach back to 1896. SWE and snow depth only; for "
|
|
@@ -676,13 +766,14 @@ PRODUCT = Product(
|
|
|
676
766
|
location=ARCHIVE_URL,
|
|
677
767
|
extent="western US, western Canada, Norway, Yukon, California, BC",
|
|
678
768
|
temporal="1896/present (most stations from ~1980)",
|
|
679
|
-
latency="
|
|
769
|
+
latency="per snapshot release (a few times a year)",
|
|
680
770
|
notes=(
|
|
681
|
-
"one ~28 MB download holding every station CSV
|
|
682
|
-
"
|
|
683
|
-
"Pages store cannot
|
|
771
|
+
"one ~28 MB download holding every station CSV of the latest "
|
|
772
|
+
"snapshot release (each has a Zenodo DOI); cached per tag. The "
|
|
773
|
+
"citable route, and the fallback when the Pages store cannot "
|
|
774
|
+
"be read"
|
|
684
775
|
),
|
|
685
|
-
title="global_snow_networks
|
|
776
|
+
title="global_snow_networks snapshot bundle (latest release)",
|
|
686
777
|
# The inventory and CSV probes were once labelled "SNOTEL/CCSS …"
|
|
687
778
|
# after the retired snotel_ccss_stations entry; the labels were
|
|
688
779
|
# renamed in 0.3 and data_status/history.json rewritten with them,
|
|
@@ -694,7 +785,7 @@ PRODUCT = Product(
|
|
|
694
785
|
),
|
|
695
786
|
Probe(
|
|
696
787
|
"Snow station archive tarball (global_snow_networks)",
|
|
697
|
-
partial(health.http_first_byte,
|
|
788
|
+
partial(health.http_first_byte, RELEASES_API),
|
|
698
789
|
),
|
|
699
790
|
),
|
|
700
791
|
),
|
|
@@ -105,7 +105,7 @@ exclude = [
|
|
|
105
105
|
# The tag it creates is what hatch-vcs reads; the files below are the ones
|
|
106
106
|
# that still carry a literal version.
|
|
107
107
|
[tool.bumpversion]
|
|
108
|
-
current_version = "0.3.
|
|
108
|
+
current_version = "0.3.2"
|
|
109
109
|
commit = true
|
|
110
110
|
tag = true
|
|
111
111
|
|
|
@@ -28,17 +28,45 @@ from .conftest import MORSE_LAKE_CODE, PARADISE_CODE, RAINIER
|
|
|
28
28
|
pytestmark = pytest.mark.allow_hosts(["127.0.0.1"])
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
FAKE_RELEASE = {
|
|
32
|
+
"tag_name": "v2026.09.22",
|
|
33
|
+
"published_at": "2026-09-22T19:31:00Z",
|
|
34
|
+
"html_url": "https://github.com/egagli/global_snow_networks/releases/tag/v2026.09.22",
|
|
35
|
+
"assets": [
|
|
36
|
+
{
|
|
37
|
+
"name": "all_station_csvs-v2026.09.22.tar.xz",
|
|
38
|
+
"browser_download_url": "https://example.test/all_station_csvs-v2026.09.22.tar.xz",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "all_station_csvs.tar.xz",
|
|
42
|
+
"browser_download_url": "https://example.test/all_station_csvs.tar.xz",
|
|
43
|
+
},
|
|
44
|
+
{"name": "by_time.zarr-v2026.09.22.zip", "browser_download_url": "x"},
|
|
45
|
+
],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
31
49
|
@pytest.fixture
|
|
32
50
|
def local_archive(station_fixtures, monkeypatch):
|
|
33
51
|
"""Point the archive module at the fixtures instead of GitHub."""
|
|
34
52
|
monkeypatch.setattr(
|
|
35
53
|
archive, "INVENTORY_URL", str(station_fixtures["inventory"]), raising=False
|
|
36
54
|
)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
fetched: list[dict] = []
|
|
56
|
+
|
|
57
|
+
def fake_fetch(url, fname=None, **kwargs):
|
|
58
|
+
fetched.append({"url": url, "fname": fname, **kwargs})
|
|
59
|
+
return station_fixtures["archive"]
|
|
60
|
+
|
|
61
|
+
monkeypatch.setattr(archive.providers.raster_http, "fetch", fake_fetch)
|
|
62
|
+
station_fixtures["fetched"] = fetched # type: ignore[assignment]
|
|
63
|
+
|
|
64
|
+
def fake_json(url):
|
|
65
|
+
if url == archive.RELEASES_API:
|
|
66
|
+
return FAKE_RELEASE
|
|
67
|
+
raise AssertionError(f"unexpected JSON fetch: {url}")
|
|
68
|
+
|
|
69
|
+
monkeypatch.setattr(archive, "_fetch_json", fake_json)
|
|
42
70
|
monkeypatch.setattr(
|
|
43
71
|
archive,
|
|
44
72
|
"ZARR_URLS",
|
|
@@ -83,7 +111,10 @@ def test_catalog_entry_is_registered_from_this_module():
|
|
|
83
111
|
|
|
84
112
|
def test_urls_point_at_the_published_artefacts():
|
|
85
113
|
assert archive.INVENTORY_URL.endswith("/all_snow_stations.geojson")
|
|
86
|
-
assert archive.ARCHIVE_URL.endswith(
|
|
114
|
+
assert archive.ARCHIVE_URL.endswith(
|
|
115
|
+
"/releases/latest/download/all_station_csvs.tar.xz"
|
|
116
|
+
)
|
|
117
|
+
assert archive.RELEASES_API.endswith("/global_snow_networks/releases/latest")
|
|
87
118
|
assert archive.csv_url(PARADISE_CODE).endswith(f"/stations/{PARADISE_CODE}.csv")
|
|
88
119
|
assert archive.PAGES_BASE == "https://egagli.github.io/global_snow_networks/archive"
|
|
89
120
|
assert archive.ZARR_URLS["by_time"].endswith("/archive/by_time.zarr")
|
|
@@ -184,7 +215,47 @@ def test_tarball_route_builds_the_station_time_dataset(local_archive):
|
|
|
184
215
|
assert ds.sizes == {"station": 3, "time": 7}
|
|
185
216
|
assert ds["swe"].dtype == np.float64
|
|
186
217
|
assert ds.attrs["source"] == "github-tarball"
|
|
187
|
-
|
|
218
|
+
# The bundle is the latest snapshot release's fixed-name asset, and the
|
|
219
|
+
# result says which snapshot it came from.
|
|
220
|
+
assert ds.attrs["source_url"] == "https://example.test/all_station_csvs.tar.xz"
|
|
221
|
+
assert ds.attrs["snapshot_tag"] == "v2026.09.22"
|
|
222
|
+
assert ds.attrs["snapshot_published_at"] == "2026-09-22"
|
|
223
|
+
assert ds.attrs["snapshot_url"].endswith("/releases/tag/v2026.09.22")
|
|
224
|
+
fetched = local_archive["fetched"][-1]
|
|
225
|
+
assert fetched["fname"] == "all_station_csvs-v2026.09.22.tar.xz" # cached per tag
|
|
226
|
+
assert fetched["max_age"] is None # a tagged bundle is immutable
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def test_latest_snapshot_reads_the_release(monkeypatch):
|
|
230
|
+
monkeypatch.setattr(archive, "_fetch_json", lambda url: FAKE_RELEASE)
|
|
231
|
+
info = archive.latest_snapshot()
|
|
232
|
+
assert info["tag"] == "v2026.09.22"
|
|
233
|
+
assert info["published_at"] == "2026-09-22"
|
|
234
|
+
assert info["tarball_url"] == "https://example.test/all_station_csvs.tar.xz"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_latest_snapshot_accepts_a_release_with_only_the_tagged_bundle(monkeypatch):
|
|
238
|
+
release = {**FAKE_RELEASE, "assets": FAKE_RELEASE["assets"][:1]}
|
|
239
|
+
monkeypatch.setattr(archive, "_fetch_json", lambda url: release)
|
|
240
|
+
assert archive.latest_snapshot()["tarball_url"].endswith("-v2026.09.22.tar.xz")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@pytest.mark.recorded
|
|
244
|
+
def test_bundle_route_survives_an_unreachable_releases_api(
|
|
245
|
+
local_archive, monkeypatch, caplog
|
|
246
|
+
):
|
|
247
|
+
def boom(url):
|
|
248
|
+
raise RuntimeError("HTTP 403 rate limited")
|
|
249
|
+
|
|
250
|
+
monkeypatch.setattr(archive, "_fetch_json", boom)
|
|
251
|
+
ds = archive.load(source="github-tarball")
|
|
252
|
+
assert ds.sizes == {"station": 3, "time": 7}
|
|
253
|
+
assert ds.attrs["source_url"] == archive.ARCHIVE_URL # the fixed-name URL, blind
|
|
254
|
+
assert ds.attrs["snapshot_tag"] == ""
|
|
255
|
+
fetched = local_archive["fetched"][-1]
|
|
256
|
+
assert fetched["fname"] == "all_station_csvs.tar.xz"
|
|
257
|
+
assert fetched["max_age"] == archive.ARCHIVE_MAX_AGE
|
|
258
|
+
assert "without knowing its tag" in caplog.text
|
|
188
259
|
|
|
189
260
|
|
|
190
261
|
@pytest.mark.recorded
|
|
@@ -241,7 +312,9 @@ def test_an_unreachable_store_falls_back_to_the_tarball_with_a_warning(
|
|
|
241
312
|
ds = archive.load()
|
|
242
313
|
assert ds.attrs["source"] == "github-tarball"
|
|
243
314
|
assert ds.sizes == {"station": 3, "time": 7}
|
|
244
|
-
assert "
|
|
315
|
+
assert ds.attrs["snapshot_tag"] == "v2026.09.22"
|
|
316
|
+
assert "snapshot v2026.09.22 (2026-09-22)" in caplog.text
|
|
317
|
+
assert "may be behind the daily archive" in caplog.text
|
|
245
318
|
|
|
246
319
|
|
|
247
320
|
@pytest.mark.recorded
|
|
@@ -377,10 +450,18 @@ def test_live_archive_store_route():
|
|
|
377
450
|
assert archive.manifest()["built_from_commit"]
|
|
378
451
|
|
|
379
452
|
|
|
453
|
+
@pytest.mark.live
|
|
454
|
+
def test_live_latest_snapshot_has_a_bundle():
|
|
455
|
+
info = archive.latest_snapshot()
|
|
456
|
+
assert info["tag"].startswith("v")
|
|
457
|
+
assert info["tarball_url"] and info["tarball_url"].endswith(".tar.xz")
|
|
458
|
+
|
|
459
|
+
|
|
380
460
|
@pytest.mark.live
|
|
381
461
|
def test_live_archive_routes_agree():
|
|
382
462
|
when = "2024-01-01/2024-03-31"
|
|
383
463
|
bundled = archive.load([PARADISE_CODE], source="github-tarball", time=when)
|
|
464
|
+
assert bundled.attrs["snapshot_tag"]
|
|
384
465
|
per_station = archive.load([PARADISE_CODE], source="github-csv", time=when)
|
|
385
466
|
chunked = archive.load([PARADISE_CODE], source="github-pages-zarr", time=when)
|
|
386
467
|
assert bundled.attrs["source"] == "github-tarball"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/clients/databc/databc_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{easysnowdata-0.3.1 → easysnowdata-0.3.2}/easysnowdata/stations/clients/yukon/yukon_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|