Skip to content
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## PyNWB 3.1.3 (Unreleased)

### Added
- Added 'target_tables' kwarg to DynamicTable subclasses to allow classes that extend DynamicTable subclasses to specify the mapping of DynamicTableRegion columns to the target tables. @rly, @stephprince [#2096](https://github.com/NeurodataWithoutBorders/pynwb/issues/2096)

### Fixed
- Fixed incorrect warning for path not ending in `.nwb` when no path argument was provided. @t-b [#2130](https://github.com/NeurodataWithoutBorders/pynwb/pull/2130)
- Fixed issue with setting `neurodata_type_inc` when reading NWB files with cached schema versions less than 2.2.0. @rly [#2135](https://github.com/NeurodataWithoutBorders/pynwb/pull/2135)
Expand Down
3 changes: 2 additions & 1 deletion src/pynwb/ecephys.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ class ElectrodesTable(DynamicTable):
'for this electrode.'), 'required': False}
)

@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'))
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
allow_positional=AllowPositional.WARNING,)
def __init__(self, **kwargs):
kwargs['name'] = 'electrodes'
kwargs['description'] = 'metadata about extracellular electrodes'
Expand Down
2 changes: 1 addition & 1 deletion src/pynwb/epoch.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class TimeIntervals(DynamicTable):
@docval({'name': 'name', 'type': str, 'doc': 'name of this TimeIntervals'}, # required
{'name': 'description', 'type': str, 'doc': 'Description of this TimeIntervals',
'default': "experimental intervals"},
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
allow_positional=AllowPositional.WARNING,)
def __init__(self, **kwargs):
super().__init__(**kwargs)
Expand Down
17 changes: 9 additions & 8 deletions src/pynwb/icephys.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ class IntracellularElectrodesTable(DynamicTable):
'table': False},
)

@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
allow_positional=AllowPositional.WARNING,)
def __init__(self, **kwargs):
# Define defaultb name and description settings
Expand Down Expand Up @@ -452,7 +452,7 @@ class IntracellularStimuliTable(DynamicTable):
'class': TimeSeriesReferenceVectorData},
)

@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
allow_positional=AllowPositional.WARNING,)
def __init__(self, **kwargs):
# Define defaultb name and description settings
Expand All @@ -476,7 +476,7 @@ class IntracellularResponsesTable(DynamicTable):
'class': TimeSeriesReferenceVectorData},
)

@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
allow_positional=AllowPositional.WARNING,)
def __init__(self, **kwargs):
# Define defaultb name and description settings
Expand All @@ -493,7 +493,8 @@ class IntracellularRecordingsTable(AlignedDynamicTable):
a single simultaneous_recording. Each row in the table represents a single recording consisting
typically of a stimulus and a corresponding response.
"""
@docval(*get_docval(AlignedDynamicTable.__init__, 'id', 'columns', 'colnames', 'category_tables', 'categories'),
@docval(*get_docval(AlignedDynamicTable.__init__, 'id', 'columns', 'colnames',
'category_tables', 'categories', 'target_tables'),
allow_positional=AllowPositional.WARNING,)
def __init__(self, **kwargs):
kwargs['name'] = 'intracellular_recordings'
Expand Down Expand Up @@ -782,7 +783,7 @@ class SimultaneousRecordingsTable(DynamicTable):
'reading the Container from file as the table attribute is already populated in this case '
'but otherwise this is required.',
'default': None},
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
allow_positional=AllowPositional.WARNING,)
def __init__(self, **kwargs):
intracellular_recordings_table = popargs('intracellular_recordings_table', kwargs)
Expand Down Expand Up @@ -842,7 +843,7 @@ class SequentialRecordingsTable(DynamicTable):
'column indexes. May be None when reading the Container from file as the '
'table attribute is already populated in this case but otherwise this is required.',
'default': None},
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
allow_positional=AllowPositional.WARNING,)
def __init__(self, **kwargs):
simultaneous_recordings_table = popargs('simultaneous_recordings_table', kwargs)
Expand Down Expand Up @@ -900,7 +901,7 @@ class RepetitionsTable(DynamicTable):
'be None when reading the Container from file as the table attribute is already populated '
'in this case but otherwise this is required.',
'default': None},
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
allow_positional=AllowPositional.WARNING,)
def __init__(self, **kwargs):
sequential_recordings_table = popargs('sequential_recordings_table', kwargs)
Expand Down Expand Up @@ -953,7 +954,7 @@ class ExperimentalConditionsTable(DynamicTable):
'type': RepetitionsTable,
'doc': 'the RepetitionsTable table that the repetitions column indexes',
'default': None},
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
allow_positional=AllowPositional.WARNING,)
def __init__(self, **kwargs):
repetitions_table = popargs('repetitions_table', kwargs)
Expand Down
5 changes: 3 additions & 2 deletions src/pynwb/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ class Units(DynamicTable):
)

@docval({'name': 'name', 'type': str, 'doc': 'Name of this Units interface', 'default': 'Units'},
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
{'name': 'description', 'type': str, 'doc': 'a description of what is in this table', 'default': None},
{'name': 'electrode_table', 'type': DynamicTable,
'doc': 'the table that the *electrodes* column indexes', 'default': None},
Expand Down Expand Up @@ -265,7 +265,8 @@ class FrequencyBandsTable(DynamicTable):
{'name': 'band_stdev', 'description': 'The standard deviation Gaussian filters, in Hz.', 'required': False}
)

@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'))
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
allow_positional=AllowPositional.WARNING,)
def __init__(self, **kwargs):
kwargs['name'] = 'bands'
kwargs['description'] = 'Table for describing the bands that DecompositionSeries was generated from.'
Expand Down
2 changes: 1 addition & 1 deletion src/pynwb/ophys.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ class PlaneSegmentation(DynamicTable):
{'name': 'name', 'type': str, 'doc': 'name of PlaneSegmentation.', 'default': None},
{'name': 'reference_images', 'type': (ImageSeries, list, dict, tuple), 'default': None,
'doc': 'One or more image stacks that the masks apply to (can be oneelement stack).'},
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
allow_positional=AllowPositional.WARNING,)
def __init__(self, **kwargs):
imaging_plane, reference_images = popargs('imaging_plane', 'reference_images', kwargs)
Expand Down
35 changes: 35 additions & 0 deletions tests/unit/test_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,42 @@ def test_lab_meta_auto(self):
nwbfile = NWBFile("a file with header data", "NB123A", datetime(2017, 5, 1, 12, 0, 0, tzinfo=tzlocal()))

nwbfile.add_lab_meta_data(MyTestMetaData(name='test_name', test_attr=5.))

def test_custom_target_table(self):
ns_builder = NWBNamespaceBuilder('Extension for custom target table', self.prefix, version='0.1.0')
test_epochs_table_ext = NWBGroupSpec(
neurodata_type_def="MyEpochsTable",
neurodata_type_inc="TimeIntervals",
doc=("Custom table for storing my epochs. Inherits from TimeIntervals."),
datasets=[
NWBDatasetSpec(
name="my_locations",
doc="References row(s) of MyLocationsTable.",
neurodata_type_inc="DynamicTableRegion",
),
]
)
test_locations_table_ext = NWBGroupSpec(
neurodata_type_def="MyLocationsTable",
neurodata_type_inc="DynamicTable",
doc=("Table to reference."),
default_name="my_locations_table",
)

ns_builder.add_spec(self.ext_source, test_epochs_table_ext)
ns_builder.add_spec(self.ext_source, test_locations_table_ext)
ns_builder.export(self.ns_path, outdir=self.tempdir)
ns_abs_path = os.path.join(self.tempdir, self.ns_path)

load_namespaces(ns_abs_path)

MyLocationsTable = get_class('MyLocationsTable', self.prefix)
MyEpochsTable = get_class('MyEpochsTable', self.prefix)
my_locations_table = MyLocationsTable(name='test_name', description='test desc')
my_epochs_table = MyEpochsTable(name='test_name',
description='test desc',
target_tables={'my_locations': my_locations_table})
self.assertIs(my_epochs_table['my_locations'].table, my_locations_table)

class TestCatchDupNS(TestCase):

Expand Down
Loading