From 0a83ee79e174cff905998a407fa47ff87bbb597f Mon Sep 17 00:00:00 2001 From: Ben Webb Date: Mon, 26 Aug 2024 12:50:49 -0700 Subject: [PATCH] Allow mmCIF inputs --- frontend/multifoxs/submit_page.py | 13 +- frontend/multifoxs/templates/download.html | 2 +- frontend/multifoxs/templates/help.html | 4 +- frontend/multifoxs/templates/index.html | 2 +- test/frontend/test_submit.py | 173 ++++++++++++++++++--- 5 files changed, 168 insertions(+), 26 deletions(-) diff --git a/frontend/multifoxs/submit_page.py b/frontend/multifoxs/submit_page.py index 62f6992..fa9caaf 100644 --- a/frontend/multifoxs/submit_page.py +++ b/frontend/multifoxs/submit_page.py @@ -59,17 +59,22 @@ def handle_new_job(): def handle_pdb(pdb_code, pdb_file, job): """Handle input PDB code or file. Return file name.""" if pdb_file: - fname = 'input.pdb' + if pdb_file.filename.endswith('.cif'): + fname = 'input.cif' + else: + fname = 'input.pdb' full_fname = job.get_path(fname) pdb_file.save(full_fname) - saliweb.frontend.check_pdb(full_fname) + saliweb.frontend.check_pdb_or_mmcif( + full_fname, show_filename=os.path.basename(pdb_file.filename)) return fname elif pdb_code: - fname = saliweb.frontend.get_pdb_chains(pdb_code, job.directory) + fname = saliweb.frontend.get_pdb_chains( + pdb_code, job.directory, formats=["PDB", "MMCIF", "IHM"]) return os.path.basename(fname) else: raise InputValidationError("Error in protein input: please specify " - "PDB code or upload file") + "PDB code or upload PDB/mmCIF file") def handle_uploaded_file(fh, job, output_file, description, diff --git a/frontend/multifoxs/templates/download.html b/frontend/multifoxs/templates/download.html index 6440fb3..7828197 100644 --- a/frontend/multifoxs/templates/download.html +++ b/frontend/multifoxs/templates/download.html @@ -14,7 +14,7 @@

Download

    -
  1. Conformational sampling. Prepare your input PDB file and flexible residues file as explained in Help :

    +
  2. Conformational sampling. Prepare your input PDB or mmCIF file and flexible residues file as explained in Help :


    rrt_sample protein_PDB flexible_residues_file -i 1000000 -n 10000
    diff --git a/frontend/multifoxs/templates/help.html b/frontend/multifoxs/templates/help.html index 9886410..290c3b6 100644 --- a/frontend/multifoxs/templates/help.html +++ b/frontend/multifoxs/templates/help.html @@ -9,8 +9,8 @@

    Input Fields

    • Input protein: it is possible to -specify the PDB code of the input protein or upload a file in PDB -format. Each code is a four character PDB ID, followed by a colon and +specify the PDB code of the input protein or upload a file in PDB or +mmCIF format. Each code is a four character PDB ID, followed by a colon and a comma-separated list of chain IDs, e.g. 2pka:A,B. If no chain IDs are given, all the chains of the PDB file are used. diff --git a/frontend/multifoxs/templates/index.html b/frontend/multifoxs/templates/index.html index 033ec84..59757fd 100644 --- a/frontend/multifoxs/templates/index.html +++ b/frontend/multifoxs/templates/index.html @@ -8,7 +8,7 @@ - +
      Type PDB code for protein or upload file in PDB format sample input filesType PDB code for protein or upload file in PDB or mmCIF format sample input files
      diff --git a/test/frontend/test_submit.py b/test/frontend/test_submit.py index 189972a..aa7a4eb 100644 --- a/test/frontend/test_submit.py +++ b/test/frontend/test_submit.py @@ -2,6 +2,7 @@ import saliweb.test import tempfile import os +import gzip import re # Import the multifoxs frontend with mocks @@ -9,6 +10,63 @@ '../../frontend') +def make_test_pdb(pdbf, compressed=False): + def write(fh): + fh.write( + "REMARK\n" + "ATOM 2 CA ALA C 1 26.711 14.576 5.091\n") + + if compressed: + with gzip.open(pdbf, 'wt') as fh: + write(fh) + else: + with open(pdbf, 'w') as fh: + write(fh) + + +def make_test_mmcif(fname, compressed=False): + def write(fh): + fh.write(""" +loop_ +_atom_site.group_PDB +_atom_site.type_symbol +_atom_site.label_atom_id +_atom_site.label_alt_id +_atom_site.label_comp_id +_atom_site.label_asym_id +_atom_site.auth_asym_id +_atom_site.label_seq_id +_atom_site.auth_seq_id +_atom_site.pdbx_PDB_ins_code +_atom_site.Cartn_x +_atom_site.Cartn_y +_atom_site.Cartn_z +_atom_site.occupancy +_atom_site.B_iso_or_equiv +_atom_site.label_entity_id +_atom_site.id +_atom_site.pdbx_PDB_model_num +ATOM N N . ALA A C 1 1 ? 27.932 14.488 4.257 1.000 23.91 1 1 1 +ATOM N N . ALA B D 1 1 ? 27.932 14.488 4.257 1.000 23.91 1 2 1 +""") + + if compressed: + with gzip.open(fname, 'wt') as fh: + write(fh) + else: + with open(fname, 'w') as fh: + write(fh) + + +def make_test_profile(saxsf): + with open(saxsf, 'w') as fh: + fh.write("# sample profile\n" + "garbage\n" + "more garbage, ignored\n" + "0.1 -0.5\n" + "0.00000 9656627.00000000 2027.89172363\n") + + class Tests(saliweb.test.TestCase): """Check submit page""" @@ -27,8 +85,9 @@ def test_submit_page_pdb_no_atoms(self): '/job', data={'pdbfile': open(pdbfile, 'rb'), 'modelsnumber': "5", "units": "unknown"}) self.assertEqual(rv.status_code, 400) - self.assertIn(b'PDB file contains no ATOM or HETATM records', - rv.data) + self.assertIn( + b'PDB file test.pdb contains no ATOM or HETATM records', + rv.data) def test_submit_page_bad_profile(self): """Test submit page with invalid profile""" @@ -38,9 +97,7 @@ def test_submit_page_bad_profile(self): multifoxs.app.config['DIRECTORIES_INCOMING'] = incoming c = multifoxs.app.test_client() pdbfile = os.path.join(tmpdir, 'test.pdb') - with open(pdbfile, 'w') as fh: - fh.write( - "ATOM 2 CA ALA 1 26.711 14.576 5.091\n") + make_test_pdb(pdbfile) saxsf = os.path.join(tmpdir, 'test.profile') with open(saxsf, 'w') as fh: fh.write("garbage\n") @@ -52,8 +109,8 @@ def test_submit_page_bad_profile(self): self.assertEqual(rv.status_code, 400) self.assertIn(b'Invalid profile uploaded', rv.data) - def test_submit_page(self): - """Test submit page""" + def test_submit_page_pdb(self): + """Test submit page, PDB format""" with tempfile.TemporaryDirectory() as tmpdir: incoming = os.path.join(tmpdir, 'incoming') os.mkdir(incoming) @@ -61,17 +118,9 @@ def test_submit_page(self): c = multifoxs.app.test_client() pdbf = os.path.join(tmpdir, 'test.pdb') - with open(pdbf, 'w') as fh: - fh.write( - "REMARK\n" - "ATOM 2 CA ALA 1 26.711 14.576 5.091\n") + make_test_pdb(pdbf) saxsf = os.path.join(tmpdir, 'test.profile') - with open(saxsf, 'w') as fh: - fh.write("# sample profile\n" - "garbage\n" - "more garbage, ignored\n" - "0.1 -0.5\n" - "0.00000 9656627.00000000 2027.89172363\n") + make_test_profile(saxsf) emptyf = os.path.join(tmpdir, 'emptyf') with open(emptyf, 'w') as fh: pass @@ -98,7 +147,8 @@ def test_submit_page(self): 'hingefile': open(linkf, 'rb'), 'jobname': 'foobar'} rv = c.post('/job', data=data) self.assertEqual(rv.status_code, 400) - self.assertIn(b'please specify PDB code or upload file', rv.data) + self.assertIn(b'please specify PDB code or upload PDB/mmCIF file', + rv.data) # Missing SAXS file data = {'modelsnumber': '100', 'units': 'unknown', @@ -148,6 +198,93 @@ def test_submit_page(self): re.MULTILINE | re.DOTALL) self.assertRegex(rv.data, r) + def test_submit_page_mmcif(self): + """Test submit page, mmCIF format""" + with tempfile.TemporaryDirectory() as tmpdir: + incoming = os.path.join(tmpdir, 'incoming') + os.mkdir(incoming) + multifoxs.app.config['DIRECTORIES_INCOMING'] = incoming + c = multifoxs.app.test_client() + + pdbf = os.path.join(tmpdir, 'test.cif') + make_test_mmcif(pdbf) + saxsf = os.path.join(tmpdir, 'test.profile') + make_test_profile(saxsf) + linkf = os.path.join(tmpdir, 'test.linkers') + with open(linkf, 'w') as fh: + fh.write("189 A\n") + + data = {'modelsnumber': '100', 'units': 'unknown', + 'pdbfile': open(pdbf, 'rb'), 'saxsfile': open(saxsf, 'rb'), + 'hingefile': open(linkf, 'rb'), 'jobname': 'foobar'} + rv = c.post('/job', data=data) + self.assertEqual(rv.status_code, 200) + r = re.compile(b'Your job foobar has been submitted.*' + b'Results will be found at', + re.MULTILINE | re.DOTALL) + self.assertRegex(rv.data, r) + + def test_submit_pdb_code_pdb(self): + """Test submit with a PDB code (PDB format)""" + with tempfile.TemporaryDirectory() as tmpdir: + incoming = os.path.join(tmpdir, 'incoming') + os.mkdir(incoming) + pdb_root = os.path.join(tmpdir, 'pdb') + os.mkdir(pdb_root) + multifoxs.app.config['DIRECTORIES_INCOMING'] = incoming + multifoxs.app.config['PDB_ROOT'] = pdb_root + c = multifoxs.app.test_client() + + os.mkdir(os.path.join(pdb_root, 'xy')) + pdbf = os.path.join(pdb_root, 'xy', 'pdb1xyz.ent.gz') + make_test_pdb(pdbf, compressed=True) + saxsf = os.path.join(tmpdir, 'test.profile') + make_test_profile(saxsf) + linkf = os.path.join(tmpdir, 'test.linkers') + with open(linkf, 'w') as fh: + fh.write("189 A\n") + + data = {'modelsnumber': '100', 'units': 'unknown', + 'pdbcode': '1xyz:C', 'saxsfile': open(saxsf, 'rb'), + 'hingefile': open(linkf, 'rb'), 'jobname': 'foobar'} + rv = c.post('/job', data=data) + self.assertEqual(rv.status_code, 200) + r = re.compile(b'Your job foobar has been submitted.*' + b'Results will be found at', + re.MULTILINE | re.DOTALL) + self.assertRegex(rv.data, r) + + def test_submit_pdb_code_mmcif(self): + """Test submit with a PDB code (mmCIF format)""" + with tempfile.TemporaryDirectory() as tmpdir: + incoming = os.path.join(tmpdir, 'incoming') + os.mkdir(incoming) + pdb_root = os.path.join(tmpdir, 'pdb') + os.mkdir(pdb_root) + multifoxs.app.config['DIRECTORIES_INCOMING'] = incoming + multifoxs.app.config['PDB_ROOT'] = pdb_root + multifoxs.app.config['MMCIF_ROOT'] = pdb_root + c = multifoxs.app.test_client() + + os.mkdir(os.path.join(pdb_root, 'xy')) + pdbf = os.path.join(pdb_root, 'xy', '1xyz.cif.gz') + make_test_mmcif(pdbf, compressed=True) + saxsf = os.path.join(tmpdir, 'test.profile') + make_test_profile(saxsf) + linkf = os.path.join(tmpdir, 'test.linkers') + with open(linkf, 'w') as fh: + fh.write("189 A\n") + + data = {'modelsnumber': '100', 'units': 'unknown', + 'pdbcode': '1xyz:C', 'saxsfile': open(saxsf, 'rb'), + 'hingefile': open(linkf, 'rb'), 'jobname': 'foobar'} + rv = c.post('/job', data=data) + self.assertEqual(rv.status_code, 200) + r = re.compile(b'Your job foobar has been submitted.*' + b'Results will be found at', + re.MULTILINE | re.DOTALL) + self.assertRegex(rv.data, r) + if __name__ == '__main__': unittest.main()