LibCST
LibCST (Concrete Syntax Tree) is a Python library that parses Python 3.0 through 3.14 source code into a CST tree, preserving all formatting details like comments, whitespaces, and parentheses. It offers a compromise between an Abstract Syntax Tree (AST) and a traditional CST, designed for building automated refactoring (codemod) applications and linters. The current version is 1.8.6, and it maintains an active release cadence with frequent updates.
Warnings
- breaking LibCST transitioned to a new, Rust-based parser by default in version 1.0.0. The old pure-Python parser, previously accessible via `LIBCST_PARSER_TYPE=pure` environment variable, is scheduled for removal in future non-patch releases.
- gotcha When programmatically modifying a CST, directly re-assigning nodes or performing in-place modifications can lead to unexpected loss of formatting (whitespace, comments). LibCST nodes are immutable.
- gotcha Building LibCST from source (e.g., if a binary wheel is not available for your specific Python version or OS architecture) requires a recent Rust toolchain, including `cargo`, to be installed and available in your PATH.
- gotcha Forking and renaming a project that internally uses absolute imports from `libcst` can lead to conflicts if both the original `libcst` and your renamed fork are installed in the same environment.
- gotcha When debugging CST structures, simply `print(tree)` might provide an overwhelming amount of detail including all whitespace and token information.
Install
-
pip install libcst
Imports
- libcst
import libcst as cst
- CSTVisitor
from libcst import CSTVisitor
- CSTTransformer
from libcst import CSTTransformer
- parse_module
from libcst import parse_module
- dump
from libcst.tool import dump
Quickstart
import libcst as cst
from libcst import CSTTransformer, parse_module
from libcst.tool import dump # For pretty-printing the CST
from libcst.metadata import MetadataWrapper, QualifiedNameProvider
class MyTransformer(CSTTransformer):
METADATA_DEPENDENCIES = {QualifiedNameProvider}
def leave_Name(self, original_node, updated_node):
# Example: change all instances of 'old_var' to 'new_var'
if updated_node.value == 'old_var':
print(f"Changing '{original_node.value}' to 'new_var' at {self.get_metadata(QualifiedNameProvider, original_node)}")
return updated_node.with_changes(value='new_var')
return updated_node
source_code = """
import os
def my_function(old_var):
x = old_var + 1
return x
old_var = 10
"""
# Parse the module into a CST
tree = parse_module(source_code)
# Wrap the tree with metadata providers
wrapper = MetadataWrapper(tree)
# Apply the transformer
modified_tree = wrapper.visit(MyTransformer())
# Generate the modified code
modified_code = modified_tree.code
print("Original Code:\n" + source_code)
print("\nModified Code:\n" + modified_code)
print("\nCST of Modified Code (dump):\n" + dump(modified_tree))