Implement a convienence class for accessing python interpreter
authorDiane Trout <diane@caltech.edu>
Fri, 28 Jul 2006 01:11:50 +0000 (01:11 +0000)
committerDiane Trout <diane@caltech.edu>
Fri, 28 Jul 2006 01:11:50 +0000 (01:11 +0000)
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.

CMakeLists.txt
py/CMakeLists.txt
py/module.cpp
py/python.cpp [new file with mode: 0644]
py/python.hpp [new file with mode: 0644]
py/test/CMakeLists.txt [new file with mode: 0644]
py/test/test_python.cpp [new file with mode: 0644]
qui/CMakeLists.txt
qui/MussaWindow.cpp
qui/mussagl.cpp

index 04e576f104a39990c60d517bf79926ae773e3b6f..2273df0dffb7fe38bc3555de3a68199e51c935e4 100644 (file)
@@ -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 
index 7f6577c0bfd1cc868a49b248b665a2f8020f4392..8c1c6cf6320d798a53cdc958e793baba4db50fc8 100644 (file)
@@ -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)
index 91bdd115fbd6d9cfce15108ccdcd133d5a3d1414..d325fdc538724564df253e51dd3928db630a02aa 100644 (file)
@@ -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 (file)
index 0000000..198d5f9
--- /dev/null
@@ -0,0 +1,91 @@
+#include "py/python.hpp"
+
+#include <iostream>
+
+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<int>(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 (file)
index 0000000..fc210c3
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef _MUSSA_PYTHON_HPP_
+#define _MUSSA_PYTHON_HPP_
+#include <boost/python.hpp>
+#include <string>
+
+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 (file)
index 0000000..e189ec9
--- /dev/null
@@ -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 (file)
index 0000000..c33b7ad
--- /dev/null
@@ -0,0 +1,29 @@
+#define BOOST_AUTO_TEST_MAIN
+#include <boost/test/auto_unit_test.hpp>
+
+#include "py/python.hpp"
+#include <string>
+
+namespace py = boost::python;
+
+BOOST_AUTO_TEST_CASE( execute_python )
+{
+  get_py().run("x = 3");
+  int x = py::extract<int>(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<std::string>(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<std::string>(result2[1]);
+  BOOST_CHECK_EQUAL(ext, ext2);
+}
index de10ec31e42736ea9da6ec997aaa40b5d33b0f6d..75630c1e58e14791f0adeb95d51090e1da88290c 100644 (file)
@@ -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)
index 90fe7f39f557014e360b8eb414a915099194ee73..9d2f95e1e72729a355c6bfe0b69a4e799a8bd95c 100644 (file)
@@ -1,3 +1,7 @@
+#include "py/python.hpp"
+#include "qui/MussaWindow.hpp"
+#include "mussa_exceptions.hpp"
+
 #include <QAction>
 #include <QApplication>
 #include <QAssistantClient>
 #include <QStringList>
 #include <QWhatsThis>
 
-#if defined(Q_WS_MAC)
-#include <CoreFoundation/CoreFoundation.h>
-#endif 
-
-#include "qui/MussaWindow.hpp"
-#include "mussa_exceptions.hpp"
-
 #include <memory>
 #include <iterator>
 #include <iostream>
@@ -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)
index da1311d0ee15610dc7089ee285a46e3f9856a49c..da238ee433c8acb78397522fe7e97078cab4a959 100644 (file)
@@ -1,12 +1,11 @@
 #include <boost/filesystem/operations.hpp>
-#ifdef USE_PYTHON
-#include <boost/python.hpp>
-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 <stdlib.h>
 #include <iostream>
 #include <QApplication>
@@ -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");