Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: HTML rendering of objects with links #1057

Closed
bendichter opened this issue Feb 21, 2024 · 4 comments · May be fixed by #1058
Closed

[Bug]: HTML rendering of objects with links #1057

bendichter opened this issue Feb 21, 2024 · 4 comments · May be fixed by #1058

Comments

@bendichter
Copy link
Contributor

bendichter commented Feb 21, 2024

What happened?

When trying to render an HDMF (or PyNWB) object with a reference to another object, it can cause an infinite recursion error.

In the following code:

from pynwb.testing.mock.base import mock_TimeSeries

ts1 = mock_TimeSeries()
ts2 = mock_TimeSeries()
ts3 = mock_TimeSeries(timestamps=ts2, rate=None)

ts1 renders as expected, but ts2._repr_html_() and ts3._repr_html_() both cause errors:

ts2
---------------------------------------------------------------------------
RecursionError                            Traceback (most recent call last)
File ~/opt/miniconda3/lib/python3.9/site-packages/IPython/core/formatters.py:344, in BaseFormatter.__call__(self, obj)
    342     method = get_real_method(obj, self.print_method)
    343     if method is not None:
--> 344         return method()
    345     return None
    346 else:

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:618, in Container._repr_html_(self)
    616 html_repr += "<div class='container-wrap'>"
    617 html_repr += f"<div class='container-header'><div class='xr-obj-type'><h3>{header_text}</h3></div></div>"
--> 618 html_repr += self._generate_html_repr(self.fields, is_field=True)
    619 html_repr += "</div>"
    620 return html_repr

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:632, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    630             html_repr += value._generate_field_html(key, value, level, current_access_code)
    631         else:
--> 632             html_repr += self._generate_field_html(key, value, level, current_access_code)
    633 elif isinstance(fields, list):
    634     for index, item in enumerate(fields):

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:663, in Container._generate_field_html(self, key, value, level, access_code)
    661     html_content = self._generate_html_repr(value.fields, level + 1, access_code, is_field=True)
    662 elif isinstance(value, (list, dict, np.ndarray)):
--> 663     html_content = self._generate_html_repr(value, level + 1, access_code, is_field=False)
    664 else:
    665     html_content = f'<span class="field-key">{value}</span>'

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:636, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    634     for index, item in enumerate(fields):
    635         access_code += f'[{index}]'
--> 636         html_repr += self._generate_field_html(index, item, level, access_code)
    637 elif isinstance(fields, np.ndarray):
    638     html_repr += self._generate_array_html(fields, level)

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:661, in Container._generate_field_html(self, key, value, level, access_code)
    658     html_content = value.__repr_html__()
    660 elif hasattr(value, "fields"):
--> 661     html_content = self._generate_html_repr(value.fields, level + 1, access_code, is_field=True)
    662 elif isinstance(value, (list, dict, np.ndarray)):
    663     html_content = self._generate_html_repr(value, level + 1, access_code, is_field=False)

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:630, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    628 current_access_code = f"{access_code}.{key}" if is_field else f"{access_code}['{key}']"
    629 if hasattr(value, '_generate_field_html'):
--> 630     html_repr += value._generate_field_html(key, value, level, current_access_code)
    631 else:
    632     html_repr += self._generate_field_html(key, value, level, current_access_code)

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:661, in Container._generate_field_html(self, key, value, level, access_code)
    658     html_content = value.__repr_html__()
    660 elif hasattr(value, "fields"):
--> 661     html_content = self._generate_html_repr(value.fields, level + 1, access_code, is_field=True)
    662 elif isinstance(value, (list, dict, np.ndarray)):
    663     html_content = self._generate_html_repr(value, level + 1, access_code, is_field=False)

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:632, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    630             html_repr += value._generate_field_html(key, value, level, current_access_code)
    631         else:
--> 632             html_repr += self._generate_field_html(key, value, level, current_access_code)
    633 elif isinstance(fields, list):
    634     for index, item in enumerate(fields):

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:663, in Container._generate_field_html(self, key, value, level, access_code)
    661     html_content = self._generate_html_repr(value.fields, level + 1, access_code, is_field=True)
    662 elif isinstance(value, (list, dict, np.ndarray)):
--> 663     html_content = self._generate_html_repr(value, level + 1, access_code, is_field=False)
    664 else:
    665     html_content = f'<span class="field-key">{value}</span>'

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:636, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    634     for index, item in enumerate(fields):
    635         access_code += f'[{index}]'
--> 636         html_repr += self._generate_field_html(index, item, level, access_code)
    637 elif isinstance(fields, np.ndarray):
    638     html_repr += self._generate_array_html(fields, level)

    [... skipping similar frames: Container._generate_field_html at line 661 (1 times)]

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:630, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    628 current_access_code = f"{access_code}.{key}" if is_field else f"{access_code}['{key}']"
    629 if hasattr(value, '_generate_field_html'):
--> 630     html_repr += value._generate_field_html(key, value, level, current_access_code)
    631 else:
    632     html_repr += self._generate_field_html(key, value, level, current_access_code)

    [... skipping similar frames: Container._generate_field_html at line 661 (978 times), Container._generate_field_html at line 663 (489 times), Container._generate_html_repr at line 632 (489 times), Container._generate_html_repr at line 636 (489 times), Container._generate_html_repr at line 630 (488 times)]

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:630, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    628 current_access_code = f"{access_code}.{key}" if is_field else f"{access_code}['{key}']"
    629 if hasattr(value, '_generate_field_html'):
--> 630     html_repr += value._generate_field_html(key, value, level, current_access_code)
    631 else:
    632     html_repr += self._generate_field_html(key, value, level, current_access_code)

    [... skipping similar frames: Container._generate_field_html at line 661 (1 times), Container._generate_field_html at line 663 (1 times), Container._generate_html_repr at line 632 (1 times)]

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:636, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    634     for index, item in enumerate(fields):
    635         access_code += f'[{index}]'
--> 636         html_repr += self._generate_field_html(index, item, level, access_code)
    637 elif isinstance(fields, np.ndarray):
    638     html_repr += self._generate_array_html(fields, level)

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:661, in Container._generate_field_html(self, key, value, level, access_code)
    658     html_content = value.__repr_html__()
    660 elif hasattr(value, "fields"):
--> 661     html_content = self._generate_html_repr(value.fields, level + 1, access_code, is_field=True)
    662 elif isinstance(value, (list, dict, np.ndarray)):
    663     html_content = self._generate_html_repr(value, level + 1, access_code, is_field=False)

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:632, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    630             html_repr += value._generate_field_html(key, value, level, current_access_code)
    631         else:
--> 632             html_repr += self._generate_field_html(key, value, level, current_access_code)
    633 elif isinstance(fields, list):
    634     for index, item in enumerate(fields):

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:663, in Container._generate_field_html(self, key, value, level, access_code)
    661     html_content = self._generate_html_repr(value.fields, level + 1, access_code, is_field=True)
    662 elif isinstance(value, (list, dict, np.ndarray)):
--> 663     html_content = self._generate_html_repr(value, level + 1, access_code, is_field=False)
    664 else:
    665     html_content = f'<span class="field-key">{value}</span>'

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:638, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    636         html_repr += self._generate_field_html(index, item, level, access_code)
    637 elif isinstance(fields, np.ndarray):
--> 638     html_repr += self._generate_array_html(fields, level)
    639 else:
    640     pass

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:677, in Container._generate_array_html(self, array, level)
    675 def _generate_array_html(self, array, level):
    676     """Generates HTML for a NumPy array."""
--> 677     str_ = str(array).replace("\n", "</br>")
    678     return f'<div style="margin-left: {level * 20}px;" class="container-fields">{str_}</div>'

File ~/opt/miniconda3/lib/python3.9/site-packages/numpy/core/arrayprint.py:1612, in _array_str_implementation(a, max_line_width, precision, suppress_small, array2string)
   1606 if a.shape == ():
   1607     # obtain a scalar and call str on it, avoiding problems for subclasses
   1608     # for which indexing with () returns a 0d instead of a scalar by using
   1609     # ndarray's getindex. Also guard against recursive 0d object arrays.
   1610     return _guarded_repr_or_str(np.ndarray.__getitem__(a, ()))
-> 1612 return array2string(a, max_line_width, precision, suppress_small, ' ', "")

File ~/opt/miniconda3/lib/python3.9/site-packages/numpy/core/arrayprint.py:736, in array2string(a, max_line_width, precision, suppress_small, separator, prefix, style, formatter, threshold, edgeitems, sign, floatmode, suffix, legacy)
    733 if a.size == 0:
    734     return "[]"
--> 736 return _array2string(a, options, separator, prefix)

File ~/opt/miniconda3/lib/python3.9/site-packages/numpy/core/arrayprint.py:513, in _recursive_guard.<locals>.decorating_function.<locals>.wrapper(self, *args, **kwargs)
    511 repr_running.add(key)
    512 try:
--> 513     return f(self, *args, **kwargs)
    514 finally:
    515     repr_running.discard(key)

File ~/opt/miniconda3/lib/python3.9/site-packages/numpy/core/arrayprint.py:539, in _array2string(a, options, separator, prefix)
    536     summary_insert = ""
    538 # find the right formatting function for the array
--> 539 format_function = _get_format_function(data, **options)
    541 # skip over "["
    542 next_line_prefix = " "

File ~/opt/miniconda3/lib/python3.9/site-packages/numpy/core/arrayprint.py:467, in _get_format_function(data, **options)
    465         return formatdict['timedelta']()
    466     else:
--> 467         return formatdict['int']()
    468 elif issubclass(dtypeobj, _nt.floating):
    469     if issubclass(dtypeobj, _nt.longfloat):

File ~/opt/miniconda3/lib/python3.9/site-packages/numpy/core/arrayprint.py:410, in _get_formatdict.<locals>.<lambda>()
    403 def _get_formatdict(data, *, precision, floatmode, suppress, sign, legacy,
    404                     formatter, **kwargs):
    405     # note: extra arguments in kwargs are ignored
    406 
    407     # wrapped in lambdas to avoid taking a code path with the wrong type of data
    408     formatdict = {
    409         'bool': lambda: BoolFormat(data),
--> 410         'int': lambda: IntegerFormat(data),
    411         'float': lambda: FloatingFormat(
    412             data, precision, floatmode, suppress, sign, legacy=legacy),
    413         'longfloat': lambda: FloatingFormat(
    414             data, precision, floatmode, suppress, sign, legacy=legacy),
    415         'complexfloat': lambda: ComplexFloatingFormat(
    416             data, precision, floatmode, suppress, sign, legacy=legacy),
    417         'longcomplexfloat': lambda: ComplexFloatingFormat(
    418             data, precision, floatmode, suppress, sign, legacy=legacy),
    419         'datetime': lambda: DatetimeFormat(data, legacy=legacy),
    420         'timedelta': lambda: TimedeltaFormat(data),
    421         'object': lambda: _object_format,
    422         'void': lambda: str_format,
    423         'numpystr': lambda: repr_format}
    425     # we need to wrap values in `formatter` in a lambda, so that the interface
    426     # is the same as the above values.
    427     def indirect(x):

RecursionError: maximum recursion depth exceeded

TimeSeries6 pynwb.base.TimeSeries at 0x5895946832
Fields:
  comments: no comments
  conversion: 1.0
  data: [1 2 3 4]
  description: no description
  offset: 0.0
  rate: 10.0
  resolution: -1.0
  starting_time: 0.0
  starting_time_unit: seconds
  timestamp_link: (
    TimeSeries7 <class 'pynwb.base.TimeSeries'>
  )
  unit: volts
ts3
---------------------------------------------------------------------------
RecursionError                            Traceback (most recent call last)
File ~/opt/miniconda3/lib/python3.9/site-packages/IPython/core/formatters.py:344, in BaseFormatter.__call__(self, obj)
    342     method = get_real_method(obj, self.print_method)
    343     if method is not None:
--> 344         return method()
    345     return None
    346 else:

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:618, in Container._repr_html_(self)
    616 html_repr += "<div class='container-wrap'>"
    617 html_repr += f"<div class='container-header'><div class='xr-obj-type'><h3>{header_text}</h3></div></div>"
--> 618 html_repr += self._generate_html_repr(self.fields, is_field=True)
    619 html_repr += "</div>"
    620 return html_repr

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:630, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    628 current_access_code = f"{access_code}.{key}" if is_field else f"{access_code}['{key}']"
    629 if hasattr(value, '_generate_field_html'):
--> 630     html_repr += value._generate_field_html(key, value, level, current_access_code)
    631 else:
    632     html_repr += self._generate_field_html(key, value, level, current_access_code)

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:661, in Container._generate_field_html(self, key, value, level, access_code)
    658     html_content = value.__repr_html__()
    660 elif hasattr(value, "fields"):
--> 661     html_content = self._generate_html_repr(value.fields, level + 1, access_code, is_field=True)
    662 elif isinstance(value, (list, dict, np.ndarray)):
    663     html_content = self._generate_html_repr(value, level + 1, access_code, is_field=False)

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:632, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    630             html_repr += value._generate_field_html(key, value, level, current_access_code)
    631         else:
--> 632             html_repr += self._generate_field_html(key, value, level, current_access_code)
    633 elif isinstance(fields, list):
    634     for index, item in enumerate(fields):

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:663, in Container._generate_field_html(self, key, value, level, access_code)
    661     html_content = self._generate_html_repr(value.fields, level + 1, access_code, is_field=True)
    662 elif isinstance(value, (list, dict, np.ndarray)):
--> 663     html_content = self._generate_html_repr(value, level + 1, access_code, is_field=False)
    664 else:
    665     html_content = f'<span class="field-key">{value}</span>'

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:636, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    634     for index, item in enumerate(fields):
    635         access_code += f'[{index}]'
--> 636         html_repr += self._generate_field_html(index, item, level, access_code)
    637 elif isinstance(fields, np.ndarray):
    638     html_repr += self._generate_array_html(fields, level)

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:661, in Container._generate_field_html(self, key, value, level, access_code)
    658     html_content = value.__repr_html__()
    660 elif hasattr(value, "fields"):
--> 661     html_content = self._generate_html_repr(value.fields, level + 1, access_code, is_field=True)
    662 elif isinstance(value, (list, dict, np.ndarray)):
    663     html_content = self._generate_html_repr(value, level + 1, access_code, is_field=False)

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:630, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    628 current_access_code = f"{access_code}.{key}" if is_field else f"{access_code}['{key}']"
    629 if hasattr(value, '_generate_field_html'):
--> 630     html_repr += value._generate_field_html(key, value, level, current_access_code)
    631 else:
    632     html_repr += self._generate_field_html(key, value, level, current_access_code)

    [... skipping similar frames: Container._generate_field_html at line 661 (1 times)]

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:632, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    630             html_repr += value._generate_field_html(key, value, level, current_access_code)
    631         else:
--> 632             html_repr += self._generate_field_html(key, value, level, current_access_code)
    633 elif isinstance(fields, list):
    634     for index, item in enumerate(fields):

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:663, in Container._generate_field_html(self, key, value, level, access_code)
    661     html_content = self._generate_html_repr(value.fields, level + 1, access_code, is_field=True)
    662 elif isinstance(value, (list, dict, np.ndarray)):
--> 663     html_content = self._generate_html_repr(value, level + 1, access_code, is_field=False)
    664 else:
    665     html_content = f'<span class="field-key">{value}</span>'

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:636, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    634     for index, item in enumerate(fields):
    635         access_code += f'[{index}]'
--> 636         html_repr += self._generate_field_html(index, item, level, access_code)
    637 elif isinstance(fields, np.ndarray):
    638     html_repr += self._generate_array_html(fields, level)

    [... skipping similar frames: Container._generate_field_html at line 661 (978 times), Container._generate_field_html at line 663 (489 times), Container._generate_html_repr at line 630 (489 times), Container._generate_html_repr at line 632 (489 times), Container._generate_html_repr at line 636 (488 times)]

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:636, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    634     for index, item in enumerate(fields):
    635         access_code += f'[{index}]'
--> 636         html_repr += self._generate_field_html(index, item, level, access_code)
    637 elif isinstance(fields, np.ndarray):
    638     html_repr += self._generate_array_html(fields, level)

    [... skipping similar frames: Container._generate_field_html at line 661 (1 times)]

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:630, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    628 current_access_code = f"{access_code}.{key}" if is_field else f"{access_code}['{key}']"
    629 if hasattr(value, '_generate_field_html'):
--> 630     html_repr += value._generate_field_html(key, value, level, current_access_code)
    631 else:
    632     html_repr += self._generate_field_html(key, value, level, current_access_code)

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:661, in Container._generate_field_html(self, key, value, level, access_code)
    658     html_content = value.__repr_html__()
    660 elif hasattr(value, "fields"):
--> 661     html_content = self._generate_html_repr(value.fields, level + 1, access_code, is_field=True)
    662 elif isinstance(value, (list, dict, np.ndarray)):
    663     html_content = self._generate_html_repr(value, level + 1, access_code, is_field=False)

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:632, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    630             html_repr += value._generate_field_html(key, value, level, current_access_code)
    631         else:
--> 632             html_repr += self._generate_field_html(key, value, level, current_access_code)
    633 elif isinstance(fields, list):
    634     for index, item in enumerate(fields):

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:663, in Container._generate_field_html(self, key, value, level, access_code)
    661     html_content = self._generate_html_repr(value.fields, level + 1, access_code, is_field=True)
    662 elif isinstance(value, (list, dict, np.ndarray)):
--> 663     html_content = self._generate_html_repr(value, level + 1, access_code, is_field=False)
    664 else:
    665     html_content = f'<span class="field-key">{value}</span>'

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:638, in Container._generate_html_repr(self, fields, level, access_code, is_field)
    636         html_repr += self._generate_field_html(index, item, level, access_code)
    637 elif isinstance(fields, np.ndarray):
--> 638     html_repr += self._generate_array_html(fields, level)
    639 else:
    640     pass

File ~/opt/miniconda3/lib/python3.9/site-packages/hdmf/container.py:677, in Container._generate_array_html(self, array, level)
    675 def _generate_array_html(self, array, level):
    676     """Generates HTML for a NumPy array."""
--> 677     str_ = str(array).replace("\n", "</br>")
    678     return f'<div style="margin-left: {level * 20}px;" class="container-fields">{str_}</div>'

File ~/opt/miniconda3/lib/python3.9/site-packages/numpy/core/arrayprint.py:1612, in _array_str_implementation(a, max_line_width, precision, suppress_small, array2string)
   1606 if a.shape == ():
   1607     # obtain a scalar and call str on it, avoiding problems for subclasses
   1608     # for which indexing with () returns a 0d instead of a scalar by using
   1609     # ndarray's getindex. Also guard against recursive 0d object arrays.
   1610     return _guarded_repr_or_str(np.ndarray.__getitem__(a, ()))
-> 1612 return array2string(a, max_line_width, precision, suppress_small, ' ', "")

File ~/opt/miniconda3/lib/python3.9/site-packages/numpy/core/arrayprint.py:736, in array2string(a, max_line_width, precision, suppress_small, separator, prefix, style, formatter, threshold, edgeitems, sign, floatmode, suffix, legacy)
    733 if a.size == 0:
    734     return "[]"
--> 736 return _array2string(a, options, separator, prefix)

File ~/opt/miniconda3/lib/python3.9/site-packages/numpy/core/arrayprint.py:513, in _recursive_guard.<locals>.decorating_function.<locals>.wrapper(self, *args, **kwargs)
    511 repr_running.add(key)
    512 try:
--> 513     return f(self, *args, **kwargs)
    514 finally:
    515     repr_running.discard(key)

File ~/opt/miniconda3/lib/python3.9/site-packages/numpy/core/arrayprint.py:539, in _array2string(a, options, separator, prefix)
    536     summary_insert = ""
    538 # find the right formatting function for the array
--> 539 format_function = _get_format_function(data, **options)
    541 # skip over "["
    542 next_line_prefix = " "

File ~/opt/miniconda3/lib/python3.9/site-packages/numpy/core/arrayprint.py:467, in _get_format_function(data, **options)
    465         return formatdict['timedelta']()
    466     else:
--> 467         return formatdict['int']()
    468 elif issubclass(dtypeobj, _nt.floating):
    469     if issubclass(dtypeobj, _nt.longfloat):

File ~/opt/miniconda3/lib/python3.9/site-packages/numpy/core/arrayprint.py:410, in _get_formatdict.<locals>.<lambda>()
    403 def _get_formatdict(data, *, precision, floatmode, suppress, sign, legacy,
    404                     formatter, **kwargs):
    405     # note: extra arguments in kwargs are ignored
    406 
    407     # wrapped in lambdas to avoid taking a code path with the wrong type of data
    408     formatdict = {
    409         'bool': lambda: BoolFormat(data),
--> 410         'int': lambda: IntegerFormat(data),
    411         'float': lambda: FloatingFormat(
    412             data, precision, floatmode, suppress, sign, legacy=legacy),
    413         'longfloat': lambda: FloatingFormat(
    414             data, precision, floatmode, suppress, sign, legacy=legacy),
    415         'complexfloat': lambda: ComplexFloatingFormat(
    416             data, precision, floatmode, suppress, sign, legacy=legacy),
    417         'longcomplexfloat': lambda: ComplexFloatingFormat(
    418             data, precision, floatmode, suppress, sign, legacy=legacy),
    419         'datetime': lambda: DatetimeFormat(data, legacy=legacy),
    420         'timedelta': lambda: TimedeltaFormat(data),
    421         'object': lambda: _object_format,
    422         'void': lambda: str_format,
    423         'numpystr': lambda: repr_format}
    425     # we need to wrap values in `formatter` in a lambda, so that the interface
    426     # is the same as the above values.
    427     def indirect(x):

File ~/opt/miniconda3/lib/python3.9/site-packages/numpy/core/arrayprint.py:1225, in IntegerFormat.__init__(self, data)
   1223 def __init__(self, data):
   1224     if data.size > 0:
-> 1225         max_str_len = max(len(str(np.max(data))),
   1226                           len(str(np.min(data))))
   1227     else:
   1228         max_str_len = 0

File ~/opt/miniconda3/lib/python3.9/site-packages/numpy/core/fromnumeric.py:2810, in max(a, axis, out, keepdims, initial, where)
   2692 @array_function_dispatch(_max_dispatcher)
   2693 @set_module('numpy')
   2694 def max(a, axis=None, out=None, keepdims=np._NoValue, initial=np._NoValue,
   2695          where=np._NoValue):
   2696     """
   2697     Return the maximum of an array or maximum along an axis.
   2698 
   (...)
   2808     5
   2809     """
-> 2810     return _wrapreduction(a, np.maximum, 'max', axis, None, out,
   2811                           keepdims=keepdims, initial=initial, where=where)

RecursionError: maximum recursion depth exceeded in comparison

TimeSeries7 pynwb.base.TimeSeries at 0x5864063040
Fields:
  comments: no comments
  conversion: 1.0
  data: [1 2 3 4]
  description: no description
  interval: 1
  offset: 0.0
  resolution: -1.0
  timestamps: TimeSeries6 pynwb.base.TimeSeries at 0x5895946832
Fields:
  comments: no comments
  conversion: 1.0
  data: [1 2 3 4]
  description: no description
  offset: 0.0
  rate: 10.0
  resolution: -1.0
  starting_time: 0.0
  starting_time_unit: seconds
  timestamp_link: (
    TimeSeries7 <class 'pynwb.base.TimeSeries'>
  )
  unit: volts

  timestamps_unit: seconds
  unit: volts

Steps to Reproduce

See above

Traceback

No response

Operating System

macOS

Python Executable

Conda

Python Version

3.12

Package Versions

No response

@stephprince
Copy link
Contributor

I'm having trouble reproducing the example. When I run ts3._repr_html_() with the latest pynwb, I get a valid html output with the timestamps value replaced to avoid the recursion error. Were there any other details or another example?

@bendichter
Copy link
Contributor Author

OK, yes, it looks like you fixed this here 2 weeks ago

https://github.com/NeurodataWithoutBorders/pynwb/blame/65b2a7d0ea642132a7a5fcb5a5199b675f092e0b/src/pynwb/base.py#L290

Thanks for doing that! The message looks great.

I guess I did not test with the latest pynwb dev branch. I didn't think to update pynwb to test this because I expected the solution to be in hdmf, where the rest of the plotting code lives.

While your solution does indeed handle the TimeSeries problem, this issue could come up with any other type that contains links/references, so I think it is also a good idea to build some sort of general cycle detection in the HDMF base class.

@stephprince
Copy link
Contributor

That makes sense to make a more general solution for links/references in the hdmf class so that the solution is where the rest of the plotting code is. From what I understand, the recursion error was happening in this case specifically because in addition to the link in ts3.timestamps to ts2, the timestamp_link attribute is added to ts2 that references ts3 (https://github.com/NeurodataWithoutBorders/pynwb/blob/dev/src/pynwb/base.py#L187). So this issue would come up with other types have similar circular references.

Maybe also related to #1043? If an object is a link, we can indicate that in the html representation and also stop any circular references.

@bendichter
Copy link
Contributor Author

OK, since the immediate issue is fixed (thanks again for this btw), let's close this and change the corresponding PR to draft mode. We can reopen if this types of thing comes up again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants