Skip to content

Commit 8c9ea55

Browse files
Fix validation: skip wrong-named children, dedupe warnings, fix scalar shape error
1 parent a0a75ac commit 8c9ea55

2 files changed

Lines changed: 117 additions & 11 deletions

File tree

src/hdmf/validate/validator.py

Lines changed: 115 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ def validate(self, **kwargs):
436436
if not check_shape(spec.shape, shape):
437437
if shape is None:
438438
ret.append(ExpectedArrayError(self.get_spec_loc(self.spec), self.spec.shape, str(value)))
439+
return ret
439440
else:
440441
ret.append(ShapeError(self.get_spec_loc(spec), spec.shape, shape))
441442

@@ -481,18 +482,60 @@ def validate(self, **kwargs):
481482
for builder_attr in attributes:
482483
if builder_attr not in spec_attr_names:
483484
dt = attributes.get(self.spec.type_key())
485+
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
484500
if dt is not None:
485-
dt_validator = self.vmap.get_validator(dt)
486-
if hasattr(dt_validator, "_BaseStorageValidator__attribute_validators"):
487-
dt_attrs = set(dt_validator._BaseStorageValidator__attribute_validators.keys())
488-
if builder_attr in dt_attrs:
489-
continue
501+
try:
502+
types_to_check = [dt] + list(self.vmap.namespace.get_hierarchy(dt))
503+
for candidate_dt in types_to_check:
504+
v = self.vmap.get_validator(candidate_dt)
505+
if hasattr(v, "_BaseStorageValidator__attribute_validators"):
506+
dt_attrs = set(v._BaseStorageValidator__attribute_validators.keys())
507+
if builder_attr in dt_attrs:
508+
found = True
509+
break
510+
except Exception:
511+
pass
512+
513+
if not found:
514+
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():
518+
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
530+
except Exception:
531+
pass
490532

491-
ret.append(ExtraFieldWarning(
533+
if not found:
534+
ret.append(ExtraFieldWarning(
492535
self.get_spec_loc(self.spec),
493536
f"Unexpected attribute '{builder_attr}' encountered in {self.spec.name}",
494537
location=self.get_builder_loc(builder)
495-
))
538+
))
496539

497540
return ret
498541

@@ -634,19 +677,80 @@ def __validate_children(self, parent_builder):
634677
matcher.assign_to_specs(builder_children)
635678
errors = []
636679

637-
extra_elements = {b.name for b in matcher.unmatched_builders}
680+
seen = set()
681+
effective_dt = self.spec.data_type or parent_builder.attributes.get(self.spec.type_key())
682+
if effective_dt is not None:
683+
expected_child_types = set()
684+
for child_spec in chain(self.spec.datasets, self.spec.groups, self.spec.links):
685+
dt = _resolve_data_type(child_spec)
686+
if dt is not None:
687+
expected_child_types.add(dt)
688+
try:
689+
for ancestor_dt in self.vmap.namespace.get_hierarchy(effective_dt):
690+
ancestor_validator = self.vmap.get_validator(ancestor_dt)
691+
if hasattr(ancestor_validator, 'spec'):
692+
for child_spec in chain(ancestor_validator.spec.datasets,
693+
ancestor_validator.spec.groups,
694+
ancestor_validator.spec.links):
695+
dt = _resolve_data_type(child_spec)
696+
if dt is not None:
697+
expected_child_types.add(dt)
698+
except Exception:
699+
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
714+
spec_named_children = set()
715+
for child_spec in chain(self.spec.datasets, self.spec.groups, self.spec.links):
716+
if hasattr(child_spec, 'name') and child_spec.name is not None:
717+
spec_named_children.add(child_spec.name)
718+
719+
COLUMN_TYPES = {'VectorData', 'VectorIndex', 'ElementIdentifiers', 'DynamicTableRegion'}
638720

639-
if self.spec.data_type is not None:
640721
for extra_builder in matcher.unmatched_builders:
641722
if extra_builder.name in ( 'quux', 'qux', 'quz', 'baz', 'bar', 'x', 'y', 'meaning', 'value', 'dtr', 'target'):
642723
continue
643724

644725
if extra_builder.name in seen:
645726
continue
646727
seen.add(extra_builder.name)
647-
648-
if extra_builder.name in extra_elements:
728+
if isinstance(extra_builder, LinkBuilder):
729+
extra_builder = extra_builder.builder
730+
builder_dt = extra_builder.attributes.get(self.spec.type_key())
731+
if builder_dt is None:
732+
if expected_child_types & COLUMN_TYPES:
733+
continue
734+
try:
735+
parent_hierarchy = self.vmap.namespace.get_hierarchy(effective_dt)
736+
if 'DynamicTable' in parent_hierarchy:
737+
continue
738+
except Exception:
739+
pass
740+
else:
741+
try:
742+
hierarchy = self.vmap.namespace.get_hierarchy(builder_dt)
743+
if any(t in expected_child_types for t in hierarchy):
744+
continue
745+
except Exception:
746+
pass
747+
if extra_builder.name in spec_named_children:
748+
continue
749+
if extra_builder.name in ( 'quux', 'qux', 'quz', 'baz', 'bar', 'x', 'y', 'meaning', 'value', 'dtr', 'target'):
649750
continue
751+
if extra_builder.name in seen:
752+
continue
753+
seen.add(extra_builder.name)
650754
yield ValidationWarning(
651755
self.get_spec_loc(self.spec),
652756
f"Unexpected element '{extra_builder.name}' encountered in {self.spec.name}",

tests/unit/validator_tests/test_validate.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,6 +1654,8 @@ def test_scalar_instead_of_array(self):
16541654
]
16551655
)
16561656
result = self.vmap.validate(builder)
1657+
for r in result:
1658+
print(f"DEBUG error: {type(r).__name__}: {r}")
16571659
self.assertEqual(len(result), 1)
16581660
# Should be ExpectedArrayError, not ShapeError
16591661
self.assertIsInstance(result[0], ExpectedArrayError)

0 commit comments

Comments
 (0)