def test_get_highest_int(): # iterable expected, but not a str with pytest.raises(TypeError): RuleBurner._get_highest_int('123') assert RuleBurner._get_highest_int((1, 2, 3)) == 3 assert RuleBurner._get_highest_int(('1', 2, '3')) == 3 assert RuleBurner._get_highest_int((1, '2', 'a')) == 2
def test_handle_result(): tuple_raw = ( Chem.MolFromSmiles('[H][O][C](=[O])[C]([H])([O][P](=[O])([O][H])[O][H])[C]([H])([H])[H]'), Chem.MolFromSmiles('[H][N]=[c]1[n][c]([O][H])[c]2[n][c]([H])[n]([C]3([H])[O][C]([H])([C]([H])([H])[O][P](=[O])([O][H])[O][P](=[O])([O][H])[O][H])[C]([H])([O][H])[C]3([H])[O][H])[c]2[n]1[H]') ) rb = RuleBurner() tuple_tuple_rdmol, tuple_tuple_failed = rb._standardize_results(tuple_tuple_rdmol=(tuple_raw,), with_hs=True, with_stereo=False) rdmols, inchikeys, inchis, smiles = rb._handle_results(list_list_rdmol=tuple_tuple_rdmol) # Check number products assert len(inchikeys) == len(inchis) == len(smiles) == 1 # Only one set of result assert len(inchikeys[0]) == len(inchis[0]) == len(smiles[0]) == 2 # 2 products # Check Inchikeys assert inchikeys[0][0] == 'CSZRNWHGZPKNKY-UHFFFAOYSA-N' assert inchikeys[0][1] == 'QGWNDRXFNXRZMB-UHFFFAOYSA-N' # Check Inchis assert inchis[0][0] == 'InChI=1S/C3H7O6P/c1-2(3(4)5)9-10(6,7)8/h2H,1H3,(H,4,5)(H2,6,7,8)' assert inchis[0][1] == 'InChI=1S/C10H15N5O11P2/c11-10-13-7-4(8(18)14-10)12-2-15(7)9-6(17)5(16)3(25-9)1-24-28(22,23)26-27(19,20)21/h2-3,5-6,9,16-17H,1H2,(H,22,23)(H2,19,20,21)(H3,11,13,14,18)' # Check SMILES #1 assert smiles[0][0] == '[H]OC(=O)C([H])(OP(=O)(O[H])O[H])C([H])([H])[H]' rdmol = Chem.MolFromSmiles(smiles[0][0]) rdmol = Chem.AddHs(rdmol) assert Chem.MolToSmiles(rdmol, allHsExplicit=True) == '[H][O][C](=[O])[C]([H])([O][P](=[O])([O][H])[O][H])[C]([H])([H])[H]' # Check SMILES #2 assert smiles[0][1] == '[H]N=c1nc(O[H])c2nc([H])n(C3([H])OC([H])(C([H])([H])OP(=O)(O[H])OP(=O)(O[H])O[H])C([H])(O[H])C3([H])O[H])c2n1[H]' rdmol = Chem.MolFromSmiles(smiles[0][1]) rdmol = Chem.AddHs(rdmol) assert Chem.MolToSmiles(rdmol, allHsExplicit=True) == '[H][N]=[c]1[n][c]([O][H])[c]2[n][c]([H])[n]([C]3([H])[O][C]([H])([C]([H])([H])[O][P](=[O])([O][H])[O][P](=[O])([O][H])[O][H])[C]([H])([O][H])[C]3([H])[O][H])[c]2[n]1[H]'
def test_standardize_chemical_3(): # Data rb = RuleBurner() wrong_smiles = '[H]OC(=O)C([H])([H])C([H])([H])C([H])(N=C(O[H])C([H])([H])C([H])([H])C([H])(N=C(O[H])C([H])(OP(=O)(O[H])OC([H])([H])C([H])(O[H])C([H])(O[H])C([H])(O[H])C([H])([H])n1c2nc(=O)nc(O[H])c-2c([H])c2c([H])c([H])c(OP(=O)(OC([H])([H])C(C([H])([H])[H])(C([H])([H])[H])C([H])(O[H])C(=NC([H])([H])C([H])([H])C(=NC([H])([H])C([H])([H])SC(=O)C([H])([H])C([H])([H])C([H])([H])C([H])(C(=C([H])[H])C([H])([H])[H])C([H])([H])C(=O)O[H])O[H])O[H])OP(=O)(O[H])OC([H])([H])C3([H])OC([H])(n4[c]([H])n([H])[c]5[c](N([H])[H])[n][c]([H])[n][c]54)C([H])(O[H])C3([H])OP(=O)(O[H])O[H])c([H])c21)C([H])([H])[H])C(=O)O[H])C(=O)O[H]' # Test wrong_mol = Chem.MolFromSmiles(wrong_smiles, sanitize=False) with pytest.raises(Exception): rb._standardize_chemical(wrong_mol)
def test_task_fire(mocker): # raises RuleFireError with pytest.raises(RuleFireError): RuleBurner._task_fire('dummy', 'dummy', with_hs=False, with_stereo=False) # goes well mol = Chem.MolFromInchi(substrate_inchi) rule = AllChem.ReactionFromSmarts(reaction_smarts) mocker.patch.object(RuleBurner, '_handle_results') RuleBurner._task_fire(rule, mol, with_hs=False, with_stereo=False)
def test_standardize_results_1(): tuple_tuple_raw = (( Chem.MolFromSmiles('[H][O][C](=[O])[C]([H])([O][P](=[O])([O][H])[O][H])[C]([H])([H])[H]'), Chem.MolFromSmiles('[H][N]=[c]1[n][c]([O][H])[c]2[n][c]([H])[n]([C]3([H])[O][C]([H])([C]([H])([H])[O][P](=[O])([O][H])[O][P](=[O])([O][H])[O][H])[C]([H])([O][H])[C]3([H])[O][H])[c]2[n]1[H]') ),( Chem.MolFromInchi('InChI=1S/C5H6N5O/c6-5-9-3-2(4(11)10-5)7-1-8-3/h1H,9H2,(H,7,8)(H2,6,10,11)') )) rb = RuleBurner() tuple_tuple_rdmol, tuple_index_failed = rb._standardize_results(tuple_tuple_raw, with_hs=True, with_stereo=False) assert len(tuple_tuple_rdmol) == 1 assert tuple_index_failed == [1]
def test_compute(smarts, inchi, expected_inchi): # Things that are expected to return results rb = RuleBurner(with_hs=True) rb.insert_rsmarts([reaction_smarts]) rb.insert_inchi([substrate_inchi]) rb.compute(rule_mol='*') result = [x for x in rb.compute(rule_mol='*')][0] assert all(x in result for x in ['rule_id', 'substrate_id', 'product_list', 'product_inchikeys', 'product_inchis', 'product_smiles']), result assert result['product_inchis'][0].sort() == expected_inchi.sort()
def test_init_rdkit_rule(): rb = RuleBurner() with pytest.raises(RuleConversionError): rb._init_rdkit_rule('DUMB') rb._init_rdkit_rule('[C:1]>>[C:1]-[C]') rb._init_rdkit_rule(reaction_smarts)
def test_gen_chemicals(valid_inputs): rb = RuleBurner() with pytest.raises(TypeError): rb._gen_chemicals() # value is mandatory rb.insert_inchi(valid_inputs) list(rb._gen_chemicals(None)) ans = list(rb._gen_chemicals(list(valid_inputs.keys()))) assert len(ans) == len(valid_inputs)
def test_compute_wrong_chemical(caplog): # Wrong chemical depiction should be caught and logged by RuleBurner with caplog.at_level(logging.ERROR): rb = RuleBurner() rb.insert_rsmarts([reaction_smarts]) rb.insert_inchi(['DUMMY']) result = [x for x in rb.compute(rule_mol='*')] assert "CHEM-CONVERSION-ERROR" in caplog.text, (result, caplog.text)
def test_compute_timeout(caplog): # Timeout should be logged rsmarts = "([#6&v4:1](=[#6&v4:2](-[#6&v4:3])-[#6&v4:4](-[#6&v4:5](-[#6&v4:6](=[#6&v4:7](-[#6&v4:8])-[#6&v4:9](-[#6&v4:10](-[#6&v4:11](=[#6&v4:12](-[#6&v4:13])-[#6&v4:14](-[#6&v4:15](-[#6&v4:16](=[#6&v4:17](-[#6&v4:18])-[#6&v4:19](-[#6&v4:20](-[#6&v4:21](=[#6&v4:22](-[#6&v4:23])-[#6&v4:24](-[#6&v4:25](-[#6&v4:26](=[#6&v4:27](-[#6&v4:28])-[#6&v4:29](-[#6&v4:30](-[#6&v4:31](=[#6&v4:32])-[#1&v1:33])(-[#1&v1:34])-[#1&v1:35])(-[#1&v1:36])-[#1&v1:37])-[#1&v1:38])(-[#1&v1:39])-[#1&v1:40])(-[#1&v1:41])-[#1&v1:42])-[#1&v1:43])(-[#1&v1:44])-[#1&v1:45])(-[#1&v1:46])-[#1&v1:47])-[#1&v1:48])(-[#1&v1:49])-[#1&v1:50])(-[#1&v1:51])-[#1&v1:52])-[#1&v1:53])(-[#1&v1:54])-[#1&v1:55])(-[#1&v1:56])-[#1&v1:57])-[#1&v1:58])(-[#1&v1:59])-[#1&v1:60])(-[#1&v1:61])-[#1&v1:62])(-[#6&v4:63](-[#6&v4:64](-[#6&v4:65](=[#6&v4:66](-[#6&v4:67](-[#6&v4:68](-[#6&v4:69](=[#6&v4:70](-[#6&v4:71](-[#6&v4:72](-[#6&v4:73](=[#6&v4:74](-[#6&v4:75](-[#6&v4:76](-[#6&v4:77](=[#6&v4:78](-[#6&v4:79](-[#6&v4:80](-[#6&v4:81](=[#6&v4:82](-[#6&v4:83](-[#8&v2:84]-[#15&v5:85])(-[#1&v1:86])-[#1&v1:87])-[#1&v1:88])-[#6&v4:89])(-[#1&v1:90])-[#1&v1:91])(-[#1&v1:92])-[#1&v1:93])-[#1&v1:94])-[#6&v4:95])(-[#1&v1:96])-[#1&v1:97])(-[#1&v1:98])-[#1&v1:99])-[#1&v1:100])-[#6&v4:101])(-[#1&v1:102])-[#1&v1:103])(-[#1&v1:104])-[#1&v1:105])-[#1&v1:106])-[#6&v4:107])(-[#1&v1:108])-[#1&v1:109])(-[#1&v1:110])-[#1&v1:111])-[#1&v1:112])-[#6&v4:113])(-[#1&v1:114])-[#1&v1:115])(-[#1&v1:116])-[#1&v1:117])-[#1&v1:118])>>([#15&v5:85]-[#8&v2:84]-[#6&v4:83](-[#6&v4:29](-[#6&v4:27](=[#6&v4:26](-[#1&v1:38])-[#1&v1])-[#6&v4:28])(-[#1&v1:36])-[#1&v1:37])(-[#1&v1:86])-[#1&v1:87].[#6&v4:30](-[#6&v4:31](=[#6&v4:32])-[#1&v1:33])(-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(=[#8&v2])-[#8&v2]-[#1&v1])=[#8&v2])(-[#1&v1:34])-[#1&v1:35].[#6&v4:82](=[#6&v4:81](-[#6&v4:89])-[#6&v4:80](-[#6&v4:79](-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(=[#8&v2])-[#8&v2]-[#1&v1])=[#8&v2])(-[#1&v1:92])-[#1&v1:93])(-[#1&v1:90])-[#1&v1:91])(-[#1&v1:88])-[#1&v1].[#6&v4:78](=[#6&v4:77](-[#6&v4:95])-[#6&v4:76](-[#6&v4:75](-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(=[#8&v2])-[#8&v2]-[#1&v1])=[#8&v2])(-[#1&v1:98])-[#1&v1:99])(-[#1&v1:96])-[#1&v1:97])(-[#1&v1:94])-[#1&v1].[#6&v4:74](=[#6&v4:73](-[#6&v4:101])-[#6&v4:72](-[#6&v4:71](-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(=[#8&v2])-[#8&v2]-[#1&v1])=[#8&v2])(-[#1&v1:104])-[#1&v1:105])(-[#1&v1:102])-[#1&v1:103])(-[#1&v1:100])-[#1&v1].[#6&v4:70](=[#6&v4:69](-[#6&v4:107])-[#6&v4:68](-[#6&v4:67](-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(=[#8&v2])-[#8&v2]-[#1&v1])=[#8&v2])(-[#1&v1:110])-[#1&v1:111])(-[#1&v1:108])-[#1&v1:109])(-[#1&v1:106])-[#1&v1].[#6&v4:66](=[#6&v4:65](-[#6&v4:113])-[#6&v4:64](-[#6&v4:63](-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(=[#8&v2])-[#8&v2]-[#1&v1])=[#8&v2])(-[#1&v1:116])-[#1&v1:117])(-[#1&v1:114])-[#1&v1:115])(-[#1&v1:112])-[#1&v1].[#6&v4:1](=[#6&v4:2](-[#6&v4:3])-[#6&v4:4](-[#6&v4:5](-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(=[#8&v2])-[#8&v2]-[#1&v1])=[#8&v2])(-[#1&v1:59])-[#1&v1:60])(-[#1&v1:61])-[#1&v1:62])(-[#1&v1:118])-[#1&v1].[#6&v4:6](=[#6&v4:7](-[#6&v4:8])-[#6&v4:9](-[#6&v4:10](-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(=[#8&v2])-[#8&v2]-[#1&v1])=[#8&v2])(-[#1&v1:54])-[#1&v1:55])(-[#1&v1:56])-[#1&v1:57])(-[#1&v1:58])-[#1&v1].[#6&v4:11](=[#6&v4:12](-[#6&v4:13])-[#6&v4:14](-[#6&v4:15](-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(=[#8&v2])-[#8&v2]-[#1&v1])=[#8&v2])(-[#1&v1:49])-[#1&v1:50])(-[#1&v1:51])-[#1&v1:52])(-[#1&v1:53])-[#1&v1].[#6&v4:16](=[#6&v4:17](-[#6&v4:18])-[#6&v4:19](-[#6&v4:20](-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(=[#8&v2])-[#8&v2]-[#1&v1])=[#8&v2])(-[#1&v1:44])-[#1&v1:45])(-[#1&v1:46])-[#1&v1:47])(-[#1&v1:48])-[#1&v1].[#6&v4:21](=[#6&v4:22](-[#6&v4:23])-[#6&v4:24](-[#6&v4:25](-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(-[#8&v2]-[#15&v5](-[#8&v2]-[#1&v1])(=[#8&v2])-[#8&v2]-[#1&v1])=[#8&v2])(-[#1&v1:39])-[#1&v1:40])(-[#1&v1:41])-[#1&v1:42])(-[#1&v1:43])-[#1&v1])" inchi = "InChI=1S/C70H116O7P2/c1-57(2)29-16-30-58(3)31-17-32-59(4)33-18-34-60(5)35-19-36-61(6)37-20-38-62(7)39-21-40-63(8)41-22-42-64(9)43-23-44-65(10)45-24-46-66(11)47-25-48-67(12)49-26-50-68(13)51-27-52-69(14)53-28-54-70(15)55-56-76-79(74,75)77-78(71,72)73/h29,31,33,35,37,39,41,43,45,47,49,51,53,55H,16-28,30,32,34,36,38,40,42,44,46,48,50,52,54,56H2,1-15H3,(H,74,75)(H2,71,72,73)" with caplog.at_level(logging.WARNING): rb = RuleBurner(with_hs=True) rb.insert_rsmarts([rsmarts]) rb.insert_inchi([inchi]) result = [x for x in rb.compute(rule_mol='*', timeout=0.1)] assert "timed-out" in caplog.text, (result, caplog.text)
def test_standardize_chemical_1(): # Without Hs rb = RuleBurner() rdmol = Chem.MolFromSmiles('[H][O][C](=[O])[C]([H])([O][H])[C]([H])([H])[H]') rdmol_std_1 = rb._standardize_chemical(rdmol, with_hs=False, with_stereo=False) assert Chem.MolToSmiles(rdmol_std_1) == 'CC(O)C(=O)O' # With Hs rb = RuleBurner() rdmol_std_2 = rb._standardize_chemical(rdmol, with_hs=True, with_stereo=False) assert Chem.MolToSmiles(rdmol_std_2, allHsExplicit=True) == '[H][O][C](=[O])[C]([H])([O][H])[C]([H])([H])[H]'
def __infile_mode(args): """ Execution mode to be used when rules and chemicals are provided in CSV files. """ import csv with open(args.rfile, 'r') as ifh: reader = csv.DictReader(ifh, delimiter='\t') rsmarts = {row['rule_id']: row['rule_smarts'] for row in reader} with open(args.cfile, 'r') as ifh: reader = csv.DictReader(ifh, delimiter='\t') inchis = {row['chem_id']: row['inchi'] for row in reader} r = RuleBurner(with_hs=args.with_hs, with_stereo=args.with_stereo) r.insert_rsmarts(rsmarts) r.insert_inchi(inchis) return [x for x in r.compute(rule_mol='*', timeout=args.fire_timeout)]
def __inline_mode(args): """Execution mode to be used when a single rule and a single chemical are provided through CLI. """ r = RuleBurner(with_hs=args.with_hs, with_stereo=args.with_stereo) if args.rid: r.insert_rsmarts({args.rid: args.rsmarts}) else: r.insert_rsmarts([args.rsmarts]) if args.cid: r.insert_inchi({args.cid: args.inchi}) else: r.insert_inchi([args.inchi]) return [x for x in r.compute(rule_mol='*', timeout=args.fire_timeout)]
def _create_db_from_retrorules_v1_0_5(path_retrosmarts_tsv, db_path, with_hs, with_stereo): def helper_metabolite(store, cid, smiles): if cid not in store: store[cid] = smiles elif store[cid] != smiles: logger.warning( f"Metabolite {cid} is suspiciously associated to distinct SMILES. " f"Only the first one will be considered: {store[cid]} and {smiles}" ) rules = {} metabolites = {} # both substrate and products results = set( ) # all "obvious" results that directly come from the reaction database (no promiscuity) pgroup = {} # index of a solution rid vs. cid # Load all valuable data in-memory # NB: is this file small enough that we do not need to chunk it? with open(path_retrosmarts_tsv) as hdl: for row in csv.DictReader(hdl, delimiter='\t'): # each row is a reaction rule automatically generated from a known metabolic reaction # each row contains 1 rule... # NB: rule identifier may be duplicated over several rows but must match the same RSMARTS rid = row["# Rule_ID"] rsmarts = row["Rule_SMARTS"] diameter = int(row["Diameter"]) direction = row["Rule_usage"] # -1, 0, 1 ==> retro, both, forward if direction == "both": direction = 0 elif direction == "retro": direction = -1 elif direction == "forward": direction = 1 else: raise ValueError( f"Found an unexpected direction for rule {rid}: {direction}" ) if rid not in rules: # warning: keys must match database schema rules[rid] = { 'rd_rule': rsmarts, 'diameter': diameter, 'direction': direction } else: assert rules[rid]['rd_rule'] == rsmarts, f"UNEXPECTED: rule {rid} from {path_retrosmarts_tsv} has " \ f"mismatching RSMARTS: {rules[rid]} and {rsmarts}" # ... and 1 substrate ... sid = row["Substrate_ID"] helper_metabolite(metabolites, sid, row["Substrate_SMILES"]) # ... and N coproducts smiles_list = row["Product_SMILES"].split('.') tmp_results = [] pid_stoichio = {} for idx, pid in enumerate(row["Product_IDs"].split('.')): if pid in pid_stoichio: pid_stoichio[pid] += 1 else: pid_stoichio[pid] = 1 helper_metabolite(metabolites, pid, smiles_list[idx]) tmp_results.append( (rid, sid, pid) ) # TODO: bug, there can be several solutions for the same rule x mol couple # each row is also a distinct solution of "1 rule applied on 1 metabolite"... but there can be many # especially at low diameters! if (rid, sid) not in pgroup: pgroup[(rid, sid)] = -1 else: pgroup[(rid, sid)] -= 1 # record the results of this row (1 by distinct product) for rid, sid, pid in tmp_results: results.add( (sid, rid, pid, pid_stoichio[pid], pgroup[(rid, sid)])) # Create the database o = RuleBurner(db_path=db_path, with_hs=with_hs, with_stereo=with_stereo) o.insert_rsmarts(rules) o.insert_smiles(metabolites) o.db.executemany("INSERT INTO results VALUES (?,?,?,?,?);", list(results)) o.create_indexes()
def test_standardize_chemical_2(): # Data violacein_smiles = 'OC1=NC(=C\\C1=C1/C(O)=NC2=CC=CC=C12)C1=CNC2=C1C=C(O)C=C2' violacein_mol = Chem.MolFromSmiles(violacein_smiles, sanitize=False) # Test simplest case rb = RuleBurner() std_mol_1 = rb._standardize_chemical(violacein_mol, with_hs=False, with_stereo=True) assert Chem.MolToSmiles(std_mol_1) == 'OC1=NC(c2c[nH]c3ccc(O)cc23)=C/C1=C1\\C(O)=Nc2ccccc21' # Test adding Hs rb = RuleBurner() std_mol_2 = rb._standardize_chemical(violacein_mol, with_hs=True, with_stereo=True) assert Chem.MolToSmiles(std_mol_2) == '[H]OC1=NC(c2c([H])n([H])c3c([H])c([H])c(O[H])c([H])c23)=C([H])/C1=C1\\C(O[H])=Nc2c([H])c([H])c([H])c([H])c21' # Test removing stereo rb = RuleBurner() std_mol_3 = rb._standardize_chemical(violacein_mol, with_hs=False, with_stereo=False) assert Chem.MolToSmiles(std_mol_3) == 'O=C1NC(c2c[nH]c3ccc(O)cc23)=CC1=C1C(=O)Nc2ccccc21' # Test adding Hs + removing stereo rb = RuleBurner() std_mol_4 = rb._standardize_chemical(violacein_mol, with_hs=True, with_stereo=False) assert Chem.MolToSmiles(std_mol_4) == '[H]Oc1c([H])c([H])c2c(c1[H])c(C1=C([H])C(=C3C(=O)N([H])c4c([H])c([H])c([H])c([H])c43)C(=O)N1[H])c([H])n2[H]'
def test_init_rdkit_mol_from_smiles(valid_smiles): rb = RuleBurner() with pytest.raises(ChemConversionError): rb._init_rdkit_mol_from_smiles('DUMB') rb._init_rdkit_mol_from_smiles(valid_smiles)
def test_gen_rules(valid_ids): rb = RuleBurner() rb.insert_rsmarts(reaction_smarts) with pytest.raises(TypeError): rb._gen_rules() # value is mandatory list(rb._gen_rules(valid_ids))
def test_init_1(): # Empty is OK rb = RuleBurner() # Empty is OK with pytest.raises(TypeError): rb.compute() # Missing argument rule_mol rb.compute(rule_mol=((), ()))
def test_rules(): # Rules to be considered rb = RuleBurner() rb.insert_rsmarts({'rid1': reaction_smarts, 'rid2': reaction_smarts}) assert sorted(rb.rules) == ['rid1', 'rid2']
def test_chemicals(): # Chemicals to be considered rb = RuleBurner() rb.insert_inchi({'cid1': substrate_inchi, 'cid2': substrate_inchi}) assert sorted(rb.chemicals) == ['cid1', 'cid2']
def test_init_2(): # Stereo support not implemented with pytest.raises(NotImplementedError): RuleBurner(with_stereo=True)