1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc. All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 // * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 // * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 // * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31 // Author: anuraag@google.com (Anuraag Agrawal)
32 // Author: tibell@google.com (Johan Tibell)
33
34 #include <google/protobuf/pyext/repeated_composite_container.h>
35
36 #include <memory>
37 #ifndef _SHARED_PTR_H
38 #include <google/protobuf/stubs/shared_ptr.h>
39 #endif
40
41 #include <google/protobuf/stubs/logging.h>
42 #include <google/protobuf/stubs/common.h>
43 #include <google/protobuf/descriptor.h>
44 #include <google/protobuf/dynamic_message.h>
45 #include <google/protobuf/message.h>
46 #include <google/protobuf/pyext/descriptor.h>
47 #include <google/protobuf/pyext/descriptor_pool.h>
48 #include <google/protobuf/pyext/message.h>
49 #include <google/protobuf/pyext/scoped_pyobject_ptr.h>
50
51 #if PY_MAJOR_VERSION >= 3
52 #define PyInt_Check PyLong_Check
53 #define PyInt_AsLong PyLong_AsLong
54 #define PyInt_FromLong PyLong_FromLong
55 #endif
56
57 namespace google {
58 namespace protobuf {
59 namespace python {
60
61 namespace repeated_composite_container {
62
63 // TODO(tibell): We might also want to check:
64 // GOOGLE_CHECK_NOTNULL((self)->owner.get());
65 #define GOOGLE_CHECK_ATTACHED(self) \
66 do { \
67 GOOGLE_CHECK_NOTNULL((self)->message); \
68 GOOGLE_CHECK_NOTNULL((self)->parent_field_descriptor); \
69 } while (0);
70
71 #define GOOGLE_CHECK_RELEASED(self) \
72 do { \
73 GOOGLE_CHECK((self)->owner.get() == NULL); \
74 GOOGLE_CHECK((self)->message == NULL); \
75 GOOGLE_CHECK((self)->parent_field_descriptor == NULL); \
76 GOOGLE_CHECK((self)->parent == NULL); \
77 } while (0);
78
79 // ---------------------------------------------------------------------
80 // len()
81
Length(RepeatedCompositeContainer * self)82 static Py_ssize_t Length(RepeatedCompositeContainer* self) {
83 Message* message = self->message;
84 if (message != NULL) {
85 return message->GetReflection()->FieldSize(*message,
86 self->parent_field_descriptor);
87 } else {
88 // The container has been released (i.e. by a call to Clear() or
89 // ClearField() on the parent) and thus there's no message.
90 return PyList_GET_SIZE(self->child_messages);
91 }
92 }
93
94 // Returns 0 if successful; returns -1 and sets an exception if
95 // unsuccessful.
UpdateChildMessages(RepeatedCompositeContainer * self)96 static int UpdateChildMessages(RepeatedCompositeContainer* self) {
97 if (self->message == NULL)
98 return 0;
99
100 // A MergeFrom on a parent message could have caused extra messages to be
101 // added in the underlying protobuf so add them to our list. They can never
102 // be removed in such a way so there's no need to worry about that.
103 Py_ssize_t message_length = Length(self);
104 Py_ssize_t child_length = PyList_GET_SIZE(self->child_messages);
105 Message* message = self->message;
106 const Reflection* reflection = message->GetReflection();
107 for (Py_ssize_t i = child_length; i < message_length; ++i) {
108 const Message& sub_message = reflection->GetRepeatedMessage(
109 *(self->message), self->parent_field_descriptor, i);
110 CMessage* cmsg = cmessage::NewEmptyMessage(self->child_message_class);
111 ScopedPyObjectPtr py_cmsg(reinterpret_cast<PyObject*>(cmsg));
112 if (cmsg == NULL) {
113 return -1;
114 }
115 cmsg->owner = self->owner;
116 cmsg->message = const_cast<Message*>(&sub_message);
117 cmsg->parent = self->parent;
118 if (PyList_Append(self->child_messages, py_cmsg.get()) < 0) {
119 return -1;
120 }
121 }
122 return 0;
123 }
124
125 // ---------------------------------------------------------------------
126 // add()
127
AddToAttached(RepeatedCompositeContainer * self,PyObject * args,PyObject * kwargs)128 static PyObject* AddToAttached(RepeatedCompositeContainer* self,
129 PyObject* args,
130 PyObject* kwargs) {
131 GOOGLE_CHECK_ATTACHED(self);
132
133 if (UpdateChildMessages(self) < 0) {
134 return NULL;
135 }
136 if (cmessage::AssureWritable(self->parent) == -1)
137 return NULL;
138 Message* message = self->message;
139 Message* sub_message =
140 message->GetReflection()->AddMessage(message,
141 self->parent_field_descriptor);
142 CMessage* cmsg = cmessage::NewEmptyMessage(self->child_message_class);
143 if (cmsg == NULL)
144 return NULL;
145
146 cmsg->owner = self->owner;
147 cmsg->message = sub_message;
148 cmsg->parent = self->parent;
149 if (cmessage::InitAttributes(cmsg, args, kwargs) < 0) {
150 Py_DECREF(cmsg);
151 return NULL;
152 }
153
154 PyObject* py_cmsg = reinterpret_cast<PyObject*>(cmsg);
155 if (PyList_Append(self->child_messages, py_cmsg) < 0) {
156 Py_DECREF(py_cmsg);
157 return NULL;
158 }
159 return py_cmsg;
160 }
161
AddToReleased(RepeatedCompositeContainer * self,PyObject * args,PyObject * kwargs)162 static PyObject* AddToReleased(RepeatedCompositeContainer* self,
163 PyObject* args,
164 PyObject* kwargs) {
165 GOOGLE_CHECK_RELEASED(self);
166
167 // Create a new Message detached from the rest.
168 PyObject* py_cmsg = PyEval_CallObjectWithKeywords(
169 self->child_message_class->AsPyObject(), args, kwargs);
170 if (py_cmsg == NULL)
171 return NULL;
172
173 if (PyList_Append(self->child_messages, py_cmsg) < 0) {
174 Py_DECREF(py_cmsg);
175 return NULL;
176 }
177 return py_cmsg;
178 }
179
Add(RepeatedCompositeContainer * self,PyObject * args,PyObject * kwargs)180 PyObject* Add(RepeatedCompositeContainer* self,
181 PyObject* args,
182 PyObject* kwargs) {
183 if (self->message == NULL)
184 return AddToReleased(self, args, kwargs);
185 else
186 return AddToAttached(self, args, kwargs);
187 }
188
189 // ---------------------------------------------------------------------
190 // extend()
191
Extend(RepeatedCompositeContainer * self,PyObject * value)192 PyObject* Extend(RepeatedCompositeContainer* self, PyObject* value) {
193 cmessage::AssureWritable(self->parent);
194 if (UpdateChildMessages(self) < 0) {
195 return NULL;
196 }
197 ScopedPyObjectPtr iter(PyObject_GetIter(value));
198 if (iter == NULL) {
199 PyErr_SetString(PyExc_TypeError, "Value must be iterable");
200 return NULL;
201 }
202 ScopedPyObjectPtr next;
203 while ((next.reset(PyIter_Next(iter.get()))) != NULL) {
204 if (!PyObject_TypeCheck(next.get(), &CMessage_Type)) {
205 PyErr_SetString(PyExc_TypeError, "Not a cmessage");
206 return NULL;
207 }
208 ScopedPyObjectPtr new_message(Add(self, NULL, NULL));
209 if (new_message == NULL) {
210 return NULL;
211 }
212 CMessage* new_cmessage = reinterpret_cast<CMessage*>(new_message.get());
213 if (ScopedPyObjectPtr(cmessage::MergeFrom(new_cmessage, next.get())) ==
214 NULL) {
215 return NULL;
216 }
217 }
218 if (PyErr_Occurred()) {
219 return NULL;
220 }
221 Py_RETURN_NONE;
222 }
223
MergeFrom(RepeatedCompositeContainer * self,PyObject * other)224 PyObject* MergeFrom(RepeatedCompositeContainer* self, PyObject* other) {
225 if (UpdateChildMessages(self) < 0) {
226 return NULL;
227 }
228 return Extend(self, other);
229 }
230
Subscript(RepeatedCompositeContainer * self,PyObject * slice)231 PyObject* Subscript(RepeatedCompositeContainer* self, PyObject* slice) {
232 if (UpdateChildMessages(self) < 0) {
233 return NULL;
234 }
235 // Just forward the call to the subscript-handling function of the
236 // list containing the child messages.
237 return PyObject_GetItem(self->child_messages, slice);
238 }
239
AssignSubscript(RepeatedCompositeContainer * self,PyObject * slice,PyObject * value)240 int AssignSubscript(RepeatedCompositeContainer* self,
241 PyObject* slice,
242 PyObject* value) {
243 if (UpdateChildMessages(self) < 0) {
244 return -1;
245 }
246 if (value != NULL) {
247 PyErr_SetString(PyExc_TypeError, "does not support assignment");
248 return -1;
249 }
250
251 // Delete from the underlying Message, if any.
252 if (self->parent != NULL) {
253 if (cmessage::InternalDeleteRepeatedField(self->parent,
254 self->parent_field_descriptor,
255 slice,
256 self->child_messages) < 0) {
257 return -1;
258 }
259 } else {
260 Py_ssize_t from;
261 Py_ssize_t to;
262 Py_ssize_t step;
263 Py_ssize_t length = Length(self);
264 Py_ssize_t slicelength;
265 if (PySlice_Check(slice)) {
266 #if PY_MAJOR_VERSION >= 3
267 if (PySlice_GetIndicesEx(slice,
268 #else
269 if (PySlice_GetIndicesEx(reinterpret_cast<PySliceObject*>(slice),
270 #endif
271 length, &from, &to, &step, &slicelength) == -1) {
272 return -1;
273 }
274 return PySequence_DelSlice(self->child_messages, from, to);
275 } else if (PyInt_Check(slice) || PyLong_Check(slice)) {
276 from = to = PyLong_AsLong(slice);
277 if (from < 0) {
278 from = to = length + from;
279 }
280 return PySequence_DelItem(self->child_messages, from);
281 }
282 }
283
284 return 0;
285 }
286
Remove(RepeatedCompositeContainer * self,PyObject * value)287 static PyObject* Remove(RepeatedCompositeContainer* self, PyObject* value) {
288 if (UpdateChildMessages(self) < 0) {
289 return NULL;
290 }
291 Py_ssize_t index = PySequence_Index(self->child_messages, value);
292 if (index == -1) {
293 return NULL;
294 }
295 ScopedPyObjectPtr py_index(PyLong_FromLong(index));
296 if (AssignSubscript(self, py_index.get(), NULL) < 0) {
297 return NULL;
298 }
299 Py_RETURN_NONE;
300 }
301
RichCompare(RepeatedCompositeContainer * self,PyObject * other,int opid)302 static PyObject* RichCompare(RepeatedCompositeContainer* self,
303 PyObject* other,
304 int opid) {
305 if (UpdateChildMessages(self) < 0) {
306 return NULL;
307 }
308 if (!PyObject_TypeCheck(other, &RepeatedCompositeContainer_Type)) {
309 PyErr_SetString(PyExc_TypeError,
310 "Can only compare repeated composite fields "
311 "against other repeated composite fields.");
312 return NULL;
313 }
314 if (opid == Py_EQ || opid == Py_NE) {
315 // TODO(anuraag): Don't make new lists just for this...
316 ScopedPyObjectPtr full_slice(PySlice_New(NULL, NULL, NULL));
317 if (full_slice == NULL) {
318 return NULL;
319 }
320 ScopedPyObjectPtr list(Subscript(self, full_slice.get()));
321 if (list == NULL) {
322 return NULL;
323 }
324 ScopedPyObjectPtr other_list(
325 Subscript(reinterpret_cast<RepeatedCompositeContainer*>(other),
326 full_slice.get()));
327 if (other_list == NULL) {
328 return NULL;
329 }
330 return PyObject_RichCompare(list.get(), other_list.get(), opid);
331 } else {
332 Py_INCREF(Py_NotImplemented);
333 return Py_NotImplemented;
334 }
335 }
336
337 // ---------------------------------------------------------------------
338 // sort()
339
ReorderAttached(RepeatedCompositeContainer * self)340 static void ReorderAttached(RepeatedCompositeContainer* self) {
341 Message* message = self->message;
342 const Reflection* reflection = message->GetReflection();
343 const FieldDescriptor* descriptor = self->parent_field_descriptor;
344 const Py_ssize_t length = Length(self);
345
346 // Since Python protobuf objects are never arena-allocated, adding and
347 // removing message pointers to the underlying array is just updating
348 // pointers.
349 for (Py_ssize_t i = 0; i < length; ++i)
350 reflection->ReleaseLast(message, descriptor);
351
352 for (Py_ssize_t i = 0; i < length; ++i) {
353 CMessage* py_cmsg = reinterpret_cast<CMessage*>(
354 PyList_GET_ITEM(self->child_messages, i));
355 reflection->AddAllocatedMessage(message, descriptor, py_cmsg->message);
356 }
357 }
358
359 // Returns 0 if successful; returns -1 and sets an exception if
360 // unsuccessful.
SortPythonMessages(RepeatedCompositeContainer * self,PyObject * args,PyObject * kwds)361 static int SortPythonMessages(RepeatedCompositeContainer* self,
362 PyObject* args,
363 PyObject* kwds) {
364 ScopedPyObjectPtr m(PyObject_GetAttrString(self->child_messages, "sort"));
365 if (m == NULL)
366 return -1;
367 if (PyObject_Call(m.get(), args, kwds) == NULL)
368 return -1;
369 if (self->message != NULL) {
370 ReorderAttached(self);
371 }
372 return 0;
373 }
374
Sort(RepeatedCompositeContainer * self,PyObject * args,PyObject * kwds)375 static PyObject* Sort(RepeatedCompositeContainer* self,
376 PyObject* args,
377 PyObject* kwds) {
378 // Support the old sort_function argument for backwards
379 // compatibility.
380 if (kwds != NULL) {
381 PyObject* sort_func = PyDict_GetItemString(kwds, "sort_function");
382 if (sort_func != NULL) {
383 // Must set before deleting as sort_func is a borrowed reference
384 // and kwds might be the only thing keeping it alive.
385 PyDict_SetItemString(kwds, "cmp", sort_func);
386 PyDict_DelItemString(kwds, "sort_function");
387 }
388 }
389
390 if (UpdateChildMessages(self) < 0) {
391 return NULL;
392 }
393 if (SortPythonMessages(self, args, kwds) < 0) {
394 return NULL;
395 }
396 Py_RETURN_NONE;
397 }
398
399 // ---------------------------------------------------------------------
400
Item(RepeatedCompositeContainer * self,Py_ssize_t index)401 static PyObject* Item(RepeatedCompositeContainer* self, Py_ssize_t index) {
402 if (UpdateChildMessages(self) < 0) {
403 return NULL;
404 }
405 Py_ssize_t length = Length(self);
406 if (index < 0) {
407 index = length + index;
408 }
409 PyObject* item = PyList_GetItem(self->child_messages, index);
410 if (item == NULL) {
411 return NULL;
412 }
413 Py_INCREF(item);
414 return item;
415 }
416
Pop(RepeatedCompositeContainer * self,PyObject * args)417 static PyObject* Pop(RepeatedCompositeContainer* self,
418 PyObject* args) {
419 Py_ssize_t index = -1;
420 if (!PyArg_ParseTuple(args, "|n", &index)) {
421 return NULL;
422 }
423 PyObject* item = Item(self, index);
424 if (item == NULL) {
425 PyErr_Format(PyExc_IndexError,
426 "list index (%zd) out of range",
427 index);
428 return NULL;
429 }
430 ScopedPyObjectPtr py_index(PyLong_FromSsize_t(index));
431 if (AssignSubscript(self, py_index.get(), NULL) < 0) {
432 return NULL;
433 }
434 return item;
435 }
436
437 // Release field of parent message and transfer the ownership to target.
ReleaseLastTo(CMessage * parent,const FieldDescriptor * field,CMessage * target)438 void ReleaseLastTo(CMessage* parent,
439 const FieldDescriptor* field,
440 CMessage* target) {
441 GOOGLE_CHECK_NOTNULL(parent);
442 GOOGLE_CHECK_NOTNULL(field);
443 GOOGLE_CHECK_NOTNULL(target);
444
445 shared_ptr<Message> released_message(
446 parent->message->GetReflection()->ReleaseLast(parent->message, field));
447 // TODO(tibell): Deal with proto1.
448
449 target->parent = NULL;
450 target->parent_field_descriptor = NULL;
451 target->message = released_message.get();
452 target->read_only = false;
453 cmessage::SetOwner(target, released_message);
454 }
455
456 // Called to release a container using
457 // ClearField('container_field_name') on the parent.
Release(RepeatedCompositeContainer * self)458 int Release(RepeatedCompositeContainer* self) {
459 if (UpdateChildMessages(self) < 0) {
460 PyErr_WriteUnraisable(PyBytes_FromString("Failed to update released "
461 "messages"));
462 return -1;
463 }
464
465 Message* message = self->message;
466 const FieldDescriptor* field = self->parent_field_descriptor;
467
468 // The reflection API only lets us release the last message in a
469 // repeated field. Therefore we iterate through the children
470 // starting with the last one.
471 const Py_ssize_t size = PyList_GET_SIZE(self->child_messages);
472 GOOGLE_DCHECK_EQ(size, message->GetReflection()->FieldSize(*message, field));
473 for (Py_ssize_t i = size - 1; i >= 0; --i) {
474 CMessage* child_cmessage = reinterpret_cast<CMessage*>(
475 PyList_GET_ITEM(self->child_messages, i));
476 ReleaseLastTo(self->parent, field, child_cmessage);
477 }
478
479 // Detach from containing message.
480 self->parent = NULL;
481 self->parent_field_descriptor = NULL;
482 self->message = NULL;
483 self->owner.reset();
484
485 return 0;
486 }
487
SetOwner(RepeatedCompositeContainer * self,const shared_ptr<Message> & new_owner)488 int SetOwner(RepeatedCompositeContainer* self,
489 const shared_ptr<Message>& new_owner) {
490 GOOGLE_CHECK_ATTACHED(self);
491
492 self->owner = new_owner;
493 const Py_ssize_t n = PyList_GET_SIZE(self->child_messages);
494 for (Py_ssize_t i = 0; i < n; ++i) {
495 PyObject* msg = PyList_GET_ITEM(self->child_messages, i);
496 if (cmessage::SetOwner(reinterpret_cast<CMessage*>(msg), new_owner) == -1) {
497 return -1;
498 }
499 }
500 return 0;
501 }
502
503 // The private constructor of RepeatedCompositeContainer objects.
NewContainer(CMessage * parent,const FieldDescriptor * parent_field_descriptor,CMessageClass * concrete_class)504 PyObject *NewContainer(
505 CMessage* parent,
506 const FieldDescriptor* parent_field_descriptor,
507 CMessageClass* concrete_class) {
508 if (!CheckFieldBelongsToMessage(parent_field_descriptor, parent->message)) {
509 return NULL;
510 }
511
512 RepeatedCompositeContainer* self =
513 reinterpret_cast<RepeatedCompositeContainer*>(
514 PyType_GenericAlloc(&RepeatedCompositeContainer_Type, 0));
515 if (self == NULL) {
516 return NULL;
517 }
518
519 self->message = parent->message;
520 self->parent = parent;
521 self->parent_field_descriptor = parent_field_descriptor;
522 self->owner = parent->owner;
523 Py_INCREF(concrete_class);
524 self->child_message_class = concrete_class;
525 self->child_messages = PyList_New(0);
526
527 return reinterpret_cast<PyObject*>(self);
528 }
529
Dealloc(RepeatedCompositeContainer * self)530 static void Dealloc(RepeatedCompositeContainer* self) {
531 Py_CLEAR(self->child_messages);
532 Py_CLEAR(self->child_message_class);
533 // TODO(tibell): Do we need to call delete on these objects to make
534 // sure their destructors are called?
535 self->owner.reset();
536
537 Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
538 }
539
540 static PySequenceMethods SqMethods = {
541 (lenfunc)Length, /* sq_length */
542 0, /* sq_concat */
543 0, /* sq_repeat */
544 (ssizeargfunc)Item /* sq_item */
545 };
546
547 static PyMappingMethods MpMethods = {
548 (lenfunc)Length, /* mp_length */
549 (binaryfunc)Subscript, /* mp_subscript */
550 (objobjargproc)AssignSubscript,/* mp_ass_subscript */
551 };
552
553 static PyMethodDef Methods[] = {
554 { "add", (PyCFunction) Add, METH_VARARGS | METH_KEYWORDS,
555 "Adds an object to the repeated container." },
556 { "extend", (PyCFunction) Extend, METH_O,
557 "Adds objects to the repeated container." },
558 { "pop", (PyCFunction)Pop, METH_VARARGS,
559 "Removes an object from the repeated container and returns it." },
560 { "remove", (PyCFunction) Remove, METH_O,
561 "Removes an object from the repeated container." },
562 { "sort", (PyCFunction) Sort, METH_VARARGS | METH_KEYWORDS,
563 "Sorts the repeated container." },
564 { "MergeFrom", (PyCFunction) MergeFrom, METH_O,
565 "Adds objects to the repeated container." },
566 { NULL, NULL }
567 };
568
569 } // namespace repeated_composite_container
570
571 PyTypeObject RepeatedCompositeContainer_Type = {
572 PyVarObject_HEAD_INIT(&PyType_Type, 0)
573 FULL_MODULE_NAME ".RepeatedCompositeContainer", // tp_name
574 sizeof(RepeatedCompositeContainer), // tp_basicsize
575 0, // tp_itemsize
576 (destructor)repeated_composite_container::Dealloc, // tp_dealloc
577 0, // tp_print
578 0, // tp_getattr
579 0, // tp_setattr
580 0, // tp_compare
581 0, // tp_repr
582 0, // tp_as_number
583 &repeated_composite_container::SqMethods, // tp_as_sequence
584 &repeated_composite_container::MpMethods, // tp_as_mapping
585 PyObject_HashNotImplemented, // tp_hash
586 0, // tp_call
587 0, // tp_str
588 0, // tp_getattro
589 0, // tp_setattro
590 0, // tp_as_buffer
591 Py_TPFLAGS_DEFAULT, // tp_flags
592 "A Repeated scalar container", // tp_doc
593 0, // tp_traverse
594 0, // tp_clear
595 (richcmpfunc)repeated_composite_container::RichCompare, // tp_richcompare
596 0, // tp_weaklistoffset
597 0, // tp_iter
598 0, // tp_iternext
599 repeated_composite_container::Methods, // tp_methods
600 0, // tp_members
601 0, // tp_getset
602 0, // tp_base
603 0, // tp_dict
604 0, // tp_descr_get
605 0, // tp_descr_set
606 0, // tp_dictoffset
607 0, // tp_init
608 };
609
610 } // namespace python
611 } // namespace protobuf
612 } // namespace google
613