#!/usr/bin/env python3
# Description
###############################################################################
'''
Console session helpers: namespace construction, REPL loop, and utilities.
Builds the preloaded interactive namespace (docking modules, Initialise
symbols, ``print_args``, ``clean_test_files``) and runs the command loop with
tab-completion and history.
'''
from __future__ import annotations
# Imports
###############################################################################
import code
import inspect
import os
import shutil
import sys
from glob import glob
from pprint import pprint
from typing import Any, Mapping
import OCDocker.Error as ocerror
from OCDocker.Console.commands import handle_command
# License
###############################################################################
'''Copyright (c) Federal University of Rio de Janeiro (UFRJ), Artur Duque Rossi, and Pedro Henrique Monteiro Torres.
SPDX-License-Identifier: BSD-3-Clause
See the LICENSE file for full terms.
'''
# Functions
###############################################################################
## Private ##
def _setup_readline(namespace: Mapping[str, Any]) -> None:
'''Configure readline tab-completion and load command history.'''
try:
import readline
import rlcompleter
completer = rlcompleter.Completer(dict(namespace))
readline.set_completer(completer.complete)
readline.parse_and_bind("tab: complete")
hist = os.path.expanduser("~/.ocdocker_console_history")
try:
readline.read_history_file(hist)
except (OSError, FileNotFoundError):
pass
except (ImportError, AttributeError):
pass
def _save_readline_history() -> None:
'''Persist readline command history (best-effort).'''
try:
if "readline" in sys.modules:
sys.modules["readline"].write_history_file(os.path.expanduser("~/.ocdocker_console_history"))
except (OSError, AttributeError):
pass
## Public ##
[docs]
def clean_test_files(baseProtPath: str, baseLigPath: str, baseDecPath: str, baseCanPath: str) -> None:
'''Reset the test_files folder to its original state.
Parameters
----------
baseProtPath : str
Path to the base protein folder.
baseLigPath : str
Path to the base ligand folder.
baseDecPath : str
Path to the base decoy folder.
baseCanPath : str
Path to the base candidates folder.
'''
for f in glob(f"{baseProtPath}/*"):
if os.path.isfile(f) and not f.endswith(f"{baseProtPath}/receptor.pdb"):
os.remove(f)
for ligFolder in [baseLigPath, baseDecPath, baseCanPath]:
for f in glob(f"{ligFolder}/*/*"):
if os.path.isfile(f) and not f.endswith("ligand.smi"):
os.remove(f)
elif os.path.isdir(f) and not f.endswith("boxes"):
shutil.rmtree(f)
return None
[docs]
def print_args(program: str = "") -> None:
'''Print environment variables and optionally program-specific settings.
Console usage examples:
- ``print_args()`` — environment overview
- ``print_args('paths')`` — relevant paths and binaries
- ``print_args('db')`` — database connections
- ``print_args('vina')`` — Vina parameters
- ``print_args('smina')`` — Smina parameters
- ``print_args('plants')`` — PLANTS parameters
- ``print_args('gnina')`` — Gnina parameters (if configured)
- ``print_args('oddt')`` — ODDT parameters
- ``print_args('all')`` — print all sections
Parameters
----------
program : str, optional
Section selector (``paths``, ``db``, ``vina``, ``smina``, ``plants``,
``gnina``, ``oddt``, ``all``). Empty string prints the overview only.
'''
from OCDocker.Config import get_config
import OCDocker.Initialise as ocinit
init_vars = vars(ocinit)
try:
cfg = get_config()
except Exception:
cfg = None
def _g(name: str, default: str = "-") -> str:
return str(init_vars.get(name, default))
def _c(attr_path: str, default: str = "-") -> str:
if cfg is None:
return default
try:
obj: Any = cfg
for attr in attr_path.split("."):
obj = getattr(obj, attr, None)
if obj is None:
return default
return str(obj) if obj != "" else default
except (AttributeError, TypeError):
return default
def _p(label: str, value: str) -> None:
try:
print(f"{label:<28}: {value}")
except Exception:
print(f"{label:<28}: <unprintable>")
prog = (program or "").strip().lower()
show_all = prog in ("all", "*")
if not prog or show_all:
print("\n=== OCDocker Runtime Arguments ===")
_p("config_file", _g("config_file"))
_p("multiprocess", _c("multiprocess", _g("multiprocess")))
_p("update", _g("update"))
ol = _g("output_level") or _c("output_level")
try:
if hasattr(ol, "name"):
ol_disp = ol.name
elif hasattr(ol, "value"):
ol_disp = ocerror.ReportLevel(ol.value).name
else:
ol_disp = ol
except Exception:
ol_disp = ol
_p("output_level", ol_disp)
_p("overwrite", _c("overwrite", _g("overwrite")))
if prog in ("paths",) or show_all:
print("\n=== Key Paths ===")
_p("ocdb_path", _c("paths.ocdb_path"))
_p("pca_path", _c("paths.pca_path"))
_p("logdir", _c("logdir"))
_p("oddt_models_dir", _c("oddt_models_dir"))
print("\n=== Docking Binaries ===")
_p("vina", _c("vina.executable"))
_p("smina", _c("smina.executable"))
_p("plants", _c("plants.executable"))
_p("gnina", _c("gnina.executable"))
_p("obabel", _c("tools.obabel"))
_p("pythonsh", _c("tools.pythonsh"))
_p("prepare_ligand", _c("tools.prepare_ligand"))
_p("prepare_receptor", _c("tools.prepare_receptor"))
if prog in ("db",) or show_all:
print("\n=== Database URLs ===")
_p("db_url", _g("db_url"))
_p("optdb_url", _g("optdb_url"))
if prog in ("vina",) or show_all:
print("\n=== Vina Parameters ===")
_p("vina_scoring", _c("vina.scoring"))
_p("vina_scoring_functions", _c("vina.scoring_functions"))
_p("vina_num_modes", _c("vina.num_modes"))
_p("vina_energy_range", _c("vina.energy_range"))
_p("vina_exhaustiveness", _c("vina.exhaustiveness"))
if prog in ("smina",) or show_all:
print("\n=== Smina Parameters ===")
_p("smina_scoring", _c("smina.scoring"))
_p("smina_scoring_functions", _c("smina.scoring_functions"))
_p("smina_num_modes", _c("smina.num_modes"))
_p("smina_energy_range", _c("smina.energy_range"))
_p("smina_exhaustiveness", _c("smina.exhaustiveness"))
_p("smina_custom_scoring", _c("smina.custom_scoring"))
_p("smina_custom_atoms", _c("smina.custom_atoms"))
_p("smina_local_only", _c("smina.local_only"))
_p("smina_minimize", _c("smina.minimize"))
_p("smina_randomize_only", _c("smina.randomize_only"))
_p("smina_minimize_iters", _c("smina.minimize_iters"))
_p("smina_accurate_line", _c("smina.accurate_line"))
_p("smina_minimize_early_term", _c("smina.minimize_early_term"))
_p("smina_approximation", _c("smina.approximation"))
_p("smina_factor", _c("smina.factor"))
_p("smina_force_cap", _c("smina.force_cap"))
_p("smina_user_grid", _c("smina.user_grid"))
_p("smina_user_grid_lambda", _c("smina.user_grid_lambda"))
if prog in ("plants",) or show_all:
print("\n=== PLANTS Parameters ===")
_p("plants_cluster_structures", _c("plants.cluster_structures"))
_p("plants_cluster_rmsd", _c("plants.cluster_rmsd"))
_p("plants_search_speed", _c("plants.search_speed"))
_p("plants_scoring", _c("plants.scoring"))
_p("plants_scoring_functions", _c("plants.scoring_functions"))
if prog in ("gnina",) or show_all:
print("\n=== Gnina Parameters ===")
_p("gnina_exhaustiveness", _c("gnina.exhaustiveness"))
_p("gnina_num_modes", _c("gnina.num_modes"))
_p("gnina_scoring", _c("gnina.scoring"))
_p("gnina_custom_scoring_file", _c("gnina.custom_scoring"))
_p("gnina_custom_atoms", _c("gnina.custom_atoms"))
_p("gnina_local_only", _c("gnina.local_only"))
_p("gnina_minimize", _c("gnina.minimize"))
_p("gnina_randomize_only", _c("gnina.randomize_only"))
_p("gnina_num_mc_steps", _c("gnina.num_mc_steps"))
_p("gnina_max_mc_steps", _c("gnina.max_mc_steps"))
_p("gnina_num_mc_saved", _c("gnina.num_mc_saved"))
_p("gnina_minimize_iters", _c("gnina.minimize_iters"))
_p("gnina_simple_ascent", _c("gnina.simple_ascent"))
_p("gnina_accurate_line", _c("gnina.accurate_line"))
_p("gnina_minimize_early_term", _c("gnina.minimize_early_term"))
_p("gnina_approximation", _c("gnina.approximation"))
_p("gnina_factor", _c("gnina.factor"))
_p("gnina_force_cap", _c("gnina.force_cap"))
_p("gnina_user_grid", _c("gnina.user_grid"))
_p("gnina_user_grid_lambda", _c("gnina.user_grid_lambda"))
_p("gnina_no_gpu", _c("gnina.no_gpu"))
if prog in ("oddt",) or show_all:
print("\n=== ODDT Parameters ===")
_p("oddt_program", _c("oddt.executable"))
_p("oddt_seed", _c("oddt.seed"))
_p("oddt_chunk_size", _c("oddt.chunk_size"))
_p("oddt_scoring_functions", _c("oddt.scoring_functions"))
if not prog and not show_all:
print("")
return None
[docs]
def build_namespace() -> dict[str, Any]:
'''Build the interactive console namespace after environment bootstrap.
Returns
-------
dict[str, Any]
Mapping of names exposed in the REPL (Initialise symbols, docking
modules, helpers).
'''
namespace: dict[str, Any] = {}
import OCDocker.Initialise as ocinit
for key, value in vars(ocinit).items():
if not key.startswith("__"):
namespace[key] = value
import OCDocker.Docking.Gnina as ocgnina
import OCDocker.Docking.PLANTS as ocplants
import OCDocker.Docking.Smina as ocsmina
import OCDocker.Docking.Vina as ocvina
import OCDocker.Ligand as ocl
import OCDocker.Processing.Preprocessing.RMSDClustering as ocrmsdclust
import OCDocker.Receptor as ocr
import OCDocker.Toolbox as octools
import OCDocker.Toolbox.Conversion as occonversion
import OCDocker.Toolbox.MoleculeProcessing as ocmolproc
namespace.update(
{
"ocgnina": ocgnina,
"ocplants": ocplants,
"ocsmina": ocsmina,
"ocvina": ocvina,
"ocl": ocl,
"ocrmsdclust": ocrmsdclust,
"ocr": ocr,
"octools": octools,
"occonversion": occonversion,
"ocmolproc": ocmolproc,
"ocerror": ocerror,
"inspect": inspect,
"glob": glob,
"pprint": pprint,
"os": os,
"sys": sys,
"print_args": print_args,
"clean_test_files": clean_test_files,
}
)
try:
import OCDocker.Rescoring.ODDT as ocoddt
namespace["ocoddt"] = ocoddt
except ModuleNotFoundError as exc:
missing_mod = getattr(exc, "name", "")
if missing_mod == "oddt" or missing_mod.startswith("oddt."):
print("Warning: optional dependency 'oddt' is not installed; 'ocoddt' is unavailable in console mode.")
else:
raise
return namespace
[docs]
def run_interactive(namespace: Mapping[str, Any], *, use_ipython: bool = False) -> int:
'''Run the interactive console loop.
Parameters
----------
namespace : Mapping[str, Any]
Preloaded namespace for Python execution.
use_ipython : bool, optional
When ``True`` and IPython is installed, use ``IPython.embed`` instead
of the simple command loop.
Returns
-------
int
Exit code (0 for success).
'''
if use_ipython:
try:
from IPython import embed
colors = "NoColor"
if sys.stdout.isatty() and os.getenv("TERM") and "dumb" not in os.getenv("TERM", ""):
colors = "Linux"
embed(user_ns=dict(namespace), banner1="", colors=colors, display_banner=False)
return 0
except ImportError:
pass
print("Launching OCDocker Console. Type 'help' for commands, 'exit' to leave.")
_setup_readline(namespace)
interactive = code.InteractiveConsole(locals=dict(namespace))
while True:
try:
line = input("ocdocker> ")
except EOFError:
print()
break
except KeyboardInterrupt:
print()
continue
stripped = line.strip()
if not stripped:
continue
if handle_command(stripped, namespace):
if stripped.lower() in ("exit", "quit"):
break
continue
try:
interactive.push(stripped)
except SystemExit:
break
_save_readline_history()
return 0