Add C++ interface for the Python interpreter
diff --git a/tests/test_cmake_build/embed.cpp b/tests/test_cmake_build/embed.cpp
index 44900c8..b9581d2 100644
--- a/tests/test_cmake_build/embed.cpp
+++ b/tests/test_cmake_build/embed.cpp
@@ -1,13 +1,8 @@
-#include <pybind11/pybind11.h>
-#include <pybind11/eval.h>
+#include <pybind11/embed.h>
namespace py = pybind11;
-PyObject *make_module() {
- py::module m("test_cmake_build");
-
+PYBIND11_EMBEDDED_MODULE(test_cmake_build, m) {
m.def("add", [](int i, int j) { return i + j; });
-
- return m.ptr();
}
int main(int argc, char *argv[]) {
@@ -15,16 +10,12 @@
throw std::runtime_error("Expected test.py file as the first argument");
auto test_py_file = argv[1];
- PyImport_AppendInittab("test_cmake_build", &make_module);
- Py_Initialize();
- {
- auto m = py::module::import("test_cmake_build");
- if (m.attr("add")(1, 2).cast<int>() != 3)
- throw std::runtime_error("embed.cpp failed");
+ py::scoped_interpreter guard{};
- auto globals = py::module::import("__main__").attr("__dict__");
- py::module::import("sys").attr("argv") = py::make_tuple("test.py", "embed.cpp");
- py::eval_file(test_py_file, globals);
- }
- Py_Finalize();
+ auto m = py::module::import("test_cmake_build");
+ if (m.attr("add")(1, 2).cast<int>() != 3)
+ throw std::runtime_error("embed.cpp failed");
+
+ py::module::import("sys").attr("argv") = py::make_tuple("test.py", "embed.cpp");
+ py::eval_file(test_py_file, py::globals());
}
diff --git a/tests/test_embed/catch.cpp b/tests/test_embed/catch.cpp
index f79fe17..cface48 100644
--- a/tests/test_embed/catch.cpp
+++ b/tests/test_embed/catch.cpp
@@ -1,5 +1,16 @@
-// Catch provides the `int main()` function here. This is a standalone
+// The Catch implementation is compiled here. This is a standalone
// translation unit to avoid recompiling it for every test change.
-#define CATCH_CONFIG_MAIN
+#include <pybind11/embed.h>
+
+#define CATCH_CONFIG_RUNNER
#include <catch.hpp>
+
+namespace py = pybind11;
+
+int main(int argc, const char *argv[]) {
+ py::scoped_interpreter guard{};
+ auto result = Catch::Session().run(argc, argv);
+
+ return result < 0xff ? result : 0xff;
+}
diff --git a/tests/test_embed/test_interpreter.cpp b/tests/test_embed/test_interpreter.cpp
index 97af7eb..1069cca 100644
--- a/tests/test_embed/test_interpreter.cpp
+++ b/tests/test_embed/test_interpreter.cpp
@@ -1,6 +1,4 @@
-#include <pybind11/pybind11.h>
-#include <pybind11/eval.h>
-
+#include <pybind11/embed.h>
#include <catch.hpp>
namespace py = pybind11;
@@ -24,41 +22,62 @@
int the_answer() const override { PYBIND11_OVERLOAD_PURE(int, Widget, the_answer); }
};
-PyObject *make_embedded_module() {
- py::module m("widget_module");
-
+PYBIND11_EMBEDDED_MODULE(widget_module, m) {
py::class_<Widget, PyWidget>(m, "Widget")
.def(py::init<std::string>())
.def_property_readonly("the_message", &Widget::the_message);
-
- return m.ptr();
}
-py::object import_file(const std::string &module, const std::string &path, py::object globals) {
- auto locals = py::dict("module_name"_a=module, "path"_a=path);
- py::eval<py::eval_statements>(
- "import imp\n"
- "with open(path) as file:\n"
- " new_module = imp.load_module(module_name, file, path, ('py', 'U', imp.PY_SOURCE))",
- globals, locals
- );
- return locals["new_module"];
+PYBIND11_EMBEDDED_MODULE(throw_exception, ) {
+ throw std::runtime_error("C++ Error");
+}
+
+PYBIND11_EMBEDDED_MODULE(throw_error_already_set, ) {
+ auto d = py::dict();
+ d["missing"].cast<py::object>();
}
TEST_CASE("Pass classes and data between modules defined in C++ and Python") {
- PyImport_AppendInittab("widget_module", &make_embedded_module);
- Py_Initialize();
+ auto module = py::module::import("test_interpreter");
+ REQUIRE(py::hasattr(module, "DerivedWidget"));
+
+ auto locals = py::dict("hello"_a="Hello, World!", "x"_a=5, **module.attr("__dict__"));
+ py::exec(R"(
+ widget = DerivedWidget("{} - {}".format(hello, x))
+ message = widget.the_message
+ )", py::globals(), locals);
+ REQUIRE(locals["message"].cast<std::string>() == "Hello, World! - 5");
+
+ auto py_widget = module.attr("DerivedWidget")("The question");
+ auto message = py_widget.attr("the_message");
+ REQUIRE(message.cast<std::string>() == "The question");
+
+ const auto &cpp_widget = py_widget.cast<const Widget &>();
+ REQUIRE(cpp_widget.the_answer() == 42);
+}
+
+TEST_CASE("Import error handling") {
+ REQUIRE_NOTHROW(py::module::import("widget_module"));
+ REQUIRE_THROWS_WITH(py::module::import("throw_exception"),
+ "ImportError: C++ Error");
+ REQUIRE_THROWS_WITH(py::module::import("throw_error_already_set"),
+ Catch::Contains("ImportError: KeyError"));
+}
+
+TEST_CASE("There can be only one interpreter") {
+ static_assert(std::is_move_constructible<py::scoped_interpreter>::value, "");
+ static_assert(!std::is_move_assignable<py::scoped_interpreter>::value, "");
+ static_assert(!std::is_copy_constructible<py::scoped_interpreter>::value, "");
+ static_assert(!std::is_copy_assignable<py::scoped_interpreter>::value, "");
+
+ REQUIRE_THROWS_WITH(py::initialize_interpreter(), "The interpreter is already running");
+ REQUIRE_THROWS_WITH(py::scoped_interpreter(), "The interpreter is already running");
+
+ py::finalize_interpreter();
+ REQUIRE_NOTHROW(py::scoped_interpreter());
{
- auto globals = py::module::import("__main__").attr("__dict__");
- auto module = import_file("widget", "test_interpreter.py", globals);
- REQUIRE(py::hasattr(module, "DerivedWidget"));
-
- auto py_widget = module.attr("DerivedWidget")("Hello, World!");
- auto message = py_widget.attr("the_message");
- REQUIRE(message.cast<std::string>() == "Hello, World!");
-
- const auto &cpp_widget = py_widget.cast<const Widget &>();
- REQUIRE(cpp_widget.the_answer() == 42);
+ auto pyi1 = py::scoped_interpreter();
+ auto pyi2 = std::move(pyi1);
}
- Py_Finalize();
+ py::initialize_interpreter();
}