diff --git a/Lib/__init__.py b/Lib/__init__.py index f122245b..ffd877aa 100644 --- a/Lib/__init__.py +++ b/Lib/__init__.py @@ -4,6 +4,7 @@ # Errors from .error import CDMSError # noqa +from .Cdunif import ReadOnlyKeyError from lazy_object_proxy import Proxy from . import dataset from . import selectors diff --git a/Lib/dataset.py b/Lib/dataset.py index 41230d03..c402b44c 100644 --- a/Lib/dataset.py +++ b/Lib/dataset.py @@ -2105,7 +2105,13 @@ def createVariableCopy(self, var, id=None, attributes=None, axes=None, extbounds if attname not in ["id", "datatype", "parent"]: if isinstance(attval, string_types): attval = str(attval) - setattr(newvar, str(attname), attval) + try: + setattr(newvar, str(attname), attval) + except Cdunif.ReadOnlyKeyError as e: + # Suppress the exception context + err = "Cannot write read-only attribute '{!s}', must be " \ + "removed from '{!s}' variable before writing to file".format(e, var.id) + raise CDMSError(err) from None if (attname == "_FillValue") or (attname == "missing_value"): setattr(newvar, "_FillValue", attval) setattr(newvar, "missing_value", attval) diff --git a/Lib/fvariable.py b/Lib/fvariable.py index a3625c2d..ddf5c70b 100644 --- a/Lib/fvariable.py +++ b/Lib/fvariable.py @@ -157,12 +157,7 @@ def __setattr__(self, name, value): if hasattr(self, "parent") and self.parent is None: raise CDMSError(FileClosedWrite + self.id) if (name not in self.__cdms_internals__) and (value is not None): - try: - setattr(self._obj_, str(name), value) - except Exception: - raise CDMSError( - "Setting %s.%s=%s" % - (self.id, name, repr(value))) + setattr(self._obj_, str(name), value) self.attributes[name] = value self.__dict__[name] = value diff --git a/Src/Cdunifmodule.c b/Src/Cdunifmodule.c index 751feefc..0172042f 100644 --- a/Src/Cdunifmodule.c +++ b/Src/Cdunifmodule.c @@ -71,6 +71,7 @@ PyThread_type_lock Cdunif_lock; #endif static PyObject *CdunifError; +static PyObject *ReadOnlyKeyError; /* Set error string */ static void Cdunif_seterror(void) { @@ -2067,10 +2068,10 @@ static int PyCdunifFile_SetAttribute(PyCdunifFileObject *self, PyObject *nameobj char *name = PyStr_AsString(nameobj); if (check_if_open(self, 1)) { if (strcmp(name, "dimensions") == 0 - || strcmp(name, "variables") == 0 + || strcmp(name, "variables") == 0 || strcmp(name, "dimensioninfo") == 0 || strcmp(name, "__dict__") == 0) { - PyErr_SetString(PyExc_TypeError, "object has read-only attributes"); + PyErr_Format(ReadOnlyKeyError, "%s", name); return -1; } define_mode(self, 1); @@ -2423,9 +2424,10 @@ static int PyCdunifVariable_SetAttribute(PyCdunifVariableObject *self, PyObject *nameobj, PyObject *value) { char *name = PyStr_AsString(nameobj); if (check_if_open(self->file, 1)) { - if (strcmp(name, "shape") == 0 || strcmp(name, "dimensions") == 0 + if (strcmp(name, "shape") == 0 + || strcmp(name, "dimensions") == 0 || strcmp(name, "__dict__") == 0) { - PyErr_SetString(PyExc_TypeError, "object has read-only attributes"); + PyErr_Format(ReadOnlyKeyError, "%s", name); return -1; } define_mode(self->file, 1); @@ -3456,9 +3458,11 @@ MODULE_INIT_FUNC (Cdunif) { PyCapsule_New((void *) PyCdunif_API, "_C_API", NULL)); - CdunifError = PyStr_FromString("CdunifError"); + CdunifError = PyStr_FromString("CdunifError"); + ReadOnlyKeyError = PyErr_NewException("Cdunif.ReadOnlyKeyError", NULL, NULL); PyDict_SetItemString(d, "CdunifError", CdunifError); + PyDict_SetItemString(d, "ReadOnlyKeyError", ReadOnlyKeyError); /* Check for errors */ if (PyErr_Occurred()) diff --git a/tests/test_dataset_io.py b/tests/test_dataset_io.py index d3ff7ffd..e23bbecf 100644 --- a/tests/test_dataset_io.py +++ b/tests/test_dataset_io.py @@ -3,6 +3,9 @@ import string import os import sys +import cdat_info +import subprocess +import shutil cdms2.setNetcdfUseParallelFlag(0) @@ -21,6 +24,34 @@ def setUp(self): def testFileAttributes(self): self.assertEqual(self.file.id, "test") + def testWriteReadOnlyAttribute(self): + out = self.getTempFile("read_only.nc", "w") + out.write(self.u) + out.close() + + temp = self.getTempFile("read_only.nc", "a") + + with self.assertRaises(cdms2.ReadOnlyKeyError): + setattr(temp, "dimensions", "test") + + with self.assertRaises(cdms2.ReadOnlyKeyError): + setattr(temp["u"], "dimensions", "test") + + def testWriteThroughReadOnlyAtttribute(self): + iname = os.path.join(cdat_info.get_sampledata_path(), "clt.nc") + oname = os.path.join(os.getcwd(), "clt-modded.nc") + + shutil.copyfile(iname, oname) + + subprocess.run(["ncatted", "-a", "dimensions,clt,c,c,'lat lon'", oname]) + + ifile = cdms2.open(oname) + data = ifile("clt") + ifile.close() + + with cdms2.open(oname, "w") as ofile, self.assertRaises(cdms2.CDMSError): + ofile.write(data) + def testScalarSlice(self): u = self.u scalar = u[0,0,0]