From 9c9df5cc398b0983131be3e53163ab89debac089 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Jun 2026 08:18:34 +0000 Subject: [PATCH 1/9] Initial plan From ac0692852e6b4fa9fdb61afb8b1326ad63225a5d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Jun 2026 08:28:20 +0000 Subject: [PATCH 2/9] Extend RDKit conversions: add smiles/inchi to inchi/inchikey --- .../libs/converters/compute/RDKit.py | 47 ++++++++++++++++++- tests/test_rdkit.py | 10 ++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/MSMetaEnhancer/libs/converters/compute/RDKit.py b/MSMetaEnhancer/libs/converters/compute/RDKit.py index 58c74dc..f8bf866 100644 --- a/MSMetaEnhancer/libs/converters/compute/RDKit.py +++ b/MSMetaEnhancer/libs/converters/compute/RDKit.py @@ -1,7 +1,7 @@ import re from rdkit.Chem.Descriptors import ExactMolWt from rdkit.Chem import MolFromSmiles, MolToSmiles -from rdkit.Chem.inchi import MolFromInchi +from rdkit.Chem.inchi import MolFromInchi, MolToInchi, InchiToInchiKey from rdkit.Chem.rdMolDescriptors import CalcMolFormula from rdkit.Chem import Atom @@ -23,6 +23,10 @@ def __init__(self): ("isomeric_smiles", "mw", "from_smiles"), ("canonical_smiles", "formula", "smiles_to_formula"), ("isomeric_smiles", "formula", "smiles_to_formula"), + ("canonical_smiles", "inchi", "smiles_to_inchi"), + ("isomeric_smiles", "inchi", "smiles_to_inchi"), + ("canonical_smiles", "inchikey", "smiles_to_inchikey"), + ("isomeric_smiles", "inchikey", "smiles_to_inchikey"), ] self.create_top_level_conversion_methods(conversions, asynch=False) @@ -106,3 +110,44 @@ def inchi_to_formula(self, inchi: str) -> dict: return {"formula": ""} formula = CalcMolFormula(mol) return {"formula": formula} + + def smiles_to_inchi(self, smiles: str) -> dict: + """ + Compute InChI from SMILES. + + :param smiles: given SMILES + :return: computed InChI + """ + mol = MolFromSmiles(smiles) + if mol is None: + return {"inchi": ""} + inchi = MolToInchi(mol) + return {"inchi": inchi} + + def smiles_to_inchikey(self, smiles: str) -> dict: + """ + Compute InChIKey from SMILES. + + :param smiles: given SMILES + :return: computed InChIKey + """ + mol = MolFromSmiles(smiles) + if mol is None: + return {"inchikey": ""} + inchi = MolToInchi(mol) + if not inchi: + return {"inchikey": ""} + inchikey = InchiToInchiKey(inchi) + return {"inchikey": inchikey} + + def inchi_to_inchikey(self, inchi: str) -> dict: + """ + Compute InChIKey from InChI. + + :param inchi: given InChI + :return: computed InChIKey + """ + if not inchi: + return {"inchikey": ""} + inchikey = InchiToInchiKey(inchi) + return {"inchikey": inchikey} diff --git a/tests/test_rdkit.py b/tests/test_rdkit.py index 74c8cf5..a6eea5b 100644 --- a/tests/test_rdkit.py +++ b/tests/test_rdkit.py @@ -6,6 +6,9 @@ INCHI = "InChI=1S/C19H28O2/c1-18-9-7-13(20)11-12(18)3-4-14-15-5-6-17(21)19(15,2)10-8-16(14)18/h11,14-17,21H,3-10H2,1-2H3/t14-,15-,16-,17-,18-,19-/m0/s1" CANONICAL_SMILES = "CC12CCC(=O)C=C1CCC1C2CCC2(C)C(O)CCC12" ISOMERIC_SMILES = "C[C@]12CC[C@H]3[C@@H](CCC4=CC(=O)CC[C@@]43C)[C@@H]1CC[C@@H]2O" +CANONICAL_INCHI = "InChI=1S/C19H28O2/c1-18-9-7-13(20)11-12(18)3-4-14-15-5-6-17(21)19(15,2)10-8-16(14)18/h11,14-17,21H,3-10H2,1-2H3" +INCHIKEY = "MUMGGOZAMZWBJJ-DYKIIFRCSA-N" +CANONICAL_INCHIKEY = "MUMGGOZAMZWBJJ-UHFFFAOYSA-N" @pytest.mark.parametrize( @@ -23,6 +26,13 @@ ["canonical_smiles_to_formula", CANONICAL_SMILES, {"formula": "C19H28O2"}], ["isomeric_smiles_to_formula", ISOMERIC_SMILES, {"formula": "C19H28O2"}], ["inchi_to_formula", INCHI, {"formula": "C19H28O2"}], + ["smiles_to_inchi", CANONICAL_SMILES, {"inchi": CANONICAL_INCHI}], + ["canonical_smiles_to_inchi", CANONICAL_SMILES, {"inchi": CANONICAL_INCHI}], + ["isomeric_smiles_to_inchi", ISOMERIC_SMILES, {"inchi": INCHI}], + ["smiles_to_inchikey", CANONICAL_SMILES, {"inchikey": CANONICAL_INCHIKEY}], + ["canonical_smiles_to_inchikey", CANONICAL_SMILES, {"inchikey": CANONICAL_INCHIKEY}], + ["isomeric_smiles_to_inchikey", ISOMERIC_SMILES, {"inchikey": INCHIKEY}], + ["inchi_to_inchikey", INCHI, {"inchikey": INCHIKEY}], ], ) def test_convert_methods(method, input, expected): From deb3eff1591eaa1c9ecefd34babbe55b1aba5133 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Jun 2026 09:08:25 +0000 Subject: [PATCH 3/9] Update changelog for RDKit InChI/InChIKey conversions --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a2f682..ab85897 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.5.1] - 2026-05-07 ### Added - Added high-level functions also for canonical smiles [#178](https://github.com/RECETOX/MSMetaEnhancer/pull/178) +- Added `smiles_to_inchi`, `smiles_to_inchikey`, and `inchi_to_inchikey` conversions to the RDKit converter [#179](https://github.com/RECETOX/MSMetaEnhancer/pull/179) ## [0.5.0] - 2026-03-10 From f137483e64b91b21b46393746036bb37e23b0ce3 Mon Sep 17 00:00:00 2001 From: Helge Hecht Date: Tue, 2 Jun 2026 09:13:12 +0000 Subject: [PATCH 4/9] Update CHANGELOG with new RDKit conversions --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab85897..2457f48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased +### Added +- Added `smiles_to_inchi`, `smiles_to_inchikey`, and `inchi_to_inchikey` conversions to the RDKit converter [#179](https://github.com/RECETOX/MSMetaEnhancer/pull/179) + ## [0.5.1] - 2026-05-07 ### Added - Added high-level functions also for canonical smiles [#178](https://github.com/RECETOX/MSMetaEnhancer/pull/178) -- Added `smiles_to_inchi`, `smiles_to_inchikey`, and `inchi_to_inchikey` conversions to the RDKit converter [#179](https://github.com/RECETOX/MSMetaEnhancer/pull/179) ## [0.5.0] - 2026-03-10 From 2878e7d9c077dbcadfefc6ab06a153aa10cbedfc Mon Sep 17 00:00:00 2001 From: hechth Date: Fri, 5 Jun 2026 14:41:50 +0200 Subject: [PATCH 5/9] fix CTS unavailability --- galaxy/generate_options.py | 17 ++++++++++++++--- tests/test_CTS.py | 5 ++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/galaxy/generate_options.py b/galaxy/generate_options.py index fe8d2ca..bb1d0ef 100644 --- a/galaxy/generate_options.py +++ b/galaxy/generate_options.py @@ -4,20 +4,31 @@ # this add to path the home dir, so it can be called from anywhere sys.path.append(os.path.split(sys.path[0])[0]) +from MSMetaEnhancer.libs.converters.web import CTS, CIR, IDSM, PubChem, BridgeDb +from MSMetaEnhancer.libs.converters.compute import RDKit from MSMetaEnhancer.libs.utils.ConverterBuilder import ConverterBuilder + from MSMetaEnhancer.libs.converters.web import __all__ as web_converters from MSMetaEnhancer.libs.converters.compute import __all__ as compute_converters def generate_options(): + ConverterBuilder.register([CTS, CIR, IDSM, PubChem, BridgeDb, RDKit]) + jobs = [] converters = web_converters + compute_converters - built_converters, built_web_converters = ConverterBuilder().build_converters( + + builder = ConverterBuilder() + builder.validate_converters(converters) + built_compute_converters, built_web_converters = builder.build_converters( None, converters ) - for converter in built_converters: - jobs += built_converters[converter].get_conversion_functions() + for converter in built_compute_converters.values(): + jobs += converter.get_conversion_functions() + + for converter in built_web_converters.values(): + jobs += converter.get_conversion_functions() for job in jobs: print( diff --git a/tests/test_CTS.py b/tests/test_CTS.py index d4d4e07..9d55c4c 100644 --- a/tests/test_CTS.py +++ b/tests/test_CTS.py @@ -8,7 +8,10 @@ @pytest.mark.dependency() def test_service_available(): - asyncio.run(wrap_with_session(CTS, "casno_to_inchikey", ["7783-89-3"])) + try: + asyncio.run(wrap_with_session(CTS, "casno_to_inchikey", ["7783-89-3"])) + except: + assert False @pytest.mark.dependency(depends=["test_service_available"]) From db827842ce0369b478793e8968f7c280e29245d8 Mon Sep 17 00:00:00 2001 From: hechth Date: Fri, 5 Jun 2026 14:44:09 +0200 Subject: [PATCH 6/9] linting --- galaxy/generate_options.py | 4 ++-- tests/test_CTS.py | 3 ++- tests/test_rdkit.py | 6 +++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/galaxy/generate_options.py b/galaxy/generate_options.py index bb1d0ef..dbed8d6 100644 --- a/galaxy/generate_options.py +++ b/galaxy/generate_options.py @@ -14,10 +14,10 @@ def generate_options(): ConverterBuilder.register([CTS, CIR, IDSM, PubChem, BridgeDb, RDKit]) - + jobs = [] converters = web_converters + compute_converters - + builder = ConverterBuilder() builder.validate_converters(converters) built_compute_converters, built_web_converters = builder.build_converters( diff --git a/tests/test_CTS.py b/tests/test_CTS.py index 9d55c4c..0f4ccfa 100644 --- a/tests/test_CTS.py +++ b/tests/test_CTS.py @@ -3,6 +3,7 @@ import pytest from MSMetaEnhancer.libs.converters.web import CTS +from MSMetaEnhancer.libs.utils.Errors import UnknownResponse from tests.utils import wrap_with_session @@ -10,7 +11,7 @@ def test_service_available(): try: asyncio.run(wrap_with_session(CTS, "casno_to_inchikey", ["7783-89-3"])) - except: + except UnknownResponse: assert False diff --git a/tests/test_rdkit.py b/tests/test_rdkit.py index a6eea5b..0b04257 100644 --- a/tests/test_rdkit.py +++ b/tests/test_rdkit.py @@ -30,7 +30,11 @@ ["canonical_smiles_to_inchi", CANONICAL_SMILES, {"inchi": CANONICAL_INCHI}], ["isomeric_smiles_to_inchi", ISOMERIC_SMILES, {"inchi": INCHI}], ["smiles_to_inchikey", CANONICAL_SMILES, {"inchikey": CANONICAL_INCHIKEY}], - ["canonical_smiles_to_inchikey", CANONICAL_SMILES, {"inchikey": CANONICAL_INCHIKEY}], + [ + "canonical_smiles_to_inchikey", + CANONICAL_SMILES, + {"inchikey": CANONICAL_INCHIKEY}, + ], ["isomeric_smiles_to_inchikey", ISOMERIC_SMILES, {"inchikey": INCHIKEY}], ["inchi_to_inchikey", INCHI, {"inchikey": INCHIKEY}], ], From 2ae2390cf5369ee06ed2ac1433e050a3c69785a1 Mon Sep 17 00:00:00 2001 From: hechth Date: Fri, 5 Jun 2026 14:48:12 +0200 Subject: [PATCH 7/9] changed to xfail --- tests/test_CTS.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_CTS.py b/tests/test_CTS.py index 0f4ccfa..aaee6d1 100644 --- a/tests/test_CTS.py +++ b/tests/test_CTS.py @@ -8,11 +8,10 @@ @pytest.mark.dependency() +@pytest.mark.xfail(raises=UnknownResponse) def test_service_available(): - try: - asyncio.run(wrap_with_session(CTS, "casno_to_inchikey", ["7783-89-3"])) - except UnknownResponse: - assert False + asyncio.run(wrap_with_session(CTS, "casno_to_inchikey", ["7783-89-3"])) + @pytest.mark.dependency(depends=["test_service_available"]) From 782caa201fc1debb184c1a46336fa2a67f74636c Mon Sep 17 00:00:00 2001 From: hechth Date: Fri, 5 Jun 2026 14:52:06 +0200 Subject: [PATCH 8/9] added xfail and another dependency --- tests/test_PubChem.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_PubChem.py b/tests/test_PubChem.py index bb9bbd5..bcf8b86 100644 --- a/tests/test_PubChem.py +++ b/tests/test_PubChem.py @@ -5,6 +5,7 @@ from MSMetaEnhancer.libs.converters.web import PubChem from frozendict import frozendict +from MSMetaEnhancer.libs.utils.Errors import UnknownResponse from tests.utils import wrap_with_session @@ -12,6 +13,7 @@ @pytest.mark.dependency() +@pytest.mark.xfail(raises=UnknownResponse) def test_service_available(): asyncio.run(wrap_with_session(PubChem, "inchi_to_inchikey", [INCHI])) @@ -72,6 +74,7 @@ def test_parse_attributes(response, expected): assert actual == expected +@pytest.mark.dependency(depends=["test_service_available"]) def test_convert_inchikey_to_inchi(): inchikey = "OHCNQFYTLLGNOE-UHFFFAOYSA-N" expected = "InChI=1S/C5H13NSi/c1-7(2,3)6-4-5-6/h4-5H2,1-3H3" From 2af7caa4d105da9d92eadf4ab3d9e59b15cda307 Mon Sep 17 00:00:00 2001 From: hechth Date: Fri, 5 Jun 2026 14:53:36 +0200 Subject: [PATCH 9/9] updated workflows --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/publish_pypi.yaml | 4 ++-- .github/workflows/test-package.yml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ec408c5..f9e5f97 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -16,7 +16,7 @@ jobs: security-events: write steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Initialize uses: github/codeql-action/init@v2 with: diff --git a/.github/workflows/publish_pypi.yaml b/.github/workflows/publish_pypi.yaml index a13c05c..3087d6c 100644 --- a/.github/workflows/publish_pypi.yaml +++ b/.github/workflows/publish_pypi.yaml @@ -9,8 +9,8 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Build and publish to pypi - uses: JRubics/poetry-publish@v1.17 + uses: JRubics/poetry-publish@v2.1 with: pypi_token: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index 695b011..52cc473 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -12,9 +12,9 @@ jobs: max-parallel: 5 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Set up Python 3.12 - uses: actions/setup-python@v3 + uses: actions/setup-python@v6 with: python-version: 3.12 - name: Install dependencies