ncarnate 2.2.1__tar.gz → 2.2.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.
- ncarnate-2.2.2/.github/workflows/ci.yml +157 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/.github/workflows/publish.yml +8 -7
- {ncarnate-2.2.1 → ncarnate-2.2.2}/.gitignore +1 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/CHANGELOG.md +14 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/CITATION.cff +3 -3
- {ncarnate-2.2.1 → ncarnate-2.2.2}/CODE_OF_CONDUCT.md +1 -1
- ncarnate-2.2.1/README.md → ncarnate-2.2.2/PKG-INFO +73 -10
- ncarnate-2.2.1/PKG-INFO → ncarnate-2.2.2/README.md +36 -44
- {ncarnate-2.2.1 → ncarnate-2.2.2}/conda-recipe/recipe.yaml +5 -2
- {ncarnate-2.2.1 → ncarnate-2.2.2}/docs/fidelity-notes.md +35 -28
- ncarnate-2.2.2/docs/index.rst +30 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/__init__.py +28 -0
- ncarnate-2.2.2/ncarnate/atttypes.py +257 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/audit/__init__.py +17 -1
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/audit/classify.py +1 -0
- ncarnate-2.2.2/ncarnate/audit/codes.py +113 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/audit/inspect.py +17 -6
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/cli.py +6 -2
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/constants.py +1 -1
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/convert/__init__.py +133 -61
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/convert/models.py +18 -1
- ncarnate-2.2.2/ncarnate/convert/preflight.py +298 -0
- ncarnate-2.2.2/ncarnate/convert/report.py +114 -0
- ncarnate-2.2.2/ncarnate/core.py +1198 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/errors.py +36 -0
- ncarnate-2.2.2/ncarnate/handoff.py +297 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/hdf4.py +2 -1
- ncarnate-2.2.2/ncarnate/hdf4_runtime.py +81 -0
- ncarnate-2.2.2/ncarnate/result.py +673 -0
- ncarnate-2.2.2/ncarnate/schemas/__init__.py +15 -0
- ncarnate-2.2.2/ncarnate/schemas/handoff.schema.json +276 -0
- ncarnate-2.2.2/ncarnate/stage.py +114 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/paper/paper.md +11 -8
- {ncarnate-2.2.1 → ncarnate-2.2.2}/pyproject.toml +18 -6
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/audit/test_codes.py +6 -1
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/convert/test_integrity.py +8 -3
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/data/hdfeos2/amsre_5daysnow_trim.provenance.json +1 -1
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/data/hdfeos2/amsre_seaice12km_trim.hdf +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/data/hdfeos2/amsre_seaice12km_trim.provenance.json +1 -1
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/data/hdfeos2/mod03_trim.hdf +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/data/hdfeos2/mod03_trim.provenance.json +1 -1
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/data/hdfeos2/myd05_trim.hdf +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/data/hdfeos2/myd05_trim.provenance.json +1 -1
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/data/hdfeos2/raingrid_trim.provenance.json +1 -1
- ncarnate-2.2.2/tests/fixtures/operation_result/amsre_seaice12km.result.json +575 -0
- ncarnate-2.2.2/tests/fixtures/operation_result/packed_fill.canonical.json +1 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/trim_hdfeos2.py +2 -2
- ncarnate-2.2.2/tests/test_attribute_types.py +210 -0
- ncarnate-2.2.2/tests/test_convert_collisions.py +336 -0
- ncarnate-2.2.2/tests/test_convert_inplace_collisions.py +237 -0
- ncarnate-2.2.2/tests/test_g5_handoff_sufficiency.py +217 -0
- ncarnate-2.2.2/tests/test_handoff_gate.py +138 -0
- ncarnate-2.2.2/tests/test_handoff_schema.py +148 -0
- ncarnate-2.2.2/tests/test_operation_result.py +178 -0
- ncarnate-2.2.2/tests/test_optional_hdf4.py +413 -0
- ncarnate-2.2.2/tests/test_plan_hash.py +91 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/test_raw_granules.py +1 -1
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/test_recompress_netcdf.py +27 -0
- ncarnate-2.2.2/tests/test_sdist_contents.py +139 -0
- ncarnate-2.2.2/tests/test_stage_api_4b.py +154 -0
- ncarnate-2.2.2/tests/test_stage_api_g4.py +128 -0
- ncarnate-2.2.2/tools/validate_distributions.py +80 -0
- ncarnate-2.2.1/.github/workflows/ci.yml +0 -73
- ncarnate-2.2.1/docs/index.rst +0 -9
- ncarnate-2.2.1/ncarnate/audit/codes.py +0 -56
- ncarnate-2.2.1/ncarnate/convert/report.py +0 -59
- ncarnate-2.2.1/ncarnate/core.py +0 -505
- {ncarnate-2.2.1 → ncarnate-2.2.2}/.gitattributes +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/.github/FUNDING.yml +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/.readthedocs.yaml +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/.zenodo.json +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/CONTRIBUTING.md +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/LICENSE +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/README.md +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/build_lockup.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/build_raster_lockup.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/compose_logo.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/globe.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/ncarnate-lockup-dark.png +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/ncarnate-lockup-dark.svg +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/ncarnate-lockup.png +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/ncarnate-lockup.svg +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/ncarnate-mark.png +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/ncarnate-mono.svg +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/ncarnate-source.png +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/ncarnate-white.svg +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/ncarnate-wordmark-white.svg +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/ncarnate-wordmark.svg +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/brand/ncarnate.svg +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/conda-recipe/README.md +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/docs/api.rst +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/docs/conf.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/docs/requirements.txt +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/__main__.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/audit/models.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/audit/report.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/convert/integrity.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/convert/reader.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/discovery.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/eos/__init__.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/eos/gctp.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/eos/grid.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/eos/structmeta.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/eos/swath.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/formats.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/hashing.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/ncarnate/limits.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/paper/paper.bib +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/audit/__init__.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/audit/record.schema.json +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/audit/test_agreement.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/audit/test_audit_path.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/audit/test_checksum.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/audit/test_classify.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/audit/test_cli_dispatch.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/audit/test_contract.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/audit/test_emission.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/audit/test_error_codes.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/audit/test_inspect.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/audit/test_models.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/audit/test_readonly.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/audit/test_scaffold.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/conftest.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/convert/__init__.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/convert/test_agreement.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/convert/test_cli_dispatch.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/convert/test_cli_integration.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/convert/test_convert_manifest.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/convert/test_inplace_skip.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/convert/test_models.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/convert/test_reader.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/convert/test_summary_exit.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/data/blockers/compound_type.nc +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/data/hdfeos2/amsre_5daysnow_trim.hdf +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/data/hdfeos2/raingrid_trim.hdf +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/data/netcdf/endianness.nc +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/data/netcdf/nested_groups.nc +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/data/netcdf/packed_fill.nc +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/data/netcdf/unlimited_dim.nc +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/make_blocker_fixtures.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/fixtures/make_fixtures.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/test_cli.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/test_discovery.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/test_eos_units.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/test_formats.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/test_hdf4_conversion.py +0 -0
- {ncarnate-2.2.1 → ncarnate-2.2.2}/tests/test_limits.py +0 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
# The pip-based jobs (test, test-windows-degraded, build) install no system
|
|
10
|
+
# HDF libraries on purpose: the netCDF4 wheels bundle libhdf5/libnetcdf, and
|
|
11
|
+
# the pyhdf >= 0.11.6 manylinux wheels bundle libhdf4 (verified against PyPI
|
|
12
|
+
# 2026-07-08 for cp310-cp313). Platforms without a pyhdf wheel (e.g.
|
|
13
|
+
# linux-aarch64) need a system libhdf4 for the sdist build instead. The
|
|
14
|
+
# one deliberate exception is test-conda-forge, which provisions the whole
|
|
15
|
+
# runtime stack (incl. a system-HDF4-linked pyhdf) from conda-forge to prove
|
|
16
|
+
# the *supported* install channel delivers full HDF4 capability (KD-L7, G2).
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
lint:
|
|
20
|
+
name: ruff
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
|
|
24
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
25
|
+
with:
|
|
26
|
+
python-version: "3.12"
|
|
27
|
+
- name: Install ruff
|
|
28
|
+
# Minor-pinned so a future ruff release can't red a green branch with
|
|
29
|
+
# no code change (new lint rules land in version bumps). Bump
|
|
30
|
+
# deliberately; keep in step with the version developers run locally.
|
|
31
|
+
run: python -m pip install "ruff==0.15.*"
|
|
32
|
+
- name: Run ruff
|
|
33
|
+
run: ruff check .
|
|
34
|
+
|
|
35
|
+
test:
|
|
36
|
+
name: pytest (${{ matrix.os }} py${{ matrix.python-version }})
|
|
37
|
+
runs-on: ${{ matrix.os }}
|
|
38
|
+
strategy:
|
|
39
|
+
fail-fast: false
|
|
40
|
+
matrix:
|
|
41
|
+
os: [ubuntu-latest]
|
|
42
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
43
|
+
# 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.
|
|
50
|
+
include:
|
|
51
|
+
- os: macos-latest
|
|
52
|
+
python-version: "3.12"
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
|
|
55
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
56
|
+
with:
|
|
57
|
+
python-version: ${{ matrix.python-version }}
|
|
58
|
+
- name: Install package with test extras
|
|
59
|
+
run: |
|
|
60
|
+
python -m pip install --upgrade pip
|
|
61
|
+
python -m pip install -e ".[test]"
|
|
62
|
+
- name: Run tests (offline; raw-granule marks self-skip)
|
|
63
|
+
run: python -m pytest -q
|
|
64
|
+
|
|
65
|
+
test-windows-degraded:
|
|
66
|
+
name: pytest degraded-capability (windows pip)
|
|
67
|
+
runs-on: windows-latest
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
|
|
70
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
71
|
+
with:
|
|
72
|
+
python-version: "3.12"
|
|
73
|
+
- 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).
|
|
77
|
+
run: |
|
|
78
|
+
python -m pip install --upgrade pip
|
|
79
|
+
python -m pip install -e ".[test]"
|
|
80
|
+
- name: Degraded-capability contract (tests/test_optional_hdf4.py)
|
|
81
|
+
# The purpose-built matrix: import / --help / --version / detection /
|
|
82
|
+
# netCDF recompression / audits all work without a usable pyhdf; an
|
|
83
|
+
# HDF4 attempt refuses with the stable HDF4_RUNTIME_UNAVAILABLE
|
|
84
|
+
# message before output creation, never an import traceback.
|
|
85
|
+
run: python -m pytest tests/test_optional_hdf4.py -q
|
|
86
|
+
|
|
87
|
+
test-conda-forge:
|
|
88
|
+
name: pytest full-capability (conda-forge)
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
# conda-forge is the *supported* HDF4 install channel (the refusal and
|
|
91
|
+
# the README point Windows/aarch64 users at `conda install -c
|
|
92
|
+
# conda-forge ncarnate`). This lane proves that channel actually
|
|
93
|
+
# delivers the full HDF4/HDF-EOS2 capability — a promise the pip matrix
|
|
94
|
+
# cannot make, since it relies on the bundled-libhdf4 pyhdf wheels
|
|
95
|
+
# rather than the conda-forge pyhdf built against a system HDF4
|
|
96
|
+
# (KD-L7 / priority step 2.5; gate G2 cannot close without it).
|
|
97
|
+
defaults:
|
|
98
|
+
run:
|
|
99
|
+
shell: bash -el {0}
|
|
100
|
+
steps:
|
|
101
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
|
|
102
|
+
- name: Provision a conda-forge full-capability environment
|
|
103
|
+
uses: mamba-org/setup-micromamba@d7c9bd84e824b79d2af72a2d4196c7f4300d3476 # v3.0.0
|
|
104
|
+
with:
|
|
105
|
+
environment-name: ncarnate-cf
|
|
106
|
+
# The runtime stack comes wholly from conda-forge — pyhdf here is
|
|
107
|
+
# built against a real HDF4 library, unlike the pip wheel's
|
|
108
|
+
# bundled runtime. Pin Python; float the rest to what the channel
|
|
109
|
+
# currently ships (the point is the channel's promise, not a
|
|
110
|
+
# frozen resolution).
|
|
111
|
+
condarc: |
|
|
112
|
+
channels:
|
|
113
|
+
- conda-forge
|
|
114
|
+
channel_priority: strict
|
|
115
|
+
create-args: >-
|
|
116
|
+
python=3.12
|
|
117
|
+
pyhdf
|
|
118
|
+
netCDF4
|
|
119
|
+
numpy
|
|
120
|
+
pyproj
|
|
121
|
+
tqdm
|
|
122
|
+
pytest
|
|
123
|
+
- name: Install ncarnate from source without replacing native deps
|
|
124
|
+
# --no-deps: install only ncarnate; the native stack (pyhdf,
|
|
125
|
+
# netCDF4, numpy, pyproj, tqdm) stays the conda-forge build. Build
|
|
126
|
+
# isolation still fetches the hatchling backend into a throwaway
|
|
127
|
+
# env, which never touches the runtime dependencies above.
|
|
128
|
+
run: python -m pip install -e . --no-deps
|
|
129
|
+
- name: Prove the conda-forge HDF4 runtime imports
|
|
130
|
+
run: |
|
|
131
|
+
python -c "from ncarnate.hdf4_runtime import require_hdf4_runtime; require_hdf4_runtime(); print('conda-forge HDF4 runtime OK')"
|
|
132
|
+
- name: Full suite — HDF4 import, conversion, audit, manifest on real fixtures
|
|
133
|
+
# The committed HDF4/HDF-EOS2 fixtures drive real conversions here
|
|
134
|
+
# (unlike the pip matrix's bundled-wheel path), exercising the
|
|
135
|
+
# supported channel end to end. Raw-granule tests self-skip offline.
|
|
136
|
+
run: python -m pytest -q
|
|
137
|
+
|
|
138
|
+
build:
|
|
139
|
+
name: build + twine check
|
|
140
|
+
runs-on: ubuntu-latest
|
|
141
|
+
steps:
|
|
142
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
|
|
143
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
144
|
+
with:
|
|
145
|
+
python-version: "3.12"
|
|
146
|
+
- name: Install build tooling
|
|
147
|
+
run: python -m pip install build twine
|
|
148
|
+
- name: Build sdist + wheel
|
|
149
|
+
run: python -m build
|
|
150
|
+
- name: Check distributions
|
|
151
|
+
run: twine check dist/*
|
|
152
|
+
- name: Validate distributions
|
|
153
|
+
run: >-
|
|
154
|
+
python tools/validate_distributions.py
|
|
155
|
+
--allowed-email dev@erickshepherd.com
|
|
156
|
+
--allowed-email ca@trufo.ai
|
|
157
|
+
dist/*
|
|
@@ -31,8 +31,8 @@ jobs:
|
|
|
31
31
|
name: Build sdist + wheel
|
|
32
32
|
runs-on: ubuntu-latest
|
|
33
33
|
steps:
|
|
34
|
-
- uses: actions/checkout@
|
|
35
|
-
- uses: actions/setup-python@
|
|
34
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
|
|
35
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
36
36
|
with:
|
|
37
37
|
python-version: "3.12"
|
|
38
38
|
- name: Build
|
|
@@ -40,7 +40,8 @@ jobs:
|
|
|
40
40
|
python -m pip install --upgrade build twine
|
|
41
41
|
python -m build
|
|
42
42
|
python -m twine check dist/*
|
|
43
|
-
|
|
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
|
|
44
45
|
with:
|
|
45
46
|
name: dist
|
|
46
47
|
path: dist/
|
|
@@ -54,11 +55,11 @@ jobs:
|
|
|
54
55
|
permissions:
|
|
55
56
|
id-token: write
|
|
56
57
|
steps:
|
|
57
|
-
- uses: actions/download-artifact@
|
|
58
|
+
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
|
58
59
|
with:
|
|
59
60
|
name: dist
|
|
60
61
|
path: dist/
|
|
61
|
-
- uses: pypa/gh-action-pypi-publish@
|
|
62
|
+
- uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
|
|
62
63
|
with:
|
|
63
64
|
repository-url: https://test.pypi.org/legacy/
|
|
64
65
|
skip-existing: true # re-dispatching a dry run for an existing version won't fail
|
|
@@ -72,8 +73,8 @@ jobs:
|
|
|
72
73
|
permissions:
|
|
73
74
|
id-token: write
|
|
74
75
|
steps:
|
|
75
|
-
- uses: actions/download-artifact@
|
|
76
|
+
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
|
76
77
|
with:
|
|
77
78
|
name: dist
|
|
78
79
|
path: dist/
|
|
79
|
-
- uses: pypa/gh-action-pypi-publish@
|
|
80
|
+
- uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
|
|
@@ -4,6 +4,20 @@ 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
|
+
## [2.2.2] - 2026-07-21
|
|
8
|
+
|
|
9
|
+
Maintenance release with packaging and CI updates. Conversion, recompression,
|
|
10
|
+
audit, and verification behavior are unchanged.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Standardized package metadata and normalized bundled test fixtures.
|
|
14
|
+
- Added automated distribution-policy validation.
|
|
15
|
+
- Pinned GitHub Actions to reviewed immutable commits and current Node 24 action
|
|
16
|
+
releases.
|
|
17
|
+
- Kept the bundled conda recipe on the last published PyPI artifact; its version
|
|
18
|
+
and source hash will be advanced together in a post-publication update after
|
|
19
|
+
the definitive 2.2.2 sdist exists.
|
|
20
|
+
|
|
7
21
|
## [2.2.1] - 2026-07-12
|
|
8
22
|
|
|
9
23
|
A documentation and branding patch. **No functional changes** to conversion,
|
|
@@ -9,10 +9,10 @@ type: software
|
|
|
9
9
|
authors:
|
|
10
10
|
- family-names: "Shepherd"
|
|
11
11
|
given-names: "Erick Edward"
|
|
12
|
-
email: "
|
|
12
|
+
email: "dev@erickshepherd.com"
|
|
13
13
|
orcid: "https://orcid.org/0000-0002-4750-6100"
|
|
14
|
-
version: "2.2.
|
|
15
|
-
date-released: "2026-07-
|
|
14
|
+
version: "2.2.2"
|
|
15
|
+
date-released: "2026-07-21"
|
|
16
16
|
doi: "10.5281/zenodo.21288802"
|
|
17
17
|
license: MIT
|
|
18
18
|
repository-code: "https://github.com/ErickShepherd/ncarnate"
|
|
@@ -57,7 +57,7 @@ an individual is officially representing the community in public spaces.
|
|
|
57
57
|
|
|
58
58
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
59
59
|
reported to the community leaders responsible for enforcement at
|
|
60
|
-
**
|
|
60
|
+
**dev@erickshepherd.com**. All complaints will be reviewed and investigated
|
|
61
61
|
promptly and fairly.
|
|
62
62
|
|
|
63
63
|
All community leaders are obligated to respect the privacy and security of the
|
|
@@ -1,3 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ncarnate
|
|
3
|
+
Version: 2.2.2
|
|
4
|
+
Summary: Convert HDF4/HDF-EOS2 (MODIS, AMSR-E) to CF-annotated netCDF4 and losslessly recompress netCDF/HDF5 files.
|
|
5
|
+
Project-URL: Homepage, https://github.com/ErickShepherd/ncarnate
|
|
6
|
+
Project-URL: Documentation, https://ncarnate.readthedocs.io/en/stable/
|
|
7
|
+
Project-URL: Source, https://github.com/ErickShepherd/ncarnate
|
|
8
|
+
Project-URL: Changelog, https://github.com/ErickShepherd/ncarnate/releases
|
|
9
|
+
Project-URL: Issues, https://github.com/ErickShepherd/ncarnate/issues
|
|
10
|
+
Author-email: Erick Shepherd <dev@erickshepherd.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: AMSR-E,CF conventions,GIS,HDF-EOS,HDF-EOS2,HDF4,HDF5,MODIS,atmospheric science,compression,geolocation,grid,netCDF,netCDF4,recompression,remote sensing,swath,xarray
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: netcdf4>=1.6
|
|
28
|
+
Requires-Dist: numpy>=1.26
|
|
29
|
+
Requires-Dist: pyhdf>=0.11.6
|
|
30
|
+
Requires-Dist: pyproj>=3.6
|
|
31
|
+
Requires-Dist: tqdm>=4.66
|
|
32
|
+
Provides-Extra: test
|
|
33
|
+
Requires-Dist: build>=1.0; extra == 'test'
|
|
34
|
+
Requires-Dist: hatchling>=1.27; extra == 'test'
|
|
35
|
+
Requires-Dist: pytest; extra == 'test'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
1
38
|
<p align="center">
|
|
2
39
|
<picture>
|
|
3
40
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ErickShepherd/ncarnate/main/brand/ncarnate-lockup-dark.png">
|
|
@@ -41,7 +78,7 @@ Reach for ncarnate if you are trying to:
|
|
|
41
78
|
- **Shrink an archive of scientific files** without risking the science: every
|
|
42
79
|
output is verified value-for-value against its source before it replaces
|
|
43
80
|
anything, and stored values round-trip value-identically (bit-for-bit for
|
|
44
|
-
integer and packed data; NaN- and signed-zero-insensitive for floating
|
|
81
|
+
integer and packed data; NaN- and signed-zero-insensitive for floating point).
|
|
45
82
|
- **Batch-convert a directory tree** of legacy granules to modern netCDF4 in one
|
|
46
83
|
command.
|
|
47
84
|
|
|
@@ -50,13 +87,19 @@ Reach for ncarnate if you are trying to:
|
|
|
50
87
|
Converting or recompressing a file changes *storage*, never *science data*:
|
|
51
88
|
|
|
52
89
|
- Every variable's stored values are preserved **value-identically** — bit-for-bit
|
|
53
|
-
for integer and packed data; for floating-point
|
|
90
|
+
for integer and packed data; for floating-point data, distinct NaN
|
|
54
91
|
bit-patterns and `-0.0`/`+0.0` compare equal. Packed integers stay packed;
|
|
55
92
|
`scale_factor`/`add_offset`/`_FillValue` are carried across as declarations,
|
|
56
93
|
never applied.
|
|
57
|
-
- Every dimension (including unlimited-ness), attribute (including its
|
|
58
|
-
|
|
59
|
-
|
|
94
|
+
- Every dimension (including unlimited-ness), attribute (including its exact
|
|
95
|
+
storage type — an `NC_STRING` scalar stays `NC_STRING`, verified via netCDF-C
|
|
96
|
+
type inquiry), and group survives. HDF-EOS2 `StructMetadata` is preserved
|
|
97
|
+
verbatim; names netCDF cannot hold are sanitized with the original recorded in
|
|
98
|
+
a companion attribute.
|
|
99
|
+
- **Complex-valued variables (`complex64`/`complex128`) are excluded** from the
|
|
100
|
+
fidelity guarantee: netCDF stores them as compound types, which ncarnate
|
|
101
|
+
**refuses loudly** with the stable `UNSUPPORTED_TYPE` error rather than
|
|
102
|
+
guessing at a lossy copy. Complex support is a later, evidence-backed feature.
|
|
60
103
|
- Geolocation reconstruction is strictly **additive**: the original information
|
|
61
104
|
always rides along, so the conversion never becomes the only copy of the
|
|
62
105
|
truth. Swath coordinates are attached to variables whose first two axes are
|
|
@@ -97,11 +140,18 @@ required. On platforms without a repaired `pyhdf` wheel (e.g. Linux aarch64),
|
|
|
97
140
|
building from sdist requires the system HDF4 library first (Debian/Ubuntu:
|
|
98
141
|
`apt install libhdf4-dev`).
|
|
99
142
|
|
|
100
|
-
**Windows via pip:** the netCDF/HDF5
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
143
|
+
**Windows via pip:** PyPI wheels give you the full netCDF/HDF5 surface —
|
|
144
|
+
`import ncarnate`, the CLI (`--help`/`--version`), format detection, audits,
|
|
145
|
+
manifest runs, and verified recompression — but **not** HDF4/HDF-EOS2
|
|
146
|
+
*conversion*: `pyhdf`'s Windows wheel ships no HDF4 runtime. An HDF4 attempt
|
|
147
|
+
is refused cleanly **before any output is created** with the stable
|
|
148
|
+
`[HDF4_RUNTIME_UNAVAILABLE]` message naming the detected cause, the
|
|
149
|
+
capabilities that still work, and the fix — never an unexplained import
|
|
150
|
+
traceback. An audit of an archive containing HDF4 files still completes,
|
|
151
|
+
recording those files as `unsupported` with the same code. For HDF4 on
|
|
152
|
+
Windows use the conda-forge install above (or **WSL** with the pip
|
|
153
|
+
instructions); a dedicated CI job pins this degraded-capability contract on
|
|
154
|
+
every change.
|
|
105
155
|
|
|
106
156
|
## Command line usage
|
|
107
157
|
|
|
@@ -176,6 +226,19 @@ The end-of-run summary counts converted / skipped / failed with reasons, and the
|
|
|
176
226
|
exit code is non-zero **iff** a selected record failed — so a partial failure on
|
|
177
227
|
a terabyte run surfaces loudly instead of silently mis-converting.
|
|
178
228
|
|
|
229
|
+
**Destination collision preflight.** Before any directory or output file is
|
|
230
|
+
created, every selected record's destination is computed up front — from the
|
|
231
|
+
source's *detected bytes*, never the manifest's declared format — and any
|
|
232
|
+
collision refuses the **entire run** with exit code 2 and a stable
|
|
233
|
+
`[DESTINATION_COLLISION]` message on stderr listing every involved source and
|
|
234
|
+
the contested destination. No last-writer-wins, no auto-rename, no partial
|
|
235
|
+
proceed, and nothing is written. Refused collisions include: two records
|
|
236
|
+
landing on one output path (e.g. an `a.hdf` → `a.nc` conversion next to a real
|
|
237
|
+
`a.nc` sibling), case-fold-equivalent names (one file on NTFS/APFS), duplicate
|
|
238
|
+
records for one source, an output tree overlapping a source tree (symlinks
|
|
239
|
+
resolved), and a pre-existing destination unless you pass `--skip-existing` to
|
|
240
|
+
resume.
|
|
241
|
+
|
|
179
242
|
## Library usage
|
|
180
243
|
|
|
181
244
|
```python
|
|
@@ -1,37 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: ncarnate
|
|
3
|
-
Version: 2.2.1
|
|
4
|
-
Summary: Convert HDF4/HDF-EOS2 (MODIS, AMSR-E) to CF-annotated netCDF4 and losslessly recompress netCDF/HDF5 files.
|
|
5
|
-
Project-URL: Homepage, https://github.com/ErickShepherd/ncarnate
|
|
6
|
-
Project-URL: Documentation, https://ncarnate.readthedocs.io/en/latest/
|
|
7
|
-
Project-URL: Source, https://github.com/ErickShepherd/ncarnate
|
|
8
|
-
Project-URL: Bug Tracker, https://github.com/ErickShepherd/ncarnate/issues
|
|
9
|
-
Author-email: Erick Shepherd <Contact@ErickShepherd.com>
|
|
10
|
-
License-Expression: MIT
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Keywords: AMSR-E,CF conventions,GIS,HDF-EOS,HDF-EOS2,HDF4,HDF5,MODIS,atmospheric science,compression,geolocation,grid,netCDF,netCDF4,recompression,remote sensing,swath,xarray
|
|
13
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
-
Classifier: Intended Audience :: Science/Research
|
|
15
|
-
Classifier: Operating System :: MacOS
|
|
16
|
-
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
-
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
-
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
24
|
-
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
25
|
-
Requires-Python: >=3.10
|
|
26
|
-
Requires-Dist: netcdf4>=1.6
|
|
27
|
-
Requires-Dist: numpy>=1.26
|
|
28
|
-
Requires-Dist: pyhdf>=0.11.6
|
|
29
|
-
Requires-Dist: pyproj>=3.6
|
|
30
|
-
Requires-Dist: tqdm>=4.66
|
|
31
|
-
Provides-Extra: test
|
|
32
|
-
Requires-Dist: pytest; extra == 'test'
|
|
33
|
-
Description-Content-Type: text/markdown
|
|
34
|
-
|
|
35
1
|
<p align="center">
|
|
36
2
|
<picture>
|
|
37
3
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ErickShepherd/ncarnate/main/brand/ncarnate-lockup-dark.png">
|
|
@@ -75,7 +41,7 @@ Reach for ncarnate if you are trying to:
|
|
|
75
41
|
- **Shrink an archive of scientific files** without risking the science: every
|
|
76
42
|
output is verified value-for-value against its source before it replaces
|
|
77
43
|
anything, and stored values round-trip value-identically (bit-for-bit for
|
|
78
|
-
integer and packed data; NaN- and signed-zero-insensitive for floating
|
|
44
|
+
integer and packed data; NaN- and signed-zero-insensitive for floating point).
|
|
79
45
|
- **Batch-convert a directory tree** of legacy granules to modern netCDF4 in one
|
|
80
46
|
command.
|
|
81
47
|
|
|
@@ -84,13 +50,19 @@ Reach for ncarnate if you are trying to:
|
|
|
84
50
|
Converting or recompressing a file changes *storage*, never *science data*:
|
|
85
51
|
|
|
86
52
|
- Every variable's stored values are preserved **value-identically** — bit-for-bit
|
|
87
|
-
for integer and packed data; for floating-point
|
|
53
|
+
for integer and packed data; for floating-point data, distinct NaN
|
|
88
54
|
bit-patterns and `-0.0`/`+0.0` compare equal. Packed integers stay packed;
|
|
89
55
|
`scale_factor`/`add_offset`/`_FillValue` are carried across as declarations,
|
|
90
56
|
never applied.
|
|
91
|
-
- Every dimension (including unlimited-ness), attribute (including its
|
|
92
|
-
|
|
93
|
-
|
|
57
|
+
- Every dimension (including unlimited-ness), attribute (including its exact
|
|
58
|
+
storage type — an `NC_STRING` scalar stays `NC_STRING`, verified via netCDF-C
|
|
59
|
+
type inquiry), and group survives. HDF-EOS2 `StructMetadata` is preserved
|
|
60
|
+
verbatim; names netCDF cannot hold are sanitized with the original recorded in
|
|
61
|
+
a companion attribute.
|
|
62
|
+
- **Complex-valued variables (`complex64`/`complex128`) are excluded** from the
|
|
63
|
+
fidelity guarantee: netCDF stores them as compound types, which ncarnate
|
|
64
|
+
**refuses loudly** with the stable `UNSUPPORTED_TYPE` error rather than
|
|
65
|
+
guessing at a lossy copy. Complex support is a later, evidence-backed feature.
|
|
94
66
|
- Geolocation reconstruction is strictly **additive**: the original information
|
|
95
67
|
always rides along, so the conversion never becomes the only copy of the
|
|
96
68
|
truth. Swath coordinates are attached to variables whose first two axes are
|
|
@@ -131,11 +103,18 @@ required. On platforms without a repaired `pyhdf` wheel (e.g. Linux aarch64),
|
|
|
131
103
|
building from sdist requires the system HDF4 library first (Debian/Ubuntu:
|
|
132
104
|
`apt install libhdf4-dev`).
|
|
133
105
|
|
|
134
|
-
**Windows via pip:** the netCDF/HDF5
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
106
|
+
**Windows via pip:** PyPI wheels give you the full netCDF/HDF5 surface —
|
|
107
|
+
`import ncarnate`, the CLI (`--help`/`--version`), format detection, audits,
|
|
108
|
+
manifest runs, and verified recompression — but **not** HDF4/HDF-EOS2
|
|
109
|
+
*conversion*: `pyhdf`'s Windows wheel ships no HDF4 runtime. An HDF4 attempt
|
|
110
|
+
is refused cleanly **before any output is created** with the stable
|
|
111
|
+
`[HDF4_RUNTIME_UNAVAILABLE]` message naming the detected cause, the
|
|
112
|
+
capabilities that still work, and the fix — never an unexplained import
|
|
113
|
+
traceback. An audit of an archive containing HDF4 files still completes,
|
|
114
|
+
recording those files as `unsupported` with the same code. For HDF4 on
|
|
115
|
+
Windows use the conda-forge install above (or **WSL** with the pip
|
|
116
|
+
instructions); a dedicated CI job pins this degraded-capability contract on
|
|
117
|
+
every change.
|
|
139
118
|
|
|
140
119
|
## Command line usage
|
|
141
120
|
|
|
@@ -210,6 +189,19 @@ The end-of-run summary counts converted / skipped / failed with reasons, and the
|
|
|
210
189
|
exit code is non-zero **iff** a selected record failed — so a partial failure on
|
|
211
190
|
a terabyte run surfaces loudly instead of silently mis-converting.
|
|
212
191
|
|
|
192
|
+
**Destination collision preflight.** Before any directory or output file is
|
|
193
|
+
created, every selected record's destination is computed up front — from the
|
|
194
|
+
source's *detected bytes*, never the manifest's declared format — and any
|
|
195
|
+
collision refuses the **entire run** with exit code 2 and a stable
|
|
196
|
+
`[DESTINATION_COLLISION]` message on stderr listing every involved source and
|
|
197
|
+
the contested destination. No last-writer-wins, no auto-rename, no partial
|
|
198
|
+
proceed, and nothing is written. Refused collisions include: two records
|
|
199
|
+
landing on one output path (e.g. an `a.hdf` → `a.nc` conversion next to a real
|
|
200
|
+
`a.nc` sibling), case-fold-equivalent names (one file on NTFS/APFS), duplicate
|
|
201
|
+
records for one source, an output tree overlapping a source tree (symlinks
|
|
202
|
+
resolved), and a pre-existing destination unless you pass `--skip-existing` to
|
|
203
|
+
resume.
|
|
204
|
+
|
|
213
205
|
## Library usage
|
|
214
206
|
|
|
215
207
|
```python
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
schema_version: 1
|
|
2
2
|
|
|
3
3
|
context:
|
|
4
|
-
|
|
4
|
+
# Keep this on the last published PyPI artifact. Updating the recipe to a
|
|
5
|
+
# new release requires that release's definitive sdist SHA-256 and therefore
|
|
6
|
+
# happens only after the new sdist exists on PyPI.
|
|
7
|
+
version: "2.2.1"
|
|
5
8
|
|
|
6
9
|
package:
|
|
7
10
|
name: ncarnate
|
|
@@ -9,7 +12,7 @@ package:
|
|
|
9
12
|
|
|
10
13
|
source:
|
|
11
14
|
url: https://pypi.org/packages/source/n/ncarnate/ncarnate-${{ version }}.tar.gz
|
|
12
|
-
sha256:
|
|
15
|
+
sha256: 213760512d0272a97148728463c41dc49c2142dd70b95fc11e8ce3df9603d98e
|
|
13
16
|
|
|
14
17
|
build:
|
|
15
18
|
number: 0
|
|
@@ -12,7 +12,8 @@ changes *storage* (compression level, shuffle, chunk/endian layout, container fo
|
|
|
12
12
|
### netCDF/HDF5 → netCDF4 (recompression)
|
|
13
13
|
|
|
14
14
|
Guaranteed preserved when read raw (`set_auto_maskandscale(False)`), value-identical —
|
|
15
|
-
bit-for-bit for integer/packed data, NaN- and signed-zero-insensitive for floating
|
|
15
|
+
bit-for-bit for integer/packed data, NaN- and signed-zero-insensitive for floating point
|
|
16
|
+
(complex is excluded — see the guarantee boundary):
|
|
16
17
|
|
|
17
18
|
- every variable's stored values (packed integers stay packed — no mask-and-scale
|
|
18
19
|
round-trip, which silently re-quantizes);
|
|
@@ -30,7 +31,8 @@ replaced until the new file has been written and re-opened successfully.
|
|
|
30
31
|
|
|
31
32
|
- every SDS's values value-identical — bit-for-bit for integer and char8 data (char8 SDS
|
|
32
33
|
map to netCDF `NC_CHAR` and round-trip byte-for-byte), NaN- and signed-zero-insensitive
|
|
33
|
-
for floating
|
|
34
|
+
for floating point (HDF4's DFNT type system has no complex type, so complex never
|
|
35
|
+
arises on this path); dimensions (with their HDF4 names) and attributes preserved; fill/scale
|
|
34
36
|
carried as declarations;
|
|
35
37
|
- `StructMetadata.0` (and other EOS metadata attributes) preserved **verbatim** as
|
|
36
38
|
attributes of an `HDFEOS_INFORMATION` group — reconstruction never becomes the only copy
|
|
@@ -64,11 +66,35 @@ edges). Interpolated variables carry a `comment` attribute saying they are inter
|
|
|
64
66
|
edge pixels outside the geolocation envelope are linearly extrapolated; geolocation fill
|
|
65
67
|
propagates, never interpolated across.
|
|
66
68
|
|
|
69
|
+
### Manifest runs: destination collision preflight (no-data-loss gate)
|
|
70
|
+
|
|
71
|
+
Fidelity is meaningless if two outputs silently overwrite each other, so
|
|
72
|
+
manifest-driven runs (`convert --manifest`) add a **whole-run destination
|
|
73
|
+
preflight** before any directory or output is created: every selected record's
|
|
74
|
+
destination is computed up front from its *detected bytes* (never the
|
|
75
|
+
manifest's declared format — the manifest is untrusted input), and any
|
|
76
|
+
collision refuses the **entire run** with the stable `DESTINATION_COLLISION`
|
|
77
|
+
code (exit code 2, rendered on stderr) listing every involved source and the
|
|
78
|
+
contested destination. No last-writer-wins, no auto-rename, no partial
|
|
79
|
+
proceed, zero output-tree mutation. Refused classes: duplicate or
|
|
80
|
+
case-fold-equivalent destinations (one file on NTFS/APFS), an `.hdf` → `.nc`
|
|
81
|
+
conversion colliding with a real `.nc` sibling, duplicate records for one
|
|
82
|
+
source (symlink-aliased included), source-tree/output-tree overlap (symlinks
|
|
83
|
+
resolved), a destination aliasing a selected source, and a pre-existing
|
|
84
|
+
destination without `--skip-existing`. Pinned end-to-end — including the
|
|
85
|
+
zero-mutation guarantee at the process boundary — by
|
|
86
|
+
`tests/test_convert_collisions.py`.
|
|
87
|
+
|
|
67
88
|
### Guarantee boundary
|
|
68
89
|
|
|
69
90
|
- Compound, VLen, enum, and opaque netCDF4 types are **out of scope for v2**: ncarnate
|
|
70
|
-
raises a clear unsupported-type error
|
|
71
|
-
files surveyed use them; revisit on demand).
|
|
91
|
+
raises a clear unsupported-type error (stable code `UNSUPPORTED_TYPE`) rather than
|
|
92
|
+
guessing (none of the target-domain files surveyed use them; revisit on demand).
|
|
93
|
+
- **Complex-valued variables (`complex64`/`complex128`) are excluded from the fidelity
|
|
94
|
+
guarantee** (owner decision, 2026-07-16): netCDF stores recognized complex encodings
|
|
95
|
+
as compound types (e.g. `_PFNC_DOUBLE_COMPLEX_TYPE`), so they are refused by the
|
|
96
|
+
compound rule above — loudly, with `UNSUPPORTED_TYPE`, before any output is written.
|
|
97
|
+
Complex support is a later, evidence-backed feature, not a silent partial copy.
|
|
72
98
|
- Already-lossy packing is preserved as-is, never "un-quantized".
|
|
73
99
|
- HDF4 inputs are read via the pyhdf SD API: SDS datasets and attributes. Vdata/Vgroup
|
|
74
100
|
payloads beyond SD are out of scope for v2.
|
|
@@ -77,9 +103,10 @@ propagates, never interpolated across.
|
|
|
77
103
|
|
|
78
104
|
For every committed fixture: convert/recompress → re-open both files raw → assert
|
|
79
105
|
(a) value arrays equal on raw reads (`numpy.array_equal`, with `equal_nan` set for
|
|
80
|
-
|
|
81
|
-
sizes, and unlimited flags equal, (c) attribute sets equal
|
|
82
|
-
|
|
106
|
+
floats — bit-for-bit for integer/packed data, NaN-/±0-insensitive for floats), (b) dimension names,
|
|
107
|
+
sizes, and unlimited flags equal, (c) attribute sets equal — values *and* netCDF storage
|
|
108
|
+
types (`NC_STRING` vs `NC_CHAR`, via netCDF-C `nc_inq_atttype`; `tests/test_attribute_types.py`)
|
|
109
|
+
— (d) group tree equal, and (e) output ≤ input size at `complevel≥7` for the
|
|
83
110
|
compressible fixtures. The packed-integer + `_FillValue` fixture exists precisely because
|
|
84
111
|
v1 failed both (a crash and a silent re-quantization).
|
|
85
112
|
|
|
@@ -102,7 +129,7 @@ exactly (written with each source attribute's true HDF4 type code via `attr.info
|
|
|
102
129
|
inferred from type-erased Python values — e.g. an INT16 `_FillValue` stays INT16).
|
|
103
130
|
|
|
104
131
|
HDF-EOS2 fixtures are trimmed from real granules kept outside the repo
|
|
105
|
-
(
|
|
132
|
+
(`the local source-granule archive/` + `PROVENANCE.md` there); each fixture has a
|
|
106
133
|
`.provenance.json` sidecar (source granule, SHA-256, trim parameters). Trim rules: the
|
|
107
134
|
AMSR-E grid fixture keeps `StructMetadata.0` **verbatim** (grid dims unchanged; metadata
|
|
108
135
|
lists DataFields not carried over — readers must tolerate that, as with subsetted granules
|
|
@@ -115,23 +142,3 @@ values *and* HDF4 type codes preserved) passed 2026-07-08.
|
|
|
115
142
|
|
|
116
143
|
Raw-granule cross-checks (the 29–60 MB originals) stay in a local, non-CI test mark.
|
|
117
144
|
|
|
118
|
-
## History-rewrite record (D1 + D2, executed 2026-07-08)
|
|
119
|
-
|
|
120
|
-
Executed in one `git filter-repo` pass (per
|
|
121
|
-
[the plan](plans/2026-07-08-repo-improvement-plan.md) §2 D1/D2), before any v2 code:
|
|
122
|
-
|
|
123
|
-
- **Stripped from all history:** `netcdf_recompressor/AMSR_E_L3_SeaIce12km_V15_20070101.hdf`
|
|
124
|
-
(62.5 MB), `netcdf4_compressor/AMSR_E_L3_SeaIce12km_V15_20070101.hdf` (62.1 MB),
|
|
125
|
-
`netcdf4_compressor/2007_MYD_CALIPSO_ice_cloud_collocation_discrepancies_test.nc` (9.5 MB).
|
|
126
|
-
- **Author/committer rewrite:** the owner's private Gmail → `24425940+ErickShepherd@users.noreply.github.com`
|
|
127
|
-
(mailmap in the same pass); local `user.email` set to the noreply address.
|
|
128
|
-
- **Pack size:** `size-pack` **30.20 MiB → 61.70 KiB** (`git count-objects -vH`, after
|
|
129
|
-
`git gc --prune=now`); `.git` = 264 KB on disk. No `.hdf`/`.nc` objects remain in
|
|
130
|
-
`git rev-list --all --objects`.
|
|
131
|
-
- **Content integrity:** the rewritten tip's tree hash equals the pre-rewrite tip's tree
|
|
132
|
-
(`12761123d5d26d62e2025abe8f5dfcf3e360ef3c`) — file content is untouched; only history
|
|
133
|
-
metadata changed.
|
|
134
|
-
- **Backup:** full pre-rewrite bundle at
|
|
135
|
-
`~/ncarnate-data/backup/netcdf_recompressor-pre-rewrite-20260708.bundle`
|
|
136
|
-
(`git bundle verify` clean). All commit SHAs changed, as planned and victimless
|
|
137
|
-
(repo never public). **Pushing the rewritten history remains owner-gated.**
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.. Theme-aware logo for Read the Docs. The README's <picture> block keys off the OS
|
|
2
|
+
``prefers-color-scheme``, which does NOT track furo's independent light/dark toggle
|
|
3
|
+
(toggling furo to light while the OS is in dark mode otherwise shows the dark,
|
|
4
|
+
cream-wordmark logo on a white page). Furo's ``.only-light`` / ``.only-dark`` classes
|
|
5
|
+
follow furo's ACTUAL theme, so render the logo with those here and skip the README's
|
|
6
|
+
own <picture> in the include below (``:start-after: </p>`` drops the logo block, whose
|
|
7
|
+
closing ``</p>`` is unique in the README).
|
|
8
|
+
|
|
9
|
+
.. image:: https://raw.githubusercontent.com/ErickShepherd/ncarnate/main/brand/ncarnate-lockup.png
|
|
10
|
+
:class: only-light
|
|
11
|
+
:width: 460
|
|
12
|
+
:align: center
|
|
13
|
+
:alt: ncarnate
|
|
14
|
+
|
|
15
|
+
.. image:: https://raw.githubusercontent.com/ErickShepherd/ncarnate/main/brand/ncarnate-lockup-dark.png
|
|
16
|
+
:class: only-dark
|
|
17
|
+
:width: 460
|
|
18
|
+
:align: center
|
|
19
|
+
:alt: ncarnate
|
|
20
|
+
|
|
21
|
+
.. include:: ../README.md
|
|
22
|
+
:parser: myst_parser.sphinx_
|
|
23
|
+
:start-after: </p>
|
|
24
|
+
|
|
25
|
+
.. toctree::
|
|
26
|
+
:hidden:
|
|
27
|
+
:maxdepth: 2
|
|
28
|
+
|
|
29
|
+
Overview <self>
|
|
30
|
+
API reference <api>
|