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.
This commit is contained in:
Campbell Barton 2021-05-11 09:40:41 +10:00
parent 65f9550813
commit 265d97556a
Notes: blender-bot 2023-02-14 09:29:42 +01:00
Referenced by commit 27fb63381e, Fix T94121: PyAPI: ID property group returns wrong type with iter()
Referenced by issue #94121, PyAPI: ID property group returns wrong type with iter()
Referenced by issue #85675, Blender 3.0 (Python API deprecation, updates)
5 changed files with 556 additions and 166 deletions

View File

@ -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.

View File

@ -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 */
@ -874,6 +880,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)
{
return PyUnicode_FromFormat("(ID Property Group Iter \"%s\")", self->group->prop->name);
}
static void BPy_IDGroup_Iter_dealloc(BPy_IDGroup_Iter *self)
{
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 PyObject *BPy_Group_Iter_Next(BPy_IDGroup_Iter *self)
{
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;
}
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)
{
IDGroup_Iter_init_type();
IDGroup_View_init_type();
PyType_Ready(&BPy_IDGroup_Type);
PyType_Ready(&BPy_IDGroup_Iter_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);
}
/**
* \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)
{
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 PyObject *BPy_IDGroup_ViewKeys_CreatePyObject(BPy_IDProperty *group)
{
return (PyObject *)IDGroup_View_New_WithType(group, &BPy_IDGroup_ViewKeys_Type);
}
static PyObject *BPy_IDGroup_ViewValues_CreatePyObject(BPy_IDProperty *group)
{
return (PyObject *)IDGroup_View_New_WithType(group, &BPy_IDGroup_ViewValues_Type);
}
static PyObject *BPy_IDGroup_ViewItems_CreatePyObject(BPy_IDProperty *group)
{
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;

View File

@ -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

View File

@ -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,

View File

@ -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):