Scripting#
This page is for C++ extension developers. It explains how to:
- execute Python code and Python files from SAMSON;
- install and remove Python packages in the embedded Python environment;
- install dependencies from a
requirements.txtfile or from a local package.
For writing Python scripts themselves, see:
Overview#
SAMSON exposes Python Scripting in two different ways:
- Use
SAMSON::runPythonCodeandSAMSON::runPythonFilewhen you want to execute Python from C++. - Use the
SEPythonScriptAppproxy when you want to manage the embedded Python environment, for example to query, install, or uninstall Python packages.
Note
Python code runs in the same embedded interpreter as the Python Console. If Python Scripting has not been initialized yet, SAMSON initializes it before executing the request.
Tip
If your extension already depends directly on Python Scripting, the same operations are also available as static functions on SEPythonScriptApp. The proxy-based approach shown below is usually preferable because it avoids introducing a compile-time dependency on that extension.
Running Python from C++#
If your goal is simply to execute Python, use the high-level SAMSON facade. You do not need to fetch the Python Scripting proxy yourself.
Run an inline Python snippet#
SAMSON::runPythonCode is the simplest entry point for short scripts generated from C++:
const std::string code = R"(
from samson import *
SAMSON.runCommand("Ligands")
SAMSON.runCommand("Licorice")
print("Applied Licorice to the current ligand selection")
)";
if (!SAMSON::runPythonCode(code, true)) {
SB_ERROR("Could not execute Python code");
}
Use the raisePythonConsole argument when you want the Python Console to be brought to the front and the output to be visible immediately:
true: useful while developing or debugging;false: useful for background execution triggered from your extension.
Run a Python file#
For reusable workflows, keep the Python logic in a .py file and execute it with SAMSON::runPythonFile:
const QString scriptPath =
QDir::cleanPath(QString::fromStdString(SB_ELEMENT_PATH) + "/Resource/scripts/my_workflow.py");
if (!QFileInfo::exists(scriptPath)) {
SB_ERROR("Missing Python script: " + scriptPath.toStdString());
return;
}
if (!SAMSON::runPythonFile(scriptPath.toStdString(), true)) {
SB_ERROR("Could not execute Python file");
}
This is usually the better choice when:
- the script is more than a few lines long;
- you want to ship Python workflows with your extension;
- you want users to be able to inspect or modify the script.
Note
Internally, runPythonFile executes the file in the embedded IPython namespace. In practice, this means the file runs in the same interpreter state as the Python Console, with the SAMSON Python bindings already available.
Managing the embedded Python environment#
Package management is exposed by the Python Scripting extension through the SEPythonScriptApp app interface. This is the API you should use when your extension needs to prepare Python dependencies before running a script.
Tip
You can see all the exposed functionality per extension and per class in the Exposed Functionality Viewer directly in SAMSON.
The examples below assume the usual SAMSON and Qt headers are already included, such as SAMSON.hpp, SBProxy.hpp, SBFunction.hpp, QDir, and QFileInfo.
Step 1: resolve the Python Scripting proxy#
SBProxy* getPythonScriptingProxy() {
const SBUUID pythonScriptingUUID("7B654CE6-E38C-B97F-6746-4FD6934487C2");
static SBProxy* proxy = SAMSON::getProxy(
"SEPythonScriptApp",
pythonScriptingUUID);
if (!proxy || !proxy->getInterface()) {
SB_ERROR("Python Scripting is not available");
return nullptr;
}
return proxy;
}
Once you have the proxy, retrieve the functions you need from its interface.
Note
When a function is overloaded, pass typed SB_VALUE(...) placeholders to getFunction(...) so SAMSON can resolve the correct signature.
Step 2: check whether a package is installed#
The code below demonstrates how you can check whether a specific Python package is installed and its version.
SBProxy* proxy = getPythonScriptingProxy();
if (!proxy) return;
SBFunction* hasPackageFunction =
proxy->getInterface()->getFunction(
"hasPackage",
SB_VALUE<const std::string&>(""));
SBFunction* getPackageVersionFunction =
proxy->getInterface()->getFunction(
"getPackageVersion",
SB_VALUE<const std::string&>(""));
const std::string packageName = "numpy";
if (hasPackageFunction && getPackageVersionFunction) {
SBValue hasPackageValue =
hasPackageFunction->call(
0,
SB_VALUE<const std::string&>(packageName));
SBValue versionValue =
getPackageVersionFunction->call(
0,
SB_VALUE<const std::string&>(packageName));
const bool hasPackage =
SB_CAN_CAST<bool>(hasPackageValue) && SB_CAST<bool>(hasPackageValue);
const std::string version =
SB_CAN_CAST<std::string>(versionValue) ? SB_CAST<std::string>(versionValue) : "";
if (hasPackage) {
SB_INFORMATION("numpy " + version + " is installed");
}
}
This is the typical first step before launching a Python-based workflow from your extension.
Step 3: install or uninstall a package#
To install the latest version of a package, call the one-argument overload of addPackage. To install a specific version, resolve the two-argument overload and pass an exact version string.
For the latest version, resolve the overload like this:
SBProxy* proxy = getPythonScriptingProxy();
if (!proxy) return;
SBFunction* addLatestPackageFunction =
proxy->getInterface()->getFunction(
"addPackage",
SB_VALUE<const std::string&>(""));
SBProxy* proxy = getPythonScriptingProxy();
if (!proxy) return;
SBFunction* addPackageFunction =
proxy->getInterface()->getFunction(
"addPackage",
SB_VALUE<const std::string&>(""),
SB_VALUE<const std::string&>(""));
SBFunction* removePackageFunction =
proxy->getInterface()->getFunction(
"removePackage",
SB_VALUE<const std::string&>(""));
const std::string packageName = "scipy";
const std::string exactVersion = "1.14.1";
if (addPackageFunction) {
SBValue ret = addPackageFunction->call(
0,
SB_VALUE<const std::string&>(packageName),
SB_VALUE<const std::string&>(exactVersion));
const bool installed = SB_CAN_CAST<bool>(ret) && SB_CAST<bool>(ret);
if (!installed) {
SB_ERROR("Could not install scipy");
}
}
if (removePackageFunction) {
SBValue ret =
removePackageFunction->call(
0,
SB_VALUE<const std::string&>(packageName));
const bool removed = SB_CAN_CAST<bool>(ret) && SB_CAST<bool>(ret);
if (!removed) {
SB_ERROR("Could not uninstall scipy");
}
}
Note
The exposed addPackage(name, version) function is intentionally simple:
- if
versionis empty, it installs the latest available version; - if
versionis set, it installsname==version.
If you need a more complex pip install command, prefer installPythonRequirementsFromFile(...) or let the user use the Package Manager UI in the Python Scripting App.
Step 4: install dependencies from a requirements.txt#
This is the most convenient option when your extension relies on several Python packages:
SBProxy* proxy = getPythonScriptingProxy();
if (!proxy) return;
SBFunction* installRequirementsFunction =
proxy->getInterface()->getFunction(
"installPythonRequirementsFromFile",
SB_VALUE<const std::string&>(""));
const QString requirementsPath =
QDir::cleanPath(QString::fromStdString(SB_ELEMENT_PATH) +
"/Resource/python/requirements.txt");
if (!QFileInfo::exists(requirementsPath)) {
SB_ERROR("Missing requirements file: " + requirementsPath.toStdString());
return;
}
if (installRequirementsFunction) {
SBValue ret = installRequirementsFunction->call(
0,
SB_VALUE<const std::string&>(requirementsPath.toStdString()));
const bool installed = SB_CAN_CAST<bool>(ret) && SB_CAST<bool>(ret);
if (!installed) {
SB_ERROR("Could not install Python requirements");
}
}
This is equivalent to running python -m pip install -r requirements.txt in SAMSON's embedded Python environment.
Step 5: install a local package#
If your workflow depends on a local Python package, use installLocalPackage:
SBProxy* proxy = getPythonScriptingProxy();
if (!proxy) return;
SBFunction* installLocalPackageFunction =
proxy->getInterface()->getFunction(
"installLocalPackage",
SB_VALUE<const std::string&>(""),
SB_VALUE<bool>(false));
const QString packagePath = "C:/work/my_python_package";
const bool editableMode = true;
if (!QFileInfo::exists(packagePath)) {
SB_ERROR("Missing local package: " + packagePath.toStdString());
return;
}
if (installLocalPackageFunction) {
SBValue ret = installLocalPackageFunction->call(
0,
SB_VALUE<const std::string&>(packagePath.toStdString()),
SB_VALUE<bool>(editableMode));
const bool installed = SB_CAN_CAST<bool>(ret) && SB_CAST<bool>(ret);
if (!installed) {
SB_ERROR("Could not install local Python package");
}
}
Set editableMode to true when you want behavior equivalent to pip install -e.
Optional: get the path to SAMSON's Python executable#
If you need to launch the bundled Python interpreter explicitly, query it through the same proxy:
SBProxy* proxy = getPythonScriptingProxy();
if (!proxy) return;
SBFunction* getPythonPathFunction = proxy->getInterface()->getFunction("getPythonPath");
if (!getPythonPathFunction) return;
SBValue ret = getPythonPathFunction->call(0);
if (SB_CAN_CAST<std::string>(ret)) {
const std::string pythonPath = SB_CAST<std::string>(ret);
SB_INFORMATION("SAMSON Python: " + pythonPath);
}
Typical developer workflow#
In practice, extensions that depend on Python usually follow this pattern:
- Resolve the Python Scripting proxy.
- Check whether the required package is already installed.
- Install missing dependencies using
addPackage(...),installPythonRequirementsFromFile(...), orinstallLocalPackage(...). - Execute the Python workflow with
SAMSON::runPythonFile(...)orSAMSON::runPythonCode(...).
This keeps Python environment setup separate from actual script execution, which usually makes the extension easier to debug and maintain.
Best practices#
- Prefer
SAMSON::runPythonFile(...)overrunPythonCode(...)for non-trivial workflows. - Keep Python scripts in your extension resources so they can be versioned and tested separately from the C++ code.
- Use
raisePythonConsole = truewhile developing to see Python output and tracebacks immediately. - Install several dependencies from a
requirements.txtinstead of chaining manyaddPackage(...)calls. - Use the proxy interface only for Python-environment management. For execution, the
SAMSON::runPython...facade is simpler.