The query system lets you navigate HCL documents by structure rather than serializing to dicts. This page covers the Python API; for the hq CLI tool, see hq Reference.
import hcl2
doc = hcl2.query('resource "aws_instance" "main" { ami = "abc-123" }')
for block in doc.blocks("resource"):
print(block.block_type, block.name_labels)
ami = block.attribute("ami")
if ami:
print(f" ami = {ami.value}")You can also parse from a file:
from hcl2.query import DocumentView
doc = DocumentView.parse_file("main.tf")The entry point for queries. Wraps a StartRule.
doc = DocumentView.parse(text) # from string
doc = DocumentView.parse_file("main.tf") # from file
doc = hcl2.query(text) # convenience alias
doc = hcl2.query(open("main.tf")) # also accepts file objects| Method / Property | Returns | Description |
|---|---|---|
body |
BodyView |
The document body |
blocks(block_type?, *labels) |
List[BlockView] |
Blocks matching type and optional labels |
attributes(name?) |
List[AttributeView] |
Attributes, optionally filtered by name |
attribute(name) |
AttributeView | None |
Single attribute by name |
Wraps a BodyRule. Same filtering methods as DocumentView.
Wraps a BlockRule.
block = doc.blocks("resource", "aws_instance")[0]
block.block_type # "resource"
block.labels # ["resource", "aws_instance", "main"]
block.name_labels # ["aws_instance", "main"]
block.body # BodyView| Property / Method | Returns | Description |
|---|---|---|
block_type |
str |
First label (the block type name) |
labels |
List[str] |
All labels as plain strings |
name_labels |
List[str] |
Labels after the block type (labels[1:]) |
body |
BodyView |
The block body |
blocks(...) |
List[BlockView] |
Nested blocks (delegates to body) |
attributes(...) |
List[AttributeView] |
Nested attributes (delegates to body) |
attribute(name) |
AttributeView | None |
Single nested attribute |
Wraps an AttributeRule.
attr = doc.attribute("ami")
attr.name # "ami"
attr.value # '"abc-123"' (serialized Python value)
attr.value_node # NodeView over the expressionWraps a TupleRule. Access via find_all or by navigating to a tuple-valued attribute.
from hcl2.query.containers import TupleView
from hcl2.walk import find_first
from hcl2.rules.containers import TupleRule
doc = DocumentView.parse('x = [1, 2, 3]\n')
node = find_first(doc.attribute("x").raw, TupleRule)
tv = TupleView(node)
len(tv) # 3
tv[0] # NodeView for the first element
tv.elements # List[NodeView]Wraps an ObjectRule.
from hcl2.query.containers import ObjectView
from hcl2.rules.containers import ObjectRule
node = find_first(doc.attribute("tags").raw, ObjectRule)
ov = ObjectView(node)
ov.keys # ["Name", "Env"]
ov.get("Name") # NodeView for the value
ov.entries # List[Tuple[str, NodeView]]Wraps ForTupleExprRule / ForObjectExprRule.
from hcl2.query.for_exprs import ForTupleView
from hcl2.rules.for_expressions import ForTupleExprRule
doc = DocumentView.parse('x = [for item in var.list : item]\n')
node = find_first(doc.raw, ForTupleExprRule)
fv = ForTupleView(node)
fv.iterator_name # "item"
fv.second_iterator_name # None (or "v" for "k, v in ...")
fv.iterable # NodeView
fv.value_expr # NodeView
fv.has_condition # bool
fv.condition # NodeView | NoneForObjectView adds key_expr and has_ellipsis.
Wraps a ConditionalRule (ternary condition ? true : false).
from hcl2.query.expressions import ConditionalView
from hcl2.rules.expressions import ConditionalRule
doc = DocumentView.parse('x = var.enabled ? "on" : "off"\n')
node = find_first(doc.raw, ConditionalRule)
cv = ConditionalView(node)
cv.condition # NodeView over the condition expression
cv.true_val # NodeView over the true branch
cv.false_val # NodeView over the false branchWraps a FunctionCallRule.
from hcl2.query.functions import FunctionCallView
from hcl2.rules.functions import FunctionCallRule
doc = DocumentView.parse('x = length(var.list)\n')
node = find_first(doc.raw, FunctionCallRule)
fv = FunctionCallView(node)
fv.name # "length"
fv.args # List[NodeView]
fv.has_ellipsis # boolAll view classes inherit from NodeView:
| Method / Property | Returns | Description |
|---|---|---|
raw |
LarkElement |
The underlying IR node |
parent_view |
NodeView | None |
View over the parent node |
to_hcl() |
str |
Reconstruct this subtree as HCL text |
to_dict(options?) |
Any |
Serialize to a Python value |
find_all(rule_type) |
List[NodeView] |
Find descendants by rule class |
find_by_predicate(fn) |
List[NodeView] |
Find descendants where fn(view) is truthy |
walk_semantic() |
List[NodeView] |
All semantic descendant nodes |
walk_rules() |
List[NodeView] |
All rule descendant nodes |
The hcl2.walk module provides free functions for traversing the IR tree directly (without view wrappers):
from hcl2.walk import walk, walk_rules, walk_semantic, find_all, find_first, ancestors
from hcl2.rules.base import AttributeRule
tree = hcl2.parses('x = 1\ny = 2\n')
# All nodes depth-first (including tokens)
for node in walk(tree):
print(node)
# Only LarkRule nodes
for rule in walk_rules(tree):
print(rule)
# Only semantic rules (skip NewLineOrCommentRule)
for rule in walk_semantic(tree):
print(rule)
# Find specific rule types
attrs = list(find_all(tree, AttributeRule))
first_attr = find_first(tree, AttributeRule)
# Walk up the parent chain
for parent in ancestors(first_attr):
print(parent)- hq Reference — query HCL files from the command line
- Advanced API Reference — intermediate pipeline stages, Builder
- Getting Started — core API (
load/dump), options, CLI converters