From 37e6a1995ac7eeabd5b6a56621ad5a850dae4149 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 20 Feb 2021 16:16:43 +1100 Subject: PyAPI: use a new type for storing the deferred result of bpy.props This is needed to support Python 3.10's Postponed annotation evaluation. It also simplifies type checking. --- source/blender/python/intern/bpy_props.c | 92 +++++++++++++++++------ source/blender/python/intern/bpy_props.h | 10 +++ source/blender/python/intern/bpy_rna.c | 125 ++++++++++++++----------------- 3 files changed, 135 insertions(+), 92 deletions(-) (limited to 'source/blender') diff --git a/source/blender/python/intern/bpy_props.c b/source/blender/python/intern/bpy_props.c index 878bf4aec5d..c596f81a91c 100644 --- a/source/blender/python/intern/bpy_props.c +++ b/source/blender/python/intern/bpy_props.c @@ -193,6 +193,71 @@ static const EnumPropertyItem property_subtype_array_items[] = { "'XYZ', 'XYZ_LENGTH', 'COLOR_GAMMA', 'COORDINATES', 'LAYER', 'LAYER_MEMBER', 'NONE'].\n" \ " :type subtype: string\n" +/* -------------------------------------------------------------------- */ +/** \name Deferred Property Type + * + * Operators and classes use this so it can store the arguments given but defer + * running it until the operator runs where these values are used to setup + * the default arguments for that operator instance. + * \{ */ + +static void bpy_prop_deferred_dealloc(BPy_PropDeferred *self) +{ + if (self->kw) { + PyObject_GC_UnTrack(self); + Py_CLEAR(self->kw); + } + PyObject_GC_Del(self); +} + +static int bpy_prop_deferred_traverse(BPy_PropDeferred *self, visitproc visit, void *arg) +{ + Py_VISIT(self->kw); + return 0; +} + +static int bpy_prop_deferred_clear(BPy_PropDeferred *self) +{ + Py_CLEAR(self->kw); + return 0; +} + +static PyObject *bpy_prop_deferred_repr(BPy_PropDeferred *self) +{ + return PyUnicode_FromFormat("<%.200s, %R, %R>", Py_TYPE(self)->tp_name, self->fn, self->kw); +} + +PyTypeObject bpy_prop_deferred_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + + .tp_name = "bpy_prop_deferred", + .tp_basicsize = sizeof(BPy_PropDeferred), + .tp_dealloc = (destructor)bpy_prop_deferred_dealloc, + .tp_repr = (reprfunc)bpy_prop_deferred_repr, + + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + + .tp_traverse = (traverseproc)bpy_prop_deferred_traverse, + .tp_clear = (inquiry)bpy_prop_deferred_clear, +}; + +static PyObject *bpy_prop_deferred_data_CreatePyObject(PyObject *fn, PyObject *kw) +{ + BPy_PropDeferred *self = PyObject_GC_New(BPy_PropDeferred, &bpy_prop_deferred_Type); + self->fn = fn; + if (kw == NULL) { + kw = PyDict_New(); + } + else { + Py_INCREF(kw); + } + self->kw = kw; + PyObject_GC_Track(self); + return (PyObject *)self; +} + +/** \} */ + /* PyObject's */ static PyObject *pymeth_BoolProperty = NULL; static PyObject *pymeth_BoolVectorProperty = NULL; @@ -248,27 +313,6 @@ static void bpy_prop_assign_flag_override(PropertyRNA *prop, const int flag_over RNA_def_property_override_flag(prop, flag_override); } -/* operators and classes use this so it can store the args given but defer - * running it until the operator runs where these values are used to setup - * the default args for that operator instance */ -static PyObject *bpy_prop_deferred_return(PyObject *func, PyObject *kw) -{ - PyObject *ret = PyTuple_New(2); - PyTuple_SET_ITEM(ret, 0, func); - Py_INCREF(func); - - if (kw == NULL) { - kw = PyDict_New(); - } - else { - Py_INCREF(kw); - } - - PyTuple_SET_ITEM(ret, 1, kw); - - return ret; -} - /* callbacks */ static void bpy_prop_update_cb(struct bContext *C, struct PointerRNA *ptr, @@ -1997,7 +2041,7 @@ static void bpy_prop_callback_assign_enum(struct PropertyRNA *prop, if (PyErr_Occurred()) { \ return NULL; \ } \ - return bpy_prop_deferred_return(pymeth_##_func, kw); \ + return bpy_prop_deferred_data_CreatePyObject(pymeth_##_func, kw); \ } \ (void)0 @@ -3668,5 +3712,9 @@ PyObject *BPY_rna_props(void) ASSIGN_STATIC(CollectionProperty); ASSIGN_STATIC(RemoveProperty); + if (PyType_Ready(&bpy_prop_deferred_Type) < 0) { + return NULL; + } + return submodule; } diff --git a/source/blender/python/intern/bpy_props.h b/source/blender/python/intern/bpy_props.h index 3d7860dcdd8..6cebf82d373 100644 --- a/source/blender/python/intern/bpy_props.h +++ b/source/blender/python/intern/bpy_props.h @@ -30,6 +30,16 @@ PyObject *BPy_PointerProperty(PyObject *self, PyObject *args, PyObject *kw); PyObject *BPy_CollectionProperty(PyObject *self, PyObject *args, PyObject *kw); StructRNA *pointer_type_from_py(PyObject *value, const char *error_prefix); +typedef struct { + PyObject_HEAD + /* This isn't GC tracked, it's a function from `bpy.props` so it's not going away. */ + void *fn; + PyObject *kw; +} BPy_PropDeferred; + +extern PyTypeObject bpy_prop_deferred_Type; +#define BPy_PropDeferred_CheckTypeExact(v) (Py_TYPE(v) == &bpy_prop_deferred_Type) + #define PYRNA_STACK_ARRAY RNA_STACK_ARRAY #ifdef __cplusplus diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c index 311a7afef01..0c091d7cd7c 100644 --- a/source/blender/python/intern/bpy_rna.c +++ b/source/blender/python/intern/bpy_rna.c @@ -4356,12 +4356,6 @@ static int pyrna_struct_pydict_contains(PyObject *self, PyObject *pyname) #endif /* --------------- setattr------------------------------------------- */ -static bool pyrna_is_deferred_prop(const PyObject *value) -{ - return PyTuple_CheckExact(value) && PyTuple_GET_SIZE(value) == 2 && - PyCFunction_Check(PyTuple_GET_ITEM(value, 0)) && - PyDict_CheckExact(PyTuple_GET_ITEM(value, 1)); -} #if 0 static PyObject *pyrna_struct_meta_idprop_getattro(PyObject *cls, PyObject *attr) @@ -4373,12 +4367,12 @@ static PyObject *pyrna_struct_meta_idprop_getattro(PyObject *cls, PyObject *attr * >>> bpy.types.Scene.foo * * ...rather than returning the deferred class register tuple - * as checked by pyrna_is_deferred_prop() + * as checked by BPy_PropDeferred_CheckTypeExact() * * Disable for now, * this is faking internal behavior in a way that's too tricky to maintain well. */ # if 0 - if ((ret == NULL) /* || pyrna_is_deferred_prop(ret) */ ) { + if ((ret == NULL) /* || BPy_PropDeferred_CheckTypeExact(ret) */ ) { StructRNA *srna = srna_from_self(cls, "StructRNA.__getattr__"); if (srna) { PropertyRNA *prop = RNA_struct_type_find_property(srna, PyUnicode_AsUTF8(attr)); @@ -4399,7 +4393,7 @@ static PyObject *pyrna_struct_meta_idprop_getattro(PyObject *cls, PyObject *attr static int pyrna_struct_meta_idprop_setattro(PyObject *cls, PyObject *attr, PyObject *value) { StructRNA *srna = srna_from_self(cls, "StructRNA.__setattr__"); - const bool is_deferred_prop = (value && pyrna_is_deferred_prop(value)); + const bool is_deferred_prop = (value && BPy_PropDeferred_CheckTypeExact(value)); const char *attr_str = PyUnicode_AsUTF8(attr); if (srna && !pyrna_write_check() && @@ -7873,78 +7867,69 @@ StructRNA *srna_from_self(PyObject *self, const char *error_prefix) static int deferred_register_prop(StructRNA *srna, PyObject *key, PyObject *item) { + if (!BPy_PropDeferred_CheckTypeExact(item)) { + /* No error, ignoring. */ + return 0; + } + /* We only care about results from C which * are for sure types, save some time with error */ - if (pyrna_is_deferred_prop(item)) { + PyObject *py_func = ((BPy_PropDeferred *)item)->fn; + PyObject *py_kw = ((BPy_PropDeferred *)item)->kw; + PyObject *py_srna_cobject, *py_ret; - PyObject *py_func, *py_kw, *py_srna_cobject, *py_ret; + PyObject *args_fake; - if (PyArg_ParseTuple(item, "OO!", &py_func, &PyDict_Type, &py_kw)) { - PyObject *args_fake; - - if (*PyUnicode_AsUTF8(key) == '_') { - PyErr_Format(PyExc_ValueError, - "bpy_struct \"%.200s\" registration error: " - "%.200s could not register because the property starts with an '_'\n", - RNA_struct_identifier(srna), - PyUnicode_AsUTF8(key)); - return -1; - } - py_srna_cobject = PyCapsule_New(srna, NULL, NULL); + if (*PyUnicode_AsUTF8(key) == '_') { + PyErr_Format(PyExc_ValueError, + "bpy_struct \"%.200s\" registration error: " + "%.200s could not register because the property starts with an '_'\n", + RNA_struct_identifier(srna), + PyUnicode_AsUTF8(key)); + return -1; + } + py_srna_cobject = PyCapsule_New(srna, NULL, NULL); - /* Not 100% nice :/, modifies the dict passed, should be ok. */ - PyDict_SetItem(py_kw, bpy_intern_str_attr, key); + /* Not 100% nice :/, modifies the dict passed, should be ok. */ + PyDict_SetItem(py_kw, bpy_intern_str_attr, key); - args_fake = PyTuple_New(1); - PyTuple_SET_ITEM(args_fake, 0, py_srna_cobject); + args_fake = PyTuple_New(1); + PyTuple_SET_ITEM(args_fake, 0, py_srna_cobject); - PyObject *type = PyDict_GetItemString(py_kw, "type"); - StructRNA *type_srna = srna_from_self(type, ""); - if (type_srna) { - if (!RNA_struct_idprops_datablock_allowed(srna) && - (*(PyCFunctionWithKeywords)PyCFunction_GET_FUNCTION(py_func) == BPy_PointerProperty || - *(PyCFunctionWithKeywords)PyCFunction_GET_FUNCTION(py_func) == - BPy_CollectionProperty) && - RNA_struct_idprops_contains_datablock(type_srna)) { - PyErr_Format(PyExc_ValueError, - "bpy_struct \"%.200s\" doesn't support datablock properties\n", - RNA_struct_identifier(srna)); - return -1; - } - } + PyObject *type = PyDict_GetItemString(py_kw, "type"); + StructRNA *type_srna = srna_from_self(type, ""); + if (type_srna) { + if (!RNA_struct_idprops_datablock_allowed(srna) && + (*(PyCFunctionWithKeywords)PyCFunction_GET_FUNCTION(py_func) == BPy_PointerProperty || + *(PyCFunctionWithKeywords)PyCFunction_GET_FUNCTION(py_func) == BPy_CollectionProperty) && + RNA_struct_idprops_contains_datablock(type_srna)) { + PyErr_Format(PyExc_ValueError, + "bpy_struct \"%.200s\" doesn't support datablock properties\n", + RNA_struct_identifier(srna)); + return -1; + } + } - py_ret = PyObject_Call(py_func, args_fake, py_kw); + py_ret = PyObject_Call(py_func, args_fake, py_kw); - if (py_ret) { - Py_DECREF(py_ret); - Py_DECREF(args_fake); /* Free's py_srna_cobject too. */ - } - else { - /* _must_ print before decreffing args_fake. */ - PyErr_Print(); - PyErr_Clear(); + if (py_ret) { + Py_DECREF(py_ret); + Py_DECREF(args_fake); /* Free's py_srna_cobject too. */ + } + else { + /* _must_ print before decreffing args_fake. */ + PyErr_Print(); + PyErr_Clear(); - Py_DECREF(args_fake); /* Free's py_srna_cobject too. */ + Py_DECREF(args_fake); /* Free's py_srna_cobject too. */ - // PyC_LineSpit(); - PyErr_Format(PyExc_ValueError, - "bpy_struct \"%.200s\" registration error: " - "%.200s could not register\n", - RNA_struct_identifier(srna), - PyUnicode_AsUTF8(key)); - return -1; - } - } - else { - /* Since this is a class dict, ignore args that can't be passed. */ - - /* For testing only. */ -#if 0 - PyC_ObSpit("Why doesn't this work??", item); - PyErr_Print(); -#endif - PyErr_Clear(); - } + // PyC_LineSpit(); + PyErr_Format(PyExc_ValueError, + "bpy_struct \"%.200s\" registration error: " + "%.200s could not register\n", + RNA_struct_identifier(srna), + PyUnicode_AsUTF8(key)); + return -1; } return 0; -- cgit v1.2.3