Skip to content

context

This module contains a number of globals, which are set by the execution when it is processing call nodes.

They are used as a side channel to pass values from the executor to special functions which need to know more about the execution context, like in the exec to know the source code of the current node.

This module exposes three global functions, which are meant to be used like:

  1. The executor calls set_context before executing every call node.
  2. The function being called can call get_context to get the current context.
  3. The executor calls teardown_context after its finished executing

I.e. the context is created for every call.

ExecutionContext dataclass

This class is available during execution of CallNodes to the functions which are being called.

It is used as a side channel to pass in metadata about the execution, such as the current node, and other global nodes (used during exec).

The side_effects property is read after the function is finished, by the executor, so that the function can pass additional side effects that were triggered back to it indirectly. This is also used by the exec functions.

Source code in lineapy/execution/context.py
 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
@dataclass
class ExecutionContext:
    """
    This class is available during execution of CallNodes to the functions which are being called.

    It is used as a side channel to pass in metadata about the execution, such as the current node, and other global nodes
    (used during exec).

    The `side_effects` property is read after the function is finished, by the executor, so that the
    function can pass additional side effects that were triggered back to it indirectly. This is also
    used by the exec functions.
    """

    # The current node being executed
    node: CallNode
    # The executor that is running
    executor: Executor

    # Mapping from each input global name to its ID
    _input_node_ids: Mapping[str, LineaID]
    # Mapping from each input global name to whether it is mutable
    _input_globals_mutable: Mapping[str, bool]

    # Mapping of input node IDs to their values.
    # Used by the exec function to understand what side effects to emit, by knowing the nodes associated with each global value used.
    input_nodes: Mapping[LineaID, object]

    # Additional function calls made in this call, to be processed for side effects at the end.
    # The exec function will add to this and we will retrieve it at the end.
    function_calls: Optional[List[FunctionCall]] = field(default=None)

    @property
    def global_variables(self) -> Dict[str, object]:
        """
        The current globals dictionary
        """
        return _global_variables

global_variables: Dict[str, object] property

The current globals dictionary

set_context(executor, variables, node)

Sets the context of the executor to the given node.

Source code in lineapy/execution/context.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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
def set_context(
    executor: Executor,
    variables: Optional[Dict[str, LineaID]],
    node: CallNode,
) -> None:
    """
    Sets the context of the executor to the given node.
    """
    global _current_context
    if _current_context:
        raise RuntimeError("Context already set")

    # Set up our global variables, by first clearing out the old, and then
    # by updating with our new inputs
    # Note: We need to save our inputs so that we can check what has changed
    # at the end
    assert not _global_variables
    # The first time this is run, variables is set, and we know
    # the scoping, so we set all of the variables we know.
    # The subsequent times, we only use those that were recorded
    input_node_ids = variables or node.global_reads

    global_name_to_value = {
        k: executor._id_to_value[id_] for k, id_ in input_node_ids.items()
    }
    _global_variables.setup_globals(global_name_to_value)

    global_node_id_to_value = {
        id_: executor._id_to_value[id_] for id_ in input_node_ids.values()
    }
    _current_context = ExecutionContext(
        _input_node_ids=input_node_ids,
        _input_globals_mutable={
            # Don't consider modules or classes as mutable inputs, so that any code which uses a module
            # we assume it doesn't mutate it.
            k: is_mutable(v) and not isinstance(v, (ModuleType, type))
            for k, v in global_name_to_value.items()
        },
        node=node,
        executor=executor,
        input_nodes=global_node_id_to_value,
    )

teardown_context()

Tearsdown the context, returning the nodes that were accessed and a mapping variables to new values that were added or crated

Source code in lineapy/execution/context.py
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
def teardown_context() -> ContextResult:
    """
    Tearsdown the context, returning the nodes that were accessed
    and a mapping variables to new values that were added or crated
    """
    global _current_context
    if not _current_context:
        raise RuntimeError(NO_CONTEXT_ERROR_MESSAGE)
    res = _global_variables.teardown_globals()
    # If we didn't trace some function calls, use the legacy worst case assumptions for side effects
    if _current_context.function_calls is None:
        side_effects = list(_compute_side_effects(_current_context, res))
    else:
        # Compute the side effects based on the function calls that happened, to understand what input nodes
        # were mutated, what views were added, and what other side effects were created.
        side_effects = list(
            function_calls_to_side_effects(
                _current_context.executor._function_inspector,
                _current_context.function_calls,
                _current_context.input_nodes,
                res.added_or_modified,
            )
        )
    if res.accessed_inputs or res.added_or_modified:
        # Record that this execution accessed and saved certain globals, as first side effect
        side_effects.insert(
            0,
            AccessedGlobals(
                res.accessed_inputs,
                list(res.added_or_modified.keys()),
            ),
        )
    _current_context = None
    return ContextResult(res.added_or_modified, side_effects)

Was this helpful?

Help us improve docs with your feedback!