diff options
author | Campbell Barton <ideasman42@gmail.com> | 2021-05-11 02:40:41 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2021-05-14 17:36:49 +0300 |
commit | 265d97556aa0f0f2a0e4dd7584e3b8573bbddd54 (patch) | |
tree | 76ffd53c8853d68a5adb87bb8c0b4f80d0ee9a90 | |
parent | 65f955081370e77a61d822da1fa78960c8a0149e (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.py | 2 | ||||
-rw-r--r-- | source/blender/python/generic/idprop_py_api.c | 578 | ||||
-rw-r--r-- | source/blender/python/generic/idprop_py_api.h | 46 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_rna.c | 30 | ||||
-rw-r--r-- | tests/python/bl_pyapi_idprop.py | 46 |
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): |