Skip to content

DynamicTable does not check constructor-passed columns against __columns__ #1553

Description

@rly

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions