Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 44 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

[![Anaconda-Server Badge](https://anaconda.org/conda-forge/retropath2_wrapper/badges/version.svg)](https://anaconda.org/conda-forge/retropath2_wrapper) [![Anaconda-Server Badge](https://anaconda.org/conda-forge/retropath2_wrapper/badges/latest_release_date.svg)](https://anaconda.org/conda-forge/retropath2_wrapper)

Implementation of the KNIME retropath2.0 workflow. Takes for input the minimal (dmin) and maximal (dmax) diameter for the reaction rules and the maximal path length (maxSteps). The tool expects the following files: `rules.csv`, `sink.csv` and `source.csv` and produces results in an output folder.
Implementation of the KNIME retropath2.0 workflow. Takes for input the minimal (dmin) and maximal (dmax) diameter for the reaction rules and the maximal path length (maxSteps). The tool expects the following files: `rules.csv`, `sink.csv` and `source.csv` and produces results in an output folder.

## Prerequisites

* Python 3
* KNIME (code was tested on `4.6.4`, `4.7.0` versions)
- Python 3
- KNIME (code was tested on `4.6.4`, `4.7.0` versions)

## Install

Expand All @@ -20,6 +20,7 @@ The tool tries to install the KNIME Anlytical Platform as well as the RetroPath2
### conda package

Install in the `<my_env>` conda environment:

```shell
conda install -c conda-forge -n <my_env> retropath2_wrapper
```
Expand All @@ -29,13 +30,15 @@ conda install -c conda-forge -n <my_env> retropath2_wrapper
**Disclaimer**: we recommand to provide absolute path to files, problems can arise with relative paths.

### From CLI (Linux, macOS)

```sh
python -m retropath2_wrapper <sink-file> <rules-file> <out-dir> --source_file <source-file>
```

### From Python code

The minimal required arguments are `sink_file`, `source_file`, `rules_file` and `outdir`.

```python
from retropath2_wrapper import retropath2

Expand All @@ -48,6 +51,7 @@ r_code = retropath2(
```

Exploration settings have default values and can be tuned:

```python
from retropath2_wrapper import retropath2

Expand All @@ -65,15 +69,21 @@ r_code = retropath2(
```

Already installed KNIME app can hence be used that way, eg:

```python
from retropath2_wrapper import retropath2
from retropath2_wrapper.knime import Knime

knime = Knime(
kinstall="/path/to/knime/directory",
workflow="/path/to/workflow",
)
r_code = retropath2(
sink_file='/path/to/sink/file',
source_file='/path/to/source/file',
rules_file='/path/to/rules/file',
outdir='/path/to/outdir'
kexec='/Applications/KNIME 4.3.0.app/Contents/MacOS/knime',
knime=knime,
)
```

Expand All @@ -82,17 +92,22 @@ Executions can be timed out using the `timeout` arguments (in minutes).
### Return codes

`retropath2()` function returns one of the following codes:
* 0: No error
* 1: File is not found
* 2: Running the RetroPath2.0 Knime program produced an OSError
* 3: InChI is malformated
* 10: Source has been found in the sink (warning)
* 11: No solution is found (warning)

- 0: No error
- 1: File is not found
- 2: Running the RetroPath2.0 Knime program produced an OSError
- 3: InChI is malformated
- 4: Sink file is malformed
- 5: Knime installation returns an error
- 10: Source has been found in the sink (warning)
- 11: No solution is found (warning)

## Tests

Test can be run with the following commands:

### Natively

```sh
conda install -c conda-forge pytest
python -m pytest tests
Expand All @@ -102,30 +117,40 @@ To run functional tests, the environment variable `RP2_FUNCTIONAL=TRUE` is requi

### Knime dependencies

Two options are available:
1. You provide a path of the Knime executable through the argument `--kexec`. You need to have the following libraries installed: `org.knime.features.chem.types.feature.group`, `org.knime.features.datageneration.feature.group`, `org.knime.features.python.feature.group`, `org.rdkit.knime.feature.feature.group`
2. `retropath2_wrapper` will install Knime for you by downloading the softwares available on Zenodo. You can choose a version among `4.6.4` or `4.7.0`. Optionally, you can locate a path for the installation. If an executable is found in the path, Knime will not be reinstalled.
Availbale options:

1. You provide a path of the Knime root directory through the argument `--kinstall`. You need to have the following libraries installed: `org.knime.features.chem.types.feature.group`, `org.knime.features.datageneration.feature.group`, `org.knime.features.python.feature.group`, `org.rdkit.knime.feature.feature.group`
2. `retropath2_wrapper` will install Knime for you, at the root of the python package, by downloading the softwares available on Zenodo. You can choose a version among `4.6.4` or `4.7.0`. Optionally, you can locate a path for the installation. If an executable is found in the path, Knime will not be reinstalled.

```bash
python -m retropath2_wrapper.knime \
--kinstall "/path/to/knime/root/dir" \
--kver {4.6.4,4.7.0}
```

Knime software and packages are available at:
* [KNIME](https://www.knime.com/)
* [KNIME v4.6.4 - Zenodo](https://zenodo.org/record/7515771)
* [KNIME v4.7.0 - Zenodo](https://zenodo.org/record/7564938)

- [KNIME](https://www.knime.com/)
- [KNIME v4.6.4 - Zenodo](https://zenodo.org/record/7515771)
- [KNIME v4.7.0 - Zenodo](https://zenodo.org/record/7564938)

## Known issues

1. Could not load native RDKit library, libfreetype.so.6: cannot open shared object file
Some Knime versions (like: 4.3.0) or environments can't load RDKit library.
You need to append the `$CONDA_PREFIX/lib` path to the `LD_LIBRARY_PATH` variable where the `libfreetype` library is available:
Some Knime versions (like: 4.3.0) or environments can't load RDKit library.
You need to append the `$CONDA_PREFIX/lib` path to the `LD_LIBRARY_PATH` variable where the `libfreetype` library is available:

```sh
conda activate <env_name>
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$CONDA_PREFIX/lib"
```

## CI/CD
For further tests and development tools, a CI toolkit is provided in `cicd-toolkit` folder (see [cicd-toolkit/README.md](cicd-toolit/README.md)).

For further tests and development tools, a CI toolkit is provided in `cicd-toolkit` folder (see [cicd-toolkit/README.md](cicd-toolit/README.md)).

### How to cite RetroPath2.0?

Please cite:

Delépine B, Duigou T, Carbonell P, Faulon JL. RetroPath2.0: A retrosynthesis workflow for metabolic engineers. Metabolic Engineering, 45: 158-170, 2018. DOI: https://doi.org/10.1016/j.ymben.2017.12.002
9 changes: 9 additions & 0 deletions environment.macos-arm64.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: retropath2_wrapper
channels:
- conda-forge
dependencies:
- python >=3.10
- brs_utils >=1.23.1
- filetype
- colored
- freetype
61 changes: 2 additions & 59 deletions retropath2_wrapper/Args.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,10 @@
)
DEFAULTS = {
'MSC_TIMEOUT': 10, # minutes
'KNIME_VERSION': "4.6.4",
'RP2_VERSION': 'r20250728',
'KNIME_FOLDER': __PACKAGE_FOLDER,
'KNIME_REPOS': [
# 'http://update.knime.com/partner/',
'http://update.knime.com/community-contributions/trunk/',
'http://update.knime.com/community-contributions/trusted/4.6',
'http://update.knime.com/analytics-platform/4.6'
],
'KNIME_PLUGINS': ','.join(
[
'org.knime.base',
'org.knime.python.nodes',
'org.knime.datageneration',
'org.knime.chem.base',
'org.rdkit.knime.feature.feature.group/4.8.1.v202312052327',
'org.rdkit.knime.nodes/4.8.1.v202312052327',
# 'org.knime.python.nodes/4.6.0.v202203011403',
# 'org.knime.datageneration/4.6.0.v202202251621',
# 'org.knime.chem.base/4.6.0.v202202251610',
# 'org.rdkit.knime.feature.feature.group/4.8.1.v202312052327',
# 'org.rdkit.knime.nodes/4.8.1.v202312052327',
]
),
'NO_NETWORK': False,
"STD_HYDROGEN": "auto", # How hydrogens are represented in chemical rules
}
# DEFAULTS['KNIME_PLUGINS'] = ','.join(
# [pkg.split('/')[0] for pkg in DEFAULTS['KNIME_PLUGINS'].split(',')]
# )
KNIME_ZENODO = {"4.6.4": "7515771", "4.7.0": "7564938"} # Map to Zenodo ID
RETCODES = {
'OK': 0,
'NoError': 0,
Expand All @@ -57,6 +30,7 @@
'OSError': 2,
'InChI': 3,
'SinkFileMalformed': 4,
'KnimeInstallationError': 5,
}


Expand Down Expand Up @@ -109,34 +83,11 @@ def _add_arguments(parser):

# Knime
parser_knime = parser.add_argument_group("Knime arguments")
parser_knime.add_argument(
'--kexec',
type=str,
default=None,
help='path to KNIME executable file (KNIME will be \
downloaded if not already installed or path is \
wrong).'
)
parser_knime.add_argument(
'--kinstall',
type=str,
default=DEFAULTS['KNIME_FOLDER'],
help='path to KNIME executable file (KNIME will be \
downloaded if not already installed or path is \
wrong).'
)
parser_knime.add_argument(
'--kver',
type=str,
default=DEFAULTS['KNIME_VERSION'],
choices=list(KNIME_ZENODO.keys()),
help='version of KNIME (mandatory if --kexec is passed).',
)
parser_knime.add_argument(
'--kplugins',
type=str,
default="",
help='KNIME plugins to use (separated by a comma).',
help='Directory where to find a KNIME executable file',
)

# RetroPath2.0 workflow options
Expand All @@ -149,14 +100,6 @@ def _add_arguments(parser):
help=f'version of RetroPath2.0 workflow (default: {DEFAULTS["RP2_VERSION"]}).'
)

# No network option
parser_rp.add_argument(
'--no-network',
action='store_true',
default=DEFAULTS['NO_NETWORK'],
help='Do not use network.'
)

parser_rp.add_argument('--max_steps' , type=int, default=3)
parser_rp.add_argument('--topx' , type=int, default=100)
parser_rp.add_argument('--dmin' , type=int, default=0)
Expand Down
21 changes: 6 additions & 15 deletions retropath2_wrapper/RetroPath2.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,7 @@ def retropath2(
rules_file: str,
outdir: str,
std_hydrogen: str,
kinstall: str = DEFAULTS['KNIME_FOLDER'],
kexec: str = None,
kver: str = DEFAULTS['KNIME_VERSION'],
knime: Knime = None,
kplugins: list = DEFAULTS['KNIME_PLUGINS'],
knime: Knime,
rp2_version: str = DEFAULTS['RP2_VERSION'],
max_steps: int = 3,
topx: int = 100,
Expand All @@ -65,9 +61,6 @@ def retropath2(
logger.debug(f'rules_file: {rules_file}')
logger.debug(f'outdir: {outdir}')
logger.debug(f'std_hydrogen: {std_hydrogen}')
logger.debug(f'kexec: {kexec}')
logger.debug(f'kinstall: {kinstall}')
logger.debug(f'kver: {kver}')
logger.debug(f'rp2_version: {rp2_version}')
logger.debug(f'max_steps: {max_steps}')
logger.debug(f'topx: {topx}')
Expand All @@ -78,12 +71,15 @@ def retropath2(

# Create Knime object
if knime is None:
knime = Knime(kexec=kexec, kinstall=kinstall, kver=kver)
knime = Knime()
if rp2_version is not None:
knime.workflow = os_path.join(
here, 'workflows', f'RetroPath2.0_{rp2_version}.knwf'
)

if knime.kexec == "":
# Install KNIME
if not knime.install(kver=Knime.DEFAULT_VERSION, logger=logger):
return RETCODES["KnimeInstallationError"]
logger.debug('knime: ' + str(knime))

# Store RetroPath2 params into a dictionary
Expand All @@ -101,11 +97,6 @@ def retropath2(
if r_code != RETCODES['OK']:
return r_code, None

# Install KNIME
r_code = knime.install(logger=logger)
if r_code > 0:
return r_code, None

logger.info('{attr1}Initializing{attr2}'.format(attr1=attr('bold'), attr2=attr('reset')))

# Preferences
Expand Down
15 changes: 1 addition & 14 deletions retropath2_wrapper/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,11 @@ def _cli():

# Create Knime object
here = os_path.dirname(os_path.realpath(__file__))
if args.kplugins == "":
kplugins = []
else:
kplugins = args.kplugins.split(',')
knime = Knime(
kexec=args.kexec,
kinstall=args.kinstall,
kver=args.kver,
kplugins=kplugins,
workflow=os_path.join(here, 'workflows', 'RetroPath2.0_%s.knwf' % (args.rp2_version,)),
network=not args.no_network,
)

# Print out configuration
if not args.silent and args.log.lower() not in ['critical', 'error']:
print_conf(knime, prog = parser.prog)
Expand Down Expand Up @@ -194,12 +187,6 @@ def parse_and_check_args(

args = parser.parse_args()

if args.kexec is not None:
if not os.path.isfile(args.kexec):
parser.error("--kexec is not a file: %s" %(args.kexec,))
if not os.access(args.kexec, os.X_OK):
parser.error("--kexec is not executable: %s" %(args.kexec,))

# Create outdir if does not exist
if not os_path.exists(args.outdir):
os_mkdir(args.outdir)
Expand Down
Loading
Loading