forcingkit 0.12.1__tar.gz → 0.12.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.
- {forcingkit-0.12.1 → forcingkit-0.12.2}/PKG-INFO +1 -1
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/conf.py +1 -1
- {forcingkit-0.12.1 → forcingkit-0.12.2}/pyproject.toml +1 -1
- {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/main.py +9 -1
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_jobs.py +30 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/uv.lock +1 -1
- {forcingkit-0.12.1 → forcingkit-0.12.2}/.agents/forcingkit.md +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/.claude/CLAUDE.md +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/.env.template +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/.github/copilot-instructions.md +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/.github/workflows/docs.yml +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/.github/workflows/publish.yml +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/.github/workflows/tests.yml +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/.gitignore +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/.markdownlint.yaml +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/.pre-commit-config.yaml +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/.python-version +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/AGENTS.md +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/CONTRIBUTING.md +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/Dockerfile +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/LICENSE +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/README.md +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/client/README.md +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/client/pyproject.toml +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/client/src/forcingkit_client/__init__.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/client/src/forcingkit_client/client.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/client/src/forcingkit_client/schema.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/client/src/forcingkit_client/store.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/client/src/forcingkit_client/trajectory.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/client/tests/conftest.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/client/tests/test_client.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/client/tests/test_schema.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/client/tests/test_trajectory.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/client/uv.lock +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docker-compose.yml +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/Makefile +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/make.bat +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/requirements-docs.txt +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/_extra/CNAME +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/_static/custom.css +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/_static/forcingkit-noreaster-wind.gif +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/atmospheric_forcing.rst +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/client.rst +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/fetchers.rst +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/hycom.rst +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/index.rst +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/layered_mesh.rst +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/mesh_frames.rst +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/necofs.rst +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/nyofs.rst +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/rivers.rst +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/roadmap.rst +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/sources.rst +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/waves.rst +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/main.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/service/__init__.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/__init__.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/jobs.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/routers/bathymetry.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/routers/plotly_api.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/routers/removed.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/routers/stores.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/routers/viewer.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/stac.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/service/run_server.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/__init__.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/data/rivers/northeast_us.json +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/dispatcher.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/coastwatch.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/dbofs.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/erddap.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/hrrr.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/hrrr_atmosphere.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/hrrr_levels.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/hrrr_points.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/hycom.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/hydrography.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/ndbc.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/necofs.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/noaa.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/nwps.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/nyofs.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/usgs.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/usgs_rivers.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/layered_mesh.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/mesh_frames.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/ramps/currents_lis.csv +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/ramps/currents_lis_dark.csv +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/ramps/currents_lis_deep.csv +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/ramps/currents_lis_haline.csv +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/render.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/settings.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/zarr_stream.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/static/app.js +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/static/favicon.ico +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/static/favicon.svg +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/static/index.html +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/static/logo.svg +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/static/preview3d.js +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/static/styles.css +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/__init__.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_auth.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_erddap_fetch.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_mesh_frames_necofs.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_necofs_volume_live.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_nwps_nomads.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_nyofs_obc_fetch.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_obc_offshore_nj.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_ofs_archive_paths.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_s3_roms_fetchers.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/scripts/inspect_dem.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/scripts/inspect_grib.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/scripts/inspect_zarr.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_coastwatch.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_dbofs.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_dispatcher.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_erddap.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_hrrr_atm.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_hrrr_idx.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_hrrr_levels.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_hrrr_points.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_hycom.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_layered_mesh.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_main.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_mesh_frames.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_ndbc.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_necofs_mesh_cache.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_necofs_parent.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_necofs_volume.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_noaa_currents.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_noaa_datum.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_nwps.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_nyofs.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_obc_donor_report.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_obc_pipeline.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_removed_routes.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_settings.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_usgs.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_usgs_rivers.py +0 -0
- {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_zarr_stream.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: forcingkit
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.2
|
|
4
4
|
Summary: Spatiotemporal forcing for computational Earth-system models: selects, regrids and serves model-ready time series with provenance.
|
|
5
5
|
Project-URL: Documentation, https://forcingkit.docs.lhzn.io
|
|
6
6
|
Project-URL: Issues, https://github.com/lhzn-io/forcingkit/issues
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "forcingkit"
|
|
7
|
-
version = "0.12.
|
|
7
|
+
version = "0.12.2"
|
|
8
8
|
description = "Spatiotemporal forcing for computational Earth-system models: selects, regrids and serves model-ready time series with provenance."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
+
import re
|
|
3
4
|
import sys
|
|
4
5
|
import time
|
|
5
6
|
import shutil
|
|
@@ -890,10 +891,17 @@ def generate_atmosphere_levels(request: AtmosphereLevelsRequest) -> Dict[str, An
|
|
|
890
891
|
return {"status": "success", "zarr_id": zarr_id, "zarr_path": final_path}
|
|
891
892
|
|
|
892
893
|
|
|
894
|
+
ATMOSPHERE_DOWNLOAD_ID = re.compile(r"^((atm|lev)_)?[0-9a-f]{12}$")
|
|
895
|
+
|
|
896
|
+
|
|
893
897
|
@app.get("/api/v1/atmosphere/download/{zarr_id}")
|
|
894
898
|
def download_atmosphere(zarr_id: str):
|
|
899
|
+
"""The surface (`atm_`) or lower-atmosphere (`lev_`) store as a zip; a bare hash means
|
|
900
|
+
`atm_`."""
|
|
901
|
+
if not ATMOSPHERE_DOWNLOAD_ID.match(zarr_id):
|
|
902
|
+
raise HTTPException(status_code=400, detail="invalid atmosphere store id")
|
|
895
903
|
cache_dir = _atmosphere_cache_dir()
|
|
896
|
-
search_id = zarr_id if
|
|
904
|
+
search_id = zarr_id if "_" in zarr_id else f"atm_{zarr_id}"
|
|
897
905
|
zarr_path = os.path.join(cache_dir, f"{search_id}.zarr")
|
|
898
906
|
if not os.path.isdir(zarr_path):
|
|
899
907
|
raise HTTPException(status_code=404, detail="Atmosphere Zarr store not found.")
|
|
@@ -215,6 +215,36 @@ def test_waves_out_of_reach_is_404(client, monkeypatch):
|
|
|
215
215
|
assert response.status_code == 404 and "no NWPS cycle" in response.json()["detail"]
|
|
216
216
|
|
|
217
217
|
|
|
218
|
+
@pytest.mark.parametrize("prefix", ["atm_", "lev_"])
|
|
219
|
+
def test_atmosphere_download_serves_surface_and_levels(client, tmp_path, prefix):
|
|
220
|
+
import io
|
|
221
|
+
import zipfile
|
|
222
|
+
|
|
223
|
+
zarr_id = prefix + "0123456789ab"
|
|
224
|
+
_write_store(str(tmp_path / f"{zarr_id}.zarr"))
|
|
225
|
+
response = client.get(f"/api/v1/atmosphere/download/{zarr_id}")
|
|
226
|
+
assert response.status_code == 200
|
|
227
|
+
assert f'filename="{zarr_id}.zip"' in response.headers["content-disposition"]
|
|
228
|
+
assert ".zattrs" in zipfile.ZipFile(io.BytesIO(response.content)).namelist()
|
|
229
|
+
if prefix == "atm_": # a bare hash still means the surface store
|
|
230
|
+
assert client.get("/api/v1/atmosphere/download/0123456789ab").status_code == 200
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_atmosphere_download_refuses_other_ids(client):
|
|
234
|
+
for bad in (
|
|
235
|
+
"obc_0123456789ab",
|
|
236
|
+
"lev_0123",
|
|
237
|
+
"..%2Fatm_0123456789ab",
|
|
238
|
+
"wav_0123456789ab",
|
|
239
|
+
):
|
|
240
|
+
assert client.get(f"/api/v1/atmosphere/download/{bad}").status_code in (
|
|
241
|
+
400,
|
|
242
|
+
404,
|
|
243
|
+
)
|
|
244
|
+
assert client.get("/api/v1/atmosphere/download/obc_0123456789ab").status_code == 400
|
|
245
|
+
assert client.get("/api/v1/atmosphere/download/lev_000000000000").status_code == 404
|
|
246
|
+
|
|
247
|
+
|
|
218
248
|
def test_store_files_are_served(client, tmp_path):
|
|
219
249
|
_write_store(str(tmp_path / "obc_0123456789ab.zarr"))
|
|
220
250
|
base = "/api/v1/stores/obc_0123456789ab.zarr"
|
|
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
|
|
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
|