From 2fd1d84695b012122ebfd6bf55044e5060c0d5e9 Mon Sep 17 00:00:00 2001 From: Eric Denovellis Date: Tue, 27 Jun 2023 19:15:14 -0700 Subject: [PATCH 01/11] Add html repr --- src/hdmf/container.py | 72 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/hdmf/container.py b/src/hdmf/container.py index 762ebeae1..008adcae2 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -452,6 +452,78 @@ def __repr__(self): template += " {}: {}\n".format(k, v) return template + def _repr_html_(self): + CSS_STYLE = """ + + """ + + JS_SCRIPT = """ + + """ + html_repr = CSS_STYLE + html_repr += JS_SCRIPT + html_repr += "
" + html_repr += ( + "
NWB File
" + ) + html_repr += self.generate_html_repr(self.fields) + html_repr += "
" + return html_repr + + def _generate_html_repr(self, fields, level=0, access_code=".fields"): + html_repr = "" + + if isinstance(fields, dict): + for key, value in fields.items(): + current_access_code = f"{access_code}['{key}']" + if ( + isinstance(value, dict) + or isinstance(value, list) + or hasattr(value, "fields") + ): + html_repr += f'
{key}' + if hasattr(value, "fields"): + value = value.fields + current_access_code = current_access_code + ".fields" + html_repr += self.generate_html_repr( + value, level + 1, current_access_code + ) + html_repr += "
" + else: + html_repr += f'
{key}: {value}
' + elif isinstance(fields, list): + for index, item in enumerate(fields): + current_access_code = f"{access_code}[{index}]" + html_repr += f'
{str(item)}
' + else: + pass + + return html_repr + @staticmethod def __smart_str(v, num_indent): """ From 09a1dbae1c046e91e4bbe6110caf61adf5d28cd0 Mon Sep 17 00:00:00 2001 From: bendichter Date: Tue, 27 Jun 2023 23:50:48 -0400 Subject: [PATCH 02/11] fix references to _generate_html_repr and adjust styles a bit --- src/hdmf/container.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hdmf/container.py b/src/hdmf/container.py index 008adcae2..191e5bece 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -456,7 +456,6 @@ def _repr_html_(self): CSS_STYLE = """ """ @@ -473,7 +473,7 @@ def _repr_html_(self): } document.addEventListener('DOMContentLoaded', function() { - let fieldKeys = document.querySelectorAll('.nwb-fields .field-key'); + let fieldKeys = document.querySelectorAll('.container-fields .field-key'); fieldKeys.forEach(function(fieldKey) { fieldKey.addEventListener('click', function() { let accessCode = fieldKey.getAttribute('title').replace('Access code: ', ''); @@ -483,17 +483,15 @@ def _repr_html_(self): }); """ - if self.__class__.__name__ == "NWBFile": - nwb_header_text = "NWBFile" - elif self.name == self.__class__.__name__: - nwb_header_text = self.name + if self.name == self.__class__.__name__: + header_text = self.name else: - nwb_header_text = f"{self.name} ({self.__class__.__name__})" + header_text = f"{self.name} ({self.__class__.__name__})" html_repr = CSS_STYLE html_repr += JS_SCRIPT - html_repr += "
" + html_repr += "
" html_repr += ( - f"
{nwb_header_text}
" + f"
{header_text}
" ) html_repr += self._generate_html_repr(self.fields) html_repr += "
" @@ -511,7 +509,7 @@ def _generate_html_repr(self, fields, level=0, access_code=".fields"): or hasattr(value, "fields") ): html_repr += ( - f'
{key}' ) if hasattr(value, "fields"): @@ -523,14 +521,14 @@ def _generate_html_repr(self, fields, level=0, access_code=".fields"): html_repr += "
" else: html_repr += ( - f'
{key}: {value}
' ) elif isinstance(fields, list): for index, item in enumerate(fields): current_access_code = f"{access_code}[{index}]" html_repr += ( - f'
{str(item)}
' ) else: From c6cf06ddde57650fdac939ea46758c161930c209 Mon Sep 17 00:00:00 2001 From: Eric Denovellis Date: Wed, 28 Jun 2023 12:21:54 -0700 Subject: [PATCH 07/11] Ensure summary display is a list item. --- src/hdmf/container.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/hdmf/container.py b/src/hdmf/container.py index 702f9aad6..ec4c16fc0 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -455,10 +455,19 @@ def __repr__(self): def _repr_html_(self): CSS_STYLE = """ """ From 9dd851871d5c0aae5037833db51f113470ad1b4e Mon Sep 17 00:00:00 2001 From: bendichter Date: Wed, 28 Jun 2023 20:29:04 -0400 Subject: [PATCH 08/11] adding list-item display styling --- src/hdmf/container.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hdmf/container.py b/src/hdmf/container.py index 702f9aad6..89f446c59 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -509,8 +509,8 @@ def _generate_html_repr(self, fields, level=0, access_code=".fields"): or hasattr(value, "fields") ): html_repr += ( - f'
{key}' + f'
{key}' ) if hasattr(value, "fields"): value = value.fields From edb1ccca58ab056f54611775fc09f985f744b18e Mon Sep 17 00:00:00 2001 From: bendichter Date: Wed, 28 Jun 2023 20:44:42 -0400 Subject: [PATCH 09/11] display numpy arrays --- src/hdmf/container.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/hdmf/container.py b/src/hdmf/container.py index be5dc4412..92eb4ff9c 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -500,7 +500,7 @@ def _repr_html_(self): html_repr += JS_SCRIPT html_repr += "
" html_repr += ( - f"
{header_text}
" + f"

{header_text}

" ) html_repr += self._generate_html_repr(self.fields) html_repr += "
" @@ -513,8 +513,7 @@ def _generate_html_repr(self, fields, level=0, access_code=".fields"): for key, value in fields.items(): current_access_code = f"{access_code}['{key}']" if ( - isinstance(value, dict) - or isinstance(value, list) + isinstance(value, (list, dict, np.ndarray)) or hasattr(value, "fields") ): html_repr += ( @@ -540,6 +539,11 @@ def _generate_html_repr(self, fields, level=0, access_code=".fields"): f'
{str(item)}
' ) + elif isinstance(fields, np.ndarray): + str_ = str(fields).replace("\n", "
") + html_repr += ( + f'
{str_}
' + ) else: pass From 877501636015bfec4ca7c4ff9873e19282726581 Mon Sep 17 00:00:00 2001 From: bendichter Date: Thu, 29 Jun 2023 12:46:32 -0400 Subject: [PATCH 10/11] add bare-bones test --- tests/unit/test_container.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/test_container.py b/tests/unit/test_container.py index d0426c85a..242281c15 100644 --- a/tests/unit/test_container.py +++ b/tests/unit/test_container.py @@ -262,6 +262,9 @@ def test_reset_parent_no_parent(self): obj.reset_parent() self.assertIsNone(obj.parent) + def test_repr_html_(self): + assert isinstance(Container('obj1')._repr_html_(), str) + class TestData(TestCase): From 00432879936ccdebad7f5a2cf0d72c48f370ac69 Mon Sep 17 00:00:00 2001 From: bendichter Date: Thu, 29 Jun 2023 15:47:50 -0400 Subject: [PATCH 11/11] add unit test which includes child container, dataset, and string --- tests/unit/test_container.py | 74 ++++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/tests/unit/test_container.py b/tests/unit/test_container.py index 242281c15..ab04f3801 100644 --- a/tests/unit/test_container.py +++ b/tests/unit/test_container.py @@ -11,6 +11,15 @@ class Subcontainer(Container): pass +class ContainerWithChild(Container): + __fields__ = ({'name': 'field1', 'child': True}, ) + + @docval({'name': 'field1', 'doc': 'field1 doc', 'type': None, 'default': None}) + def __init__(self, **kwargs): + super().__init__('test name') + self.field1 = kwargs['field1'] + + class TestExternalResourcesManager(TestCase): def test_link_and_get_resources(self): em = ExternalResourcesManager() @@ -262,8 +271,56 @@ def test_reset_parent_no_parent(self): obj.reset_parent() self.assertIsNone(obj.parent) + +class TestHTMLRepr(TestCase): + + class ContainerWithChildAndData(Container): + __fields__ = ( + {'name': 'child', 'child': True}, + "data", + "str" + ) + + @docval( + {'name': 'child', 'doc': 'field1 doc', 'type': Container}, + {'name': "data", "doc": 'data', 'type': list, "default": None}, + {'name': "str", "doc": 'str', 'type': str, "default": None}, + + ) + def __init__(self, **kwargs): + super().__init__('test name') + self.child = kwargs['child'] + self.data = kwargs['data'] + self.str = kwargs['str'] + def test_repr_html_(self): - assert isinstance(Container('obj1')._repr_html_(), str) + child_obj1 = Container('test child 1') + obj1 = self.ContainerWithChildAndData(child=child_obj1, data=[1, 2, 3], str="hello") + assert obj1._repr_html_() == ( + '\n \n \n \n' + '

test ' + 'name (ContainerWithChildAndData)

child
data
1
2
3
<' + 'div style="margin-left: 0px;" class="container-fields">st' + 'r: hello
' + ) class TestData(TestCase): @@ -510,14 +567,6 @@ def __init__(self, **kwargs): self.assertIsNone(obj4.field1) def test_child(self): - class ContainerWithChild(Container): - __fields__ = ({'name': 'field1', 'child': True}, ) - - @docval({'name': 'field1', 'doc': 'field1 doc', 'type': None, 'default': None}) - def __init__(self, **kwargs): - super().__init__('test name') - self.field1 = kwargs['field1'] - child_obj1 = Container('test child 1') obj1 = ContainerWithChild(child_obj1) self.assertIs(child_obj1.parent, obj1) @@ -535,13 +584,6 @@ def __init__(self, **kwargs): self.assertIsNone(obj2.field1) def test_setter_set_modified(self): - class ContainerWithChild(Container): - __fields__ = ({'name': 'field1', 'child': True}, ) - - @docval({'name': 'field1', 'doc': 'field1 doc', 'type': None, 'default': None}) - def __init__(self, **kwargs): - super().__init__('test name') - self.field1 = kwargs['field1'] child_obj1 = Container('test child 1') obj1 = ContainerWithChild()