Add ContextTempOverride as context manager, support __enter__/__exit__ dunders

- Discover ContextTempOverride by probing Context.temp_override() return value
- Add __enter__ and __exit__ to _USEFUL_DUNDERS set
- Fix __enter__ return type to Self, __exit__ params to standard CM signature
- Add Self import to bpy.types generator when used in type annotations
- Remove ContextTempOverride from undefined type map (now a real class)

Remaining conformance issue: context.copy() returns dict[str, object] which
loses specific types when unpacked with **kwargs into temp_override params.
This is an inherent limitation of the copy() -> dict pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph HENRY 2026-03-31 12:47:44 +02:00
parent a30e772cf8
commit 80d5311e33
2 changed files with 40 additions and 3 deletions

View File

@ -553,6 +553,8 @@ def generate_types_stub(
typing_imports = ["Generic", "TypeVar"] typing_imports = ["Generic", "TypeVar"]
if "Literal[" in all_type_strs: if "Literal[" in all_type_strs:
typing_imports.append("Literal") typing_imports.append("Literal")
if re.search(r"\bSelf\b", all_type_strs):
typing_imports.append("Self")
abc_imports = ["Iterator"] abc_imports = ["Iterator"]
if "Callable" in all_type_strs: if "Callable" in all_type_strs:

View File

@ -542,9 +542,7 @@ _UNDEFINED_TYPE_NAMES = {
"_PropertyDeferred", "_PropertyDeferred",
} }
_UNDEFINED_TYPE_MAP = { _UNDEFINED_TYPE_MAP: dict[str, str] = {}
"ContextTempOverride": "object",
}
_UNDEFINED_GENERIC_TYPES = {"BMVertSkin"} _UNDEFINED_GENERIC_TYPES = {"BMVertSkin"}
@ -1226,6 +1224,8 @@ _USEFUL_DUNDERS = {
"__le__", "__le__",
"__gt__", "__gt__",
"__ge__", "__ge__",
"__enter__",
"__exit__",
} }
@ -1293,6 +1293,7 @@ def _fix_dunder_signatures(
"__ge__": "bool", "__ge__": "bool",
"__delitem__": "None", "__delitem__": "None",
"__setitem__": "None", "__setitem__": "None",
"__exit__": "None",
} }
for method in methods: for method in methods:
fixed = _FIXED_RETURNS.get(method["name"]) fixed = _FIXED_RETURNS.get(method["name"])
@ -1300,6 +1301,15 @@ def _fix_dunder_signatures(
method["return_type"] = fixed method["return_type"] = fixed
if method["name"] == "__len__": if method["name"] == "__len__":
method["params"] = [] method["params"] = []
if method["name"] == "__enter__":
method["return_type"] = "Self"
method["params"] = []
if method["name"] == "__exit__":
method["params"] = [
{"name": "exc_type", "type": "type[BaseException] | None", "default": None, "kind": "POSITIONAL_OR_KEYWORD"},
{"name": "exc_val", "type": "BaseException | None", "default": None, "kind": "POSITIONAL_OR_KEYWORD"},
{"name": "exc_tb", "type": "object", "default": None, "kind": "POSITIONAL_OR_KEYWORD"},
]
if method["name"] == "__delitem__": if method["name"] == "__delitem__":
method["params"] = [ method["params"] = [
{ {
@ -2680,6 +2690,31 @@ def introspect_rna_types() -> ModuleData:
_merge_non_rna_bpy_types(structs, _bpy_types) _merge_non_rna_bpy_types(structs, _bpy_types)
_merge_missing_c_methods(structs, _bpy_types) _merge_missing_c_methods(structs, _bpy_types)
# Discover hidden C types reachable only via method return values
# (e.g. ContextTempOverride from Context.temp_override()).
known = {s["name"] for s in structs}
bpy = importlib.import_module("bpy")
ctx = getattr(bpy, "context")
_HIDDEN_TYPE_PROBES: list[tuple[object, str]] = [
(ctx, "temp_override"),
]
for obj, method_name in _HIDDEN_TYPE_PROBES:
func = getattr(obj, method_name, None)
if func is None or not callable(func):
continue
try:
result = func()
result_cls = result.__class__
if result_cls.__name__ not in known:
structs.append(introspect_class(result_cls, "bpy.types"))
known.add(result_cls.__name__)
# Clean up context manager if applicable
exit_fn = getattr(result, "__exit__", None)
if exit_fn is not None:
exit_fn(None, None, None)
except Exception:
pass
return { return {
"module": "bpy.types", "module": "bpy.types",
"doc": "Blender RNA type definitions.", "doc": "Blender RNA type definitions.",