diff --git a/changelog.org b/changelog.org index 9320077..92b93f4 100644 --- a/changelog.org +++ b/changelog.org @@ -1,3 +1,10 @@ +* v0.6.3 +- make ~[]~ for attributes taking a string only for the data type kind + to check if the key exist, raise if not. + Previously if the attribute hadn't been opened before, the call + would fail even if it exists in the file +- make ~withAttr~ check if the attribute exists in the file + first. Previously would fail if it hadn't been opened before * v0.6.2 - fix a small memory leak related to ~cstrings~ not being freed - fix the ~-d:DEBUG_HDF5~ option for debug information diff --git a/src/nimhdf5/attributes.nim b/src/nimhdf5/attributes.nim index 77afb6f..ea6ce5f 100644 --- a/src/nimhdf5/attributes.nim +++ b/src/nimhdf5/attributes.nim @@ -101,28 +101,6 @@ proc readAttributeInfo(h5attr: H5Attributes, # add to this attribute object h5attr.attr_tab[name] = attr -proc readAttributeInfo(h5attr: H5Attributes, key: string) = - ## reads all information about the attribute `key` from the H5 file - ## NOTE: this does ``not`` read the value of that attribute! - var attr = newH5Attr() - attr.attr_id = openAttribute(h5attr, key) - attr.opened = true - readAttributeInfo(h5attr, attr, key) - -proc read_all_attributes*(h5attr: H5Attributes) = - ## proc to read all attributes of the parent from file and store the names - ## and attribute ids in `h5attr`. - ## NOTE: If possible try to avoid using this proc! However, if you must, make - ## sure to close all attributes after usage, otherwise memory leaks might happen. - # first get how many objects there are - h5attr.num_attrs = h5attr.getNumAttrs - for i in 0 ..< h5attr.num_attrs: - var attr = newH5Attr() - attr.attr_id = openAttrByIdx(h5attr, i) - attr.opened = true - let name = getAttrName(attr.attr_id) - readAttributeInfo(h5attr, attr, name) - proc existsAttribute*(parent: ParentID, name: string): bool = ## simply check if the given attribute name corresponds to an attribute ## of the given object @@ -147,6 +125,38 @@ proc contains*(attr: H5Attributes, key: string): bool = ## field of a group or dataset result = attr.parent_id.existsAttribute(key) +proc readAttributeInfo*(h5attr: H5Attributes, key: string, closeAttribute = false) = + ## Checks if the given attribute `key` exists in the set of attributes + ## and raises if not. + ## If it exists, it reads the attribute information, making it available + ## in the `attr_tab`. + ## + ## NOTE: this does ``not`` read the value of that attribute! + let attr_exists = key in h5attr + if attr_exists: + var attr = newH5Attr() + attr.attr_id = openAttribute(h5attr, key) + attr.opened = true + readAttributeInfo(h5attr, attr, key) + if closeAttribute: # close again + attr.close() + else: + raise newException(KeyError, "No attribute `$#` exists in object `$#`" % [key, h5attr.parent_name]) + +proc read_all_attributes*(h5attr: H5Attributes) = + ## proc to read all attributes of the parent from file and store the names + ## and attribute ids in `h5attr`. + ## NOTE: If possible try to avoid using this proc! However, if you must, make + ## sure to close all attributes after usage, otherwise memory leaks might happen. + # first get how many objects there are + h5attr.num_attrs = h5attr.getNumAttrs + for i in 0 ..< h5attr.num_attrs: + var attr = newH5Attr() + attr.attr_id = openAttrByIdx(h5attr, i) + attr.opened = true + let name = getAttrName(attr.attr_id) + readAttributeInfo(h5attr, attr, name) + proc deleteAttribute*(h5id: ParentID, name: string): bool = ## deletes the given attribute `name` on the object defined by ## the H5 id `h5id` @@ -498,17 +508,11 @@ proc read_attribute*[T](h5attr: H5Attributes, name: string, dtype: typedesc[T]): ## throws: ## KeyError: In case the key does not exist as an attribute # TODO: check err values! - let attr_exists = name in h5attr - var err: herr_t - if attr_exists: - # in case of existence, read the data and return - h5attr.readAttributeInfo(name) - let attr = h5attr.attr_tab[name] - result = attr.readAttribute(dtype) - # close attribute again after reading - h5attr.attr_tab[name].close() - else: - raise newException(KeyError, "No attribute `$#` exists in object `$#`" % [name, h5attr.parent_name]) + h5attr.readAttributeInfo(name) # raises KeyError if it does not exist + let attr = h5attr.attr_tab[name] + result = attr.readAttribute(dtype) + # close attribute again after reading + h5attr.attr_tab[name].close() proc `[]`*[T](h5attr: H5Attributes, name: string, dtype: typedesc[T]): T = # convenience access to read_attribute @@ -517,7 +521,8 @@ proc `[]`*[T](h5attr: H5Attributes, name: string, dtype: typedesc[T]): T = proc `[]`*(h5attr: H5Attributes, name: string): DtypeKind = # accessing H5Attributes by string simply returns the datatype of the stored # attribute as an AnyKind value - h5attr.attr_tab[name].dtypeAnyKind + h5attr.readAttributeInfo(name, closeAttribute = true) # raises KeyError if it does not exist + result = h5attr.attr_tab[name].dtypeAnyKind proc `[]`*[T](attr: H5Attr, dtype: typedesc[T]): T = # convenience access to readAttribute for the actual attribute diff --git a/src/nimhdf5/datatypes.nim b/src/nimhdf5/datatypes.nim index ead32ba..b79a0dc 100644 --- a/src/nimhdf5/datatypes.nim +++ b/src/nimhdf5/datatypes.nim @@ -565,7 +565,8 @@ proc close*(attr: var H5AttrObj) = $(attr.attr_id.id) & "!") withDebug: echo "Closed attribute with status ", err - attr.opened = false + # close state regardless of object, to make sure we don't leave it open accidentally + attr.opened = false proc close*(attr: H5Attr) = attr[].close() diff --git a/src/nimhdf5/hdf5_json.nim b/src/nimhdf5/hdf5_json.nim index d2cc71d..9fd9ddb 100644 --- a/src/nimhdf5/hdf5_json.nim +++ b/src/nimhdf5/hdf5_json.nim @@ -204,6 +204,9 @@ template withAttr*(h5attr: H5Attributes, name: string, actions: untyped) = ## to get access to the data as Nim objects, but also when copying attributes. ## Copying itself can also be done by simply getting the size, reading into a buffer, ## copying data type and space and writing the same buffer to a new location. + + # read attribute info so that `attr_tab` knows it + h5attr.readAttributeInfo(name) # raises KeyError if it does not exist let attrObj {.inject.} = h5attr.attr_tab[name] case attrObj.dtypeAnyKind of dkBool: @@ -287,6 +290,8 @@ template withAttr*(h5attr: H5Attributes, name: string, actions: untyped) = else: let attr {.inject.} = readJson(attrObj) actions + # close the attribute again + h5attr.attr_tab[name].close() ## The following JSON related procs are placeholders. We might implement them fully to be ## able to write compound data at runtime baesd on JSON data. diff --git a/tests/tattributes.nim b/tests/tattributes.nim index d1ef488..05ec5c0 100644 --- a/tests/tattributes.nim +++ b/tests/tattributes.nim @@ -32,19 +32,20 @@ proc write_attrs(grp: var H5Group) = grp.attrs["ComplexNamedSeqTuple"] = NamedTupSeqComplexAttr proc assert_attrs(grp: var H5Group) = - template readAndCheck(arg, typ, exp): untyped = + template readAndCheck(arg, typ, exp, kind): untyped = let data = grp.attrs[arg, typ] echo "Read: ", data doAssert data == exp, "Mismatch, was = " & $data & ", but expected = " & $exp - - readAndCheck("Time", string, TimeStr) - readAndCheck("Counter", int, Counter) - readAndCheck("Seq", seq[int], SeqAttr) - readAndCheck("SeqStr", seq[string], SeqStrAttr) - readAndCheck("Tuple", (float, int), TupAttr) - readAndCheck("NamedTuple", tuple[foo: float, bar: int], NamedTupAttr) - readAndCheck("ComplexNamedTuple", tuple[foo: string, bar: float], NamedTupStrAttr) - readAndCheck("ComplexNamedSeqTuple", seq[tuple[foo: string, bar: float]], NamedTupSeqComplexAttr) + doAssert grp.attrs[arg] == kind, "Mismatch, was = " & $grp.attrs[arg] & ", but expected = " & $kind + + readAndCheck("Time", string, TimeStr, dkString) + readAndCheck("Counter", int, Counter, dkInt64) + readAndCheck("Seq", seq[int], SeqAttr, dkSequence) + readAndCheck("SeqStr", seq[string], SeqStrAttr, dkSequence) + readAndCheck("Tuple", (float, int), TupAttr, dkObject) + readAndCheck("NamedTuple", tuple[foo: float, bar: int], NamedTupAttr, dkObject) + readAndCheck("ComplexNamedTuple", tuple[foo: string, bar: float], NamedTupStrAttr, dkObject) + readAndCheck("ComplexNamedSeqTuple", seq[tuple[foo: string, bar: float]], NamedTupSeqComplexAttr, dkSequence) doAssert("Time" in grp.attrs) doAssert("NoTime" notin grp.attrs)