ncfunc
Functional-style reading and writing of NetCDF data files.
ncfunc wraps netCDF4 and trades
its open-handle, object-oriented style for small stateless functions: every
call opens the file, does one thing, closes it, and leaves nothing behind.
Along the way it decodes CF time coordinates, subsets by coordinate bounds,
and turns opaque netCDF4 errors into messages that say what failed, where,
and why - keeping the original exception chained underneath.
Features
- Stateless functions - no
Datasethandles to open, close or leak - Readable errors - Exceptions are raised with details such as file path, variable name, dimension name, etc.
- Time decoding -
read_timedecodes CF'delta since epoch'units to datenum serials, including fixed-length calendars (360_day,365_day) - Bounds-based subsetting -
read_withinslices variables by coordinate ranges instead of index arithmetic - Multi-file reads -
read_mf_withinreads the same variable across many files and combines the results along a time or spatial axis - Cached metadata - structure queries reuse a path-keyed cache that
auto-refreshes when a file's mtime changes (
DatasetMeta) - One-shot writer -
save()creates dimensions, coordinates and the variable in a single call, with overwrite protection
Installation
Requires Python >= 3.10, with dependencies numpy, netCDF4 and datenum installed automatically:
pip install ncfunc
# or
uv add ncfunc
Quickstart
import ncfunc as ncf
file = "tests/ersst_2022-2024.nc"
API overview
| function | purpose |
|---|---|
var_names(path) |
variable names |
dim_names(path, var) |
dimension names of a variable |
shape(path, var) / ndim(path, var) |
shape / rank of a variable |
attr_names(path, var) / attr_val(path, var, attr) |
attributes of a variable, or of the file with var='/' |
var_names_include(...) |
find variables by substring and rank |
attr_names_include(...) |
find attributes by substring |
read(path, var, subsets?) |
read a variable into np.ndarray |
read_time(path, ...) |
read and decode a time coordinate to datenum values |
read_within(path, var, withins, ...) |
bounds-based subset read with coordinates |
read_mf_within(paths, var, withins, ...) |
bounds-based read across many files, combined along an axis |
write(path, var, data, subsets?) |
write into an existing variable |
create(path, var, dim_specs, ...) |
create a variable (+ dimensions), idempotently |
save(path, data, ...) |
create + write a variable and its coordinates in one call |
write_attr(path, var, attr, value) |
set a variable or root ('/') attribute |
DatasetMeta(path) |
static structural snapshot, cached per resolved path |
Inspect metadata
>>> ncf.var_names(file)
('time', 'lon', 'lat', 'sst', 'ssta')
>>> ncf.shape(file, 'sst')
(36, 121, 240)
>>> ncf.dim_names(file, 'sst')
('time', 'lat', 'lon')
>>> ncf.attr_val(file, '/', 'title') # '/' selects the file's root attributes
'NOAA monthly ERSSTv6 (in situ only)'
Search helpers locate variables and attributes by substring, and insist on an unambiguous match (by rank or count) before returning:
>>> ncf.var_names_include(file, ('sst',), accept_ndims=3)
('sst', 'ssta')
>>> ncf.var_names_include(file, name_includes=('sst',), accept_ndims=3, accept_counts=(2,))
('sst', 'ssta')
Read data
sst = ncf.read(file, 'sst') # whole variable
top = ncf.read(file, 'sst', ((slice(-4, None),) * 3)) # last 4 steps of every dim
Decode time
>>> import datenum as dn
>>> t = ncf.read_time(file)
>>> dn.to_string(t[0]), dn.to_string(t[-1])
('2022-01-15 00:00:00', '2024-12-15 00:00:00')
The time variable, its units attribute and its calendar attribute are all
guessed; pass time_name, unit_name, calendar_name explicitly to override.
Month/year-based units decode via month arithmetic, day-based ones against the
declared calendar - including 360_day and 365_day fixed calendars.
Subset by bounds, not indices
read_within takes one (lower, upper) pair per dimension of the variable
(None = unbounded), reads only what intersects, and returns both the data
and the bounded coordinates. Coordinates come back ascending even when stored
descending; the data is flipped to stay aligned:
>>> sst, (time, lat, lon) = ncf.read_within(
... file,
... 'sst',
... withins=((None, None), (-30.0, 30.0), (150.0, 210.0)),
... )
>>> sst.shape, lat[0], lat[-1]
((36, 41, 41), -30.0, 30.0)
The time dimension is found by guessing a time-named coordinate; point at it
explicitly with idim_time=<index> if the guess would be wrong, or disable
time handling with decode_time=False.
Read across many files
read_mf_within applies the same bounds-based read to every file in a list and
combines the results. With stacked_along=None a new leading axis is prepended
(one slice per file, in path order); with an integer stacked_along the values
(and that coordinate) are concatenated along the given dimension - typically
the time axis of files split by time:
tiles = [f"era5_2022-{m:02d}.nc" for m in range(1, 13)]
tas, (time, lat, lon) = ncf.read_mf_within(
tiles,
"tas",
withins=((None, None), (-30.0, 30.0), (150.0, 210.0)),
stacked_along=0, # concatenate along the time axis
)
Non-stacked coordinates are taken from the last file, so tiles are expected to share them.
Write data
save writes a variable plus its dimensions in one shot. The first entry is
the variable, the rest are its 1-D dimensions:
import numpy as np
ncf.save(
"out.nc",
{
"tas": np.arange(12, dtype="f4").reshape(3, 4),
"time": np.array([0, 31, 59]),
"lon": np.linspace(0.5, 3.5, 4),
},
)
This creates out.nc with dimensions time and lon, coordinate variables
stamped with CF-ish attributes (axis, units, standard_name), and the
compressed tas variable. If tas already exists in the file, save asks
for confirmation on the terminal; overwrite_var=True and overwrite_dim=True
skips the question.
For finer control, use the pieces directly:
ncf.create(path, "tas", {"time": 3, "lon": 4}) # idempotent; missing dims created
ncf.create(path, "tas", {"time": 3, "lon": 4}, chunksizes=(1, 4)) # chunk the variable
ncf.write(path, "tas", data) # full write, shape must match
ncf.write(path, "tas", data, ((slice(0, 1), slice(None)),)) # or by slices
ncf.write_attr(path, "/", "history", "created today") # '/' = root attribute
create accepts an optional chunksizes (one size per dimension) to set the
variable's HDF5 chunk shape; it defaults to netCDF4's own chunking.
Development
uv sync # install dependencies
uv run pytest # run the test suite
uv run ruff check src/ tests/
uv run ruff format --check src/ tests/
uv run ty check src/ tests/
License
MIT - see LICENSE.
Metadata
Release files for ncfunc 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ncfunc-0.2.1.tar.gz | 13.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ncfunc-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 28.3 kB
Release files / ncfunc-0.2.1.tar.gz
| Download URL | ncfunc-0.2.1.tar.gz |
|---|---|
| Size | 13.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3c4ef111f25ef01de3df7e33c259158b44f9289f81a55bbceabfdcd12b1faeb2
|
|
BLAKE2b-256 checksum How to use checksums |
b15a91b7c6a17ca907b6b1883ec04ef043afde1a2bf21fe995c5527962c49a48
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / ncfunc-0.2.1-py3-none-any.whl
| Download URL | ncfunc-0.2.1-py3-none-any.whl |
|---|---|
| Size | 14.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4c94eab1147a9e9ba629ce41ca49970a43e044d932085d3c7fb0f33601880ef1
|
|
BLAKE2b-256 checksum How to use checksums |
81d92b66f6158d1caf2ea3850b713e82f9d703ee0729d0c144d21fb1780beb6d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency log