diff --git a/Code/GraphMol/Wrap/rough_test.py b/Code/GraphMol/Wrap/rough_test.py index 5375768ad..77bd4a2ce 100644 --- a/Code/GraphMol/Wrap/rough_test.py +++ b/Code/GraphMol/Wrap/rough_test.py @@ -8,16 +8,21 @@ it's intended to be shallow, but broad """ -import os, sys, tempfile, gzip, gc -import unittest, doctest -from datetime import datetime, timedelta -from rdkit import RDConfig, rdBase -from rdkit import DataStructs -from rdkit import Chem -import rdkit.Chem.rdDepictor -from rdkit.Chem import rdqueries +import doctest +import gc +import gzip +import logging +import os +import sys import tempfile -from rdkit import __version__ +import unittest +from contextlib import contextmanager +from datetime import datetime, timedelta +from io import StringIO + +import rdkit.Chem.rdDepictor +from rdkit import Chem, DataStructs, RDConfig, __version__, rdBase +from rdkit.Chem import rdqueries # Boost functions are NOT found by doctest, this "fixes" them # by adding the doctests to a fake module @@ -34,6 +39,42 @@ def ReplaceCore(*a, **kw): exec(code, TestReplaceCore.__dict__) +@contextmanager +def log_to_python(level=None): + """ + Temporarily redirect logging to Python streams, optionally + setting a specific log level. + """ + rdBase.LogToPythonLogger() + pylog = logging.getLogger("rdkit") + if level is not None: + original_level = pylog.level + pylog.setLevel(level) + + yield pylog + + if level is not None: + pylog.setLevel(original_level) + rdBase.LogToCppStreams() + + +@contextmanager +def capture_logging(level=None): + """ + Temporarily redirect logging to a Python StringIO, optionally + setting a specific log level. + """ + log_stream = StringIO() + stream_handler = logging.StreamHandler(stream=log_stream) + + with log_to_python(level) as pylog: + pylog.addHandler(stream_handler) + + yield log_stream + + pylog.removeHandler(stream_handler) + + def load_tests(loader, tests, ignore): tests.addTests(doctest.DocTestSuite(TestReplaceCore)) return tests @@ -227,9 +268,10 @@ class TestCase(unittest.TestCase): self.assertTrue([x.GetNoImplicit() for x in aList] == [0, 0, 0, 1]) self.assertTrue([x.GetNumExplicitHs() for x in aList] == [0, 0, 0, 2]) self.assertTrue([x.GetIsAromatic() for x in aList] == [1, 1, 0, 0]) - self.assertTrue([x.GetHybridization() for x in aList]==[Chem.HybridizationType.SP2,Chem.HybridizationType.SP2, - Chem.HybridizationType.SP3,Chem.HybridizationType.SP3], - [x.GetHybridization() for x in aList]) + self.assertTrue([x.GetHybridization() for x in aList] == [ + Chem.HybridizationType.SP2, Chem.HybridizationType.SP2, Chem.HybridizationType.SP3, + Chem.HybridizationType.SP3 + ], [x.GetHybridization() for x in aList]) def test8Bond(self): mol = Chem.MolFromSmiles('n1ccccc1CC(=O)O') @@ -1005,8 +1047,8 @@ class TestCase(unittest.TestCase): self.assertTrue(mol.HasProp("ID")) self.assertTrue(mol.GetProp("ID") == "Lig1") self.assertTrue(mol.HasProp("ANOTHER_PROPERTY")) - self.assertTrue(mol.GetProp("ANOTHER_PROPERTY") == - "No blank line before dollars\n" + self.assertTrue( + mol.GetProp("ANOTHER_PROPERTY") == "No blank line before dollars\n" "$$$$\n" "Structure1\n" "csChFnd70/05230312262D") @@ -1038,8 +1080,8 @@ class TestCase(unittest.TestCase): self.assertTrue(mol.HasProp("ID")) self.assertTrue(mol.GetProp("ID") == "Lig1") self.assertTrue(mol.HasProp("ANOTHER_PROPERTY")) - self.assertTrue(mol.GetProp("ANOTHER_PROPERTY") == - "No blank line before dollars\n" + self.assertTrue( + mol.GetProp("ANOTHER_PROPERTY") == "No blank line before dollars\n" "$$$$\n" "Structure1\n" "csChFnd70/05230312262D") @@ -2119,7 +2161,7 @@ CAS<~> self.assertEqual(len(ri.BondMembers(2)), 2) self.assertEqual(len(ri.BondMembers(0)), 1) self.assertEqual(ri.BondRingSizes(2), (4, 3)) - self.assertEqual(ri.BondRingSizes(0), (4,)) + self.assertEqual(ri.BondRingSizes(0), (4, )) self.assertEqual(ri.BondRingSizes(99), ()) self.assertTrue(ri.AreBondsInSameRing(1, 2)) self.assertTrue(ri.AreBondsInSameRing(2, 5)) @@ -2730,18 +2772,20 @@ CAS<~> def test64MoleculeCleanup(self): m = Chem.MolFromSmiles('CN(=O)=O', False) self.assertTrue(m) - self.assertTrue(m.GetAtomWithIdx(1).GetFormalCharge()==0 and - m.GetAtomWithIdx(2).GetFormalCharge()==0 and - m.GetAtomWithIdx(3).GetFormalCharge()==0) - self.assertTrue(m.GetBondBetweenAtoms(1,3).GetBondType()==Chem.BondType.DOUBLE and - m.GetBondBetweenAtoms(1,2).GetBondType()==Chem.BondType.DOUBLE ) + self.assertTrue( + m.GetAtomWithIdx(1).GetFormalCharge() == 0 and m.GetAtomWithIdx(2).GetFormalCharge() == 0 + and m.GetAtomWithIdx(3).GetFormalCharge() == 0) + self.assertTrue( + m.GetBondBetweenAtoms(1, 3).GetBondType() == Chem.BondType.DOUBLE + and m.GetBondBetweenAtoms(1, 2).GetBondType() == Chem.BondType.DOUBLE) Chem.Cleanup(m) m.UpdatePropertyCache() - self.assertTrue(m.GetAtomWithIdx(1).GetFormalCharge()==1 and - (m.GetAtomWithIdx(2).GetFormalCharge()==-1 or - m.GetAtomWithIdx(3).GetFormalCharge()==-1)) - self.assertTrue(m.GetBondBetweenAtoms(1,3).GetBondType()==Chem.BondType.SINGLE or - m.GetBondBetweenAtoms(1,2).GetBondType()==Chem.BondType.SINGLE ) + self.assertTrue( + m.GetAtomWithIdx(1).GetFormalCharge() == 1 and + (m.GetAtomWithIdx(2).GetFormalCharge() == -1 or m.GetAtomWithIdx(3).GetFormalCharge() == -1)) + self.assertTrue( + m.GetBondBetweenAtoms(1, 3).GetBondType() == Chem.BondType.SINGLE + or m.GetBondBetweenAtoms(1, 2).GetBondType() == Chem.BondType.SINGLE) def test65StreamSupplier(self): fileN = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data', @@ -3856,10 +3900,10 @@ CAS<~> self.assertFalse(resMolSuppl.GetIsEnumerated()) resMolSuppl.Enumerate() self.assertTrue(resMolSuppl.GetIsEnumerated()) - self.assertTrue((resMolSuppl[0].GetBondBetweenAtoms(0, 1).GetBondType() - != resMolSuppl[1].GetBondBetweenAtoms(0, 1).GetBondType()) - or (resMolSuppl[0].GetBondBetweenAtoms(9, 10).GetBondType() - != resMolSuppl[1].GetBondBetweenAtoms(9, 10).GetBondType())) + self.assertTrue( + (resMolSuppl[0].GetBondBetweenAtoms(0, 1).GetBondType() != resMolSuppl[1].GetBondBetweenAtoms( + 0, 1).GetBondType()) or (resMolSuppl[0].GetBondBetweenAtoms(9, 10).GetBondType() != + resMolSuppl[1].GetBondBetweenAtoms(9, 10).GetBondType())) resMolSuppl = Chem.ResonanceMolSupplier(mol, Chem.KEKULE_ALL) self.assertEqual(len(resMolSuppl), 8) @@ -3870,8 +3914,8 @@ CAS<~> self.assertEqual(len(bondTypeSet), 2) bondTypeDict = {} - resMolSuppl = Chem.ResonanceMolSupplier(mol, - Chem.ALLOW_INCOMPLETE_OCTETS + resMolSuppl = Chem.ResonanceMolSupplier( + mol, Chem.ALLOW_INCOMPLETE_OCTETS | Chem.UNCONSTRAINED_CATIONS | Chem.UNCONSTRAINED_ANIONS) self.assertEqual(len(resMolSuppl), 32) @@ -3884,8 +3928,8 @@ CAS<~> resMolSuppl.reset() cmpFormalChargeBondOrder(self, resMolSuppl[0], next(resMolSuppl)) - resMolSuppl = Chem.ResonanceMolSupplier(mol, - Chem.ALLOW_INCOMPLETE_OCTETS + resMolSuppl = Chem.ResonanceMolSupplier( + mol, Chem.ALLOW_INCOMPLETE_OCTETS | Chem.UNCONSTRAINED_CATIONS | Chem.UNCONSTRAINED_ANIONS, 10) self.assertEqual(len(resMolSuppl), 10) @@ -4805,6 +4849,7 @@ M END def testOldPropPickles(self): data = 'crdkit.Chem.rdchem\nMol\np0\n(S\'\\xef\\xbe\\xad\\xde\\x00\\x00\\x00\\x00\\x08\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00)\\x00\\x00\\x00-\\x00\\x00\\x00\\x80\\x01\\x06\\x00`\\x00\\x00\\x00\\x01\\x03\\x07\\x00`\\x00\\x00\\x00\\x02\\x01\\x06 4\\x00\\x00\\x00\\x01\\x01\\x04\\x06\\x00`\\x00\\x00\\x00\\x01\\x03\\x06\\x00(\\x00\\x00\\x00\\x03\\x04\\x08\\x00(\\x00\\x00\\x00\\x03\\x02\\x07\\x00h\\x00\\x00\\x00\\x03\\x02\\x01\\x06 4\\x00\\x00\\x00\\x02\\x01\\x04\\x06\\x00(\\x00\\x00\\x00\\x03\\x04\\x08\\x00(\\x00\\x00\\x00\\x03\\x02\\x07\\x00(\\x00\\x00\\x00\\x03\\x03\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06 4\\x00\\x00\\x00\\x01\\x01\\x04\\x08\\x00(\\x00\\x00\\x00\\x03\\x02\\x06@(\\x00\\x00\\x00\\x03\\x04\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06 4\\x00\\x00\\x00\\x01\\x01\\x04\\x06\\x00(\\x00\\x00\\x00\\x03\\x04\\x08\\x00(\\x00\\x00\\x00\\x03\\x02\\x07\\x00h\\x00\\x00\\x00\\x03\\x02\\x01\\x06 4\\x00\\x00\\x00\\x02\\x01\\x04\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06@(\\x00\\x00\\x00\\x03\\x04\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@h\\x00\\x00\\x00\\x03\\x03\\x01\\x06@(\\x00\\x00\\x00\\x03\\x04\\x06\\x00`\\x00\\x00\\x00\\x03\\x01\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x06\\x00`\\x00\\x00\\x00\\x02\\x02\\x0b\\x00\\x01\\x00\\x01\\x02\\x00\\x02\\x03\\x00\\x02\\x04\\x00\\x04\\x05(\\x02\\x04\\x06 \\x06\\x07\\x00\\x07\\x08\\x00\\x08\\t(\\x02\\x08\\n \\n\\x0b\\x00\\x0b\\x0c\\x00\\x0c\\r\\x00\\r\\x0e \\x0e\\x0fh\\x0c\\x0f\\x10h\\x0c\\x10\\x11h\\x0c\\x11\\x12h\\x0c\\x12\\x13h\\x0c\\x0c\\x14\\x00\\x14\\x15\\x00\\x15\\x16\\x00\\x16\\x17(\\x02\\x16\\x18 \\x18\\x19\\x00\\x19\\x1a\\x00\\x1a\\x1b\\x00\\x1b\\x1c\\x00\\x1c\\x1d\\x00\\x1d\\x1eh\\x0c\\x1e\\x1fh\\x0c\\x1f h\\x0c !h\\x0c!"h\\x0c\\x07#\\x00#$\\x00$%\\x00%&\\x00&\\\'\\x00\\\'(\\x00\\x15\\n\\x00"\\x19\\x00(#\\x00\\x13\\x0eh\\x0c"\\x1dh\\x0c\\x14\\x05\\x05\\x0b\\n\\x15\\x14\\x0c\\x06\\x0f\\x10\\x11\\x12\\x13\\x0e\\x06\\x1a\\x1b\\x1c\\x1d"\\x19\\x06\\x1e\\x1f !"\\x1d\\x06$%&\\\'(#\\x17\\x00\\x00\\x00\\x00\\x12\\x03\\x00\\x00\\x00\\x07\\x00\\x00\\x00numArom\\x01\\x02\\x00\\x00\\x00\\x0f\\x00\\x00\\x00_StereochemDone\\x01\\x01\\x00\\x00\\x00\\x03\\x00\\x00\\x00foo\\x00\\x03\\x00\\x00\\x00bar\\x13:\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x12\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x000\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x1d\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x001\\x04\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x15\\x00\\x00\\x00\\x12\\x00\\x00\\x00_ChiralityPossible\\x01\\x01\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPCode\\x00\\x01\\x00\\x00\\x00S\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x002\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x00\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x003\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x1a\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x004\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02"\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x005\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x1f\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x006\\x04\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x16\\x00\\x00\\x00\\x12\\x00\\x00\\x00_ChiralityPossible\\x01\\x01\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPCode\\x00\\x01\\x00\\x00\\x00S\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x007\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x1c\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x008\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02$\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x01\\x00\\x00\\x009\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02 \\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0010\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x13\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0011\\x04\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x18\\x00\\x00\\x00\\x12\\x00\\x00\\x00_ChiralityPossible\\x01\\x01\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPCode\\x00\\x01\\x00\\x00\\x00S\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0012\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02!\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0013\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x19\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0014\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x0f\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0015\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x0b\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0016\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x08\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0017\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x0b\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0018\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x0f\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0019\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x07\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0020\\x04\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x17\\x00\\x00\\x00\\x12\\x00\\x00\\x00_ChiralityPossible\\x01\\x01\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPCode\\x00\\x01\\x00\\x00\\x00S\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0021\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x1b\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0022\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02#\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0023\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x1e\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0024\\x04\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x14\\x00\\x00\\x00\\x12\\x00\\x00\\x00_ChiralityPossible\\x01\\x01\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPCode\\x00\\x01\\x00\\x00\\x00R\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0025\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x06\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0026\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x03\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0027\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x05\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0028\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x10\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0029\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x0c\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0030\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\t\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0031\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\n\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0032\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\r\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0033\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x11\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0034\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x0e\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0035\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x04\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0036\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x02\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0037\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x01\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0038\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x02\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0039\\x02\\x00\\x00\\x00\\x08\\x00\\x00\\x00_CIPRank\\x02\\x04\\x00\\x00\\x00\\x05\\x00\\x00\\x00myidx\\x00\\x02\\x00\\x00\\x0040\\x13\\x16\'\np1\ntp2\nRp3\n.' import pickle + # bonds were broken in v1 m2 = pickle.loads(data.encode("utf-8"), encoding='bytes') @@ -5215,25 +5260,25 @@ M END def testGetQueryType(self): query_a = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data', - 'query_A.mol') + 'query_A.mol') m = next(Chem.SDMolSupplier(query_a)) self.assertTrue(m.GetAtomWithIdx(6).HasQuery()) self.assertTrue(m.GetAtomWithIdx(6).GetQueryType() == "A") query_a_v3k = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data', - 'query_A.v3k.mol') + 'query_A.v3k.mol') m = next(Chem.SDMolSupplier(query_a_v3k)) self.assertTrue(m.GetAtomWithIdx(6).HasQuery()) self.assertTrue(m.GetAtomWithIdx(6).GetQueryType() == "A") query_q = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data', - 'query_Q.mol') + 'query_Q.mol') m = next(Chem.SDMolSupplier(query_q)) self.assertTrue(m.GetAtomWithIdx(6).HasQuery()) self.assertTrue(m.GetAtomWithIdx(6).GetQueryType() == "Q") query_q_v3k = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'FileParsers', 'test_data', - 'query_Q.v3k.mol') + 'query_Q.v3k.mol') m = next(Chem.SDMolSupplier(query_q_v3k)) self.assertTrue(m.GetAtomWithIdx(6).HasQuery()) self.assertTrue(m.GetAtomWithIdx(6).GetQueryType() == "Q") @@ -5245,7 +5290,6 @@ M END self.assertTrue(m.GetAtomWithIdx(0).HasQuery()) self.assertTrue(m.GetAtomWithIdx(0).GetQueryType() == "") - def testBondSetQuery(self): pat = Chem.MolFromSmarts('[#6]=[#6]') mol = Chem.MolFromSmiles("c1ccccc1") @@ -6495,7 +6539,6 @@ M END p = Chem.MolzipParams() p.enforceValenceRules = False c = Chem.molzip(a, b, p) - def testContextManagers(self): from rdkit import RDLogger @@ -6646,8 +6689,8 @@ CAS<~> self.assertEqual(m.GetSubstructMatches(q), ()) def testGithub4144(self): - ''' the underlying problem with #4144 was that the - includeRings argument could not be passed to ClearComputedProps() + ''' the underlying problem with #4144 was that the + includeRings argument could not be passed to ClearComputedProps() from Python. Make sure that's fixed ''' m = Chem.MolFromSmiles('c1ccccc1') @@ -6735,7 +6778,7 @@ CAS<~> def testgithub4992(self): if not hasattr(Chem, "Chem.MultithreadedSDMolSupplier"): return - + good1 = Chem.MolFromSmiles('C') #good1.SetProp('molname', 'good1') good2 = Chem.MolFromSmiles('CC') @@ -6744,21 +6787,21 @@ CAS<~> #good3.SetProp('molname', 'good3') bad = Chem.MolFromSmiles('CN(C)(C)C', sanitize=False) #bad.SetProp('molname', 'bad') - + with Chem.SDWriter("good1_good2_good3.sdf") as w: w.write(good1) w.write(good2) w.write(good3) w.write(good1) w.write(good2) - w.write(good3) - + w.write(good3) + with Chem.SDWriter("good1_good2_good3_bad.sdf") as w: w.write(good1) w.write(good2) w.write(good3) w.write(bad) - + with Chem.SDWriter("good1_good2_bad_good3.sdf") as w: w.write(good1) w.write(good2) @@ -6782,16 +6825,47 @@ CAS<~> counts = [] for i, s in enumerate((Chem.SDMolSupplier, Chem.MultithreadedSDMolSupplier)): - for j, f in enumerate(('good1_good2_good3.sdf', - 'good1_good2_bad_good3.sdf', - 'good1_good2_bad_good3.sdf', - 'bad_good1_good2_good3.sdf')): + for j, f in enumerate(('good1_good2_good3.sdf', 'good1_good2_bad_good3.sdf', + 'good1_good2_bad_good3.sdf', 'bad_good1_good2_good3.sdf')): print(f'---------------\n{s.__name__} {f}') - if i == 0: + if i == 0: counts.append(read_mols(s, f)) - else: + else: self.assertEqual(counts[j], read_mols(s, f)) - + + def test_blocklogs(self): + with capture_logging(logging.INFO) as log_stream: + + # Logging is known to be problematic with static linked libs + # so let's skip the test if we can't see the expected error + # log before we block logging. + Chem.MolFromSmiles('garbage_0') + if 'garbage_0' not in log_stream.getvalue(): + self.skipTest('cannot fetch log msgs') + else: + log_stream.truncate(0) + + with rdBase.BlockLogs(): + Chem.MolFromSmiles('garbage_1') + self.assertNotIn('garbage_1', log_stream.getvalue()) + + Chem.MolFromSmiles('garbage_2') + self.assertIn('garbage_2', log_stream.getvalue()) + log_stream.truncate(0) + + block = rdBase.BlockLogs() + self.assertIsNotNone(block) + + Chem.MolFromSmiles('garbage_3') + self.assertNotIn('garbage_3', log_stream.getvalue()) + + del block + + Chem.MolFromSmiles('garbage_4') + self.assertIn('garbage_4', log_stream.getvalue()) + log_stream.truncate(0) + + if __name__ == '__main__': if "RDTESTCASE" in os.environ: suite = unittest.TestSuite() diff --git a/Code/RDBoost/Wrap/RDBase.cpp b/Code/RDBoost/Wrap/RDBase.cpp index 04fe4f609..15fe90565 100644 --- a/Code/RDBoost/Wrap/RDBase.cpp +++ b/Code/RDBoost/Wrap/RDBase.cpp @@ -29,12 +29,11 @@ namespace logging = boost::logging; std::string _version() { return "$Id$"; } - // std::ostream wrapper around Python's stderr stream struct PyErrStream : std::ostream, std::streambuf { static thread_local std::string buffer; - PyErrStream(): std::ostream(this) { + PyErrStream() : std::ostream(this) { // All done! } @@ -48,8 +47,7 @@ struct PyErrStream : std::ostream, std::streambuf { PyGILStateHolder h; PySys_WriteStderr("%s\n", buffer.c_str()); buffer.clear(); - } - else { + } else { buffer += c; } } @@ -60,7 +58,7 @@ struct PyLogStream : std::ostream, std::streambuf { static thread_local std::string buffer; PyObject *logfn = nullptr; - PyLogStream(std::string level): std::ostream(this) { + PyLogStream(std::string level) : std::ostream(this) { PyObject *module = PyImport_ImportModule("logging"); PyObject *logger = nullptr; @@ -100,8 +98,7 @@ struct PyLogStream : std::ostream, std::streambuf { PyObject *result = PyObject_CallFunction(logfn, "s", buffer.c_str()); Py_XDECREF(result); buffer.clear(); - } - else { + } else { buffer += c; } } @@ -111,17 +108,16 @@ struct PyLogStream : std::ostream, std::streambuf { thread_local std::string PyErrStream::buffer; thread_local std::string PyLogStream::buffer; - void LogToPythonLogger() { static PyLogStream debug("debug"); static PyLogStream info("info"); static PyLogStream warning("warning"); static PyLogStream error("error"); - rdDebugLog = std::make_shared(&debug); - rdInfoLog = std::make_shared(&info); + rdDebugLog = std::make_shared(&debug); + rdInfoLog = std::make_shared(&info); rdWarningLog = std::make_shared(&warning); - rdErrorLog = std::make_shared(&error); + rdErrorLog = std::make_shared(&error); } void LogToPythonStderr() { @@ -130,17 +126,17 @@ void LogToPythonStderr() { static PyErrStream warning; static PyErrStream error; - rdDebugLog = std::make_shared(&debug); - rdInfoLog = std::make_shared(&info); + rdDebugLog = std::make_shared(&debug); + rdInfoLog = std::make_shared(&info); rdWarningLog = std::make_shared(&warning); - rdErrorLog = std::make_shared(&error); + rdErrorLog = std::make_shared(&error); } void WrapLogs() { - static PyErrStream debug; //("RDKit DEBUG: "); - static PyErrStream error; //("RDKit ERROR: "); - static PyErrStream warning; //("RDKit WARNING: "); - static PyErrStream info; //("RDKit INFO: "); + static PyErrStream debug; //("RDKit DEBUG: "); + static PyErrStream error; //("RDKit ERROR: "); + static PyErrStream warning; //("RDKit WARNING: "); + static PyErrStream info; //("RDKit INFO: "); if (!rdDebugLog || !rdInfoLog || !rdErrorLog || !rdWarningLog) { RDLog::InitLogs(); @@ -152,18 +148,11 @@ void WrapLogs() { rdErrorLog->SetTee(error); } +void EnableLog(std::string spec) { logging::enable_logs(spec); } -void EnableLog(std::string spec) { - logging::enable_logs(spec); -} +void DisableLog(std::string spec) { logging::disable_logs(spec); } -void DisableLog(std::string spec) { - logging::disable_logs(spec); -} - -std::string LogStatus() { - return logging::log_status(); -} +std::string LogStatus() { return logging::log_status(); } void AttachFileToLog(std::string spec, std::string filename, int delay = 100) { (void)spec; @@ -182,7 +171,6 @@ void AttachFileToLog(std::string spec, std::string filename, int delay = 100) { #endif } - void LogDebugMsg(const std::string &msg) { // NOGIL nogil; BOOST_LOG(rdDebugLog) << msg << std::endl; @@ -215,6 +203,24 @@ void LogMessage(std::string spec, std::string msg) { } } +class BlockLogs : public boost::noncopyable { + public: + BlockLogs() : m_log_setter{new RDLog::LogStateSetter} {} + ~BlockLogs() = default; + + BlockLogs *enter() { return this; } + + void exit(python::object exc_type, python::object exc_val, + python::object traceback) { + RDUNUSED_PARAM(exc_type); + RDUNUSED_PARAM(exc_val); + RDUNUSED_PARAM(traceback); + m_log_setter.reset(); + } + + private: + std::unique_ptr m_log_setter; +}; namespace { struct python_streambuf_wrapper { @@ -223,7 +229,7 @@ struct python_streambuf_wrapper { static void wrap() { using namespace boost::python; class_("streambuf", no_init) - .def(init( + .def(init( (arg("python_file_obj"), arg("buffer_size") = 0), "documentation")[with_custodian_and_ward_postcall<0, 2>()]); } @@ -236,7 +242,7 @@ struct python_ostream_wrapper { using namespace boost::python; class_("std_ostream", no_init); class_>("ostream", no_init) - .def(init( + .def(init( (arg("python_file_obj"), arg("buffer_size") = 0))); } }; @@ -244,7 +250,6 @@ struct python_ostream_wrapper { void seedRNG(unsigned int seed) { std::srand(seed); } } // namespace - BOOST_PYTHON_MODULE(rdBase) { python::scope().attr("__doc__") = "Module containing basic definitions for wrapped C++ code\n" @@ -296,14 +301,12 @@ BOOST_PYTHON_MODULE(rdBase) { python::def("LogDebugMsg", LogDebugMsg, "Log a message to the RDKit debug logs"); - python::def("LogInfoMsg", LogInfoMsg, - "Log a message to the RDKit info logs"); + python::def("LogInfoMsg", LogInfoMsg, "Log a message to the RDKit info logs"); python::def("LogWarningMsg", LogWarningMsg, "Log a message to the RDKit warning logs"); python::def("LogErrorMsg", LogErrorMsg, "Log a message to the RDKit error logs"); - python::def("LogMessage", LogMessage, - "Log a message to any rdApp.* log"); + python::def("LogMessage", LogMessage, "Log a message to any rdApp.* log"); python::def("AttachFileToLog", AttachFileToLog, "Causes the log to write to a file", @@ -319,8 +322,11 @@ BOOST_PYTHON_MODULE(rdBase) { python_streambuf_wrapper::wrap(); python_ostream_wrapper::wrap(); - python::class_( + python::class_( "BlockLogs", - "Temporarily block logs from outputting while this instance is in " - "scope."); + "Temporarily block logs from outputting while this instance is in scope.", + python::init<>()) + .def("__enter__", &BlockLogs::enter, + python::return_internal_reference<>()) + .def("__exit__", &BlockLogs::exit); } diff --git a/Code/RDGeneral/RDLog.cpp b/Code/RDGeneral/RDLog.cpp index ed693ca03..a5a0682e1 100644 --- a/Code/RDGeneral/RDLog.cpp +++ b/Code/RDGeneral/RDLog.cpp @@ -24,31 +24,35 @@ RDLogger rdWarningLog = nullptr; RDLogger rdStatusLog = nullptr; namespace RDLog { +namespace { +const std::vector allLogs = {&rdAppLog, &rdDebugLog, + &rdInfoLog, &rdErrorLog, + &rdWarningLog, &rdStatusLog}; +} + LogStateSetter::LogStateSetter() { - std::vector allLogs = {rdAppLog, rdDebugLog, rdInfoLog, - rdErrorLog, rdWarningLog, rdStatusLog}; for (auto i = 0u; i < allLogs.size(); ++i) { - if (allLogs[i] && allLogs[i]->df_enabled) { + if (*allLogs[i] && (*allLogs[i])->df_enabled) { d_origState |= 1 << i; - allLogs[i]->df_enabled = false; + (*allLogs[i])->df_enabled = false; } } } LogStateSetter::LogStateSetter(RDLoggerList toEnable) : LogStateSetter() { - for (auto &log : toEnable) { - if (log) { - log->df_enabled = true; + for (auto i = 0u; i < allLogs.size(); ++i) { + if (*allLogs[i] && std::find(toEnable.begin(), toEnable.end(), + *allLogs[i]) != toEnable.end()) { + d_origState ^= 1 << i; + (*allLogs[i])->df_enabled = true; } } } LogStateSetter::~LogStateSetter() { - std::vector allLogs = {rdAppLog, rdDebugLog, rdInfoLog, - rdErrorLog, rdWarningLog, rdStatusLog}; for (auto i = 0u; i < allLogs.size(); ++i) { - if (allLogs[i]) { - allLogs[i]->df_enabled = d_origState & 1 << i; + if (*allLogs[i]) { + (*allLogs[i])->df_enabled ^= d_origState >> i & 1; } } } diff --git a/Code/RDGeneral/RDLog.h b/Code/RDGeneral/RDLog.h index 4084cf89e..17ca6b729 100644 --- a/Code/RDGeneral/RDLog.h +++ b/Code/RDGeneral/RDLog.h @@ -117,7 +117,7 @@ namespace RDLog { RDKIT_RDGENERAL_EXPORT void InitLogs(); using RDLoggerList = std::vector; -class RDKIT_RDGENERAL_EXPORT LogStateSetter { +class RDKIT_RDGENERAL_EXPORT LogStateSetter : public boost::noncopyable { public: //! enables only the logs in the list, the current state will be restored when //! this object is destroyed diff --git a/Code/RDGeneral/catch_logs.cpp b/Code/RDGeneral/catch_logs.cpp index 27ecc2d3c..9a7c50e2a 100644 --- a/Code/RDGeneral/catch_logs.cpp +++ b/Code/RDGeneral/catch_logs.cpp @@ -62,4 +62,52 @@ TEST_CASE("LogStateSetter") { strm->ClearTee(); } } +} + +TEST_CASE("GitHub Issue #5172", "[bug][logging]") { + std::stringstream err_ostrm; + std::stringstream warn_ostrm; + rdErrorLog->df_enabled = true; + rdWarningLog->df_enabled = true; + rdErrorLog->SetTee(err_ostrm); + rdWarningLog->SetTee(warn_ostrm); + + { + RDLog::LogStateSetter disabler; + + BOOST_LOG(rdErrorLog) << "should be silent" << std::endl; + auto txt = err_ostrm.str(); + CHECK(txt.find("should") == std::string::npos); + + BOOST_LOG(rdWarningLog) << "should be silent" << std::endl; + txt = warn_ostrm.str(); + CHECK(txt.find("should") == std::string::npos); + + { + // The second setter overrides the first one: + RDLog::LogStateSetter disabler2({rdWarningLog}); + + BOOST_LOG(rdErrorLog) << "should be silent" << std::endl; + txt = err_ostrm.str(); + CHECK(txt.find("should") == std::string::npos); + + BOOST_LOG(rdWarningLog) << "should not be silent" << std::endl; + txt = warn_ostrm.str(); + CHECK(txt.find("should") != std::string::npos); + warn_ostrm.clear(); + } + } + + // Both setters are destroyed, and we revert to initial state + + BOOST_LOG(rdErrorLog) << "should not be silent" << std::endl; + auto txt = err_ostrm.str(); + CHECK(txt.find("should") != std::string::npos); + + BOOST_LOG(rdWarningLog) << "should not be silent" << std::endl; + txt = warn_ostrm.str(); + CHECK(txt.find("should") != std::string::npos); + + rdErrorLog->ClearTee(); + rdWarningLog->ClearTee(); } \ No newline at end of file diff --git a/Contrib/FreeWilson/test/test_freewilson.py b/Contrib/FreeWilson/test/test_freewilson.py index 2f1948fbc..c2e54eaa1 100644 --- a/Contrib/FreeWilson/test/test_freewilson.py +++ b/Contrib/FreeWilson/test/test_freewilson.py @@ -5,59 +5,61 @@ import logging PATH = os.path.join(os.path.dirname(fw.__file__), 'data') assert os.path.exists(PATH), PATH + def test_chembl(): - logging.getLogger().setLevel(logging.INFO) - smilesfile = os.path.join(PATH, "CHEMBL2321810.smi") - scaffoldfile = os.path.join(PATH, "CHEMBL2321810_scaffold.mol") - csvfile = os.path.join(PATH, "CHEMBL2321810_act.csv") - assert os.path.exists(smilesfile) - mols = [] - for line in open(smilesfile): - smiles, name = line.strip().split() - m = Chem.MolFromSmiles(smiles) - m.SetProp("_Name", name) - mols.append(m) + logging.getLogger().setLevel(logging.INFO) + smilesfile = os.path.join(PATH, "CHEMBL2321810.smi") + scaffoldfile = os.path.join(PATH, "CHEMBL2321810_scaffold.mol") + csvfile = os.path.join(PATH, "CHEMBL2321810_act.csv") + assert os.path.exists(smilesfile) + mols = [] + for line in open(smilesfile): + smiles, name = line.strip().split() + m = Chem.MolFromSmiles(smiles) + m.SetProp("_Name", name) + mols.append(m) - scaffold = Chem.MolFromMolBlock(open(scaffoldfile).read()) - data = {k:float(v) for k,v in list(csv.reader(open(csvfile)))[1:]} + scaffold = Chem.MolFromMolBlock(open(scaffoldfile).read()) + data = {k: float(v) for k, v in list(csv.reader(open(csvfile)))[1:]} - scores = [data[m.GetProp("_Name")] for m in mols] - assert mols and len(mols) == len(scores) + scores = [data[m.GetProp("_Name")] for m in mols] + assert mols and len(mols) == len(scores) - blocker = rdBase.BlockLogs() + with rdBase.BlockLogs(): free = fw.FWDecompose(scaffold, mols, scores) - # let's make sure the r squared is decent - assert free.r2 > 0.8 + # let's make sure the r squared is decent + assert free.r2 > 0.8 - # assert we get something - preds = list(fw.FWBuild(free)) - assert len(preds) + # assert we get something + preds = list(fw.FWBuild(free)) + assert len(preds) - # check to see that the prediction filters work - preds2 = list(fw.FWBuild(free, pred_filter=lambda x: x > 8)) - assert len(preds2) - assert len([p for p in preds if p.prediction > 8]) == len(list(preds2)) + # check to see that the prediction filters work + preds2 = list(fw.FWBuild(free, pred_filter=lambda x: x > 8)) + assert len(preds2) + assert len([p for p in preds if p.prediction > 8]) == len(list(preds2)) + # check to see that the R groups are output in order, i.e. R10 after R3 + s = io.StringIO() + fw.predictions_to_csv(s, free, preds2) + assert s.getvalue() - # check to see that the R groups are output in order, i.e. R10 after R3 - s = io.StringIO() - fw.predictions_to_csv(s, free, preds2) - assert s.getvalue() - - s2 = io.StringIO(s.getvalue()) - for i,row in enumerate(csv.reader(s2)): - if i == 0: - assert row == ['smiles','prediction','Core_smiles','R1_smiles','R3_smiles','R10_smiles'] - assert i>0 + s2 = io.StringIO(s.getvalue()) + for i, row in enumerate(csv.reader(s2)): + if i == 0: + assert row == ['smiles', 'prediction', 'Core_smiles', 'R1_smiles', 'R3_smiles', 'R10_smiles'] + assert i > 0 def test_multicore(): - # test that we can add rgroups for later cores and not throw an exception - scaffolds = [Chem.MolFromSmiles("c1ccccc1[*].NC=O"), Chem.MolFromSmiles("C1CCCCC1")] - mols = [Chem.MolFromSmiles(x) for x in ['c1ccccc1CC2CNC2C(=O)N', 'Cc1ccccc1CC2CNC2C(=O)N', 'Cc1ccccc1CC2CNCC(=O)NC2', 'C3c1ccccc1CC2CNC2C(=O)N3', 'C1CCCCC1F', 'ClC1CCCCC1F']] - decomp=fw.FWDecompose(scaffolds, mols, [1,2,3,4,5,6]) - s = io.StringIO() - fw.predictions_to_csv(s, decomp, fw.FWBuild(decomp)) - - - + # test that we can add rgroups for later cores and not throw an exception + scaffolds = [Chem.MolFromSmiles("c1ccccc1[*].NC=O"), Chem.MolFromSmiles("C1CCCCC1")] + mols = [ + Chem.MolFromSmiles(x) for x in [ + 'c1ccccc1CC2CNC2C(=O)N', 'Cc1ccccc1CC2CNC2C(=O)N', 'Cc1ccccc1CC2CNCC(=O)NC2', + 'C3c1ccccc1CC2CNC2C(=O)N3', 'C1CCCCC1F', 'ClC1CCCCC1F' + ] + ] + decomp = fw.FWDecompose(scaffolds, mols, [1, 2, 3, 4, 5, 6]) + s = io.StringIO() + fw.predictions_to_csv(s, decomp, fw.FWBuild(decomp)) diff --git a/rdkit/Chem/Draw/__init__.py b/rdkit/Chem/Draw/__init__.py index b7ba25760..ece080930 100644 --- a/rdkit/Chem/Draw/__init__.py +++ b/rdkit/Chem/Draw/__init__.py @@ -446,8 +446,8 @@ def _okToKekulizeMol(mol, kekulize): def _moltoimg(mol, sz, highlights, legend, returnPNG=False, drawOptions=None, **kwargs): try: - blocker = rdBase.BlockLogs() - mol.GetAtomWithIdx(0).GetExplicitValence() + with rdBase.BlockLogs(): + mol.GetAtomWithIdx(0).GetExplicitValence() except RuntimeError: mol.UpdatePropertyCache(False) @@ -455,8 +455,8 @@ def _moltoimg(mol, sz, highlights, legend, returnPNG=False, drawOptions=None, ** wedge = kwargs.get('wedgeBonds', True) try: - blocker = rdBase.BlockLogs() - mc = rdMolDraw2D.PrepareMolForDrawing(mol, kekulize=kekulize, wedgeBonds=wedge) + with rdBase.BlockLogs(): + mc = rdMolDraw2D.PrepareMolForDrawing(mol, kekulize=kekulize, wedgeBonds=wedge) except ValueError: # <- can happen on a kekulization failure mc = rdMolDraw2D.PrepareMolForDrawing(mol, kekulize=False, wedgeBonds=wedge) if not hasattr(rdMolDraw2D, 'MolDraw2DCairo'): @@ -489,16 +489,16 @@ def _moltoimg(mol, sz, highlights, legend, returnPNG=False, drawOptions=None, ** def _moltoSVG(mol, sz, highlights, legend, kekulize, drawOptions=None, **kwargs): try: - blocker = rdBase.BlockLogs() - mol.GetAtomWithIdx(0).GetExplicitValence() + with rdBase.BlockLogs(): + mol.GetAtomWithIdx(0).GetExplicitValence() except RuntimeError: mol.UpdatePropertyCache(False) kekulize = _okToKekulizeMol(mol, kekulize) try: - blocker = rdBase.BlockLogs() - mc = rdMolDraw2D.PrepareMolForDrawing(mol, kekulize=kekulize) + with rdBase.BlockLogs(): + mc = rdMolDraw2D.PrepareMolForDrawing(mol, kekulize=kekulize) except ValueError: # <- can happen on a kekulization failure mc = rdMolDraw2D.PrepareMolForDrawing(mol, kekulize=False) d2d = rdMolDraw2D.MolDraw2DSVG(sz[0], sz[1])