Added map support.
diff --git a/python/map.c b/python/map.c
new file mode 100644
index 0000000..aed6ab5
--- /dev/null
+++ b/python/map.c
@@ -0,0 +1,487 @@
+/*
+ * Copyright (c) 2009-2021, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Google LLC nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "python/map.h"
+
+#include "python/convert.h"
+#include "python/message.h"
+#include "python/protobuf.h"
+
+// -----------------------------------------------------------------------------
+// MapContainer
+// -----------------------------------------------------------------------------
+
+typedef struct {
+ PyObject_HEAD
+ PyObject* arena;
+ uintptr_t field; // upb_fielddef*, low bit 1 == unset
+ union {
+ upb_map* map; // when set, the data for this array.
+ PyObject* parent; // when unset owning pointer to parent message.
+ };
+ int version;
+} PyUpb_MapContainer;
+
+static PyObject* PyUpb_MapIterator_New(PyUpb_MapContainer* map);
+
+static bool PyUpb_MapContainer_IsUnset(PyUpb_MapContainer* self) {
+ return self->field & 1;
+}
+
+static upb_map* PyUpb_MapContainer_GetIfWritable(PyUpb_MapContainer* self) {
+ return PyUpb_MapContainer_IsUnset(self) ? NULL : self->map;
+}
+
+static const upb_fielddef* PyUpb_MapContainer_GetField(
+ PyUpb_MapContainer* self) {
+ return (const upb_fielddef*)(self->field & ~(uintptr_t)1);
+}
+
+static void PyUpb_MapContainer_Dealloc(void* _self) {
+ PyUpb_MapContainer* self = _self;
+ Py_DECREF(self->arena);
+ if (PyUpb_MapContainer_IsUnset(self)) {
+ PyUpb_CMessage_CacheDelete(self->parent, PyUpb_MapContainer_GetField(self));
+ Py_DECREF(self->parent);
+ } else {
+ PyUpb_ObjCache_Delete(self->map);
+ }
+ PyUpb_Dealloc(_self);
+}
+
+PyTypeObject* PyUpb_MapContainer_GetClass(const upb_fielddef* f) {
+ assert(upb_fielddef_ismap(f));
+ PyUpb_ModuleState* state = PyUpb_ModuleState_Get();
+ return upb_fielddef_issubmsg(f) ? state->message_map_container_type
+ : state->scalar_map_container_type;
+}
+
+PyObject* PyUpb_MapContainer_NewUnset(PyObject* parent, const upb_fielddef* f,
+ PyObject* arena) {
+ PyTypeObject* cls = PyUpb_MapContainer_GetClass(f);
+ // We are GC because of the MutableMapping base class. Ideally we could
+ // implement them ourselves so we don't need a base so we don't need to be GC.
+ PyUpb_MapContainer* map = (void*)PyType_GenericAlloc(cls, 0);
+ map->arena = arena;
+ map->field = (uintptr_t)f | 1;
+ map->parent = parent;
+ map->version = 0;
+ Py_INCREF(arena);
+ Py_INCREF(parent);
+ return &map->ob_base;
+}
+
+void PyUpb_MapContainer_SwitchToSet(PyObject* _self, upb_map* map) {
+ PyUpb_MapContainer* self = (PyUpb_MapContainer*)_self;
+ PyUpb_ObjCache_Add(map, &self->ob_base);
+ Py_DECREF(self->parent);
+ self->map = map; // Overwrites self->parent.
+ self->field = self->field & ~(uintptr_t)1;
+ assert(!PyUpb_MapContainer_IsUnset(self));
+}
+
+void PyUpb_MapContainer_Invalidate(PyObject* obj) {
+ PyUpb_MapContainer* self = (PyUpb_MapContainer*)obj;
+ self->version++;
+}
+
+static upb_map* PyUpb_MapContainer_AssureWritable(PyUpb_MapContainer* self) {
+ self->version++;
+ upb_map* map = PyUpb_MapContainer_GetIfWritable(self);
+ if (map) return map; // Already writable.
+
+ const upb_fielddef* f = PyUpb_MapContainer_GetField(self);
+ upb_arena* arena = PyUpb_Arena_Get(self->arena);
+ const upb_msgdef* entry_m = upb_fielddef_msgsubdef(f);
+ const upb_fielddef* key_f = upb_msgdef_field(entry_m, 0);
+ const upb_fielddef* val_f = upb_msgdef_field(entry_m, 1);
+ map = upb_map_new(arena, upb_fielddef_type(key_f), upb_fielddef_type(val_f));
+ upb_msgval msgval = {.map_val = map};
+ PyUpb_CMessage_SetConcreteSubobj(self->parent, f, msgval);
+ PyUpb_MapContainer_SwitchToSet((PyObject*)self, map);
+ return map;
+}
+
+int PyUpb_MapContainer_AssignSubscript(PyObject* _self, PyObject* key,
+ PyObject* val) {
+ PyUpb_MapContainer* self = (PyUpb_MapContainer*)_self;
+ upb_map* map = PyUpb_MapContainer_AssureWritable(self);
+ const upb_fielddef* f = PyUpb_MapContainer_GetField(self);
+ const upb_msgdef* entry_m = upb_fielddef_msgsubdef(f);
+ const upb_fielddef* key_f = upb_msgdef_field(entry_m, 0);
+ const upb_fielddef* val_f = upb_msgdef_field(entry_m, 1);
+ upb_arena* arena = PyUpb_Arena_Get(self->arena);
+ upb_msgval u_key, u_val;
+ if (!PyUpb_PyToUpb(key, key_f, &u_key, arena)) return -1;
+
+ if (val) {
+ if (!PyUpb_PyToUpb(val, val_f, &u_val, arena)) return -1;
+ upb_map_set(map, u_key, u_val, arena);
+ } else {
+ if (!upb_map_delete(map, u_key)) {
+ PyErr_Format(PyExc_KeyError, "Key not present in map");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+PyObject* PyUpb_MapContainer_Contains(PyObject* _self, PyObject* key) {
+ PyUpb_MapContainer* self = (PyUpb_MapContainer*)_self;
+ upb_map* map = PyUpb_MapContainer_GetIfWritable(self);
+ if (!map) Py_RETURN_FALSE;
+ const upb_fielddef* f = PyUpb_MapContainer_GetField(self);
+ const upb_msgdef* entry_m = upb_fielddef_msgsubdef(f);
+ const upb_fielddef* key_f = upb_msgdef_field(entry_m, 0);
+ upb_msgval u_key;
+ if (!PyUpb_PyToUpb(key, key_f, &u_key, NULL)) return NULL;
+ if (upb_map_get(map, u_key, NULL)) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+}
+
+PyObject* PyUpb_MapContainer_Clear(PyObject* _self, PyObject* key) {
+ PyUpb_MapContainer* self = (PyUpb_MapContainer*)_self;
+ upb_map* map = PyUpb_MapContainer_GetIfWritable(self);
+ if (map) upb_map_clear(map);
+ Py_RETURN_NONE;
+}
+
+static PyObject* PyUpb_MapContainer_Get(PyObject* _self, PyObject* args,
+ PyObject* kwargs) {
+ PyUpb_MapContainer* self = (PyUpb_MapContainer*)_self;
+ static const char* kwlist[] = {"key", "default", NULL};
+ PyObject* key;
+ PyObject* default_value = NULL;
+ upb_map* map = PyUpb_MapContainer_GetIfWritable(self);
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", (char**)kwlist, &key,
+ &default_value)) {
+ return NULL;
+ }
+
+ const upb_fielddef* f = PyUpb_MapContainer_GetField(self);
+ const upb_msgdef* entry_m = upb_fielddef_msgsubdef(f);
+ const upb_fielddef* key_f = upb_msgdef_field(entry_m, 0);
+ const upb_fielddef* val_f = upb_msgdef_field(entry_m, 1);
+ upb_arena* arena = PyUpb_Arena_Get(self->arena);
+ upb_msgval u_key, u_val;
+ if (!PyUpb_PyToUpb(key, key_f, &u_key, arena)) return NULL;
+ if (map && upb_map_get(map, u_key, &u_val)) {
+ return PyUpb_UpbToPy(u_val, val_f, self->arena);
+ } else {
+ if (default_value) {
+ Py_INCREF(default_value);
+ return default_value;
+ } else {
+ Py_RETURN_NONE;
+ }
+ }
+}
+
+static PyObject* PyUpb_MapContainer_GetEntryClass(PyObject* _self,
+ PyObject* arg) {
+ PyUpb_MapContainer* self = (PyUpb_MapContainer*)_self;
+ const upb_fielddef* f = PyUpb_MapContainer_GetField(self);
+ const upb_msgdef* entry_m = upb_fielddef_msgsubdef(f);
+ return PyUpb_Descriptor_GetClass(entry_m);
+}
+
+Py_ssize_t PyUpb_MapContainer_Length(PyObject* _self) {
+ PyUpb_MapContainer* self = (PyUpb_MapContainer*)_self;
+ upb_map* map = PyUpb_MapContainer_GetIfWritable(self);
+ return map ? upb_map_size(map) : 0;
+}
+
+PyUpb_MapContainer* PyUpb_MapContainer_Check(PyObject* _self) {
+ PyUpb_ModuleState* state = PyUpb_ModuleState_Get();
+ if (!PyObject_TypeCheck(_self, state->message_map_container_type) &&
+ !PyObject_TypeCheck(_self, state->scalar_map_container_type)) {
+ PyErr_Format(PyExc_TypeError, "Expected protobuf map, but got %R", _self);
+ return NULL;
+ }
+ return (PyUpb_MapContainer*)_self;
+}
+
+int PyUpb_CMessage_InitMapAttributes(PyObject* map, PyObject* value,
+ const upb_fielddef* f);
+
+static PyObject* PyUpb_MapContainer_MergeFrom(PyObject* _self, PyObject* _arg) {
+ PyUpb_MapContainer* self = (PyUpb_MapContainer*)_self;
+ const upb_fielddef* f = PyUpb_MapContainer_GetField(self);
+
+ if (PyDict_Check(_arg)) {
+ return PyErr_Format(PyExc_AttributeError, "Merging of dict is not allowed");
+ }
+
+ if (PyUpb_CMessage_InitMapAttributes(_self, _arg, f) < 0) {
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+static PyObject* PyUpb_MapContainer_Repr(PyObject* _self) {
+ PyUpb_MapContainer* self = (PyUpb_MapContainer*)_self;
+ upb_map* map = PyUpb_MapContainer_GetIfWritable(self);
+ PyObject* dict = PyDict_New();
+ if (map) {
+ size_t iter = UPB_MAP_BEGIN;
+ const upb_fielddef* f = PyUpb_MapContainer_GetField(self);
+ const upb_msgdef* entry_m = upb_fielddef_msgsubdef(f);
+ const upb_fielddef* key_f = upb_msgdef_field(entry_m, 0);
+ const upb_fielddef* val_f = upb_msgdef_field(entry_m, 1);
+ while (upb_mapiter_next(map, &iter)) {
+ PyObject* key =
+ PyUpb_UpbToPy(upb_mapiter_key(map, iter), key_f, self->arena);
+ PyObject* val =
+ PyUpb_UpbToPy(upb_mapiter_value(map, iter), val_f, self->arena);
+ PyDict_SetItem(dict, key, val);
+ Py_DECREF(key);
+ Py_DECREF(val);
+ }
+ }
+ PyObject* repr = PyObject_Repr(dict);
+ Py_DECREF(dict);
+ return repr;
+}
+
+PyObject* PyUpb_MapContainer_Subscript(PyObject* _self, PyObject* key) {
+ PyUpb_MapContainer* self = (PyUpb_MapContainer*)_self;
+ upb_map* map = PyUpb_MapContainer_GetIfWritable(self);
+ const upb_fielddef* f = PyUpb_MapContainer_GetField(self);
+ const upb_msgdef* entry_m = upb_fielddef_msgsubdef(f);
+ const upb_fielddef* key_f = upb_msgdef_field(entry_m, 0);
+ const upb_fielddef* val_f = upb_msgdef_field(entry_m, 1);
+ upb_arena* arena = PyUpb_Arena_Get(self->arena);
+ upb_msgval u_key, u_val;
+ if (!PyUpb_PyToUpb(key, key_f, &u_key, arena)) return NULL;
+ if (!map || !upb_map_get(map, u_key, &u_val)) {
+ map = PyUpb_MapContainer_AssureWritable(self);
+ upb_arena* arena = PyUpb_Arena_Get(self->arena);
+ if (upb_fielddef_issubmsg(val_f)) {
+ u_val.msg_val = upb_msg_new(upb_fielddef_msgsubdef(val_f), arena);
+ } else {
+ memset(&u_val, 0, sizeof(u_val));
+ }
+ upb_map_set(map, u_key, u_val, arena);
+ }
+ return PyUpb_UpbToPy(u_val, val_f, self->arena);
+}
+
+PyObject* PyUpb_MapContainer_GetOrCreateWrapper(upb_map* u_map,
+ const upb_fielddef* f,
+ PyObject* arena) {
+ PyObject* ret = PyUpb_ObjCache_Get(u_map);
+
+ if (!ret) {
+ PyTypeObject* cls = PyUpb_MapContainer_GetClass(f);
+ PyUpb_MapContainer* map = (void*)PyType_GenericAlloc(cls, 0);
+ map->arena = arena;
+ map->field = (uintptr_t)f;
+ map->map = u_map;
+ map->version = 0;
+ ret = &map->ob_base;
+ Py_INCREF(arena);
+ PyUpb_ObjCache_Add(u_map, ret);
+ }
+
+ return ret;
+}
+
+// -----------------------------------------------------------------------------
+// ScalarMapContainer
+// -----------------------------------------------------------------------------
+
+static PyMethodDef PyUpb_ScalarMapContainer_Methods[] = {
+ {"__contains__", PyUpb_MapContainer_Contains, METH_O,
+ "Tests whether a key is a member of the map."},
+ {"clear", PyUpb_MapContainer_Clear, METH_NOARGS,
+ "Removes all elements from the map."},
+ {"get", (PyCFunction)PyUpb_MapContainer_Get, METH_VARARGS | METH_KEYWORDS,
+ "Gets the value for the given key if present, or otherwise a default"},
+ {"GetEntryClass", PyUpb_MapContainer_GetEntryClass, METH_NOARGS,
+ "Return the class used to build Entries of (key, value) pairs."},
+ {"MergeFrom", PyUpb_MapContainer_MergeFrom, METH_O,
+ "Merges a map into the current map."},
+ /*
+ { "__deepcopy__", (PyCFunction)DeepCopy, METH_VARARGS,
+ "Makes a deep copy of the class." },
+ { "__reduce__", (PyCFunction)Reduce, METH_NOARGS,
+ "Outputs picklable representation of the repeated field." },
+ */
+ {NULL, NULL},
+};
+
+static PyType_Slot PyUpb_ScalarMapContainer_Slots[] = {
+ {Py_tp_dealloc, PyUpb_MapContainer_Dealloc},
+ {Py_mp_length, PyUpb_MapContainer_Length},
+ {Py_mp_subscript, PyUpb_MapContainer_Subscript},
+ {Py_mp_ass_subscript, PyUpb_MapContainer_AssignSubscript},
+ {Py_tp_methods, PyUpb_ScalarMapContainer_Methods},
+ {Py_tp_iter, PyUpb_MapIterator_New},
+ {Py_tp_repr, PyUpb_MapContainer_Repr},
+ {Py_tp_hash, PyObject_HashNotImplemented},
+ {0, NULL},
+};
+
+static PyType_Spec PyUpb_ScalarMapContainer_Spec = {
+ PYUPB_MODULE_NAME ".ScalarMapContainer",
+ sizeof(PyUpb_MapContainer),
+ 0,
+ Py_TPFLAGS_DEFAULT,
+ PyUpb_ScalarMapContainer_Slots,
+};
+
+// -----------------------------------------------------------------------------
+// MessageMapContainer
+// -----------------------------------------------------------------------------
+
+static PyMethodDef PyUpb_MessageMapContainer_Methods[] = {
+ {"__contains__", PyUpb_MapContainer_Contains, METH_O,
+ "Tests whether the map contains this element."},
+ {"clear", PyUpb_MapContainer_Clear, METH_NOARGS,
+ "Removes all elements from the map."},
+ {"get", (PyCFunction)PyUpb_MapContainer_Get, METH_VARARGS | METH_KEYWORDS,
+ "Gets the value for the given key if present, or otherwise a default"},
+ {"get_or_create", PyUpb_MapContainer_Subscript, METH_O,
+ "Alias for getitem, useful to make explicit that the map is mutated."},
+ {"GetEntryClass", PyUpb_MapContainer_GetEntryClass, METH_NOARGS,
+ "Return the class used to build Entries of (key, value) pairs."},
+ {"MergeFrom", PyUpb_MapContainer_MergeFrom, METH_O,
+ "Merges a map into the current map."},
+ /*
+ { "__deepcopy__", (PyCFunction)DeepCopy, METH_VARARGS,
+ "Makes a deep copy of the class." },
+ { "__reduce__", (PyCFunction)Reduce, METH_NOARGS,
+ "Outputs picklable representation of the repeated field." },
+ */
+ {NULL, NULL},
+};
+
+static PyType_Slot PyUpb_MessageMapContainer_Slots[] = {
+ {Py_tp_dealloc, PyUpb_MapContainer_Dealloc},
+ {Py_mp_length, PyUpb_MapContainer_Length},
+ {Py_mp_subscript, PyUpb_MapContainer_Subscript},
+ {Py_mp_ass_subscript, PyUpb_MapContainer_AssignSubscript},
+ {Py_tp_methods, PyUpb_MessageMapContainer_Methods},
+ {Py_tp_iter, PyUpb_MapIterator_New},
+ {Py_tp_repr, PyUpb_MapContainer_Repr},
+ {Py_tp_hash, PyObject_HashNotImplemented},
+ {0, NULL}};
+
+static PyType_Spec PyUpb_MessageMapContainer_Spec = {
+ PYUPB_MODULE_NAME ".MessageMapContainer", sizeof(PyUpb_MapContainer), 0,
+ Py_TPFLAGS_DEFAULT, PyUpb_MessageMapContainer_Slots};
+
+// -----------------------------------------------------------------------------
+// MapIterator
+// -----------------------------------------------------------------------------
+
+typedef struct {
+ PyObject_HEAD
+ PyUpb_MapContainer* map; // We own a reference.
+ size_t iter;
+ int version;
+} PyUpb_MapIterator;
+
+static PyObject* PyUpb_MapIterator_New(PyUpb_MapContainer* map) {
+ PyUpb_ModuleState* state = PyUpb_ModuleState_Get();
+ PyUpb_MapIterator* iter =
+ (void*)PyType_GenericAlloc(state->map_iterator_type, 0);
+ iter->map = map;
+ iter->iter = UPB_MAP_BEGIN;
+ iter->version = map->version;
+ Py_INCREF(map);
+ return &iter->ob_base;
+}
+
+static void PyUpb_MapIterator_Dealloc(void* _self) {
+ PyUpb_MapIterator* self = (PyUpb_MapIterator*)_self;
+ Py_DECREF(&self->map->ob_base);
+ PyUpb_Dealloc(_self);
+}
+
+PyObject* PyUpb_MapIterator_IterNext(PyObject* _self) {
+ PyUpb_MapIterator* self = (PyUpb_MapIterator*)_self;
+ if (self->version != self->map->version) {
+ return PyErr_Format(PyExc_RuntimeError, "Map modified during iteration.");
+ }
+ upb_map* map = PyUpb_MapContainer_GetIfWritable(self->map);
+ if (!map) return NULL;
+ if (!upb_mapiter_next(map, &self->iter)) return NULL;
+ upb_msgval key = upb_mapiter_key(map, self->iter);
+ const upb_fielddef* f = PyUpb_MapContainer_GetField(self->map);
+ const upb_msgdef* entry_m = upb_fielddef_msgsubdef(f);
+ const upb_fielddef* key_f = upb_msgdef_field(entry_m, 0);
+ return PyUpb_UpbToPy(key, key_f, self->map->arena);
+}
+
+static PyType_Slot PyUpb_MapIterator_Slots[] = {
+ {Py_tp_dealloc, PyUpb_MapIterator_Dealloc},
+ {Py_tp_iter, PyObject_SelfIter},
+ {Py_tp_iternext, PyUpb_MapIterator_IterNext},
+ {0, NULL}};
+
+static PyType_Spec PyUpb_MapIterator_Spec = {
+ PYUPB_MODULE_NAME ".MapIterator", sizeof(PyUpb_MapIterator), 0,
+ Py_TPFLAGS_DEFAULT, PyUpb_MapIterator_Slots};
+
+// -----------------------------------------------------------------------------
+// Top Level
+// -----------------------------------------------------------------------------
+
+static PyObject* GetMutableMappingBase(void) {
+ PyObject* collections = NULL;
+ PyObject* mapping = NULL;
+ PyObject* bases = NULL;
+ if ((collections = PyImport_ImportModule("collections.abc")) &&
+ (mapping = PyObject_GetAttrString(collections, "MutableMapping"))) {
+ bases = Py_BuildValue("(O)", mapping);
+ }
+ Py_XDECREF(collections);
+ Py_XDECREF(mapping);
+ return bases;
+}
+
+bool PyUpb_Map_Init(PyObject* m) {
+ PyUpb_ModuleState* state = PyUpb_ModuleState_GetFromModule(m);
+ PyObject* bases = GetMutableMappingBase();
+ if (!bases) return false;
+
+ state->message_map_container_type =
+ PyUpb_AddClassWithBases(m, &PyUpb_MessageMapContainer_Spec, bases);
+ state->scalar_map_container_type =
+ PyUpb_AddClassWithBases(m, &PyUpb_ScalarMapContainer_Spec, bases);
+ state->map_iterator_type = PyUpb_AddClass(m, &PyUpb_MapIterator_Spec);
+
+ Py_DECREF(bases);
+
+ return state->message_map_container_type &&
+ state->scalar_map_container_type && state->map_iterator_type;
+}