Skip to content

Commit

Permalink
Handle null features in dumper
Browse files Browse the repository at this point in the history
If a Feature base object is encountered, rather
than raising an obscure exception, report it.
This can happen if an input file references a
feature by ID but does not define it anywhere.
If checks are disabled, output the feature in
ihm_feature_list with a type of "?".
  • Loading branch information
benmwebb committed Oct 18, 2024
1 parent a218a21 commit 739c711
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 1 deletion.
2 changes: 2 additions & 0 deletions ihm/dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2176,6 +2176,8 @@ def dump_list(self, writer):
["feature_id", "feature_type", "entity_type",
"details"]) as lp:
for f in self._features_by_id:
if self._check and f.type is ihm.unknown:
raise ValueError("Invalid null feature %s" % f)
lp.write(feature_id=f._id, feature_type=f.type,
entity_type=f._get_entity_type(),
details=f.details)
Expand Down
7 changes: 6 additions & 1 deletion ihm/restraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,19 +574,24 @@ def __init__(self, psi=None, sigma1=None, sigma2=None):

class Feature(object):
"""Base class for selecting parts of the system that a restraint acts on.
See :class:`ResidueFeature`, :class:`AtomFeature`,
This class should not be used itself; instead,
see :class:`ResidueFeature`, :class:`AtomFeature`,
:class:`NonPolyFeature`, and :class:`PseudoSiteFeature`.
Features are typically assigned to one or more
:class:`~ihm.restraint.GeometricRestraint` or
:class:`~ihm.restraint.DerivedDistanceRestraint` objects.
"""
details = None
type = ihm.unknown

def _all_entities_or_asyms(self):
# Get all Entities or AsymUnits referenced by this object
return []

def _get_entity_type(self):
return ihm.unknown


class ResidueFeature(Feature):
"""Selection of one or more residues from the system.
Expand Down
23 changes: 23 additions & 0 deletions test/test_dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3712,6 +3712,29 @@ def test_feature_dumper_no_residues(self):
self.assertEqual(len(dumper._features_by_id), 1)
self.assertRaises(ValueError, _get_dumper_output, dumper, system)

def test_feature_dumper_base_class(self):
"""Test FeatureDumper with a Feature base class"""
system = ihm.System()

f = ihm.restraint.Feature()
system.orphan_features.append(f)

dumper = ihm.dumper._FeatureDumper()
dumper.finalize(system) # assign IDs
self.assertEqual(len(dumper._features_by_id), 1)
self.assertRaises(ValueError, _get_dumper_output, dumper, system)
# Should be OK if checks are disabled
out = _get_dumper_output(dumper, system, check=False)
self.assertEqual(out, """#
loop_
_ihm_feature_list.feature_id
_ihm_feature_list.feature_type
_ihm_feature_list.entity_type
_ihm_feature_list.details
1 ? ? .
#
""")

def test_pseudo_site_dumper(self):
"""Test PseudoSiteDumper"""
system = ihm.System()
Expand Down
2 changes: 2 additions & 0 deletions test/test_restraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ def test_feature(self):
"""Test Feature base class"""
f = ihm.restraint.Feature() # does nothing
self.assertEqual(f._all_entities_or_asyms(), [])
self.assertIs(f.type, ihm.unknown)
self.assertIs(f._get_entity_type(), ihm.unknown)

def test_residue_feature(self):
"""Test ResidueFeature class"""
Expand Down

0 comments on commit 739c711

Please sign in to comment.