diff --git a/generate_stubs.py b/generate_stubs.py index eee389a..cf8190c 100644 --- a/generate_stubs.py +++ b/generate_stubs.py @@ -84,8 +84,9 @@ def collect_all_type_strings(module_data: ModuleData) -> list[str]: for var in module_data["variables"]: all_types.append(map_type(var["type"])) for struct in module_data.get("structs", []): - if struct.get("base"): - all_types.append(struct["base"]) + base = struct.get("base") + if base: + all_types.append(base) for prop in struct["properties"]: all_types.append(prop["type"]) for method in struct["methods"]: @@ -538,13 +539,11 @@ def collect_inherited_info( if base in by_name: base_methods = {m["name"] for m in by_name[base]["methods"]} base_ro_props = { - p["name"] - for p in by_name[base]["properties"] - if p["is_readonly"] + p["name"] for p in by_name[base]["properties"] if p["is_readonly"] } else: - base_methods = set() - base_ro_props = set() + base_methods: set[str] = set() + base_ro_props: set[str] = set() parent = get_inherited(base) cache[name] = _InheritedInfo( base_methods | parent.methods, @@ -755,8 +754,7 @@ def generate_module_stub( # Add TypeVar if any struct uses Generic[_T] if any( - "Generic[_T]" in (s.get("base") or "") - for s in module_data.get("structs", []) + "Generic[_T]" in (s.get("base") or "") for s in module_data.get("structs", []) ): parts.append('_T = TypeVar("_T")') parts.append("") diff --git a/introspect.py b/introspect.py index f682daf..4426aa5 100644 --- a/introspect.py +++ b/introspect.py @@ -333,9 +333,7 @@ def clean_type_str(type_str: str) -> 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 - ) + 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: @@ -495,9 +493,7 @@ def clean_type_str(type_str: str) -> str: # 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"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) @@ -1293,7 +1289,8 @@ def _fix_dunder_signatures( # __getitem__: probe with int index to discover element type if name == "__getitem__": try: - result = instance[0] + getitem = getattr(instance, "__getitem__") + result = getitem(0) rtype = _type_name(result) method["return_type"] = rtype method["params"] = [ @@ -1311,7 +1308,8 @@ def _fix_dunder_signatures( # __iter__: return Iterator[element_type] based on __getitem__ if name == "__iter__": try: - result = instance[0] + getitem = getattr(instance, "__getitem__") + result = getitem(0) etype = _type_name(result) method["return_type"] = f"Iterator[{etype}]" method["params"] = [] @@ -1322,7 +1320,8 @@ def _fix_dunder_signatures( # __setitem__: refine value type from __getitem__ return type if name == "__setitem__": try: - result = instance[0] + getitem = getattr(instance, "__getitem__") + result = getitem(0) vtype = _type_name(result) method["params"] = [ { @@ -1839,13 +1838,13 @@ def introspect_module(module_name: str) -> ModuleData: val = getattr(instance, prop["name"]) except Exception: continue - val_type = type(val) - val_name = val_type.__name__ + val_cls = val.__class__ + val_name = val_cls.__name__ if hasattr(_builtins_mod, val_name) or val_name in _SKIP_PROP_TYPES: continue # Add the hidden type if not already known if val_name not in known_struct_names: - hidden_struct = introspect_class(val_type, module_name) + hidden_struct = introspect_class(val_cls, module_name) # Fix dunders using the live instance (since the type # may not be directly constructible) dunder_methods = [ @@ -2512,23 +2511,27 @@ def introspect_rna_types() -> ModuleData: raw_type = type(raw).__name__ # Only pick up C-level methods, not Python-defined ones. # classmethod wrapping a builtin is C-level (e.g. Space.draw_handler_add) - is_c_classmethod = raw_type == "classmethod_descriptor" or ( - isinstance(raw, classmethod) - and not hasattr(getattr(raw, "__func__", None), "__code__") - ) + is_c_classmethod = raw_type == "classmethod_descriptor" + if not is_c_classmethod and raw_type == "classmethod": + # classmethod wrapping a builtin (e.g. Space.draw_handler_add) + inner = getattr(raw, "__func__", None) + is_c_classmethod = inner is not None and not hasattr(inner, "__code__") if is_c_classmethod: - obj = getattr(cls, attr_name) - func_data = introspect_callable(obj, attr_name) - if func_data: - func_data["is_classmethod"] = True - struct["methods"].append(func_data) + bound = getattr(cls, attr_name) + if callable(bound): + func_data = introspect_callable(bound, attr_name) + if func_data: + func_data["is_classmethod"] = True + struct["methods"].append(func_data) elif raw_type in ( "method_descriptor", "builtin_function_or_method", ): - func_data = introspect_callable(raw, attr_name) - if func_data: - struct["methods"].append(func_data) + obj = getattr(cls, attr_name) + if callable(obj): + func_data = introspect_callable(obj, attr_name) + if func_data: + struct["methods"].append(func_data) return { "module": "bpy.types",