Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCampbell Barton <ideasman42@gmail.com>2021-05-11 02:40:41 +0300
committerCampbell Barton <ideasman42@gmail.com>2021-05-14 17:36:49 +0300
commit265d97556aa0f0f2a0e4dd7584e3b8573bbddd54 (patch)
tree76ffd53c8853d68a5adb87bb8c0b4f80d0ee9a90
parent65f955081370e77a61d822da1fa78960c8a0149e (diff)
PyAPI: use iterators for ID property methods (keys, values & items)
- Matches changes in Python 3.x dictionary methods. - Iterating now raises a run-time error if the property-group changes size during iteration. - IDPropertyGroup.iteritems() has been removed. - IDPropertyGroup View & Iterator types have been added. - Some set functionality from dict_keys/values/items aren't yet supported (isdisjoint method and boolean set style operations). Proposed as part of T85675.
-rw-r--r--release/scripts/modules/rna_prop_ui.py2
-rw-r--r--source/blender/python/generic/idprop_py_api.c578
-rw-r--r--source/blender/python/generic/idprop_py_api.h46
-rw-r--r--source/blender/python/intern/bpy_rna.c30
-rw-r--r--tests/python/bl_pyapi_idprop.py46
5 files changed, 546 insertions, 156 deletions
diff --git a/release/scripts/modules/rna_prop_ui.py b/release/scripts/modules/rna_prop_ui.py
index e3158118146..54cde1e1c04 100644
--- a/release/scripts/modules/rna_prop_ui.py
+++ b/release/scripts/modules/rna_prop_ui.py
@@ -235,7 +235,7 @@ def draw(layout, context, context_member, property_type, use_edit=True):
assert(isinstance(rna_item, property_type))
- items = rna_item.items()
+ items = list(rna_item.items())
items.sort()
# TODO: Allow/support adding new custom props to overrides.
diff --git a/source/blender/python/generic/idprop_py_api.c b/source/blender/python/generic/idprop_py_api.c
index fc7054f675a..9b6ca7fcec5 100644
--- a/source/blender/python/generic/idprop_py_api.c
+++ b/source/blender/python/generic/idprop_py_api.c
@@ -42,6 +42,18 @@ extern bool pyrna_id_FromPyObject(PyObject *obj, ID **id);
extern PyObject *pyrna_id_CreatePyObject(ID *id);
extern bool pyrna_id_CheckPyObject(PyObject *obj);
+/* Currently there is no need to expose this publicly. */
+static PyObject *BPy_IDGroup_IterKeys_CreatePyObject(BPy_IDProperty *group, const bool reversed);
+static PyObject *BPy_IDGroup_IterValues_CreatePyObject(BPy_IDProperty *group, const bool reversed);
+static PyObject *BPy_IDGroup_IterItems_CreatePyObject(BPy_IDProperty *group, const bool reversed);
+
+static PyObject *BPy_IDGroup_ViewKeys_CreatePyObject(BPy_IDProperty *group);
+static PyObject *BPy_IDGroup_ViewValues_CreatePyObject(BPy_IDProperty *group);
+static PyObject *BPy_IDGroup_ViewItems_CreatePyObject(BPy_IDProperty *group);
+
+static BPy_IDGroup_View *IDGroup_View_New_WithType(BPy_IDProperty *group, PyTypeObject *type);
+static int BPy_IDGroup_Contains(BPy_IDProperty *self, PyObject *value);
+
/* -------------------------------------------------------------------- */
/** \name Python from ID-Property (Internal Conversions)
*
@@ -756,13 +768,7 @@ static int BPy_IDGroup_Map_SetItem(BPy_IDProperty *self, PyObject *key, PyObject
static PyObject *BPy_IDGroup_iter(BPy_IDProperty *self)
{
- BPy_IDGroup_Iter *iter = PyObject_GC_New(BPy_IDGroup_Iter, &BPy_IDGroup_Iter_Type);
- iter->group = self;
- Py_INCREF(self);
- iter->mode = IDPROP_ITER_KEYS;
- iter->cur = self->prop->data.group.first;
- PyObject_GC_Track(iter);
- return (PyObject *)iter;
+ return BPy_IDGroup_ViewKeys_CreatePyObject(self);
}
/* for simple, non nested types this is the same as BPy_IDGroup_WrapData */
@@ -875,6 +881,370 @@ PyObject *BPy_IDGroup_MapDataToPy(IDProperty *prop)
/** \} */
/* -------------------------------------------------------------------- */
+/** \name ID-Property Group Iterator Type
+ * \{ */
+
+static PyObject *BPy_IDGroup_Iter_repr(BPy_IDGroup_Iter *self)
+{
+ if (self->group == NULL) {
+ return PyUnicode_FromFormat("<%s>", Py_TYPE(self)->tp_name);
+ }
+ return PyUnicode_FromFormat("<%s \"%s\">", Py_TYPE(self)->tp_name, self->group->prop->name);
+}
+
+static void BPy_IDGroup_Iter_dealloc(BPy_IDGroup_Iter *self)
+{
+ if (self->group != NULL) {
+ PyObject_GC_UnTrack(self);
+ }
+ Py_CLEAR(self->group);
+ PyObject_GC_Del(self);
+}
+
+static int BPy_IDGroup_Iter_traverse(BPy_IDGroup_Iter *self, visitproc visit, void *arg)
+{
+ Py_VISIT(self->group);
+ return 0;
+}
+
+static int BPy_IDGroup_Iter_clear(BPy_IDGroup_Iter *self)
+{
+ Py_CLEAR(self->group);
+ return 0;
+}
+
+static bool BPy_Group_Iter_same_size_or_raise_error(BPy_IDGroup_Iter *self)
+{
+ if (self->len_init == self->group->prop->len) {
+ return true;
+ }
+ PyErr_SetString(PyExc_RuntimeError, "IDPropertyGroup changed size during iteration");
+ return false;
+}
+
+static PyObject *BPy_Group_IterKeys_next(BPy_IDGroup_Iter *self)
+{
+ if (self->cur != NULL) {
+ /* When `cur` is set, `group` cannot be NULL. */
+ if (!BPy_Group_Iter_same_size_or_raise_error(self)) {
+ return NULL;
+ }
+ IDProperty *cur = self->cur;
+ self->cur = self->reversed ? self->cur->prev : self->cur->next;
+ return PyUnicode_FromString(cur->name);
+ }
+ PyErr_SetNone(PyExc_StopIteration);
+ return NULL;
+}
+
+static PyObject *BPy_Group_IterValues_next(BPy_IDGroup_Iter *self)
+{
+ if (self->cur != NULL) {
+ /* When `cur` is set, `group` cannot be NULL. */
+ if (!BPy_Group_Iter_same_size_or_raise_error(self)) {
+ return NULL;
+ }
+ IDProperty *cur = self->cur;
+ self->cur = self->reversed ? self->cur->prev : self->cur->next;
+ return BPy_IDGroup_WrapData(self->group->id, cur, self->group->prop);
+ }
+ PyErr_SetNone(PyExc_StopIteration);
+ return NULL;
+}
+
+static PyObject *BPy_Group_IterItems_next(BPy_IDGroup_Iter *self)
+{
+ if (self->cur != NULL) {
+ /* When `cur` is set, `group` cannot be NULL. */
+ if (!BPy_Group_Iter_same_size_or_raise_error(self)) {
+ return NULL;
+ }
+ IDProperty *cur = self->cur;
+ self->cur = self->reversed ? self->cur->prev : self->cur->next;
+ PyObject *ret = PyTuple_New(2);
+ PyTuple_SET_ITEMS(ret,
+ PyUnicode_FromString(cur->name),
+ BPy_IDGroup_WrapData(self->group->id, cur, self->group->prop));
+ return ret;
+ }
+ PyErr_SetNone(PyExc_StopIteration);
+ return NULL;
+}
+
+PyTypeObject BPy_IDGroup_IterKeys_Type = {PyVarObject_HEAD_INIT(NULL, 0)};
+PyTypeObject BPy_IDGroup_IterValues_Type = {PyVarObject_HEAD_INIT(NULL, 0)};
+PyTypeObject BPy_IDGroup_IterItems_Type = {PyVarObject_HEAD_INIT(NULL, 0)};
+
+/* ID Property Group Iterator. */
+static void IDGroup_Iter_init_type(void)
+{
+#define SHARED_MEMBER_SET(member, value) \
+ { \
+ k_ty->member = v_ty->member = i_ty->member = value; \
+ } \
+ ((void)0)
+
+ PyTypeObject *k_ty = &BPy_IDGroup_IterKeys_Type;
+ PyTypeObject *v_ty = &BPy_IDGroup_IterValues_Type;
+ PyTypeObject *i_ty = &BPy_IDGroup_IterItems_Type;
+
+ /* Unique members. */
+ k_ty->tp_name = "IDPropertyGroupIterKeys";
+ v_ty->tp_name = "IDPropertyGroupIterValues";
+ i_ty->tp_name = "IDPropertyGroupIterItems";
+
+ k_ty->tp_iternext = (iternextfunc)BPy_Group_IterKeys_next;
+ v_ty->tp_iternext = (iternextfunc)BPy_Group_IterValues_next;
+ i_ty->tp_iternext = (iternextfunc)BPy_Group_IterItems_next;
+
+ /* Shared members. */
+ SHARED_MEMBER_SET(tp_basicsize, sizeof(BPy_IDGroup_Iter));
+ SHARED_MEMBER_SET(tp_dealloc, (destructor)BPy_IDGroup_Iter_dealloc);
+ SHARED_MEMBER_SET(tp_repr, (reprfunc)BPy_IDGroup_Iter_repr);
+ SHARED_MEMBER_SET(tp_flags, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC);
+ SHARED_MEMBER_SET(tp_traverse, (traverseproc)BPy_IDGroup_Iter_traverse);
+ SHARED_MEMBER_SET(tp_clear, (inquiry)BPy_IDGroup_Iter_clear);
+ SHARED_MEMBER_SET(tp_iter, PyObject_SelfIter);
+
+#undef SHARED_MEMBER_SET
+}
+
+static PyObject *IDGroup_Iter_New_WithType(BPy_IDProperty *group,
+ const bool reversed,
+ PyTypeObject *type)
+{
+ BLI_assert(group ? group->prop->type == IDP_GROUP : true);
+ BPy_IDGroup_Iter *iter = PyObject_GC_New(BPy_IDGroup_Iter, type);
+ iter->reversed = reversed;
+ iter->group = group;
+ if (group != NULL) {
+ Py_INCREF(group);
+ PyObject_GC_Track(iter);
+ iter->cur = (reversed ? group->prop->data.group.last : group->prop->data.group.first);
+ iter->len_init = group->prop->len;
+ }
+ else {
+ iter->cur = NULL;
+ iter->len_init = 0;
+ }
+ return (PyObject *)iter;
+}
+
+static PyObject *BPy_IDGroup_IterKeys_CreatePyObject(BPy_IDProperty *group, const bool reversed)
+{
+ return IDGroup_Iter_New_WithType(group, reversed, &BPy_IDGroup_IterKeys_Type);
+}
+
+static PyObject *BPy_IDGroup_IterValues_CreatePyObject(BPy_IDProperty *group, const bool reversed)
+{
+ return IDGroup_Iter_New_WithType(group, reversed, &BPy_IDGroup_IterValues_Type);
+}
+
+static PyObject *BPy_IDGroup_IterItems_CreatePyObject(BPy_IDProperty *group, const bool reversed)
+{
+ return IDGroup_Iter_New_WithType(group, reversed, &BPy_IDGroup_IterItems_Type);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name ID-Property Group View Types (Keys/Values/Items)
+ *
+ * This view types is a thin wrapper on keys/values/items, this matches Python's `dict_view` type.
+ * The is returned by `property.keys()` and is separate from the iterator that loops over keys.
+ *
+ * There are some less common features this type could support (matching Python's `dict_view`)
+ *
+ * TODO:
+ * - Efficient contains checks for values and items which currently convert to a list first.
+ * - Missing `dict_views.isdisjoint`.
+ * - Missing `tp_as_number` (`nb_subtract`, `nb_and`, `nb_xor`, `nb_or`).
+ * \{ */
+
+static PyObject *BPy_IDGroup_View_repr(BPy_IDGroup_View *self)
+{
+ if (self->group == NULL) {
+ return PyUnicode_FromFormat("<%s>", Py_TYPE(self)->tp_name);
+ }
+ return PyUnicode_FromFormat("<%s \"%s\">", Py_TYPE(self)->tp_name, self->group->prop->name);
+}
+
+static void BPy_IDGroup_View_dealloc(BPy_IDGroup_View *self)
+{
+ if (self->group != NULL) {
+ PyObject_GC_UnTrack(self);
+ }
+ Py_CLEAR(self->group);
+ PyObject_GC_Del(self);
+}
+
+static int BPy_IDGroup_View_traverse(BPy_IDGroup_View *self, visitproc visit, void *arg)
+{
+ Py_VISIT(self->group);
+ return 0;
+}
+
+static int BPy_IDGroup_View_clear(BPy_IDGroup_View *self)
+{
+ Py_CLEAR(self->group);
+ return 0;
+}
+
+/* View Specific API's (Key/Value/Items). */
+
+static PyObject *BPy_Group_ViewKeys_iter(BPy_IDGroup_View *self)
+{
+ return BPy_IDGroup_IterKeys_CreatePyObject(self->group, self->reversed);
+}
+
+static PyObject *BPy_Group_ViewValues_iter(BPy_IDGroup_View *self)
+{
+ return BPy_IDGroup_IterValues_CreatePyObject(self->group, self->reversed);
+}
+
+static PyObject *BPy_Group_ViewItems_iter(BPy_IDGroup_View *self)
+{
+ return BPy_IDGroup_IterItems_CreatePyObject(self->group, self->reversed);
+}
+
+static Py_ssize_t BPy_Group_View_len(BPy_IDGroup_View *self)
+{
+ if (self->group == NULL) {
+ return 0;
+ }
+ return self->group->prop->len;
+}
+
+static int BPy_Group_ViewKeys_Contains(BPy_IDGroup_View *self, PyObject *value)
+{
+ if (self->group == NULL) {
+ return 0;
+ }
+ return BPy_IDGroup_Contains(self->group, value);
+}
+
+static int BPy_Group_ViewValues_Contains(BPy_IDGroup_View *self, PyObject *value)
+{
+ if (self->group == NULL) {
+ return 0;
+ }
+ /* TODO: implement this without first converting to a list. */
+ PyObject *list = PySequence_List((PyObject *)self);
+ const int result = PySequence_Contains(list, value);
+ Py_DECREF(list);
+ return result;
+}
+
+static int BPy_Group_ViewItems_Contains(BPy_IDGroup_View *self, PyObject *value)
+{
+ if (self->group == NULL) {
+ return 0;
+ }
+ /* TODO: implement this without first converting to a list. */
+ PyObject *list = PySequence_List((PyObject *)self);
+ const int result = PySequence_Contains(list, value);
+ Py_DECREF(list);
+ return result;
+}
+
+static PySequenceMethods BPy_IDGroup_ViewKeys_as_sequence = {
+ (lenfunc)BPy_Group_View_len, /* sq_length */
+ 0, /* sq_concat */
+ 0, /* sq_repeat */
+ 0, /* sq_item */
+ 0, /* sq_slice */
+ 0, /* sq_ass_item */
+ 0, /* sq_ass_slice */
+ (objobjproc)BPy_Group_ViewKeys_Contains, /* sq_contains */
+};
+
+static PySequenceMethods BPy_IDGroup_ViewValues_as_sequence = {
+ (lenfunc)BPy_Group_View_len, /* sq_length */
+ 0, /* sq_concat */
+ 0, /* sq_repeat */
+ 0, /* sq_item */
+ 0, /* sq_slice */
+ 0, /* sq_ass_item */
+ 0, /* sq_ass_slice */
+ (objobjproc)BPy_Group_ViewValues_Contains, /* sq_contains */
+};
+
+static PySequenceMethods BPy_IDGroup_ViewItems_as_sequence = {
+ (lenfunc)BPy_Group_View_len, /* sq_length */
+ 0, /* sq_concat */
+ 0, /* sq_repeat */
+ 0, /* sq_item */
+ 0, /* sq_slice */
+ 0, /* sq_ass_item */
+ 0, /* sq_ass_slice */
+ (objobjproc)BPy_Group_ViewItems_Contains, /* sq_contains */
+};
+
+/* Methods. */
+
+PyDoc_STRVAR(BPy_IDGroup_View_reversed_doc,
+ "Return a reverse iterator over the ID Property keys values or items.");
+
+static PyObject *BPy_IDGroup_View_reversed(BPy_IDGroup_View *self, PyObject *UNUSED(ignored))
+{
+ BPy_IDGroup_View *result = IDGroup_View_New_WithType(self->group, Py_TYPE(self));
+ result->reversed = !self->reversed;
+ return (PyObject *)result;
+}
+
+static PyMethodDef BPy_IDGroup_View_methods[] = {
+ {"__reversed__",
+ (PyCFunction)(void (*)(void))BPy_IDGroup_View_reversed,
+ METH_NOARGS,
+ BPy_IDGroup_View_reversed_doc},
+ {NULL, NULL},
+};
+
+PyTypeObject BPy_IDGroup_ViewKeys_Type = {PyVarObject_HEAD_INIT(NULL, 0)};
+PyTypeObject BPy_IDGroup_ViewValues_Type = {PyVarObject_HEAD_INIT(NULL, 0)};
+PyTypeObject BPy_IDGroup_ViewItems_Type = {PyVarObject_HEAD_INIT(NULL, 0)};
+
+/* ID Property Group View. */
+static void IDGroup_View_init_type(void)
+{
+ PyTypeObject *k_ty = &BPy_IDGroup_ViewKeys_Type;
+ PyTypeObject *v_ty = &BPy_IDGroup_ViewValues_Type;
+ PyTypeObject *i_ty = &BPy_IDGroup_ViewItems_Type;
+
+ /* Unique members. */
+ k_ty->tp_name = "IDPropertyGroupViewKeys";
+ v_ty->tp_name = "IDPropertyGroupViewValues";
+ i_ty->tp_name = "IDPropertyGroupViewItems";
+
+ k_ty->tp_iter = (getiterfunc)BPy_Group_ViewKeys_iter;
+ v_ty->tp_iter = (getiterfunc)BPy_Group_ViewValues_iter;
+ i_ty->tp_iter = (getiterfunc)BPy_Group_ViewItems_iter;
+
+ k_ty->tp_as_sequence = &BPy_IDGroup_ViewKeys_as_sequence;
+ v_ty->tp_as_sequence = &BPy_IDGroup_ViewValues_as_sequence;
+ i_ty->tp_as_sequence = &BPy_IDGroup_ViewItems_as_sequence;
+
+ /* Shared members. */
+#define SHARED_MEMBER_SET(member, value) \
+ { \
+ k_ty->member = v_ty->member = i_ty->member = value; \
+ } \
+ ((void)0)
+
+ SHARED_MEMBER_SET(tp_basicsize, sizeof(BPy_IDGroup_View));
+ SHARED_MEMBER_SET(tp_dealloc, (destructor)BPy_IDGroup_View_dealloc);
+ SHARED_MEMBER_SET(tp_repr, (reprfunc)BPy_IDGroup_View_repr);
+ SHARED_MEMBER_SET(tp_flags, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC);
+ SHARED_MEMBER_SET(tp_traverse, (traverseproc)BPy_IDGroup_View_traverse);
+ SHARED_MEMBER_SET(tp_clear, (inquiry)BPy_IDGroup_View_clear);
+ SHARED_MEMBER_SET(tp_methods, BPy_IDGroup_View_methods);
+
+#undef SHARED_MEMBER_SET
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name ID-Property Group Methods
* \{ */
@@ -923,22 +1293,6 @@ static PyObject *BPy_IDGroup_pop(BPy_IDProperty *self, PyObject *args)
return pyform;
}
-PyDoc_STRVAR(
- BPy_IDGroup_iter_items_doc,
- ".. method:: iteritems()\n"
- "\n"
- " Iterate through the items in the dict; behaves like dictionary method iteritems.\n");
-static PyObject *BPy_IDGroup_iter_items(BPy_IDProperty *self)
-{
- BPy_IDGroup_Iter *iter = PyObject_GC_New(BPy_IDGroup_Iter, &BPy_IDGroup_Iter_Type);
- iter->group = self;
- Py_INCREF(self);
- iter->mode = IDPROP_ITER_ITEMS;
- iter->cur = self->prop->data.group.first;
- PyObject_GC_Track(iter);
- return (PyObject *)iter;
-}
-
/* utility function */
static void BPy_IDGroup_CorrectListLen(IDProperty *prop, PyObject *seq, int len, const char *func)
{
@@ -1023,13 +1377,37 @@ PyObject *BPy_Wrap_GetItems(ID *id, IDProperty *prop)
return seq;
}
+PyObject *BPy_Wrap_GetKeys_View_WithID(ID *id, IDProperty *prop)
+{
+ PyObject *self = prop ? idprop_py_from_idp_group(id, prop, NULL) : NULL;
+ PyObject *ret = BPy_IDGroup_ViewKeys_CreatePyObject((BPy_IDProperty *)self);
+ Py_XDECREF(self); /* Owned by `ret`. */
+ return ret;
+}
+
+PyObject *BPy_Wrap_GetValues_View_WithID(ID *id, IDProperty *prop)
+{
+ PyObject *self = prop ? idprop_py_from_idp_group(id, prop, NULL) : NULL;
+ PyObject *ret = BPy_IDGroup_ViewValues_CreatePyObject((BPy_IDProperty *)self);
+ Py_XDECREF(self); /* Owned by `ret`. */
+ return ret;
+}
+
+PyObject *BPy_Wrap_GetItems_View_WithID(ID *id, IDProperty *prop)
+{
+ PyObject *self = prop ? idprop_py_from_idp_group(id, prop, NULL) : NULL;
+ PyObject *ret = BPy_IDGroup_ViewItems_CreatePyObject((BPy_IDProperty *)self);
+ Py_XDECREF(self); /* Owned by `ret`. */
+ return ret;
+}
+
PyDoc_STRVAR(BPy_IDGroup_keys_doc,
".. method:: keys()\n"
"\n"
" Return the keys associated with this group as a list of strings.\n");
static PyObject *BPy_IDGroup_keys(BPy_IDProperty *self)
{
- return BPy_Wrap_GetKeys(self->prop);
+ return BPy_IDGroup_ViewKeys_CreatePyObject(self);
}
PyDoc_STRVAR(BPy_IDGroup_values_doc,
@@ -1038,16 +1416,16 @@ PyDoc_STRVAR(BPy_IDGroup_values_doc,
" Return the values associated with this group.\n");
static PyObject *BPy_IDGroup_values(BPy_IDProperty *self)
{
- return BPy_Wrap_GetValues(self->id, self->prop);
+ return BPy_IDGroup_ViewValues_CreatePyObject(self);
}
PyDoc_STRVAR(BPy_IDGroup_items_doc,
".. method:: items()\n"
"\n"
- " Return the items associated with this group.\n");
+ " Iterate through the items in the dict; behaves like dictionary method items.\n");
static PyObject *BPy_IDGroup_items(BPy_IDProperty *self)
{
- return BPy_Wrap_GetItems(self->id, self->prop);
+ return BPy_IDGroup_ViewItems_CreatePyObject(self);
}
static int BPy_IDGroup_Contains(BPy_IDProperty *self, PyObject *value)
@@ -1148,7 +1526,6 @@ static PyObject *BPy_IDGroup_get(BPy_IDProperty *self, PyObject *args)
static struct PyMethodDef BPy_IDGroup_methods[] = {
{"pop", (PyCFunction)BPy_IDGroup_pop, METH_VARARGS, BPy_IDGroup_pop_doc},
- {"iteritems", (PyCFunction)BPy_IDGroup_iter_items, METH_NOARGS, BPy_IDGroup_iter_items_doc},
{"keys", (PyCFunction)BPy_IDGroup_keys, METH_NOARGS, BPy_IDGroup_keys_doc},
{"values", (PyCFunction)BPy_IDGroup_values, METH_NOARGS, BPy_IDGroup_values_doc},
{"items", (PyCFunction)BPy_IDGroup_items, METH_NOARGS, BPy_IDGroup_items_doc},
@@ -1678,120 +2055,59 @@ PyTypeObject BPy_IDArray_Type = {
/** \} */
/* -------------------------------------------------------------------- */
-/** \name ID-Property Group Iterator Type
+/** \name Initialize Types
* \{ */
-static PyObject *IDGroup_Iter_repr(BPy_IDGroup_Iter *self)
+void IDProp_Init_Types(void)
{
- return PyUnicode_FromFormat("(ID Property Group Iter \"%s\")", self->group->prop->name);
-}
+ IDGroup_Iter_init_type();
+ IDGroup_View_init_type();
-static void BPy_IDGroup_Iter_dealloc(BPy_IDGroup_Iter *self)
-{
- PyObject_GC_UnTrack(self);
- Py_CLEAR(self->group);
- PyObject_GC_Del(self);
+ PyType_Ready(&BPy_IDGroup_Type);
+ PyType_Ready(&BPy_IDArray_Type);
+
+ PyType_Ready(&BPy_IDGroup_IterKeys_Type);
+ PyType_Ready(&BPy_IDGroup_IterValues_Type);
+ PyType_Ready(&BPy_IDGroup_IterItems_Type);
+
+ PyType_Ready(&BPy_IDGroup_ViewKeys_Type);
+ PyType_Ready(&BPy_IDGroup_ViewValues_Type);
+ PyType_Ready(&BPy_IDGroup_ViewItems_Type);
}
-static int BPy_IDGroup_Iter_traverse(BPy_IDGroup_Iter *self, visitproc visit, void *arg)
+/**
+ * \note `group` may be NULL, unlike most other uses of this argument.
+ * This is supported so RNA keys/values/items methods returns an iterator with the expected type:
+ * - Without having ID-properties.
+ * - Without supporting #BPy_IDProperty.prop being NULL, which would incur many more checks.
+ * Python's own dictionary-views also works this way too.
+ */
+static BPy_IDGroup_View *IDGroup_View_New_WithType(BPy_IDProperty *group, PyTypeObject *type)
{
- Py_VISIT(self->group);
- return 0;
+ BLI_assert(group ? group->prop->type == IDP_GROUP : true);
+ BPy_IDGroup_View *iter = PyObject_GC_New(BPy_IDGroup_View, type);
+ iter->reversed = false;
+ iter->group = group;
+ if (group != NULL) {
+ Py_INCREF(group);
+ PyObject_GC_Track(iter);
+ }
+ return iter;
}
-static int BPy_IDGroup_Iter_clear(BPy_IDGroup_Iter *self)
+static PyObject *BPy_IDGroup_ViewKeys_CreatePyObject(BPy_IDProperty *group)
{
- Py_CLEAR(self->group);
- return 0;
+ return (PyObject *)IDGroup_View_New_WithType(group, &BPy_IDGroup_ViewKeys_Type);
}
-static PyObject *BPy_Group_Iter_Next(BPy_IDGroup_Iter *self)
+static PyObject *BPy_IDGroup_ViewValues_CreatePyObject(BPy_IDProperty *group)
{
-
- if (self->cur) {
- PyObject *ret;
- IDProperty *cur;
-
- cur = self->cur;
- self->cur = self->cur->next;
-
- if (self->mode == IDPROP_ITER_ITEMS) {
- ret = PyTuple_New(2);
- PyTuple_SET_ITEMS(ret,
- PyUnicode_FromString(cur->name),
- BPy_IDGroup_WrapData(self->group->id, cur, self->group->prop));
- return ret;
- }
-
- return PyUnicode_FromString(cur->name);
- }
-
- PyErr_SetNone(PyExc_StopIteration);
- return NULL;
+ return (PyObject *)IDGroup_View_New_WithType(group, &BPy_IDGroup_ViewValues_Type);
}
-PyTypeObject BPy_IDGroup_Iter_Type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- /* For printing, in format "<module>.<name>" */
- "IDPropertyGroupIter", /* char *tp_name; */
- sizeof(BPy_IDGroup_Iter), /* int tp_basicsize; */
- 0, /* tp_itemsize; For allocation */
-
- /* Methods to implement standard operations */
-
- (destructor)BPy_IDGroup_Iter_dealloc, /* tp_dealloc */
- 0, /* tp_vectorcall_offset */
- NULL, /* getattrfunc tp_getattr; */
- NULL, /* setattrfunc tp_setattr; */
- NULL, /* cmpfunc tp_compare; */
- (reprfunc)IDGroup_Iter_repr, /* reprfunc tp_repr; */
-
- /* Method suites for standard classes */
-
- NULL, /* PyNumberMethods *tp_as_number; */
- NULL, /* PySequenceMethods *tp_as_sequence; */
- NULL, /* PyMappingMethods *tp_as_mapping; */
-
- /* More standard operations (here for binary compatibility) */
-
- NULL, /* hashfunc tp_hash; */
- NULL, /* ternaryfunc tp_call; */
- NULL, /* reprfunc tp_str; */
- NULL, /* getattrofunc tp_getattro; */
- NULL, /* setattrofunc tp_setattro; */
-
- /* Functions to access object as input/output buffer */
- NULL, /* PyBufferProcs *tp_as_buffer; */
-
- /*** Flags to define presence of optional/expanded features ***/
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* long tp_flags; */
-
- NULL, /* char *tp_doc; Documentation string */
- /*** Assigned meaning in release 2.0 ***/
- /* call function for all accessible objects */
- (traverseproc)BPy_IDGroup_Iter_traverse, /* traverseproc tp_traverse; */
-
- /* delete references to contained objects */
- (inquiry)BPy_IDGroup_Iter_clear, /* inquiry tp_clear; */
-
- /*** Assigned meaning in release 2.1 ***/
- /*** rich comparisons ***/
- NULL, /* richcmpfunc tp_richcompare; */
-
- /*** weak reference enabler ***/
- 0, /* long tp_weaklistoffset; */
-
- /*** Added in release 2.2 ***/
- /* Iterators */
- PyObject_SelfIter, /* getiterfunc tp_iter; */
- (iternextfunc)BPy_Group_Iter_Next, /* iternextfunc tp_iternext; */
-};
-
-void IDProp_Init_Types(void)
+static PyObject *BPy_IDGroup_ViewItems_CreatePyObject(BPy_IDProperty *group)
{
- PyType_Ready(&BPy_IDGroup_Type);
- PyType_Ready(&BPy_IDGroup_Iter_Type);
- PyType_Ready(&BPy_IDArray_Type);
+ return (PyObject *)IDGroup_View_New_WithType(group, &BPy_IDGroup_ViewItems_Type);
}
/** \} */
@@ -1822,7 +2138,15 @@ static PyObject *BPyInit_idprop_types(void)
/* bmesh_py_types.c */
PyModule_AddType(submodule, &BPy_IDGroup_Type);
- PyModule_AddType(submodule, &BPy_IDGroup_Iter_Type);
+
+ PyModule_AddType(submodule, &BPy_IDGroup_ViewKeys_Type);
+ PyModule_AddType(submodule, &BPy_IDGroup_ViewValues_Type);
+ PyModule_AddType(submodule, &BPy_IDGroup_ViewItems_Type);
+
+ PyModule_AddType(submodule, &BPy_IDGroup_IterKeys_Type);
+ PyModule_AddType(submodule, &BPy_IDGroup_IterValues_Type);
+ PyModule_AddType(submodule, &BPy_IDGroup_IterItems_Type);
+
PyModule_AddType(submodule, &BPy_IDArray_Type);
return submodule;
diff --git a/source/blender/python/generic/idprop_py_api.h b/source/blender/python/generic/idprop_py_api.h
index 4cccea3a936..1e8e26a3b6d 100644
--- a/source/blender/python/generic/idprop_py_api.h
+++ b/source/blender/python/generic/idprop_py_api.h
@@ -25,16 +25,35 @@ struct ID;
struct IDProperty;
extern PyTypeObject BPy_IDArray_Type;
-extern PyTypeObject BPy_IDGroup_Iter_Type;
extern PyTypeObject BPy_IDGroup_Type;
+extern PyTypeObject BPy_IDGroup_ViewKeys_Type;
+extern PyTypeObject BPy_IDGroup_ViewValues_Type;
+extern PyTypeObject BPy_IDGroup_ViewItems_Type;
+
+extern PyTypeObject BPy_IDGroup_IterKeys_Type;
+extern PyTypeObject BPy_IDGroup_IterValues_Type;
+extern PyTypeObject BPy_IDGroup_IterItems_Type;
+
#define BPy_IDArray_Check(v) (PyObject_TypeCheck(v, &BPy_IDArray_Type))
#define BPy_IDArray_CheckExact(v) (Py_TYPE(v) == &BPy_IDArray_Type)
-#define BPy_IDGroup_Iter_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_Iter_Type))
-#define BPy_IDGroup_Iter_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_Iter_Type)
#define BPy_IDGroup_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_Type))
#define BPy_IDGroup_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_Type)
+#define BPy_IDGroup_ViewKeys_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_ViewKeys_Type))
+#define BPy_IDGroup_ViewKeys_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_ViewKeys_Type)
+#define BPy_IDGroup_ViewValues_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_ViewValues_Type))
+#define BPy_IDGroup_ViewValues_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_ViewValues_Type)
+#define BPy_IDGroup_ViewItems_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_ViewItems_Type))
+#define BPy_IDGroup_ViewItems_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_ViewItems_Type)
+
+#define BPy_IDGroup_IterKeys_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_IterKeys_Type))
+#define BPy_IDGroup_IterKeys_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_IterKeys_Type)
+#define BPy_IDGroup_IterValues_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_IterValues_Type))
+#define BPy_IDGroup_IterValues_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_IterValues_Type)
+#define BPy_IDGroup_IterItems_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_IterItems_Type))
+#define BPy_IDGroup_IterItems_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_IterItems_Type)
+
typedef struct BPy_IDProperty {
PyObject_VAR_HEAD
struct ID *id; /* can be NULL */
@@ -52,12 +71,28 @@ typedef struct BPy_IDGroup_Iter {
PyObject_VAR_HEAD
BPy_IDProperty *group;
struct IDProperty *cur;
- int mode;
+ /** Use for detecting manipulation during iteration (which is not allowed). */
+ int len_init;
+ /** Iterate in the reverse direction. */
+ bool reversed;
} BPy_IDGroup_Iter;
+/** Use to implement `IDPropertyGroup.keys/values/items` */
+typedef struct BPy_IDGroup_View {
+ PyObject_VAR_HEAD
+ /** This will be NULL when accessing keys on data that has no ID properties. */
+ BPy_IDProperty *group;
+ bool reversed;
+} BPy_IDGroup_View;
+
PyObject *BPy_Wrap_GetKeys(struct IDProperty *prop);
PyObject *BPy_Wrap_GetValues(struct ID *id, struct IDProperty *prop);
PyObject *BPy_Wrap_GetItems(struct ID *id, struct IDProperty *prop);
+
+PyObject *BPy_Wrap_GetKeys_View_WithID(struct ID *id, struct IDProperty *prop);
+PyObject *BPy_Wrap_GetValues_View_WithID(struct ID *id, struct IDProperty *prop);
+PyObject *BPy_Wrap_GetItems_View_WithID(struct ID *id, struct IDProperty *prop);
+
int BPy_Wrap_SetMapItem(struct IDProperty *prop, PyObject *key, PyObject *val);
PyObject *BPy_IDGroup_MapDataToPy(struct IDProperty *prop);
@@ -67,6 +102,3 @@ bool BPy_IDProperty_Map_ValidateAndCreate(PyObject *key, struct IDProperty *grou
void IDProp_Init_Types(void);
PyObject *BPyInit_idprop(void);
-
-#define IDPROP_ITER_KEYS 0
-#define IDPROP_ITER_ITEMS 1
diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c
index 1711637458a..fb1cb823964 100644
--- a/source/blender/python/intern/bpy_rna.c
+++ b/source/blender/python/intern/bpy_rna.c
@@ -3562,7 +3562,7 @@ PyDoc_STRVAR(pyrna_struct_keys_doc,
" dictionary function of the same name).\n"
"\n"
" :return: custom property keys.\n"
- " :rtype: list of strings\n"
+ " :rtype: :class:`idprop.type.IDPropertyGroupViewKeys`\n"
"\n" BPY_DOC_ID_PROP_TYPE_NOTE);
static PyObject *pyrna_struct_keys(BPy_PropertyRNA *self)
{
@@ -3573,13 +3573,9 @@ static PyObject *pyrna_struct_keys(BPy_PropertyRNA *self)
return NULL;
}
+ /* `group` may be NULL. */
group = RNA_struct_idprops(&self->ptr, 0);
-
- if (group == NULL) {
- return PyList_New(0);
- }
-
- return BPy_Wrap_GetKeys(group);
+ return BPy_Wrap_GetKeys_View_WithID(self->ptr.owner_id, group);
}
PyDoc_STRVAR(pyrna_struct_items_doc,
@@ -3589,7 +3585,7 @@ PyDoc_STRVAR(pyrna_struct_items_doc,
" dictionary function of the same name).\n"
"\n"
" :return: custom property key, value pairs.\n"
- " :rtype: list of key, value tuples\n"
+ " :rtype: :class:`idprop.type.IDPropertyGroupViewItems`\n"
"\n" BPY_DOC_ID_PROP_TYPE_NOTE);
static PyObject *pyrna_struct_items(BPy_PropertyRNA *self)
{
@@ -3600,13 +3596,9 @@ static PyObject *pyrna_struct_items(BPy_PropertyRNA *self)
return NULL;
}
+ /* `group` may be NULL. */
group = RNA_struct_idprops(&self->ptr, 0);
-
- if (group == NULL) {
- return PyList_New(0);
- }
-
- return BPy_Wrap_GetItems(self->ptr.owner_id, group);
+ return BPy_Wrap_GetItems_View_WithID(self->ptr.owner_id, group);
}
PyDoc_STRVAR(pyrna_struct_values_doc,
@@ -3616,7 +3608,7 @@ PyDoc_STRVAR(pyrna_struct_values_doc,
" dictionary function of the same name).\n"
"\n"
" :return: custom property values.\n"
- " :rtype: list\n"
+ " :rtype: :class:`idprop.type.IDPropertyGroupViewValues`\n"
"\n" BPY_DOC_ID_PROP_TYPE_NOTE);
static PyObject *pyrna_struct_values(BPy_PropertyRNA *self)
{
@@ -3628,13 +3620,9 @@ static PyObject *pyrna_struct_values(BPy_PropertyRNA *self)
return NULL;
}
+ /* `group` may be NULL. */
group = RNA_struct_idprops(&self->ptr, 0);
-
- if (group == NULL) {
- return PyList_New(0);
- }
-
- return BPy_Wrap_GetValues(self->ptr.owner_id, group);
+ return BPy_Wrap_GetValues_View_WithID(self->ptr.owner_id, group);
}
PyDoc_STRVAR(pyrna_struct_is_property_set_doc,
diff --git a/tests/python/bl_pyapi_idprop.py b/tests/python/bl_pyapi_idprop.py
index 7b480f5fa16..1e570bf9a7f 100644
--- a/tests/python/bl_pyapi_idprop.py
+++ b/tests/python/bl_pyapi_idprop.py
@@ -2,6 +2,7 @@
# ./blender.bin --background -noaudio --python tests/python/bl_pyapi_idprop.py -- --verbose
import bpy
+import idprop
import unittest
import numpy as np
from array import array
@@ -139,6 +140,51 @@ class TestIdPropertyCreation(TestHelper, unittest.TestCase):
with self.assertRaises(TypeError):
self.id["a"] = self
+class TestIdPropertyGroupView(TestHelper, unittest.TestCase):
+
+ def test_type(self):
+ self.assertEqual(type(self.id.keys()), idprop.types.IDPropertyGroupViewKeys)
+ self.assertEqual(type(self.id.values()), idprop.types.IDPropertyGroupViewValues)
+ self.assertEqual(type(self.id.items()), idprop.types.IDPropertyGroupViewItems)
+
+ self.assertEqual(type(iter(self.id.keys())), idprop.types.IDPropertyGroupIterKeys)
+ self.assertEqual(type(iter(self.id.values())), idprop.types.IDPropertyGroupIterValues)
+ self.assertEqual(type(iter(self.id.items())), idprop.types.IDPropertyGroupIterItems)
+
+ def test_basic(self):
+ text = ["A", "B", "C"]
+ for i, ch in enumerate(text):
+ self.id[ch] = i
+ self.assertEqual(len(self.id.keys()), len(text))
+ self.assertEqual(list(self.id.keys()), text)
+ self.assertEqual(list(reversed(self.id.keys())), list(reversed(text)))
+
+ self.assertEqual(len(self.id.values()), len(text))
+ self.assertEqual(list(self.id.values()), list(range(len(text))))
+ self.assertEqual(list(reversed(self.id.values())), list(reversed(range(len(text)))))
+
+ self.assertEqual(len(self.id.items()), len(text))
+ self.assertEqual(list(self.id.items()), [(k, v) for v, k in enumerate(text)])
+ self.assertEqual(list(reversed(self.id.items())), list(reversed([(k, v) for v, k in enumerate(text)])))
+
+ def test_contains(self):
+ # Check `idprop.types.IDPropertyGroupView{Keys/Values/Items}.__contains__`
+ text = ["A", "B", "C"]
+ for i, ch in enumerate(text):
+ self.id[ch] = i
+
+ self.assertIn("A", self.id)
+ self.assertNotIn("D", self.id)
+
+ self.assertIn("A", self.id.keys())
+ self.assertNotIn("D", self.id.keys())
+
+ self.assertIn(2, self.id.values())
+ self.assertNotIn(3, self.id.values())
+
+ self.assertIn(("A", 0), self.id.items())
+ self.assertNotIn(("D", 3), self.id.items())
+
class TestBufferProtocol(TestHelper, unittest.TestCase):