Ben Traje
← Back to python

Print Python Module Structures

13 Jun 26 (10d ago)

Normally, when you are trying to figure out a python library, you just look at the documentation. But sometimes, the documentation is not that fleshed out. So, usually, I just print the whole library and skim through the whole data.

Heck, even in AI now, you can just feed it to RAG, and then ask it what are you trying to look for. Nevertheless, you still need to get the actual modules.


import inspect
import pkgutil
import sys
import mgear.anim_picker as anim_picker

def print_module_structure(root_module):
    """
    Crawls through a package, maps its modules, classes, functions, and parent classes.
    """
    indent_step = "    "
    
    print("=" * 80)
    print(f"INTROSPECTING PACKAGE: {root_module.__name__}")
    print(f"File Path: {getattr(root_module, '__file__', 'Built-in/Namespace')}")
    print("=" * 80 + "\n")

    # Dictionary to keep track of walked modules
    modules_to_scan = {root_module.__name__: root_module}

    # Discover all submodules within the package package
    if hasattr(root_module, "__path__"):
        for _, module_name, _ in pkgutil.walk_packages(root_module.__path__, root_module.__name__ + "."):
            try:
                # Dynamically import submodules to read them
                __import__(module_name)
                modules_to_scan[module_name] = sys.modules[module_name]
            except Exception as e:
                print(f"[Error importing {module_name}]: {e}")

    # Process each discovered module
    for mod_name, mod_obj in sorted(modules_to_scan.items()):
        print(f"\n📦 MODULE: {mod_name}")
        print("-" * 60)

        # 1. Fetch module-level functions
        functions = inspect.getmembers(mod_obj, inspect.isfunction)
        # Filter to ensure functions belong to this module, not imported into it
        local_functions = [f for f in functions if f[1].__module__ == mod_name]
        
        if local_functions:
            print(f"{indent_step}Functions:")
            for func_name, func_obj in local_functions:
                try:
                    sig = inspect.signature(func_obj)
                except (ValueError, TypeError):
                    sig = "(...)"
                print(f"{indent_step * 2}{func_name}{sig}")
        
        # 2. Fetch classes inside the module
        classes = inspect.getmembers(mod_obj, inspect.isclass)
        local_classes = [c for c in classes if c[1].__module__ == mod_name]

        if local_classes:
            print(f"{indent_step}Classes:")
            for cls_name, cls_obj in local_classes:
                # Show inheritance lineage (Grandparents / Parents of this class)
                mro = inspect.getmro(cls_obj)
                parents_lineage = " -> ".join([base.__name__ for base in mro[1:] if base.__name__ != 'object'])
                parent_info = f" (Inherits from: {parents_lineage})" if parents_lineage else ""
                
                print(f"{indent_step * 2}🔹 class {cls_name}{parent_info}:")

                # Fetch methods inside this class
                methods = inspect.getmembers(cls_obj, inspect.isroutine)
                for meth_name, meth_obj in methods:
                    # Skip inherited boilerplate methods from built-in 'object' or Qt base bindings to keep output clean
                    if meth_name.startswith('__') and meth_name.endswith('__'):
                        continue
                        
                    # Filter out inherited methods from deep Qt/UI ancestors to focus on mGear source overrides
                    defining_class = None
                    for base in mro:
                        if meth_name in base.__dict__:
                            defining_class = base.__name__
                            break
                    
                    if defining_class and defining_class != cls_name and ("PySide" in defining_class or "QtWidgets" in defining_class):
                        continue # Skip raw un-overridden Qt ecosystem methods
                    
                    try:
                        sig = inspect.signature(meth_obj)
                    except (ValueError, TypeError):
                        sig = "(...)"
                        
                    origin_info = f" [from {defining_class}]" if defining_class and defining_class != cls_name else ""
                    print(f"{indent_step * 3}{meth_name}{sig}{origin_info}")

# Run the introspection
print_module_structure(anim_picker)