diff --git a/modules/core/dependency/python-ihm/.codecov.yml b/modules/core/dependency/python-ihm/.codecov.yml index 93a7220f9c..dbd70c491c 100644 --- a/modules/core/dependency/python-ihm/.codecov.yml +++ b/modules/core/dependency/python-ihm/.codecov.yml @@ -1,4 +1,3 @@ ignore: - test - util - - **/_compat_collections.py diff --git a/modules/core/dependency/python-ihm/.github/workflows/testpy.yml b/modules/core/dependency/python-ihm/.github/workflows/testpy.yml index ad7b9d9c34..f1bab20d67 100644 --- a/modules/core/dependency/python-ihm/.github/workflows/testpy.yml +++ b/modules/core/dependency/python-ihm/.github/workflows/testpy.yml @@ -49,4 +49,6 @@ jobs: else flake8 --ignore E402,W503,W504,N816 fi - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/modules/core/dependency/python-ihm/ihm/dumper.py b/modules/core/dependency/python-ihm/ihm/dumper.py index a3e302a492..7b01b9ccf6 100644 --- a/modules/core/dependency/python-ihm/ihm/dumper.py +++ b/modules/core/dependency/python-ihm/ihm/dumper.py @@ -1358,10 +1358,12 @@ def dump(self, system, writer): def dump_summary(self, system, writer): with writer.loop("_ihm_modeling_protocol", - ["id", "protocol_name", "num_steps"]) as lp: + ["id", "protocol_name", "num_steps", + "details"]) as lp: for p in system._all_protocols(): lp.write(id=p._id, - protocol_name=p.name, num_steps=len(p.steps)) + protocol_name=p.name, num_steps=len(p.steps), + details=p.details) def dump_details(self, system, writer): ordinal = itertools.count(1) @@ -1657,6 +1659,7 @@ def dump_atoms(self, system, writer, add_ihm=True): type_symbol=atom.type_symbol, group_PDB='HETATM' if atom.het else 'ATOM', label_atom_id=atom.atom_id, + label_alt_id=atom.alt_id, label_comp_id=comp.id, label_asym_id=atom.asym_unit._id, label_entity_id=atom.asym_unit.entity._id, diff --git a/modules/core/dependency/python-ihm/ihm/format.py b/modules/core/dependency/python-ihm/ihm/format.py index 9e8d203503..a043011733 100644 --- a/modules/core/dependency/python-ihm/ihm/format.py +++ b/modules/core/dependency/python-ihm/ihm/format.py @@ -228,8 +228,10 @@ def _repr(self, obj): # which isn't valid mmCIF syntax. _long_type = long only on Python 2. elif isinstance(obj, _long_type): return "%d" % obj - else: + elif isinstance(obj, str): return repr(obj) + else: + return str(obj) # Acceptable 'whitespace' characters in CIF diff --git a/modules/core/dependency/python-ihm/ihm/model.py b/modules/core/dependency/python-ihm/ihm/model.py index 8e48938fca..a4ab93bf37 100644 --- a/modules/core/dependency/python-ihm/ihm/model.py +++ b/modules/core/dependency/python-ihm/ihm/model.py @@ -56,20 +56,23 @@ class Atom(object): :param float biso: Temperature factor or equivalent (if applicable) :param float occupancy: Fraction of the atom type present (if applicable) + :param float alt_id: Alternate conformation indicator + (if applicable) """ # Reduce memory usage __slots__ = ['asym_unit', 'seq_id', 'atom_id', 'type_symbol', - 'x', 'y', 'z', 'het', 'biso', 'occupancy'] + 'x', 'y', 'z', 'het', 'biso', 'occupancy', 'alt_id'] def __init__(self, asym_unit, seq_id, atom_id, type_symbol, x, y, z, - het=False, biso=None, occupancy=None): + het=False, biso=None, occupancy=None, alt_id=None): self.asym_unit = asym_unit self.seq_id, self.atom_id = seq_id, atom_id self.type_symbol = type_symbol self.x, self.y, self.z = x, y, z self.het, self.biso = het, biso self.occupancy = occupancy + self.alt_id = alt_id class Model(object): diff --git a/modules/core/dependency/python-ihm/ihm/protocol.py b/modules/core/dependency/python-ihm/ihm/protocol.py index 0a67b0d29a..22ddbdd777 100644 --- a/modules/core/dependency/python-ihm/ihm/protocol.py +++ b/modules/core/dependency/python-ihm/ihm/protocol.py @@ -65,9 +65,13 @@ class Protocol(object): Normally a protocol is passed to one or more :class:`~ihm.model.Model` objects, although unused protocols can still be included in the file if desired by adding them to :attr:`~ihm.System.orphan_protocols`. + + :param str name: Optional name for the protocol + :param str details: Additional text describing the protocol """ - def __init__(self, name=None): + def __init__(self, name=None, details=None): self.name = name + self.details = details #: All modeling steps (:class:`Step` objects) self.steps = [] diff --git a/modules/core/dependency/python-ihm/ihm/reader.py b/modules/core/dependency/python-ihm/ihm/reader.py index dd721c74a3..012059c9d7 100644 --- a/modules/core/dependency/python-ihm/ihm/reader.py +++ b/modules/core/dependency/python-ihm/ihm/reader.py @@ -1738,9 +1738,10 @@ class _ProtocolHandler(Handler): category = '_ihm_modeling_protocol' ignored_keywords = ['ordinal_id', 'struct_assembly_description'] - def __call__(self, id, protocol_name, num_steps): + def __call__(self, id, protocol_name, num_steps, details): p = self.sysr.protocols.get_by_id(id) - self.copy_if_present(p, locals(), mapkeys={'protocol_name': 'name'}) + self.copy_if_present(p, locals(), mapkeys={'protocol_name': 'name'}, + keys=['details']) class _ProtocolDetailsHandler(Handler): @@ -2136,7 +2137,7 @@ def _get_seq_id_from_auth(self, auth_seq_id, pdbx_pdb_ins_code, asym): def __call__(self, pdbx_pdb_model_num, label_asym_id, b_iso_or_equiv, label_seq_id, label_atom_id, type_symbol, cartn_x, cartn_y, cartn_z, occupancy, group_pdb, auth_seq_id, - pdbx_pdb_ins_code, auth_asym_id, label_comp_id): + pdbx_pdb_ins_code, auth_asym_id, label_comp_id, label_alt_id): # seq_id can be None for non-polymers (HETATM) seq_id = self.get_int(label_seq_id) # todo: handle fields other than those output by us @@ -2164,7 +2165,7 @@ def __call__(self, pdbx_pdb_model_num, label_asym_id, b_iso_or_equiv, asym_unit=asym, seq_id=our_seq_id, atom_id=label_atom_id, type_symbol=type_symbol, x=float(cartn_x), y=float(cartn_y), z=float(cartn_z), het=group != 'ATOM', biso=biso, - occupancy=occupancy) + occupancy=occupancy, alt_id=label_alt_id) model.add_atom(a) # Note any residues that have different seq_id and auth_seq_id diff --git a/modules/core/dependency/python-ihm/test/test_dumper.py b/modules/core/dependency/python-ihm/test/test_dumper.py index 81090bfb56..081a2ba064 100644 --- a/modules/core/dependency/python-ihm/test/test_dumper.py +++ b/modules/core/dependency/python-ihm/test/test_dumper.py @@ -1762,7 +1762,7 @@ class MockObject(object): num_models_end=2000, multi_scale=True, ensemble=False)) system.orphan_protocols.append(p1) - p2 = ihm.protocol.Protocol('sampling') + p2 = ihm.protocol.Protocol('sampling', details='extra details') p2.steps.append(ihm.protocol.Step( assembly=assembly, dataset_group=dsg2, method='Replica exchange', num_models_begin=2000, @@ -1779,8 +1779,9 @@ class MockObject(object): _ihm_modeling_protocol.id _ihm_modeling_protocol.protocol_name _ihm_modeling_protocol.num_steps -1 equilibration 2 -2 sampling 1 +_ihm_modeling_protocol.details +1 equilibration 2 . +2 sampling 1 'extra details' # # loop_ @@ -2357,7 +2358,7 @@ def test_model_dumper_atoms(self): het=True), ihm.model.Atom(asym_unit=asym, seq_id=2, atom_id='N', type_symbol='N', x=4.0, y=5.0, z=6.0, - biso=42.0, occupancy=0.2)] + biso=42.0, occupancy=0.2, alt_id='A')] dumper = ihm.dumper._ModelDumper() dumper.finalize(system) # assign model/group IDs @@ -2410,7 +2411,7 @@ def test_model_dumper_atoms(self): _atom_site.ihm_model_id ATOM 1 C C . ALA 1 1 ? X 1.000 2.000 3.000 . 9 X ALA . 1 1 HETATM 2 C CA . ALA 1 1 ? X 10.000 20.000 30.000 . 9 X ALA . 1 1 -ATOM 3 N N . CYS 2 2 ? X 4.000 5.000 6.000 0.200 9 X CYS 42.000 1 1 +ATOM 3 N N A CYS 2 2 ? X 4.000 5.000 6.000 0.200 9 X CYS 42.000 1 1 # # loop_ @@ -2431,7 +2432,7 @@ def test_model_dumper_atoms(self): self.assertEqual( out.split('\n')[44:47:2], ["ATOM 1 C C . ALA 1 0 ? X 1.000 2.000 3.000 . 9 X ALA . 1 1", - "ATOM 3 N N . CYS 2 1 ? X 4.000 5.000 6.000 " + "ATOM 3 N N A CYS 2 1 ? X 4.000 5.000 6.000 " "0.200 9 X CYS 42.000 1 1"]) # With auth_seq_id map @@ -2440,7 +2441,7 @@ def test_model_dumper_atoms(self): self.assertEqual( out.split('\n')[44:47:2], ["ATOM 1 C C . ALA 1 42 ? X 1.000 2.000 3.000 . 9 X ALA . 1 1", - "ATOM 3 N N . CYS 2 99 ? X 4.000 5.000 6.000 " + "ATOM 3 N N A CYS 2 99 ? X 4.000 5.000 6.000 " "0.200 9 X CYS 42.000 1 1"]) def test_model_dumper_water_atoms(self): diff --git a/modules/core/dependency/python-ihm/test/test_examples.py b/modules/core/dependency/python-ihm/test/test_examples.py index 9918075264..8cbc96152f 100644 --- a/modules/core/dependency/python-ihm/test/test_examples.py +++ b/modules/core/dependency/python-ihm/test/test_examples.py @@ -41,7 +41,7 @@ def test_simple_docking_example(self): # can read it with open(os.path.join(tmpdir, 'output.cif')) as fh: contents = fh.readlines() - self.assertEqual(len(contents), 318) + self.assertEqual(len(contents), 319) with open(os.path.join(tmpdir, 'output.cif')) as fh: s, = ihm.reader.read(fh) @@ -70,7 +70,7 @@ def test_ligands_water_example(self): # can read it with open(out) as fh: contents = fh.readlines() - self.assertEqual(len(contents), 254) + self.assertEqual(len(contents), 255) with open(out) as fh: s, = ihm.reader.read(fh) # Make sure that resulting Python objects are picklable diff --git a/modules/core/dependency/python-ihm/test/test_format.py b/modules/core/dependency/python-ihm/test/test_format.py index 5e6ce710d3..4425da143d 100644 --- a/modules/core/dependency/python-ihm/test/test_format.py +++ b/modules/core/dependency/python-ihm/test/test_format.py @@ -2,6 +2,10 @@ import os import unittest import sys +try: + import numpy +except ImportError: + numpy = None if sys.version_info[0] >= 3: from io import StringIO @@ -226,6 +230,11 @@ def test_repr(self): # Literal . must be quoted to distinguish from the omitted value self.assertEqual(w._repr('.foo'), ".foo") self.assertEqual(w._repr('.'), "'.'") + # Make sure that numpy ints are treated like plain ints, + # not rendered as "np.int32(42)" or similar + if numpy is not None: + self.assertEqual(w._repr(numpy.int32(42)), '42') + self.assertEqual(w._repr(numpy.int64(42)), '42') def test_reader_base(self): """Test Reader base class""" diff --git a/modules/core/dependency/python-ihm/test/test_reader.py b/modules/core/dependency/python-ihm/test/test_reader.py index 884ab8c00a..9e0616d9b5 100644 --- a/modules/core/dependency/python-ihm/test/test_reader.py +++ b/modules/core/dependency/python-ihm/test/test_reader.py @@ -1320,12 +1320,14 @@ def test_protocol_handler(self): _ihm_modeling_protocol.id _ihm_modeling_protocol.protocol_name _ihm_modeling_protocol.num_steps -1 Prot1 5 +_ihm_modeling_protocol.details +1 Prot1 5 'extra details' """ for fh in cif_file_handles(cif): s, = ihm.reader.read(fh) p1, = s.orphan_protocols self.assertEqual(p1.name, "Prot1") + self.assertEqual(p1.details, "extra details") # no step objects read yet, num_steps ignored self.assertEqual(len(p1.steps), 0) @@ -1987,7 +1989,7 @@ def test_atom_site_handler(self): _atom_site.pdbx_PDB_model_num _atom_site.ihm_model_id ATOM 1 N N . SER 1 A 54.401 -49.984 -35.287 . 1 A . 1 1 -HETATM 2 C CA . SER . B 54.452 -48.492 -35.210 0.200 1 A 42.0 1 1 +HETATM 2 C CA A SER . B 54.452 -48.492 -35.210 0.200 1 A 42.0 1 1 """) s, = ihm.reader.read(fh) m = s.state_groups[0][0][0][0] @@ -2002,6 +2004,7 @@ def test_atom_site_handler(self): self.assertEqual(a1.het, False) self.assertIsNone(a1.biso) self.assertIsNone(a1.occupancy) + self.assertIsNone(a1.alt_id) self.assertEqual(a2.asym_unit._id, 'B') self.assertEqual(a2.seq_id, 1) @@ -2010,6 +2013,7 @@ def test_atom_site_handler(self): self.assertEqual(a2.het, True) self.assertAlmostEqual(a2.biso, 42.0, delta=1.0) self.assertAlmostEqual(a2.occupancy, 0.2, delta=0.1) + self.assertEqual(a2.alt_id, 'A') def test_atom_site_handler_auth_seq_id(self): """Test AtomSiteHandler handling of auth_seq_id and ins_code"""