forcingkit 0.11.0__tar.gz → 0.12.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. {forcingkit-0.11.0 → forcingkit-0.12.1}/.github/workflows/tests.yml +15 -0
  2. {forcingkit-0.11.0 → forcingkit-0.12.1}/AGENTS.md +6 -0
  3. {forcingkit-0.11.0 → forcingkit-0.12.1}/PKG-INFO +6 -2
  4. {forcingkit-0.11.0 → forcingkit-0.12.1}/README.md +2 -1
  5. forcingkit-0.12.1/client/README.md +27 -0
  6. forcingkit-0.12.1/client/pyproject.toml +53 -0
  7. forcingkit-0.12.1/client/src/forcingkit_client/__init__.py +18 -0
  8. forcingkit-0.12.1/client/src/forcingkit_client/client.py +512 -0
  9. forcingkit-0.12.1/client/src/forcingkit_client/schema.py +56 -0
  10. forcingkit-0.12.1/client/src/forcingkit_client/store.py +73 -0
  11. forcingkit-0.12.1/client/src/forcingkit_client/trajectory.py +264 -0
  12. forcingkit-0.12.1/client/tests/conftest.py +73 -0
  13. forcingkit-0.12.1/client/tests/test_client.py +293 -0
  14. forcingkit-0.12.1/client/tests/test_schema.py +44 -0
  15. forcingkit-0.12.1/client/tests/test_trajectory.py +95 -0
  16. forcingkit-0.12.1/client/uv.lock +944 -0
  17. forcingkit-0.12.1/docs/source/client.rst +133 -0
  18. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/source/conf.py +1 -1
  19. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/source/fetchers.rst +19 -0
  20. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/source/index.rst +16 -0
  21. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/source/necofs.rst +18 -2
  22. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/source/roadmap.rst +15 -0
  23. forcingkit-0.12.1/docs/source/waves.rst +124 -0
  24. {forcingkit-0.11.0 → forcingkit-0.12.1}/pyproject.toml +7 -1
  25. forcingkit-0.12.1/service/forcingkit_serve/jobs.py +119 -0
  26. {forcingkit-0.11.0 → forcingkit-0.12.1}/service/forcingkit_serve/main.py +308 -70
  27. forcingkit-0.12.1/service/forcingkit_serve/routers/stores.py +43 -0
  28. forcingkit-0.12.1/service/forcingkit_serve/stac.py +100 -0
  29. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/dispatcher.py +68 -0
  30. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/fetchers/necofs.py +145 -25
  31. forcingkit-0.12.1/src/forcingkit/fetchers/nwps.py +517 -0
  32. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/settings.py +11 -0
  33. forcingkit-0.12.1/tests/integration/test_nwps_nomads.py +83 -0
  34. forcingkit-0.12.1/tests/unit/test_jobs.py +237 -0
  35. forcingkit-0.12.1/tests/unit/test_necofs_mesh_cache.py +119 -0
  36. forcingkit-0.12.1/tests/unit/test_nwps.py +370 -0
  37. {forcingkit-0.11.0 → forcingkit-0.12.1}/uv.lock +466 -1
  38. {forcingkit-0.11.0 → forcingkit-0.12.1}/.agents/forcingkit.md +0 -0
  39. {forcingkit-0.11.0 → forcingkit-0.12.1}/.claude/CLAUDE.md +0 -0
  40. {forcingkit-0.11.0 → forcingkit-0.12.1}/.env.template +0 -0
  41. {forcingkit-0.11.0 → forcingkit-0.12.1}/.github/copilot-instructions.md +0 -0
  42. {forcingkit-0.11.0 → forcingkit-0.12.1}/.github/workflows/docs.yml +0 -0
  43. {forcingkit-0.11.0 → forcingkit-0.12.1}/.github/workflows/publish.yml +0 -0
  44. {forcingkit-0.11.0 → forcingkit-0.12.1}/.gitignore +0 -0
  45. {forcingkit-0.11.0 → forcingkit-0.12.1}/.markdownlint.yaml +0 -0
  46. {forcingkit-0.11.0 → forcingkit-0.12.1}/.pre-commit-config.yaml +0 -0
  47. {forcingkit-0.11.0 → forcingkit-0.12.1}/.python-version +0 -0
  48. {forcingkit-0.11.0 → forcingkit-0.12.1}/CONTRIBUTING.md +0 -0
  49. {forcingkit-0.11.0 → forcingkit-0.12.1}/Dockerfile +0 -0
  50. {forcingkit-0.11.0 → forcingkit-0.12.1}/LICENSE +0 -0
  51. {forcingkit-0.11.0 → forcingkit-0.12.1}/docker-compose.yml +0 -0
  52. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/Makefile +0 -0
  53. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/make.bat +0 -0
  54. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/requirements-docs.txt +0 -0
  55. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/source/_extra/CNAME +0 -0
  56. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/source/_static/custom.css +0 -0
  57. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/source/_static/forcingkit-noreaster-wind.gif +0 -0
  58. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/source/atmospheric_forcing.rst +0 -0
  59. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/source/hycom.rst +0 -0
  60. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/source/layered_mesh.rst +0 -0
  61. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/source/mesh_frames.rst +0 -0
  62. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/source/nyofs.rst +0 -0
  63. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/source/rivers.rst +0 -0
  64. {forcingkit-0.11.0 → forcingkit-0.12.1}/docs/source/sources.rst +0 -0
  65. {forcingkit-0.11.0 → forcingkit-0.12.1}/main.py +0 -0
  66. {forcingkit-0.11.0 → forcingkit-0.12.1}/service/__init__.py +0 -0
  67. {forcingkit-0.11.0 → forcingkit-0.12.1}/service/forcingkit_serve/__init__.py +0 -0
  68. {forcingkit-0.11.0 → forcingkit-0.12.1}/service/forcingkit_serve/routers/bathymetry.py +0 -0
  69. {forcingkit-0.11.0 → forcingkit-0.12.1}/service/forcingkit_serve/routers/plotly_api.py +0 -0
  70. {forcingkit-0.11.0 → forcingkit-0.12.1}/service/forcingkit_serve/routers/removed.py +0 -0
  71. {forcingkit-0.11.0 → forcingkit-0.12.1}/service/forcingkit_serve/routers/viewer.py +0 -0
  72. {forcingkit-0.11.0 → forcingkit-0.12.1}/service/run_server.py +0 -0
  73. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/__init__.py +0 -0
  74. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/data/rivers/northeast_us.json +0 -0
  75. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/fetchers/coastwatch.py +0 -0
  76. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/fetchers/dbofs.py +0 -0
  77. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/fetchers/erddap.py +0 -0
  78. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/fetchers/hrrr.py +0 -0
  79. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/fetchers/hrrr_atmosphere.py +0 -0
  80. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/fetchers/hrrr_levels.py +0 -0
  81. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/fetchers/hrrr_points.py +0 -0
  82. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/fetchers/hycom.py +0 -0
  83. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/fetchers/hydrography.py +0 -0
  84. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/fetchers/ndbc.py +0 -0
  85. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/fetchers/noaa.py +0 -0
  86. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/fetchers/nyofs.py +0 -0
  87. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/fetchers/usgs.py +0 -0
  88. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/fetchers/usgs_rivers.py +0 -0
  89. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/layered_mesh.py +0 -0
  90. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/mesh_frames.py +0 -0
  91. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/ramps/currents_lis.csv +0 -0
  92. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/ramps/currents_lis_dark.csv +0 -0
  93. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/ramps/currents_lis_deep.csv +0 -0
  94. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/ramps/currents_lis_haline.csv +0 -0
  95. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/render.py +0 -0
  96. {forcingkit-0.11.0 → forcingkit-0.12.1}/src/forcingkit/zarr_stream.py +0 -0
  97. {forcingkit-0.11.0 → forcingkit-0.12.1}/static/app.js +0 -0
  98. {forcingkit-0.11.0 → forcingkit-0.12.1}/static/favicon.ico +0 -0
  99. {forcingkit-0.11.0 → forcingkit-0.12.1}/static/favicon.svg +0 -0
  100. {forcingkit-0.11.0 → forcingkit-0.12.1}/static/index.html +0 -0
  101. {forcingkit-0.11.0 → forcingkit-0.12.1}/static/logo.svg +0 -0
  102. {forcingkit-0.11.0 → forcingkit-0.12.1}/static/preview3d.js +0 -0
  103. {forcingkit-0.11.0 → forcingkit-0.12.1}/static/styles.css +0 -0
  104. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/__init__.py +0 -0
  105. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/integration/test_auth.py +0 -0
  106. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/integration/test_erddap_fetch.py +0 -0
  107. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/integration/test_mesh_frames_necofs.py +0 -0
  108. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/integration/test_necofs_volume_live.py +0 -0
  109. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/integration/test_nyofs_obc_fetch.py +0 -0
  110. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/integration/test_obc_offshore_nj.py +0 -0
  111. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/integration/test_ofs_archive_paths.py +0 -0
  112. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/integration/test_s3_roms_fetchers.py +0 -0
  113. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/scripts/inspect_dem.py +0 -0
  114. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/scripts/inspect_grib.py +0 -0
  115. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/scripts/inspect_zarr.py +0 -0
  116. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_coastwatch.py +0 -0
  117. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_dbofs.py +0 -0
  118. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_dispatcher.py +0 -0
  119. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_erddap.py +0 -0
  120. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_hrrr_atm.py +0 -0
  121. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_hrrr_idx.py +0 -0
  122. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_hrrr_levels.py +0 -0
  123. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_hrrr_points.py +0 -0
  124. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_hycom.py +0 -0
  125. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_layered_mesh.py +0 -0
  126. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_main.py +0 -0
  127. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_mesh_frames.py +0 -0
  128. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_ndbc.py +0 -0
  129. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_necofs_parent.py +0 -0
  130. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_necofs_volume.py +0 -0
  131. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_noaa_currents.py +0 -0
  132. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_noaa_datum.py +0 -0
  133. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_nyofs.py +0 -0
  134. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_obc_donor_report.py +0 -0
  135. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_obc_pipeline.py +0 -0
  136. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_removed_routes.py +0 -0
  137. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_settings.py +0 -0
  138. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_usgs.py +0 -0
  139. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_usgs_rivers.py +0 -0
  140. {forcingkit-0.11.0 → forcingkit-0.12.1}/tests/unit/test_zarr_stream.py +0 -0
@@ -33,3 +33,18 @@ jobs:
33
33
  # Run them locally or via workflow_dispatch.
34
34
  - name: Unit tests
35
35
  run: uv run python -m pytest tests/unit -m "not integration" --durations=10
36
+
37
+ client:
38
+ name: Client tests
39
+ runs-on: ubuntu-latest
40
+ steps:
41
+ - uses: actions/checkout@v4
42
+
43
+ - name: Install uv
44
+ uses: astral-sh/setup-uv@v5
45
+ with:
46
+ enable-cache: true
47
+
48
+ # Its own environment, so a client import of anything from the service stack fails here.
49
+ - name: Client tests
50
+ run: uv run --project client python -m pytest client/tests --durations=10
@@ -37,6 +37,12 @@ observations for validation.
37
37
  Use `python -m pytest` rather than the bare `pytest` entry point: the latter can
38
38
  resolve to a system interpreter outside the venv. `tests/conftest.py` puts
39
39
  `src/` and `service/` on `sys.path`, so no `PYTHONPATH` export is needed.
40
+ - **Client**: `client/` is a second distribution, `forcingkit-client` (import
41
+ `forcingkit_client`), with its own `pyproject.toml` and lock. It talks to the service over
42
+ HTTP only and must never import `forcingkit` or take on the service's dependencies. Test it
43
+ with `uv run --project client python -m pytest client/tests`. A change to the job routes
44
+ (`/api/v1/jobs`), the store routes (`/api/v1/stores`) or the STAC Item shape
45
+ (`service/forcingkit_serve/stac.py`) needs the client and its tests updated with it.
40
46
  - **Commits**: Conventional commits (`feat:`, `fix:`, `docs:`, `chore:`).
41
47
  Author and committer are always the human contributor (`dfry-lhzn <dfry@lhzn.io>`,
42
48
  from `git config`), so the log shows who was behind each change. No `Co-Authored-By:`
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: forcingkit
3
- Version: 0.11.0
3
+ Version: 0.12.1
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
@@ -220,6 +220,7 @@ Requires-Dist: cfgrib>=0.9.15.1
220
220
  Requires-Dist: cftime>=1.6.5
221
221
  Requires-Dist: dask[array]>=2026.3.0
222
222
  Requires-Dist: eccodes>=2.46.0
223
+ Requires-Dist: eccodeslib>=2.46.0
223
224
  Requires-Dist: fastapi>=0.135.1
224
225
  Requires-Dist: h5netcdf>=1.8.1
225
226
  Requires-Dist: h5py>=3.16.0
@@ -238,6 +239,8 @@ Requires-Dist: utide>=0.3.1
238
239
  Requires-Dist: uvicorn>=0.41.0
239
240
  Requires-Dist: xarray>=2026.2.0
240
241
  Requires-Dist: zarr>=3.1.5
242
+ Provides-Extra: gcs
243
+ Requires-Dist: gcsfs>=2026.2.0; extra == 'gcs'
241
244
  Description-Content-Type: text/markdown
242
245
 
243
246
  # <img src="https://raw.githubusercontent.com/lhzn-io/forcingkit/main/static/logo.svg" height="36" valign="middle" alt="Logo" /> forcingkit
@@ -254,7 +257,7 @@ Description-Content-Type: text/markdown
254
257
 
255
258
  Spatiotemporal forcing for computational Earth-system models: selects, regrids and serves model-ready time series with provenance.
256
259
 
257
- `forcingkit` sits between operational and archive data providers (NOAA HRRR, NECOFS, NOAA OFS, HYCOM, NDBC, CO-OPS) and model codes such as `Oceananigans.jl`. It delivers a model's parent ocean on true z levels (`/api/v1/obc`) and its HRRR surface atmosphere on a regular grid (`/api/v1/atmosphere`), plus station observations for validation, as schema-versioned Zarr stores that record their sources.
260
+ `forcingkit` sits between operational and archive data providers (NOAA HRRR, NWPS, NECOFS, NOAA OFS, HYCOM, NDBC, CO-OPS) and model codes such as `Oceananigans.jl`. It delivers a model's parent ocean on true z levels (`/api/v1/obc`), its HRRR surface atmosphere on a regular grid (`/api/v1/atmosphere`) and NWPS surface waves on the forecast office's own grid (`/api/v1/waves`), plus station observations for validation, as schema-versioned Zarr stores that record their sources.
258
261
 
259
262
  Documentation: <https://forcingkit.docs.lhzn.io>.
260
263
 
@@ -276,6 +279,7 @@ The primary goal of forcingkit is to provide clean, standardized ocean and atmos
276
279
 
277
280
  - **Oceanic Forcing**: NYOFS, NECOFS, DBOFS, HYCOM (global, 1994 to present).
278
281
  - **Atmospheric Forcing**: HRRR (3 km, hourly, 2014-07-30 to present), regridded to 0.03 degrees.
282
+ - **Surface Waves**: NWPS (one forecast office's grid per request, for example OKX CG1 at about 1.8 km, hourly, not regridded). NOMADS keeps about five days; older windows need an NWPS archive (`FORCINGKIT_NWPS_ARCHIVE`).
279
283
 
280
284
  ## Roadmap
281
285
 
@@ -12,7 +12,7 @@
12
12
 
13
13
  Spatiotemporal forcing for computational Earth-system models: selects, regrids and serves model-ready time series with provenance.
14
14
 
15
- `forcingkit` sits between operational and archive data providers (NOAA HRRR, NECOFS, NOAA OFS, HYCOM, NDBC, CO-OPS) and model codes such as `Oceananigans.jl`. It delivers a model's parent ocean on true z levels (`/api/v1/obc`) and its HRRR surface atmosphere on a regular grid (`/api/v1/atmosphere`), plus station observations for validation, as schema-versioned Zarr stores that record their sources.
15
+ `forcingkit` sits between operational and archive data providers (NOAA HRRR, NWPS, NECOFS, NOAA OFS, HYCOM, NDBC, CO-OPS) and model codes such as `Oceananigans.jl`. It delivers a model's parent ocean on true z levels (`/api/v1/obc`), its HRRR surface atmosphere on a regular grid (`/api/v1/atmosphere`) and NWPS surface waves on the forecast office's own grid (`/api/v1/waves`), plus station observations for validation, as schema-versioned Zarr stores that record their sources.
16
16
 
17
17
  Documentation: <https://forcingkit.docs.lhzn.io>.
18
18
 
@@ -34,6 +34,7 @@ The primary goal of forcingkit is to provide clean, standardized ocean and atmos
34
34
 
35
35
  - **Oceanic Forcing**: NYOFS, NECOFS, DBOFS, HYCOM (global, 1994 to present).
36
36
  - **Atmospheric Forcing**: HRRR (3 km, hourly, 2014-07-30 to present), regridded to 0.03 degrees.
37
+ - **Surface Waves**: NWPS (one forecast office's grid per request, for example OKX CG1 at about 1.8 km, hourly, not regridded). NOMADS keeps about five days; older windows need an NWPS archive (`FORCINGKIT_NWPS_ARCHIVE`).
37
38
 
38
39
  ## Roadmap
39
40
 
@@ -0,0 +1,27 @@
1
+ # forcingkit-client
2
+
3
+ A lightweight Python client for a [forcingkit](https://github.com/lhzn-io/forcingkit) service. It
4
+ returns ocean, atmosphere and station forcing as xarray objects, and interpolates forcing along a
5
+ trajectory (a tagged animal, a vessel, a drifter). It depends on `httpx`, `numpy`, `pandas`,
6
+ `scipy`, `xarray` and `zarr` only, none of the service's data-access stack, so it installs into a
7
+ simulator or an application's embedded Python (3.11 or newer).
8
+
9
+ ```python
10
+ from forcingkit_client import Client
11
+
12
+ fk = Client("http://localhost:9598")
13
+ box = (-70.9, 41.3, -70.6, 41.6) # west, south, east, north
14
+ ocean = fk.ocean(box, "2025-07-01", 24) # u, v, temp, salt, zeta
15
+ air = fk.atmosphere(box, "2025-07-01", 24) # HRRR u10, v10, t2m, ...
16
+
17
+ # lon, lat, time, z: one value per point; z in metres, positive up
18
+ track = fk.interpolate_trajectory(
19
+ lon, lat, time, z, variables=("u", "v"), atmosphere=("u10", "v10")
20
+ )
21
+ ```
22
+
23
+ Store builds run as jobs on the service; the client polls them and opens the finished store over
24
+ HTTP through a local cache (`~/.cache/forcingkit-client`), so only the chunks read are fetched,
25
+ once. `AsyncClient` has the same methods for callers that must not block an event loop.
26
+
27
+ Units are SI; z is metres, positive up. Documentation: <https://forcingkit.docs.lhzn.io/client.html>.
@@ -0,0 +1,53 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "forcingkit-client"
7
+ version = "0.1.0"
8
+ description = "Lightweight Python client for a forcingkit service: ocean, atmosphere and station forcing as xarray, and forcing interpolated along a trajectory."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "Apache-2.0"
12
+ authors = [
13
+ { name = "Daniel Fry", email = "dfry@lhzn.io" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Science/Research",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Scientific/Engineering :: Atmospheric Science",
20
+ "Topic :: Scientific/Engineering :: Hydrology",
21
+ ]
22
+ # Deliberately small: nothing from the service's stack (GRIB, OPeNDAP, S3), so the client
23
+ # installs into a simulator or an application's embedded Python.
24
+ dependencies = [
25
+ "httpx>=0.27",
26
+ "numpy>=1.26",
27
+ "pandas>=2.2",
28
+ "scipy>=1.11",
29
+ "xarray>=2024.10",
30
+ "zarr>=3.0.8",
31
+ ]
32
+
33
+ [project.urls]
34
+ Documentation = "https://forcingkit.docs.lhzn.io/client.html"
35
+ Issues = "https://github.com/lhzn-io/forcingkit/issues"
36
+ Source = "https://github.com/lhzn-io/forcingkit"
37
+
38
+ [tool.hatch.build.targets.wheel]
39
+ packages = ["src/forcingkit_client"]
40
+
41
+ [tool.ruff.lint]
42
+ select = ["E4", "E7", "E9", "F"]
43
+
44
+ [tool.pytest.ini_options]
45
+ pythonpath = ["src"]
46
+ asyncio_mode = "auto"
47
+ asyncio_default_fixture_loop_scope = "function"
48
+
49
+ [dependency-groups]
50
+ dev = [
51
+ "pytest>=9.0.2",
52
+ "pytest-asyncio>=1.3.0",
53
+ ]
@@ -0,0 +1,18 @@
1
+ """Lightweight client for a forcingkit service."""
2
+
3
+ from .client import AsyncClient, Client, ForcingkitError
4
+ from .schema import SchemaError, normalise
5
+ from .trajectory import interpolate_points, interpolate_trajectory, trajectory_window
6
+
7
+ __all__ = [
8
+ "AsyncClient",
9
+ "Client",
10
+ "ForcingkitError",
11
+ "SchemaError",
12
+ "interpolate_points",
13
+ "interpolate_trajectory",
14
+ "normalise",
15
+ "trajectory_window",
16
+ ]
17
+
18
+ __version__ = "0.1.0"