molop.config¶
本页面提供了 molop.config 模块的 API 参考。
模块暴露进程级 molopconfig 对象,用于控制进度显示、并行任务上限、日志和结构恢复选项。
单次调用的行为优先使用显式函数参数;只有需要影响后续调用的全局策略时才修改全局配置。
配置总览¶
MolOPConfig 的字段及默认值如下。from molop import molopconfig 得到的是进程级共享对象;直接修改
它会影响后续调用。创建独立的 MolOPConfig(...) 时,构造函数会立即应用进度、文件日志和原生日志设置。
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
show_progress_bar |
bool |
True |
是否显示批量操作的进度条;同时控制 MolOP 的终端日志 handler。 |
max_jobs |
int \| None |
None |
进程级并行任务上限;None 表示自动使用当前进程可用 CPU。 |
graph_reconstruction_backend |
"cpp" \| "python" |
"cpp" |
分子图重建后端。 |
reconstruction_failure_policy |
"raise" \| "return_suspicious" |
"raise" |
图重建失败时抛出异常,或保留标记为可疑的 fallback 分子图。 |
prewarm_topologies |
bool |
False |
是否在主进程预热依赖分子图的拓扑;默认在使用结果的 worker 中惰性重建。 |
make_dative_bonds |
bool |
True |
图重建时是否生成配位键。 |
make_stereochemistry |
bool |
True |
图重建时是否分配立体化学信息。 |
force_unit_transform |
bool |
False |
是否强制执行单位转换。 |
parallel_max_size |
int |
8 * 1024**2 |
并行调度及 joblib 数据传输使用的大小阈值,单位为字节(8 MiB)。 |
max_recursion_depth |
int |
3000 |
set_max_recursion_depth() 请求设置的 Python 递归深度;导入时不会自动修改解释器。 |
log_to_file |
bool |
False |
是否启用 MolOP 文件日志。 |
log_file_path |
str |
"molop.log" |
文件日志路径;只有启用 log_to_file 或调用 enable_file_logging() 时生效。 |
suppress_rdkit_logs |
bool |
True |
是否关闭 RDKit 原生诊断输出。 |
suppress_openbabel_logs |
bool |
True |
是否关闭 Open Babel 原生诊断输出。 |
use_dof_effect_drawer |
bool |
True |
绘图时是否优先使用 rdkit-dof 景深绘制器;关闭后使用标准 RDKit 绘制器。 |
除字段外,effective_max_jobs 和 effective_molgr_max_jobs 是根据 CPU 资源和配置计算出的只读并行上限。
导入副作用与显式初始化¶
导入 molop 会初始化全局 molopconfig,并默认关闭 RDKit 和 Open Babel 的原生日志。它不会创建
molop.log,也不会修改宿主进程的 Python 递归上限。rdkit-dof 只在需要景深绘制或显式调用相关设置时加载。
原生日志属于原生库自己的输出,不会自动写入 MolOP 文件日志。
需要保留原生日志时,修改开关后调用 configure_native_logging() 使设置作用于当前进程:
from molop import molopconfig
molopconfig.suppress_rdkit_logs = False
molopconfig.suppress_openbabel_logs = False
molopconfig.configure_native_logging()
只想保留其中一种日志时,将另一开关保持为 True。通过 MolOPConfig(...) 创建新配置时,构造函数会自动应用
两个原生日志开关;直接修改已有对象后需要显式调用 configure_native_logging()。
需要同时启用文件日志和安静的原生日志时,可以显式创建配置:
from molop.config import MolOPConfig
config = MolOPConfig(
log_to_file=True,
log_file_path="run.log",
suppress_rdkit_logs=True,
suppress_openbabel_logs=True,
)
enable_file_logging()、disable_file_logging() 负责文件 handler 的生命周期。对全局对象修改
log_to_file = True 不会自动创建 handler,应调用 enable_file_logging();关闭时调用
disable_file_logging()。set_log_level() 支持 DEBUG、INFO、WARNING、ERROR 和 CRITICAL。
进度条和 MolOP 终端日志使用方法控制,避免只修改字段而留下旧 handler:
set_max_recursion_depth() 只在调用方明确请求时修改当前进程的递归上限。
主进程拓扑预热由 prewarm_topologies 控制,默认值为 False。默认情况下,依赖分子图的操作会在
使用结果的 spawn-like loky worker 中按需惰性重建,避免单独的主进程预热步骤。需要确定性主进程
缓存的工作流可以显式开启:
MolGR 重建失败默认采用严格模式。需要保留错误结构以便复核时,可以开启原始 fallback 保留:
保留的分子图会标记为 topology_reconstruction_status == "suspicious_fallback",不能作为可信
化学结构直接使用。
并行默认值¶
max_jobs 默认是 None,表示自动检测当前进程可用的 CPU。MolOP 会在 joblib/loky 可检测的
调度器和容器限制、os.cpu_count(),以及操作系统支持时的当前进程 affinity 中取最严格的
有效值。MolOP 将 psutil 作为直接依赖,以启用 joblib 在 Windows 等受支持平台上的 affinity
fallback。因此 n_jobs=-1 会使用当前进程可用的 CPU,而不超过已检测到的分配额度。
Linux 支持 affinity 和 cgroup quota 检测。Windows 的进程 affinity 通过 psutil 获取,joblib 还会
应用 Windows 进程池的安全 worker 上限。macOS 没有可移植的进程级 CPU affinity API,因此自动
值使用当前进程可见 CPU 和 joblib 可检测的限制。若运行平台无法向进程暴露实际配额,所有平台
都可以通过 max_jobs、MOLOP_MAX_JOBS 或 LOKY_MAX_CPU_COUNT 显式设置上限。
需要设置进程级上限时传入正整数:
由环境创建默认配置时,可设置 MOLOP_MAX_JOBS=32。即使环境变量存在,显式构造
MolOPConfig(max_jobs=None) 仍会保持自动检测。正数 n_jobs 仍表示单次调用上限,并受
effective_max_jobs 限制;n_jobs=1 继续表示串行排查模式。CLI 可通过
molop --max-jobs 32 parse ... 为单次命令设置相同的进程级上限。
可能进入 MolGR 的任务使用独立的 effective_molgr_max_jobs 上限。自动值和显式值都会先受
floor(2 * available_cpu_count / 3) 限制(最少保留 1 个串行 worker),再受 max_jobs 限制。
该策略覆盖拓扑重建、依赖分子图的摘要、导出、格式转换以及 filter_custom 等用户回调;
文件解析以及不调用 MolGR 的其他操作仍使用完整的 effective_max_jobs 上限。
Author: TMJ Date: 2025-01-15 23:01:22 LastEditors: TMJ LastEditTime: 2026-06-18 19:45:06 Description: 请填写简介
MolOPConfig
¶
Bases: BaseModel
Configuration class for MolOP operations. Used to manage settings related to molecule processing, fingerprint generation, and logging.
Source code in src/molop/config/__init__.py
class MolOPConfig(BaseModel):
"""
Configuration class for MolOP operations.
Used to manage settings related to molecule processing, fingerprint generation, and logging.
"""
model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True)
# --- General Settings ---
show_progress_bar: bool = Field(default=True, description="Whether to display the progress bar")
max_jobs: int | None = Field(
default=None,
ge=1,
description="Maximum parallel jobs; None uses all CPUs available to the process",
)
# --- Advanced Settings ---
graph_reconstruction_backend: Literal["cpp", "python"] = Field(
default="cpp", description="Backend for graph reconstruction"
)
reconstruction_failure_policy: Literal["raise", "return_suspicious"] = Field(
default="raise",
description=(
"Whether MolGR reconstruction failures raise or retain an untrusted "
"suspicious fallback molecule"
),
)
prewarm_topologies: bool = Field(
default=False,
description=(
"Whether graph-dependent operations prewarm coordinate-only topologies "
"in the parent process"
),
)
make_dative_bonds: bool = Field(default=True, description="Whether to make dative bonds")
make_stereochemistry: bool = Field(
default=True, description="Whether to assign stereochemistry during graph reconstruction"
)
force_unit_transform: bool = Field(
default=False, description="Whether to force unit conversion"
)
parallel_max_size: int = Field(
default=8 * 1024**2,
description="Maximum data size for parallel processing (bytes)",
)
max_recursion_depth: int = Field(default=3000, description="Maximum recursion depth for Python")
# --- Log File Control ---
log_to_file: bool = Field(default=False, description="Whether to write log messages to a file")
log_file_path: str = Field(default="molop.log", description="Path used for optional file logs")
# --- Native library logging control ---
suppress_rdkit_logs: bool = Field(
default=True,
description="Whether to suppress RDKit native diagnostics",
)
suppress_openbabel_logs: bool = Field(
default=True,
description="Whether to suppress Open Babel native diagnostics",
)
# --- DOF Effect Drawer Control ---
use_dof_effect_drawer: bool = Field(
default=True, description="Whether to use DOF effect drawer"
)
def __init__(self, **data: Any):
"""
Initializes the configuration object and configures the logger based on current settings.
"""
if "max_jobs" not in data and (env_max_jobs := os.environ.get(MAX_JOBS_ENV_VAR)):
data["max_jobs"] = env_max_jobs
super().__init__(**data)
# Set log state based on initial configuration values
if self.show_progress_bar:
self.verbose()
else:
self.quiet()
if self.log_to_file:
self.enable_file_logging()
else:
self.disable_file_logging()
# ``rdkit-dof`` changes the interpreter recursion limit when imported.
# Do not import it during a normal library import; explicit rendering
# configuration can still enable it through this method.
if not self.use_dof_effect_drawer:
self.set_dof_effect_drawer(enable=False)
self.configure_native_logging()
def quiet(self):
"""
Disables the progress bar and console log output.
This allows the program to run silently in the background.
"""
self.show_progress_bar = False
if stream_handler in moloplogger.handlers:
moloplogger.removeHandler(stream_handler)
def verbose(self):
"""
Enables the progress bar and console log output.
"""
self.show_progress_bar = True
if stream_handler not in moloplogger.handlers:
moloplogger.addHandler(stream_handler)
def enable_file_logging(self):
"""Enable logging to a file."""
global file_handler
self.log_to_file = True
if file_handler is None or file_handler.baseFilename != os.path.abspath(self.log_file_path):
if file_handler is not None:
file_handler.close()
file_handler = logging.FileHandler(self.log_file_path)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
if file_handler not in moloplogger.handlers:
moloplogger.addHandler(file_handler)
moloplogger.info(
f"File logging enabled. Logs will be written to: {getattr(file_handler, 'baseFilename', 'N/A')}"
)
def disable_file_logging(self):
"""Disable logging to a file."""
global file_handler
self.log_to_file = False
if file_handler is not None and file_handler in moloplogger.handlers:
moloplogger.removeHandler(file_handler)
if file_handler is not None:
file_handler.close()
file_handler = None
def set_log_level(self, level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]):
"""
Sets the level for the molop logger.
Args:
level (str): The logging level, must be one of 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'.
"""
try:
moloplogger.setLevel(level)
logging.info(f"Log level set to {level}")
except ValueError as e:
logging.error(f"Error setting log level: Invalid level '{level}'")
raise ValueError(f"Invalid log level: {level}") from e
def set_max_recursion_depth(self, depth: int):
"""
Set the maximum recursion depth for Python.
Args:
depth (int): The maximum recursion depth.
"""
sys.setrecursionlimit(depth)
self.max_recursion_depth = depth
logging.info(f"Maximum recursion depth set to {depth}")
def configure_native_logging(self) -> None:
"""Apply the configured native-library logging policy.
Native diagnostics are suppressed by default. Call this method after
changing either suppression flag to apply the new policy to the current
process.
"""
if self.suppress_rdkit_logs:
RDLogger.DisableLog("rdApp.*") # type: ignore
else:
RDLogger.EnableLog("rdApp.*") # type: ignore
if self.suppress_openbabel_logs:
pybel.ob.obErrorLog.StopLogging()
else:
pybel.ob.obErrorLog.StartLogging()
@property
def effective_max_jobs(self) -> int:
"""Current process-aware worker limit after applying ``max_jobs``."""
available_jobs = available_cpu_count()
return available_jobs if self.max_jobs is None else min(available_jobs, self.max_jobs)
@property
def effective_molgr_max_jobs(self) -> int:
"""Worker limit for tasks that may enter MolGR's native runtime.
MolGR work intentionally leaves one third of the process-visible CPU
budget available for the parent process, native helper threads, and
the operating system. The existing ``max_jobs`` setting remains an
additional upper bound.
"""
available_jobs = available_cpu_count()
molgr_jobs = max(1, (available_jobs * 2) // 3)
return min(self.effective_max_jobs, molgr_jobs)
def set_n_jobs(self, n_jobs: int) -> int:
"""Resolve automatic or explicit parallelism within the effective worker limit."""
return self.effective_max_jobs if n_jobs <= 0 else min(n_jobs, self.effective_max_jobs)
def set_molgr_n_jobs(self, n_jobs: int) -> int:
"""Resolve parallelism for work that may invoke MolGR.
Both automatic (non-positive) and explicit values are capped so a
caller cannot accidentally bypass the native-runtime safety budget.
"""
limit = self.effective_molgr_max_jobs
return limit if n_jobs <= 0 else min(n_jobs, limit)
def apply_molgr_reconstruction_policy(
self,
policy: Literal["raise", "return_suspicious"] | None = None,
) -> Literal["raise", "return_suspicious"]:
"""Apply MolOP's reconstruction policy to the shared MolGR config."""
resolved_policy = self.reconstruction_failure_policy if policy is None else policy
MOLGR_CONFIG.interface.reconstruction_failure_policy = resolved_policy
return resolved_policy
def set_dof_effect_drawer(self, enable: bool):
"""
Set whether to use the DOF effect drawer.
Args:
enable (bool): Whether to use the DOF effect drawer.
"""
dofconfig.enable_ipython_integration(enable)
effective_max_jobs
property
¶
Current process-aware worker limit after applying max_jobs.
effective_molgr_max_jobs
property
¶
Worker limit for tasks that may enter MolGR’s native runtime.
MolGR work intentionally leaves one third of the process-visible CPU
budget available for the parent process, native helper threads, and
the operating system. The existing max_jobs setting remains an
additional upper bound.
__init__(**data)
¶
Initializes the configuration object and configures the logger based on current settings.
Source code in src/molop/config/__init__.py
def __init__(self, **data: Any):
"""
Initializes the configuration object and configures the logger based on current settings.
"""
if "max_jobs" not in data and (env_max_jobs := os.environ.get(MAX_JOBS_ENV_VAR)):
data["max_jobs"] = env_max_jobs
super().__init__(**data)
# Set log state based on initial configuration values
if self.show_progress_bar:
self.verbose()
else:
self.quiet()
if self.log_to_file:
self.enable_file_logging()
else:
self.disable_file_logging()
# ``rdkit-dof`` changes the interpreter recursion limit when imported.
# Do not import it during a normal library import; explicit rendering
# configuration can still enable it through this method.
if not self.use_dof_effect_drawer:
self.set_dof_effect_drawer(enable=False)
self.configure_native_logging()
apply_molgr_reconstruction_policy(policy=None)
¶
Apply MolOP’s reconstruction policy to the shared MolGR config.
Source code in src/molop/config/__init__.py
def apply_molgr_reconstruction_policy(
self,
policy: Literal["raise", "return_suspicious"] | None = None,
) -> Literal["raise", "return_suspicious"]:
"""Apply MolOP's reconstruction policy to the shared MolGR config."""
resolved_policy = self.reconstruction_failure_policy if policy is None else policy
MOLGR_CONFIG.interface.reconstruction_failure_policy = resolved_policy
return resolved_policy
configure_native_logging()
¶
Apply the configured native-library logging policy.
Native diagnostics are suppressed by default. Call this method after changing either suppression flag to apply the new policy to the current process.
Source code in src/molop/config/__init__.py
def configure_native_logging(self) -> None:
"""Apply the configured native-library logging policy.
Native diagnostics are suppressed by default. Call this method after
changing either suppression flag to apply the new policy to the current
process.
"""
if self.suppress_rdkit_logs:
RDLogger.DisableLog("rdApp.*") # type: ignore
else:
RDLogger.EnableLog("rdApp.*") # type: ignore
if self.suppress_openbabel_logs:
pybel.ob.obErrorLog.StopLogging()
else:
pybel.ob.obErrorLog.StartLogging()
disable_file_logging()
¶
Disable logging to a file.
Source code in src/molop/config/__init__.py
enable_file_logging()
¶
Enable logging to a file.
Source code in src/molop/config/__init__.py
def enable_file_logging(self):
"""Enable logging to a file."""
global file_handler
self.log_to_file = True
if file_handler is None or file_handler.baseFilename != os.path.abspath(self.log_file_path):
if file_handler is not None:
file_handler.close()
file_handler = logging.FileHandler(self.log_file_path)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
if file_handler not in moloplogger.handlers:
moloplogger.addHandler(file_handler)
moloplogger.info(
f"File logging enabled. Logs will be written to: {getattr(file_handler, 'baseFilename', 'N/A')}"
)
quiet()
¶
Disables the progress bar and console log output. This allows the program to run silently in the background.
set_dof_effect_drawer(enable)
¶
Set whether to use the DOF effect drawer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enable
|
bool
|
Whether to use the DOF effect drawer. |
required |
set_log_level(level)
¶
Sets the level for the molop logger.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
level
|
str
|
The logging level, must be one of ‘DEBUG’, ‘INFO’, ‘WARNING’, ‘ERROR’, ‘CRITICAL’. |
required |
Source code in src/molop/config/__init__.py
def set_log_level(self, level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]):
"""
Sets the level for the molop logger.
Args:
level (str): The logging level, must be one of 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'.
"""
try:
moloplogger.setLevel(level)
logging.info(f"Log level set to {level}")
except ValueError as e:
logging.error(f"Error setting log level: Invalid level '{level}'")
raise ValueError(f"Invalid log level: {level}") from e
set_max_recursion_depth(depth)
¶
Set the maximum recursion depth for Python.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
depth
|
int
|
The maximum recursion depth. |
required |
Source code in src/molop/config/__init__.py
set_molgr_n_jobs(n_jobs)
¶
Resolve parallelism for work that may invoke MolGR.
Both automatic (non-positive) and explicit values are capped so a caller cannot accidentally bypass the native-runtime safety budget.
Source code in src/molop/config/__init__.py
def set_molgr_n_jobs(self, n_jobs: int) -> int:
"""Resolve parallelism for work that may invoke MolGR.
Both automatic (non-positive) and explicit values are capped so a
caller cannot accidentally bypass the native-runtime safety budget.
"""
limit = self.effective_molgr_max_jobs
return limit if n_jobs <= 0 else min(n_jobs, limit)
set_n_jobs(n_jobs)
¶
Resolve automatic or explicit parallelism within the effective worker limit.