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
This commit is contained in:
parent
de67e3c0c0
commit
08dbc4f996
Notes:
blender-bot
2023-02-14 10:21:11 +01:00
Referenced by commit81178eca7c
, Revert removal of lambda usage for Python RNA callbacks Referenced by commit8d50a3e19e
, Fix T85930: Custom Property Error: Wrong Subtype Referenced by issue #86332, Python error when using lambda for poll function and annotations from future Referenced by issue #83626, Postponed annotation evaluation in Python 3.10 breaks scripts.
|
@ -15,6 +15,7 @@
|
|||
#
|
||||
|
||||
# <pep8 compliant>
|
||||
from __future__ import annotations
|
||||
|
||||
bl_info = {
|
||||
"name": "Cycles Render Engine",
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#
|
||||
|
||||
# <pep8 compliant>
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def _is_using_buggy_driver():
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#
|
||||
|
||||
# <pep8 compliant>
|
||||
from __future__ import annotations
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#
|
||||
|
||||
# <pep8 compliant>
|
||||
from __future__ import annotations
|
||||
|
||||
import bpy
|
||||
import _cycles
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#
|
||||
|
||||
# <pep8 compliant>
|
||||
from __future__ import annotations
|
||||
|
||||
from bl_operators.presets import AddPresetBase
|
||||
from bpy.types import Operator
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#
|
||||
|
||||
# <pep8 compliant>
|
||||
from __future__ import annotations
|
||||
|
||||
import bpy
|
||||
from bpy_extras.node_utils import find_node_input
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#
|
||||
|
||||
# <pep8 compliant>
|
||||
from __future__ import annotations
|
||||
|
||||
import bpy
|
||||
import math
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
from __future__ import annotations
|
||||
|
||||
# support reloading sub-modules
|
||||
if "bpy" in locals():
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8-80 compliant>
|
||||
from __future__ import annotations
|
||||
|
||||
if "bpy" in locals():
|
||||
from importlib import reload
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
from __future__ import annotations
|
||||
|
||||
import bpy
|
||||
|
||||
from bpy_extras.asset_utils import (
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8-80 compliant>
|
||||
from __future__ import annotations
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8-80 compliant>
|
||||
from __future__ import annotations
|
||||
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue