LibCST

raw JSON →
1.8.6 verified Tue May 12 auth: no python install: verified quickstart: verified

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.

pip install libcst
error ModuleNotFoundError: No module named 'libcst'
cause The `libcst` library is not installed in the Python environment.
fix
Install the library using pip: pip install libcst
error libcst._exceptions.ParserSyntaxError: Syntax Error @ X:Y. Incomplete input. Unexpectedly encountered '...'
cause The input source code contains invalid Python syntax or uses Python features not yet supported by the installed `libcst` version, or the wrong parsing function (`parse_expression` instead of `parse_module`) was used.
fix
Ensure the input code is valid Python for the target version, upgrade libcst to a version that supports the syntax, or use libcst.parse_module() for full modules.
error Exception: Logic error, unexpected top level type!
cause This error, often accompanied by Rust compilation messages, indicates compatibility issues between the `libcst` native parser (written in Rust) and the Python version or Rust toolchain being used, particularly with newer Python releases like 3.12+ where `libcst` might not have prebuilt wheels.
fix
Ensure you have a compatible Rust toolchain installed (e.g., rustup install stable if using rustup) or use a Python version for which libcst provides prebuilt wheels, or explicitly install a libcst version compatible with your Python interpreter.
error AttributeError: 'CSTNode' object has no attribute 'code_for_node'
cause The `code_for_node` method is available on a `Module` object, not directly on individual `CSTNode` instances, as it requires the module's context (like indentation and newline formats) to correctly render code.
fix
Access code_for_node through the Module object from which the node was parsed: module.code_for_node(your_cst_node).
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.
fix Ensure your environment is set up to use the default native parser. If you encounter parsing issues or rely on the old parser, update your setup to use the native implementation. Install with pre-built binary wheels when possible to avoid Rust toolchain dependencies.
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.
fix Always return `updated_node` (or a modified copy of it) from `leave_` methods in `CSTTransformer`. Utilize `with_changes()` for modifying existing nodes and `FlattenSentinel` for inserting or removing multiple nodes to preserve formatting. Avoid `isinstance` for traversal; prefer visitor methods like `visit_<NodeType>` or matchers.
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.
fix The easiest way to install Rust is via `rustup`. If you frequently build Python packages with native extensions, consider using an environment where Rust is pre-configured.
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.
fix Consider using `sys.modules` checks at runtime to prevent co-installation, or implement a build-time script using a LibCST transformer to rewrite internal import paths in your fork. Explicitly declare conflicts in `pyproject.toml` if such a feature becomes available in package managers.
gotcha When debugging CST structures, simply `print(tree)` might provide an overwhelming amount of detail including all whitespace and token information.
fix Use `from libcst.tool import dump` and then `print(dump(tree))` for a more concise and readable representation of the CST's essential elements, which is often more useful for understanding the tree's structure.
python os / libc status wheel install import disk
3.10 alpine (musl) wheel - 0.53s 32.2M
3.10 alpine (musl) - - 0.51s 32.2M
3.10 slim (glibc) wheel 2.2s 0.29s 33M
3.10 slim (glibc) - - 0.30s 33M
3.11 alpine (musl) wheel - 0.91s 35.9M
3.11 alpine (musl) - - 1.06s 35.9M
3.11 slim (glibc) wheel 2.2s 0.81s 37M
3.11 slim (glibc) - - 0.73s 37M
3.12 alpine (musl) wheel - 0.75s 27.5M
3.12 alpine (musl) - - 0.81s 27.5M
3.12 slim (glibc) wheel 2.1s 0.81s 29M
3.12 slim (glibc) - - 0.77s 29M
3.13 alpine (musl) wheel - 0.64s 27.3M
3.13 alpine (musl) - - 0.68s 27.2M
3.13 slim (glibc) wheel 2.3s 0.66s 28M
3.13 slim (glibc) - - 0.65s 28M
3.9 alpine (musl) wheel - 0.43s 32.1M
3.9 alpine (musl) - - 0.49s 32.1M
3.9 slim (glibc) wheel 2.7s 0.45s 33M
3.9 slim (glibc) - - 0.40s 33M

This quickstart demonstrates parsing Python code, applying a `CSTTransformer` to modify a variable name (`old_var` to `new_var`), and then generating the modified code. It also shows how to use `libcst.tool.dump` for a concise representation of the CST and how to incorporate metadata providers.

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))