Skip to content

Commit 3c3ded8

Browse files
committed
Use add_note() to annotate exceptions when encoding fails
We now always add variable names and contents when encoding fails, in contrast to the current practice where variable names and values are only sometimes specified, based on the `name` passed into `VariableCoder.encode()`. This provides better debugging experience for users. In the future, we might remove `name` from `VariableCoder.encode()` because this makes it redundant. For example, attempting to save a int64 fail in netCDF3 file now shows something like the following: ValueError: could not safely cast array from int64 to int32... Raised while encoding variable 'invalid' with value <xarray.Variable (invalid: 1)> Size: 8B array([9223372036854775807]) Note that `Exception.add_note()` is a Python 3.11+ feature, so this PR will need to wait until #10438 is submitted.
1 parent 293297d commit 3c3ded8

File tree

4 files changed

+44
-8
lines changed

4 files changed

+44
-8
lines changed

doc/whats-new.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ New Features
1414
~~~~~~~~~~~~
1515
- Expose :py:class:`~xarray.indexes.RangeIndex`, and :py:class:`~xarray.indexes.CoordinateTransformIndex` as public api
1616
under the ``xarray.indexes`` namespace. By `Deepak Cherian <https://github.com/dcherian>`_.
17-
17+
- Better error messages when encoding data to be written to disk fails.
18+
By `Stephan Hoyer <https://github.com/shoyer>`_
1819

1920
Breaking changes
2021
~~~~~~~~~~~~~~~~

xarray/backends/common.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,23 @@ def encode(self, variables, attributes):
389389
attributes : dict-like
390390
391391
"""
392-
variables = {k: self.encode_variable(v, name=k) for k, v in variables.items()}
393-
attributes = {k: self.encode_attribute(v) for k, v in attributes.items()}
394-
return variables, attributes
392+
encoded_variables = {}
393+
for k, v in variables.items():
394+
try:
395+
encoded_variables[k] = self.encode_variable(v)
396+
except Exception as e:
397+
e.add_note(f"Raised while encoding variable {k!r} with value {v!r}")
398+
raise
399+
400+
encoded_attributes = {}
401+
for k, v in attributes.items():
402+
try:
403+
encoded_attributes[k] = self.encode_attribute(v)
404+
except Exception as e:
405+
e.add_note(f"Raised while encoding attribute {k!r} with value {v!r}")
406+
raise
407+
408+
return encoded_variables, encoded_attributes
395409

396410
def encode_variable(self, v, name=None):
397411
"""encode one variable"""
@@ -641,9 +655,7 @@ def encode(self, variables, attributes):
641655
variables = {
642656
k: ensure_dtype_not_object(v, name=k) for k, v in variables.items()
643657
}
644-
variables = {k: self.encode_variable(v, name=k) for k, v in variables.items()}
645-
attributes = {k: self.encode_attribute(v) for k, v in attributes.items()}
646-
return variables, attributes
658+
return super().encode(variables, attributes)
647659

648660

649661
class BackendEntrypoint:

xarray/conventions.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,13 @@ def cf_encoder(variables: T_Variables, attributes: T_Attrs):
792792
# add encoding for time bounds variables if present.
793793
_update_bounds_encoding(variables)
794794

795-
new_vars = {k: encode_cf_variable(v, name=k) for k, v in variables.items()}
795+
new_vars = {}
796+
for k, v in variables.items():
797+
try:
798+
new_vars[k] = encode_cf_variable(v, name=k)
799+
except Exception as e:
800+
e.add_note(f"Raised while encoding variable {k!r} with value {v!r}")
801+
raise
796802

797803
# Remove attrs from bounds variables (issue #2921)
798804
for var in new_vars.values():

xarray/tests/test_backends_common.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
from __future__ import annotations
22

3+
import re
4+
35
import numpy as np
46
import pytest
57

8+
import xarray as xr
69
from xarray.backends.common import _infer_dtype, robust_getitem
10+
from xarray.tests import requires_scipy
711

812

913
class DummyFailure(Exception):
@@ -43,3 +47,16 @@ def test_robust_getitem() -> None:
4347
def test_infer_dtype_error_on_mixed_types(data):
4448
with pytest.raises(ValueError, match="unable to infer dtype on variable"):
4549
_infer_dtype(data, "test")
50+
51+
52+
@requires_scipy
53+
def test_encoding_failure_note():
54+
# Create an arbitrary value that cannot be encoded in netCDF3
55+
ds = xr.Dataset({"invalid": np.array([2**63 - 1], dtype=np.int64)})
56+
with pytest.raises(
57+
ValueError,
58+
match=re.escape(
59+
"Raised while encoding variable 'invalid' with value <xarray.Variable"
60+
),
61+
):
62+
ds.to_netcdf()

0 commit comments

Comments
 (0)