Packages

A unified Elixir client for multiple AI providers including Vertex AI, OpenAI, Anthropic, AWS Bedrock, and local PyTorch models.

Retired package: Security issue - Copile gcp issues

Current section

Files

Jump to
axiom_ai lib python_interface ast scan_globals.py
Raw

lib/python_interface/ast/scan_globals.py

import ast
class GlobalsVisitor(ast.NodeVisitor):
def __init__(self):
# References to undefined globals.
self.refs = set()
# Scope information, including definitions.
self.scopes = []
# Add the global scope to the stack.
self._push_scope()
# Scope information specific to exception handlers.
self.exception_handler_scopes = []
def _push_scope(self, is_comprehension=False):
self.scopes.append({"defs": set(), "is_comprehension": is_comprehension})
def _pop_scope(self):
self.scopes.pop()
def _push_exception_handler_scope(self):
self.exception_handler_scopes.append({"defs": set()})
def _pop_exception_handler_scope(self):
self.exception_handler_scopes.pop()
def _handle_ref(self, name):
is_defined = any(name in scope["defs"] for scope in self.scopes) or any(
name in scope["defs"] for scope in self.exception_handler_scopes
)
if not is_defined:
self.refs.add(name)
def visit_Name(self, node):
if isinstance(node.ctx, ast.Store):
self.scopes[-1]["defs"].add(node.id)
elif isinstance(node.ctx, ast.Load):
self._handle_ref(node.id)
def visit_FunctionDef(self, node):
self._visit_function_def(node)
def visit_AsyncFunctionDef(self, node):
self._visit_function_def(node)
def _visit_function_def(self, node):
self.scopes[-1]["defs"].add(node.name)
self._push_scope()
self.generic_visit(node)
self._pop_scope()
def visit_arguments(self, node):
# Scan defaults for refs before adding defs from args.
for default in node.defaults:
self.visit(default)
for default in node.kw_defaults:
if default is not None:
self.visit(default)
for arg in node.posonlyargs:
self.visit(arg)
for arg in node.args:
self.visit(arg)
if node.vararg is not None:
self.visit(node.vararg)
for arg in node.kwonlyargs:
self.visit(arg)
if node.kwarg is not None:
self.visit(node.kwarg)
def visit_arg(self, node):
self.scopes[-1]["defs"].add(node.arg)
def visit_ClassDef(self, node):
self.scopes[-1]["defs"].add(node.name)
self._push_scope()
self.generic_visit(node)
self._pop_scope()
def visit_Lambda(self, node):
self._push_scope()
self.generic_visit(node)
self._pop_scope()
def visit_ListComp(self, node):
self._visit_comprehension_expr(node)
def visit_SetComp(self, node):
self._visit_comprehension_expr(node)
def visit_GeneratorExp(self, node):
self._visit_comprehension_expr(node)
def _visit_comprehension_expr(self, node):
self._push_scope(is_comprehension=True)
# Scan the generator for defs first.
for generator in node.generators:
self.visit(generator)
self.visit(node.elt)
self._pop_scope()
def visit_DictComp(self, node):
self._push_scope(is_comprehension=True)
# Scan the generator for defs first.
for generator in node.generators:
self.visit(generator)
self.visit(node.key)
self.visit(node.value)
self._pop_scope()
def visit_comprehension(self, node):
# Scan iter for refs before adding defs from target.
self.visit(node.iter)
self.visit(node.target)
for if_node in node.ifs:
self.visit(if_node)
def visit_Import(self, node):
for alias in node.names:
name = alias.asname or alias.name.split(".")[0]
self.scopes[-1]["defs"].add(name)
def visit_ImportFrom(self, node):
for alias in node.names:
name = alias.asname or alias.name
self.scopes[-1]["defs"].add(name)
def visit_Assign(self, node):
# Scan the right side for refs before adding defs from the left side.
self.visit(node.value)
for target in node.targets:
self.visit(target)
def visit_AugAssign(self, node):
# Scan the right side for refs before adding defs from the left side.
self.visit(node.value)
# Name on the left is also referenced.
if isinstance(node.target, ast.Name):
self._handle_ref(node.target.id)
self.visit(node.target)
def visit_NamedExpr(self, node):
# Scan the right side for refs before adding defs from the left side.
self.visit(node.value)
# Walrus operator leaks out of comprehensions into the outer scope.
if self.scopes[-1]["is_comprehension"] and isinstance(node.target, ast.Name):
outer_scope = next(
scope for scope in reversed(self.scopes) if not scope["is_comprehension"]
)
outer_scope["defs"].add(node.target.id)
else:
self.visit(node.target)
def visit_AnnAssign(self, node):
# Scan the right side for refs before adding defs from the left side.
if node.value is not None:
self.visit(node.value)
self.visit(node.target)
def visit_ExceptHandler(self, node):
# Exception handlers define local variables available only within
# the handler, however the handler body is still within the outer
# scope, so we add a special ref-only scope.
self._push_exception_handler_scope()
self.exception_handler_scopes[-1]["defs"].add(node.name)
self.generic_visit(node)
self._pop_exception_handler_scope()
def visit_MatchAs(self, node):
if node.name is not None:
self.scopes[-1]["defs"].add(node.name)
if node.pattern is not None:
self.visit(node.pattern)
def visit_MatchStar(self, node):
if node.name is not None:
self.scopes[-1]["defs"].add(node.name)
def visit_MatchMapping(self, node):
for key in node.keys:
self.visit(key)
for pattern in node.patterns:
self.visit(pattern)
if node.rest is not None:
self.scopes[-1]["defs"].add(node.rest)
tree = ast.parse(code)
visitor = GlobalsVisitor()
visitor.visit(tree)
builtin_names = globals()["__builtins__"].keys()
(visitor.refs - builtin_names, visitor.scopes[0]["defs"])