Skip to content

Commit 59f2a6e

Browse files
Two new functions in PyProto_API to manage DescriptorPools
`DescriptorPool_FromPool` has a new overload that takes ownership of the C++ instance. `DescriptorPool_AsPool` returns the C++ instance held by the Python object. PiperOrigin-RevId: 842651360
1 parent 73459c5 commit 59f2a6e

File tree

6 files changed

+151
-0
lines changed

6 files changed

+151
-0
lines changed

python/google/protobuf/internal/proto_api_test.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ def test_dynamic_message(self):
2424
result = proto_api_test_ext.repr_dynamic_message(789)
2525
self.assertEqual(result, 'optional_int32: 789\n')
2626

27+
def test_dynamic_pool_message(self):
28+
result = proto_api_test_ext.create_dynamic_pool_message()
29+
self.assertEqual(result.DESCRIPTOR.full_name, 'test_package.MyMessage')
30+
self.assertEqual(result.my_field, 42)
31+
2732

2833
if __name__ == '__main__':
2934
unittest.main()

python/google/protobuf/internal/proto_api_test_ext.cc

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "google/protobuf/descriptor.h"
99
#include "google/protobuf/dynamic_message.h"
1010
#include "google/protobuf/message.h"
11+
#include "google/protobuf/text_format.h"
1112
#include "google/protobuf/unittest.pb.h"
1213
#include "google/protobuf/proto_api.h"
1314
#include "third_party/pybind11/include/pybind11/eval.h"
@@ -137,10 +138,65 @@ auto ReprDynamicMessage(int value) {
137138
return result_string;
138139
}
139140

141+
py::object CreateDynamicPoolMessage() {
142+
FileDescriptorProto file_descriptor;
143+
file_descriptor.set_name("test_file");
144+
file_descriptor.set_package("test_package");
145+
DescriptorProto* message_descriptor = file_descriptor.add_message_type();
146+
message_descriptor->set_name("MyMessage");
147+
FieldDescriptorProto* field_descriptor = message_descriptor->add_field();
148+
field_descriptor->set_name("my_field");
149+
field_descriptor->set_number(1);
150+
field_descriptor->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
151+
field_descriptor->set_type(FieldDescriptorProto::TYPE_INT32);
152+
auto owned_pool = std::make_unique<DescriptorPool>();
153+
if (!owned_pool->BuildFile(file_descriptor)) {
154+
throw std::runtime_error("Failed to build file descriptor");
155+
}
156+
157+
// Create a Python DescriptorPool from the C++ one.
158+
const PyProto_API* api = GetProtoApi();
159+
auto py_pool = py::reinterpret_steal<py::object>(
160+
api->DescriptorPool_FromPool(std::move(owned_pool), nullptr));
161+
if (!py_pool) {
162+
throw py::error_already_set();
163+
}
164+
165+
const DescriptorPool* pool = api->DescriptorPool_AsPool(py_pool.ptr());
166+
if (!pool) {
167+
throw py::error_already_set();
168+
}
169+
170+
// Navigate through the C++ Descriptors, and create a Python message.
171+
const Descriptor* descriptor =
172+
pool->FindMessageTypeByName("test_package.MyMessage");
173+
if (!descriptor) {
174+
throw std::runtime_error("Failed to find file descriptor");
175+
}
176+
auto py_msg =
177+
py::reinterpret_steal<py::object>(api->NewMessage(descriptor, nullptr));
178+
if (!py_msg) {
179+
throw py::error_already_set();
180+
}
181+
Message* msg = api->GetMutableMessagePointer(py_msg.ptr());
182+
if (!msg) {
183+
throw py::error_already_set();
184+
}
185+
186+
// Populate the message, and return it.
187+
if (!google::protobuf::TextFormat::ParseFromString("my_field: 42", msg)) {
188+
throw std::runtime_error("Failed to parse message");
189+
}
190+
// This is safe: the Python object keeps a reference to the Python
191+
// DescriptorPool, which owns the C++ DescriptorPool.
192+
return py_msg;
193+
}
194+
140195
PYBIND11_MODULE(proto_api_test_ext, m) {
141196
m.def("get_const_message", &GetConstMessage);
142197
m.def("set_message_field_with_mutator", &SetMessageFieldWithMutator);
143198
m.def("repr_dynamic_message", &ReprDynamicMessage);
199+
m.def("create_dynamic_pool_message", &CreateDynamicPoolMessage);
144200
}
145201

146202
} // namespace python

python/google/protobuf/proto_api.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@
2323
#define GOOGLE_PROTOBUF_PYTHON_PROTO_API_H__
2424

2525
#include <cstddef>
26+
#include <memory>
2627
#include <string>
2728
#define PY_SSIZE_T_CLEAN
2829
#include <Python.h>
2930

3031
#include "absl/status/status.h"
32+
#include "google/protobuf/descriptor.h"
3133
#include "google/protobuf/descriptor_database.h"
3234
#include "google/protobuf/message.h"
3335

@@ -142,9 +144,25 @@ struct PyProto_API {
142144
// As long as the returned Python DescriptorPool object is kept alive,
143145
// functions that process C++ descriptors or messages created from this pool
144146
// can work and return their Python counterparts.
147+
// On error, returns nullptr and sets a Python exception.
145148
virtual PyObject* DescriptorPool_FromPool(
146149
const google::protobuf::DescriptorPool* pool) const = 0;
147150

151+
// Takes ownership of a C++ DescriptorPool and returns a Python DescriptorPool
152+
// that wraps it.
153+
// The optional google::protobuf::DescriptorDatabase will also be owned by the Python
154+
// DescriptorPool: use it when the C++ DescriptorPool was built with this
155+
// database.
156+
// On error, returns nullptr and sets a Python exception.
157+
virtual PyObject* DescriptorPool_FromPool(
158+
std::unique_ptr<const google::protobuf::DescriptorPool> pool,
159+
std::unique_ptr<const google::protobuf::DescriptorDatabase> database) const = 0;
160+
161+
// Returns the C++ descriptor pool wrapped by a Python object.
162+
// On error, returns nullptr and sets a Python exception.
163+
virtual const google::protobuf::DescriptorPool* DescriptorPool_AsPool(
164+
PyObject* pool) const = 0;
165+
148166
protected:
149167
PythonMessageMutator CreatePythonMessageMutator(Message* owned_msg,
150168
Message* msg,

python/google/protobuf/pyext/descriptor_pool.cc

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
// Implements the DescriptorPool, which collects all descriptors.
99

10+
#include <memory>
1011
#include <string>
1112
#include <utility>
1213
#include <vector>
@@ -21,6 +22,7 @@
2122
#include "absl/status/status.h"
2223
#include "absl/strings/str_cat.h"
2324
#include "absl/strings/string_view.h"
25+
#include "google/protobuf/descriptor_database.h"
2426
#include "google/protobuf/pyext/descriptor.h"
2527
#include "google/protobuf/pyext/descriptor_database.h"
2628
#include "google/protobuf/pyext/descriptor_pool.h"
@@ -764,6 +766,54 @@ PyObject* PyDescriptorPool_FromPool(const DescriptorPool* pool) {
764766
return reinterpret_cast<PyObject*>(cpool);
765767
}
766768

769+
PyObject* PyDescriptorPool_FromPool(
770+
std::unique_ptr<const google::protobuf::DescriptorPool> pool,
771+
std::unique_ptr<const google::protobuf::DescriptorDatabase> database) {
772+
if (pool == nullptr) {
773+
PyErr_SetString(PyExc_ValueError, "DescriptorPool is null");
774+
return nullptr;
775+
}
776+
// There should not be any Python-side pool for this C++ object.
777+
PyDescriptorPool* existing_pool = GetDescriptorPool_FromPool(pool.get());
778+
if (existing_pool != nullptr) {
779+
PyErr_SetString(PyExc_ValueError, "DescriptorPool already registered");
780+
return nullptr;
781+
} else {
782+
PyErr_Clear();
783+
}
784+
785+
PyDescriptorPool* cpool = cdescriptor_pool::_CreateDescriptorPool();
786+
if (cpool == nullptr) {
787+
return nullptr;
788+
}
789+
cpool->pool = pool.release();
790+
cpool->is_owned = true;
791+
cpool->database = database.release();
792+
cpool->is_mutable = false;
793+
cpool->underlay = nullptr;
794+
795+
{
796+
FreeThreadingLockGuard lock(descriptor_pool_map_mutex);
797+
if (!descriptor_pool_map->insert(std::make_pair(cpool->pool, cpool))
798+
.second) {
799+
// Should never happen -- We already checked the existence above.
800+
PyErr_SetString(PyExc_ValueError, "DescriptorPool already registered");
801+
return nullptr;
802+
}
803+
}
804+
805+
return reinterpret_cast<PyObject*>(cpool);
806+
}
807+
808+
const DescriptorPool* PyDescriptorPool_AsPool(PyObject* pool) {
809+
if (!PyObject_TypeCheck(pool, &PyDescriptorPool_Type)) {
810+
PyErr_SetString(PyExc_TypeError, "Not a DescriptorPool");
811+
return nullptr;
812+
}
813+
PyDescriptorPool* cpool = reinterpret_cast<PyDescriptorPool*>(pool);
814+
return cpool->pool;
815+
}
816+
767817
} // namespace python
768818
} // namespace protobuf
769819
} // namespace google

python/google/protobuf/pyext/descriptor_pool.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#ifndef GOOGLE_PROTOBUF_PYTHON_CPP_DESCRIPTOR_POOL_H__
99
#define GOOGLE_PROTOBUF_PYTHON_CPP_DESCRIPTOR_POOL_H__
1010

11+
#include <memory>
1112
#define PY_SSIZE_T_CLEAN
1213
#include <Python.h>
1314

@@ -119,6 +120,16 @@ PyDescriptorPool* GetDescriptorPool_FromPool(const DescriptorPool* pool);
119120
// Returns a new reference.
120121
PyObject* PyDescriptorPool_FromPool(const DescriptorPool* pool);
121122

123+
// Takes ownership of a C++ DescriptorPool and returns a new Python
124+
// DescriptorPool that wraps it.
125+
// If set, the DescriptorDatabase is also managed by the returned object.
126+
PyObject* PyDescriptorPool_FromPool(
127+
std::unique_ptr<const google::protobuf::DescriptorPool> pool,
128+
std::unique_ptr<const google::protobuf::DescriptorDatabase> database);
129+
130+
// Returns the C++ descriptor pool wrapped by a Python object.
131+
const DescriptorPool* PyDescriptorPool_AsPool(PyObject* pool);
132+
122133
// Initialize objects used by this module.
123134
bool InitDescriptorPool();
124135

python/google/protobuf/pyext/message_module.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,17 @@ struct ApiImplementation : google::protobuf::python::PyProto_API {
379379
const google::protobuf::DescriptorPool* pool) const override {
380380
return google::protobuf::python::PyDescriptorPool_FromPool(pool);
381381
}
382+
PyObject* DescriptorPool_FromPool(
383+
std::unique_ptr<const google::protobuf::DescriptorPool> pool,
384+
std::unique_ptr<const google::protobuf::DescriptorDatabase> database)
385+
const override {
386+
return google::protobuf::python::PyDescriptorPool_FromPool(std::move(pool),
387+
std::move(database));
388+
}
389+
const google::protobuf::DescriptorPool* DescriptorPool_AsPool(
390+
PyObject* pool) const override {
391+
return google::protobuf::python::PyDescriptorPool_AsPool(pool);
392+
}
382393
};
383394

384395
} // namespace

0 commit comments

Comments
 (0)