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:
- The
executor
calls set_context
before executing every call node.
- The function being called can call
get_context
to get the current context.
- 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)
|