What happened?
DynamicTable.__init__ does not check columns passed via columns= against __columns__, so a column whose name matches a predefined column can be passed in with the wrong class and is silently accepted. There is already a TODO for this at src/hdmf/common/table.py:421:
# TODO: check columns against __columns__
# mismatches should raise an error (e.g., a VectorData cannot be passed in with the same name as a
# prespecified table region column)
The add_column path does warn on a class mismatch (table.py:966-976), but only for predefined optional columns that are still uninitialized, so a required predefined column is not covered there either.
This surfaced in PyNWB's EventsTable, whose timestamp column is declared {'required': True, 'class': TimestampVectorData}. The NWB schema requires that column to be a TimestampVectorData, but a plain VectorData can be substituted through the constructor with no error and no warning. Writing the result emits only a MissingRequiredBuildWarning, and the resulting file fails validation. See NeurodataWithoutBorders/nwb-schema#708.
Steps to Reproduce
from hdmf.common import VectorData
from pynwb.event import EventsTable
# EventsTable.__columns__ declares timestamp as required with class=TimestampVectorData
et = EventsTable(
name="et",
description="d",
columns=[VectorData(name="timestamp", description="plain", data=[1, 2, 3])],
)
print(type(et["timestamp"])) # <class 'hdmf.common.table.VectorData'> -- no error, no warning
Writing this table produces a file that the NWB validator rejects with missing data type TimestampVectorData (timestamp).
Expected behavior
DynamicTable.__init__ validates columns against __columns__ and reports a mismatch, so an invalid table cannot be built silently. Matching the existing add_column behavior (warn now, error in a future version) seems reasonable, as does raising immediately given that the result cannot be written validly.
Operating System
macOS
Python Executable
Conda
Python Version
3.14
Package Versions
hdmf 6.1.1.dev14 (dev branch), pynwb 4.1.1.dev
Code of Conduct
Related: add_column has the same root cause
add_column does not honor __columns__ either, for the same reason: col_cls is defaulted to VectorData at table.py:962-964 before the predefined spec is consulted at table.py:966-976. A caller who never passes col_cls therefore gets a plain VectorData for a predefined typed column, plus a warning that blames an argument they did not supply:
et = EventsTable(name="et", description="d")
et.add_column(name="duration", description="dur") # 'duration' is predefined with class=DurationVectorData
print(type(et["duration"])) # <class 'hdmf.common.table.VectorData'>
UserWarning: Column 'duration' is predefined in EventsTable with class=<class 'pynwb.event.DurationVectorData'> which does not match the entered col_cls argument. The predefined class spec will be ignored. Please ensure the new column complies with the spec. This will raise an error in a future version of HDMF.
Suggested behavior: when col_cls is not supplied and the column is predefined in __columns__ with a class, use that class. Warn only when the caller passed a col_cls that conflicts with the predefined one.
This one is quieter than the constructor case in practice, because duration is optional in the schema (quantity: '?'), so the resulting file validates with no errors at all. That validator behavior is filed separately as #1554.
What happened?
DynamicTable.__init__does not check columns passed viacolumns=against__columns__, so a column whose name matches a predefined column can be passed in with the wrong class and is silently accepted. There is already a TODO for this atsrc/hdmf/common/table.py:421:The
add_columnpath does warn on a class mismatch (table.py:966-976), but only for predefined optional columns that are still uninitialized, so a required predefined column is not covered there either.This surfaced in PyNWB's
EventsTable, whosetimestampcolumn is declared{'required': True, 'class': TimestampVectorData}. The NWB schema requires that column to be aTimestampVectorData, but a plainVectorDatacan be substituted through the constructor with no error and no warning. Writing the result emits only aMissingRequiredBuildWarning, and the resulting file fails validation. See NeurodataWithoutBorders/nwb-schema#708.Steps to Reproduce
Writing this table produces a file that the NWB validator rejects with
missing data type TimestampVectorData (timestamp).Expected behavior
DynamicTable.__init__validatescolumnsagainst__columns__and reports a mismatch, so an invalid table cannot be built silently. Matching the existingadd_columnbehavior (warn now, error in a future version) seems reasonable, as does raising immediately given that the result cannot be written validly.Operating System
macOS
Python Executable
Conda
Python Version
3.14
Package Versions
hdmf 6.1.1.dev14 (dev branch), pynwb 4.1.1.dev
Code of Conduct
Related:
add_columnhas the same root causeadd_columndoes not honor__columns__either, for the same reason:col_clsis defaulted toVectorDataattable.py:962-964before the predefined spec is consulted attable.py:966-976. A caller who never passescol_clstherefore gets a plainVectorDatafor a predefined typed column, plus a warning that blames an argument they did not supply:Suggested behavior: when
col_clsis not supplied and the column is predefined in__columns__with aclass, use that class. Warn only when the caller passed acol_clsthat conflicts with the predefined one.This one is quieter than the constructor case in practice, because
durationis optional in the schema (quantity: '?'), so the resulting file validates with no errors at all. That validator behavior is filed separately as #1554.