From: Diane Trout Date: Fri, 28 Jul 2006 01:11:50 +0000 (+0000) Subject: Implement a convienence class for accessing python interpreter X-Git-Url: http://woldlab.caltech.edu/gitweb/?p=mussa.git;a=commitdiff_plain;h=065e0114a8b862eb102a93e7a30e66cd06512878 Implement a convienence class for accessing python interpreter MussaPython provides a couple of convienence functions to make it easy to grab python objects out of the python interpreter and play with them at the C++ layer. I used this to let the help action call webbrowser.open to go to our documentation. (or at least our website). Also to make my life a little safer I also wrote a simple unit test for that python interface. The one unfortuante problem is that I ended up with a cyclic library dependency which meant I needed to remove the Qt gui python wrapping code from my current python library. Hopefully I can resolve that later. --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 04e576f..2273df0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ ADD_SUBDIRECTORY( py ) SET(MAIN_SOURCES qui/mussagl.cpp) + ADD_EXECUTABLE(mussagl WIN32 MACOSX_BUNDLE ${MAIN_SOURCES} ) TARGET_LINK_LIBRARIES(mussagl diff --git a/py/CMakeLists.txt b/py/CMakeLists.txt index 7f6577c..8c1c6cf 100644 --- a/py/CMakeLists.txt +++ b/py/CMakeLists.txt @@ -18,7 +18,7 @@ IF(BOOST_PYTHON_LIBRARY) glsequence.cpp module.cpp mussa.cpp - MussaWindow.cpp + #MussaWindow.cpp nway_paths.cpp sequence.cpp ) @@ -60,3 +60,4 @@ IF(BOOST_PYTHON_LIBRARY) ELSE(BOOST_PYTHON_LIBRARY) ENDIF(BOOST_PYTHON_LIBRARY) +ADD_SUBDIRECTORY(test) diff --git a/py/module.cpp b/py/module.cpp index 91bdd11..d325fdc 100644 --- a/py/module.cpp +++ b/py/module.cpp @@ -19,5 +19,5 @@ BOOST_PYTHON_MODULE(mussa) export_mussa(); export_nway_paths(); export_sequence(); - export_mussa_window(); + //export_mussa_window(); } diff --git a/py/python.cpp b/py/python.cpp new file mode 100644 index 0000000..198d5f9 --- /dev/null +++ b/py/python.cpp @@ -0,0 +1,91 @@ +#include "py/python.hpp" + +#include + +namespace py = boost::python; + +MussaPython::MussaPython() +{ + try { + // add our mussa module to our environment + PyImport_AppendInittab("mussa", &initmussa); + // go ahead and finish initalizing python + Py_Initialize(); + // get reference to the global namespace + py::object main_module( + (py::handle<>(py::borrowed(PyImport_AddModule("__main__")))) + ); + main_namespace = main_module.attr("__dict__"); + // FIXME: this really should be a configuration file? + run("import __main__\n" + "import mussa\n" + "import webbrowser\n"); + } catch (py::error_already_set e) { + PyErr_Print(); + } +} + +void MussaPython::run(std::string code) +{ + try { + PyObject *global_ptr = main_namespace.ptr(); + py::object result( py::handle<>( + (PyRun_String(code.c_str(), Py_file_input, global_ptr, global_ptr) + ))); + } catch( py::error_already_set ) { + PyErr_Print(); + } +} + +py::object MussaPython::eval(std::string code) +{ + try { + PyObject *global_ptr = main_namespace.ptr(); + py::object result( py::handle<>( + (PyRun_String(code.c_str(), Py_eval_input, global_ptr, global_ptr) + ))); + return result; + } catch( py::error_already_set ) { + PyErr_Print(); + } + py::object result; + return result; +} + + +void MussaPython::interpreter(FILE *fp) +{ + try { + PyRun_InteractiveLoop(fp, "mussa"); + } catch (py::error_already_set e) { + PyErr_Print(); + } +} + +py::object MussaPython::operator[](std::string name) +{ + std::string code = "reduce(lambda m,n: m.__dict__[n], [__main__] + '"; + code += name; + code += "'.split('.'))"; + return eval(code); + /* + py::object py_name(name); + py::object name_list = py_name.attr("split")("."); + int name_list_len = extract(name_list.attr("__len__")()); + py::object lookup = main_namespace["__main__"]; + for(int i=0; i < name_list_len; ++i) { + lookup = lookup.attr("__dict__")(name_list[i]); + //std::cout << lookup << std::endl; + } + return lookup; + */ +} + +//! return a reference to a single mussa python interpreter +MussaPython& get_py() +{ + static MussaPython py; + return py; +} + + diff --git a/py/python.hpp b/py/python.hpp new file mode 100644 index 0000000..fc210c3 --- /dev/null +++ b/py/python.hpp @@ -0,0 +1,29 @@ +#ifndef _MUSSA_PYTHON_HPP_ +#define _MUSSA_PYTHON_HPP_ +#include +#include + +extern "C" void initmussa(); + +//! Create a singleton class to manage our reference to the python interpreter +class MussaPython { + public: + MussaPython(); + + //! pass multi-statement code block to the python interpreter + void run(std::string); + //! pass single expression to the python interpreter and return the result + boost::python::object eval(std::string); + //! launch read-eval-print loop tied to the provided FILE pointer + void interpreter(FILE *fp=stdin); + //! return an object in the python namespace + boost::python::object operator[](std::string); + + protected: + boost::python::object main_namespace; +}; + +//! return a reference to a single mussa python interpreter +MussaPython& get_py(); + +#endif // _MUSSA_PYTHON_HPP_ diff --git a/py/test/CMakeLists.txt b/py/test/CMakeLists.txt new file mode 100644 index 0000000..e189ec9 --- /dev/null +++ b/py/test/CMakeLists.txt @@ -0,0 +1,37 @@ +FIND_PACKAGE(PythonLibs) + +INCLUDE(FindBoost) +INCLUDE(Platform) + +SET(SOURCES test_python.cpp ) + +GET_MUSSA_COMPILE_FLAGS(PY_TEST_CFLAGS) +GET_MUSSA_LINK_FLAGS(PY_TEST_LDFLAGS) + +SET_SOURCE_FILES_PROPERTIES( + ${SOURCES} + COMPILE_FLAGS "${PY_TEST_CFLAGS}" +) + +ADD_EXECUTABLE(mussa_python_test ${SOURCES}) +ADD_TEST(mussa_python_test ${CMAKE_BINARY_DIR}/py/test/mussa_python_test) +LINK_DIRECTORIES(${MUSSA_BINARY_DIR}/alg}) +TARGET_LINK_LIBRARIES(mussa_python_test + mussa_core + mussa_qui + mussa_py + ${QT_LIBRARIES} + ${OPENGL_gl_LIBRARY} + ${BOOST_PROGRAM_OPTIONS_LIBRARY} + ${BOOST_FILESYSTEM_LIBRARY} + ${BOOST_PYTHON_LIBRARY} + ${PYTHON_LIBRARIES} + ${BOOST_UNIT_TEST_LIBRARY} + ) + +SET_TARGET_PROPERTIES( + mussa_python_test PROPERTIES + COMPILE_FLAGS "${PY_TEST_CFLAGS}" + LINK_FLAGS "${PY_TEST_LDFLAGS}" +) + diff --git a/py/test/test_python.cpp b/py/test/test_python.cpp new file mode 100644 index 0000000..c33b7ad --- /dev/null +++ b/py/test/test_python.cpp @@ -0,0 +1,29 @@ +#define BOOST_AUTO_TEST_MAIN +#include + +#include "py/python.hpp" +#include + +namespace py = boost::python; + +BOOST_AUTO_TEST_CASE( execute_python ) +{ + get_py().run("x = 3"); + int x = py::extract(get_py().eval("x")); + BOOST_CHECK_EQUAL(x, 3); +} + +BOOST_AUTO_TEST_CASE( lookup_python ) +{ + get_py().run("import os"); + py::object splitext = get_py()["os.path.splitext"]; + py::object result = splitext("/home/diane/foo.txt"); + std::string ext = py::extract(result[1]); + BOOST_CHECK_EQUAL(ext, ".txt"); + + get_py().run("from os.path import splitext"); + py::object splitext2 = get_py()["splitext"]; + py::object result2 = splitext("/home/diane/bar.txt"); + std::string ext2 = py::extract(result2[1]); + BOOST_CHECK_EQUAL(ext, ext2); +} diff --git a/qui/CMakeLists.txt b/qui/CMakeLists.txt index de10ec3..75630c1 100644 --- a/qui/CMakeLists.txt +++ b/qui/CMakeLists.txt @@ -61,13 +61,13 @@ SET(GUI_SOURCES seqbrowser/SequenceBrowserSidebar.cpp seqbrowser/SequenceBrowserWidget.cpp seqbrowser/SequenceDescription.cpp - ) +SET(PY_SOURCES ../py/python.cpp) SET(RCCS ../icons.qrc) QT4_ADD_RESOURCES(RCC_SOURCES ${RCCS}) QT4_WRAP_CPP(MOC_SOURCES ${MOC_HEADERS}) -SET(SOURCES ${MOC_SOURCES} ${GUI_SOURCES} ${RCC_SOURCES} ${MAIN_SOURCES}) +SET(SOURCES ${MOC_SOURCES} ${GUI_SOURCES} ${RCC_SOURCES} ${PY_SOURCES}) GET_MUSSA_COMPILE_FLAGS(QUI_CFLAGS) GET_MUSSA_LINK_FLAGS(QUI_LDFLAGS) diff --git a/qui/MussaWindow.cpp b/qui/MussaWindow.cpp index 90fe7f3..9d2f95e 100644 --- a/qui/MussaWindow.cpp +++ b/qui/MussaWindow.cpp @@ -1,3 +1,7 @@ +#include "py/python.hpp" +#include "qui/MussaWindow.hpp" +#include "mussa_exceptions.hpp" + #include #include #include @@ -13,13 +17,6 @@ #include #include -#if defined(Q_WS_MAC) -#include -#endif - -#include "qui/MussaWindow.hpp" -#include "mussa_exceptions.hpp" - #include #include #include @@ -257,9 +254,7 @@ void MussaWindow::setupMainMenu() newMenu->addAction(showMussaViewToolbarAction); newMenu = menuBar()->addMenu(tr("&Help")); -#if defined(QT_ASSISTANT_LIB) newMenu->addAction(mussaManualAssistantAction); -#endif newMenu->addAction(whatsThisAction); newMenu->addSeparator(); newMenu->addAction(aboutAction); @@ -481,7 +476,16 @@ void MussaWindow::toggleMotifs() void MussaWindow::showManual() { +#if QT_QTASSISTANT_FOUND manualAssistant->openAssistant(); +#else + try { + boost::python::object webopen = get_py()["webbrowser.open"]; + webopen("http://woldlab.caltech.edu/cgi-bin/mussa"); + } catch( boost::python::error_already_set ) { + PyErr_Print(); + } +#endif //QT_QTASSISTANT_FOUND } void MussaWindow::assistantError(QString message) diff --git a/qui/mussagl.cpp b/qui/mussagl.cpp index da1311d..da238ee 100644 --- a/qui/mussagl.cpp +++ b/qui/mussagl.cpp @@ -1,12 +1,11 @@ #include -#ifdef USE_PYTHON -#include -namespace py = boost::python; -extern "C" void initmussa(); -#endif - using namespace boost::filesystem; +#include "py/python.hpp" +#include "qui/MussaWindow.hpp" +#include "alg/parse_options.hpp" +#include "mussa_exceptions.hpp" + #include #include #include @@ -39,9 +38,7 @@ int main(int argc, char **argv) try { #ifdef USE_PYTHON if (opts.runAsPythonInterpeter) { - PyImport_AppendInittab("mussa", &initmussa); - Py_Initialize(); - PyRun_InteractiveLoop(stdin, "mussa"); + get_py().interpreter(); } else #endif /* USE_PYTHON */ if (opts.useGUI) { @@ -54,13 +51,9 @@ int main(int argc, char **argv) qFatal(e.what()); } catch (boost::filesystem::filesystem_error e) { qFatal(e.what()); - } -#ifdef USE_PYTHON - catch (py::error_already_set e) { + } catch( boost::python::error_already_set ) { PyErr_Print(); - } -#endif /* USE_PYTHON */ - catch (std::runtime_error e) { + } catch (std::runtime_error e) { qFatal(e.what()); } catch (...) { qFatal("unrecognized exception");