diff --git a/generate_stubs.py b/generate_stubs.py index a0d8523..1ea470d 100644 --- a/generate_stubs.py +++ b/generate_stubs.py @@ -176,10 +176,14 @@ def format_param(param: ParamData, force_type: bool = False) -> str: parts.append(f": {type_str}") if param["default"] is not None: + default = param["default"] + # Fix incompatible defaults: Literal types can't have bool defaults + if type_str and "Literal[" in type_str and default in ("False", "True"): + default = "..." if type_str: - parts.append(f" = {param['default']}") + parts.append(f" = {default}") else: - parts.append(f"={param['default']}") + parts.append(f"={default}") return "".join(parts) @@ -552,6 +556,8 @@ def generate_types_stub( typing_imports.append("Literal") abc_imports = ["Iterator"] + if "Callable" in all_type_strs: + abc_imports.append("Callable") if "MutableSequence[" in all_type_strs: abc_imports.append("MutableSequence") # Only import bare Sequence if used without collections.abc. prefix diff --git a/introspect.py b/introspect.py index d264950..b040154 100644 --- a/introspect.py +++ b/introspect.py @@ -322,12 +322,24 @@ def clean_type_str(type_str: str) -> str: type_str = re.sub(r":class:`([^`]+)`", r"\1", type_str) # Remove double backtick RST markup type_str = re.sub(r"``([^`]+)``", r"\1", type_str) + # Remove single backtick markup (` int ` → int) + type_str = re.sub(r"`\s*([^`]+?)\s*`", r"\1", type_str) # Strip leaked RST directives from type strings type_str = re.sub(r"\.?\s*(?:r?type|returns?):.*", "", type_str) # Strip stray RST role colons but not :param, :arg, :type directives type_str = re.sub(r":(?!param|arg|type|return)(\w)", r"\1", type_str) type_str = type_str.rstrip(":.,") + # Convert "string in ['X', 'Y', ...]" to Literal["X", "Y", ...] + str_in_match = re.match( + r"str(?:ing)?\s+in\s+\[([^\]]+)\]", type_str, re.IGNORECASE + ) + if str_in_match: + values = re.findall(r"'([^']+)'", str_in_match.group(1)) + if values: + quoted = ", ".join(f'"{v}"' for v in values) + return f"Literal[{quoted}]" + # Convert tuple(X, Y) to tuple[X, Y] (docstrings sometimes use parens) type_str = re.sub(r"\btuple\(([^)]+)\)", r"tuple[\1]", type_str) @@ -480,6 +492,10 @@ def clean_type_str(type_str: str) -> str: # Bare generics without params -> add default params. # Use \b on both sides to avoid matching inside longer names (e.g. SequenceEntry). type_str = re.sub(r"\bCallable\b(?!\[)", "Callable[..., object]", type_str) + # Fix Callable[[..., ...], X] or Callable[[object, ...], X] -> Callable[..., X] + type_str = re.sub( + r"Callable\[\[[^\]]*\.\.\.[^\]]*\]", "Callable[...", type_str + ) type_str = re.sub(r"\bdict\b(?!\[)", "dict[str, object]", type_str) type_str = re.sub(r"\blist\b(?!\[)", "list[object]", type_str) type_str = re.sub(r"\btuple\b(?!\[)", "tuple[object, ...]", type_str) @@ -682,6 +698,13 @@ def clean_type_str(type_str: str) -> str: ): type_str = "object" + # Map types that exist in docstrings but not in the Blender Python API + _UNDEFINED_TYPE_MAP = { + "ContextTempOverride": "object", + } + if type_str in _UNDEFINED_TYPE_MAP: + return _UNDEFINED_TYPE_MAP[type_str] + # Final balance check — catch any remaining malformed types if type_str.count("[") != type_str.count("]") or type_str.count( "("