astcheck - Check Python ASTs against templates
astcheck is a Python library designed to check Abstract Syntax Trees (ASTs) against structured templates. It's particularly useful for testing AST transformations, linters, or any code that processes Python ASTs. The current version is 0.4.0, with releases occurring periodically to address minor improvements and Python version compatibility.
Common errors
-
TypeError: expected ast.AST, got <class 'str'>
cause Attempting to pass a raw Python code string as a template or target AST to `is_ast_match` or `assert_ast_equal`.fixUse `import ast` and `ast.parse('your code here')` to convert your code strings into AST nodes before passing them to `astcheck`. -
NameError: name 'is_ast_match' is not defined
cause Forgetting to import the required `astcheck` functions or placeholders.fixAdd `from astcheck import is_ast_match, assert_ast_equal, NAME` (or other necessary symbols) at the top of your script. -
AssertionError (from assert_ast_equal or a failed assert is_ast_match)
cause The target AST node does not structurally match the provided template AST node, or the specific values/types expected by the template are not present.fixUse `print(ast.dump(target_node))` and `print(ast.dump(template_node))` to inspect the exact AST structures. Pay close attention to node types (e.g., `ast.Constant` vs `ast.Name`), values, and attribute names.
Warnings
- gotcha Templates for `astcheck` functions must be actual AST nodes, not raw Python code strings. If you pass a string directly, you'll get a `TypeError`.
- gotcha Placeholders like `NAME`, `N_INT`, `N_STR`, etc., must be explicitly imported from `astcheck` and correctly assigned in the `kwargs` of matching functions or used directly in the template AST (e.g., `astcheck.NAME`).
- gotcha AST comparison is structural, not textual. Differences in whitespace, comments, or explicit parentheses that do not alter the underlying AST structure will be ignored by `ast.parse` and thus by `astcheck`.
Install
-
pip install astcheck
Imports
- is_ast_match
from astcheck import is_ast_match
- assert_ast_equal
from astcheck import assert_ast_equal
- match_ast
from astcheck import match_ast
- NAME
from astcheck import NAME
Quickstart
import ast
from astcheck import is_ast_match, assert_ast_equal, NAME
# The target AST node to check
code_to_check = """x = 1 + y"""
node = ast.parse(code_to_check)
# Define a template AST using ast.parse() and astcheck placeholders
template = ast.parse("""some_var = 1 + another_var""")
# Using is_ast_match to check if the target AST matches the template structure
is_match = is_ast_match(node, template, some_var=NAME, another_var=NAME)
assert is_match, "The AST structure should match the template!"
print(f"AST '{code_to_check.strip()}' matches template: {is_match}")
# Using assert_ast_equal for a stricter comparison (all non-placeholder nodes must be identical)
# For exact matching, often you wouldn't use NAME placeholders
strict_template = ast.parse("""x = 1 + y""")
assert_ast_equal(node, strict_template)
print("Strict AST comparison successful.")
# Example with extracted matches (if using match_ast instead of is_ast_match)
from astcheck import match_ast, N_INT
match_result = match_ast(node, ast.parse("var_name = LITERAL + OTHER_VAR"), var_name=NAME, LITERAL=N_INT, OTHER_VAR=NAME)
assert match_result is not None
print(f"Extracted variable name: {match_result['var_name'].id}")
print(f"Extracted literal value: {match_result['LITERAL'].value}")