mirror of
https://github.com/protocolbuffers/protobuf
synced 2026-08-26 02:23:14 -04:00
319 lines
11 KiB
C++
319 lines
11 KiB
C++
// An extension module to test proto_api.h.
|
|
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include "google/protobuf/descriptor.pb.h"
|
|
#include "google/protobuf/descriptor.h"
|
|
#include "google/protobuf/descriptor_database.h"
|
|
#include "google/protobuf/dynamic_message.h"
|
|
#include "google/protobuf/message.h"
|
|
#include "google/protobuf/message_lite.h"
|
|
#include "google/protobuf/text_format.h"
|
|
#include "google/protobuf/unittest.pb.h"
|
|
#include "google/protobuf/proto_api.h"
|
|
#include "third_party/pybind11/include/pybind11/eval.h"
|
|
#include "third_party/pybind11/include/pybind11/pybind11.h"
|
|
#include "third_party/pybind11/include/pybind11/stl.h"
|
|
|
|
namespace google {
|
|
namespace protobuf {
|
|
namespace python {
|
|
|
|
namespace py = pybind11;
|
|
using ::google_protobuf_unittest::TestAllTypes;
|
|
|
|
const PyProto_API* GetProtoApi() {
|
|
py::module_::import("google.protobuf.pyext._message");
|
|
const PyProto_API* py_proto_api = static_cast<const PyProto_API*>(
|
|
PyCapsule_Import(PyProtoAPICapsuleName(), 0));
|
|
if (!py_proto_api) {
|
|
throw py::error_already_set();
|
|
}
|
|
return py_proto_api;
|
|
}
|
|
|
|
// Test for GetConstMessagePointer
|
|
auto GetConstMessage(py::handle py_msg) {
|
|
const PyProto_API* api = GetProtoApi();
|
|
auto msg_ptr = api->GetConstMessagePointer(py_msg.ptr());
|
|
if (!msg_ptr.ok()) {
|
|
throw std::runtime_error(msg_ptr.status().ToString());
|
|
}
|
|
const auto* msg = DynamicCastMessage<TestAllTypes>(&msg_ptr->get());
|
|
if (!msg) {
|
|
throw std::runtime_error("Invalid message type");
|
|
}
|
|
return py::make_tuple(msg->optional_int32(), msg->optional_string());
|
|
}
|
|
|
|
// Test for GetClearedMessageMutator
|
|
auto SetMessageFieldWithMutator(py::handle py_msg, int value) {
|
|
const PyProto_API* api = GetProtoApi();
|
|
auto status_or_mutator = api->GetClearedMessageMutator(py_msg.ptr());
|
|
if (!status_or_mutator.ok()) {
|
|
throw std::runtime_error(status_or_mutator.status().ToString());
|
|
}
|
|
TestAllTypes* msg = DownCastMessage<TestAllTypes>(status_or_mutator->get());
|
|
msg->set_optional_int32(value);
|
|
// On destruction, the mutator will copy content back to python message.
|
|
}
|
|
|
|
// Test for DescriptorPool_FromPool and NewMessageOwnedExternally
|
|
auto ReprDynamicMessage(int value) {
|
|
const PyProto_API* api = GetProtoApi();
|
|
|
|
// Create a descriptor pool which copies everything from the linked protos.
|
|
DescriptorPool pool(DescriptorPool::internal_generated_database());
|
|
// FileDescriptorProto file_descriptor;
|
|
// TestAllTypes::descriptor()->file()->CopyTo(&file_descriptor);
|
|
// if (!pool.BuildFile(file_descriptor)) {
|
|
// throw std::runtime_error("Failed to build file descriptor");
|
|
// }
|
|
const Descriptor* descriptor =
|
|
pool.FindMessageTypeByName("proto2_unittest.TestAllTypes");
|
|
if (!descriptor) {
|
|
throw std::runtime_error("Failed to find file descriptor");
|
|
}
|
|
DynamicMessageFactory factory(&pool);
|
|
const Message* prototype = factory.GetPrototype(descriptor);
|
|
if (!prototype) {
|
|
throw std::runtime_error("Failed to get prototype for descriptor");
|
|
}
|
|
std::unique_ptr<Message> msg(prototype->New());
|
|
if (!msg) {
|
|
throw std::runtime_error("Failed to create message");
|
|
}
|
|
msg->GetReflection()->SetInt32(
|
|
msg.get(), descriptor->FindFieldByName("optional_int32"), value);
|
|
|
|
// These calls to NewMessage fail because the descriptor pool is not
|
|
// known to Python yet.
|
|
{
|
|
auto py_msg =
|
|
py::reinterpret_steal<py::object>(api->NewMessage(descriptor, nullptr));
|
|
if (py_msg) {
|
|
throw std::runtime_error("NewMessage succeeded unexpectedly");
|
|
}
|
|
py_msg = py::reinterpret_steal<py::object>(
|
|
api->NewMessageOwnedExternally(msg.get(), nullptr));
|
|
if (py_msg) {
|
|
throw std::runtime_error("NewMessage succeeded unexpectedly");
|
|
}
|
|
}
|
|
|
|
// Create the Python DescriptorPool...
|
|
auto py_pool =
|
|
py::reinterpret_steal<py::object>(api->DescriptorPool_FromPool(&pool));
|
|
if (!py_pool) {
|
|
throw py::error_already_set();
|
|
}
|
|
|
|
// ... And now the API Can use it to create the messages.
|
|
std::string result_string;
|
|
{
|
|
auto py_msg =
|
|
py::reinterpret_steal<py::object>(api->NewMessage(descriptor, nullptr));
|
|
if (!py_msg) {
|
|
throw py::error_already_set();
|
|
}
|
|
|
|
py_msg = py::reinterpret_steal<py::object>(
|
|
api->NewMessageOwnedExternally(msg.get(), nullptr));
|
|
if (!py_msg) {
|
|
throw py::error_already_set();
|
|
}
|
|
result_string = py::repr(py_msg);
|
|
}
|
|
|
|
// The code above is dangerous! It relies on the C++ DescriptorPool being
|
|
// alive for whole duration of the test.
|
|
// At this point, there are no external references to the Python Message
|
|
// classes, but they always form a reference cycle with their Python
|
|
// MessageFactory.
|
|
// So it is necessary to run the garbage collector.
|
|
py::exec("import gc; gc.collect()");
|
|
// Now the Python MessageFactory has been deleted, and it is safe to destroy
|
|
// the C++ DescriptorPool.
|
|
|
|
return result_string;
|
|
}
|
|
|
|
auto ReprDynamicMessageSharedPool(int value) {
|
|
const PyProto_API* api = GetProtoApi();
|
|
|
|
auto pool = std::make_shared<DescriptorPool>(
|
|
DescriptorPool::internal_generated_database());
|
|
|
|
// Create the Python DescriptorPool using shared_ptr...
|
|
auto py_pool = py::reinterpret_steal<py::object>(
|
|
api->DescriptorPool_FromSharedPool(pool, nullptr));
|
|
if (!py_pool) {
|
|
throw py::error_already_set();
|
|
}
|
|
|
|
std::string result_string;
|
|
{
|
|
DynamicMessageFactory factory(pool.get());
|
|
const Descriptor* descriptor =
|
|
pool->FindMessageTypeByName("proto2_unittest.TestAllTypes");
|
|
if (!descriptor) {
|
|
throw std::runtime_error("Failed to find file descriptor");
|
|
}
|
|
const Message* prototype = factory.GetPrototype(descriptor);
|
|
if (!prototype) {
|
|
throw std::runtime_error("Failed to get prototype for descriptor");
|
|
}
|
|
std::unique_ptr<Message> msg(prototype->New());
|
|
if (!msg) {
|
|
throw std::runtime_error("Failed to create message");
|
|
}
|
|
msg->GetReflection()->SetInt32(
|
|
msg.get(), descriptor->FindFieldByName("optional_int32"), value);
|
|
|
|
auto py_msg = py::reinterpret_steal<py::object>(
|
|
api->NewMessageOwnedExternally(msg.get(), nullptr));
|
|
if (!py_msg) {
|
|
throw py::error_already_set();
|
|
}
|
|
result_string = py::repr(py_msg);
|
|
} // msg, py_msg, and factory are safely destroyed here before pool.
|
|
|
|
// Testing co-ownership: When C++ drops its handle, the
|
|
// pool stays alive because Python still owns it.
|
|
pool.reset();
|
|
|
|
// Omit manual gc.collect() here and return naturally.
|
|
//
|
|
// When PyMessageFactory creates dynamic message classes (e.g.
|
|
// CustomMessageClass), Python establishes two interlocking reference cycles
|
|
// on the CPython heap:
|
|
// (PyDescriptorPool <-> PyMessageFactory <-> CustomMessageClass).
|
|
//
|
|
// Even after local C++ stack objects (factory) exit
|
|
// scope above, these Python objects sit in an unreferenced cyclic island. If
|
|
// manual gc.collect() runs mid-flight, CPython runs gc_collect_main() to
|
|
// break cycles via tp_clear in non-deterministic heap order.
|
|
//
|
|
// Natural return allows Python to tear down wrappers cleanly
|
|
// during finalization.
|
|
return result_string;
|
|
}
|
|
|
|
auto ReprDynamicMessageSharedPoolAndDb(int value) {
|
|
const PyProto_API* api = GetProtoApi();
|
|
|
|
// Create custom DB and Pool held by shared_ptr
|
|
auto db = std::make_shared<SimpleDescriptorDatabase>();
|
|
FileDescriptorProto file_proto;
|
|
file_proto.set_name("custom_unittest.proto");
|
|
file_proto.set_package("custom_unittest");
|
|
DescriptorProto* msg_proto = file_proto.add_message_type();
|
|
msg_proto->set_name("CustomMessage");
|
|
FieldDescriptorProto* field_proto = msg_proto->add_field();
|
|
field_proto->set_name("val");
|
|
field_proto->set_number(1);
|
|
field_proto->set_type(FieldDescriptorProto::TYPE_INT32);
|
|
field_proto->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
|
|
db->Add(file_proto);
|
|
|
|
auto pool = std::make_shared<DescriptorPool>(db.get());
|
|
|
|
auto py_pool = py::reinterpret_steal<py::object>(
|
|
api->DescriptorPool_FromSharedPool(pool, db));
|
|
if (!py_pool) {
|
|
throw py::error_already_set();
|
|
}
|
|
|
|
std::string result_string;
|
|
{
|
|
DynamicMessageFactory factory(pool.get());
|
|
const Descriptor* descriptor =
|
|
pool->FindMessageTypeByName("custom_unittest.CustomMessage");
|
|
const Message* prototype = factory.GetPrototype(descriptor);
|
|
std::unique_ptr<Message> msg(prototype->New());
|
|
msg->GetReflection()->SetInt32(msg.get(),
|
|
descriptor->FindFieldByName("val"), value);
|
|
|
|
auto py_msg = py::reinterpret_steal<py::object>(
|
|
api->NewMessageOwnedExternally(msg.get(), nullptr));
|
|
result_string = py::repr(py_msg);
|
|
} // msg, py_msg, and factory are safely destroyed here before pool.
|
|
|
|
// Testing co-ownership: When C++ drops its handle, the
|
|
// pool stays alive because Python still owns it.
|
|
pool.reset();
|
|
db.reset();
|
|
|
|
return result_string;
|
|
}
|
|
|
|
py::object CreateDynamicPoolMessage() {
|
|
FileDescriptorProto file_descriptor;
|
|
file_descriptor.set_name("test_file");
|
|
file_descriptor.set_package("test_package");
|
|
DescriptorProto* message_descriptor = file_descriptor.add_message_type();
|
|
message_descriptor->set_name("MyMessage");
|
|
FieldDescriptorProto* field_descriptor = message_descriptor->add_field();
|
|
field_descriptor->set_name("my_field");
|
|
field_descriptor->set_number(1);
|
|
field_descriptor->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
|
|
field_descriptor->set_type(FieldDescriptorProto::TYPE_INT32);
|
|
auto owned_pool = std::make_unique<DescriptorPool>();
|
|
if (!owned_pool->BuildFile(file_descriptor)) {
|
|
throw std::runtime_error("Failed to build file descriptor");
|
|
}
|
|
|
|
// Create a Python DescriptorPool from the C++ one.
|
|
const PyProto_API* api = GetProtoApi();
|
|
auto py_pool = py::reinterpret_steal<py::object>(
|
|
api->DescriptorPool_FromPool(std::move(owned_pool), nullptr));
|
|
if (!py_pool) {
|
|
throw py::error_already_set();
|
|
}
|
|
|
|
const DescriptorPool* pool = api->DescriptorPool_AsPool(py_pool.ptr());
|
|
if (!pool) {
|
|
throw py::error_already_set();
|
|
}
|
|
|
|
// Navigate through the C++ Descriptors, and create a Python message.
|
|
const Descriptor* descriptor =
|
|
pool->FindMessageTypeByName("test_package.MyMessage");
|
|
if (!descriptor) {
|
|
throw std::runtime_error("Failed to find file descriptor");
|
|
}
|
|
auto py_msg =
|
|
py::reinterpret_steal<py::object>(api->NewMessage(descriptor, nullptr));
|
|
if (!py_msg) {
|
|
throw py::error_already_set();
|
|
}
|
|
Message* msg = api->GetMutableMessagePointer(py_msg.ptr());
|
|
if (!msg) {
|
|
throw py::error_already_set();
|
|
}
|
|
|
|
// Populate the message, and return it.
|
|
if (!google::protobuf::TextFormat::ParseFromString("my_field: 42", msg)) {
|
|
throw std::runtime_error("Failed to parse message");
|
|
}
|
|
// This is safe: the Python object keeps a reference to the Python
|
|
// DescriptorPool, which owns the C++ DescriptorPool.
|
|
return py_msg;
|
|
}
|
|
|
|
PYBIND11_MODULE(proto_api_test_ext, m) {
|
|
m.def("get_const_message", &GetConstMessage);
|
|
m.def("set_message_field_with_mutator", &SetMessageFieldWithMutator);
|
|
m.def("repr_dynamic_message", &ReprDynamicMessage);
|
|
m.def("repr_dynamic_message_shared_pool", &ReprDynamicMessageSharedPool);
|
|
m.def("repr_dynamic_message_shared_pool_and_db",
|
|
&ReprDynamicMessageSharedPoolAndDb);
|
|
m.def("create_dynamic_pool_message", &CreateDynamicPoolMessage);
|
|
}
|
|
|
|
} // namespace python
|
|
} // namespace protobuf
|
|
} // namespace google
|