Flow123d  last_with_con_2.0.0-4-g42e6930
python_loader.cc
Go to the documentation of this file.
1 /*!
2  *
3  * Copyright (C) 2015 Technical University of Liberec. All rights reserved.
4  *
5  * This program is free software; you can redistribute it and/or modify it under
6  * the terms of the GNU General Public License version 3 as published by the
7  * Free Software Foundation. (http://www.gnu.org/licenses/gpl-3.0.en.html)
8  *
9  * This program is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12  *
13  *
14  * @file python_loader.cc
15  * @brief
16  */
17 
18 #include "global_defs.h"
19 
20 #ifdef FLOW123D_HAVE_PYTHON
21 
22 #include "system/python_loader.hh"
23 
24 #include "system/system.hh"
25 #include "system/file_path.hh"
26 #include <string>
27 #include <iostream>
28 #include <fstream>
29 #include <sstream>
30 
31 using namespace std;
32 
33 void PythonLoader::initialize(const std::string &python_home)
34 {
35  static internal::PythonRunning _running(python_home);
36 }
37 
38 
39 PyObject * PythonLoader::load_module_from_file(const std::string& fname) {
40  initialize();
41 
42  // don't know direct way how to load module form file, so we read it into string and use load_module_from_string
43  std::ifstream file_stream;
44  // check correct openning
45  FilePath(fname, FilePath::input_file).open_stream(file_stream);
46  file_stream.exceptions ( ifstream::failbit | ifstream::badbit );
47 
48  std::stringstream buffer;
49  buffer << file_stream.rdbuf();
50 
51  string module_name;
52  unsigned int pos = fname.rfind("/");
53  if (pos != string::npos)
54  module_name = fname.substr(pos+1);
55  else
56  module_name = fname;
57 
58  // cout << "python module: " << module_name <<endl;
59  // DebugOut() << buffer.str() << "\n";
60  // TODO: use exceptions and catch it here to produce shorter and more precise error message
61  return load_module_from_string(module_name, buffer.str() );
62 }
63 
64 
65 
66 PyObject * PythonLoader::load_module_from_string(const std::string& module_name, const std::string& source_string) {
67  initialize();
68 
69  // for unknown reason module name is non-const, so we have to make char * copy
70  char * tmp_name = new char[ module_name.size() + 2 ];
71  strcpy( tmp_name, module_name.c_str() );
72 
73  PyObject * compiled_string = Py_CompileString( source_string.c_str(), module_name.c_str(), Py_file_input );
74  if (PyErr_Occurred()) {
75  PyObject *ptype, *pvalue, *ptraceback, *pystr;
76  PyErr_Fetch(&ptype, &pvalue, &ptraceback);
77 
78  if ( !PyString_Check(pvalue) ) { // syntax error
79  stringstream ss;
80  PyObject* err_type = PySequence_GetItem(pvalue, 0);
81  pystr = PyObject_Str(err_type);
82  ss << PyString_AsString(pystr) << endl;
83  PyObject* descr = PySequence_GetItem(pvalue, 1);
84  PyObject* file = PySequence_GetItem(descr, 0);
85  pystr = PyObject_Str(file);
86  ss << " File \"" << PyString_AsString(pystr) << "\"";
87  PyObject* row = PySequence_GetItem(descr, 1);
88  pystr = PyObject_Str(row);
89  ss << ", line " << PyString_AsString(pystr) << endl;
90  PyObject* line = PySequence_GetItem(descr, 3);
91  pystr = PyObject_Str(line);
92  ss << PyString_AsString(pystr);
93  PyObject* col = PySequence_GetItem(descr, 2);
94  pystr = PyObject_Str(col);
95  int pos = atoi( PyString_AsString(pystr) );
96  ss << setw(pos) << "^" << endl;
97  THROW(ExcPythonError() << EI_PythonMessage( ss.str() ));
98  } else {
99  THROW(ExcPythonError() << EI_PythonMessage( PyString_AsString(pvalue) ));
100  }
101  }
102 
103  PyObject * result = PyImport_ExecCodeModule(tmp_name, compiled_string);
104  PythonLoader::check_error();
105 
106  delete[] tmp_name;
107  return result;
108 }
109 
110 PyObject * PythonLoader::load_module_by_name(const std::string& module_name) {
111  initialize();
112 
113  // import module by dot separated path and its name
114  PyObject * module_object = PyImport_ImportModule (module_name.c_str());
115  PythonLoader::check_error();
116 
117  return module_object;
118 }
119 
120 
121 void PythonLoader::check_error() {
122  if (PyErr_Occurred()) {
123  PyObject *ptype, *pvalue, *ptraceback;
124 
125  PyErr_Fetch(&ptype, &pvalue, &ptraceback);
126  string error_description = string(PyString_AsString(PyObject_Str(pvalue)));
127 
128  /* See if we can get a full traceback */
129  PyObject *module_name = PyString_FromString("traceback");
130  PyObject *pyth_module = PyImport_Import(module_name);
131  Py_DECREF(module_name);
132 
133  string str_traceback;
134  if (pyth_module) {
135  PyObject *pyth_func = PyObject_GetAttrString(pyth_module, "format_exception");
136  if (pyth_func && PyCallable_Check(pyth_func)) {
137  PyObject *pyth_val = PyObject_CallFunctionObjArgs(pyth_func, ptype, pvalue, ptraceback, NULL);
138  if (pyth_val) {
139  str_traceback = string(PyString_AsString(PyObject_Str(pyth_val)));
140  Py_DECREF(pyth_val);
141  }
142  }
143  }
144 
145  string py_message =
146  "\nType: " + string(PyString_AsString(PyObject_Str(ptype))) + "\n"
147  + "Message: " + string(PyString_AsString(PyObject_Str(pvalue))) + "\n"
148  + "Traceback: " + str_traceback;
149 
150  THROW(ExcPythonError() << EI_PythonMessage( py_message ));
151  }
152 }
153 
154 
155 
156 PyObject * PythonLoader::get_callable(PyObject *module, const std::string &func_name) {
157  char func_char[func_name.size()+2];
158  strcpy(func_char, func_name.c_str());
159  PyObject * func = PyObject_GetAttrString(module, func_char );
160  PythonLoader::check_error();
161 
162  if (! PyCallable_Check(func)) {
163  stringstream ss;
164  ss << "Field '" << func_name << "' from the python module: " << PyModule_GetName(module) << " is not callable." << endl;
165  THROW(ExcPythonError() << EI_PythonMessage( ss.str() ));
166  }
167 
168  return func;
169 }
170 
171 
172 
173 wstring to_py_string(const string &str) {
174  wchar_t wbuff[ str.size() ];
175  size_t wstr_size = mbstowcs( wbuff, str.c_str(), str.size() );
176  return wstring( wbuff, wstr_size );
177 }
178 
179 string from_py_string(const wstring &wstr) {
180  char buff[ wstr.size() ];
181  size_t str_size = wcstombs( buff, wstr.c_str(), wstr.size() );
182  return string( buff, str_size );
183 }
184 
185 // currently we support only Python 2.7
186 //
187 #if FLOW123D_PYTHONLIBS_VERSION_MAJOR<3
188  #define to_py_string string
189  #define from_py_string string
190  #define PY_STRING string
191 #else
192  #define PY_STRING wstring
193 #endif
194 
195 #define STR_EXPAND(tok) #tok
196 #define STR(tok) string(STR_EXPAND(tok))
197 
198 namespace internal {
199 
200 PythonRunning::PythonRunning(const std::string& program_name)
201 {
202 #ifdef FLOW123D_PYTHON_PREFIX
203  static PY_STRING _python_program_name = to_py_string(program_name);
204  Py_SetProgramName( &(_python_program_name[0]) );
205  PY_STRING full_program_name = Py_GetProgramFullPath();
206  // cout << "full program name: " << from_py_string(full_program_name) << std::endl;
207 
208  // try to find string "flow123d" from right side of program_name
209  // if such a string is not present, we are most likely unit-testing
210  // in that case, full_flow_prefix is current dir '.'
211  size_t pos = full_program_name.rfind( to_py_string("flow123d") );
212  PY_STRING full_flow_prefix = ".";
213  if (pos != PY_STRING::npos) {
214  full_flow_prefix = full_program_name.substr(0,pos-string("/bin/").size() );
215  }
216  // cout << "full flow prefix: " << from_py_string(full_flow_prefix) << std::endl;
217  PY_STRING default_py_prefix(to_py_string(STR(FLOW123D_PYTHON_PREFIX)));
218  // cout << "default py prefix: " << from_py_string(default_py_prefix) << std::endl;
219 
220  static PY_STRING our_py_home(full_flow_prefix + ":" +default_py_prefix);
221  Py_SetPythonHome( &(our_py_home[0]) );
222 
223  /*
224  Py_GetPath();
225 
226  static PY_STRING our_py_path;
227  string python_subdir("/lib/python" + STR(FLOW123D_PYTHONLIBS_VERSION_MAJOR) + "." + STR(FLOW123D_PYTHONLIBS_VERSION_MINOR));
228  our_py_path+=full_flow_prefix + to_py_string( python_subdir + "/:");
229  our_py_path+=full_flow_prefix + to_py_string( python_subdir + "/plat-cygwin:");
230  our_py_path+=full_flow_prefix + to_py_string( python_subdir + "/lib-dynload:");
231  our_py_path+=default_py_prefix + to_py_string( python_subdir + "/lib-dynload:");
232  our_py_path+=default_py_prefix + to_py_string( python_subdir + "/lib-dynload:");
233  our_py_path+=default_py_prefix + to_py_string( python_subdir + "/lib-dynload:");
234 
235  Py_SetPath(our_py_path);
236  //our_py_path+=full_flow_prefix + to_py_string( python_subdir);
237 
238 // string prefix = ;
239  */
240  // cout << "Python path: " << from_py_string( Py_GetPath() ) << std::endl;
241  // cout << "Python home: " << from_py_string( Py_GetPythonHome() ) << std::endl;
242  // cout << "Python prefix: " << from_py_string( Py_GetPrefix() ) << std::endl;
243  // cout << "Python exec prefix: " << from_py_string( Py_GetExecPrefix() ) << std::endl;
244 
245  // 1. set program name
246  // 2. get prefix
247  // 3. get python full path
248  // 4. extract prefix from the full path
249  // 5. substitute the prefix in python path
250  // 6. set python path (suppress its computation during Py_Initialize)
251  /*
252  wchar_t wbuf[ python_home.size() ];
253  size_t num_chars = mbstowcs( wbuf, python_home.c_str(), python_home.size() );
254  static wstring _python_home_storage( wbuf, num_chars ); // permanent non-const storage required
255 
256  std::wcout << "new python home: " << _python_home_storage << std::endl;
257 
258  Py_SetProgramName( &(_python_home_storage[0]) );
259 
260  char buff[ 1024 ];
261  num_chars = wcstombs(buff, Py_GetPath(), 256);
262  buff[1024]=0;
263  string str(buff, num_chars);
264  std::cout << "Python path: " << str << std::endl;
265 
266 
267 
268  wchar_t wbuf[ python_home.size() ];
269  size_t num_chars = mbstowcs( wbuf, python_home.c_str(), python_home.size() );
270  static wstring _python_home_storage( wbuf, num_chars ); // permanent non-const storage required
271 
272  std::wcout << "new python home: " << _python_home_storage << std::endl;
273 
274  Py_SetProgramName( &(_python_home_storage[0]) );
275 
276  char buff[ 1024 ];
277  num_chars = wcstombs(buff, Py_GetPath(), 1024);
278  //std::cout << "num chars: " << num_chars << std::endl;
279  std::cout << "Python path: " << buff << std::endl;
280  //num_chars = wcstombs(buff, Py_GetPythonHome(), 1024);
281  //std::cout << "Python home: " << buff << std::endl;
282  num_chars = wcstombs(buff, Py_GetProgramName(), 1024);
283  std::cout << "Python prog. name: " << buff << std::endl;
284  num_chars = wcstombs(buff, Py_GetPrefix(), 1024);
285  std::cout << "Python prefix: " << buff << std::endl;
286  num_chars = wcstombs(buff, Py_GetProgramFullPath(), 1024);
287  std::cout << "Python full: " << buff << std::endl;
288 */
289 #endif //FLOW123D_PYTHON_PREFIX
290 
291  // initialize the Python interpreter.
292  Py_Initialize();
293 
294 #ifdef FLOW123D_PYTHON_EXTRA_MODULES_PATH
295  // update module path, first get current system path (Py_GetPath)
296  // than append flow123d Python modules path to sys.path
297  std::string path = Py_GetPath();
298  path = path + ":" + std::string(FLOW123D_PYTHON_EXTRA_MODULES_PATH);
299  // conversion to non const char
300  char * path_char = const_cast<char *>(path.c_str());
301  PySys_SetPath (path_char);
302 #endif //FLOW123D_PYTHON_EXTRA_MODULES_PATH
303 }
304 
305 
306 
307 PythonRunning::~PythonRunning() {
308  Py_Finalize();
309 }
310 
311 } // close namespace internal
312 
313 #endif // FLOW123D_HAVE_PYTHON
void open_stream(Stream &stream) const
Definition: file_path.cc:211
Global macros to enhance readability and debugging, general constants.
Dedicated class for storing path to input and output files.
Definition: file_path.hh:48
#define THROW(whole_exception_expr)
Wrapper for throw. Saves the throwing point.
Definition: exceptions.hh:45