Skip to content
Draft
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
18 changes: 4 additions & 14 deletions src/hdmf/common/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def get(self, arg, **kwargs):
if isinstance(arg, slice):
indices = list(range(*arg.indices(len(self.data))))
else:
if isinstance(arg[0], bool):
if isinstance(arg[0], (bool, np.bool_)):
arg = np.where(arg)[0]
indices = arg
ret = list()
Expand Down Expand Up @@ -1108,17 +1108,9 @@ def __get_selection_as_dict(self, arg, df, index, exclude=None, **kwargs):
return ret
# if index is out of range, different errors can be generated depending on the dtype of the column
# but despite the differences, raise an IndexError from that error
except ValueError as ve:
except IndexError as ie:
# in h5py <2, if the column is an h5py.Dataset, a ValueError was raised
# in h5py 3+, this became an IndexError
x = re.match(r"^Index \((.*)\) out of range \(.*\)$", str(ve))
if x:
msg = ("Row index %s out of range for %s '%s' (length %d)."
% (x.groups()[0], self.__class__.__name__, self.name, len(self)))
raise IndexError(msg) from ve
else: # pragma: no cover
raise ve
except IndexError as ie:
x = re.match(r"^Index \((.*)\) out of range for \(.*\)$", str(ie))
if x:
msg = ("Row index %s out of range for %s '%s' (length %d)."
Expand Down Expand Up @@ -1288,10 +1280,8 @@ def generate_html_repr(self, level: int = 0, access_code: str = "", nrows: int =

inside = f"{self[:min(nrows, len(self))].to_html()}"

if len(self) == nrows + 1:
inside += "<p>... and 1 more row.</p>"
elif len(self) > nrows + 1:
inside += f"<p>... and {len(self) - nrows} more rows.</p>"
if len(self) >= nrows + 1:
inside += f"<p>... and {len(self) - nrows} more row(s).</p>"

out += (
f'<details><summary style="display: list-item; margin-left: {level * 20}px;" '
Expand Down
14 changes: 13 additions & 1 deletion tests/unit/common/test_common.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from hdmf import Data, Container
from hdmf.common import get_type_map, load_type_config, unload_type_config
from hdmf.common import get_type_map, load_type_config, unload_type_config, available_namespaces, _get_resources
from hdmf.testing import TestCase


Expand Down Expand Up @@ -28,3 +28,15 @@ def test_copy_ts_config(self):

self.assertEqual(tm.type_config.config, config)
self.assertEqual(tm.type_config.paths, [path])


class TestCommonInit(TestCase):
def test_available_namespaces(self):
ns = available_namespaces()
self.assertIn('hdmf-common', ns)
self.assertIn('hdmf-experimental', ns)

def test_get_resources_legacy(self):
"""Test legacy _get_resources function."""
result = _get_resources()
self.assertIsInstance(result, dict)
146 changes: 144 additions & 2 deletions tests/unit/common/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,81 @@ def test_from_dataframe_dup_attr(self):
self.assertEqual(table['description'].name, 'description')
self.assertEqual(table['parent'].name, 'parent')

def test_from_dataframe_with_index(self):
df = pd.DataFrame({
'foo': [1, 2, 3, 4, 5],
'bar': [10.0, 20.0, 30.0, 40.0, 50.0],
'baz': ['cat', 'dog', 'bird', 'fish', 'lizard'],
'index': [0, 1, 2, 3, 4]
})

obtained_table = DynamicTable.from_dataframe(df, 'test', index_column='index')
self.check_table(obtained_table)

def test_from_dataframe_missing_required_columns(self):
df = pd.DataFrame({
'col1': [1, 2, 3, 4, 5],
'col3': [1, 2, 3, 4, 5],})

msg = "DataFrame is missing required columns: {'col5', 'col7'}"
with self.assertRaises(ValueError, msg=msg):
SubTable.from_dataframe(df, 'test')

def test_build_columns_with_nested_index_error(self):
"""Test that building columns with nested index > 1 raises an error"""
df = pd.DataFrame({'col1': [1, 2, 3, 4, 5],})

msg = ('Creating nested index columns using this method is not yet supported. '
'Use add_column or define the columns using __columns__ instead.')
with self.assertRaisesWith(ValueError, msg):
DynamicTable.from_dataframe(df, 'test',
columns=([{'name': 'col1',
'description': 'optional column',
'index': 2},]))

def test_build_columns_with_index(self):
"""Test that building columns with index=True creates a VectorIndex column"""
ragged_list = [[1, 2], [3], [4, 5]]
df = pd.DataFrame({'col1': ragged_list,})

table = DynamicTable.from_dataframe(df, 'test', columns=([{'name': 'col1',
'description': 'optional column',
'index': True},]))
self.assertIsInstance(table['col1'], VectorData)
self.assertIsInstance(table['col1_index'], VectorIndex)
self.assertEqual(table['col1_index'][:], ragged_list)

def test_build_columns_with_dynamic_table_region(self):
"""Test that building columns with index=True creates a VectorIndex column"""
df = pd.DataFrame({'col1': list()},)

table = DynamicTable.from_dataframe(df, 'test',
columns=([{'name': 'col1',
'description': 'required region',
'required': True,
'table': True}]))
self.assertIsInstance(table['col1'], DynamicTableRegion)

def test_build_columns_with_enum(self):
"""Test that building columns with enum as true creates an Enum column"""
# TODO - diffiult to trigger empty enum data, add test if possible
df = pd.DataFrame({'col1': [1, 2, 3, 4, 5],})
table = DynamicTable.from_dataframe(df, 'test', columns=([{'name': 'col1',
'description': 'optional enum column',
'enum': True},]))
self.assertIsInstance(table['col1'], EnumData)

def test_from_dataframe_columns_specified_not_provided(self):
df = pd.DataFrame({
'col1': [1, 2, 3, 4, 5],
'col3': [1, 2, 3, 4, 5],})

msg = "cols specified but not provided: {'col2'}"
with self.assertRaises(ValueError, msg=msg):
DynamicTable.from_dataframe(df, 'test', columns=([{'name': 'col1', 'description': 'optional column'},
{'name': 'col2', 'description': 'optional column'},
{'name': 'col3', 'description': 'optional column'},]))

def test_missing_columns(self):
table = self.with_spec()
with self.assertRaises(ValueError):
Expand Down Expand Up @@ -946,6 +1021,8 @@ def test_repr(self):

def test_repr_html(self):
table = self.with_spec()
for _ in range(5):
table.add_row(foo='a', bar='b', baz='c')
html = table._repr_html_()

assert html == (
Expand All @@ -969,8 +1046,12 @@ def test_repr_html(self):
'margin-left: 0px;" class="container-fields field-key" title=""><b>table</b></summary><table border="1" '
'class="dataframe">\n <thead>\n <tr style="text-align: right;">\n <th></th>\n '
'<th>foo</th>\n <th>bar</th>\n <th>baz</th>\n </tr>\n <tr>\n <th>id</th>\n '
'<th></th>\n <th></th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n '
'</tbody>\n</table></details></div>'
'<th></th>\n <th></th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n '
'<th>0</th>\n <td>a</td>\n <td>b</td>\n <td>c</td>\n </tr>\n <tr>\n '
'<th>1</th>\n <td>a</td>\n <td>b</td>\n <td>c</td>\n </tr>\n <tr>\n '
'<th>2</th>\n <td>a</td>\n <td>b</td>\n <td>c</td>\n </tr>\n <tr>\n '
'<th>3</th>\n <td>a</td>\n <td>b</td>\n <td>c</td>\n </tr>\n '
'</tbody>\n</table><p>... and 1 more row(s).</p></details></div>'
)


Expand Down Expand Up @@ -1114,6 +1195,14 @@ def test_eq_bad_type(self):
table = self.with_columns_and_data()
self.assertFalse(table == container)

def test_copy(self):
table = self.with_columns_and_data()
table2 = table.copy()
self.assertTrue(table == table2)
self.assertIsNot(table, table2)
for colname in table.colnames:
self.assertTrue(getattr(table, colname) == getattr(table2, colname))


class TestDynamicTableRoundTrip(H5RoundTripMixin, TestCase):

Expand Down Expand Up @@ -1285,6 +1374,34 @@ def test_no_df_nested(self):
with self.assertRaisesWith(ValueError, msg):
dynamic_table_region.get(0, df=False, index=False)

def test_create_region_with_valid_slice_range(self):
table = self.with_columns_and_data()
region = table.create_region(name='region', region=slice(0, 2), description='test region')
self.assertEqual(region.data, [0, 1])

def test_create_region_with_invalid_slice_range(self):
table = self.with_columns_and_data()
msg = 'region slice slice(-1, 2, None) is out of range for this DynamicTable of length 5'
with self.assertRaisesWith(IndexError, msg):
table.create_region(name='region2', region=slice(-1, 2), description='test region')

def test_create_region_with_none_slice(self):
table = self.with_columns_and_data()
region = table.create_region(name='region2', region=slice(0, None), description='test region')
self.assertEqual(region.data, [0, 1, 2, 3, 4])

def test_create_region_with_negative_index(self):
table = self.with_columns_and_data()

msg = 'The index -1 is out of range for this DynamicTable of length 5'
with self.assertRaisesWith(IndexError, msg):
table.create_region(name='region', region=[-1, 0], description='test region')

def test_create_region_with_out_of_range_index(self):
table = self.with_columns_and_data()
msg = 'The index 10 is out of range for this DynamicTable of length 5'
with self.assertRaisesWith(IndexError, msg):
table.create_region(name='region', region=[0, 10], description='test region')

class DynamicTableRegionRoundTrip(H5RoundTripMixin, TestCase):

Expand Down Expand Up @@ -2463,6 +2580,23 @@ def test_init_data(self):
self.assertListEqual(foo_ind[0], ['a', 'b'])
self.assertListEqual(foo_ind[1], ['c'])

def test_get_with_boolean(self):
"""Test VectorIndex.get with boolean argument"""
data = VectorData(name='data', description='desc', data=['a', 'b', 'c', 'd', 'e'])
index = VectorIndex(name='index', data=[2, 3, 5], target=data)
result = index.get([True, False, True])

self.assertEqual(result, [['a', 'b',], ['d', 'e']])
self.assertEqual(len(result), 2)

def test_get_with_boolean_array(self):
"""Test VectorIndex.get with boolean np.array argument"""
data = VectorData(name='data', description='desc', data=['a', 'b', 'c', 'd', 'e'])
index = VectorIndex(name='index', data=[2, 3, 5], target=data)
result = index.get(np.array([True, False, True]))

self.assertEqual(result, [['a', 'b',], ['d', 'e']])
self.assertEqual(len(result), 2)

class TestDoubleIndex(TestCase):

Expand Down Expand Up @@ -2610,6 +2744,14 @@ def test_enum_index(self):
index=pd.Series(name='id', data=[0, 1, 2]))
pd.testing.assert_frame_equal(exp, rec)

def test_add_column_table_and_enum_error(self):
"""Test that adding a column with both table and enum raises an error."""
table = DynamicTable(name='table0', description='an example table')

msg = "column 'col1' cannot be both a table region and come from an enumerable set of elements"
with self.assertRaisesWith(ValueError, msg):
table.add_column(name='col1', description='test', table=True, enum=True)


class TestDynamicTableInitIndexRoundTrip(H5RoundTripMixin, TestCase):

Expand Down
Loading