Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

### Fixed
- Fixed `Units.waveform_unit` having no effect on the written file. The `waveform_unit` passed to `Units` is now written to the `waveform_mean`, `waveform_sd`, and `waveforms` columns, and `"volts"` remains the default. PyNWB now also warns when the waveform columns of a file being read carry different `unit` or `sampling_rate` attributes, since only one value per attribute is kept on the `Units` container. @rly [#2162](https://github.com/NeurodataWithoutBorders/pynwb/issues/2162)
- Fixed `NWBFile.objects` being cached on first access and never invalidated, so containers added to (or removed from) the file after the property was first read were missing from (or still in) it. It is now rebuilt on every access and always reflects the current contents of the file. @HugoFara [#2242](https://github.com/NeurodataWithoutBorders/pynwb/issues/2242)
- Fixed `mock_DeviceModel` defaulting `manufacturer` to `None`. The mock now defaults it to `"manufacturer"`. @HugoFara [#2232](https://github.com/NeurodataWithoutBorders/pynwb/pull/2232)
- Fixed reading a file whose dates carry a sub-minute UTC offset (e.g. `1900-10-01T00:00:00-05:50:36`). @h-mayorquin [#2230](https://github.com/NeurodataWithoutBorders/pynwb/pull/2230)
- Fixed wide pandas DataFrames in the tutorials spilling out of the content column and into the right margin. @bendichter [#2236](https://github.com/NeurodataWithoutBorders/pynwb/pull/2236)
Expand Down
20 changes: 11 additions & 9 deletions src/pynwb/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,18 +566,14 @@ def __init__(self, **kwargs):
for key, val in args_to_set.items():
setattr(self, key, val)

self.__obj = None

def all_children(self):
"""Get a list of this file and all of its children, recursively."""
stack = [self]
ret = list()
self.__obj = LabelledDict(label='all_objects', key_attr='object_id')
while len(stack):
n = stack.pop()
ret.append(n)
if n.object_id is not None:
self.__obj[n.object_id] = n
else:
if n.object_id is None:
warn('%s "%s" does not have an object_id' % (n.neurodata_type, n.name))
if hasattr(n, 'children'):
for c in n.children:
Expand All @@ -586,9 +582,15 @@ def all_children(self):

@property
def objects(self):
if self.__obj is None:
self.all_children()
return self.__obj
"""A dict of this file and all of its children, recursively, keyed by object ID.

The dict is rebuilt on every access, so it always reflects the current contents of the file.
"""
ret = LabelledDict(label='all_objects', key_attr='object_id')
for n in self.all_children():
if n.object_id is not None:
ret[n.object_id] = n
return ret

@property
def epoch_tags(self):
Expand Down
24 changes: 24 additions & 0 deletions tests/unit/test_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,30 @@ def test_all_children(self):
self.assertIn(device, children)
self.assertIn(elecgrp, children)

def test_objects_includes_container_added_after_first_access(self):
"""Reading objects must not freeze its contents: containers added later must show up."""
self.assertNotIn('test_ts', [obj.name for obj in self.nwbfile.objects.values()])
ts = TimeSeries(name='test_ts', data=[0, 1, 2], unit='grams', timestamps=[0.0, 0.1, 0.2])
self.nwbfile.add_acquisition(ts)
self.assertIs(self.nwbfile.objects[ts.object_id], ts)

def test_objects_includes_nested_container_added_after_first_access(self):
"""Containers added to a child of the file must show up in objects as well."""
module = self.nwbfile.create_processing_module(name='behavior', description='a test module')
self.nwbfile.objects
ts = TimeSeries(name='test_ts', data=[0, 1, 2], unit='grams', timestamps=[0.0, 0.1, 0.2])
module.add(ts)
self.assertIs(self.nwbfile.objects[ts.object_id], ts)

def test_objects_excludes_removed_container(self):
"""Containers removed from the file must not linger in objects."""
ts = TimeSeries(name='test_ts', data=[0, 1, 2], unit='grams', timestamps=[0.0, 0.1, 0.2])
self.nwbfile.add_acquisition(ts)
self.assertIn(ts.object_id, self.nwbfile.objects)
del self.nwbfile.acquisition['test_ts']
ts.reset_parent()
self.assertNotIn(ts.object_id, self.nwbfile.objects)

def test_fail_if_source_script_file_name_without_source_script(self):
with self.assertRaises(ValueError):
# <-- source_script_file_name without source_script is not allowed
Expand Down
Loading