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.
Files changed (140) hide show
  1. {forcingkit-0.12.1 → forcingkit-0.12.2}/PKG-INFO +1 -1
  2. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/conf.py +1 -1
  3. {forcingkit-0.12.1 → forcingkit-0.12.2}/pyproject.toml +1 -1
  4. {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/main.py +9 -1
  5. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_jobs.py +30 -0
  6. {forcingkit-0.12.1 → forcingkit-0.12.2}/uv.lock +1 -1
  7. {forcingkit-0.12.1 → forcingkit-0.12.2}/.agents/forcingkit.md +0 -0
  8. {forcingkit-0.12.1 → forcingkit-0.12.2}/.claude/CLAUDE.md +0 -0
  9. {forcingkit-0.12.1 → forcingkit-0.12.2}/.env.template +0 -0
  10. {forcingkit-0.12.1 → forcingkit-0.12.2}/.github/copilot-instructions.md +0 -0
  11. {forcingkit-0.12.1 → forcingkit-0.12.2}/.github/workflows/docs.yml +0 -0
  12. {forcingkit-0.12.1 → forcingkit-0.12.2}/.github/workflows/publish.yml +0 -0
  13. {forcingkit-0.12.1 → forcingkit-0.12.2}/.github/workflows/tests.yml +0 -0
  14. {forcingkit-0.12.1 → forcingkit-0.12.2}/.gitignore +0 -0
  15. {forcingkit-0.12.1 → forcingkit-0.12.2}/.markdownlint.yaml +0 -0
  16. {forcingkit-0.12.1 → forcingkit-0.12.2}/.pre-commit-config.yaml +0 -0
  17. {forcingkit-0.12.1 → forcingkit-0.12.2}/.python-version +0 -0
  18. {forcingkit-0.12.1 → forcingkit-0.12.2}/AGENTS.md +0 -0
  19. {forcingkit-0.12.1 → forcingkit-0.12.2}/CONTRIBUTING.md +0 -0
  20. {forcingkit-0.12.1 → forcingkit-0.12.2}/Dockerfile +0 -0
  21. {forcingkit-0.12.1 → forcingkit-0.12.2}/LICENSE +0 -0
  22. {forcingkit-0.12.1 → forcingkit-0.12.2}/README.md +0 -0
  23. {forcingkit-0.12.1 → forcingkit-0.12.2}/client/README.md +0 -0
  24. {forcingkit-0.12.1 → forcingkit-0.12.2}/client/pyproject.toml +0 -0
  25. {forcingkit-0.12.1 → forcingkit-0.12.2}/client/src/forcingkit_client/__init__.py +0 -0
  26. {forcingkit-0.12.1 → forcingkit-0.12.2}/client/src/forcingkit_client/client.py +0 -0
  27. {forcingkit-0.12.1 → forcingkit-0.12.2}/client/src/forcingkit_client/schema.py +0 -0
  28. {forcingkit-0.12.1 → forcingkit-0.12.2}/client/src/forcingkit_client/store.py +0 -0
  29. {forcingkit-0.12.1 → forcingkit-0.12.2}/client/src/forcingkit_client/trajectory.py +0 -0
  30. {forcingkit-0.12.1 → forcingkit-0.12.2}/client/tests/conftest.py +0 -0
  31. {forcingkit-0.12.1 → forcingkit-0.12.2}/client/tests/test_client.py +0 -0
  32. {forcingkit-0.12.1 → forcingkit-0.12.2}/client/tests/test_schema.py +0 -0
  33. {forcingkit-0.12.1 → forcingkit-0.12.2}/client/tests/test_trajectory.py +0 -0
  34. {forcingkit-0.12.1 → forcingkit-0.12.2}/client/uv.lock +0 -0
  35. {forcingkit-0.12.1 → forcingkit-0.12.2}/docker-compose.yml +0 -0
  36. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/Makefile +0 -0
  37. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/make.bat +0 -0
  38. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/requirements-docs.txt +0 -0
  39. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/_extra/CNAME +0 -0
  40. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/_static/custom.css +0 -0
  41. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/_static/forcingkit-noreaster-wind.gif +0 -0
  42. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/atmospheric_forcing.rst +0 -0
  43. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/client.rst +0 -0
  44. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/fetchers.rst +0 -0
  45. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/hycom.rst +0 -0
  46. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/index.rst +0 -0
  47. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/layered_mesh.rst +0 -0
  48. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/mesh_frames.rst +0 -0
  49. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/necofs.rst +0 -0
  50. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/nyofs.rst +0 -0
  51. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/rivers.rst +0 -0
  52. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/roadmap.rst +0 -0
  53. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/sources.rst +0 -0
  54. {forcingkit-0.12.1 → forcingkit-0.12.2}/docs/source/waves.rst +0 -0
  55. {forcingkit-0.12.1 → forcingkit-0.12.2}/main.py +0 -0
  56. {forcingkit-0.12.1 → forcingkit-0.12.2}/service/__init__.py +0 -0
  57. {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/__init__.py +0 -0
  58. {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/jobs.py +0 -0
  59. {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/routers/bathymetry.py +0 -0
  60. {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/routers/plotly_api.py +0 -0
  61. {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/routers/removed.py +0 -0
  62. {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/routers/stores.py +0 -0
  63. {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/routers/viewer.py +0 -0
  64. {forcingkit-0.12.1 → forcingkit-0.12.2}/service/forcingkit_serve/stac.py +0 -0
  65. {forcingkit-0.12.1 → forcingkit-0.12.2}/service/run_server.py +0 -0
  66. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/__init__.py +0 -0
  67. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/data/rivers/northeast_us.json +0 -0
  68. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/dispatcher.py +0 -0
  69. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/coastwatch.py +0 -0
  70. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/dbofs.py +0 -0
  71. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/erddap.py +0 -0
  72. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/hrrr.py +0 -0
  73. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/hrrr_atmosphere.py +0 -0
  74. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/hrrr_levels.py +0 -0
  75. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/hrrr_points.py +0 -0
  76. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/hycom.py +0 -0
  77. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/hydrography.py +0 -0
  78. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/ndbc.py +0 -0
  79. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/necofs.py +0 -0
  80. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/noaa.py +0 -0
  81. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/nwps.py +0 -0
  82. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/nyofs.py +0 -0
  83. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/usgs.py +0 -0
  84. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/fetchers/usgs_rivers.py +0 -0
  85. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/layered_mesh.py +0 -0
  86. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/mesh_frames.py +0 -0
  87. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/ramps/currents_lis.csv +0 -0
  88. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/ramps/currents_lis_dark.csv +0 -0
  89. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/ramps/currents_lis_deep.csv +0 -0
  90. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/ramps/currents_lis_haline.csv +0 -0
  91. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/render.py +0 -0
  92. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/settings.py +0 -0
  93. {forcingkit-0.12.1 → forcingkit-0.12.2}/src/forcingkit/zarr_stream.py +0 -0
  94. {forcingkit-0.12.1 → forcingkit-0.12.2}/static/app.js +0 -0
  95. {forcingkit-0.12.1 → forcingkit-0.12.2}/static/favicon.ico +0 -0
  96. {forcingkit-0.12.1 → forcingkit-0.12.2}/static/favicon.svg +0 -0
  97. {forcingkit-0.12.1 → forcingkit-0.12.2}/static/index.html +0 -0
  98. {forcingkit-0.12.1 → forcingkit-0.12.2}/static/logo.svg +0 -0
  99. {forcingkit-0.12.1 → forcingkit-0.12.2}/static/preview3d.js +0 -0
  100. {forcingkit-0.12.1 → forcingkit-0.12.2}/static/styles.css +0 -0
  101. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/__init__.py +0 -0
  102. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_auth.py +0 -0
  103. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_erddap_fetch.py +0 -0
  104. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_mesh_frames_necofs.py +0 -0
  105. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_necofs_volume_live.py +0 -0
  106. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_nwps_nomads.py +0 -0
  107. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_nyofs_obc_fetch.py +0 -0
  108. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_obc_offshore_nj.py +0 -0
  109. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_ofs_archive_paths.py +0 -0
  110. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/integration/test_s3_roms_fetchers.py +0 -0
  111. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/scripts/inspect_dem.py +0 -0
  112. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/scripts/inspect_grib.py +0 -0
  113. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/scripts/inspect_zarr.py +0 -0
  114. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_coastwatch.py +0 -0
  115. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_dbofs.py +0 -0
  116. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_dispatcher.py +0 -0
  117. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_erddap.py +0 -0
  118. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_hrrr_atm.py +0 -0
  119. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_hrrr_idx.py +0 -0
  120. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_hrrr_levels.py +0 -0
  121. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_hrrr_points.py +0 -0
  122. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_hycom.py +0 -0
  123. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_layered_mesh.py +0 -0
  124. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_main.py +0 -0
  125. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_mesh_frames.py +0 -0
  126. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_ndbc.py +0 -0
  127. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_necofs_mesh_cache.py +0 -0
  128. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_necofs_parent.py +0 -0
  129. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_necofs_volume.py +0 -0
  130. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_noaa_currents.py +0 -0
  131. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_noaa_datum.py +0 -0
  132. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_nwps.py +0 -0
  133. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_nyofs.py +0 -0
  134. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_obc_donor_report.py +0 -0
  135. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_obc_pipeline.py +0 -0
  136. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_removed_routes.py +0 -0
  137. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_settings.py +0 -0
  138. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_usgs.py +0 -0
  139. {forcingkit-0.12.1 → forcingkit-0.12.2}/tests/unit/test_usgs_rivers.py +0 -0
  140. {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.1
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
@@ -9,7 +9,7 @@ sys.path.insert(0, os.path.abspath("../../src"))
9
9
  project = "forcingkit"
10
10
  copyright = "2026, Long Horizon Observatory"
11
11
  author = "Daniel Fry"
12
- release = "0.12.1"
12
+ release = "0.12.2"
13
13
 
14
14
  extensions = [
15
15
  "sphinx.ext.autodoc",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "forcingkit"
7
- version = "0.12.1"
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 zarr_id.startswith("atm_") else f"atm_{zarr_id}"
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"
@@ -1000,7 +1000,7 @@ wheels = [
1000
1000
 
1001
1001
  [[package]]
1002
1002
  name = "forcingkit"
1003
- version = "0.12.1"
1003
+ version = "0.12.2"
1004
1004
  source = { editable = "." }
1005
1005
  dependencies = [
1006
1006
  { name = "bottleneck" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes