Python Construct
Continuing from enhancement of ast construction based from the code_component
,
this week was primarily focused on implementing the remaining Python constructs.
In addition to handling child nodes (e.g., Literals, Imports), the definition for each node are now more dynamic.
The nodes are connected through compositions another by using compositions to establish relationship where the part_id
in composition
matches the id of code_component
, as in construct_node
>
In the previous construct_node
the attributes such as value, elts, name, and other is mostly using the name of code_component
,
either by manually spliting, extract the call function, or just use the value as it is and instatiating it under ast.Constant
construct_node
def construct_node(component):
label_ = component.name
if component.type == 'script':
return ast.Module(name=component.name, body=[], type_ignores=[])
elif component.type == 'literal':
return ast.Constant(value=component.name)
elif component.type == 'list':
ast_ = ast.List(elts=[], ctx=ast.Load())
ast_.label = label_
return ast_
elif component.type == 'tuple':
ast_ = ast.Tuple(elts=[], ctx=ast.Load())
ast_.label = label_
return ast_
elif component.type == 'set':
ast_ = ast.Set(elts=[])
ast_.label = label_
return ast_
elif component.type == 'dict':
ast_ = ast.Dict(keys=[], values=[])
ast_.label = label_
return ast_
elif component.type == 'key_value':
key_ = ''.join(component.name.split(':')[0]).strip()
value_ = ''.join(component.name.split(':')[1]).strip()
return ([ast.Constant(value=key_), ast.Constant(value=value_)])
elif component.type == 'name':
return ast.Name(id=component.name, ctx=ast.Load())
elif component.type == 'expr':
ast_ = ast.Expr(value=None)
ast_.label = label_
return ast_
elif component.type == 'uadd':
ast_ = ast.UnaryOp(op=ast.UAdd(), operand=None)
ast_.label = label_
return ast_
elif component.type == 'usub':
ast_ = ast.UnaryOp(op=ast.USub(), operand=None)
ast_.label = label_
return ast_
elif component.type == 'not':
ast_ = ast.UnaryOp(op=ast.Not(), operand=None)
ast_.label = label_
return ast_
elif component.type == 'invert':
ast_ = ast.UnaryOp(op=ast.Invert(), operand=None)
ast_.label = label_
return ast_
elif component.type == "add":
ast_ = ast.BinOp(left=None, op=ast.Add(), right=None)
ast_.label = label_
return ast_
elif component.type == "sub":
ast_ = ast.BinOp(left=None, op=ast.Sub(), right=None)
ast_.label = label_
return ast_
elif component.type == "div":
ast_ = ast.BinOp(left=None, op=ast.Div(), right=None)
ast_.label = label_
return ast_
elif component.type == "mult":
ast_ = ast.BinOp(left=None, op=ast.Mult(), right=None)
ast_.label = label_
return ast_
elif component.type == "floordiv":
ast_ = ast.BinOp(left=None, op=ast.FloorDiv(), right=None)
ast_.label = label_
return ast_
elif component.type == "mod":
ast_ = ast.BinOp(left=None, op=ast.Mod(), right=None)
ast_.label = label_
return ast_
elif component.type == "pow":
ast_ = ast.BinOp(left=None, op=ast.Pow(), right=None)
ast_.label = label_
return ast_
elif component.type == "lshift":
ast_ = ast.BinOp(left=None, op=ast.LShift(), right=None)
ast_.label = label_
return ast_
elif component.type == "rshift":
ast_ = ast.BinOp(left=None, op=ast.RShift(), right=None)
ast_.label = label_
return ast_
elif component.type == "bitor":
ast_ = ast.BinOp(left=None, op=ast.BitOr(), right=None)
ast_.label = label_
return ast_
elif component.type == "bitxor":
ast_ = ast.BinOp(left=None, op=ast.BitXor(), right=None)
ast_.label = label_
return ast_
elif component.type == "bitand":
ast_ = ast.BinOp(left=None, op=ast.BitAnd(), right=None)
ast_.label = label_
return ast_
elif component.type == "and":
ast_ = ast.BoolOp(op=ast.And(), values=[])
ast_.label = label_
return ast_
elif component.type == "or":
ast_ = ast.BoolOp(op=ast.Or(), values=[])
ast_.label = label_
return ast_
elif any(t in ['eq', 'noteq', 'lt', 'lte', 'gt', 'gte', 'is', 'isnot', 'in', 'notin'] for t in component.type.split('.')):
ops_ = []
ops_map = {
'eq': ast.Eq(), 'noteq': ast.NotEq(), 'lt': ast.Lt(), 'lte': ast.LtE(),
'gt': ast.Gt(), 'gte': ast.GtE(), 'is': ast.Is(), 'isnot': ast.IsNot(),
'in': ast.In(), 'notin': ast.NotIn()
}
for t in component.type.split('.'):
if t in ops_map.keys():
ops_.append(ops_map[t])
ast_ = ast.Compare(left=None, ops=ops_, comparators=[])
ast_.label = label_
return ast_
elif component.type == "call":
ast_ = ast.Call(func=None, args=[], keywords=[])
ast_.label = label_
return ast_
elif component.type == 'ifexp':
ast_ = ast.IfExp(test=None, body=None, orelse=None)
ast_.label = label_
return ast_
elif component.type == 'attribute':
ast_ = ast.Attribute(value=''.join(
component.name.split('.')[1]), attr=None, ctx=ast.Load())
ast_.label = label_
return ast_
elif component.type == "subscript":
return ast.Subscript(value=None, slice=None, ctx=ast.Load())
elif component.type == 'index':
return ast.Index(value=component.name)
elif component.type == 'slice':
ast_ = ast.Slice(lower=None, upper=None, step=None)
ast_.label = label_
return ast_
elif component.type == 'extslice':
ast_ = ast.ExtSlice(dims=[])
ast_.label = label_
return ast_
elif component.type == 'listcomp':
ast_ = ast.ListComp(elt=None, generators=[])
ast_.label = label_
return ast_
elif component.type == 'setcomp':
ast_ = ast.SetComp(elt=None, generators=[])
ast_.label = label_
return ast_
elif component.type == 'genexpcomp':
ast_ = ast.GeneratorExp(elt=None, generators=[])
ast_.label = label_
return ast_
elif component.type == 'dictcomp':
ast_ = ast.DictComp(key=None, value=None, generators=[])
ast_.label = label_
return ast_
elif component.type == 'comprehension':
ast_ = ast.comprehension(target=None, iter=None, ifs=[], is_async=0)
ast_.label = label_
return ast_
elif component.type == 'assign':
ast_ = ast.Assign(targets=[], value=None)
ast_.label = label_
return ast_
elif component.type == 'ann_assign':
ast_ = ast.AnnAssign(target=None, annotation=None, simple=None)
ast_.label = label_
return ast_
elif component.type == 'ann_target':
if '.' in component.name:
values_ = component.name.split('.')
ast_ = ast.Attribute(value=ast.Name(
id=values_[0], ctx=ast.Load()), attr=values_[1], ctx=ast.Load())
ast_.label = label_
return ast_
elif '[' in component.name and ']' in component.name:
value_, slice_ = component.name.split('[')
slice_ = slice_.rstrip(']')
if ':' in slice_:
parts = slice_.split(':')
lower_ = parts[0].strip() if parts[0].strip() else None
upper_ = parts[1].strip() if parts[1].strip() else None
step_ = parts[2].strip() if len(
parts) > 2 and parts[2].strip() else None
ast_ = ast.Subscript(value=ast.Name(id=value_, ctx=ast.Load()),
slice=ast.Slice(lower=lower_, upper=upper_, step=step_), ctx=ast.Store())
ast_.label = label_
return ast_
else:
ast_ = ast.Subscript(value=ast.Name(id=value_, ctx=ast.Store()),
slice=ast.Index(value=slice_), ctx=ast.Load())
ast_.label = label_
return ast_
else:
ast_ = ast.Name(id=component.name, ctx=ast.Load())
ast_.label = label_
return ast_
elif component.type == 'annotation':
ast_ = ast.Constant(value=component.name)
ast_.label = label_
return ast_
elif component.type == 'aug_assign':
ops_map = {
'+=': ast.Add(), '-=': ast.Sub(), '*=': ast.Mult(), '/=': ast.Div(),
'%=': ast.Mod(), '**=': ast.Pow(), '<<=': ast.LShift(), '>>=': ast.RShift(),
'&=': ast.BitAnd(), '|=': ast.BitOr(), '^=': ast.BitXor()
}
op_ = component.name.split()[1].strip()
ast_ = ast.AugAssign(target=None, op=ops_map[op_], value=None)
ast_.label = label_
return ast_
elif component.type == 'raise':
ast_ = ast.Raise(exc=ast.Name(id=component.name, ctx=ast.Load()))
ast_.label = label_
return ast_
elif component.type == 'assert':
test_ = component.name.split('assert ')[1].split(',')[0].strip()
msg_ = component.name.split('assert ')[1].split(',')[1].strip() if len(
component.name.split('assert ')[1].split(',')) > 1 else None
ast_ = ast.Assert(test=ast.Name(id=test_, ctx=ast.Load()),
msg=ast.Constant(value=msg_) if msg_ else None)
ast_.label = label_
return ast_
elif component.type == 'delete':
targets_ = [ast.Name(id=target.strip(), ctx=ast.Del())
for target in component.name.split(',')]
ast_ = ast.Delete(targets=targets_)
ast_.label = label_
return ast_
elif component.type == "pass":
return ast.Pass()
elif component.type == 'import':
name_ = ' '.join(component.name.split()[1:])
name_ = name_.split(' as ')[0].split()[0]
asname_ = component.name.split(
' as ')[-1].strip() if ' as ' in component.name else None
return ast.Import(names=[ast.alias(name=name_, asname=asname_)])
elif component.type == 'import_from':
module_ = component.name.split()[1]
level_ = module_.count('.') if module_.startswith('.') else 0
names_ = ' '.join(component.name.split()[3:])
names_ = [n_.strip() for n_ in names_.split(',')]
alias = [ast.alias(name=n.split(' ')[0], asname=n.split(
' as ')[-1].strip() if ' as ' in n else None) for n in names_]
return ast.ImportFrom(module=module_, names=alias, level=level_)
elif component.type == "if":
ast_ = ast.If(test=None, body=[], orelse=[])
ast_.label = label_
return ast_
elif component.type == "for":
ast_ = ast.For(target=None, iter=None, body=[], orelse=[])
ast_.label = label_
return ast_
elif component.type == 'break':
return ast.Break()
elif component.type == 'continue':
return ast.Continue()
elif component.type == 'while':
ast_ = ast.While(test=None, body=[], orelse=[])
ast_.label = label_
return ast_
elif component.type == 'try':
ast_ = ast.Try(body=[], handlers=[], orelse=[], finalbody=[])
ast_.label = label_
return ast_
elif component.type == 'exception':
ast_ = ast.ExceptHandler(name=component.name, body=[])
ast_.label = label_
return ast_
elif component.type == 'with':
ast_ = ast.With(items=[], body=[])
ast_.label = label_
return ast_
elif component.type == 'withitem':
as_ = ''.join(component.name.split(' as ')[
1]) if ' as ' in component.name else None
return ast.withitem(context_expr=None, optional_vars=as_)
elif component.type == 'function_def':
args = [body['name'] for body in results_dict
if body['type'] == 'arguments' and body['whole_id'] == component.id][0]
label_lines = []
for body in results_dict:
if body['whole_id'] == component.id and body['position'] is not None:
if body['type'] == 'function_def':
label_lines.append(f"def {body['name']}({args}):")
else:
label_lines.append(body['name'])
label_ = '\n'.join(label_lines)
ast_ = ast.FunctionDef(name=None, args=None,
body=[], decorator_list=[], type_params=[])
ast_.label = label_
return ast_
elif component.type == 'identifier':
return component.name
elif component.type == 'lambda_def':
return ast.Lambda(args=None, body=[])
elif component.type == 'arguments':
return ast.arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[],
kw_defaults=[], kwarg=None, defaults=[])
elif component.type == 'param':
return ast.arg(arg=component.name, annotation=None)
elif component.type == "return":
ast_ = ast.Return(value=None)
ast_.label = label_
return ast_
elif component.type == 'yield':
ast_ = ast.Yield(value=None, ctx=ast.Load())
ast_.label = label_
return ast_
elif component.type == 'yield_from':
ast_ = ast.YieldFrom(value=None)
ast_.label = label_
return ast_
elif component.type == 'global':
names_ = component.name.replace('global ', '').strip().split(',')
ast_ = ast.Global(names=names_)
ast_.label = label_
return ast_
elif component.type == 'nonlocal':
names_ = component.name.replace('nonlocal ', '').strip().split(',')
ast_ = ast.Nonlocal(names=names_)
ast_.label = label_
return ast_
elif component.type == 'class_def':
label_lines = []
for body in results_dict:
if body['whole_id'] == component.id and body['position'] is not None:
if body['type'] == 'function_def':
label_lines.append(f"def {body['name']}:")
else:
label_lines.append(body['name'])
label_ = '\n'.join(label_lines)
ast_ = ast.ClassDef(name=None, bases=[], keywords=[],
body=[], decorator_list=[], type_params=[])
ast_.label = label_
return ast_
Nodes defined
- Script (
script
): Creates an ast.Module node representing the script/module. - Literal (
literal
): Creates an ast.Constant node representing a literal value (e.g., numbers, strings). - List (
list
): Creates an ast.List node representing a list. - Tuple (
tuple
): Creates an ast.Tuple node representing a tuple. - Set (
set
): Creates an ast.Set node representing a set. - Dictionary (
dict
): Creates an ast.Dict node representing a dictionary. - Key-Value Pair (
key_value
): Creates two ast.Constant nodes representing a key-value pair in a dictionary. - Name (
name
): Creates an ast.Name node representing a variable or identifier. - Expression (
expr
): Creates an ast.Expr node representing an expression. - Unary Operations:
- Unary Plus (
uadd
): Creates an ast.UnaryOp node with ast.UAdd as the operation. - Unary Minus (
usub
): Creates an ast.UnaryOp node with ast.USub as the operation. - Not (not):
Creates
an ast.UnaryOp node with ast.Not as the operation. - Invert (
invert
): Creates an ast.UnaryOp node with ast.Invert as the operation.
- Unary Plus (
- Binary Operations:
- Addition (
add
): Creates an ast.BinOp node with ast.Add as the operation. - Subtraction (
sub
): Creates an ast.BinOp node with ast.Sub as the operation. - Division (
div
): Creates an ast.BinOp node with ast.Div as the operation. - Multiplication (
mult
): Creates an ast.BinOp node with ast.Mult as the operation. - Floor Division (
floordiv
): Creates an ast.BinOp node with ast.FloorDiv as the operation. - Modulus (
mod
): Creates an ast.BinOp node with ast.Mod as the operation. - Power (
pow
): Creates an ast.BinOp node with ast.Pow as the operation. - Left Shift (
lshift
): Creates an ast.BinOp node with ast.LShift as the operation. - Right Shift (
rshift
): Creates an ast.BinOp node with ast.RShift as the operation. - Bitwise OR (
bitor
): Creates an ast.BinOp node with ast.BitOr as the operation. - Bitwise XOR (
bitxor
): Creates an ast.BinOp node with ast.BitXor as the operation. - Bitwise AND (
bitand
): Creates an ast.BinOp node with ast.BitAnd as the operation.
- Addition (
- Boolean Operations:
- And (
and
): Creates an ast.BoolOp node with ast.And as the operation. - Or (
or
): Creates an ast.BoolOp node with ast.Or as the operation.
- And (
- Comparisons: Creates an ast.Compare node with appropriate comparison operations combinations that seperate with
.
(ast.Eq
, ast.NotEq
, ast.Lt
, ast.LtE, ast.Gt, ast.GtE, ast.Is, ast.IsNot, ast.In, ast.NotIn). - Function/Method Call (
call
): Creates an ast.Call node representing a function/method call. - If Else Ternary Operation (
ifexp
): Creates an ast.IfExp representinga if b else c
expression - Attribute (
attribute
): Creates an ast.Attribute node representing attribute access. - Subscript Operation (
subscript
): Creates an ast.Subscript node representing a subscript operation. - List Comprehension (
listcomp
): Creates an ast.ListComp node representing a list comprehension. - Set Comprehension (
setcomp
): Creates an ast.SetComp node representing a set comprehension. - Generator Expression Comprehension (
genexpcomp
): Creates an ast.GeneratorExp node representing a generator expression comprehension. - Dictionary Comprehension (
dictcomp
): Creates an ast.DictComp node representing a dictionary comprehension. - Comprehension (
comprehension
): Creates an ast.comprehension node representing a comprehension clause. - Assignment (
assign
): Creates an ast.Assign node representing an assignment statement. - Annotated Assignment (
ann_assign
): Creates an ast.AnnAssign node representing an annotated assignment statement. - Augmented Assignment (
aug_assign
): Creates an ast.AugAssign node representing an augmented assignment statement. - Raise (
raise
): Creates an ast.Raise node representing a raise statement. - Assert (
assert
): Creates an ast.Assert node representing an assert statement. - Delete (
del
): Creates an ast.Delete node representing a delete statement. - Pass (
pass
): Creates an ast.Pass node representing a pass statement. - Import (
import
): Creates an ast.Import node representing an import statement. - Import From (
import_from
): Creates an ast.ImportFrom node representing an import from statement. - If Statement (
if
): Creates an ast.If node representing an if statement. - While Loop (
while
): Creates an ast.While node representing a while loop statement. - For Loop (
for
): Creates an ast.For node representing a for loop statement. - Break (
break
): Creates an ast.Break node representing a break statement. - Continue (
continue
): Creates an ast.Continue node representing a continue statement. - With Statement (
with
): Creates an ast.With node representing a with statement. - With Item (
withitem
): Creates an ast.withitem node representing an item in a with statement. - Function Definition (
function_def
): Creates an ast.FunctionDef node representing a function definition. - Lambda Function Definition (
lambda_def
): Creates an astLambda node representing a lambda function definition. - Arguments (
arguments
): Creates an ast.arguments node representing arguments of a function. - Argument (
param
): Creates an ast.arg node representing a single argument. - Return (
return
): Creates an ast.Return node representing a return statement. - Yield (
yield
): Creates an ast.Yield node representing a yield statement. - Yield From (
yieldfrom
): Creates an ast.YieldFrom node representing a yield from statement. - Global (
global
): Creates an ast.Global node representing a global statement. - Nonlocal (
nonlocal
): Creates an ast.Nonlocal node representing a nonlocal statement. - Class Definition (
class_def
): Creates an ast.ClassDef node representing a class definition.
Additionally, I added another atribute for the nodes, which is the label. node.label is going to be the label of the node from ast graph visualization. These label is mainly from the name of code_component
, appart from function_def
& class_def
.
Querying the labels for def nodes
The complicated label for the function_def
& class_def
are queried from the database. The following query extracts the IDs of code components that are either function definitions (function_def) or class definitions (class_def) for a specific trial. The resulting IDs are used as a subquery for the next query.
def_id = session.query(CodeComponent.id).join(
Composition,
(CodeComponent.id == Composition.part_id) &
(CodeComponent.trial_id == Composition.trial_id)
).filter(
(CodeComponent.trial_id == trial_id) &
(CodeComponent.type == 'function_def') | (CodeComponent.type == 'class_def')
).subquery()
Next, the details of the code_component
are needed. Below query fetches details of code components related to the previously identified function and class definitions for the same trial. The details include the name, type, whole_id, and position fields from the CodeComponent and Composition tables. We join these tables on matching id and trial_id fields, filter the results to exclude components of type syntax, and to include only those whose whole_id is in the list of IDs from our previous query.
results = session.query(
CodeComponent.name,
CodeComponent.type,
Composition.whole_id,
Composition.position
).join(
Composition,
(CodeComponent.id == Composition.part_id) &
(CodeComponent.trial_id == Composition.trial_id)
).filter(
(CodeComponent.trial_id == trial_id) &
(CodeComponent.type != 'syntax') &
Composition.whole_id.in_(def_id)
).all()
The query results are then converted into a list of dictionaries for easier manipulation and readability.
results_dict = [
{
'name': row.name,
'type': row.type,
'whole_id': row.whole_id,
'position': row.position
}
for row in results
]
build_ast
As mentioned before, the relationship between code_component
& composition
is more dynamic. With the Python constructs coverage in noworkflow, the attribute of the nodes are more defined. Each block handles how to assign or append the attribute of parent node to current iteration node based on the composition type.
def build_ast(code_components, compositions):
node_dict = {}
for component in code_components:
node = construct_node(component)
if node is None:
continue
node_dict[component.id] = node
for composition in compositions:
whole_node = node_dict.get(composition.whole_id)
part_node = node_dict.get(composition.part_id)
if whole_node is None or part_node is None:
if composition.extra is None:
continue
if isinstance(whole_node, ast.Module):
whole_node.body.append(part_node)
elif isinstance(whole_node, ast.List) or (isinstance(whole_node, ast.Tuple) and isinstance(part_node, ast.AST)) or isinstance(whole_node, ast.Set):
whole_node.elts.append(part_node)
whole_node.content = []
composition.whole_id == part_node and whole_node.content.append(
whole_node.name)
elif isinstance(whole_node, ast.Dict):
print(part_node[0])
print(part_node[1])
whole_node.keys.append(part_node[0])
whole_node.values.append(part_node[1])
elif isinstance(whole_node, ast.Expr):
whole_node.value = part_node
elif isinstance(whole_node, ast.UnaryOp):
whole_node.operand = part_node
elif isinstance(whole_node, ast.BinOp):
if composition.type == 'left':
whole_node.left = part_node
elif composition.type == 'right':
whole_node.right = part_node
elif isinstance(whole_node, ast.BoolOp):
whole_node.values.append(part_node)
elif isinstance(whole_node, ast.Compare):
if composition.type == 'left':
whole_node.left = part_node
elif composition.type == '*comparators':
whole_node.comparators.append(part_node)
elif isinstance(whole_node, ast.Call):
if composition.type == 'func':
whole_node.func = part_node
elif composition.type == '*args':
whole_node.args.append(part_node)
elif composition.type == '*keywords':
whole_node.keywords.append(part_node)
elif isinstance(whole_node, ast.IfExp):
if composition.type == 'test':
whole_node.test = part_node
elif composition.type == 'body':
whole_node.body = part_node
elif composition.type == 'orelse':
whole_node.orelse = part_node
elif isinstance(whole_node, ast.Attribute):
if composition.type == 'value':
whole_node.value = part_node
elif composition.extra is not None:
extra_ = composition.extra[composition.extra.find(
"'") + 1:composition.extra.rfind("'")]
whole_node.attr = extra_
elif isinstance(whole_node, ast.Subscript):
if composition.type == 'value':
whole_node.value = part_node
elif composition.type == 'slice':
if isinstance(part_node, ast.AST):
whole_node.slice = part_node
else:
whole_node.slice = ast.Constant(value=part_node)
elif isinstance(whole_node, ast.Slice):
if composition.type == 'lower':
whole_node.lower = part_node
elif composition.type == 'upper':
whole_node.upper = part_node
elif composition.type == 'step':
whole_node.step = part_node
elif isinstance(whole_node, ast.Tuple): # ast.ExtSlice
if isinstance(part_node, ast.AST):
whole_node.dims.append(part_node)
else:
whole_node.dims.append(ast.Constant(value=part_node))
elif isinstance(whole_node, ast.ListComp) or isinstance(whole_node, ast.SetComp) or isinstance(whole_node, ast.GeneratorExp):
if composition.type == 'elt':
whole_node.elt = part_node
elif composition.type == '*generators':
whole_node.generators.append(part_node)
elif isinstance(whole_node, ast.DictComp):
if composition.type == 'key_value':
print(part_node[0])
print(part_node[1])
whole_node.key = part_node[0]
whole_node.value = part_node[1]
elif composition.type == '*generators':
whole_node.generators.append(part_node)
elif isinstance(whole_node, ast.comprehension):
if composition.type == 'target':
whole_node.target = part_node
elif composition.type == 'iter':
whole_node.iter = part_node
elif composition.type == '*ifs':
whole_node.ifs.append(part_node)
elif isinstance(whole_node, ast.Assign):
if composition.type == '*targets':
whole_node.targets.append(part_node)
elif composition.type == 'value':
whole_node.value = part_node
elif isinstance(whole_node, ast.AnnAssign):
if composition.type == 'annotation':
whole_node.annotation = part_node
elif composition.type == 'simple':
simple_ = composition.extra.split('(')[1].split(')')[0]
whole_node.simple = simple_
elif composition.type == 'target':
whole_node.target = part_node
elif composition.type == 'value':
whole_node.value = part_node
elif isinstance(whole_node, ast.AugAssign):
if composition.type == 'target':
whole_node.target = part_node
elif composition.type == 'value':
whole_node.value = part_node
elif isinstance(whole_node, ast.If):
if composition.type == 'test':
whole_node.test = part_node
elif composition.type == '*body':
whole_node.body.append(part_node)
elif composition.type == '*orelse':
whole_node.orelse.append(part_node)
elif isinstance(whole_node, ast.For):
if composition.type == 'target':
whole_node.target = part_node
elif composition.type == 'iter':
whole_node.iter = part_node
elif composition.type == '*body':
whole_node.body.append(part_node)
elif composition.type == '*orelse':
whole_node.body.append(part_node)
elif isinstance(whole_node, ast.While):
if composition.type == 'test':
whole_node.test = part_node
elif composition.type == '*body':
whole_node.body.append(part_node)
elif composition.type == '*orelse':
whole_node.orelse.append(part_node)
elif isinstance(whole_node, ast.Try):
if composition.type == '*body':
whole_node.body.append(part_node)
elif composition.type == '*handlers':
whole_node.handlers.append(part_node)
elif composition.type == '*orelse':
whole_node.orelse.append(part_node)
elif composition.type == '*finalbody':
whole_node.finalbody.append(part_node)
elif isinstance(whole_node, ast.ExceptHandler):
if composition.type == '*body':
whole_node.body.append(part_node)
elif isinstance(whole_node, ast.With):
if composition.type == '*items':
if isinstance(part_node, ast.withitem):
whole_node.items.append(part_node)
else:
whole_node.items[composition.position].context_expr = part_node
elif composition.type == '*body':
whole_node.body.append(part_node)
elif isinstance(whole_node, ast.FunctionDef):
if composition.type == 'name_node':
whole_node.name = part_node
elif composition.type == 'args':
if isinstance(part_node, ast.arguments):
whole_node.args = part_node
elif composition.type == '*body':
whole_node.body.append(part_node)
elif composition.type == '*decorator_list':
whole_node.decorator_list.append(part_node)
elif isinstance(whole_node, ast.Lambda):
if composition.type == 'args':
whole_node.args = part_node
elif composition.type == 'body':
whole_node.body = part_node
elif isinstance(whole_node, ast.Return):
whole_node.value = part_node
elif isinstance(whole_node, ast.arguments):
if composition.type == '*args':
whole_node.args.append(part_node)
elif composition.type == 'vararg':
whole_node.vararg = part_node
elif composition.type == '*defaults':
whole_node.defaults.append(part_node)
elif composition.type == 'kwarg':
whole_node.kwarg = part_node
elif composition.type == '*kwonlyargs':
whole_node.kwonlyargs.append(part_node)
elif composition.type == '*kw_defaults':
whole_node.kw_defaults.append(part_node)
elif composition.type == '*posonlyargs':
whole_node.posonlyargs.append(part_node)
elif isinstance(whole_node, ast.Yield) or isinstance(whole_node, ast.YieldFrom):
whole_node.value = part_node
elif isinstance(whole_node, ast.ClassDef):
if composition.type == 'name_node':
whole_node.name = part_node
elif composition.type == '*body':
whole_node.body.append(part_node)
return node_dict
ast_to_dot
The label for the AST is displayed as mention before. The sample of the output label on Name node:
Name
x
def ast_to_dot(node):
"""Converts AST node to DOT format for Graphviz."""
def node_label(node):
label = type(node).__name__
if isinstance(node, ast.Module):
return f"{label}\n{node.name}"
elif isinstance(node, ast.Constant):
return f"{label}\n{node.value}"
elif isinstance(node, ast.Name):
return f"{label}\n{node.id}"
elif isinstance(node, ast.Import):
return f"Import\nimport {node.names[0].name}"
elif isinstance(node, ast.ImportFrom):
names_ = ', '.join([names.name for names in node.names])
return f"ImportFrom\nfrom {node.module} import {names_}"
elif isinstance(node, ast.alias):
return f"alias\n{node.name} as {node.asname}" if node.asname else f"alias\n{node.name}"
elif isinstance(node, ast.FunctionDef):
args = ', '.join(arg.arg for arg in node.args.args)
return f"{label}\ndef {node.name}({args}):\n{node.label}"
elif isinstance(node, ast.arg):
return f"{label}\n{node.arg}"
elif isinstance(node, ast.ClassDef):
return f"{label}\nclass {node.name}:\n{node.label}"
elif hasattr(node, 'label') and isinstance(node, ast.AST):
return f"{label}\n{node.label}"
else:
return f"{label}"
def node_id(node):
return f"node{str(id(node))}"
dot = graphviz.Digraph()
dot.attr(rankdir='TB', nodesep='0.75', ranksep='0.75')
root_id = node_id(node)
def visit(node, parent=None):
if not isinstance(node, ast.AST) or isinstance(node, ast.Load) or isinstance(node, ast.Store):
return
node_name = node_id(node)
dot.node(node_name, node_label(node), margin='0.1,0.1', width='0.2', height='0.2', fontsize='10')
if parent:
dot.edge(parent, node_name, minlen='1', arrowsize='0.5')
for child_name, child_node in ast.iter_fields(node):
if isinstance(child_node, list):
for child in child_node:
if isinstance(child, ast.AST):
visit(child, node_name)
elif isinstance(child_node, ast.AST):
visit(child_node, node_name)
visit(node)
return dot
The labels are no longer display the AST Object but instead use the node.label. This is more apparent for nodes that has multiple sub-nodes, such as if, for, function_def, etc.
Nodes Issue
While most of the python construct pre 3.12 has been defined properly, some constructs have some incompability with the noworkflow. For more details on the nodes, refer to below issues.
With the ast for the code_components is finished, now next task is to integrate the now vis