File size: 3,386 Bytes
0d1138e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import inspect
from typing import Any, Optional


def get_nested_attr(
    module: ..., 
    attr_path: str,
) -> Any | None:
    """Loop through nested attributes."""
    x = module
    for attr in attr_path.split("."):
        x = getattr(x, attr, None)
    return x


def get_callable_names(
    prefix: str, 
    module: ..., 
    max_depth: int = 3, 
    depth: int = 0,
    include: Optional[set[str]] = None,
) -> list[str]:
    """
    Recursively retrieve the names of all callables in a given module.
    """
    callable_names = []
    if depth > max_depth:
        return callable_names
    for attr in dir(module):
        if (
            attr.startswith("_") or
            attr in {"model_fields", "model_computed_fields"} or
            include and attr not in include
        ):
            continue
        try:
            value = getattr(module, attr)
        except Exception:
            continue
        full_name = f"{prefix}.{attr}"
        if callable(value):
            callable_names.append(full_name)
        elif hasattr(value, "__dict__") or hasattr(value, "__module__"):
            callable_names.extend(
                get_callable_names(
                    full_name,
                    value,
                    max_depth,
                    depth + 1
                ) # Recurse
            )
    return callable_names


def get_callable_params(
    prefix: str, 
    module: ...,
    callable_name: str
) -> list[dict[str, str]]:
    """
    Get parameter names and default values for a given callable.
    """
    callable = get_nested_attr(
        module, 
        callable_name.replace(f"{prefix}.", "", 1)
    )
    if not callable:
        return []
    try:
        sig = inspect.signature(callable)
        params = []
        for name, param in sig.parameters.items():
            if name == "self":
                continue
            default = (
                param.default if
                param.default is not inspect.Parameter.empty else 
                ""
            )
            kind = param.kind
            params.append({
                "name": name,
                "default": default, 
                "kind": kind
            })
        return params
    except Exception:
        return []


def test_callable(
    prefix: str,
    module: ...,
    callable_name: str,
    *args, **kwargs
) -> str:
    """Test a given callable and return the output."""

    callable = get_nested_attr(
        module,
        callable_name.replace("_", ".").replace(f"{prefix}.", "", 1)
    )
    if not callable:
        return f"Callable '{callable_name}' not found."
    try:
        result = callable(*args, **kwargs)
        return result
    except TypeError as e:
        return f"Callable '{callable_name}' requires arguments: {e}"
    except Exception as e:
        return f"Error running '{callable_name}': {e}"


def generate_callable_guides(prefix: str, module: ..., callable_names: list[str]) -> list[str]:
    """
    Generate callable reference guides from docstrings, parameters, and
    parameter/return types.
    """
    guide = []
    for callable in callable_names:
        callable = get_nested_attr(module, callable.replace(f"{prefix}.", "", 1))
        doc = callable.__doc__ if callable and hasattr(callable, "__doc__") else "No docstring available."
        guide.append(f"{doc}")
    return guide