Clean up dead code, fix type errors, and add publish workflow

Remove unused _append_ops_call_method, fix _property_data_from_member
call signature, replace sys.version_info guard with hasattr check for
__buffer__ protocol, fix implicit string concatenations, add msgbus
overrides for 4.0-4.4, add conformance tests, and add publish script.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 11:07:52 +02:00
co-authored by Claude Opus 4.6
parent 673b9e54bc
commit f4df542dd1
17 changed files with 954 additions and 165 deletions
+34 -32
View File
@@ -172,6 +172,15 @@ def _requires_builtins_import(module_data: ModuleData) -> bool:
return False
def _has_override_methods(module_data: ModuleData) -> bool:
"""Check if any method in the module has is_override set."""
for struct in module_data.get("structs", []):
for method in struct["methods"]:
if method.get("is_override", False):
return True
return False
def collect_imports(module_data: ModuleData) -> set[str]:
"""Collect all import statements needed for the stub file."""
all_types = collect_all_type_strings(module_data)
@@ -179,6 +188,9 @@ def collect_imports(module_data: ModuleData) -> set[str]:
imports = _collect_token_imports(joined) | _collect_module_reference_imports(joined)
if _requires_builtins_import(module_data):
imports.add("import builtins")
if _has_override_methods(module_data):
# override is in typing since 3.12, typing_extensions for 3.11
imports.add("from typing_extensions import override")
return imports
@@ -367,28 +379,6 @@ def generate_property_stub(
result += f"{indent} ...\n"
return result
# Writable properties with different getter/setter types need @property
# (e.g. location: mathutils.Vector | Sequence[float] -> getter returns Vector,
# setter accepts both)
if " | " in prop["type"]:
parts_list = [p.strip() for p in prop["type"].split(" | ")]
getter_type = parts_list[0]
setter_type = prop["type"]
result = f"{indent}{property_decorator}\n"
result += f"{indent}def {prop['name']}(self) -> {getter_type}:\n"
if prop["description"]:
desc = prop["description"].replace("\\", "\\\\").replace('"""', r"\"\"\"")
if desc.endswith('"'):
desc += " "
result += f'{indent} """{desc}"""\n'
else:
result += f"{indent} ...\n"
result += f"{indent}@{prop['name']}.setter\n"
result += (
f"{indent}def {prop['name']}(self, value: {setter_type}) -> None: ...\n"
)
return result
result = f"{indent}{prop['name']}: {prop['type']}\n"
if prop["description"]:
desc = prop["description"].replace("\\", "\\\\").replace('"""', r"\"\"\"")
@@ -478,7 +468,9 @@ def generate_struct_stub(
has_body = True
for method in struct["methods"]:
is_override = method["name"] in inherited_methods
is_override = method["name"] in inherited_methods or method.get(
"is_override", False
)
parts.append(generate_method_stub(method, is_override=is_override))
has_body = True
@@ -513,10 +505,14 @@ def topological_sort_structs(structs: list[StructData]) -> list[StructData]:
class _InheritedInfo:
methods: set[str]
readonly_props: set[str]
all_props: set[str]
def __init__(self, methods: set[str], readonly_props: set[str]):
def __init__(
self, methods: set[str], readonly_props: set[str], all_props: set[str]
):
self.methods = methods
self.readonly_props = readonly_props
self.all_props = all_props
def collect_inherited_info(
@@ -531,7 +527,7 @@ def collect_inherited_info(
return cache[name]
struct = by_name.get(name)
if not struct or not struct["base"]:
cache[name] = _InheritedInfo(set(), set())
cache[name] = _InheritedInfo(set(), set(), set())
return cache[name]
base = struct["base"]
if base in by_name:
@@ -539,13 +535,16 @@ def collect_inherited_info(
base_ro_props = {
p["name"] for p in by_name[base]["properties"] if p["is_readonly"]
}
base_all_props = {p["name"] for p in by_name[base]["properties"]}
else:
base_methods: set[str] = set()
base_ro_props: set[str] = set()
base_all_props: set[str] = set()
parent = get_inherited(base)
cache[name] = _InheritedInfo(
base_methods | parent.methods,
base_ro_props | parent.readonly_props,
base_all_props | parent.all_props,
)
return cache[name]
@@ -562,7 +561,9 @@ def _generate_context_dict(context_struct: StructData) -> str:
can return a precisely typed dict instead of dict[str, object].
"""
lines: list[str] = ["class ContextDict(TypedDict):"]
lines.append(' """Dictionary returned by Context.copy() with all context members."""')
lines.append(
' """Dictionary returned by Context.copy() with all context members."""'
)
method_names = {m["name"] for m in context_struct["methods"]}
for prop in context_struct["properties"]:
if prop["name"] in method_names:
@@ -648,12 +649,13 @@ def generate_types_stub(
_patch_context_copy_return_type(context_struct)
for struct in sorted_structs:
info = inherited_map.get(struct["name"], _InheritedInfo(set(), set()))
# Force writable properties to readonly when they override a parent's
# readonly @property (avoids reportIncompatibleMethodOverride)
for prop in struct["properties"]:
if not prop["is_readonly"] and prop["name"] in info.readonly_props:
prop["is_readonly"] = True
info = inherited_map.get(struct["name"], _InheritedInfo(set(), set(), set()))
# Skip properties already defined on a parent class — re-emitting them
# can cause reportIncompatibleVariableOverride or reportImplicitOverride
# when RNA subtypes differ (e.g., FunctionNodeInputColor.color vs Node.color)
struct["properties"] = [
p for p in struct["properties"] if p["name"] not in info.all_props
]
# Emit ContextDict TypedDict right before the Context class
if struct["name"] == "Context":
parts.append("")