forked from enviPath/enviPy
[Feature] Generate Compounds by Molfile (#423)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch> Reviewed-on: enviPath/enviPy#423
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
from django.conf import settings as s
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from epdb.exceptions import InvalidSMILESException
|
||||
from epdb.exceptions import InvalidSMILESException, InvalidMolfileException
|
||||
from epdb.logic import PackageManager
|
||||
from epdb.models import Compound, User, CompoundStructure
|
||||
|
||||
@ -19,6 +19,10 @@ class CompoundTest(TestCase):
|
||||
cls.user = User.objects.get(username="anonymous")
|
||||
cls.package = PackageManager.create_package(cls.user, "Anon Test Package", "No Desc")
|
||||
|
||||
# A valid V2000 molfile for 4-Nitrobenzoic acid (O=C(O)C1=CC=C([N+](=O)[O-])C=C1)
|
||||
cls.VALID_MOLFILE = """\n Mrv2211 01012500002D\n\n 12 12 0 0 0 0 999 V2000\n 1.4289 -0.8250 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 0.7145 -0.4125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 0.0000 -0.8250 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 0.0000 -1.6500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 0.7145 -2.0625 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 1.4289 -1.6500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 0.7145 0.4125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 0.7145 1.2375 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0\n 1.4289 0.8250 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0\n 0.7145 -2.8875 0.0000 N 0 3 0 0 0 0 0 0 0 0 0 0\n 0.0000 -3.3000 0.0000 O 0 5 0 0 0 0 0 0 0 0 0 0\n 1.4289 -3.3000 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0\n 1 2 2 0 0 0 0\n 2 3 1 0 0 0 0\n 3 4 2 0 0 0 0\n 4 5 1 0 0 0 0\n 5 6 2 0 0 0 0\n 6 1 1 0 0 0 0\n 2 7 1 0 0 0 0\n 7 8 2 0 0 0 0\n 7 9 1 0 0 0 0\n 5 10 1 0 0 0 0\n 10 11 1 0 0 0 0\n 10 12 2 0 0 0 0\nM CHG 2 10 1 11 -1\nM END\n"""
|
||||
cls.INVALID_MOLFILE = "this is not a valid molfile"
|
||||
|
||||
def test_smoke(self):
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
@ -190,3 +194,153 @@ class CompoundTest(TestCase):
|
||||
|
||||
c1.set_default_structure(c2)
|
||||
self.assertNotEqual(default_structure, c2)
|
||||
|
||||
def test_create_structure_from_molfile(self):
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles="PLACEHOLDER",
|
||||
molfile=self.VALID_MOLFILE,
|
||||
name="Molfile Compound",
|
||||
description="Created from molfile",
|
||||
)
|
||||
|
||||
cs = CompoundStructure.create(
|
||||
compound=c,
|
||||
smiles="PLACEHOLDER",
|
||||
molfile=self.VALID_MOLFILE,
|
||||
name="Molfile Structure",
|
||||
description="Structure from molfile",
|
||||
)
|
||||
|
||||
# The SMILES must have been derived from the molfile, not the placeholder
|
||||
self.assertNotEqual(cs.smiles, "PLACEHOLDER")
|
||||
self.assertIsNotNone(cs.smiles)
|
||||
self.assertTrue(len(cs.smiles) > 0)
|
||||
|
||||
def test_create_structure_from_molfile_stores_molfile(self):
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles="c1c(C(=O)O)ccc([N+]([O-])=O)c1",
|
||||
name="Molfile Store Test",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
cs = c.default_structure
|
||||
|
||||
# O=C(O)C1=CC=C([N+](=O)[O-])C=C1 will be overwritten with
|
||||
# c1c(C(=O)O)ccc([N+]([O-])=O)c1 and molfile will be set
|
||||
# on the existing structure
|
||||
_ = CompoundStructure.create(
|
||||
compound=c,
|
||||
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||
molfile=self.VALID_MOLFILE,
|
||||
name="Structure with Molfile",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
# Fetch fresh from DB to confirm persistence
|
||||
cs_db = CompoundStructure.objects.get(pk=cs.pk)
|
||||
self.assertIsNotNone(cs_db.molfile)
|
||||
self.assertNotEqual(cs_db.molfile.strip(), "")
|
||||
|
||||
def test_create_structure_from_invalid_molfile_raises(self):
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||
name="Invalid Molfile Test",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
with self.assertRaises(InvalidMolfileException):
|
||||
CompoundStructure.create(
|
||||
compound=c,
|
||||
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||
molfile=self.INVALID_MOLFILE,
|
||||
name="Bad Structure",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
def test_molfile_takes_precedence_over_smiles(self):
|
||||
"""When both molfile and smiles are supplied, molfile must win."""
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||
name="Precedence Test",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
cs = CompoundStructure.create(
|
||||
compound=c,
|
||||
smiles="C", # intentionally wrong / different SMILES
|
||||
molfile=self.VALID_MOLFILE,
|
||||
name="Molfile Wins",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
# SMILES should be derived from molfile, not the supplied "C"
|
||||
self.assertNotEqual(cs.smiles, "C")
|
||||
|
||||
def test_empty_molfile_falls_back_to_smiles(self):
|
||||
"""An empty or whitespace-only molfile should be ignored and SMILES used instead."""
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||
name="Empty Molfile Test",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
cs = CompoundStructure.create(
|
||||
compound=c,
|
||||
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||
molfile=" ", # whitespace only – should be ignored
|
||||
name="Fallback to SMILES",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
self.assertEqual(cs.smiles, "O=C(O)C1=CC=C([N+](=O)[O-])C=C1")
|
||||
|
||||
def test_none_molfile_falls_back_to_smiles(self):
|
||||
"""None as molfile should be ignored and SMILES used instead."""
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||
name="None Molfile Test",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
cs = CompoundStructure.create(
|
||||
compound=c,
|
||||
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||
molfile=None,
|
||||
name="Fallback None Molfile",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
self.assertEqual(cs.smiles, "O=C(O)C1=CC=C([N+](=O)[O-])C=C1")
|
||||
|
||||
def test_molfile_deduplication(self):
|
||||
"""Creating a structure twice from the same molfile should return the existing object."""
|
||||
c = Compound.create(
|
||||
self.package,
|
||||
smiles="O=C(O)C1=CC=C([N+](=O)[O-])C=C1",
|
||||
name="Molfile Dedup Test",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
cs1 = CompoundStructure.create(
|
||||
compound=c,
|
||||
smiles="PLACEHOLDER",
|
||||
molfile=self.VALID_MOLFILE,
|
||||
name="Molfile Structure",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
cs2 = CompoundStructure.create(
|
||||
compound=c,
|
||||
smiles="PLACEHOLDER",
|
||||
molfile=self.VALID_MOLFILE,
|
||||
name="Molfile Structure",
|
||||
description="No Desc",
|
||||
)
|
||||
|
||||
self.assertEqual(cs1.pk, cs2.pk)
|
||||
|
||||
Reference in New Issue
Block a user