Library / SDK
tree-sitter/py-tree-sitter avatar
tree-sitter/py-tree-sitter

py-tree-sitter: Python API for syntax trees and incremental parsing

Python bindings to the Tree-sitter parsing library

1,513 stars200 forksCMIT

At a glance

What is it?
py-tree-sitter exposes the Tree-sitter parsing library through Python for syntax-tree inspection, querying, tree walking, and incremental reparsing. It is suitable for Python 3.10 and newer, but language support comes from separate packages.
Who is it for?
Python developers building syntax-aware editors, linters, code-navigation tools, or source analysis services should evaluate py-tree-sitter when incremental parsing and structured syntax trees matter. Projects that need only fixed-pattern text replacement should first confirm that a syntax tree is necessary.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 54 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Where py-tree-sitter fits in a source-analysis stack

py-tree-sitter solves the problem of turning source text into a structured syntax tree that Python code can inspect. It is intended for developers who need syntax-aware behavior rather than regular-expression-only text matching. Typical jobs include identifying function definitions, locating calls, traversing large trees, or updating an analysis after a source edit. The repository identifies `incremental`, `parsing`, and `text-processing` as project keywords, and its implementation is primarily C exposed through Python.

The package is the Python binding layer, not a collection of language grammars. A separate Tree-sitter language implementation supplies the grammar. The README uses `tree-sitter-python` as its example, while `pyproject.toml` lists separate test packages for Python, JavaScript, JSON, Rust, and HTML. This separation matters when planning dependencies: installing `tree-sitter` gives Python access to parsers, but it does not by itself make every target language parseable.

The API supports more than obtaining a root node. It exposes parser configuration, byte and point coordinates, field-specific child lookup, cursors, queries, captures, tree edits, and changed ranges. That puts it below higher-level editor or code-analysis frameworks. The binding leaves applications responsible for deciding which syntax nodes matter and how edits should be applied to source text.

Installation requires both bindings and a language grammar

The README states that the package has no library dependencies and provides pre-compiled wheels for major platforms. Install the binding with the documented command. The project metadata requires Python 3.10 or newer, so an older interpreter is outside the declared support range.

This installs the parser API:

sh
pip install tree-sitter

Parsing Python source also requires its language implementation. The README uses `tree-sitter-python` for this purpose:

sh
pip install tree-sitter-python

The metadata pins `tree-sitter-python==0.25.0` among its optional test dependencies, but the README installation example does not prescribe that version for application use. It would be safer not to infer that the test pin is a compatibility declaration for every release. If no pre-compiled wheel is available for a target platform, the README directs users to submit an issue on GitHub. The repository also contains a native extension build defined in `setup.py`, so a local source build requires a C compiler and the relevant build tools.

Constructing a Python parser and inspecting named nodes

The first useful parser requires a language object and a `Parser` configured with that language. This creates a parse tree whose nodes expose types, fields, byte ranges, and point coordinates.

Use the documented imports and language construction:

python
import tree_sitter_python as tspython
from tree_sitter import Language, Parser

PY_LANGUAGE = Language(tspython.language())
parser = Parser(PY_LANGUAGE)

The README parses UTF-8 bytes because the Tree-sitter API expects source input as bytes at this stage:

python
tree = parser.parse(
    bytes(
        """
def foo():
    if bar:
        baz()
""",
        "utf8"
    )
)

Application code can navigate the tree through children or named fields. For example, the README obtains the function name with `child_by_field_name("name")` and the body through `child_by_field_name("body")`. This is more precise than searching every node for a matching string because fields associate child nodes with grammatical roles. Node coordinates support source mapping, while `str(root_node)` gives a parenthesized representation of the syntax structure.

Streaming parse input through byte offsets and points

Direct parsing is not limited to one in-memory bytes object. The parser accepts a read callable for source held in another data structure. Returning an empty bytes object or `None` terminates input for that line, and the README states that input must use UTF-8 or UTF-16.

A byte-offset reader can return one byte at a time:

python
src = bytes(
    """
def foo():
    if bar:
        baz()
""",
    "utf8",
)


def read_callable_byte_offset(byte_offset, point):
    return src[byte_offset : byte_offset + 1]


tree = parser.parse(read_callable_byte_offset, encoding="utf8")

This callback interface avoids requiring the caller to assemble one contiguous source buffer before parsing. The parser supplies both a byte offset and a point tuple. For line-oriented storage, the callable can use the row and column to return the remainder of the current line encoded as bytes. The documentation does not define behavior for arbitrary callback exceptions or partial multi-byte sequences, so callers should preserve the stated UTF-8 or UTF-16 contract rather than assume undocumented recovery behavior.

TreeCursor traversal and its subtree boundary

Repeated access through `Node.children` is straightforward, but the README specifically presents `TreeCursor` for efficient traversal of a large number of nodes. Cursor operations move through the first child, next sibling, and parent while exposing the current `cursor.node`.

Create a cursor from a parsed tree:

python
cursor = tree.walk()

assert cursor.node.type == "module"
assert cursor.goto_first_child()
assert cursor.node.type == "function_definition"
assert cursor.goto_first_child()
assert cursor.node.type == "def"

The documented boundary is important: the cursor can only walk into children of the node where it started. This makes traversal scope explicit and prevents accidental movement into an unrelated branch. The repository includes `examples/walk_tree.py` as a complete example of iterating over every node. Applications needing broader movement should review that example rather than assuming the cursor can escape its initial subtree.

Incremental edits reuse an existing Tree-sitter tree

The binding's most consequential feature is incremental parsing. After source changes, the old `Tree` must be told about the edit before the parser is invoked again with that tree. The edit records old and new byte ranges and point coordinates, keeping structural positions aligned with the source.

Apply an edit to the tree as shown in the README:

python
new_src = src[:5] + src[5 : 5 + 2].upper() + src[5 + 2 :]

tree.edit(
    start_byte=5,
    old_end_byte=5,
    new_end_byte=5 + 2,
    start_point=(0, 5),
    old_end_point=(0, 5),
    new_end_point=(0, 5 + 2),
)

Then pass the old tree back to `Parser.parse`:

python
new_tree = parser.parse(new_src, tree)

The README says this runs much faster than parsing from scratch. The old tree can then report regions whose syntactic structure changed:

python
for changed_range in tree.changed_ranges(new_tree):
    print("Changed range:")
    print(f"  Start point {changed_range.start_point}")
    print(f"  Start byte {changed_range.start_byte}")
    print(f"  End point {changed_range.end_point}")
    print(f"  End byte {changed_range.end_byte}")

This optimization carries a correctness obligation. Every coordinate passed to `Tree.edit` must match the actual source change. Inconsistent metadata can invalidate reuse even if the edited bytes are correct. Projects that cannot track those coordinates reliably should parse from scratch rather than introduce speculative incremental state.

Queries select grammar patterns while captures return matching nodes

Tree queries provide a grammar-aware alternative to manually following every child and field. A `Query` combines a language with a query expression, and a `QueryCursor` returns captures from a parsed tree. Capture names let application code distinguish function definitions, bodies, calls, and argument lists.

The README constructs a query for Python function definitions and calls:

python
query = Query(
    PY_LANGUAGE,
    """
(function_definition
  name: (identifier) @function.def
  body: (block) @function.block)

(call
  function: (identifier) @function.call
  arguments: (argument_list) @function.args)
""",
)

The returned capture map groups nodes by capture name. This fits code that repeatedly searches for known grammatical structures. It is less suitable for an arbitrary source-text replacement where a syntax tree provides no value.

A real alternative is to call the underlying Tree-sitter library directly. The native library performs parsing, but this repository adds the Python object model, Python packaging, typed interfaces, Python callbacks, and Python-side language and query objects. Programs already written as native C extensions can avoid the Python binding boundary. Python applications would gain little from bypassing the supported binding unless they have a native-language requirement that the documented Python API cannot satisfy.

Release, license, and maintenance considerations

The repository is not archived, and its last push was on 2026-08-30. The latest listed release is `v0.26.0`, dated 2026-06-30, while earlier releases include `v0.25.2` and `v0.25.1`. The project therefore has recent release and repository activity, but upgrade planning must still account for its native extension and separately installed grammar packages.

The declared license is MIT. That permits use and modification under the license terms, but this review does not interpret legal obligations. Projects should retain the applicable license notices and verify the terms of the core Tree-sitter library and each language package they distribute.

Upgrade cost depends on deployment and API exposure. Pre-compiled wheels can simplify installation for supported platforms. Source builds compile C11 files through `setup.py`, which may require platform-specific compiler support. Application code that depends on node traversal, query captures, or edit coordinates must be checked against changes between binding and language-package versions. The repository includes `tests/` and `examples/`, but the README excerpt does not document a formal compatibility matrix, so users should not infer one from the optional test dependency pins.

Editorial conclusion

Python developers building syntax-aware editors, linters, code-navigation tools, or source analysis services should evaluate py-tree-sitter when incremental parsing and structured syntax trees matter. Projects that need only fixed-pattern text replacement should first confirm that a syntax tree is necessary. Before adoption, verify the required Tree-sitter language package supports the project languages and that pre-compiled wheels cover every target platform and Python version. For source builds, also confirm the build toolchain can compile the bundled C11 extension.

Frequently asked questions

What Python versions does py-tree-sitter support?

The project metadata declares `requires-python = ">=3.10"`, so Python 3.10 and newer are in the stated range.

How do I install py-tree-sitter and parse Python source?

Run `pip install tree-sitter`, install `tree-sitter-python` for Python grammar support, load it with `Language(tspython.language())`, and pass that object to `Parser`.

Does py-tree-sitter include language grammars?

No. Language implementations are separate packages, and the README installs `tree-sitter-python` as the language implementation for its Python example.

What is the minimum requirement for incremental parsing?

The old tree must be updated with `Tree.edit` using byte and point coordinates for the source change. That edited tree is then passed to `Parser.parse` with the new source.

Which license applies to py-tree-sitter?

The repository metadata and project classifiers identify the package under the MIT License.

Can py-tree-sitter walk a complete parsed tree?

The README documents `examples/walk_tree.py` as a complete node-iteration example. A `TreeCursor` itself is restricted to walking children of the node where it started.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. tree-sitter/py-tree-sitter on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tree-sitter-py-tree-sitter.svg)](https://hysenlabs.com/projects/tree-sitter-py-tree-sitter)