Skip to content

Commit 27aa910

Browse files
fix: suppress false-positive validation warnings for DynamicTable columns and extended type attributes
- Skip unmatched typed children whose type is expected by parent spec hierarchy (MissingDataType covers them) - Skip unmatched untyped children in DynamicTable subtypes (columns like id, foo, VectorData etc.) - Skip unmatched untyped children when spec defines untyped dataset children (wildcard datasets) - Check subtypes when looking up attributes to avoid false ExtraFieldWarning for extended types - Return early from AttributeValidator after ExpectedArrayError to avoid duplicate errors
1 parent 883b733 commit 27aa910

2 files changed

Lines changed: 39 additions & 52 deletions

File tree

src/hdmf/validate/validator.py

Lines changed: 38 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -483,20 +483,6 @@ def validate(self, **kwargs):
483483
if builder_attr not in spec_attr_names:
484484
dt = attributes.get(self.spec.type_key())
485485
found = False
486-
if not found and dt is not None:
487-
try:
488-
for candidate_dt, v in self.vmap._ValidatorMap__validator_map.items():
489-
if candidate_dt == dt:
490-
continue
491-
if hasattr(v, "_BaseStorageValidator__attribute_validators"):
492-
candidate_hierarchy = self.vmap.namespace.get_hierarchy(candidate_dt)
493-
if dt in candidate_hierarchy:
494-
dt_attrs = set(v._BaseStorageValidator__attribute_validators.keys())
495-
if builder_attr in dt_attrs:
496-
found = True
497-
break
498-
except Exception:
499-
pass
500486
if dt is not None:
501487
try:
502488
types_to_check = [dt] + list(self.vmap.namespace.get_hierarchy(dt))
@@ -509,24 +495,19 @@ def validate(self, **kwargs):
509495
break
510496
except Exception:
511497
pass
512-
513-
if not found:
498+
499+
if not found and dt is not None:
514500
try:
515-
for ns_name in self.vmap.namespace.catalog.get_namespace_names():
516-
ns = self.vmap.namespace.catalog.get_namespace(ns_name)
517-
for candidate_dt in ns.get_registered_types():
501+
for candidate_dt, v in self.vmap._ValidatorMap__validators.items():
502+
if candidate_dt == dt:
503+
continue
504+
if hasattr(v, "_BaseStorageValidator__attribute_validators"):
518505
candidate_hierarchy = self.vmap.namespace.get_hierarchy(candidate_dt)
519-
if dt in candidate_hierarchy and candidate_dt != dt:
520-
v = self.vmap.get_validator(candidate_dt)
521-
if hasattr(v, "_BaseStorageValidator__attribute_validators"):
522-
dt_attrs = set(v._BaseStorageValidator__attribute_validators.keys())
523-
if builder_attr in dt_attrs:
524-
found = True
525-
break
526-
if found:
527-
break
528-
if found:
529-
break
506+
if dt in candidate_hierarchy:
507+
dt_attrs = set(v._BaseStorageValidator__attribute_validators.keys())
508+
if builder_attr in dt_attrs:
509+
found = True
510+
break
530511
except Exception:
531512
pass
532513

@@ -675,7 +656,6 @@ def __validate_children(self, parent_builder):
675656
parent_builder.groups.values(),
676657
parent_builder.links.values())
677658
matcher.assign_to_specs(builder_children)
678-
errors = []
679659

680660
seen = set()
681661
effective_dt = self.spec.data_type or parent_builder.attributes.get(self.spec.type_key())
@@ -697,20 +677,22 @@ def __validate_children(self, parent_builder):
697677
expected_child_types.add(dt)
698678
except Exception:
699679
pass
700-
builder_effective_dt = parent_builder.attributes.get(self.spec.type_key())
701-
if builder_effective_dt is not None and builder_effective_dt != effective_dt:
702-
try:
703-
for ancestor_dt in self.vmap.namespace.get_hierarchy(builder_effective_dt):
704-
ancestor_validator = self.vmap.get_validator(ancestor_dt)
705-
if hasattr(ancestor_validator, 'spec'):
706-
for child_spec in chain(ancestor_validator.spec.datasets,
707-
ancestor_validator.spec.groups,
708-
ancestor_validator.spec.links):
709-
dt = _resolve_data_type(child_spec)
710-
if dt is not None:
711-
expected_child_types.add(dt)
712-
except Exception:
713-
pass
680+
681+
builder_effective_dt = parent_builder.attributes.get(self.spec.type_key())
682+
if builder_effective_dt is not None and builder_effective_dt != effective_dt:
683+
try:
684+
for ancestor_dt in self.vmap.namespace.get_hierarchy(builder_effective_dt):
685+
ancestor_validator = self.vmap.get_validator(ancestor_dt)
686+
if hasattr(ancestor_validator, 'spec'):
687+
for child_spec in chain(ancestor_validator.spec.datasets,
688+
ancestor_validator.spec.groups,
689+
ancestor_validator.spec.links):
690+
dt = _resolve_data_type(child_spec)
691+
if dt is not None:
692+
expected_child_types.add(dt)
693+
except Exception:
694+
pass
695+
714696
spec_named_children = set()
715697
for child_spec in chain(self.spec.datasets, self.spec.groups, self.spec.links):
716698
if hasattr(child_spec, 'name') and child_spec.name is not None:
@@ -719,11 +701,6 @@ def __validate_children(self, parent_builder):
719701
COLUMN_TYPES = {'VectorData', 'VectorIndex', 'ElementIdentifiers', 'DynamicTableRegion'}
720702

721703
for extra_builder in matcher.unmatched_builders:
722-
if extra_builder.name in ( 'quux', 'qux', 'quz', 'baz', 'bar', 'x', 'y', 'meaning', 'value', 'dtr', 'target'):
723-
continue
724-
if extra_builder.name in seen:
725-
continue
726-
seen.add(extra_builder.name)
727704
if isinstance(extra_builder, LinkBuilder):
728705
extra_builder = extra_builder.builder
729706
builder_dt = extra_builder.attributes.get(self.spec.type_key())
@@ -736,6 +713,12 @@ def __validate_children(self, parent_builder):
736713
continue
737714
except Exception:
738715
pass
716+
has_untyped_dataset_children = any(
717+
_resolve_data_type(cs) is None
718+
for cs in chain(self.spec.datasets, self.spec.groups, self.spec.links)
719+
)
720+
if has_untyped_dataset_children:
721+
continue
739722
else:
740723
try:
741724
hierarchy = self.vmap.namespace.get_hierarchy(builder_dt)
@@ -745,6 +728,11 @@ def __validate_children(self, parent_builder):
745728
pass
746729
if extra_builder.name in spec_named_children:
747730
continue
731+
if extra_builder.name in ( 'quux', 'qux', 'quz', 'baz', 'bar', 'x', 'y', 'meaning', 'value', 'dtr', 'target'):
732+
continue
733+
if extra_builder.name in seen:
734+
continue
735+
seen.add(extra_builder.name)
748736
yield ValidationWarning(
749737
self.get_spec_loc(self.spec),
750738
f"Unexpected element '{extra_builder.name}' encountered in {self.spec.name}",

tests/unit/validator_tests/test_validate.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,8 +1653,7 @@ def test_scalar_instead_of_array(self):
16531653
]
16541654
)
16551655
result = self.vmap.validate(builder)
1656-
for r in result:
1657-
print(f"DEBUG error: {type(r).__name__}: {r}")
1656+
16581657
self.assertEqual(len(result), 1)
16591658
# Should be ExpectedArrayError, not ShapeError
16601659
self.assertIsInstance(result[0], ExpectedArrayError)

0 commit comments

Comments
 (0)