Mantid
Loading...
Searching...
No Matches
RunPythonScript.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
15
16#include <boost/python/call_method.hpp>
17#include <boost/python/exec.hpp>
18#include <boost/python/extract.hpp>
19#include <boost/python/import.hpp>
20#include <boost/python/to_python_value.hpp>
21#include <boost/regex.hpp>
22#include <fstream>
23
25
26using namespace API;
27using namespace Kernel;
28
30const std::string RunPythonScript::name() const { return "RunPythonScript"; }
31
33int RunPythonScript::version() const { return 1; }
34
36const std::string RunPythonScript::category() const { return "DataHandling\\LiveData\\Support"; }
37
39const std::string RunPythonScript::summary() const { return "Executes a snippet of Python code"; }
40
45bool RunPythonScript::checkGroups() { return false; }
46
52 std::make_unique<WorkspaceProperty<Workspace>>("InputWorkspace", "", Direction::Input, PropertyMode::Optional),
53 "An input workspace that the python code will modify."
54 "The workspace will be in the python variable named 'input'.");
55 declareProperty("Code", "", "Python code (can be on multiple lines).");
56 declareProperty(std::make_unique<FileProperty>("Filename", "", FileProperty::OptionalLoad, "py"),
57 "A File containing a python script");
59 std::make_unique<WorkspaceProperty<Workspace>>("OutputWorkspace", "", Direction::Output, PropertyMode::Optional),
60 "An output workspace to be produced by the python code."
61 "The workspace will be in the python variable named 'output'.");
62}
63
64std::map<std::string, std::string> RunPythonScript::validateInputs() {
65 std::map<std::string, std::string> out;
66
67 bool hasCode = (!this->getPropertyValue("Code").empty());
68 bool hasFile = (!this->getPropertyValue("Filename").empty());
69
70 std::string msg;
71 if ((!hasCode) && (!hasFile)) {
72 msg = "Must specify python to execute";
73 }
74
75 if (!msg.empty()) {
76 out["Code"] = msg;
77 out["Filename"] = msg;
78 }
79
80 return out;
81}
82
83//----------------------------------------------------------------------------------------------
88 setProperty<Workspace_sptr>("OutputWorkspace", outputWS);
89}
90
103std::string RunPythonScript::scriptCode() const {
104 std::string userCode = getPropertyValue("Code");
105 std::string filename = getPropertyValue("Filename");
106
107 // fill userCode with the contents of the supplied file if it wasn't already
108 // supplied
109 if (userCode.empty() && (!filename.empty())) {
110 std::ifstream handle(filename.c_str(), std::ios_base::in);
111 if (!handle.is_open()) {
112 // throw exception if file cannot be opened
113 std::stringstream errss;
114 errss << "Unable to open file " << filename;
115 throw std::runtime_error(errss.str());
116 }
117
118 std::stringstream buffer;
119 buffer << handle.rdbuf();
120 userCode = buffer.str();
121 }
122
123 if (userCode.empty()) {
124 throw std::runtime_error("Python script is empty");
125 }
126
127 // Unify line endings
128 boost::regex eol("\\R"); // \R is Perl syntax for matching any EOL sequence
129 userCode = boost::regex_replace(userCode, eol, "\n"); // converts all to LF
130
131 // Wrap and indent the user code (see method documentation)
132 std::istringstream is(userCode);
133 std::ostringstream os;
134 const char *indent = " ";
135 os << "import mantid\n"
136 << "from mantid.simpleapi import *\n"
137 << "class _DUMMY_ALG(mantid.api.PythonAlgorithm):\n"
138 << indent << "def PyExec(self, input=None,output=None):\n";
139 std::string line;
140 while (getline(is, line)) {
141 os << indent << indent << line << "\n";
142 }
143 os << indent << indent << "return input,output\n"; // When executed the global scope needs to know
144 // about input,output so we return them
145 os << "input,output = _DUMMY_ALG().PyExec(input,output)";
146
147 if (g_log.is(Kernel::Logger::Priority::PRIO_DEBUG))
148 g_log.debug() << "Full code to be executed:\n" << os.str() << "\n";
149 return os.str();
150}
151
163std::shared_ptr<API::Workspace> RunPythonScript::executeScript(const std::string &script) const {
164 using namespace API;
165 using namespace boost::python;
166
167 // Execution
169 auto locals = doExecuteScript(script);
170 return extractOutputWorkspace(locals);
171}
172
181boost::python::dict RunPythonScript::doExecuteScript(const std::string &script) const {
182 // Retrieve the main module.
183 auto main = boost::python::import("__main__");
184 // Retrieve the main module's namespace
185 boost::python::object globals(main.attr("__dict__"));
186 boost::python::dict locals = buildLocals();
187 try {
188 boost::python::exec(script.c_str(), globals, locals);
189 } catch (boost::python::error_already_set &) {
190 throw PythonException();
191 }
192 return locals;
193}
194
202boost::python::dict RunPythonScript::buildLocals() const {
203 // Define the local variable names required by the script, in this case
204 // - input: Points to input workspace if one has been given
205 // - output: Will point to the output workspace if one has been given
206 using namespace boost::python;
207
208 dict locals;
209 locals["input"] = object(); // default to None
210 locals["output"] = object();
211
212 API::Workspace_sptr inputWS = getProperty("InputWorkspace");
213 if (inputWS) {
214 locals["input"] = object(handle<>(to_python_value<API::Workspace_sptr>()(inputWS)));
215 }
216 std::string outputWSName = getPropertyValue("OutputWorkspace");
217 if (!outputWSName.empty()) {
218 locals["output"] = object(handle<>(to_python_value<const std::string &>()(outputWSName)));
219 }
220 return locals;
221}
222
229std::shared_ptr<API::Workspace> RunPythonScript::extractOutputWorkspace(const boost::python::dict &locals) const {
230 using namespace API;
231 using namespace boost::python;
232
233 // Might be None, string or a workspace object
234 auto pyoutput = locals.get("output");
235 if (isNone(pyoutput))
236 return Workspace_sptr();
237
238 auto ptrExtract = ExtractSharedPtr<API::Workspace>(pyoutput);
239 if (ptrExtract.check()) {
240 return ptrExtract();
241 } else {
242 extract<std::string> extractString(pyoutput);
243 if (extractString.check()) {
244 // Will raise an error if the workspace does not exist as the user
245 // requested
246 // an output workspace
247 // but didn't create one.
248 return AnalysisDataService::Instance().retrieve(extractString());
249 } else {
250 throw std::runtime_error("Invalid type assigned to 'output' variable. Must "
251 "be a string or a Workspace object");
252 }
253 }
254}
255
256} // namespace Mantid::PythonInterface
PeakIndexingStats main
Definition: IndexPeaks.cpp:164
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
Definition: Algorithm.cpp:1913
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
Definition: Algorithm.cpp:2026
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Definition: Algorithm.cpp:2076
Kernel::Logger & g_log
Definition: Algorithm.h:451
@ OptionalLoad
to specify a file to read but the file doesn't have to exist
Definition: FileProperty.h:53
A property class for workspaces.
void debug(const std::string &msg)
Logs at debug level.
Definition: Logger.cpp:114
bool is(int level) const
Returns true if at least the given log level is set.
Definition: Logger.cpp:146
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
Defines a structure for acquiring/releasing the Python GIL using the RAII pattern.
Exception type that captures the current Python error state as a generic C++ exception for any genera...
Definition: ErrorHandling.h:24
boost::python::dict doExecuteScript(const std::string &script) const
Execute the code in the given local context.
void exec() override
Execute the algorithm.
const std::string summary() const override
function returns a summary message that will be displayed in the default GUI, and in the help.
int version() const override
Algorithm's version for identification.
void init() override
Initialize the algorithm's properties.
std::map< std::string, std::string > validateInputs() override
Perform validation of ALL the input properties of the algorithm.
std::shared_ptr< API::Workspace > executeScript(const std::string &script) const
Sets up the code context & executes it.
const std::string name() const override
Algorithm's name for identification.
std::shared_ptr< API::Workspace > extractOutputWorkspace(const boost::python::dict &locals) const
Extracts any output workspace pointer that was created.
std::string scriptCode() const
Return the code string to execute.
bool checkGroups() override
Override standard group behaviour so that the algorithm is only called once for the whole group.
boost::python::dict buildLocals() const
Builds the local dictionary that defines part of the execution context of the script.
const std::string category() const override
Algorithm's category for identification.
std::shared_ptr< Workspace > Workspace_sptr
shared pointer to Mantid::API::Workspace
Definition: Workspace_fwd.h:20
bool isNone(PyObject *ptr)
Definition: IsNone.h:26
@ Input
An input workspace.
Definition: Property.h:53
@ Output
An output workspace.
Definition: Property.h:54