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