From cf3fb6f065491ecc82086ea08ae3b60e4ebf400f Mon Sep 17 00:00:00 2001
From: Xavier Robin <xavalias-github@xavier.robin.name>
Date: Thu, 11 May 2023 15:59:58 +0200
Subject: [PATCH] feat: SCHWED-5913 setup compound lib with import ost

This sets up the compound lib upon calling import ost.
It simplifies the ost_startup.py script accordingly.
It is now possible to override the compound lib with
an environment variable: OST_COMPOUNDS_CHEMLIB
---
 modules/base/pymod/__init__.py.in | 75 ++++++++++++++++++++++++++++++-
 modules/conop/doc/compoundlib.rst | 15 +++++--
 modules/doc/install.rst           | 16 +++++++
 scripts/ost_config.in             |  1 +
 scripts/ost_startup.py.in         | 13 ------
 5 files changed, 102 insertions(+), 18 deletions(-)

diff --git a/modules/base/pymod/__init__.py.in b/modules/base/pymod/__init__.py.in
index 7b39730af..2d6f85a73 100644
--- a/modules/base/pymod/__init__.py.in
+++ b/modules/base/pymod/__init__.py.in
@@ -16,12 +16,13 @@
 # along with this library; if not, write to the Free Software Foundation, Inc.,
 # 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 #------------------------------------------------------------------------------
-
+import os
 
 __all__=['CharList','Correl', 'FileLogSink', 'FloatList', 'FloatMatrix', 'FloatMatrix3', 'FloatMatrix4', 'GetCurrentLogSink', 'GetPrefixPath', 'GetSharedDataPath', 'GetVerbosityLevel', 'Histogram', 'IntList', 'LogDebug', 'LogError', 'LogInfo', 'LogScript', 'LogSink', 'LogTrace', 'LogVerbose', 'LogWarning', 'Max', 'Mean', 'Median', 'Min', 'MultiLogSink', 'PopLogSink', 'PopVerbosityLevel', 'PushLogSink', 'PushVerbosityLevel', 'Range', 'SetPrefixPath', 'StdDev', 'StreamLogSink', 'StringList', 'StringLogSink', 'Units', 'VERSION', 'VERSION_MAJOR', 'VERSION_MINOR', 'VERSION_PATCH', 'WITH_NUMPY', 'conop', 'geom', 'io', 'mol', 'seq', 'stutil' @ALL_ADDITIONAL_MODULES@]
 
 from ._ost_base import *
 from .stutil import *
+from ost import conop
 
 class StreamLogSink(LogSink):
   def __init__(self, stream):
@@ -29,3 +30,75 @@ class StreamLogSink(LogSink):
     self._stream=stream
   def LogMessage(self, message, level):
     self._stream.write(message)
+
+
+def _SetupOstPrefix():
+  """ This function attempts to set the OST prefix path if $OST_ROOT was not
+  set already.
+
+  It does so by assuming that the __init__.py file is located under
+  $OST_ROOT/lib(64)?/pythonX.XX/site-packages/ost. This might not be the case
+  in all settings (conda, pip?). In that case this function should be
+  adapted.
+  """
+
+  try:
+    # $OST_ROOT was set
+    GetPrefixPath()
+  except RuntimeError:
+    # Setup from this file's path
+    prefix_path = __file__
+    for _ in range(5):
+      prefix_path = os.path.dirname(prefix_path)
+    SetPrefixPath(prefix_path)
+
+
+def _TrySetCompoundsLib(path):
+  """Tries to set the compound lib to 'path', or raises a ValueError."""
+  if path is not None and os.path.exists(path):
+    compound_lib = conop.CompoundLib.Load(path)
+    if compound_lib is not None:
+      conop.SetDefaultLib(compound_lib)
+      return
+  raise ValueError("Could not load %s" % path)
+
+
+def _SetupCompoundsLib():
+  """ This function sets up the compound lib.
+
+  By order of priority, the following heuristics are used:
+
+  1. The $OST_COMPOUNDS_CHEMLIB environment variable
+  2. The 'compounds.chemlib' file in the shared path under the folder pointed
+    by the $OST_ROOT environment variable
+
+  If no compound library can be loaded with any of these strategies, a warning
+  message is issued.
+
+  """
+  # Try with the $OST_COMPOUNDS_CHEMLIB environment variable
+  compound_lib_path = os.getenv("OST_COMPOUNDS_CHEMLIB")
+  try:
+    _TrySetCompoundsLib(compound_lib_path)
+  except ValueError:
+    pass
+  else:
+    return
+
+  # Try from GetSharedDataPath() - requires $OST_ROOT to be set.
+  try:
+    compound_lib_path = os.path.join(GetSharedDataPath(), 'compounds.chemlib')
+    _TrySetCompoundsLib(compound_lib_path)
+  except (RuntimeError, ValueError):
+    pass
+  else:
+    return
+
+  LogWarning("Compound library not available. Some functionality may not " \
+             "work as expected.")
+
+
+# Setup OST
+PushVerbosityLevel(LogLevel.Script)
+_SetupOstPrefix()
+_SetupCompoundsLib()
diff --git a/modules/conop/doc/compoundlib.rst b/modules/conop/doc/compoundlib.rst
index b454ff98d..fed70b637 100644
--- a/modules/conop/doc/compoundlib.rst
+++ b/modules/conop/doc/compoundlib.rst
@@ -21,10 +21,17 @@ build the compound library manually.
 
 .. function:: GetDefaultLib()
 
-  :return: Default compound library set by :func:`SetDefaultLib`. If you got
-           OpenStructure as a bundle or you :ref:`compiled <cmake-flags>`  it
-           with a specified ``COMPOUND_LIB`` flag, this will return a compound
-           library when executing scripts with ``ost``.
+  Get the default compound library. This is set by :func:`SetDefaultLib`.
+
+  If you obtained OpenStructure as a container or you
+  :ref:`compiled <cmake-flags>` it with a specified ``COMPOUND_LIB`` flag,
+  this function will return a compound library.
+
+  You can override the default compound library by pointing the
+  ``OST_COMPOUNDS_CHEMLIB`` environment variable to a valid compound library
+  file.
+
+  :return: Default compound library.
   :rtype:  :class:`CompoundLib` or None if no library set
 
 .. function:: SetDefaultLib(lib)
diff --git a/modules/doc/install.rst b/modules/doc/install.rst
index d840eeab2..dd4d4d5e1 100644
--- a/modules/doc/install.rst
+++ b/modules/doc/install.rst
@@ -420,6 +420,22 @@ or, to start the command-line interpreter:
 If you repeatedly use OpenStructure, it is recommended to add
 /path/to/ost/stage/bin to your path.
 
+You can also import OpenStructure directly into your existing python scripts,
+jupyter notebooks etc. Simply make sure to point the following environment
+variables to the right folders:
+
+.. code-block:: bash
+
+  export OST_ROOT=/path/to/ost/stage
+  export PYTHONPATH=$OST_ROOT/lib64/python3.10/site-packages/:$PYTHONPATH
+  python
+
+And then you can simply import ost as a module:
+
+.. code-block:: python
+
+  import ost
+
 Getting the newest changes
 --------------------------------------------------------------------------------
 
diff --git a/scripts/ost_config.in b/scripts/ost_config.in
index d0ba65da0..69e792960 100644
--- a/scripts/ost_config.in
+++ b/scripts/ost_config.in
@@ -25,6 +25,7 @@ export DNG_ROOT=`cd "$BIN_DIR/..";pwd`
 export DNG_BINDIR="$DNG_ROOT/bin"
 export DNG_LIBDIR="$DNG_ROOT/@LIBDIR@"
 export DNG_INITDIR="$DNG_LIBDIR/@PYTHON_MODULE_PATH@/ost/"
+export OST_ROOT="$DNG_ROOT"
 
 export PATH="$DNG_BINDIR:${PATH}"
 export DYLD_FRAMEWORK_PATH="$DNG_LIBDIR:${DYLD_FRAMEWORK_PATH}"
diff --git a/scripts/ost_startup.py.in b/scripts/ost_startup.py.in
index 3d22c7e7e..c451bec38 100644
--- a/scripts/ost_startup.py.in
+++ b/scripts/ost_startup.py.in
@@ -44,23 +44,10 @@ parser.disable_interspersed_args()
 _site_packs='python%d.%d/site-packages' % sys.version_info[0:2]
 _base_dir=os.getenv('DNG_ROOT')
 sys.path.insert(0, os.path.join(_base_dir, '@LIBDIR@', _site_packs))
-     
-from ost import SetPrefixPath, GetSharedDataPath, conop
 
-SetPrefixPath(_base_dir)
-
-def _InitRuleBasedProcessor():
-  compound_lib_path=os.path.join(GetSharedDataPath(), 'compounds.chemlib')
-  if os.path.exists(compound_lib_path):
-    compound_lib=conop.CompoundLib.Load(compound_lib_path)
-    conop.SetDefaultLib(compound_lib)
-
-# switch to rule-based processor, if compound library is available
-_InitRuleBasedProcessor()
 from ost import *
 import ost
 
-import os.path
 HistoryFile=os.path.expanduser('~/.ost_history')
 
 # we are not in GUI mode. 
-- 
GitLab