Skip to main content

Format-preserving high level AST editing for Python 3.10+.

Project description

pfst

High-level Python AST manipulation that preserves formatting

PyPI version Python versions License

pfst (Python Formatted Syntax Tree) exists in order to allow quick and easy modification of Python source without losing formatting or comments. The goal is simple, Pythonic, container-like access to the AST, with the ability to modify any node while preserving formatting in the rest of the tree.

Yes, we said "formatting" and "AST" in the same sentence.

Normally AST nodes don't store any explicit formatting, much less, comments. But pfst works by adding FST nodes to existing Python AST nodes as an .f attribute (type-safe accessor castf() provided). This keeps extra structure information, the original source, and provides the interface to format-preserving operations. Each operation through FST nodes is a simultaneous edit of the AST tree and the source code, and those are kept synchronized so that the current source will always parse to the current tree.

pfst automatically handles:

  • Operator precedence and parentheses
  • Indentation and line continuations
  • Commas, semicolons, and tuple edge cases
  • Comments and docstrings
  • Various Python version-specific syntax quirks
  • Lots more...

See Example Recipes for more in-depth examples. Or go straight to the Documentation.

Links

Install

From PyPI:

pip install pfst

From GitHub using pip:

pip install git+https://github.com/tom-pytel/pfst.git

From GitHub, after cloning for development:

pip install -e .[dev]

Getting Started

Since pfst is built directly on Python's standard AST nodes, if you are familiar with those then you already know the FST node structure. Our focus on simple Pythonic operations means you can get up to speed quickly.

  1. Parse source
>>> import ast, fst  # pip install pfst, import fst

>>> a = fst.parse('def func(): pass  # comment')
  1. Modify via .f
>>> f = a.body[0].f

>>> f.returns = ast.Name('int')  # use nodes or text
>>> f.args.append('arg: int = 0')
>>> f.body.extend('call()  # call comment\n\nreturn arg')
>>> f.put_docstr("I'm a happy\nlittle docstring")
>>> f.body[1:1] = '\n'
  1. View formatted source
>>> print(f.src)
def func(arg: int = 0) -> int:
    """I'm a happy
    little docstring"""

    pass  # comment
    call()  # call comment

    return arg
  1. Verify AST synchronization
>>> print(ast.unparse(a))
def func(arg: int=0) -> int:
    """I'm a happy
    little docstring"""
    pass
    call()
    return arg

Beyond basic editing, pfst provides syntax-ordered traversal, scope symbol analysis, structural pattern matching and substitution, and a mechanism for reconciling external AST mutations with the formatted tree, preserving comments and layout wherever the structure still permits it.

Here is an example of more advanced substitution usage.

>>> from fst.match import *

>>> print(FST('i = j.k = a + b[c]').sub(Mexpr(ctx=Load), 'log(__FST_)', True).src)
i = log(j).k = log(a) + log(log(b)[log(c)])

TODO

  • Prescribed get / put slice from / to:

    • MatchClass.patterns+kwd_attrs:kwd_patterns with _pattern_args special slice container class
    • JoinedStr.values
    • TemplateStr.values
  • Put one to:

    • FormattedValue.conversion
    • FormattedValue.format_spec
    • Interpolation.str
    • Interpolation.conversion
    • Interpolation.format_spec
  • The aesthetics of multiline slice operation alignment are not concretized yet. The current alignment behavior basically just aligns, not necessarily always at the expected place. It should get more standard and controllable in the future.

  • Maybe allow non-slice individual expressionlike nodes to own comments (as opposed to only individual statementlikes and expressionlike slices), allowing them to be copied and put with these nodes. More direct comment manipulation functions.

  • Finish reconcile(). Proper comment handling, locations and deduplication. Make it use all slice operations to preserve more formatting.

  • Clean up typing, other code cleanups, API additions for real-world use, optimization, testing, bughunting, etc...

Trivia

The "F" in FST stands for "Fun".

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pfst-0.3.1-py3-none-any.whl (540.4 kB view details)

Uploaded Python 3

File details

Details for the file pfst-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: pfst-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 540.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2+

File hashes

Hashes for pfst-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ab3c2ef66c5c2f17b07c91f2c3008673ede4b968e3407b951f225e364e70add2
MD5 bca8a663e661ce58003a1a7cdcdd1134
BLAKE2b-256 51428006f72955aa0ddfb3fdde37bf94438e68526ff8be18b85c09a606db0f6c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page