Skip to content

API Reference

backframe.

Inspect the caller.

CallResolver

Bases: NodeVisitor

Resolve a simple call to named callable.

Source code in backframe/__init__.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
class CallResolver(ast.NodeVisitor):
    """Resolve a simple call to named callable."""

    # ruff: noqa: N802

    def __init__(self, filter_name: str) -> None:
        self.filter_name = filter_name
        self.call_exprs: list[ast.Call] = []

    def visit_Call(self, node: ast.Call) -> None:
        if isinstance(node.func, ast.Name) and node.func.id == self.filter_name:
            self.call_exprs.append(node)

resolve_expression(lines: list[str], resolver: Callable[[ast.stmt], list[ExprT]]) -> ExprT | None

Resolve an expression of interest from lines.

Parameters:

Name Type Description Default
lines list[str]

Lines to get the statement from.

required
resolver Callable[[stmt], list[ExprT]]

Typically a node visitor that extracts expressions of interest from statements.

required

Returns:

Type Description
First matching statement or `None` if no statement was found.
Source code in backframe/__init__.py
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
def resolve_expression(
    lines: list[str],
    resolver: Callable[[ast.stmt], list[ExprT]],
) -> ExprT | None:
    """
    Resolve an expression of interest from `lines`.

    Parameters
    ----------
    lines
        Lines to get the statement from.
    resolver
        Typically a node visitor that extracts expressions of interest from statements.

    Returns
    -------
    First matching statement or `None` if no statement was found.

    """
    stmts: list[ast.stmt] = []

    for n in range(len(lines)):
        chunk = lines[: n + 1]
        with suppress(SyntaxError):
            stmts.extend(ast.parse("\n".join(chunk), mode="exec").body)
            break

    matching_exprs = [*chain.from_iterable(filter(None, map(resolver, stmts)))]

    if not matching_exprs:
        return None

    if len(matching_exprs) > 1:
        msg = (
            "Multiple matching statements found: "
            f"{', '.join(map(ast.dump, matching_exprs))}"
        )
        raise ValueError(msg)

    return matching_exprs[0]

map_args_to_identifiers(*objects: Any, function: Callable[..., Any] | None = None, stack_offset: int = 2) -> dict[str, Any]

Map objects (passed to the caller function) to their original identifiers.

def test(args): ... print(map_args_to_identifiers(args)) ... foo = 1; bar = 2; biz = 3 test(foo) {'foo': 1} test( ... bar) {'bar': 2} baz = 4; test(bar, ... biz, ... baz, ... ) {'bar': 2, 'biz': 3, 'baz': 4}

Parameters:

Name Type Description Default
objects Any

Objects to map to identifiers.

()
function Callable[..., Any] | None

Function to get the caller expression from.

None
stack_offset int

Stack level to get the caller expression from.

2

Returns:

Type Description
Dictionary with identifiers as keys and objects as values.
Source code in backframe/__init__.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
def map_args_to_identifiers(
    *objects: Any,
    function: Callable[..., Any] | None = None,
    stack_offset: int = 2,
) -> dict[str, Any]:
    """
    Map objects (passed to the caller function) to their original identifiers.

    >>> def test(*args):
    ...     print(map_args_to_identifiers(*args))
    ...
    >>> foo = 1; bar = 2; biz = 3
    >>> test(foo)
    {'foo': 1}
    >>> test(
    ...     bar)
    {'bar': 2}
    >>> baz = 4; test(bar,
    ... biz,
    ...          baz,
    ... )
    {'bar': 2, 'biz': 3, 'baz': 4}

    Parameters
    ----------
    objects
        Objects to map to identifiers.
    function
        Function to get the caller expression from.
    stack_offset
        Stack level to get the caller expression from.

    Returns
    -------
    Dictionary with identifiers as keys and objects as values.

    """
    current_frame = inspect.currentframe()
    if current_frame is None:
        return {}

    caller_frame = current_frame.f_back
    if caller_frame is None:
        return {}

    caller_function_name = caller_frame.f_code.co_name
    if not caller_function_name.isidentifier():
        msg = "Cannot call `map_to_identifiers` outside functions."
        raise RuntimeError(msg)

    if function is None:
        function = _get_frame_namespace(caller_frame)[caller_function_name]

    frame = inspect.stack()[stack_offset].frame
    source_lines, bof = inspect.getsourcelines(frame)
    cutoff_lines = source_lines[frame.f_lineno - 1 - bof :]
    resolver = partial(_resolve_calls, function_name=function.__name__)

    call = resolve_expression(cutoff_lines, resolver)
    if call is None:
        return {}

    mapping: dict[str, Any] = {}

    for arg, obj in zip(call.args, objects):
        if not isinstance(arg, ast.Name):
            msg = f"Expected `ast.Name` but got `{arg}`."
            raise TypeError(msg)
        mapping[arg.id] = obj

    return mapping