diff options
author | Campbell Barton <ideasman42@gmail.com> | 2021-02-21 13:21:18 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2021-02-21 14:37:53 +0300 |
commit | 08dbc4f996e4e95f3ab64f7bb3e1193700c585f5 (patch) | |
tree | 8f9d121258c5f88cf825aa6e3030ccc7bbe983cd /source/blender/python | |
parent | de67e3c0c02e9d08022cff58338099d287505f91 (diff) |
PyAPI: use postponed annotations to support Python 3.10
Support Python 3.10a5 or 3.9x with support explicitly enabled.
- Enable Python's postponed annotations for Blender's RNA classes
types registered on startup.
- Using postponed annotations has implications for how they are defined,
since they must evaluate in the modules name-space instead of the
classes name-space. See changes to annotations in `release/scripts`.
- Use `from __future__ import annotations` at the top of the module
to ensure the script will run with Python 3.10.
- Old logic is kept since it could be used if PEP-649 is supported.
Resolves T83626
Ref D10474
Diffstat (limited to 'source/blender/python')
-rw-r--r-- | source/blender/python/intern/bpy_props.c | 16 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_rna.c | 77 |
2 files changed, 92 insertions, 1 deletions
diff --git a/source/blender/python/intern/bpy_props.c b/source/blender/python/intern/bpy_props.c index 2b3599df86e..3cc894826bd 100644 --- a/source/blender/python/intern/bpy_props.c +++ b/source/blender/python/intern/bpy_props.c @@ -227,6 +227,20 @@ static PyObject *bpy_prop_deferred_repr(BPy_PropDeferred *self) return PyUnicode_FromFormat("<%.200s, %R, %R>", Py_TYPE(self)->tp_name, self->fn, self->kw); } +/** + * HACK: needed by `typing.get_type_hints` + * with `from __future__ import annotations` enabled or when using Python 3.10 or newer. + * + * When callable this object type passes the test for being an acceptable annotation. + */ +static PyObject *bpy_prop_deferred_call(BPy_PropDeferred *UNUSED(self), + PyObject *UNUSED(args), + PyObject *UNUSED(kw)) +{ + /* Dummy value. */ + Py_RETURN_NONE; +} + /* Get/Set Items. */ /** @@ -257,7 +271,6 @@ static PyGetSetDef bpy_prop_deferred_getset[] = { {NULL, NULL, NULL, NULL, NULL} /* Sentinel */ }; - PyTypeObject bpy_prop_deferred_Type = { PyVarObject_HEAD_INIT(NULL, 0) @@ -265,6 +278,7 @@ PyTypeObject bpy_prop_deferred_Type = { .tp_basicsize = sizeof(BPy_PropDeferred), .tp_dealloc = (destructor)bpy_prop_deferred_dealloc, .tp_repr = (reprfunc)bpy_prop_deferred_repr, + .tp_call = (ternaryfunc)bpy_prop_deferred_call, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c index 0c091d7cd7c..ae0d5b46458 100644 --- a/source/blender/python/intern/bpy_rna.c +++ b/source/blender/python/intern/bpy_rna.c @@ -80,6 +80,15 @@ #define USE_MATHUTILS #define USE_STRING_COERCE +/** + * This _must_ be enabled to support Python 3.10's postponed annotations, + * `from __future__ import annotations`. + * + * This has the disadvantage of evaluating strings at run-time, in the future we might be able to + * reinstate the older, more efficient logic using descriptors, see: pep-0649 + */ +#define USE_POSTPONED_ANNOTATIONS + /* Unfortunately Python needs to hold a global reference to the context. * If we remove this is means `bpy.context` won't be usable from some parts of the code: * `bpy.app.handler` callbacks for example. @@ -7935,6 +7944,65 @@ static int deferred_register_prop(StructRNA *srna, PyObject *key, PyObject *item return 0; } +/** + * Extract `__annotations__` using `typing.get_type_hints` which handles the delayed evaluation. + */ +static int pyrna_deferred_register_class_from_type_hints(StructRNA *srna, PyTypeObject *py_class) +{ + PyObject *annotations_dict = NULL; + + /* `typing.get_type_hints(py_class)` */ + { + PyObject *typing_mod = PyImport_ImportModuleLevel("typing", NULL, NULL, NULL, 0); + if (typing_mod != NULL) { + PyObject *get_type_hints_fn = PyObject_GetAttrString(typing_mod, "get_type_hints"); + if (get_type_hints_fn != NULL) { + PyObject *args = PyTuple_New(1); + + PyTuple_SET_ITEM(args, 0, (PyObject *)py_class); + Py_INCREF(py_class); + + annotations_dict = PyObject_CallObject(get_type_hints_fn, args); + + Py_DECREF(args); + Py_DECREF(get_type_hints_fn); + } + Py_DECREF(typing_mod); + } + } + + int ret = 0; + if (annotations_dict != NULL) { + if (PyDict_CheckExact(annotations_dict)) { + PyObject *item, *key; + Py_ssize_t pos = 0; + + while (PyDict_Next(annotations_dict, &pos, &key, &item)) { + ret = deferred_register_prop(srna, key, item); + if (ret != 0) { + break; + } + } + } + else { + /* Should never happen, an error wont have been raised, so raise one. */ + PyErr_Format(PyExc_TypeError, + "typing.get_type_hints returned: %.200s, expected dict\n", + Py_TYPE(annotations_dict)->tp_name); + ret = -1; + } + + Py_DECREF(annotations_dict); + } + else { + BLI_assert(PyErr_Occurred()); + fprintf(stderr, "typing.get_type_hints failed with: %.200s\n", py_class->tp_name); + ret = -1; + } + + return ret; +} + static int pyrna_deferred_register_props(StructRNA *srna, PyObject *class_dict) { PyObject *annotations_dict; @@ -7999,6 +8067,15 @@ int pyrna_deferred_register_class(StructRNA *srna, PyTypeObject *py_class) return 0; } +#ifdef USE_POSTPONED_ANNOTATIONS + const bool use_postponed_annotations = true; +#else + const bool use_postponed_annotations = false; +#endif + + if (use_postponed_annotations) { + return pyrna_deferred_register_class_from_type_hints(srna, py_class); + } return pyrna_deferred_register_class_recursive(srna, py_class); } |