diff --git a/nixio/block.py b/nixio/block.py index 1bf849d9..622f7b04 100644 --- a/nixio/block.py +++ b/nixio/block.py @@ -38,8 +38,9 @@ class Block(Entity): - def __init__(self, nixparent, h5group, compression=Compression.Auto): - super(Block, self).__init__(nixparent, h5group) + def __init__(self, nixfile, nixparent, h5group, + compression=Compression.Auto): + super(Block, self).__init__(nixfile, nixparent, h5group) self._groups = None self._data_arrays = None self._tags = None @@ -49,9 +50,10 @@ def __init__(self, nixparent, h5group, compression=Compression.Auto): self._data_frames = None @classmethod - def _create_new(cls, nixparent, h5parent, name, type_, compression): - newentity = super(Block, cls)._create_new(nixparent, h5parent, - name, type_) + def create_new(cls, nixparent, h5parent, name, type_, compression): + nixfile = nixparent # file is parent + newentity = super(Block, cls).create_new(nixfile, nixparent, h5parent, + name, type_) newentity._compr = compression return newentity @@ -101,8 +103,8 @@ def create_multi_tag(self, name="", type_="", positions=None, "{}-extents".format(type_), data=extents) extcreated = True - mtag = MultiTag._create_new(self, multi_tags, - name, type_, positions) + mtag = MultiTag.create_new(self.file, self, multi_tags, + name, type_, positions) except Exception as e: msg = "MultiTag Creation Failed" if poscreated: @@ -150,7 +152,7 @@ def create_tag(self, name="", type_="", position=0, tags = self._h5group.open_group("tags") if name in tags: raise exceptions.DuplicateName("create_tag") - tag = Tag._create_new(self, tags, name, type_, position) + tag = Tag.create_new(self.file, self, tags, name, type_, position) return tag # Source @@ -170,7 +172,7 @@ def create_source(self, name, type_): sources = self._h5group.open_group("sources") if name in sources: raise exceptions.DuplicateName("create_source") - src = Source._create_new(self, sources, name, type_) + src = Source.create_new(self.file, self, sources, name, type_) return src # Group @@ -190,7 +192,7 @@ def create_group(self, name, type_): groups = self._h5group.open_group("groups") if name in groups: raise exceptions.DuplicateName("open_group") - grp = Group._create_new(self, groups, name, type_) + grp = Group.create_new(self.file, self, groups, name, type_) return grp def create_data_array(self, name="", array_type="", dtype=None, shape=None, @@ -250,8 +252,8 @@ def create_data_array(self, name="", array_type="", dtype=None, shape=None, raise exceptions.DuplicateName("create_data_array") if compression == Compression.Auto: compression = self._compr - da = DataArray._create_new(self, data_arrays, name, array_type, - dtype, shape, compression) + da = DataArray.create_new(self.file, self, data_arrays, name, array_type, + dtype, shape, compression) if data is not None: da.write_direct(data) return da @@ -328,7 +330,7 @@ def create_data_frame(self, name="", type_="", col_dict=None, ) else: # col_dtypes is None and data is None raise ValueError( - "The data type of each column have to be specified" + "The data type of each column have to be specified" ) if len(col_names) != len(col_dict): raise exceptions.DuplicateColumnName @@ -348,7 +350,7 @@ def create_data_frame(self, name="", type_="", col_dict=None, # data is None or type(data[0]) != np.void # data_type doesnt matter raise ValueError( - "No information about column names is provided!" + "No information about column names is provided!" ) if col_dict is not None: @@ -362,8 +364,8 @@ def create_data_frame(self, name="", type_="", col_dict=None, dt_arr = list(col_dict.items()) col_dtype = np.dtype(dt_arr) - df = DataFrame._create_new(self, data_frames, name, - type_, shape, col_dtype, compression) + df = DataFrame.create_new(self.file, self, data_frames, name, + type_, shape, col_dtype, compression) if data is not None: if type(data[0]) == np.void: @@ -510,7 +512,7 @@ def sources(self): This is a read only attribute. """ if self._sources is None: - self._sources = SourceContainer("sources", self, Source) + self._sources = SourceContainer("sources", self.file, self, Source) return self._sources @property @@ -522,7 +524,8 @@ def multi_tags(self): This is a read only attribute. """ if self._multi_tags is None: - self._multi_tags = Container("multi_tags", self, MultiTag) + self._multi_tags = Container("multi_tags", self.file, + self, MultiTag) return self._multi_tags @property @@ -534,7 +537,7 @@ def tags(self): This is a read only attribute. """ if self._tags is None: - self._tags = Container("tags", self, Tag) + self._tags = Container("tags", self.file, self, Tag) return self._tags @property @@ -547,13 +550,15 @@ def data_arrays(self): This is a read only attribute. """ if self._data_arrays is None: - self._data_arrays = Container("data_arrays", self, DataArray) + self._data_arrays = Container("data_arrays", self.file, + self, DataArray) return self._data_arrays @property def data_frames(self): if self._data_frames is None: - self._data_frames = Container("data_frames", self, DataFrame) + self._data_frames = Container("data_frames", self.file, + self, DataFrame) return self._data_frames @property @@ -565,7 +570,7 @@ def groups(self): This is a read only attribute. """ if self._groups is None: - self._groups = Container("groups", self, Group) + self._groups = Container("groups", self.file, self, Group) return self._groups # metadata @@ -579,7 +584,8 @@ def metadata(self): :type: Section """ if "metadata" in self._h5group: - return Section(None, self._h5group.open_group("metadata")) + return Section(self.file, None, + self._h5group.open_group("metadata")) else: return None diff --git a/nixio/container.py b/nixio/container.py index bd2e2a79..0804a81a 100644 --- a/nixio/container.py +++ b/nixio/container.py @@ -23,14 +23,15 @@ class Container(object): checking and instantiations) """ - def __init__(self, name, parent, itemclass): + def __init__(self, name, nixfile, parent, itemclass): self._backend = parent._h5group.open_group(name) self._itemclass = itemclass + self._file = nixfile self._parent = parent self._name = name def _inst_item(self, item): - return self._itemclass(self._parent, item) + return self._itemclass(self._file, self._parent, item) def __len__(self): return len(self._backend) @@ -56,10 +57,7 @@ def __delitem__(self, item): self._itemclass.__name__) ) - root = self._backend.h5root - if not root: - root = self._parent._h5group - root.delete_all([item.id]) + self._file._h5group.delete_all([item.id]) def __iter__(self): for group in self._backend: @@ -121,8 +119,7 @@ def __delitem__(self, item): # the root block secids = [s.id for s in item.find_sections()] - root = self._backend.file - root.delete_all(secids) + self._file._h5group.delete_all(secids) class SourceContainer(Container): @@ -145,9 +142,7 @@ def __delitem__(self, item): # the root block srcids = [s.id for s in item.find_sources()] srcids.append(item.id) - - root = self._backend.h5root - root.delete_all(srcids) + self._file._h5group.delete_all(srcids) class LinkContainer(Container): @@ -180,7 +175,8 @@ class LinkContainer(Container): """ def __init__(self, name, parent, itemclass, itemstore): - super(LinkContainer, self).__init__(name, parent, itemclass) + super(LinkContainer, self).__init__(name, parent.file, + parent, itemclass) self._itemstore = itemstore def __delitem__(self, item): @@ -249,7 +245,7 @@ def __contains__(self, item): return False def _inst_item(self, item): - return self._itemclass(self._itemstore._parent, item) + return self._itemclass(self._file, self._itemstore._parent, item) @staticmethod def _item_key(item): diff --git a/nixio/data_array.py b/nixio/data_array.py index 7384f51a..22d1a9a9 100644 --- a/nixio/data_array.py +++ b/nixio/data_array.py @@ -32,16 +32,16 @@ class DataSliceMode(Enum): class DataArray(Entity, DataSet): - def __init__(self, nixparent, h5group): - super(DataArray, self).__init__(nixparent, h5group) + def __init__(self, nixfile, nixparent, h5group): + super(DataArray, self).__init__(nixfile, nixparent, h5group) self._sources = None self._dimensions = None @classmethod - def _create_new(cls, nixparent, h5parent, name, type_, data_type, shape, - compression): - newentity = super(DataArray, cls)._create_new(nixparent, h5parent, - name, type_) + def create_new(cls, nixfile, nixparent, h5parent, name, type_, + data_type, shape, compression): + newentity = super(DataArray, cls).create_new(nixfile, nixparent, + h5parent, name, type_) datacompr = False if compression == Compression.DeflateNormal: datacompr = True @@ -85,12 +85,11 @@ def append_set_dimension(self, labels=None): :returns: The newly created SetDimension. :rtype: SetDimension """ - dimgroup = self._h5group.open_group("dimensions") - index = len(dimgroup) + 1 - setdim = SetDimension._create_new(dimgroup, index) + index = len(self.dimensions) + 1 + setdim = SetDimension.create_new(self, index) if labels: setdim.labels = labels - if self._parent._parent.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() return setdim @@ -107,17 +106,15 @@ def append_sampled_dimension(self, sampling_interval, label=None, :returns: The newly created SampledDimension. :rtype: SampledDimension """ - dimgroup = self._h5group.open_group("dimensions") - index = len(dimgroup) + 1 - smpldim = SampledDimension._create_new(dimgroup, index, - sampling_interval) + index = len(self.dimensions) + 1 + smpldim = SampledDimension.create_new(self, index, sampling_interval) if label: smpldim.label = label if unit: smpldim.unit = unit if offset: smpldim.offset = offset - if self._parent._parent.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() return smpldim @@ -132,13 +129,12 @@ def append_range_dimension(self, ticks, label=None, unit=None): :returns: The newly created RangeDimension. :rtype: RangeDimension """ - dimgroup = self._h5group.open_group("dimensions") - index = len(dimgroup) + 1 - rdim = RangeDimension._create_new(dimgroup, index, ticks) + index = len(self.dimensions) + 1 + rdim = RangeDimension.create_new(self, index, ticks) if label: rdim.label = label rdim.unit = unit - if self._parent._parent.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() return rdim @@ -158,11 +154,10 @@ def append_data_frame_dimension(self, data_frame, column_idx=None): :returns: Thew newly created DataFrameDimension. :rtype: DataFrameDimension """ - dimgroup = self._h5group.open_group("dimensions") - index = len(dimgroup) + 1 - dfdim = DataFrameDimension._create_new(dimgroup, index, - data_frame, column_idx) - if self._parent._parent.time_auto_update: + index = len(self.dimensions) + 1 + dfdim = DataFrameDimension.create_new(self, index, + data_frame, column_idx) + if self.file.auto_update_timestamps: self.force_updated_at() return dfdim @@ -183,7 +178,6 @@ def append_alias_range_dimension(self): if self._dimension_count() > 0: raise ValueError("Cannot append additional alias dimension. " "There must only be one!") - dimgroup = self._h5group.open_group("dimensions") # check if existing unit is SI if self.unit: u = self.unit @@ -194,7 +188,10 @@ def append_alias_range_dimension(self): "Current SI unit is {}".format(u), "DataArray.append_alias_range_dimension" ) - return RangeDimension._create_new_alias(dimgroup, 1, self) + aliasdim = RangeDimension.create_new_alias(self, 1) + if self.file.auto_update_timestamps: + self.force_updated_at() + return aliasdim def delete_dimensions(self): """ @@ -258,7 +255,7 @@ def polynom_coefficients(self, coeff): else: dtype = DataType.Double self._h5group.write_data("polynom_coefficients", coeff, dtype) - if self._parent._parent.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() @property @@ -276,7 +273,7 @@ def expansion_origin(self): def expansion_origin(self, eo): util.check_attr_type(eo, Number) self._h5group.set_attr("expansion_origin", eo) - if self._parent._parent.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() @property @@ -294,7 +291,7 @@ def label(self): def label(self, l): util.check_attr_type(l, str) self._h5group.set_attr("label", l) - if self._parent._parent.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() @property @@ -315,7 +312,7 @@ def unit(self, u): u = None util.check_attr_type(u, str) if (self._dimension_count() == 1 and - self.dimensions[0].dimension_type == DimensionType.Range and + self.dimensions[0].dimension_type == DimensionType.Range and self.dimensions[0].is_alias and u is not None): if not (util.units.is_si(u) or util.units.is_compound(u)): raise InvalidUnit( @@ -324,7 +321,7 @@ def unit(self, u): "DataArray.unit" ) self._h5group.set_attr("unit", u) - if self._parent._parent.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() def get_slice(self, positions, extents=None, mode=DataSliceMode.Index): @@ -390,8 +387,8 @@ def dimensions(self): :type: Container of dimension descriptors. """ if self._dimensions is None: - self._dimensions = DimensionContainer("dimensions", self, - Dimension) + self._dimensions = DimensionContainer("dimensions", self.file, + self, Dimension) return self._dimensions # metadata @@ -406,7 +403,8 @@ def metadata(self): :type: Section """ if "metadata" in self._h5group: - return Section(None, self._h5group.open_group("metadata")) + return Section(self.file, None, + self._h5group.open_group("metadata")) else: return None diff --git a/nixio/data_frame.py b/nixio/data_frame.py index ab5fea36..a4ce238e 100644 --- a/nixio/data_frame.py +++ b/nixio/data_frame.py @@ -26,17 +26,17 @@ class DataFrame(Entity, DataSet): - def __init__(self, nixparent, h5group): - super(DataFrame, self).__init__(nixparent, h5group) + def __init__(self, nixfile, nixparent, h5group): + super(DataFrame, self).__init__(nixfile, nixparent, h5group) self._sources = None self._columns = None self._rows = None @classmethod - def _create_new(cls, nixparent, h5parent, - name, type_, shape, col_dtype, compression): - newentity = super(DataFrame, cls)._create_new(nixparent, h5parent, - name, type_) + def create_new(cls, nixfile, nixparent, h5parent, name, type_, + shape, col_dtype, compression): + newentity = super(DataFrame, cls).create_new(nixfile, nixparent, + h5parent, name, type_) newentity._h5group.create_dataset("data", (shape, ), col_dtype) return newentity @@ -348,7 +348,7 @@ def units(self, u): util.check_attr_type(i, str) unit = np.array(u, util.vlen_str_dtype) self._h5group.set_attr("units", unit) - if self._parent._parent.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() @property @@ -423,7 +423,8 @@ def metadata(self): :type: Section """ if "metadata" in self._h5group: - return Section(None, self._h5group.open_group("metadata")) + return Section(self.file, None, + self._h5group.open_group("metadata")) else: return None diff --git a/nixio/dimensions.py b/nixio/dimensions.py index 7f1e2e8b..0eda9d22 100644 --- a/nixio/dimensions.py +++ b/nixio/dimensions.py @@ -32,20 +32,18 @@ def _inst_item(self, item): DimensionType.DataFrame: DataFrameDimension, }[DimensionType(item.get_attr("dimension_type"))] idx = item.name - return cls(item, idx) + return cls(self._parent, idx) class Dimension(object): - def __init__(self, h5group, index): + def __init__(self, nixfile, data_array, index): + dimgroup = data_array._h5group.open_group("dimensions") + h5group = dimgroup.open_group(str(index)) self._h5group = h5group self.dim_index = int(index) - - @classmethod - def _create_new(cls, parent, index): - h5group = parent.open_group(str(index)) - newdim = cls(h5group, index) - return newdim + self._parent = data_array + self._file = nixfile @property def dimension_type(self): @@ -72,12 +70,13 @@ def __repr__(self): class SampledDimension(Dimension): - def __init__(self, h5group, index): - super(SampledDimension, self).__init__(h5group, index) + def __init__(self, data_array, index): + nixfile = data_array.file + super(SampledDimension, self).__init__(nixfile, data_array, index) @classmethod - def _create_new(cls, parent, index, sample): - newdim = super(SampledDimension, cls)._create_new(parent, index) + def create_new(cls, data_array, index, sample): + newdim = cls(data_array, index) newdim._set_dimension_type(DimensionType.Sample) newdim.sampling_interval = sample return newdim @@ -168,21 +167,22 @@ def offset(self, o): class RangeDimension(Dimension): - def __init__(self, h5group, index): - super(RangeDimension, self).__init__(h5group, index) + def __init__(self, data_array, index): + nixfile = data_array.file + super(RangeDimension, self).__init__(nixfile, data_array, index) @classmethod - def _create_new(cls, parent, index, ticks): - newdim = super(RangeDimension, cls)._create_new(parent, index) + def create_new(cls, data_array, index, ticks): + newdim = cls(data_array, index) newdim._set_dimension_type(DimensionType.Range) newdim._h5group.write_data("ticks", ticks, dtype=DataType.Double) return newdim @classmethod - def _create_new_alias(cls, parent, index, da): - newdim = super(RangeDimension, cls)._create_new(parent, index) + def create_new_alias(cls, data_array, index): + newdim = cls(data_array, index) newdim._set_dimension_type(DimensionType.Range) - newdim._h5group.create_link(da, da.id) + newdim._h5group.create_link(data_array, data_array.id) return newdim @property @@ -297,12 +297,13 @@ def axis(self, count, start=0): class SetDimension(Dimension): - def __init__(self, h5group, index): - super(SetDimension, self).__init__(h5group, index) + def __init__(self, data_array, index): + nixfile = data_array.file + super(SetDimension, self).__init__(nixfile, data_array, index) @classmethod - def _create_new(cls, parent, index): - newdim = super(SetDimension, cls)._create_new(parent, index) + def create_new(cls, data_array, index): + newdim = cls(data_array, index) newdim._set_dimension_type(DimensionType.Set) return newdim @@ -321,15 +322,18 @@ def labels(self, labels): class DataFrameDimension(Dimension): - def __init__(self, h5group, index): - super(DataFrameDimension, self).__init__(h5group, index) + def __init__(self, data_array, index): + nixfile = data_array.file + super(DataFrameDimension, self).__init__(nixfile, data_array, index) @classmethod - def _create_new(cls, parent, index, data_frame, column): + def create_new(cls, data_array, index, data_frame, column): """ Create a new Dimension that points to a DataFrame - :param parent: DataArray the dimension will be attached to + :param data_array: The DataArray this Dimension belongs to + + :param parent: The H5Group for the dimensions :param data_frame: the referenced DataFrame for this Dimension @@ -338,7 +342,7 @@ def _create_new(cls, parent, index, data_frame, column): :return: The new DataFrameDimension """ - newdim = super(DataFrameDimension, cls)._create_new(parent, index) + newdim = cls(data_array, index) newdim.data_frame = data_frame newdim.column_idx = column newdim._set_dimension_type(DimensionType.DataFrame) @@ -419,7 +423,9 @@ def get_label(self, index=None): def data_frame(self): dfname = self._h5group.get_by_pos(0).name grp = self._h5group.open_group(dfname) - df = DataFrame(grp.h5root.group["data_frames"], grp) + nixblock = self._parent._parent + nixfile = self._file + df = DataFrame(nixfile, nixblock, grp) return df @data_frame.setter diff --git a/nixio/entity.py b/nixio/entity.py index fc8b99dd..6babde5f 100644 --- a/nixio/entity.py +++ b/nixio/entity.py @@ -12,13 +12,14 @@ class Entity(object): - def __init__(self, nixparent, h5group): + def __init__(self, nixfile, nixparent, h5group): util.check_entity_id(h5group.get_attr("entity_id")) self._h5group = h5group self._parent = nixparent + self._file = nixfile @classmethod - def _create_new(cls, nixparent, h5parent, name=None, type_=None): + def create_new(cls, nixfile, nixparent, h5parent, name=None, type_=None): if name and type_: util.check_entity_name_and_type(name, type_) id_ = util.create_id() @@ -29,7 +30,8 @@ def _create_new(cls, nixparent, h5parent, name=None, type_=None): h5group.set_attr("name", name) h5group.set_attr("type", type_) h5group.set_attr("entity_id", id_) - newentity = cls(nixparent, h5group) + + newentity = cls(nixfile, nixparent, h5group) newentity.force_created_at() newentity.force_updated_at() return newentity @@ -54,6 +56,16 @@ def created_at(self): """ return util.str_to_time(self._h5group.get_attr("created_at")) + @property + def file(self): + """ + Reference to the NIX File object. + This is a read-only property. + + :rtype: nixio.File + """ + return self._file + def force_created_at(self, t=None): """ Sets the creation time `created_at` to the given time @@ -108,10 +120,7 @@ def definition(self): def definition(self, d): util.check_attr_type(d, str) self._h5group.set_attr("definition", d) - par = self._parent - while isinstance(par, Entity): - par = par._parent - if par.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() @property @@ -142,10 +151,7 @@ def type(self, t): raise AttributeError("type can't be None") util.check_attr_type(t, str) self._h5group.set_attr("type", t) - par = self._parent - while isinstance(par, Entity): - par = par._parent - if par.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() def __eq__(self, other): diff --git a/nixio/feature.py b/nixio/feature.py index bedca5ea..9feccdf0 100644 --- a/nixio/feature.py +++ b/nixio/feature.py @@ -14,17 +14,18 @@ class Feature(object): - def __init__(self, nixparent, h5group): + def __init__(self, nixfile, nixparent, h5group): util.check_entity_id(h5group.get_attr("entity_id")) self._h5group = h5group self._parent = nixparent + self._file = nixfile @classmethod - def _create_new(cls, nixparent, h5parent, data, link_type): + def create_new(cls, nixfile, nixparent, h5parent, data, link_type): id_ = util.create_id() h5group = h5parent.open_group(id_) h5group.set_attr("entity_id", id_) - newfeature = cls(nixparent, h5group) + newfeature = cls(nixfile, nixparent, h5group) newfeature.link_type = link_type newfeature.data = data newfeature._h5group.set_attr("created_at", @@ -38,6 +39,10 @@ def _create_new(cls, nixparent, h5parent, data, link_type): def id(self): return self._h5group.get_attr("entity_id") + @property + def file(self): + return self._file + @property def link_type(self): return LinkType(self._h5group.get_attr("link_type")) @@ -48,7 +53,7 @@ def link_type(self, lt): lt = lt.lower() lt = LinkType(lt) self._h5group.set_attr("link_type", lt.value) - if self._parent._parent._parent.time_auto_update: + if self.file.auto_update_timestamps: t = util.now_int() self._h5group.set_attr("updated_at", util.time_to_str(t)) @@ -56,7 +61,7 @@ def link_type(self, lt): def data(self): if "data" not in self._h5group: raise RuntimeError("Feature.data: DataArray not found!") - return DataArray(self._parent._parent, + return DataArray(self.file, self._parent._parent, self._h5group.open_group("data")) @data.setter @@ -69,7 +74,7 @@ def data(self, da): if "data" in self._h5group: del self._h5group["data"] self._h5group.create_link(da, "data") - if self._parent._parent._parent.time_auto_update: + if self.file.auto_update_timestamps: t = util.now_int() self._h5group.set_attr("updated_at", util.time_to_str(t)) diff --git a/nixio/file.py b/nixio/file.py index 3f415aa5..a2985549 100644 --- a/nixio/file.py +++ b/nixio/file.py @@ -85,7 +85,8 @@ def make_fcpl(): class File(object): def __init__(self, path, mode=FileMode.ReadWrite, - compression=Compression.Auto, auto_update_time=False): + compression=Compression.Auto, + auto_update_timestamps=True): """ Open a NIX file, or create it if it does not exist. @@ -93,6 +94,9 @@ def __init__(self, path, mode=FileMode.ReadWrite, :param mode: FileMode ReadOnly, ReadWrite, or Overwrite. (default: ReadWrite) :param compression: No, DeflateNormal, Auto (default: Auto) + :param auto_update_timestamps: Enable/disable automatic updating of + 'updated_at' timestamp. (default: True) + :return: nixio.File object """ try: @@ -120,7 +124,7 @@ def __init__(self, path, mode=FileMode.ReadWrite, self._root = H5Group(self._h5file, "/") self._h5group = self._root # to match behaviour of other objects - self._time_auto_update = True + self._auto_update_timestamps = auto_update_timestamps self._check_header(mode) self.mode = mode self._data = self._root.open_group("data", create=True) @@ -129,7 +133,6 @@ def __init__(self, path, mode=FileMode.ReadWrite, self.force_created_at() if "updated_at" not in self._h5file.attrs: self.force_updated_at() - self.time_auto_update = auto_update_time if compression == Compression.Auto: compression = Compression.No self._compr = compression @@ -139,10 +142,10 @@ def __init__(self, path, mode=FileMode.ReadWrite, @classmethod def open(cls, path, mode=FileMode.ReadWrite, compression=Compression.Auto, - backend=None, auto_update_time=False): + backend=None, auto_update_timestamps=True): if backend is not None: warn("Backend selection is deprecated. Ignoring value.") - return cls(path, mode, compression, auto_update_time) + return cls(path, mode, compression, auto_update_timestamps) def _create_header(self): self._set_format() @@ -218,18 +221,24 @@ def _set_format(self): self._root.set_attr("format", FILE_FORMAT.encode("ascii")) @property - def time_auto_update(self): + def auto_update_timestamps(self): """ - A user defined flag which decided if time should always be updated - when properties are changed. + If enabled, automatically updates the 'updated_at' attribute when an + object's data or attributes are changed. :type: bool """ - return self._time_auto_update + return self._auto_update_timestamps + + @auto_update_timestamps.setter + def auto_update_timestamps(self, enable): + """ + If enabled, automatically updates the 'updated_at' attribute when an + object's data or attributes are changed. - @time_auto_update.setter - def time_auto_update(self, auto_update_flag): - self._time_auto_update = auto_update_flag + :type: bool + """ + self._auto_update_timestamps = enable @property def created_at(self): @@ -416,7 +425,7 @@ def create_block(self, name="", type_="", compression=Compression.Auto, raise ValueError("Block with the given name already exists!") if compression == Compression.Auto: compression = self._compr - block = Block._create_new(self, self._data, name, type_, compression) + block = Block.create_new(self, self._data, name, type_, compression) return block # Section @@ -437,7 +446,7 @@ def create_section(self, name, type_="undefined", oid=None): """ if name in self.sections: raise DuplicateName("create_section") - sec = Section._create_new(self, self._metadata, name, type_, oid) + sec = Section.create_new(self, self, self._metadata, name, type_, oid) return sec @property @@ -450,7 +459,7 @@ def blocks(self): create_block method of File. This is a read-only attribute. """ if self._blocks is None: - self._blocks = Container("data", self, Block) + self._blocks = Container("data", self, self, Block) return self._blocks def find_sections(self, filtr=lambda _: True, limit=None): @@ -486,7 +495,7 @@ def sections(self): This is a read-only property. """ if self._sections is None: - self._sections = SectionContainer("metadata", self, Section) + self._sections = SectionContainer("metadata", self, self, Section) return self._sections diff --git a/nixio/group.py b/nixio/group.py index 628aaff8..9360509f 100644 --- a/nixio/group.py +++ b/nixio/group.py @@ -19,8 +19,8 @@ class Group(Entity): - def __init__(self, nixparent, h5group): - super(Group, self).__init__(nixparent, h5group) + def __init__(self, nixfile, nixparent, h5group): + super(Group, self).__init__(nixfile, nixparent, h5group) self._data_arrays = None self._data_frames = None self._tags = None @@ -28,9 +28,9 @@ def __init__(self, nixparent, h5group): self._sources = None @classmethod - def _create_new(cls, nixparent, h5parent, name, type_): - newentity = super(Group, cls)._create_new(nixparent, h5parent, - name, type_) + def create_new(cls, nixfile, nixparent, h5parent, name, type_): + newentity = super(Group, cls).create_new(nixfile, nixparent, h5parent, + name, type_) return newentity @property @@ -115,7 +115,8 @@ def metadata(self): :type: Section """ if "metadata" in self._h5group: - return Section(None, self._h5group.open_group("metadata")) + return Section(self.file, None, + self._h5group.open_group("metadata")) else: return None diff --git a/nixio/hdf5/h5group.py b/nixio/hdf5/h5group.py index b7d6c703..28195042 100644 --- a/nixio/hdf5/h5group.py +++ b/nixio/hdf5/h5group.py @@ -12,11 +12,8 @@ from .h5dataset import H5DataSet from ..datatype import DataType -from ..block import Block -from ..section import Section from .. import util -from ..exceptions import InvalidEntity class H5Group(object): @@ -297,56 +294,6 @@ def change_id(_, igrp): g.visititems(change_id) return g - @property - def file(self): - """ - An H5Group object which represents the file root. - - :return: H5Group at '/' - """ - return H5Group(self.group.file, "/", create=False) - - @property - def h5root(self): - """ - Returns the H5Group of the Block or top-level Section which contains - this object. Returns None if requested on the file root '/' or the - /data or /metadata groups. - - :return: Top level object containing this group (H5Group) - """ - pathparts = self.group.name.split("/") - if len(pathparts) == 3: - return self - if self.group.name == "/": - return None - if len(pathparts) == 2: - return None - - return self.parent.h5root - - @property - def root(self): - """ - Returns the Block or top-level Section which contains this object. - Returns None if requested on the file root '/' or the /data or - /metadata groups. - - :return: Top level object containing this group (Block or Section) - """ - h5root = self.h5root - if h5root is None: - return None - topgroup = self.group.name.split("/")[1] - if topgroup == "data": - cls = Block - return cls(h5root.parent, h5root) - elif topgroup == "metadata": - cls = Section - return cls(h5root.parent, h5root) - else: - raise InvalidEntity - @property def parent(self): return self.create_from_h5obj(self._parent) diff --git a/nixio/multi_tag.py b/nixio/multi_tag.py index 7ad3402b..2d208667 100644 --- a/nixio/multi_tag.py +++ b/nixio/multi_tag.py @@ -22,16 +22,16 @@ class MultiTag(BaseTag): - def __init__(self, nixparent, h5group): - super(MultiTag, self).__init__(nixparent, h5group) + def __init__(self, nixfile, nixparent, h5group): + super(MultiTag, self).__init__(nixfile, nixparent, h5group) self._sources = None self._references = None self._features = None @classmethod - def _create_new(cls, nixparent, h5parent, name, type_, positions): - newentity = super(MultiTag, cls)._create_new(nixparent, h5parent, - name, type_) + def create_new(cls, nixfile, nixparent, h5parent, name, type_, positions): + newentity = super(MultiTag, cls).create_new(nixfile, nixparent, + h5parent, name, type_) newentity.positions = positions return newentity @@ -44,7 +44,8 @@ def positions(self): """ if "positions" not in self._h5group: raise RuntimeError("MultiTag.positions: DataArray not found!") - return DataArray(self._parent, self._h5group.open_group("positions")) + return DataArray(self.file, self._parent, + self._h5group.open_group("positions")) @positions.setter def positions(self, da): @@ -53,7 +54,7 @@ def positions(self, da): if "positions" in self._h5group: del self._h5group["positions"] self._h5group.create_link(da, "positions") - if self._parent._parent.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() @property @@ -65,9 +66,9 @@ def extents(self): :type: DataArray or None """ if "extents" in self._h5group: - return DataArray(self._parent, self._h5group.open_group("extents")) - else: - return None + return DataArray(self.file, self._parent, + self._h5group.open_group("extents")) + return None @extents.setter def extents(self, da): @@ -75,7 +76,7 @@ def extents(self, da): del self._h5group["extents"] else: self._h5group.create_link(da, "extents") - if self._parent._parent.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() @property @@ -106,14 +107,10 @@ def features(self): :type: Container of Feature. """ if self._features is None: - self._features = FeatureContainer("features", self, Feature) + self._features = FeatureContainer("features", self.file, + self, Feature) return self._features - def _get_slice(self, data, index): - offset, count = self._get_offset_and_count(data, index) - sl = tuple(slice(o, o + c) for o, c in zip(offset, count)) - return sl - def _calc_data_slices(self, data, index): positions = self.positions extents = self.extents @@ -266,7 +263,7 @@ def metadata(self): :type: Section """ if "metadata" in self._h5group: - return Section(None, self._h5group.open_group("metadata")) + return Section(self.file, None, self._h5group.open_group("metadata")) else: return None diff --git a/nixio/property.py b/nixio/property.py index 0145126d..bf18729a 100644 --- a/nixio/property.py +++ b/nixio/property.py @@ -92,13 +92,13 @@ def get_odml_type(cls, dtype): class Property(Entity): """An odML Property""" - def __init__(self, nixparent, h5dataset): - super(Property, self).__init__(nixparent, h5dataset) + def __init__(self, nixfile, nixparent, h5dataset): + super(Property, self).__init__(nixfile, nixparent, h5dataset) self._h5dataset = self._h5group @classmethod - def _create_new(cls, nixparent, h5parent, name, - dtype, shape=None, oid=None): + def create_new(cls, nixfile, nixparent, h5parent, name, + dtype, shape=None, oid=None): if shape is None or shape[0] == 0: shape = (8, ) util.check_entity_name(name) @@ -112,7 +112,7 @@ def _create_new(cls, nixparent, h5parent, name, h5dataset.set_attr("entity_id", oid) - newentity = cls(nixparent, h5dataset) + newentity = cls(nixfile, nixparent, h5dataset) newentity.force_created_at() newentity.force_updated_at() diff --git a/nixio/section.py b/nixio/section.py index 8837f2f0..d3a14ab5 100644 --- a/nixio/section.py +++ b/nixio/section.py @@ -48,16 +48,16 @@ def __getattribute__(self, item): class Section(Entity): - def __init__(self, nixparent, h5group): - super(Section, self).__init__(nixparent, h5group) + def __init__(self, nixfile, nixparent, h5group): + super(Section, self).__init__(nixfile, nixparent, h5group) self._sec_parent = None self._sections = None self._properties = None @classmethod - def _create_new(cls, nixparent, h5parent, name, type_, oid=None): - newentity = super(Section, cls)._create_new(nixparent, h5parent, - name, type_) + def create_new(cls, nixfile, nixparent, h5parent, name, type_, oid=None): + newentity = super(Section, cls).create_new(nixfile, nixparent, + h5parent, name, type_) if util.is_uuid(oid): newentity._h5group.set_attr("entity_id", oid) @@ -83,7 +83,7 @@ def create_section(self, name, type_="undefined", oid=None): sections = self._h5group.open_group("sections", True) if name in sections: raise exceptions.DuplicateName("create_section") - sec = Section._create_new(self, sections, name, type_, oid) + sec = Section.create_new(self.file, self, sections, name, type_, oid) sec._sec_parent = self return sec @@ -169,7 +169,8 @@ def create_property(self, name="", values_or_dtype=0, oid=None, raise TypeError("Array contains inconsistent values.") shape = (len(vals),) - prop = Property._create_new(self, properties, name, dtype, shape, oid) + prop = Property.create_new(self.file, self, properties, + name, dtype, shape, oid) prop.values = vals return prop @@ -225,7 +226,7 @@ def reference(self): def reference(self, ref): util.check_attr_type(ref, str) self._h5group.set_attr("reference", ref) - if self.file.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() @property @@ -240,7 +241,7 @@ def link(self): if "link" not in self._h5group: return None - return Section(self, self._h5group.open_group("link")) + return Section(self.file, self, self._h5group.open_group("link")) @link.setter def link(self, id_or_sec): @@ -249,16 +250,16 @@ def link(self, id_or_sec): if isinstance(id_or_sec, Section): sec = id_or_sec else: - rootsec = Section(self, self._h5group.h5root) + rootsec = Section(self.file, self, self._h5group.h5root) sec = rootsec.find_sections(filtr=lambda x: x.id == id_or_sec) self._h5group.create_link(sec, "link") - if self.file.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() def inherited_properties(self): properties = self._h5group.open_group("properties") - inhprops = [Property(self, h5prop) for h5prop in properties] + inhprops = [Property(self.file, self, h5prop) for h5prop in properties] if self.link: inhprops.extend(self.link.inherited_properties()) return inhprops @@ -277,7 +278,7 @@ def repository(self): def repository(self, r): util.check_attr_type(r, str) self._h5group.set_attr("repository", r) - if self.file.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() @property @@ -292,9 +293,8 @@ def parent(self): """ if self._sec_parent is not None: return self._sec_parent - rootmd = self._h5group.file.open_group("metadata") - # Assuming most metadata trees are shallow---doing BFS - sections = [Section(None, sg) for sg in rootmd] + # BFS + sections = list(self.file.sections) if self in sections: # Top-level section return None @@ -308,18 +308,6 @@ def parent(self): return None - @property - def file(self): - """ - Root file object. - - :type: File - """ - par = self._parent - while isinstance(par, Entity): - par = par._parent - return par - @property def referring_objects(self): objs = [] @@ -444,7 +432,8 @@ def sections(self): :type: Container of Section """ if self._sections is None: - self._sections = SectionContainer("sections", self, Section) + self._sections = SectionContainer("sections", self.file, + self, Section) return self._sections def __len__(self): @@ -504,7 +493,8 @@ def props(self): :type: Container of Property """ if self._properties is None: - self._properties = Container("properties", self, Property) + self._properties = Container("properties", self.file, + self, Property) return self._properties def pprint(self, indent=2, max_depth=1, max_length=80, current_depth=0): diff --git a/nixio/source.py b/nixio/source.py index ec2e7ab0..dc33987c 100644 --- a/nixio/source.py +++ b/nixio/source.py @@ -18,14 +18,14 @@ class Source(Entity): - def __init__(self, nixparent, h5group): - super(Source, self).__init__(nixparent, h5group) + def __init__(self, nixfile, nixparent, h5group): + super(Source, self).__init__(nixfile, nixparent, h5group) self._sources = None @classmethod - def _create_new(cls, nixparent, h5parent, name, type_): - newentity = super(Source, cls)._create_new(nixparent, h5parent, - name, type_) + def create_new(cls, nixfile, nixparent, h5parent, name, type_): + newentity = super(Source, cls).create_new(nixfile, nixparent, h5parent, + name, type_) return newentity # Source @@ -45,7 +45,7 @@ def create_source(self, name, type_): sources = self._h5group.open_group("sources", True) if name in sources: raise exceptions.DuplicateName("create_source") - src = Source._create_new(self, sources, name, type_) + src = Source.create_new(self.file, self, sources, name, type_) return src @property @@ -99,7 +99,7 @@ def sources(self): This is a read only attribute. """ if self._sources is None: - self._sources = SourceContainer("sources", self, Source) + self._sources = SourceContainer("sources", self.file, self, Source) return self._sources # metadata @@ -114,7 +114,7 @@ def metadata(self): :type: Section """ if "metadata" in self._h5group: - return Section(None, self._h5group.open_group("metadata")) + return Section(self.file, None, self._h5group.open_group("metadata")) else: return None diff --git a/nixio/tag.py b/nixio/tag.py index 7e5ba127..d38fae22 100644 --- a/nixio/tag.py +++ b/nixio/tag.py @@ -86,7 +86,7 @@ def units(self, units): dtype = DataType.String self._h5group.write_data("units", sanitized, dtype) - if self._parent._parent.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() def create_feature(self, data, link_type): @@ -105,7 +105,7 @@ def create_feature(self, data, link_type): link_type = link_type.lower() link_type = LinkType(link_type) features = self._h5group.open_group("features") - feat = Feature._create_new(self, features, data, link_type) + feat = Feature.create_new(self.file, self, features, data, link_type) return feat @staticmethod @@ -166,16 +166,16 @@ def _pos_to_idx(pos, unit, dim): class Tag(BaseTag): - def __init__(self, nixparent, h5group): - super(Tag, self).__init__(nixparent, h5group) + def __init__(self, nixfile, nixparent, h5group): + super(Tag, self).__init__(nixfile, nixparent, h5group) self._sources = None self._references = None self._features = None @classmethod - def _create_new(cls, nixparent, h5parent, name, type_, position): - newentity = super(Tag, cls)._create_new(nixparent, h5parent, - name, type_) + def create_new(cls, nixfile, nixparent, h5parent, name, type_, position): + newentity = super(Tag, cls).create_new(nixfile, nixparent, h5parent, + name, type_) newentity.position = position return newentity @@ -196,7 +196,7 @@ def position(self, pos): else: dtype = DataType.Double self._h5group.write_data("position", pos, dtype) - if self._parent._parent.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() @property @@ -217,7 +217,7 @@ def extent(self, ext): else: dtype = DataType.Double self._h5group.write_data("extent", ext, dtype) - if self._parent._parent.time_auto_update: + if self.file.auto_update_timestamps: self.force_updated_at() def _calc_data_slices(self, data): @@ -347,7 +347,8 @@ def features(self): :type: Container of Feature. """ if self._features is None: - self._features = FeatureContainer("features", self, Feature) + self._features = FeatureContainer("features", self.file, + self, Feature) return self._features @property @@ -375,9 +376,8 @@ def metadata(self): :type: Section """ if "metadata" in self._h5group: - return Section(None, self._h5group.open_group("metadata")) - else: - return None + return Section(self.file, None, self._h5group.open_group("metadata")) + return None @metadata.setter def metadata(self, sect): diff --git a/nixio/test/test_container.py b/nixio/test/test_container.py index d8ed5b79..21e5b393 100644 --- a/nixio/test/test_container.py +++ b/nixio/test/test_container.py @@ -73,13 +73,53 @@ def test_link_container_index_getter(self): self.assertEqual(self.multi_tag, self.group.multi_tags[0]) + def test_file_references(self): + # add some sources + self.source = self.block.create_source("test source", "containertest") + self.child_source = self.source.create_source("test source 2", + "containertest") + # using assertIs since the reference should be the same instance as + # the original + + # created objects + self.assertIs(self.block.file, self.file) + self.assertIs(self.group.file, self.file) + self.assertIs(self.dataarray.file, self.file) + self.assertIs(self.tag.file, self.file) + self.assertIs(self.multi_tag.file, self.file) + self.assertIs(self.positions.file, self.file) + self.assertIs(self.source.file, self.file) + self.assertIs(self.child_source.file, self.file) + + # instantiated through container getters + blk = self.file.blocks[0] + self.assertIs(blk.file, self.file) + self.assertIs(blk.groups[0].file, self.file) + self.assertIs(blk.data_arrays[0].file, self.file) + self.assertIs(blk.tags[0].file, self.file) + self.assertIs(blk.multi_tags[0].file, self.file) + self.assertIs(blk.sources[0].file, self.file) + self.assertIs(blk.sources[0].sources[0].file, self.file) + + # linked through group + self.assertIs(self.group.data_arrays[0].file, self.file) + self.assertIs(self.group.tags[0].file, self.file) + self.assertIs(self.group.multi_tags[0].file, self.file) + def test_parent_references(self): + # add some sources + self.source = self.block.create_source("test source", "containertest") + self.child_source = self.source.create_source("test source 2", + "containertest") + self.assertEqual(self.block._parent, self.file) self.assertEqual(self.group._parent, self.block) self.assertEqual(self.dataarray._parent, self.block) self.assertEqual(self.tag._parent, self.block) self.assertEqual(self.multi_tag._parent, self.block) self.assertEqual(self.positions._parent, self.block) + self.assertEqual(self.source._parent, self.block) + self.assertEqual(self.child_source._parent, self.source) def test_link_parent_references(self): self.assertEqual(self.group.data_arrays[0]._parent, self.block) diff --git a/nixio/test/test_data_array.py b/nixio/test/test_data_array.py index 0cd41913..49fe0b54 100644 --- a/nixio/test/test_data_array.py +++ b/nixio/test/test_data_array.py @@ -7,6 +7,7 @@ # modification, are permitted under the terms of the BSD License. See # LICENSE file in the root of the Project. import os +import time from six import string_types import sys import unittest @@ -476,3 +477,114 @@ def test_dim_one_based(self): for idx, dim in dim_container_one_based: assert self.array.dimensions[idx-1].dimension_type ==\ dim.dimension_type + + def test_timestamp_autoupdate(self): + array = self.block.create_data_array("array.time", "signal", + nix.DataType.Double, (100, )) + # Append dimensions and check time + datime = array.updated_at + time.sleep(1) + array.append_set_dimension() + self.assertNotEqual(datime, array.updated_at) + + datime = array.updated_at + time.sleep(1) + array.append_sampled_dimension(sampling_interval=0.1) + self.assertNotEqual(datime, array.updated_at) + + datime = array.updated_at + time.sleep(1) + array.append_range_dimension(ticks=[0.1]) + self.assertNotEqual(datime, array.updated_at) + + datime = array.updated_at + time.sleep(1) + df = self.block.create_data_frame( + "df", "test.data_array.timestamp.data_frame", + col_dict={"idx": int} + ) + array.append_data_frame_dimension(data_frame=df) + self.assertNotEqual(datime, array.updated_at) + + array.delete_dimensions() + datime = array.updated_at + time.sleep(1) + array.append_alias_range_dimension() + self.assertNotEqual(datime, array.updated_at) + + # other properties + datime = array.updated_at + time.sleep(1) + array.polynom_coefficients = [1.1, 2.2] + self.assertNotEqual(datime, array.updated_at) + + datime = array.updated_at + time.sleep(1) + array.expansion_origin = -1 + self.assertNotEqual(datime, array.updated_at) + + datime = array.updated_at + time.sleep(1) + array.label = "lbl" + self.assertNotEqual(datime, array.updated_at) + + datime = array.updated_at + time.sleep(1) + array.unit = "Ms" + self.assertNotEqual(datime, array.updated_at) + + def test_timestamp_noautoupdate(self): + self.file.auto_update_timestamps = False + array = self.block.create_data_array("array.time", "signal", + nix.DataType.Double, (100, )) + # Append dimensions and check time + datime = array.updated_at + time.sleep(1) + array.append_set_dimension() + self.assertEqual(datime, array.updated_at) + + datime = array.updated_at + time.sleep(1) + array.append_sampled_dimension(sampling_interval=0.1) + self.assertEqual(datime, array.updated_at) + + datime = array.updated_at + time.sleep(1) + array.append_range_dimension(ticks=[0.1]) + self.assertEqual(datime, array.updated_at) + + datime = array.updated_at + time.sleep(1) + df = self.block.create_data_frame( + "df", "test.data_array.timestamp.data_frame", + col_dict={"idx": int} + ) + array.append_data_frame_dimension(data_frame=df) + self.assertEqual(datime, array.updated_at) + + array.delete_dimensions() + datime = array.updated_at + time.sleep(1) + array.append_alias_range_dimension() + self.assertEqual(datime, array.updated_at) + + # other properties + datime = array.updated_at + time.sleep(1) + array.polynom_coefficients = [1.1, 2.2] + self.assertEqual(datime, array.updated_at) + + datime = array.updated_at + time.sleep(1) + array.expansion_origin = -1 + self.assertEqual(datime, array.updated_at) + + datime = array.updated_at + time.sleep(1) + array.label = "lbl" + self.assertEqual(datime, array.updated_at) + + datime = array.updated_at + time.sleep(1) + array.unit = "Ms" + self.assertEqual(datime, array.updated_at) diff --git a/nixio/test/test_data_frame.py b/nixio/test/test_data_frame.py index 2b2655f0..61445f7b 100644 --- a/nixio/test/test_data_frame.py +++ b/nixio/test/test_data_frame.py @@ -3,6 +3,7 @@ import nixio as nix from .tmp import TempDir import os +import time import numpy as np from six import string_types try: @@ -206,3 +207,21 @@ def test_creation_without_name(self): df = self.block.create_data_frame("without_name", "test", data=data) assert sorted(list(df.column_names)) == sorted(["name", "id", "val"]) assert sorted(list(df["name"])) == ["a", "b", "c"] + + def test_timestamp_autoupdate(self): + self.file.auto_update_timestamps = True + df = self.block.create_data_frame("df.time", "test.time", + col_dict={"idx": int}) + dftime = df.updated_at + time.sleep(1) + df.units = "ly" + self.assertNotEqual(dftime, df.updated_at) + + def test_timestamp_noautoupdate(self): + self.file.auto_update_timestamps = False + df = self.block.create_data_frame("df.time", "test.time", + col_dict={"idx": int}) + dftime = df.updated_at + time.sleep(1) + df.units = "ly" + self.assertEqual(dftime, df.updated_at) diff --git a/nixio/test/test_feature.py b/nixio/test/test_feature.py index 0b83eea2..e2d91f1e 100644 --- a/nixio/test/test_feature.py +++ b/nixio/test/test_feature.py @@ -7,6 +7,7 @@ # modification, are permitted under the terms of the BSD License. See # LICENSE file in the root of the Project. import os +import time import unittest import nixio as nix from .tmp import TempDir @@ -84,3 +85,36 @@ def test_feature_on_group(self): def test_create_diff_link_type_style(self): self.stimuli_tag.create_feature(self.movie1, nix.LinkType.Tagged) + + def test_timestamp_autoupdate(self): + array = self.block.create_data_array("array.time", "signal", + nix.DataType.Double, (100, )) + feature = self.stimuli_tag.create_feature(array, nix.LinkType.Tagged) + + ftime = feature.updated_at + time.sleep(1) + feature.data = self.block.create_data_array("alt.array", "signal", + nix.DataType.Int8, (1,)) + self.assertNotEqual(ftime, feature.updated_at) + + ftime = feature.updated_at + time.sleep(1) + feature.link_type = nix.LinkType.Untagged + self.assertNotEqual(ftime, feature.updated_at) + + def test_timestamp_noautoupdate(self): + self.file.auto_update_timestamps = False + array = self.block.create_data_array("array.time", "signal", + nix.DataType.Double, (100, )) + feature = self.stimuli_tag.create_feature(array, nix.LinkType.Tagged) + + ftime = feature.updated_at + time.sleep(1) + feature.data = self.block.create_data_array("alt.array", "signal", + nix.DataType.Int8, (1,)) + self.assertEqual(ftime, feature.updated_at) + + ftime = feature.updated_at + time.sleep(1) + feature.link_type = nix.LinkType.Untagged + self.assertEqual(ftime, feature.updated_at) diff --git a/nixio/test/test_file.py b/nixio/test/test_file.py index 2266b5de..dc37ffae 100644 --- a/nixio/test/test_file.py +++ b/nixio/test/test_file.py @@ -10,6 +10,7 @@ import unittest import h5py import numpy as np +import time import nixio as nix import nixio.file as filepy @@ -166,6 +167,60 @@ def test_copy_on_file(self): assert self.file.blocks[0] == self.file.blocks[1] # ID stays the same assert self.file.blocks[0].name != self.file.blocks[1].name + def test_timestamp_autoupdate(self): + # Using Block to test Entity.definition + blk = self.file.create_block("block", "timetest") + blktime = blk.updated_at + time.sleep(1) # wait for time to change + blk.definition = "updated" + # no update + self.assertNotEqual(blk.updated_at, blktime) + + rblk = self.file.blocks["block"] # read through container + time.sleep(1) # wait for time to change + rblk.definition = "updated again" + self.assertNotEqual(rblk.updated_at, blktime) + + # Using Block to test Entity.type + blktime = blk.updated_at + time.sleep(1) # wait for time to change + blk.type = "updated" + # no update + self.assertNotEqual(blk.updated_at, blktime) + + rblk = self.file.blocks["block"] # read through container + time.sleep(1) # wait for time to change + rblk.type = "updated again" + self.assertNotEqual(rblk.updated_at, blktime) + + def test_timestamp_noautoupdate(self): + # Using Block to test Entity.definition + blk = self.file.create_block("block", "timetest") + + # disable timestamp autoupdating + self.file.auto_update_timestamps = False + blktime = blk.updated_at + time.sleep(1) # wait for time to change + blk.definition = "update" + self.assertEqual(blk.updated_at, blktime) + + rblk = self.file.blocks["block"] # read through container + rblktime = rblk.updated_at + time.sleep(1) # wait for time to change + rblk.definition = "time should change" + self.assertEqual(rblk.updated_at, rblktime) + + blktime = blk.updated_at + time.sleep(1) # wait for time to change + blk.type = "update" + self.assertEqual(blk.updated_at, blktime) + + rblk = self.file.blocks["block"] # read through container + rblktime = rblk.updated_at + time.sleep(1) # wait for time to change + rblk.type = "time should change" + self.assertEqual(rblk.updated_at, rblktime) + class TestFileVer(unittest.TestCase): diff --git a/nixio/test/test_multi_tag.py b/nixio/test/test_multi_tag.py index c64956f8..628e8894 100644 --- a/nixio/test/test_multi_tag.py +++ b/nixio/test/test_multi_tag.py @@ -7,6 +7,7 @@ # modification, are permitted under the terms of the BSD License. See # LICENSE file in the root of the Project. import os +import time import unittest import numpy as np import nixio as nix @@ -562,3 +563,42 @@ def out_of_bounds(): self.feature_tag.feature_data(2, 1) self.assertRaises(IndexError, out_of_bounds) + + def test_timestamp_autoupdate(self): + pos = self.block.create_data_array("positions.time", "test.time", + nix.DataType.Int16, (0, 0)) + mtag = self.block.create_multi_tag("mtag.time", "test.time", pos) + + mtagtime = mtag.updated_at + time.sleep(1) # wait for time to change + mtag.positions = self.block.create_data_array("pos2.time", + "test.time", + nix.DataType.Int8, (0,)) + self.assertNotEqual(mtag.updated_at, mtagtime) + + mtagtime = mtag.updated_at + time.sleep(1) # wait for time to change + mtag.extents = self.block.create_data_array("extents.time", + "test.time", + nix.DataType.Int8, (0,)) + self.assertNotEqual(mtag.updated_at, mtagtime) + + def test_timestamp_noautoupdate(self): + self.file.auto_update_timestamps = False + pos = self.block.create_data_array("positions.time", "test.time", + nix.DataType.Int16, (0, 0)) + mtag = self.block.create_multi_tag("mtag.time", "test.time", pos) + + mtagtime = mtag.updated_at + time.sleep(1) # wait for time to change + mtag.positions = self.block.create_data_array("pos2.time", + "test.time", + nix.DataType.Int8, (0,)) + self.assertEqual(mtag.updated_at, mtagtime) + + mtagtime = mtag.updated_at + time.sleep(1) # wait for time to change + mtag.extents = self.block.create_data_array("extents.time", + "test.time", + nix.DataType.Int8, (0,)) + self.assertEqual(mtag.updated_at, mtagtime) diff --git a/nixio/test/test_section.py b/nixio/test/test_section.py index 2f950ac7..20571432 100644 --- a/nixio/test/test_section.py +++ b/nixio/test/test_section.py @@ -7,6 +7,7 @@ # modification, are permitted under the terms of the BSD License. See # LICENSE file in the root of the Project. import os +import time import unittest import nixio as nix from .tmp import TempDir @@ -231,6 +232,9 @@ def test_parent(self): grp.metadata = child self.assertEqual(grp.metadata.parent, self.section) + # indirect access parent check + self.assertEqual(block.groups["group"].metadata.parent, self.section) + def test_inverse_search(self): block = self.file.create_block("a block", "block with metadata") block.metadata = self.section @@ -314,3 +318,42 @@ def test_copy_on_sections(self): assert sec2 == tarsec.sections[1] assert sec1.sections[0] == tarsec.sections[1] tarfile.close() + + def test_timestamp_autoupdate(self): + section = self.file.create_section("section.time", "test.time") + + sectime = section.updated_at + time.sleep(1) # wait for time to change + section.reference = "whatever" + self.assertNotEqual(sectime, section.updated_at) + + sectime = section.updated_at + linksec = self.file.create_section("link.section.time", "test.time") + time.sleep(1) # wait for time to change + section.link = linksec + self.assertNotEqual(sectime, section.updated_at) + + sectime = section.updated_at + time.sleep(1) # wait for time to change + section.repository = "repo" + self.assertNotEqual(sectime, section.updated_at) + + def test_timestamp_noautoupdate(self): + self.file.auto_update_timestamps = False + section = self.file.create_section("section.time", "test.time") + + sectime = section.updated_at + time.sleep(1) # wait for time to change + section.reference = "whatever" + self.assertEqual(sectime, section.updated_at) + + sectime = section.updated_at + time.sleep(1) # wait for time to change + linksec = self.file.create_section("link.section.time", "test.time") + section.link = linksec + self.assertEqual(sectime, section.updated_at) + + sectime = section.updated_at + time.sleep(1) # wait for time to change + section.repository = "repo" + self.assertEqual(sectime, section.updated_at) diff --git a/nixio/test/test_tag.py b/nixio/test/test_tag.py index c48d9d94..b10f4eb4 100644 --- a/nixio/test/test_tag.py +++ b/nixio/test/test_tag.py @@ -7,6 +7,7 @@ # modification, are permitted under the terms of the BSD License. See # LICENSE file in the root of the Project. import os +import time import unittest import numpy as np import nixio as nix @@ -297,3 +298,40 @@ def test_tag_feature_data(self): assert (data1.size == 1) assert (data2.size == 3) + + def test_timestamp_autoupdate(self): + tag = self.block.create_tag("tag.time", "test.time", [-1]) + + tagtime = tag.updated_at + time.sleep(1) # wait for time to change + tag.position = [-100] + self.assertNotEqual(tag.updated_at, tagtime) + + tagtime = tag.updated_at + time.sleep(1) # wait for time to change + tag.extent = [30] + self.assertNotEqual(tag.updated_at, tagtime) + + tagtime = tag.updated_at + time.sleep(1) # wait for time to change + tag.units = "Mm" + self.assertNotEqual(tag.updated_at, tagtime) + + def test_timestamp_noautoupdate(self): + self.file.auto_update_timestamps = False + tag = self.block.create_tag("tag.time", "test.time", [-1]) + + tagtime = tag.updated_at + time.sleep(1) # wait for time to change + tag.position = [-100] + self.assertEqual(tag.updated_at, tagtime) + + tagtime = tag.updated_at + time.sleep(1) # wait for time to change + tag.extent = [30] + self.assertEqual(tag.updated_at, tagtime) + + tagtime = tag.updated_at + time.sleep(1) # wait for time to change + tag.units = "Mm" + self.assertEqual(tag.updated_at, tagtime) diff --git a/nixio/test/test_validator.py b/nixio/test/test_validator.py index ad8486ae..ac5f5893 100644 --- a/nixio/test/test_validator.py +++ b/nixio/test/test_validator.py @@ -245,18 +245,16 @@ def test_check_data_array_invalid_unit(self): def test_incorrect_dim_index(self): da = self.file.blocks[1].data_arrays["data-1d"] da.delete_dimensions() - dimgroup = da._h5group.open_group("dimensions") # This wont work if we ever change the internals - nix.SetDimension._create_new(dimgroup, "10") + nix.SetDimension.create_new(da, "10") res = self.file.validate() assert VE.IncorrectDimensionIndex.format(1, 10) in res["errors"][da] def test_invalid_dim_index(self): da = self.file.blocks[1].data_arrays["data-1d"] da.delete_dimensions() - dimgroup = da._h5group.open_group("dimensions") # This wont work if we ever change the internals - nix.SetDimension._create_new(dimgroup, "-1") + nix.SetDimension.create_new(da, "-1") res = self.file.validate() assert VE.InvalidDimensionIndex.format(1) in res["errors"][da]