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 | |
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
19 files changed, 123 insertions, 12 deletions
diff --git a/intern/cycles/blender/addon/__init__.py b/intern/cycles/blender/addon/__init__.py index b1deb5cb64e..10b95133912 100644 --- a/intern/cycles/blender/addon/__init__.py +++ b/intern/cycles/blender/addon/__init__.py @@ -15,6 +15,7 @@ # # <pep8 compliant> +from __future__ import annotations bl_info = { "name": "Cycles Render Engine", diff --git a/intern/cycles/blender/addon/engine.py b/intern/cycles/blender/addon/engine.py index 7595261f523..dfa696714fb 100644 --- a/intern/cycles/blender/addon/engine.py +++ b/intern/cycles/blender/addon/engine.py @@ -15,6 +15,7 @@ # # <pep8 compliant> +from __future__ import annotations def _is_using_buggy_driver(): diff --git a/intern/cycles/blender/addon/operators.py b/intern/cycles/blender/addon/operators.py index 087e5b666a5..dca727e316e 100644 --- a/intern/cycles/blender/addon/operators.py +++ b/intern/cycles/blender/addon/operators.py @@ -15,6 +15,7 @@ # # <pep8 compliant> +from __future__ import annotations import bpy from bpy.types import Operator diff --git a/intern/cycles/blender/addon/osl.py b/intern/cycles/blender/addon/osl.py index 4c6e7952491..d4c3c447951 100644 --- a/intern/cycles/blender/addon/osl.py +++ b/intern/cycles/blender/addon/osl.py @@ -15,6 +15,7 @@ # # <pep8 compliant> +from __future__ import annotations import bpy import _cycles diff --git a/intern/cycles/blender/addon/presets.py b/intern/cycles/blender/addon/presets.py index 78a8605e93f..04b18b38927 100644 --- a/intern/cycles/blender/addon/presets.py +++ b/intern/cycles/blender/addon/presets.py @@ -15,6 +15,7 @@ # # <pep8 compliant> +from __future__ import annotations from bl_operators.presets import AddPresetBase from bpy.types import Operator diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index dd218d93d32..0708c371a0e 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -15,6 +15,7 @@ # # <pep8 compliant> +from __future__ import annotations import bpy from bpy.props import ( @@ -841,7 +842,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup): ('MEGA', "Mega", ""), ('SPLIT', "Split", ""), ), - update=_devices_update_callback + update=CyclesRenderSettings._devices_update_callback ) debug_opencl_device_type: EnumProperty( @@ -855,11 +856,9 @@ class CyclesRenderSettings(bpy.types.PropertyGroup): ('GPU', "GPU", ""), ('ACCELERATOR', "Accelerator", ""), ), - update=_devices_update_callback + update=CyclesRenderSettings._devices_update_callback ) - del _devices_update_callback - debug_use_opencl_debug: BoolProperty(name="Debug OpenCL", default=False) debug_opencl_mem_limit: IntProperty( @@ -1481,7 +1480,7 @@ class CyclesPreferences(bpy.types.AddonPreferences): compute_device_type: EnumProperty( name="Compute Device Type", description="Device to use for computation (rendering with Cycles)", - items=get_device_types, + items=CyclesPreferences.get_device_types, ) devices: bpy.props.CollectionProperty(type=CyclesDeviceSettings) diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 4576cf4e413..68f6291b373 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -15,6 +15,7 @@ # # <pep8 compliant> +from __future__ import annotations import bpy from bpy_extras.node_utils import find_node_input diff --git a/intern/cycles/blender/addon/version_update.py b/intern/cycles/blender/addon/version_update.py index cdff3c2b915..aeecc265399 100644 --- a/intern/cycles/blender/addon/version_update.py +++ b/intern/cycles/blender/addon/version_update.py @@ -15,6 +15,7 @@ # # <pep8 compliant> +from __future__ import annotations import bpy import math diff --git a/release/scripts/modules/bpy_extras/object_utils.py b/release/scripts/modules/bpy_extras/object_utils.py index 977cbc20f0d..8afb09882fd 100644 --- a/release/scripts/modules/bpy_extras/object_utils.py +++ b/release/scripts/modules/bpy_extras/object_utils.py @@ -17,6 +17,7 @@ # ##### END GPL LICENSE BLOCK ##### # <pep8-80 compliant> +from __future__ import annotations __all__ = ( "add_object_align_init", @@ -180,7 +181,7 @@ class AddObjectHelper: ('CURSOR', "3D Cursor", "Use the 3D cursor orientation for the new object"), ), default='WORLD', - update=align_update_callback, + update=AddObjectHelper.align_update_callback, ) location: FloatVectorProperty( name="Location", diff --git a/release/scripts/startup/bl_operators/__init__.py b/release/scripts/startup/bl_operators/__init__.py index e91d3b3ce60..7e6f14a0a51 100644 --- a/release/scripts/startup/bl_operators/__init__.py +++ b/release/scripts/startup/bl_operators/__init__.py @@ -17,6 +17,7 @@ # ##### END GPL LICENSE BLOCK ##### # <pep8 compliant> +from __future__ import annotations # support reloading sub-modules if "bpy" in locals(): diff --git a/release/scripts/startup/bl_operators/add_mesh_torus.py b/release/scripts/startup/bl_operators/add_mesh_torus.py index c2f9a4189cf..0ebb6ae27e1 100644 --- a/release/scripts/startup/bl_operators/add_mesh_torus.py +++ b/release/scripts/startup/bl_operators/add_mesh_torus.py @@ -17,6 +17,8 @@ # ##### END GPL LICENSE BLOCK ##### # <pep8-80 compliant> +from __future__ import annotations + import bpy from bpy.types import Operator @@ -156,7 +158,7 @@ class AddTorus(Operator, object_utils.AddObjectHelper): ('EXT_INT', "Exterior/Interior", "Use the exterior/interior radii for torus dimensions"), ), - update=mode_update_callback, + update=AddTorus.mode_update_callback, ) major_radius: FloatProperty( name="Major Radius", diff --git a/release/scripts/startup/bl_operators/anim.py b/release/scripts/startup/bl_operators/anim.py index 279b66a0833..85290bfe3f0 100644 --- a/release/scripts/startup/bl_operators/anim.py +++ b/release/scripts/startup/bl_operators/anim.py @@ -17,6 +17,7 @@ # ##### END GPL LICENSE BLOCK ##### # <pep8-80 compliant> +from __future__ import annotations if "bpy" in locals(): from importlib import reload diff --git a/release/scripts/startup/bl_operators/assets.py b/release/scripts/startup/bl_operators/assets.py index 317555280e5..e7fbb155b2f 100644 --- a/release/scripts/startup/bl_operators/assets.py +++ b/release/scripts/startup/bl_operators/assets.py @@ -16,6 +16,9 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> +from __future__ import annotations + import bpy from bpy_extras.asset_utils import ( diff --git a/release/scripts/startup/bl_operators/console.py b/release/scripts/startup/bl_operators/console.py index bffac4eef55..231dade820d 100644 --- a/release/scripts/startup/bl_operators/console.py +++ b/release/scripts/startup/bl_operators/console.py @@ -17,6 +17,7 @@ # ##### END GPL LICENSE BLOCK ##### # <pep8-80 compliant> +from __future__ import annotations import bpy from bpy.types import Operator diff --git a/release/scripts/startup/bl_operators/constraint.py b/release/scripts/startup/bl_operators/constraint.py index 213122952ae..49fc6a04112 100644 --- a/release/scripts/startup/bl_operators/constraint.py +++ b/release/scripts/startup/bl_operators/constraint.py @@ -17,6 +17,7 @@ # ##### END GPL LICENSE BLOCK ##### # <pep8-80 compliant> +from __future__ import annotations from bpy.types import ( Operator, diff --git a/release/scripts/startup/bl_operators/node.py b/release/scripts/startup/bl_operators/node.py index 1c5ebb4fa17..7884a7287b7 100644 --- a/release/scripts/startup/bl_operators/node.py +++ b/release/scripts/startup/bl_operators/node.py @@ -17,6 +17,7 @@ # ##### END GPL LICENSE BLOCK ##### # <pep8-80 compliant> +from __future__ import annotations import bpy import nodeitems_utils @@ -218,7 +219,7 @@ class NODE_OT_add_search(NodeAddOperator, Operator): node_item: EnumProperty( name="Node Type", description="Node type", - items=node_enum_items, + items=NODE_OT_add_search.node_enum_items, ) def execute(self, context): diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index c5457713d36..db15f4597bf 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -17,6 +17,7 @@ # ##### END GPL LICENSE BLOCK ##### # <pep8 compliant> +from __future__ import annotations import bpy from bpy.types import ( @@ -859,9 +860,7 @@ class WM_OT_url_open_preset(Operator): type: EnumProperty( name="Site", - items=lambda self, _context: ( - item for (item, _) in WM_OT_url_open_preset.preset_items - ), + items=WM_OT_url_open_preset._preset_items, ) id: StringProperty( @@ -916,6 +915,10 @@ class WM_OT_url_open_preset(Operator): "https://www.blender.org/about/credits/"), ] + @staticmethod + def _preset_items(_self, _context): + return (item for (item, _) in WM_OT_url_open_preset.preset_items) + def execute(self, context): url = None type = self.type 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); } |