ncarnate 2.2.2__tar.gz → 2.3.0__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 (177) hide show
  1. ncarnate-2.3.0/.gitattributes +2 -0
  2. {ncarnate-2.2.2 → ncarnate-2.3.0}/.github/workflows/ci.yml +104 -14
  3. ncarnate-2.3.0/.github/workflows/full-granules.yml +39 -0
  4. {ncarnate-2.2.2 → ncarnate-2.3.0}/.github/workflows/publish.yml +12 -21
  5. {ncarnate-2.2.2 → ncarnate-2.3.0}/.gitignore +6 -0
  6. {ncarnate-2.2.2 → ncarnate-2.3.0}/.readthedocs.yaml +1 -1
  7. ncarnate-2.3.0/.zenodo.json +26 -0
  8. {ncarnate-2.2.2 → ncarnate-2.3.0}/CHANGELOG.md +45 -0
  9. {ncarnate-2.2.2 → ncarnate-2.3.0}/CITATION.cff +5 -2
  10. {ncarnate-2.2.2 → ncarnate-2.3.0}/CONTRIBUTING.md +33 -5
  11. {ncarnate-2.2.2 → ncarnate-2.3.0}/PKG-INFO +135 -32
  12. {ncarnate-2.2.2 → ncarnate-2.3.0}/README.md +125 -29
  13. ncarnate-2.3.0/brand/OFL.txt +90 -0
  14. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/README.md +21 -0
  15. ncarnate-2.3.0/brand/build_social_preview.py +78 -0
  16. ncarnate-2.3.0/brand/ncarnate-social-preview.png +0 -0
  17. ncarnate-2.3.0/brand/sora-600.ttf +0 -0
  18. ncarnate-2.3.0/docs/api.rst +184 -0
  19. {ncarnate-2.2.2 → ncarnate-2.3.0}/docs/fidelity-notes.md +32 -7
  20. {ncarnate-2.2.2 → ncarnate-2.3.0}/docs/index.rst +2 -0
  21. ncarnate-2.3.0/docs/release-notes.rst +5 -0
  22. ncarnate-2.3.0/docs/releasing.rst +97 -0
  23. ncarnate-2.3.0/examples/verified_conversion.py +37 -0
  24. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/__init__.py +4 -0
  25. ncarnate-2.3.0/ncarnate/_bounded_worker.py +38 -0
  26. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/atttypes.py +78 -13
  27. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/audit/__init__.py +1 -0
  28. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/audit/codes.py +19 -1
  29. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/audit/models.py +29 -0
  30. ncarnate-2.3.0/ncarnate/checkpoints.py +152 -0
  31. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/constants.py +1 -1
  32. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/convert/__init__.py +75 -28
  33. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/convert/preflight.py +14 -125
  34. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/convert/reader.py +5 -1
  35. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/convert/report.py +1 -1
  36. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/core.py +31 -18
  37. ncarnate-2.3.0/ncarnate/destinations.py +57 -0
  38. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/eos/swath.py +43 -10
  39. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/handoff.py +141 -12
  40. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/hdf4.py +258 -40
  41. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/hdf4_runtime.py +2 -3
  42. ncarnate-2.3.0/ncarnate/hdf4_stream.py +485 -0
  43. ncarnate-2.3.0/ncarnate/journal.py +42 -0
  44. ncarnate-2.3.0/ncarnate/memory.py +216 -0
  45. ncarnate-2.3.0/ncarnate/prepared.py +186 -0
  46. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/result.py +81 -6
  47. ncarnate-2.3.0/ncarnate/streaming.py +94 -0
  48. {ncarnate-2.2.2 → ncarnate-2.3.0}/paper/paper.md +12 -2
  49. {ncarnate-2.2.2 → ncarnate-2.3.0}/pyproject.toml +19 -9
  50. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/audit/test_audit_path.py +12 -0
  51. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/audit/test_codes.py +4 -0
  52. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/conftest.py +3 -2
  53. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/convert/test_agreement.py +1 -1
  54. ncarnate-2.3.0/tests/fixtures/corpus.json +60 -0
  55. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/data/hdfeos2/myd05_trim.provenance.json +5 -1
  56. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/trim_hdfeos2.py +9 -2
  57. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_attribute_types.py +17 -7
  58. ncarnate-2.3.0/tests/test_contract_hardening.py +260 -0
  59. ncarnate-2.3.0/tests/test_conversion_reporting.py +87 -0
  60. ncarnate-2.3.0/tests/test_full_corpus.py +70 -0
  61. ncarnate-2.3.0/tests/test_hdf4_streaming.py +676 -0
  62. ncarnate-2.3.0/tests/test_memory_worker.py +158 -0
  63. ncarnate-2.3.0/tests/test_prepared.py +197 -0
  64. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_raw_granules.py +2 -2
  65. ncarnate-2.3.0/tests/test_release_review_regressions.py +179 -0
  66. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_sdist_contents.py +19 -2
  67. ncarnate-2.3.0/tests/test_streaming_cache.py +22 -0
  68. ncarnate-2.3.0/tests/test_swath_tiling.py +55 -0
  69. ncarnate-2.3.0/tools/corpus.py +84 -0
  70. ncarnate-2.3.0/tools/hatch_build.py +18 -0
  71. ncarnate-2.3.0/tools/test_distribution.py +75 -0
  72. ncarnate-2.3.0/tools/test_full_corpus.py +45 -0
  73. ncarnate-2.3.0/tools/tests/test_ci_helpers.py +86 -0
  74. ncarnate-2.3.0/tools/validate_release_metadata.py +99 -0
  75. ncarnate-2.2.2/.gitattributes +0 -2
  76. ncarnate-2.2.2/.zenodo.json +0 -25
  77. ncarnate-2.2.2/conda-recipe/README.md +0 -54
  78. ncarnate-2.2.2/conda-recipe/recipe.yaml +0 -78
  79. ncarnate-2.2.2/docs/api.rst +0 -70
  80. {ncarnate-2.2.2 → ncarnate-2.3.0}/.github/FUNDING.yml +0 -0
  81. {ncarnate-2.2.2 → ncarnate-2.3.0}/CODE_OF_CONDUCT.md +0 -0
  82. {ncarnate-2.2.2 → ncarnate-2.3.0}/LICENSE +0 -0
  83. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/build_lockup.py +0 -0
  84. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/build_raster_lockup.py +0 -0
  85. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/compose_logo.py +0 -0
  86. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/globe.py +0 -0
  87. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/ncarnate-lockup-dark.png +0 -0
  88. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/ncarnate-lockup-dark.svg +0 -0
  89. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/ncarnate-lockup.png +0 -0
  90. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/ncarnate-lockup.svg +0 -0
  91. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/ncarnate-mark.png +0 -0
  92. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/ncarnate-mono.svg +0 -0
  93. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/ncarnate-source.png +0 -0
  94. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/ncarnate-white.svg +0 -0
  95. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/ncarnate-wordmark-white.svg +0 -0
  96. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/ncarnate-wordmark.svg +0 -0
  97. {ncarnate-2.2.2 → ncarnate-2.3.0}/brand/ncarnate.svg +0 -0
  98. {ncarnate-2.2.2 → ncarnate-2.3.0}/docs/conf.py +0 -0
  99. {ncarnate-2.2.2 → ncarnate-2.3.0}/docs/requirements.txt +0 -0
  100. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/__main__.py +0 -0
  101. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/audit/classify.py +0 -0
  102. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/audit/inspect.py +0 -0
  103. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/audit/report.py +0 -0
  104. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/cli.py +0 -0
  105. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/convert/integrity.py +0 -0
  106. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/convert/models.py +0 -0
  107. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/discovery.py +0 -0
  108. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/eos/__init__.py +0 -0
  109. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/eos/gctp.py +0 -0
  110. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/eos/grid.py +0 -0
  111. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/eos/structmeta.py +0 -0
  112. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/errors.py +0 -0
  113. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/formats.py +0 -0
  114. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/hashing.py +0 -0
  115. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/limits.py +0 -0
  116. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/schemas/__init__.py +0 -0
  117. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/schemas/handoff.schema.json +0 -0
  118. {ncarnate-2.2.2 → ncarnate-2.3.0}/ncarnate/stage.py +0 -0
  119. {ncarnate-2.2.2 → ncarnate-2.3.0}/paper/paper.bib +0 -0
  120. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/audit/__init__.py +0 -0
  121. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/audit/record.schema.json +0 -0
  122. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/audit/test_agreement.py +0 -0
  123. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/audit/test_checksum.py +0 -0
  124. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/audit/test_classify.py +0 -0
  125. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/audit/test_cli_dispatch.py +0 -0
  126. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/audit/test_contract.py +0 -0
  127. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/audit/test_emission.py +0 -0
  128. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/audit/test_error_codes.py +0 -0
  129. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/audit/test_inspect.py +0 -0
  130. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/audit/test_models.py +0 -0
  131. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/audit/test_readonly.py +0 -0
  132. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/audit/test_scaffold.py +0 -0
  133. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/convert/__init__.py +0 -0
  134. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/convert/test_cli_dispatch.py +0 -0
  135. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/convert/test_cli_integration.py +0 -0
  136. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/convert/test_convert_manifest.py +0 -0
  137. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/convert/test_inplace_skip.py +0 -0
  138. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/convert/test_integrity.py +0 -0
  139. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/convert/test_models.py +0 -0
  140. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/convert/test_reader.py +0 -0
  141. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/convert/test_summary_exit.py +0 -0
  142. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/data/blockers/compound_type.nc +0 -0
  143. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/data/hdfeos2/amsre_5daysnow_trim.hdf +0 -0
  144. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/data/hdfeos2/amsre_5daysnow_trim.provenance.json +0 -0
  145. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/data/hdfeos2/amsre_seaice12km_trim.hdf +0 -0
  146. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/data/hdfeos2/amsre_seaice12km_trim.provenance.json +0 -0
  147. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/data/hdfeos2/mod03_trim.hdf +0 -0
  148. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/data/hdfeos2/mod03_trim.provenance.json +0 -0
  149. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/data/hdfeos2/myd05_trim.hdf +0 -0
  150. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/data/hdfeos2/raingrid_trim.hdf +0 -0
  151. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/data/hdfeos2/raingrid_trim.provenance.json +0 -0
  152. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/data/netcdf/endianness.nc +0 -0
  153. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/data/netcdf/nested_groups.nc +0 -0
  154. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/data/netcdf/packed_fill.nc +0 -0
  155. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/data/netcdf/unlimited_dim.nc +0 -0
  156. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/make_blocker_fixtures.py +0 -0
  157. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/make_fixtures.py +0 -0
  158. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/operation_result/amsre_seaice12km.result.json +0 -0
  159. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/fixtures/operation_result/packed_fill.canonical.json +0 -0
  160. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_cli.py +0 -0
  161. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_convert_collisions.py +0 -0
  162. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_convert_inplace_collisions.py +0 -0
  163. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_discovery.py +0 -0
  164. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_eos_units.py +0 -0
  165. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_formats.py +0 -0
  166. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_g5_handoff_sufficiency.py +0 -0
  167. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_handoff_gate.py +0 -0
  168. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_handoff_schema.py +0 -0
  169. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_hdf4_conversion.py +0 -0
  170. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_limits.py +0 -0
  171. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_operation_result.py +0 -0
  172. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_optional_hdf4.py +0 -0
  173. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_plan_hash.py +0 -0
  174. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_recompress_netcdf.py +0 -0
  175. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_stage_api_4b.py +0 -0
  176. {ncarnate-2.2.2 → ncarnate-2.3.0}/tests/test_stage_api_g4.py +0 -0
  177. {ncarnate-2.2.2 → ncarnate-2.3.0}/tools/validate_distributions.py +0 -0
@@ -0,0 +1,2 @@
1
+ # Keep release source bytes consistent in Windows and Linux checkouts.
2
+ * text=auto eol=lf
@@ -5,6 +5,17 @@ on:
5
5
  branches: [main]
6
6
  pull_request:
7
7
  workflow_dispatch:
8
+ workflow_call:
9
+ inputs:
10
+ release_tag:
11
+ type: string
12
+ default: ''
13
+ publish_target:
14
+ type: string
15
+ default: ''
16
+
17
+ permissions:
18
+ contents: read
8
19
 
9
20
  # The pip-based jobs (test, test-windows-degraded, build) install no system
10
21
  # HDF libraries on purpose: the netCDF4 wheels bundle libhdf5/libnetcdf, and
@@ -16,6 +27,20 @@ on:
16
27
  # the *supported* install channel delivers full HDF4 capability (KD-L7, G2).
17
28
 
18
29
  jobs:
30
+ docs-and-metadata:
31
+ name: Documentation and release metadata
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
35
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
36
+ with:
37
+ python-version: "3.12"
38
+ - run: python -m pip install -r docs/requirements.txt PyYAML cffconvert pytest
39
+ - run: python -m pytest -q tools/tests
40
+ - run: python tools/validate_release_metadata.py
41
+ - run: cffconvert --validate
42
+ - run: python -m sphinx -W --keep-going -b html docs docs/_build/html
43
+
19
44
  lint:
20
45
  name: ruff
21
46
  runs-on: ubuntu-latest
@@ -41,15 +66,13 @@ jobs:
41
66
  os: [ubuntu-latest]
42
67
  python-version: ["3.10", "3.11", "3.12", "3.13"]
43
68
  # Smoke the self-contained-wheel install story (pyhdf + netCDF4 +
44
- # pyproj, no system HDF libs) on macOS too. Windows is deliberately
45
- # excluded from the *full* matrix: pyhdf's Windows wheel ships no
46
- # HDF4 runtime, so the HDF4 fixtures can't run there. Windows-pip
47
- # instead gets the dedicated degraded-capability job below
48
- # (KD-L4/KD-L7), which pins exactly what must keep working — and
49
- # exactly how the HDF4 path must refuse — without a usable pyhdf.
69
+ # pyproj, no system HDF libs) on macOS and Windows. A separate
70
+ # injected-missing-runtime job retains degraded-capability coverage.
50
71
  include:
51
72
  - os: macos-latest
52
73
  python-version: "3.12"
74
+ - os: windows-latest
75
+ python-version: "3.11"
53
76
  steps:
54
77
  - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
55
78
  - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
@@ -71,9 +94,8 @@ jobs:
71
94
  with:
72
95
  python-version: "3.12"
73
96
  - name: Install package with test extras (pip wheels only)
74
- # pyhdf's Windows wheel *installs* but cannot *import* (it ships no
75
- # HDF4 runtime) — which is precisely the degraded environment this
76
- # job exists to pin (KD-L4/KD-L7, gate G2).
97
+ # Tests inject an unavailable runtime, independently of whether the
98
+ # installed pyhdf wheel works on this particular Windows runner.
77
99
  run: |
78
100
  python -m pip install --upgrade pip
79
101
  python -m pip install -e ".[test]"
@@ -85,8 +107,12 @@ jobs:
85
107
  run: python -m pytest tests/test_optional_hdf4.py -q
86
108
 
87
109
  test-conda-forge:
88
- name: pytest full-capability (conda-forge)
89
- runs-on: ubuntu-latest
110
+ name: pytest full-capability (conda-forge, ${{ matrix.os }})
111
+ runs-on: ${{ matrix.os }}
112
+ strategy:
113
+ fail-fast: false
114
+ matrix:
115
+ os: [ubuntu-latest, windows-latest]
90
116
  # conda-forge is the *supported* HDF4 install channel (the refusal and
91
117
  # the README point Windows/aarch64 users at `conda install -c
92
118
  # conda-forge ncarnate`). This lane proves that channel actually
@@ -120,12 +146,15 @@ jobs:
120
146
  pyproj
121
147
  tqdm
122
148
  pytest
149
+ pip
123
150
  - name: Install ncarnate from source without replacing native deps
124
151
  # --no-deps: install only ncarnate; the native stack (pyhdf,
125
152
  # netCDF4, numpy, pyproj, tqdm) stays the conda-forge build. Build
126
153
  # isolation still fetches the hatchling backend into a throwaway
127
154
  # env, which never touches the runtime dependencies above.
128
- run: python -m pip install -e . --no-deps
155
+ run: |
156
+ python -m pip install build "hatchling>=1.27"
157
+ python -m pip install -e . --no-deps
129
158
  - name: Prove the conda-forge HDF4 runtime imports
130
159
  run: |
131
160
  python -c "from ncarnate.hdf4_runtime import require_hdf4_runtime; require_hdf4_runtime(); print('conda-forge HDF4 runtime OK')"
@@ -144,14 +173,75 @@ jobs:
144
173
  with:
145
174
  python-version: "3.12"
146
175
  - name: Install build tooling
147
- run: python -m pip install build twine
176
+ run: python -m pip install build twine PyYAML cffconvert
177
+ - name: Validate release identity before building
178
+ env:
179
+ NCARNATE_RELEASE_TAG: ${{ inputs.release_tag || '' }}
180
+ NCARNATE_PUBLISH_TARGET: ${{ inputs.publish_target || '' }}
181
+ run: |
182
+ python tools/validate_release_metadata.py
183
+ cffconvert --validate
148
184
  - name: Build sdist + wheel
149
185
  run: python -m build
150
186
  - name: Check distributions
151
- run: twine check dist/*
187
+ run: twine check --strict dist/*
152
188
  - name: Validate distributions
153
189
  run: >-
154
190
  python tools/validate_distributions.py
155
191
  --allowed-email dev@erickshepherd.com
156
192
  --allowed-email ca@trufo.ai
157
193
  dist/*
194
+ - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
195
+ with:
196
+ name: dist
197
+ path: dist/
198
+ if-no-files-found: error
199
+
200
+ installed-packages:
201
+ name: Installed ${{ matrix.kind }} (${{ matrix.os }})
202
+ needs: build
203
+ runs-on: ${{ matrix.os }}
204
+ timeout-minutes: 20
205
+ strategy:
206
+ fail-fast: false
207
+ matrix:
208
+ os: [ubuntu-latest, windows-latest, macos-latest]
209
+ kind: [wheel, sdist]
210
+ steps:
211
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
212
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
213
+ with:
214
+ python-version: "3.12"
215
+ - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
216
+ with:
217
+ name: dist
218
+ path: dist/
219
+ - name: Test installed package outside the source checkout
220
+ run: python tools/test_distribution.py dist --kind ${{ matrix.kind }}
221
+
222
+ zarr-companion:
223
+ name: Installed Zarr companion
224
+ needs: build
225
+ runs-on: ubuntu-latest
226
+ timeout-minutes: 15
227
+ steps:
228
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
229
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
230
+ with:
231
+ python-version: "3.12"
232
+ - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
233
+ with:
234
+ name: dist
235
+ path: dist/
236
+ - name: Build and install the companion with the candidate wheel
237
+ run: |
238
+ python -m pip install build twine
239
+ python -m build companions/zarr-demo --outdir companion-dist
240
+ python -m twine check --strict companion-dist/*
241
+ python -m pip install dist/*.whl companion-dist/*.whl pytest
242
+ python -m pip check
243
+ - name: Run companion tests outside both source packages
244
+ run: |
245
+ cp -R companions/zarr-demo/tests "$RUNNER_TEMP/zarr-tests"
246
+ cd "$RUNNER_TEMP"
247
+ python -m pytest -q zarr-tests
@@ -0,0 +1,39 @@
1
+ name: Full-granule validation
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ pull_request:
6
+ branches: [main]
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ full-granules:
13
+ name: Exact originals and independent reference
14
+ if: github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release/')
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 30
17
+ steps:
18
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
19
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
20
+ with:
21
+ python-version: "3.12"
22
+ - run: python -m pip install -e ".[test]"
23
+ - name: Set the corpus location outside the checkout
24
+ run: echo "NCARNATE_GRANULE_DIR=$RUNNER_TEMP/ncarnate-granules" >> "$GITHUB_ENV"
25
+ - name: Fetch and verify every required file before running tests
26
+ run: >-
27
+ python tools/corpus.py "$NCARNATE_GRANULE_DIR" --fetch --include-references
28
+ > corpus-verification.json
29
+ - name: Run full-size conversions and reference comparisons
30
+ run: python tools/test_full_corpus.py --junitxml full-granules.xml
31
+ - name: Retain verification and test results, including failures
32
+ if: always()
33
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
34
+ with:
35
+ name: full-granule-results
36
+ path: |
37
+ corpus-verification.json
38
+ full-granules.xml
39
+ if-no-files-found: warn
@@ -27,28 +27,19 @@ permissions:
27
27
  contents: read
28
28
 
29
29
  jobs:
30
- build:
31
- name: Build sdist + wheel
32
- runs-on: ubuntu-latest
33
- steps:
34
- - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
35
- - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
36
- with:
37
- python-version: "3.12"
38
- - name: Build
39
- run: |
40
- python -m pip install --upgrade build twine
41
- python -m build
42
- python -m twine check dist/*
43
- python tools/validate_distributions.py --allowed-email dev@erickshepherd.com --allowed-email ca@trufo.ai dist/*
44
- - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
45
- with:
46
- name: dist
47
- path: dist/
30
+ verify:
31
+ name: Test and build the exact release revision
32
+ # A local reusable workflow uses the same revision as this caller.
33
+ # Its dist artifact is installed by the package tests and then published
34
+ # unchanged below. Failure of ANY CI job prevents either upload.
35
+ uses: ./.github/workflows/ci.yml
36
+ with:
37
+ release_tag: ${{ github.event_name == 'release' && github.event.release.tag_name || (github.ref_type == 'tag' && github.ref_name || '') }}
38
+ publish_target: ${{ github.event_name == 'release' && 'pypi' || inputs.target }}
48
39
 
49
40
  publish-testpypi:
50
41
  name: Publish to TestPyPI
51
- needs: build
42
+ needs: verify
52
43
  if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
53
44
  runs-on: ubuntu-latest
54
45
  environment: testpypi
@@ -62,11 +53,11 @@ jobs:
62
53
  - uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
63
54
  with:
64
55
  repository-url: https://test.pypi.org/legacy/
65
- skip-existing: true # re-dispatching a dry run for an existing version won't fail
56
+ # Duplicate versions fail: success must mean these files were uploaded.
66
57
 
67
58
  publish-pypi:
68
59
  name: Publish to PyPI
69
- needs: build
60
+ needs: verify
70
61
  if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.target == 'pypi')
71
62
  runs-on: ubuntu-latest
72
63
  environment: pypi
@@ -23,3 +23,9 @@ docs/audits/*.audit-state.json
23
23
 
24
24
  # Sphinx documentation build output.
25
25
  docs/_build/
26
+
27
+ # Machine-local publication policy, requests and private reports.
28
+ .publication-private/
29
+ .publication-reports/
30
+ .publication-cache/
31
+ full-granules.xml
@@ -3,7 +3,7 @@
3
3
  version: 2
4
4
 
5
5
  build:
6
- os: ubuntu-22.04
6
+ os: ubuntu-24.04
7
7
  tools:
8
8
  python: "3.12"
9
9
 
@@ -0,0 +1,26 @@
1
+ {
2
+ "upload_type": "software",
3
+ "version": "2.3.0",
4
+ "title": "ncarnate: Convert HDF4/HDF-EOS2 (MODIS, AMSR-E) to CF-annotated netCDF4 and losslessly recompress netCDF/HDF5",
5
+ "description": "A Python library and command-line tool for converting HDF4/HDF-EOS2 granules to CF-annotated netCDF4 and losslessly recompressing netCDF/HDF5. Version 2.3.0 adds explicit-output prepared jobs, digest-verified resume, streamed copying and verification, tiled swath interpolation, and optional isolated conversion workers with OS-enforced memory limits on Windows and Linux. Outputs are verified value-for-value before publication. Generated coordinate fields can still require whole arrays, and the worker limit excludes the calling process.",
6
+ "access_right": "open",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "netCDF4",
10
+ "HDF4",
11
+ "HDF-EOS2",
12
+ "HDF5",
13
+ "CF conventions",
14
+ "MODIS",
15
+ "AMSR-E",
16
+ "remote sensing",
17
+ "atmospheric science",
18
+ "Python"
19
+ ],
20
+ "creators": [
21
+ {
22
+ "name": "Shepherd, Erick Edward",
23
+ "orcid": "0000-0002-4750-6100"
24
+ }
25
+ ]
26
+ }
@@ -4,6 +4,51 @@ All notable changes to this project are documented here. The format is
4
4
  based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
5
5
  this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## Unreleased
8
+
9
+ ## [2.3.0] - 2026-10-08
10
+
11
+ - Add explicit-output prepared plans, shared finite-batch collision checks and
12
+ digest-verified resume journals. Existing one-shot defaults remain compatible.
13
+ - Store completion checkpoints separately so large batches remain resumable;
14
+ resolve directory aliases and check every output parent before execution.
15
+ - Refuse unknown inputs before output-directory creation; reserve CLI result
16
+ journals before conversion and distinguish later reporting failures.
17
+ - Bound hostile handoff records, refuse invalid text and audit modes, freeze
18
+ encoding options, and guard unsynchronized attribute inquiries.
19
+ - Stream netCDF and HDF4 SDS copying and verification in slices; offer an
20
+ OS-enforced conversion-worker memory cap on Windows and Linux. Generated
21
+ HDF-EOS coordinates still materialize whole arrays within that worker cap,
22
+ while tiled swath interpolation avoids large full-grid temporaries.
23
+ - Preserve reads of multidimensional variables named like dimensions when
24
+ configuring netCDF native caches (found with the complete MOD03 granule).
25
+ - Finish large storage chunks before advancing and coalesce small chunks to
26
+ avoid repeated compression and excessive per-chunk Python calls.
27
+ - Add exact-granule retrieval and verification, Windows test portability fixes,
28
+ and a separately packaged numeric Zarr/xarray demonstration.
29
+ - Document the public staged, prepared, handoff and bounded-worker APIs on Read
30
+ the Docs, and synchronize package, citation, Zenodo and conda recipe metadata.
31
+ - Gate publication on the complete CI suite, test built packages outside the
32
+ checkout on Linux, Windows and macOS, and test the separate Zarr companion.
33
+ Add release-PR and manual full-granule validation that refuses incomplete evidence; require
34
+ matching production tags and fail duplicate TestPyPI uploads.
35
+ - Reconcile MYD05 fixture regeneration with its recorded trimmed metadata and
36
+ emit canonical source-archive gzip headers for publication inspection.
37
+
38
+ ### Compatibility notes
39
+
40
+ - Existing one-shot source-replacement defaults remain unchanged. The new
41
+ prepared and bounded APIs always require a separate explicit output.
42
+ - Encoding options are now immutable, enforcing the documented immutable-plan
43
+ contract. Use `dataclasses.replace(options, complevel=...)` to derive options;
44
+ assigning to a field now raises `FrozenInstanceError`.
45
+ - Unknown audit modes and invalid UTF-8 result text are now deliberately refused.
46
+ Metadata-only audit is the only implemented audit depth.
47
+ - The handoff schema stays at version 1. New diagnostic codes are append-only;
48
+ the classification ruleset advances independently to version 9.
49
+ - Hard worker limits are available on Windows and Linux; macOS is explicitly
50
+ unsupported by `execute_bounded`. Ordinary conversion remains portable.
51
+
7
52
  ## [2.2.2] - 2026-07-21
8
53
 
9
54
  Maintenance release with packaging and CI updates. Conversion, recompression,
@@ -5,14 +5,17 @@ abstract: >-
5
5
  Reincarnate legacy scientific data as modern netCDF4: losslessly
6
6
  recompress netCDF/HDF5 files and convert HDF4/HDF-EOS2 granules to
7
7
  CF-annotated netCDF4 with reconstructed grid and swath geolocation.
8
+ Supports verified resumable conversions, streamed data transfer, and
9
+ isolated conversion workers with memory limits on Windows and Linux.
8
10
  type: software
9
11
  authors:
10
12
  - family-names: "Shepherd"
11
13
  given-names: "Erick Edward"
12
14
  email: "dev@erickshepherd.com"
13
15
  orcid: "https://orcid.org/0000-0002-4750-6100"
14
- version: "2.2.2"
15
- date-released: "2026-07-21"
16
+ version: "2.3.0"
17
+ date-released: "2026-10-08"
18
+ # This is the verified concept DOI, which identifies all versions.
16
19
  doi: "10.5281/zenodo.21288802"
17
20
  license: MIT
18
21
  repository-code: "https://github.com/ErickShepherd/ncarnate"
@@ -30,12 +30,15 @@ or an issue labelled `question`. The README covers installation (including the
30
30
  ```console
31
31
  git clone https://github.com/ErickShepherd/ncarnate
32
32
  cd ncarnate
33
- pip install -e ".[test]"
33
+ python -m pip install -e ".[test,release]" "ruff==0.15.*"
34
+ python -m pip install -r docs/requirements.txt
34
35
  ```
35
36
 
36
- On Linux (x86_64) and macOS (arm64) every dependency — including `pyhdf` —
37
- installs as a binary wheel. On other platforms the HDF4 path may need a system
38
- HDF4 library first (see the README).
37
+ Compatible pip wheels can supply the native dependencies. If a wheel is not
38
+ available for your platform and Python version, use conda-forge or install the
39
+ native libraries and build tools described in the README. Ordinary conversion
40
+ and the hard memory-limit worker have different platform coverage: the worker
41
+ supports Windows and Linux and deliberately refuses macOS.
39
42
 
40
43
  ## Making a change
41
44
 
@@ -47,12 +50,37 @@ HDF4 library first (see the README).
47
50
  4. Run the checks locally:
48
51
  ```console
49
52
  ruff check .
50
- pytest
53
+ python -m pytest -q
54
+ python -m pytest -q tools/tests
55
+ python tools/validate_release_metadata.py
56
+ cffconvert --validate
57
+ python -m sphinx -W --keep-going -b html docs docs/_build/html
51
58
  ```
52
59
  5. Open a pull request describing the change and, for a conversion change, how
53
60
  it preserves the fidelity contract (stored values unchanged; output verified
54
61
  against the source before it replaces anything).
55
62
 
63
+ For full-size regression evidence, fetch and verify the catalogued originals
64
+ and independent reference with
65
+ `python tools/corpus.py DATA_DIRECTORY --fetch --include-references`.
66
+ Set `NCARNATE_GRANULE_DIR` to that directory and
67
+ run `python tools/test_full_corpus.py --junitxml full-granules.xml`. That check
68
+ fails on missing files or skipped/missing tests; ordinary pytest remains usable
69
+ without the external corpus. Keep downloaded inputs and reports outside the
70
+ tracked source tree. The manual full-granule GitHub workflow retains its reports.
71
+
72
+ The Zarr demonstration requires Python 3.11 or newer and is a separate package:
73
+ install `companions/zarr-demo` after the candidate ncarnate package, then run
74
+ `python -m pytest -q companions/zarr-demo/tests`. Its CI job builds and tests the
75
+ installed wheel outside both source packages.
76
+
77
+ To exercise release installations locally, build to a directory outside the
78
+ checkout with `python -m build --outdir PATH_TO_ARTIFACTS`, then run
79
+ `python tools/test_distribution.py PATH_TO_ARTIFACTS --kind wheel` and repeat
80
+ with `--kind sdist`. Each run creates a clean environment and tests the installed
81
+ package. PyPI and TestPyPI wait for these jobs and the full source CI. Publication
82
+ is a separate maintainer action; see `docs/releasing.rst`.
83
+
56
84
  ## Scope and design
57
85
 
58
86
  ncarnate deliberately **fails loud** on constructs it cannot convert correctly