Flow123d  release_2.1.0-84-g6a13a75
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 string python_sys_path = R"CODE(
34 
35 def get_paths():
36  import sys
37  import os
38  # print('\n'.join(sys.path))
39  return os.pathsep.join(sys.path)
40 
41 )CODE";
42 
43 // default value
44 string PythonLoader::sys_path = "";
45 
46 
47 void PythonLoader::initialize(const std::string &python_home)
48 {
49  static internal::PythonRunning _running(python_home);
50 }
51 
52 
53 PyObject * PythonLoader::load_module_from_file(const std::string& fname) {
54  initialize();
55 
56  // don't know direct way how to load module form file, so we read it into string and use load_module_from_string
57  std::ifstream file_stream;
58  // check correct openning
59  FilePath(fname, FilePath::input_file).open_stream(file_stream);
60  file_stream.exceptions ( ifstream::failbit | ifstream::badbit );
61 
62  std::stringstream buffer;
63  buffer << file_stream.rdbuf();
64 
65  string module_name;
66  unsigned int pos = fname.rfind("/");
67  if (pos != string::npos)
68  module_name = fname.substr(pos+1);
69  else
70  module_name = fname;
71 
72  // cout << "python module: " << module_name <<endl;
73  // DebugOut() << buffer.str() << "\n";
74  // TODO: use exceptions and catch it here to produce shorter and more precise error message
75  return load_module_from_string(module_name, buffer.str() );
76 }
77 
78 
79 
80 PyObject * PythonLoader::load_module_from_string(const std::string& module_name, const std::string& source_string) {
81  initialize();
82  // compile string and check for errors
83  PyObject * compiled_string = Py_CompileString(source_string.c_str(), module_name.c_str(), Py_file_input);
84  PythonLoader::check_error();
85 
86  // import modle and check for error
87  PyObject * result = PyImport_ExecCodeModule(module_name.c_str(), compiled_string);
88  PythonLoader::check_error();
89  return result;
90 }
91 
92 PyObject * PythonLoader::load_module_by_name(const std::string& module_name) {
93  initialize();
94 
95  // import module by dot separated path and its name
96  PyObject * module_object = PyImport_ImportModule (module_name.c_str());
97  PythonLoader::check_error();
98 
99  return module_object;
100 }
101 
102 
103 void PythonLoader::check_error() {
104  if (PyErr_Occurred()) {
105  PyObject *ptype, *pvalue, *ptraceback;
106  PyErr_Fetch(&ptype, &pvalue, &ptraceback);
107  string error_description = string(PyUnicode_AsUTF8(PyObject_Str(pvalue)));
108 
109  // clear error indicator
110  PyErr_Clear();
111 
112  /* See if we can get a full traceback */
113  PyObject *traceback_module = PyImport_ImportModule("traceback");
114 
115  string str_traceback;
116  if (traceback_module && ptraceback) {
117  PyObject *format_tb_method = PyObject_GetAttrString(traceback_module, "format_tb");
118  if (format_tb_method) {
119  PyObject * traceback_lines = PyObject_CallFunctionObjArgs(format_tb_method, ptraceback, NULL);
120  if (traceback_lines) {
121  PyObject * join_str = PyUnicode_FromString("");
122  PyObject * join = PyObject_GetAttrString(join_str, "join");
123  PyObject * message = PyObject_CallFunctionObjArgs(join, traceback_lines, NULL);
124  if (message) {
125  str_traceback = string(PyUnicode_AsUTF8(PyObject_Str(message)));
126  }
127  Py_DECREF(traceback_lines);
128  Py_DECREF(join_str);
129  Py_DECREF(join);
130  Py_DECREF(message);
131  }
132  }
133  }
134 
135  // get value of python's "sys.path"
136  string python_path = PythonLoader::sys_path;
137  replace(python_path.begin(), python_path.end(), ':', '\n');
138 
139  // construct error message
140  string py_message =
141  "\nType: " + string(PyUnicode_AsUTF8(PyObject_Str(ptype))) + "\n"
142  + "Message: " + string(PyUnicode_AsUTF8(PyObject_Str(pvalue))) + "\n"
143  + "Traceback: \n" + str_traceback + "\n"
144  + "Paths: " + "\n" + python_path + "\n";
145 
146  THROW(ExcPythonError() << EI_PythonMessage( py_message ));
147  }
148 }
149 
150 
151 
152 PyObject * PythonLoader::get_callable(PyObject *module, const std::string &func_name) {
153  char func_char[func_name.size()+2];
154  strcpy(func_char, func_name.c_str());
155  PyObject * func = PyObject_GetAttrString(module, func_char );
156  PythonLoader::check_error();
157 
158  if (! PyCallable_Check(func)) {
159  stringstream ss;
160  ss << "Field '" << func_name << "' from the python module: " << PyModule_GetName(module) << " is not callable." << endl;
161  THROW(ExcPythonError() << EI_PythonMessage( ss.str() ));
162  }
163 
164  return func;
165 }
166 
167 
168 
169 wstring to_py_string(const string &str) {
170  wchar_t wbuff[ str.size() ];
171  size_t wstr_size = mbstowcs( wbuff, str.c_str(), str.size() );
172  return wstring( wbuff, wstr_size );
173 }
174 
175 string from_py_string(const wstring &wstr) {
176  char buff[ wstr.size() ];
177  size_t str_size = wcstombs( buff, wstr.c_str(), wstr.size() );
178  return string( buff, str_size );
179 }
180 
181 #define STR_EXPAND(tok) #tok
182 #define STR(tok) string(STR_EXPAND(tok))
183 
184 namespace internal {
185 
186 PythonRunning::PythonRunning(const std::string& program_name)
187 {
188 #ifdef FLOW123D_PYTHON_PREFIX
189  static wstring _python_program_name = to_py_string(program_name);
190  Py_SetProgramName( &(_python_program_name[0]) );
191  wstring full_program_name = Py_GetProgramFullPath();
192  // cout << "full program name: " << from_py_string(full_program_name) << std::endl;
193 
194  // try to find string "flow123d" from right side of program_name
195  // if such a string is not present, we are most likely unit-testing
196  // in that case, full_flow_prefix is current dir '.'
197  size_t pos = full_program_name.rfind( to_py_string("flow123d") );
198  wstring full_flow_prefix = ".";
199  if (pos != wstring::npos) {
200  full_flow_prefix = full_program_name.substr(0,pos-string("/bin/").size() );
201  }
202  // cout << "full flow prefix: " << from_py_string(full_flow_prefix) << std::endl;
203  wstring default_py_prefix(to_py_string(STR(FLOW123D_PYTHON_PREFIX)));
204  // cout << "default py prefix: " << from_py_string(default_py_prefix) << std::endl;
205 
206  static wstring our_py_home(full_flow_prefix + ":" +default_py_prefix);
207  Py_SetPythonHome( &(our_py_home[0]) );
208 
209 #endif //FLOW123D_PYTHON_PREFIX
210 
211  // initialize the Python interpreter.
212  Py_Initialize();
213 
214 #ifdef FLOW123D_PYTHON_EXTRA_MODULES_PATH
215  // update module path, first get current system path (Py_GetPath)
216  // than append flow123d Python modules path to sys.path
217  wstring path = Py_GetPath();
218  path = path + to_py_string(":") + to_py_string(string(FLOW123D_PYTHON_EXTRA_MODULES_PATH));
219  PySys_SetPath (path.c_str());
220 
221 
222 #endif //FLOW123D_PYTHON_EXTRA_MODULES_PATH
223 
224  // call python and get paths available
225  PyObject *moduleMain = PyImport_ImportModule("__main__");
226  PyRun_SimpleString(python_sys_path.c_str());
227  PyObject *func = PyObject_GetAttrString(moduleMain, "get_paths");
228  PyObject *args = PyTuple_New (0);
229  PyObject *result = PyObject_CallObject(func, args);
230  PythonLoader::check_error();
231 
232  // save value so we dont have to call python again
233  PythonLoader::sys_path = string(PyUnicode_AsUTF8(result));
234 }
235 
236 
237 
238 PythonRunning::~PythonRunning() {
239  Py_Finalize();
240 }
241 
242 } // close namespace internal
243 
244 #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