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