fst.fst

Main FST module. Contains the FST class as well as drop-in replacement parse() and unparse() functions for their respective ast module counterparts.

def parse( source: str | bytes | ast.AST, filename: str = '<unknown>', mode: str = 'exec', *, type_comments: bool = False, feature_version: tuple[int, int] | None = None, **kwargs) -> ast.AST:

Executes ast.parse() and then adds FST nodes to the parsed tree. Drop-in replacement for ast.parse(). For parameters, see ast.parse(). Returned AST tree has added .f attribute at each node which accesses the parallel FST tree.

Parameters:

  • source: The python source to parse.
  • filename: ast.parse() parameter.
  • mode: Parse mode. Either one of the normal ast module parse modes 'exec', 'eval' or 'single', or an extended fst parse mode parameter which allows parsing things the ast module cannot, See fst.parsex.Mode.
  • type_comments: ast.parse() parameter.
  • feature_version: ast.parse() parameter.

Returns:

  • AST: Tree with an FST .f attribute added to each AST node.

Examples:

>>> import ast, fst
>>> a = fst.parse('if 1:\n  i = 2')
>>> type(a)
<class 'ast.Module'>
>>> a.f  # FST node
<Module ROOT 0,0..1,7>
>>> print(fst.dump(a, indent=2))
Module(
  body=[
    If(
      test=Constant(value=1),
      body=[
        Assign(
          targets=[
            Name(id='i', ctx=Store())],
          value=Constant(value=2))],
      orelse=[])],
  type_ignores=[])
>>> _ = a.f.dump()
Module - ROOT 0,0..1,7
  .body[1]
   0] If - 0,0..1,7
     .test Constant 1 - 0,3..0,4
     .body[1]
      0] Assign - 1,2..1,7
        .targets[1]
         0] Name 'i' Store - 1,2..1,3
        .value Constant 2 - 1,6..1,7
def unparse(ast_obj: ast.AST) -> str:

Returns the formatted source that is kept for this tree. Drop-in replacement for ast.unparse() If there is no FST information in the AST tree then just executes ast.unparse().

Parameters:

  • ast_obj: The AST to unparse.

Returns:

  • str: The unparsed source code, formatted if it came from FST.

Examples:

>>> import ast, fst
>>> a = fst.parse('''
... if 1: i = 1  # same line
... else:
...   j=2 # comment
... '''.strip())
>>> print(ast.unparse(a))
if 1:
    i = 1
else:
    j = 2
>>> print(fst.unparse(a))
if 1: i = 1  # same line
else:
  j=2 # comment
>>> a = ast.parse('i = 1')
>>> ast.unparse(a)
'i = 1'
>>> fst.unparse(a)  # also unparses regular AST
'i = 1'
def dump( node: ast.AST, annotate_fields: bool = True, include_attributes: bool = False, *, indent: int | str | None = None, show_empty: bool = True) -> str:

This function is a convenience function and only exists to make python version 3.13 and above ast.dump() output compatible on a default call with previous python versions (important for doctests). All arguments correspond to their respective ast.dump() arguments and show_empty is eaten on python versions below 3.13.

def castf(ast: ast.AST) -> FST:

Cast AST F, type-safe way to access ast.f when you know it exists. Intentionally fail with AttributeError if ast does not have .f.

Example:

>>> a = fst.parse('module')
>>> print(type(a))
<class 'ast.Module'>
>>> print(type(castf(a)))
<class 'fst.fst.FST'>
>>> castf(a) is a.f
True
>>> del a.f
>>> print(castf(a))
Traceback (most recent call last):
...
AttributeError: 'Module' object has no attribute 'f'
def gastf(ast: ast.AST) -> FST | None:

Get AST F, type-safe way to get ast.f when it may or may not exist. Returns None if ast does not have an .f.

Example:

>>> a = fst.parse('module')
>>> print(type(a))
<class 'ast.Module'>
>>> print(type(gastf(a)))
<class 'fst.fst.FST'>
>>> gastf(a) is a.f
True
>>> del a.f
>>> print(gastf(a))
None
class FST:

Class which maintains structure and formatted source code for an AST tree. An instance of this class is added to each AST node in a tree. It provides format-preserving operations as well as ability to navigate the tree in any direction.

FST( src_or_ast_or_fst: FST | ast.AST | str | list[str] = '', mode: Union[FST, list[str], Literal['all', 'strict', 'exec', 'eval', 'single', 'stmts', 'stmt', 'ExceptHandler', '_ExceptHandlers', 'match_case', '_match_cases', 'expr', 'expr_all', 'expr_arglike', 'expr_slice', 'Tuple_elt', 'Tuple', '_Assign_targets', '_decorator_list', '_arglike', '_arglikes', 'boolop', 'operator', 'unaryop', 'cmpop', 'comprehension', '_comprehensions', '_comprehension_ifs', 'arguments', 'arguments_lambda', 'arg', 'keyword', 'alias', '_aliases', 'Import_name', '_Import_names', 'ImportFrom_name', '_ImportFrom_names', 'withitem', '_withitems', 'pattern', 'type_param', '_type_params'], str, type[ast.AST], Literal[False], NoneType] = None, pfield: Union[fst.common.astfield, Literal[False], NoneType] = False, /, **kwargs)

Create a new individual FST node or full tree. The main way to use this constructor is as a shortcut for FST.fromsrc(), FST.fromast() or FST.as_(), the usage is:

FST(src_or_ast_or_fst, mode=None)

This will create an FST from either an AST or source code in the form of a string or list of lines, or it will attempt to coerce an existing FST to the type you want specified by mode.

WARNING! In normal usage, you should always leave pfield at its default as anything else will select other modes of opertation for this constructor.

Parameters:

  • src_or_ast_or_fst: Source code or an AST or FST node.
  • mode: See fst.parsex.Mode. Has the following meanings when the src_or_ast_or_fst parameter is:
    • src: If mode is None then we will try to guess what the source is and parse it accordingly. If specified then will attempt to parse this type and if fails then the exception will propagate. If guessing then it may take more than one attempt so it is always better to specify what you expect to get if you know it.
    • AST: If mode is None then defaults to the type of the AST, which will always succeed in generating valid source unless the AST itself is in an invalid state (like an empty Set). Anything else you pass here will be passed on as the mode to fromast(). In which case if the AST is this then the FST tree is just build for it and otherwise coercion will be attempted to this type.
    • FST: mode should be specified and will be the type of node you want to coerce the FST you pass in to. If you leave it at the default None then you will just get either the same FST or a copy of it, depending on the copy kwarg.
  • kwargs: options if coercing from another FST or extra parameters, mostly for internal use documented below. One extra parameter relevant to this use case is:
    • copy: If coercing from another FST, this parameter is what will be passed to as_(). It defaults to True here so that FST(other_FST) works in the same way as list(other_list), meaning it doesn't alter the source object, which is the opposite of what the default for the as_() function is.

Examples:

>>> FST('def f(): call()\ndef g(): pass')
<Module ROOT 0,0..1,13>

Minimal node representation is created by default, so stmt instead of mod if possible and expr instead of stmt.

>>> FST('def f(): call()')
<FunctionDef ROOT 0,0..0,15>
>>> FST('call()')
<Call ROOT 0,0..0,6>

You can force a module.

>>> FST('call()', 'exec')
<Module ROOT 0,0..0,6>

Can parse things not normally parsable.

>>> FST('start:stop:step')
<Slice ROOT 0,0..0,15>

FST guesses what you want in this case but doesn't always get it right.

>>> FST('start:stop')
<AnnAssign ROOT 0,0..0,10>

So you can tell it. This is also usually faster to parse than if fst has to guess and try parsing something that it is not before finding out what it is, so if this is important to you then always pass the type you expect to get when you can.

>>> FST('start:stop', 'Slice')
<Slice ROOT 0,0..0,10>

You can tell it to only parse things which are top-level parsable by ast.parse().

>>> try:
...     FST('start:stop:step', 'strict')
... except Exception as exc:
...     print(str(exc))
invalid syntax (<FST>, line 1)

You can also pass an AST and the source will be generated from it.

>>> FST(ast.Slice(Constant(1), Constant(2), Constant(3)))
<Slice ROOT 0,0..0,5>
>>> print(_.src)
1:2:3

Coercion is straightforward.

>>> f = FST('expr')
>>> f, f.src
(<Name ROOT 0,0..0,4>, 'expr')
>>> g = FST(f, 'stmt')
>>> g, g.src
(<Expr ROOT 0,0..0,4>, 'expr')
>>> g = FST(f, 'Name')
>>> g, g.src, g is f
(<Name ROOT 0,0..0,4>, 'expr', False)

Turn off copy to return the same node if it is the type you are requesting.

>>> g = FST(f, 'Name', copy=False)
>>> g, g.src, g is f
(<Name ROOT 0,0..0,4>, 'expr', True)

But still coerces if is not.

>>> g = FST(f, 'arg', copy=False)
>>> g, g.src, g is f
(<arg ROOT 0,0..0,4>, 'expr', False)

Same options apply as to all other operations.

>>> FST(FST('[]'), 'Set').src  # will give invalid empty Set node
'{}'
>>> FST(FST('[]'), 'Set', norm=True).src
'{*()}'

The other forms of this function are meant for internal use, their parameters are below for reference just in case:

Parameters:

  • src_or_ast_or_fst: AST node for FST or source code in the form of a str or a list of lines. If an AST then will be processed differently depending on if creating child node, top level node or using this as a shortcut for a full fromsrc() or fromast(). If left as empty default then just creates a new empty Module.
  • mode: Is really mode_or_lines_or_parent. Parent node for this child node or lines for a root node creating a new tree. If pfield is False then this is a shortcut to create a full tree from an AST node or source provided in src_or_ast_or_fst.
  • pfield: fst.common.astfield indication position in parent of this node. If provided then creating a simple child node and it is created with the self.parent set to mode node and self.pfield set to this. If None then it means the creation of a full new FST tree and this is the root node with mode providing the source. If False then this is a shortcut for FST.fromsrc() or FST.fromast().
  • kwargs: Contextual parameters:
    • from_: If this is provided then it must be an FST node from which this node is being created. This allows to copy parse parameters and already determined default indentation. Does not have to be root.
    • parse_params: A dict with values for filename, type_comments and feature_version which will be used for any AST reparse done on this tree. Only valid when creating a root node.
    • indent: Indentation string to use as default indentation. If not provided and not gotten from from_ then indentation will be inferred from source. Only valid when creating a root node.
    • filename, type_comments and feature_version: If creating from an AST or source only then these are the parameteres passed to the respective .fromsrc() or .fromast() functions. Only valid when pfield is False, meaning a shortcut use of FST().
    • lcopy: Whether to copy lines of source on root node create or just use what is passed in, which in this case must be a list of fst.astutil.bistr and this node takes ownership of the list.
    • tmake: Whether to do _make_fst_tree() on self or not. Turning this off can be used to create a root node for a specific existing FST tree. Useful for optimizing some operations.
a: ast.AST | None

The actual AST node. Will be set to None for FST nodes which were deleted or otherwise invalidated so can be checked for that to see if the FST is still alive (while walking and modifying for example).

parent: FST | None

Parent FST node, None in root node.

pfield: fst.common.astfield | None

The fst.common.astfield location of this node in the parent, None in root node.

parse_params: Mapping[str, Any]

The parameters to use for any ast.parse() (filename, type_comments, feature_version). Exists mostly for passing filename and future-proofing.

indent: str

The default single level block indentation string for this tree when not available from context.

is_FST: bool = True

Allows to quickly differentiate between actual FST nodes vs. views or locations.

is_alive: bool

True if the node is part of a tree, False if has been replaced or removed.

is_root: bool

True for the root node, False otherwise.

root: FST

Root node of the tree this node belongs to.

lines: list[str]

Whole lines of this node from the RAW SOURCE, without any dedentation, may also contain parts of enclosing nodes. Will have indentation as it appears in the top level source if multiple lines. If gotten at root then the entire source is returned, which may extend beyond the location of the top-level node (mostly for statements which may have leading / trailing comments or empty lines).

A valid list of strings is always returned, even for nodes which can never have source like Load, etc... The lines list returned is always a copy so safe to modify.

WARNING! You get just the text that is there so you will get unparsable source if you get for example a string Constant from the values field of a JoinedStr, or a format_spec.

src: str

Source code of this node from the RAW SOURCE clipped out as a single string, without any dedentation. Will have indentation as it appears in the top level source if multiple lines. If gotten at root then the entire source is returned, regardless of whether the actual top level node location includes it or not.

A string is always returned, even for nodes which can never have source like Load, etc...

WARNING! You get just the text that is there so you will get unparsable source if you get for example a string Constant from the values field of a JoinedStr, or a format_spec.

has_own_loc: bool

True when the node has its own location which comes directly from AST lineno and other location fields. Otherwise False if no loc or loc is calculated.

whole_loc: fst.common.fstloc

Whole source location, from (0, 0) to end of source. Works from any node (not just root).

loc: fst.common.fstloc | None

Zero based character indexed location of node (may not be entire location if node has decorators). Not all nodes have locations (like expr_context). Other nodes which normally don't have locations like arguments or most operators have this location calculated from their children or source.

Note: Empty arguments do NOT have a location even though the AST exists, while non-empty arguments have a calculated location.

bloc: fst.common.fstloc | None

Bounding location of node. For non-statements or non-block statements is same as loc. For block statements will include any leading decorators and a trailing line comment on the last child (or self if no children).

Examples:

>>> f = FST('''
... @decorator
... def func():
...     pass  # comment
... '''.strip())
>>> f.bloc
fstloc(0, 0, 2, 19)
>>> f.loc
fstloc(1, 0, 2, 8)
>>> f = FST('stmt  # comment')

Non-block statement doesn't include line comment.

>>> f.bloc
fstloc(0, 0, 0, 4)
ln: int

Line number of the first line of this node (0 based).

col: int

CHARACTER index of the start of this node (0 based).

end_ln: int

Line number of the LAST LINE of this node (0 based).

end_col: int

CHARACTER index one past the end of this node (0 based).

bln: int

Line number of the first line of this node or the first decorator if present (0 based).

bcol: int

CHARACTER column of this node or the first decorator if present (0 based).

bend_ln: int

Line number of the last line of this node.

bend_col: int

CHARACTER column of the end of the last line of this node, past a trailing line comment on last child if self is a block statement.

lineno: int

AST-style line number of the first line of this node (1 based), available for all nodes which have loc (otherwise None).

col_offset: int

AST-style BYTE index of the start of this node (0 based), available for all nodes which have loc (otherwise None).

end_lineno: int

AST-style line number of the LAST LINE of this node (1 based), available for all nodes which have loc (otherwise None).

end_col_offset: int

AST-style BYTE index one past the end of this node (0 based), available for all nodes which have loc (otherwise None).

has_docstr: bool

Indicates whether this node has a docstring. Can only be True for FunctionDef, AsyncFunctionDef, ClassDef or Module. For quick use as start index.

@staticmethod
def fromsrc( src: str | list[str], mode: Union[Literal['all', 'strict', 'exec', 'eval', 'single', 'stmts', 'stmt', 'ExceptHandler', '_ExceptHandlers', 'match_case', '_match_cases', 'expr', 'expr_all', 'expr_arglike', 'expr_slice', 'Tuple_elt', 'Tuple', '_Assign_targets', '_decorator_list', '_arglike', '_arglikes', 'boolop', 'operator', 'unaryop', 'cmpop', 'comprehension', '_comprehensions', '_comprehension_ifs', 'arguments', 'arguments_lambda', 'arg', 'keyword', 'alias', '_aliases', 'Import_name', '_Import_names', 'ImportFrom_name', '_ImportFrom_names', 'withitem', '_withitems', 'pattern', 'type_param', '_type_params'], str, type[ast.AST]] = 'exec', *, filename: str = '<FST>', type_comments: bool = False, feature_version: tuple[int, int] | None = None) -> FST:

Parse and create a new FST tree from source, preserving the original source and locations.

WARNING! The type_comments parameter is False by default and no guarantees are made if you turn it on. It is provided just in case but really shouldn't be used as fst takes care of comments anyway, this just turns on storing the comments in the AST nodes which may cause all sorts of madness like duplication on unparse or nodes failing to reparse to themselves on operations.

Parameters:

  • src: The source to parse as a single str or list of individual line strings (without newlines).
  • mode: Parse mode, extended ast.parse() parameter, see fst.parsex.Mode. If you know what kind of node you are expecting then it is always better to pass in this parameter because otherwise several different parse attempts may be made to try to find the type.
  • filename: ast.parse() parameter.
  • type_comments: ast.parse() parameter. Don't use this, see warning above.
  • feature_version: ast.parse() parameter. Don't use this either, here just in case.

Returns:

  • FST: The parsed tree with .f attributes added to each AST node for FST access.

Examples:

>>> _ = FST.fromsrc('var').dump()
Module - ROOT 0,0..0,3
  .body[1]
   0] Expr - 0,0..0,3
     .value Name 'var' Load - 0,0..0,3
>>> _ = FST.fromsrc('var', mode='stmt').dump()
Expr - ROOT 0,0..0,3
  .value Name 'var' Load - 0,0..0,3
>>> _ = FST.fromsrc('var', mode='expr').dump()
Name 'var' Load - ROOT 0,0..0,3
>>> _ = FST.fromsrc('except Exception: pass', 'ExceptHandler').dump()
ExceptHandler - ROOT 0,0..0,22
  .type Name 'Exception' Load - 0,7..0,16
  .body[1]
   0] Pass - 0,18..0,22
>>> _ = FST.fromsrc('case f(a=1): pass', 'match_case').dump()
match_case - ROOT 0,0..0,17
  .pattern MatchClass - 0,5..0,11
    .cls Name 'f' Load - 0,5..0,6
    .kwd_attrs[1]
     0] 'a'
    .kwd_patterns[1]
     0] MatchValue - 0,9..0,10
       .value Constant 1 - 0,9..0,10
  .body[1]
   0] Pass - 0,13..0,17
>>> import ast
>>> _ = FST.fromsrc('a:b', ast.Slice).dump()
Slice - ROOT 0,0..0,3
  .lower Name 'a' Load - 0,0..0,1
  .upper Name 'b' Load - 0,2..0,3
@staticmethod
def fromast( ast: ast.AST, mode: Union[Literal['all', 'strict', 'exec', 'eval', 'single', 'stmts', 'stmt', 'ExceptHandler', '_ExceptHandlers', 'match_case', '_match_cases', 'expr', 'expr_all', 'expr_arglike', 'expr_slice', 'Tuple_elt', 'Tuple', '_Assign_targets', '_decorator_list', '_arglike', '_arglikes', 'boolop', 'operator', 'unaryop', 'cmpop', 'comprehension', '_comprehensions', '_comprehension_ifs', 'arguments', 'arguments_lambda', 'arg', 'keyword', 'alias', '_aliases', 'Import_name', '_Import_names', 'ImportFrom_name', '_ImportFrom_names', 'withitem', '_withitems', 'pattern', 'type_param', '_type_params'], str, type[ast.AST], NoneType] = None, *, coerce: bool = True, filename: str = '<FST>', type_comments: bool = False, feature_version: tuple[int, int] | None = None) -> FST:

Convert an AST tree to a full FST tree. This can take the existing node and just create the FST nodes for it (unparsing to get source code). Or if mode is specified, will try to coerce the AST node to this type and create the FST tree for that.

The passed ast node is not actually consumed. It is unparsed and reparsed to a new AST since we need to make sure the locations are all correct.

WARNING! The type_comments parameter is False by default and no guarantees are made if you turn it on. It is provided just in case but really shouldn't be used as fst takes care of comments anyway, this just turns on storing the comments in the AST nodes which may cause all sorts of madness like duplication on unparse or nodes failing to reparse to themselves on operations.

Parameters:

  • ast: The root AST node. This is NOT consumed and will NOT become the actual AST node of the FST tree.
  • mode: Parse mode to enable coercion, see fst.parsex.Mode. If None then will just use the given ast type and make an FST using that type.
  • coerce: This exists to allow you to turn OFF coercion for some reason as by default coerce is attempted.
  • filename: ast.parse() parameter.
  • type_comments: ast.parse() parameter. Don't use this, see warning above.
  • feature_version: ast.parse() parameter. Don't use this either, here just in case.

Returns:

  • FST: The augmented tree with .f attributes added to each AST node for FST access.

Examples:

>>> import ast
>>> from ast import Assign, Constant, List, Name, Slice
>>> _ = FST.fromast(Assign(targets=[Name(id='var')],
...                        value=Constant(value=123))).dump('stmt')
0: var = 123
Assign - ROOT 0,0..0,9
  .targets[1]
   0] Name 'var' Store - 0,0..0,3
  .value Constant 123 - 0,6..0,9
>>> _ = FST.fromast(Slice(lower=Constant(value=1),
...                       step=Name(id='step'))).dump('node')
0: 1::step
Slice - ROOT 0,0..0,7
0: 1
  .lower Constant 1 - 0,0..0,1
0:    step
  .step Name 'step' Load - 0,3..0,7
>>> _ = FST.fromast(List(elts=[Name(id='var')]),
...                 'pattern', coerce=True).dump('stmt')
0: [var]
MatchSequence - ROOT 0,0..0,5
  .patterns[1]
   0] MatchAs - 0,1..0,4
     .name 'var'
>>> _ = FST.fromast(List(elts=[Name(id='var')]),
...                 'pattern', coerce=False).dump('stmt')
Traceback (most recent call last):
...
fst.NodeError: expecting pattern, got List, coerce disabled
>>> _ = FST.fromast(ast.parse('if 1:\n    j = 5')).dump('stmt')
Module - ROOT 0,0..1,9
  .body[1]
0: if 1:
   0] If - 0,0..1,9
     .test Constant 1 - 0,3..0,4
     .body[1]
1:     j = 5
      0] Assign - 1,4..1,9
        .targets[1]
         0] Name 'j' Store - 1,4..1,5
        .value Constant 5 - 1,8..1,9
@staticmethod
def get_options() -> dict[str, typing.Any]:

Get a dictionary of ALL global options. These are the same options that can be passed to operations and this function returns their global defaults which are used when those options are not passed to operations or if they are passed with a value of None

Returns:

  • {option: value, ...}: Dictionary of all global default options.

Examples:

>>> from pprint import pp
>>> pp(FST.get_options())
{'raw': False,
 'trivia': True,
 'coerce': True,
 'elif_': True,
 'pep8space': True,
 'docstr': True,
 'pars': 'auto',
 'pars_walrus': False,
 'pars_arglike': True,
 'norm': False,
 'norm_self': None,
 'norm_get': None,
 'set_norm': 'star',
 'op_side': 'left',
 'args_as': None}
@staticmethod
def get_option(option: str, options: Mapping[str, Any] = {}) -> object:

Get a single option from options dict or global default if option not in dict or is None there. For a list of options used see options().

Parameters:

  • option: Name of option to get.
  • options: Dictionary which may or may not contain the requested option.

Returns:

  • object: The option value from the passed options dict, if passed and not None there, else the global default value for option.

Examples:

>>> FST.get_option('pars')
'auto'
>>> FST.get_option('pars', {'pars': True})
True
@staticmethod
def set_options(**options) -> dict[str, typing.Any]:

Set global defaults for options parameters.

Parameters:

  • options: Names / values of parameters to set. These can also be passed to various methods to override the defaults set here for those individual operations, see options().

Returns:

  • old_options: dict of previous values of changed parameters, reset with set_options(**old_options).

Examples:

>>> FST.get_option('pars')
'auto'
>>> FST.set_options(pars=False)
{'pars': 'auto'}
>>> FST.get_option('pars')
False
>>> FST.set_options(**{'pars': 'auto'})
{'pars': False}
>>> FST.get_option('pars')
'auto'
>>> FST.set_options(pars=True, raw=True, docstr=False)
{'pars': 'auto', 'raw': False, 'docstr': True}
@staticmethod
@contextmanager
def options(**options) -> Iterator[dict[str, Any]]:

Context manager to temporarily set specified global options defaults for a group of operations.

WARNING! Only the options specified in the call to this function will be returned to their original values when the context manager exits. Any other global options changed inside the context block will continue to have those values on context manager exit.

Options:

  • raw: When to do raw source operations. This may result in more nodes changed than just the targeted one(s).
    • False: Do not do raw source operations. DEFAULT
    • True: Only do raw source operations.
    • 'auto': Only do raw source operations if the normal operation fails in a way that raw might not.
  • trivia: What comments and empty lines to copy / delete / overwrite when doing operations on elements which may have leading or trailing comments and / or empty lines. These are the values as interpreted WHEN SET GLOBALLY. If passed as a parameter to a function then if a non-tuple is passed then that becomes the leading trivia parameter. If a tuple is passed then both the leading and trailing from the tuple are used, unless they are None in which case the global value for that particular parameter is used.
    • False: Same as (False, 'line').
    • True: Same as ('block', 'line'). DEFAULT
    • 'all': Same as ('all', 'line').
    • 'block': Same as ('block', 'line').
    • (leading, trailing): Tuple specifying individual behavior for leading and trailing trivia. The text options can also have a suffix of the form '+/-[#]', meaning plus or minus an optional integer which indicates to include this number of leading / trailing empty lines in the trivia. The values for each element of the tuple can be:
      • False: Don't copy / delete / overwrite any trivia.
      • True: Means 'block' for leading trivia, 'line' for trailing.
      • 'all[+/-[#]]': Copy / delete / overwrite all leading or trailing comments regardless of if they are contiguous or not.
      • 'block[+/-[#]]': Copy / delete / overwrite a single contiguous leading or trailing block of comments where an empty line ends the block.
      • 'line': Only valid for trailing trivia, means just the comment on the last line of the element. Except for block statements where the last line is a child node where that comment belongs to the child regardless of this parameter and so will be copied / deleted / overwritten along with the block.
      • int: A specific line number (starting at 0) indicating the first or last line that can be copied / deleted / overwritten. If not interrupted by other code, will always return from / to this line inclusive. For trailing trivia, if this number is the last line of the element (where the line comment resides) then it will include that tail line comment. If it is BEFORE that line then the line comment will not be included.
  • coerce: Whether to allow coercion between compatible AST / FST node types on put. For example allow put a non-slice expr as a slice to something that expects a slice of exprs or allowing use of arg where arguments is expected.
    • False: Do not allow node type coercion, meant for strict type control.
    • True: Allow coercion between similar types. DEFAULT
  • elif_: How to handle lone If statements as the only statements in an If statement orelse field.
    • False: Always put as a standalone If statement on put.
    • True: If putting a single If statement to an orelse field of a parent If statement then put it as an elif. DEFAULT
  • pep8space: Preceding and trailing empty lines for function and class definitions.
    • False: No empty lines.
    • True: Two empty lines at module scope and one empty line in other scopes. DEFAULT
    • 1: One empty line in all scopes.
  • docstr: Which docstrings are indentable / dedentable.
    • False: None.
    • True: All Expr string constants (as they serve no other coding purpose). DEFAULT
    • 'strict': Only string constants in expected docstring positions (functions, classes and top of module).
  • pars: How parentheses are handled, can be False, True or 'auto'. This is for individual element operations, slice operations ignore this for the most part as parentheses usually cannot be removed or may need to be added to keep the slices usable. Raw puts generally do not have parentheses added or removed automatically, except maybe removed according to this from the destination node if putting to a node instead of a pure location. See below for pars behavior matrix.
    • False: Parentheses are not MODIFIED, doesn't mean remove all parentheses. Not copied with nodes or removed on put from destination or source. Using incorrectly can result in invalid trees.
    • True: Parentheses are copied with nodes, added to copies if needed and not present and removed from destination on put if not needed there (but not removed from source).
    • 'auto': Same as True except they are not copied with nodes and possibly removed from source on put if not needed (removed from destination first if needed and present on both). DEFAULT
  • pars_walrus: Whether to ADD parentheses to top level cut / copied NamedExpr nodes or not. If parentheses were already copied due to pars=True then setting this to False will not remove them. This is more of an aesthetic option as fst can deal with unparenthesized walruses at root level.
    • True: Parenthesize cut / copied NamedExpr walrus expressions.
    • False: Do not parenthesize cut / copied NamedExpr walrus expressions. DEFAULT
    • None: Parenthesize according to the pars option (True and 'auto' parenthesize, False does not).
  • pars_arglike: Whether to ADD parentheses to argumentlike-only expressions (*not a, *b or c) when cut / copied either as single element or as part of a slice. If parentheses were already present then setting this to False will not remove them. Unlike pars_walrus this is NOT mostly an aesthetic option as unparenthesized arglike-only expressions are invalid everywhere except in Call.args, ClassDef.bases or an unparenthesized Subscript.slice Tuple.
    • True: Parenthesize cut / copied argumentlike expressions. DEFAULT
    • False: Do not parenthesize cut / copied argumentlike expressions.
    • None: Parenthesize according to the pars option (True and 'auto' parenthesize, False does not).
  • norm: Default normalize option for return from get() functions and self target. Determines how ASTs which would otherwise be invalid because of an operation are handled. Mostly how zero or sometimes one-length sequences which normally cannot be zero or one length are left or returned, e.g. zero-length Set. This option can be overridden individually for the two cases of norm_self (target) and norm_get (return from any get operation).
    • False: Allow the AST to go to an unsupported length or state and become invalid. A Set will result in empty curlies which reparse to a Dict. A MatchOr can go to 1 or zero length. Other AST types can also go to zero length. Useful for easier editing. DEFAULT
    • True: Do not allow the AST to go to an unsupported length or state. Can result in an exception being thrown if no alternative exists. A Set which results in zero length gets converted to {*()}.
    • str: In some cases an alternate representation can be specified instead of just True, e.g. 'call' for Set operations.
  • norm_self: Override for norm which only applies to a target self on which an operation is being carried out. If this is None then norm is used.
  • norm_get: Override for norm which only applies to the return value from any get operation. If this is None then norm is used.
  • set_norm: The alternate representation for an empty Set normalization by norm. This can also be set to False to disable normalization for all operations on a Set (unless one of the norm options is set to one of these string modes).
    • 'star': Starred sequence {*()} returned or used for empty self. DEFAULT
    • 'call': set() call returned and used for empty self.
    • False: No Set normalization regardless of norm or norm_* options, just leave or return an invalid Set object.
  • op_side: When doing slice operations on a BoolOp or a Compare it may be necessary to specify which side operator is to be deleted or inserted before or after. This can take the values of 'left' or 'right' and specifies which side operator to delete for delete operations. For insert operations this specifies whether to insert before an operator 'left' or operand 'right', roughly translating to which side operator is treated as part of the operator+operand unit. This option is treated as a hint and may be overridden by source code passed to the slice operation or slice position constraints, it will never raise an error if set incompatible with what the operation needs. When inserting into a Compare a non-global op option may be necessary to specify extra missing operator to add if a dangling operator is not passed in the source to insert.
    • 'left': Delete preceding operator on left side of slice or insert before preceding operator. DEFAULT
    • 'right': Delete trailing operator on right side of slice or insert after preceding operator.
  • args_as: Conversion for argument types on argument node slice operations. This is mostly meant for per-call use but is a global option in order to allow with options(args_as=?): .... When used on a slice get it converts the gotten slice. When used on a slice put, it converts the slice being put before the attempted put.
    • 'pos': Convert all arguments to posonlyargs if possible, if not then error. If vararg or kwarg present then will error.
    • 'arg': Convert all arguments to args if possible, if not then error. A vararg is allowed but if present then will prevent any present kwonlyargs from being converted and in this case will error. If kwarg is present then will error.
    • 'kw': Convert all arguments to kwonlyargs if possible, if not then error. A kwarg is allowed and will not prevent any conversion as it is always follows all other arguments. If vararg is present then will error.
    • 'arg_only': Same as arg except does not allow a vararg and if present will error.
    • 'kw_only': Same as kw except does not allow a kwarg and if present will error.
    • 'pos_maybe': Attempt to convert all arguments to posonlyargs. args can always be converted but if kwonlyargs cannot be because of a vararg or default value incompatibilities then they will not be converted and there will not be an error. kwarg is left in place.
    • 'arg_maybe': Attempt to convert all arguments to args. posonlyargs can always be converted but if kwonlyargs cannot be because of a vararg or default value incompatibilities then they will not be converted and there will not be an error. kwarg is left in place.
    • 'kw_maybe': Attempt to convert all arguments to kwonlyargs. If vararg is present the posonlyargs and args are not converted, but posonlyargs will be converted in this case to args. kwarg is left in place.

Note: pars behavior:

                                                            False      True    'auto'
Copy pars from source on copy / cut:                         no       yes        no
Add pars needed for parsability to copy:                     no       yes       yes
Remove unneeded pars from destination on put:                no       yes       yes
Remove unneeded pars from source on put:                     no        no       yes
Add pars needed for parse / precedence to source on put:     no       yes       yes

Note: trivia text suffix behavior:

  • '+[#]' On copy and delete an extra number of lines after any comments specified by the #. If no number is specified and just a '+' then all empty lines will be copied / deleted.
  • '-[#]' On delete but not copy, an extra number of lines after any comments specified by the #. If no number is specified and just a '-' then all empty lines will be deleted.

Examples:

>>> print(FST.get_option('pars'), FST.get_option('elif_'))
auto True
>>> with FST.options(pars=False, elif_=False):
...     print(FST.get_option('pars'), FST.get_option('elif_'))
False False
>>> print(FST.get_option('pars'), FST.get_option('elif_'))
auto True
def as_( self, mode: Union[Literal['all', 'strict', 'exec', 'eval', 'single', 'stmts', 'stmt', 'ExceptHandler', '_ExceptHandlers', 'match_case', '_match_cases', 'expr', 'expr_all', 'expr_arglike', 'expr_slice', 'Tuple_elt', 'Tuple', '_Assign_targets', '_decorator_list', '_arglike', '_arglikes', 'boolop', 'operator', 'unaryop', 'cmpop', 'comprehension', '_comprehensions', '_comprehension_ifs', 'arguments', 'arguments_lambda', 'arg', 'keyword', 'alias', '_aliases', 'Import_name', '_Import_names', 'ImportFrom_name', '_ImportFrom_names', 'withitem', '_withitems', 'pattern', 'type_param', '_type_params'], str, type[ast.AST], NoneType] = None, copy: bool = False, **options) -> FST:

Attempt to coerce self to the type of node given by mode. If self is already the requested type of node at root level then will do nothing and return self. If is not at root level then will copy the node first and then attempt to coerce.

If self is not the type requested then will attempt to coerce, which is a destructive operation to the original node (self) even if it fails, rendering self unusable. For this reason the copy parameter allows you to specify that self must be copied first before coerce is attempted, leaving the original node intact. Note that a copy is always made regardless if self is not a root node so the original node will be unharmed in that case.

Note: If self is a subclass of the type you request then it counts as a match and self is returned unchanged, e.g. Assign and you request a stmt.

Parameters:

  • mode: The type of node you want, None means no coerce and just return self, see fst.parsex.Mode.
  • copy: This only matters if you are calling this function on a root node, in which case a copy is made before coerce is attempted in order to make sure the node you are calling remains valid.
  • options: See options(). These same options will be used for both a possible copy and for coercion.

Returns:

  • FST: Coerced node.

Examples:

>>> f = FST('expr')
>>> f
<Name ROOT 0,0..0,4>
>>> (f := f.as_('stmt'))
<Expr ROOT 0,0..0,4>
>>> f.as_('exec')
<Module ROOT 0,0..0,4>
>>> _ = FST('if a if b', '_comprehension_ifs').as_(expr).dump()
Tuple - ROOT 0,0..0,4
  .elts[2]
   0] Name 'a' Load - 0,0..0,1
   1] Name 'b' Load - 0,3..0,4
  .ctx Load
>>> _ = FST('case [a, cls(b)]: pass').pattern.as_(expr).dump()
List - ROOT 0,0..0,11
  .elts[2]
   0] Name 'a' Load - 0,1..0,2
   1] Call - 0,4..0,10
     .func Name 'cls' Load - 0,4..0,7
     .args[1]
      0] Name 'b' Load - 0,8..0,9
  .ctx Load
def reparse(self) -> FST:

Force a reparse of this node to synchronize the AST tree with the source in case the source was changed with non-native or non-synchronous operations such as put_src() with only offset. Can use on any node, not just root.

Returns:

  • self: New self after reparse, if possible, otherwise None if could not be found. May also be another neighbor node if the source of self was structurally changed too much. If used on root node then self will not change.

Examples:

>>> f = FST('f"{expr}"')
>>> _ = f.dump('stmt', loc=False)  # loc=False because of py < 3.12
0: f"{expr}"
JoinedStr - ROOT
  .values[1]
   0] FormattedValue
     .value Name 'expr' Load
     .conversion -1
>>> f.put_src('=', 0, 7, 0, 7, 'offset')
(0, 8)
>>> _ = f.dump('stmt', loc=False)
0: f"{expr=}"
JoinedStr - ROOT
  .values[1]
   0] FormattedValue
     .value Name 'expr' Load
     .conversion -1
>>> bool(f.verify(raise_=False))
False
>>> f = f.reparse()
>>> bool(f.verify(raise_=False))
True
>>> _ = f.dump('stmt', loc=False)
0: f"{expr=}"
JoinedStr - ROOT
  .values[2]
   0] Constant 'expr='
   1] FormattedValue
     .value Name 'expr' Load
     .conversion 114
def verify( self, mode: Union[Literal['all', 'strict', 'exec', 'eval', 'single', 'stmts', 'stmt', 'ExceptHandler', '_ExceptHandlers', 'match_case', '_match_cases', 'expr', 'expr_all', 'expr_arglike', 'expr_slice', 'Tuple_elt', 'Tuple', '_Assign_targets', '_decorator_list', '_arglike', '_arglikes', 'boolop', 'operator', 'unaryop', 'cmpop', 'comprehension', '_comprehensions', '_comprehension_ifs', 'arguments', 'arguments_lambda', 'arg', 'keyword', 'alias', '_aliases', 'Import_name', '_Import_names', 'ImportFrom_name', '_ImportFrom_names', 'withitem', '_withitems', 'pattern', 'type_param', '_type_params'], str, type[ast.AST], NoneType] = None, reparse: bool = True, *, locs: bool = True, ctx: bool = True, raise_: bool = True) -> FST | None:

Sanity check. Walk the tree and make sure all ASTs have corresponding FST nodes with valid parent / child links, then (optionally) reparse source and make sure parsed tree matches currently stored tree (locations and everything). The reparse can only be carried out on root nodes but the link validation can be done on any level.

SPECIAL SLICEs like _decorator_list will verify ok with reparse but invalid nodes like an empty Set or block statements with empty bodies will not.

Parameters:

  • mode: Parse mode to use, otherwise if None then use the top level AST node type for the mode. Depending on how this is set will determine whether the verification is checking if is parsable by python ('exec' or 'strict' for example), or if the node itself is just in a valid state (None specifies this). See fst.parsex.Mode.
  • reparse: Whether to reparse the source and compare ASTs (including location). Otherwise the check is limited to a structure check that all children have FST nodes which are all liked correctly to their parents. reparse=True only allowed on root node.
  • locs: Whether to compare locations after reparse or not.
  • ctx: Whether to compare ctx nodes after reparse or not.
  • raise_: Whether to raise an exception on verify failed or return None.

Returns:

  • None on failure to verify (if not raise_), otherwise self.

Examples:

>>> FST('var = 123').verify()
<Assign ROOT 0,0..0,9>
>>> FST('a:b:c').verify()
<Slice ROOT 0,0..0,5>
>>> bool(FST('a:b:c').verify('exec', raise_=False))
False
>>> FST('if a if b').verify()
<_comprehension_ifs ROOT 0,0..0,9>
>>> bool(FST('if a if b').verify('strict', raise_=False))
False
>>> (f := FST('a + b')).put_src('-', 0, 2, 0, 3, action=None)
(0, 3)
>>> f.verify()
Traceback (most recent call last):
...
fst.astutil.WalkFail: child classes differ in BinOp.op, Sub vs. Add, locs ('?', '?', '?', '?') / ('?', '?', '?', '?')
def dump( self, src: Union[Literal['stmt', 'all'], <property object>, NoneType] = None, full: bool = False, *, expand: bool = False, indent: int = 2, list_indent: int | bool = 1, loc: bool = True, color: bool | None = None, out: Union[Literal['print', 'str', 'lines'], Callable[[<property object>], NoneType], TextIO] = 'print', eol: str | None = None) -> FST | str | list[str]:

Dump a representation of the tree to stdout or other TextIO or return as a str or list[str] of lines, or call a provided function once with each line of the output.

Parameters:

  • src: Either what level of source to show along with the nodes or a shorthand string which can specify almost all the formatting parameters as a string of characters.
    • 'stmt' or 'stmt+' means output statement source lines (including ExceptHandler and match_case) or top level source if level is below statement. The + means put all lines of source including non-coding ones so that whole source is shown.
    • 'node' or 'node+' means output source for each individual node. The + means the same as for 'stmt+'.
    • None does not output any source.
    • str: Can be a string for shortcut specification of source and flags by first letter, 's+feL' would be equivalent to .dump(src='stmt+', full=True, expand=True, loc=False).
      • 's' or 's+' means src='stmt' or src='stmt+'
      • 'S' same as 's+'
      • 'n' or 'n+' means src='node' or src='node+'
      • 'N' same as 'N+'
      • 'f' means full=True
      • 'e' means expand=True
      • 'i' means list_indent=indent
      • 'I' means list_indent=False
      • 'L' means loc=False
      • 'c' means color=True
      • 'C' means color=False
  • full: If True then will list all fields in nodes including empty ones, otherwise will exclude most empty fields.
  • expand: If False then the output is a nice compact representation. If True then it is ugly and wasteful.
  • indent: Indentation per level as an integer (number of spaces) or a string.
  • list_indent: Extra indentation for elements of lists as an integer or string (added to indent). If True then will be same as indent.
  • loc: Whether to dump locations of nodes or not.
  • color: True or False means whether to use ANSI color codes or not. If None then will autodetect, which can be overridden with environment variables FORCE_COLOR and NO_COLOR.
  • out: 'print' means print to stdout, 'lines' returns a list of lines and 'str' returns a whole string. A TextIO object here will use the write method for each line of output. Otherwise a Callable[[str], None] which is called for each line of output individually. If 'str' or 'lines' then that is returned, otherwise self is returned for chaining.
  • eol: What to put at the end of each text line, None means newline for a TextIO out and nothing for other types of out.

Returns:

  • str: If was requested with out='str', string of entire dump, lines ended with eol.
  • list[str]: If was requested with out='lines', list of lines ended wiith eol.
  • self: If out is anything else, for convenience.

Examples:

>>> f = FST('''
... if 1:
...     call(a=b, **c)
... '''.strip())
>>> _ = f.dump()
If - ROOT 0,0..1,18
  .test Constant 1 - 0,3..0,4
  .body[1]
   0] Expr - 1,4..1,18
     .value Call - 1,4..1,18
       .func Name 'call' Load - 1,4..1,8
       .keywords[2]
        0] keyword - 1,9..1,12
          .arg 'a'
          .value Name 'b' Load - 1,11..1,12
        1] keyword - 1,14..1,17
          .value Name 'c' Load - 1,16..1,17
>>> _ = f.dump(src='node', indent=3, list_indent=True)
0: if 1:
If - ROOT 0,0..1,18
0:    1
   .test Constant 1 - 0,3..0,4
   .body[1]
1:     call(a=b, **c)
      0] Expr - 1,4..1,18
         .value Call - 1,4..1,18
1:     call
            .func Name 'call' Load - 1,4..1,8
            .keywords[2]
1:          a=b
               0] keyword - 1,9..1,12
                  .arg 'a'
1:            b
                  .value Name 'b' Load - 1,11..1,12
1:               **c
               1] keyword - 1,14..1,17
1:                 c
                  .value Name 'c' Load - 1,16..1,17
>>> f.dump(out='str')[:64]
'If - ROOT 0,0..1,18\n  .test Constant 1 - 0,3..0,4\n  .body[1]\n   '
>>> from pprint import pp
>>> pp(f.dump('stmt', loc=False, out='lines'))
['0: if 1:',
 'If - ROOT',
 '  .test Constant 1',
 '  .body[1]',
 '1:     call(a=b, **c)',
 '   0] Expr',
 '     .value Call',
 "       .func Name 'call' Load",
 '       .keywords[2]',
 '        0] keyword',
 "          .arg 'a'",
 "          .value Name 'b' Load",
 '        1] keyword',
 "          .value Name 'c' Load"]
def mark(self) -> FST:

Create a checkpoint with data needed for a later reconcile(). After this function is used, no more FST-native modifications should be made to the tree until after the reconcile(). Any changes for the reconcile() to work must be exclusively in the AST tree. If any modifications are made using FST functions then the checkpoint will be invalidated and will need to be recreated if reconcile() is wanted after the modifications.

Returns:

  • self
def reconcile( self, trivia_ast_put: bool | str | int | tuple[bool | str | int | None, bool | str | int | None] | None = None, trivia_fst_put: bool | str | int | tuple[bool | str | int | None, bool | str | int | None] | None = None, trivia_fst_get: bool | str | int | tuple[bool | str | int | None, bool | str | int | None] | None = None, *, cleanup: bool = True, **options) -> FST:

Reconcile self with a previously marked version and return a new valid FST tree. This is meant for allowing non-FST modifications to an FST tree and later converting it to a valid FST tree to preserve as much formatting as possible and maybe continue operating in FST land. Only AST nodes from the original tree carry formatting information, so the more of those are replaced the more formatting is lost.

Note: When replacing the AST nodes, make sure you are replacing the nodes in the actual AST node fields, not the .a attribute in FST nodes, that won't do anything.

Disclaimer: This functionality is still experimental and unfinished so not all comments which should be may be preserved and others may be duplicated.

Parameters:

  • trivia_ast_put: The trivia option to use when putting an AST node. Since AST nodes have no trivia in practice this means how much of the existing trivia at the target node is deleted. None means use default.
  • trivia_fst_put: The trivia option to use when putting an FST node, which may or may not have attached trivia depending on the trivia_fst_get option. This option only specifies what happens to the target trivia so depending on if the node being put has any it means delete or replace. None means use default.
  • trivia_fst_get: The trivia option to use when getting FST nodes for put. This can happen due to nodes completely translated to a place they were not anywhere near or due to movement because of deletions or insertions into the body where the nodes live. None means use default.
  • cleanup: By default after a successful reconcile self is cleanued up and no more reconcile can be attempted on it. It can be left in place to continue working on it and reconcile() again by passing cleanup=False but this is not guaranteed to survive further reconcile development.
  • options: Only a few options are allowed for reconcile as others are managed during the process. The most useful ones are elif_ and pep8space. See options().

Returns:

  • FST: A new valid reconciled FST if possible.

Examples:

>>> f = FST('''
... @decorator  # something
... def function(a: int, b=2)->int:  # blah
...     return a+b  # return this
...
... def other_function(a, b):
...     return a - b  # return that
... '''.strip())
>>> f.mark()
<Module ROOT 0,0..5,31>
>>> f.a.body[0].returns = Name('float')  # pure AST
>>> f.a.body[0].args.args[0].annotation = Name('float')
>>> f.a.body[0].decorator_list[0] = FST('call_decorator(1, 2, 3)').a
>>> f.a.body[1].name = 'last_function'  # can change non-AST
>>> f.a.body[1].body[0] = f.a.body[0].body[0]  # AST from same FST tree
>>> other = FST('def first_function(a, b): return a * b  # yay!')
>>> f.a.body.insert(0, other.a)  # AST from other FST tree
>>> f = f.reconcile(pep8space=1)
>>> print('\n'.join(l or '.' for l in f.lines))  # print this way for doctest
def first_function(a, b): return a * b  # yay!
.
@call_decorator(1, 2, 3)  # something
def function(a: float, b=2)->float:  # blah
    return a+b  # return this
.
def last_function(a, b):
    return a+b  # return this
>>> f.mark()
<Module ROOT 0,0..7,29>
>>> body = f.a.body[1].body
>>> f.a.body[1] = FST('def f(): pass').a
>>> f.a.body[1].body = body
>>> f = f.reconcile(pep8space=1)
>>> print('\n'.join(l or '.' for l in f.lines))
def first_function(a, b): return a * b  # yay!
.
def f():
    return a+b  # return this
.
def last_function(a, b):
    return a+b  # return this
def __getitem__(self, idx: int | slice) -> fst.view.fstview | FST | str | None:

Get a single item or a slice view from the default field of self. This is just an access, not a cut or a copy, so if you want a copy you must explicitly do .copy() on the returned value.

Same as self.default_field[idx].

Note that fstview can also hold references to non-AST lists of items, so keep this in mind when dealing with return values which may be None or may not be FST nodes.

Parameters:

  • idx: The index or builtins.slice where to get the element(s) from.

Returns:

  • fstview | FST | str | None: Either a single FST node if accessing a single item or a new fstview view according to the slice passed. builtins.str can also be returned from a view of Global.names or None from a Dict.keys.

Examples:

>>> FST('[0, 1, 2, 3]')[1].src
'1'
>>> FST('[0, 1, 2, 3]')[:3]
<<List ROOT 0,0..0,12>.elts[:3] [<Constant 0,1..0,2>, <Constant 0,4..0,5>, <Constant 0,7..0,8>]>
>>> FST('[0, 1, 2, 3]')[:3].copy().src
'[0, 1, 2]'
>>> FST('[0, 1, 2, 3]')[-3:]
<<List ROOT 0,0..0,12>.elts[1:4] [<Constant 0,4..0,5>, <Constant 0,7..0,8>, <Constant 0,10..0,11>]>
>>> FST('def fun(): pass\nclass cls: pass\nvar = val').body[1]
<ClassDef 1,0..1,15>
>>> FST('global a, b, c').names
<<Global ROOT 0,0..0,14>.names ['a', 'b', 'c']>
>>> FST('global a, b, c')[1]
'b'
def __setitem__( self, idx: int | slice, code: Union[FST, ast.AST, list[str], str, NoneType]) -> None:

Set a single item or a slice view in the default field of self.

Same as self.default_field[idx] = code.

Note that fstview can also hold references to non-AST lists of items, so keep this in mind when assigning values.

Parameters:

  • idx: The index or builtins.slice where to put the element(s).

Examples:

>>> from fst import FST
>>> (f := FST('[0, 1, 2, 3]'))[1] = '4'; f.src
'[0, 4, 2, 3]'
>>> (f := FST('[0, 1, 2, 3]'))[:3] = '5'; f.src
'[5, 3]'
>>> (f := FST('[0, 1, 2, 3]'))[:3] = '[5]'; f.src
'[[5], 3]'
>>> (f := FST('[0, 1, 2, 3]'))[:3] = '5,'; f.src
'[5, 3]'
>>> (f := FST('[0, 1, 2, 3]'))[-3:] = '6'; f.src
'[0, 6]'
>>> (f := FST('[0, 1, 2, 3]'))[-3:] = '[6]'; f.src
'[0, [6]]'
>>> (f := FST('[0, 1, 2, 3]'))[:] = '7, 8'; f.src
'[7, 8]'
>>> (f := FST('[0, 1, 2, 3]'))[:] = '[7, 8]'; f.src
'[[7, 8]]'
>>> f = FST('[0, 1, 2, 3]')
>>> f[2:2] = f[1:3].copy()
>>> f.src
'[0, 1, 1, 2, 2, 3]'
def __delitem__(self, idx: int | slice) -> None:

Delete a single item or a slice from default field of self.

Note that fstview can also hold references to non-AST lists of items, so keep this in mind when assigning values.

Parameters:

  • idx: The index or builtins.slice to delete.

Examples:

>>> from fst import FST
>>> del (f := FST('[0, 1, 2, 3]'))[1]; f.src
'[0, 2, 3]'
>>> del (f := FST('[0, 1, 2, 3]'))[:3]; f.src
'[3]'
>>> del (f := FST('[0, 1, 2, 3]'))[-3:]; f.src
'[0]'
>>> del (f := FST('[0, 1, 2, 3]'))[:]; f.src
'[]'
def own_lines( self, whole: bool = True, *, docstr: Union[bool, Literal['strict'], NoneType] = None) -> list[str]:

Lines of this node copied out of the tree and dedented as if the node were copied out. Unlike the .lines property these will not contain parts of other nodes.

This function is a faster way to get this if you just want the source over self.copy().lines. The intermediate parameters for getting this are also cached (not the lines themselves), so it is not so expensive to call this repeatedly.

A valid list of strings is always returned, even for nodes which can never have source like Load, etc... The lines list returned is always a copy so safe to modify.

Caveat: There is an actual qualitative difference between this and self.copy().lines, though it is usually inconsequential. The copy operation may carry out some reformatting to make sure the node is presentable at root level (mostly adding parentheses), this function does not do that. It will however convert an elif to an if. The copy also does trivia, we do not here (yet).

WARNING! With the exception of the elif fix, you get just the text that is there so you will get unparsable source if you get for example a string Constant from the values field of a JoinedStr, or a format_spec.

Parameters:

  • whole: If at root this determines whether to return the whole source or just the location of the node (which may not be the whole source).
  • docstr: How to treat multiline string docstring lines.
    • False: Don't dedent any.
    • True: Dedent all Expr multiline strings (as they serve no coding purpose).
    • 'strict': Only dedent Expr multiline strings in standard docstring locations.
    • None: Use the global default for the docstr option.

Returns:

  • list[str]: List of lines belonging to this node dedented or list with one empty string if node does not have location to get source from (unless at root and whole=True, in which case the whole source is returned).

Examples:

>>> f = FST('''
... def func():
...     return ('not_self', [
...         'self1',
...         'self2',
...     ], 'other_not_self',
...     'outside_lines')
... '''.strip())

Notice the indentation comes with it as well as parts of other nodes.

>>> for l in f.body[0].value.elts[1].lines:  # the list node in the return value
...     print(repr(l))
"    return ('not_self', ["
"        'self1',"
"        'self2',"
"    ], 'other_not_self',"

And here it is all cleaned up.

>>> for l in f.body[0].value.elts[1].own_lines():
...     print(repr(l))
'['
"    'self1',"
"    'self2',"
']'
def own_src( self, whole: bool = True, *, docstr: Union[bool, Literal['strict'], NoneType] = None) -> str:

Source of this node as a string copied out of the tree and dedented as if the node were copied out.

This function is a faster way to get this if you just want the source over self.copy().src. The intermediate parameters for getting this are also cached (not the source itself), so it is not so expensive to call this repeatedly.

Here is a rough performance comparison for scale when using the three methods of getting source of the FST.mark() function from this class (py 3.14).

  • .src: 532 nanoseconds
  • .copy().src: 140 microseconds
  • .own_src(): 7.96 microseconds - intermediate parameters not in cache
  • .own_src(): 2.43 microseconds - intermediate parameters in cache (second+ time)

A string is always returned, even for nodes which can never have source like Load, etc...

Note: The first line is always completely dedented.

Caveat: There is an actual qualitative difference between this and self.copy().src, though it is usually inconsequential. The copy operation may carry out some reformatting to make sure the node is presentable at root level (mostly adding parentheses), this function does not do that. It will however convert an elif to an if. The copy also does trivia, we do not here (yet).

WARNING! With the exception of the elif fix, you get just the text that is there so you will get unparsable source if you get for example a string Constant from the values field of a JoinedStr, or a format_spec.

Parameters:

  • whole: If at root this determines whether to return the whole source or just the location of the node (which may not be the whole source).
  • docstr: How to treat multiline string docstring lines.
    • False: Don't dedent any.
    • True: Dedent all Expr multiline strings (as they serve no coding purpose).
    • 'strict': Only dedent Expr multiline strings in standard docstring locations.
    • None: Use the global default for the docstr option.

Returns:

  • str: The source of this node dedented as a string or empty string if node does not have location to get source from (unless at root and whole=True, in which case the whole source is returned).

Examples:

>>> f = FST('''
... def func():
...     return ('not_self', [
...         'self1',
...         'self2',
...     ], 'other_not_self',
...     'outside_lines')
... '''.strip())

Notice the indentation comes with it, but not parts of other nodes like for .lines. The first line for .src is also completely dedented just like .own_src().

>>> print(f.body[0].value.elts[1].src)  # the list node in the return value
[
        'self1',
        'self2',
    ]

And here it is all cleaned up.

>>> print(f.body[0].value.elts[1].own_src())
[
    'self1',
    'self2',
]
def ast_src(self) -> <property object at 0x7f590550ea20>:

Unparse the AST tree of self discarding all formatting. The unparse will correctly handle our own SPECIAL SLICE nodes and does a few other minor tweaks like removing parentheses from top-level Tuple which contains Slice nodes.

Returns:

  • str: Unparsed source with all formatting lost.

Examples:

>>> f = FST('if a: b=c  # comment')
>>> print(f.src)
if a: b=c  # comment
>>> print(f.ast_src())
if a:
    b = c
>>> f = FST('array[start : stop : step, other_start : other_stop]')
>>> print(ast.unparse(f.slice.a))  # invalid in any universe
(start:stop:step, other_start:other_stop)
>>> print(f.slice.ast_src())
start:stop:step, other_start:other_stop
def copy_ast(self) -> ast.AST:

Copy the AST node tree of this FST node, not including any FST stuff. Use when you just want a copy of the AST tree from this point down.

Needless to say since this just returns an AST all formatting is lost, except that the AST nodes will have the same lineno, col_offset, end_lineno and end_col_offset values as they had in the FST tree.

Returns:

  • AST: Copied AST tree from this point down.

Examples:

>>> a = FST('[0, 1, 2, 3]').copy_ast()
>>> print(type(a))
<class 'ast.List'>
>>> print(dump(a))
List(elts=[Constant(value=0), Constant(value=1), Constant(value=2), Constant(value=3)], ctx=Load())
def copy(self, whole: bool = True, **options) -> FST:

Copy this node to a new top-level tree, dedenting and fixing as necessary. If copying root node then an identical copy is made and no fixes / modifications are applied unless whole=False.

Parameters:

  • whole: This only has meaning when copying a root node, otherwise all options rules apply.
    • True: Copies the entire tree and source without any modifications (including leading and trailing source which is not part of the node), no options are honored.
    • False: For statementlike nodes will honor the trivia option and only copy allowed trivia source along with the node. For expressions and patterns the various pars options will be honored and any parentesization which may happen on a normal copy will be carried out as well. For all other node types that have a location will trim away any source that falls outside of the node and only copy the node and its own source.
  • options: See options().

Returns:

  • FST: Copied node.

Examples:

>>> FST('[a(), b(), c(), d()]').elts[1].copy().src
'b()'

Copies at root are special, default copies everything.

>>> f = FST('''
... # pre
... call()  # tail
... # post
... '''.strip(), 'expr')
>>> print('\n'.join(repr(l) for l in f.copy().lines))
'# pre'
'call()  # tail'
'# post'
>>> print('\n'.join(repr(l) for l in f.copy(whole=False).lines))
'call()'

A module is always copied whole.

>>> f = FST('''
... # pre
... call()  # tail
... # post
... '''.strip(), 'Module')
>>> print('\n'.join(repr(l) for l in f.copy(whole=False).lines))
'# pre'
'call()  # tail'
'# post'
def cut(self, **options) -> FST:

Cut out this node to a new top-level tree (if possible), dedenting and fixing as necessary. Cannot cut root node.

Parameters:

Returns:

  • FST: Cut node.

Examples:

>>> (f := FST('[0, 1, 2, 3]')).elts[1].cut().src
'1'
>>> f.src
'[0, 2, 3]'
def replace( self, code: Union[FST, ast.AST, list[str], str, NoneType], one: bool | None = True, **options) -> FST | None:

Replace or delete (if code=None, if possible) this node. Returns the new node for self, not the old replaced node, or None if was deleted or raw replaced and the old node disappeared. Cannot delete root node. CAN replace root node, in which case self remains the same but the top-level AST and source change.

Note: If replacing root node, the trivia option is not honored.

WARNING! If passing an FST then this is not guaranteed to become the new node (on purpose). If you wish to continue using the FST node you just replaced then make sure to use the one returned from this function. The AST node will also not be identical if coercion happened.

Parameters:

  • code: FST, AST or source str or list[str] to put at this location. None to delete this node.
  • one: Default True means replace with a single element. If False and field allows it then can replace single element with a slice.
  • options: See options().
    • to: Special option which only applies replacing in raw mode (either through True or 'auto'). Instead of replacing just this node, will replace the entire span from this node to the node specified in to with the code passed.

Returns:

  • FST or None: Returns the new node if successfully replaced or None if deleted or raw replace and corresponding new node could not be found.

Examples:

>>> FST('[0, 1, 2, 3]').elts[1].replace('4').root.src
'[0, 4, 2, 3]'
>>> f = FST('def f(a, /, b, *c, **d) -> int: pass')
>>> f.args.posonlyargs[0].replace(')', to=f.returns, raw=True)  # raw reparse
<arguments 0,6..0,6>
>>> f.src
'def f(): pass'
def remove(self, **options) -> None:

Delete this node if possible, equivalent to replace(None, ...). Cannot delete root node.

Parameters:

Examples:

>>> (f := FST('[0, 1, 2, 3]')).elts[1].remove()
>>> f.src
'[0, 2, 3]'
def insert( self, code: Union[FST, ast.AST, list[str], str], idx: Union[int, Literal['end']] = 0, field: str | None = None, *, one: bool | None = True, **options) -> FST:

Insert into field of self at a specific index. Default field if field=None. This is a convenience function for self.put_slice().

Same as self.field.insert().

Returns:

  • self

Parameters:

  • code: FST, AST or source str or list[str] to insert.
  • idx: Index to insert before. Can be 'end' to indicate add at end of slice.
  • field: Field to operate on or the default field if is None. Must be a list field.
  • one: If True then will insert code as a single item. Otherwise False will attempt a slice insertion (type must be compatible).
  • options: See fst.fst.FST.options().

Note: The field value can optionally be passed positionally in the idx parameter. If passed in idx idx is assumed to be 0.

Examples:

>>> from fst import FST
>>> FST('[0, 1, 2, 3]').insert('4, 5', 1).src
'[0, (4, 5), 1, 2, 3]'
>>> FST('[0, 1, 2, 3]').insert('(4, 5)', 1).src
'[0, (4, 5), 1, 2, 3]'
>>> FST('[0, 1, 2, 3]').insert('4, 5', 'end', one=False).src
'[0, 1, 2, 3, 4, 5]'
>>> FST('[0, 1, 2, 3]').insert('(4, 5)', 'end', one=False).src
'[0, 1, 2, 3, (4, 5)]'
>>> # same as 'end' but 'end' is always 'end'
>>> FST('[0, 1, 2, 3]').insert('4, 5', 4, one=False).src
'[0, 1, 2, 3, 4, 5]'
>>> FST('[0, 1, 2, 3]').insert('(4, 5)', 4, one=False).src
'[0, 1, 2, 3, (4, 5)]'
>>> FST('[0, 1, 2, 3]')[1:3].insert('*star').base.src
'[0, *star, 1, 2, 3]'
def append( self, code: Union[FST, ast.AST, list[str], str], field: str | None = None, **options) -> FST:

Append code as a single element to field of self. Default field if field=None. This is a convenience function for self.put_slice().

Same as self.field.append().

Returns:

  • self

Parameters:

  • code: FST, AST or source str or list[str] to append.
  • field: Field to operate on or the default field if is None. Must be a list field.
  • options: See fst.fst.FST.options().

Examples:

>>> from fst import FST
>>> FST('[0, 1, 2, 3]').append('(4, 5)').src
'[0, 1, 2, 3, (4, 5)]'
>>> FST('[0, 1, 2, 3]')[1:3].append('*star').base.src
'[0, 1, 2, *star, 3]'
def extend( self, code: Union[FST, ast.AST, list[str], str], field: str | None = None, one: Optional[Literal[False]] = False, **options) -> FST:

Extend field of self with the slice in code (type must be compatible). Default field if field=None. This is a convenience function for self.put_slice().

Same as self.field.extend().

Returns:

  • self

Parameters:

  • code: FST, AST or source str or list[str] slice to extend.
  • field: Field to operate on or the default field if is None. Must be a list field.
  • options: See fst.fst.FST.options().

Examples:

>>> from fst import FST
>>> FST('[0, 1, 2, 3]').extend('4, 5').src
'[0, 1, 2, 3, 4, 5]'
>>> FST('[0, 1, 2, 3]').extend('(4, 5)').src
'[0, 1, 2, 3, (4, 5)]'
>>> FST('[0, 1, 2, 3]')[1:3].extend('4, 5').base.src
'[0, 1, 2, 4, 5, 3]'
>>> FST('[0, 1, 2, 3]')[1:3].extend('(4, 5)').base.src
'[0, 1, 2, (4, 5), 3]'
def prepend( self, code: Union[FST, ast.AST, list[str], str], field: str | None = None, **options) -> FST:

prepend code as a single element to the beginning of field of self. Default field if field=None. This is a convenience function for self.put_slice().

Same as self.field.prepend().

Returns:

  • self

Parameters:

Examples:

>>> from fst import FST
>>> FST('[0, 1, 2, 3]').prepend('(4, 5)').src
'[(4, 5), 0, 1, 2, 3]'
>>> FST('[0, 1, 2, 3]')[1:3].prepend('*star').base.src
'[0, *star, 1, 2, 3]'
def prextend( self, code: Union[FST, ast.AST, list[str], str], field: str | None = None, one: Optional[Literal[False]] = False, **options) -> fst.view.fstview:

Extend the beginning of the field of self with the slice in code (type must be compatible). Default field if field=None. This is a convenience function for self.put_slice().

Same as self.field.prextend().

Returns:

  • self

Parameters:

  • code: FST, AST or source str or list[str] to extend at the start.
  • field: Field to operate on or the default field if is None. Must be a list field.
  • options: See fst.fst.FST.options().

Examples:

>>> from fst import FST
>>> FST('[0, 1, 2, 3]').prextend('4, 5').src
'[4, 5, 0, 1, 2, 3]'
>>> FST('[0, 1, 2, 3]').prextend('(4, 5)').src
'[(4, 5), 0, 1, 2, 3]'
>>> FST('[0, 1, 2, 3]')[1:3].prextend('4, 5').base.src
'[0, 4, 5, 1, 2, 3]'
>>> FST('[0, 1, 2, 3]')[1:3].prextend('(4, 5)').base.src
'[0, (4, 5), 1, 2, 3]'
def get( self, idx: Union[int, Literal['end'], NoneType] = None, stop: Union[int, Literal['end'], NoneType] = None, field: str | None = None, cut: bool = False, **options) -> FST | None | str | ellipsis | int | float | complex | bytes | bool:

Copy or cut an individual child node or a slice of child nodes from self if possible. This function can do everything that get_slice() can do.

Parameters:

  • idx: The index of the child node to get if the field being gotten from contains multiple elements or the start of the slice to get if getting a slice (by specifying stop). If the field being gotten from is an individual element then this must be None.
  • stop: The end index (exclusive) of the child node to get if getting a slice from a field that contains multiple elements. This should be one past the last element to get (like python list indexing). If the field being gotten from is an individual element then this must be None.
  • field: The name of the field to get the element(s) from, which can be an individual element like a value or a list like body. If this is None then the default field for the node type is used. Most node types have a common-sense default field, e.g. body for all block statements, value for things like Return and Yield. Dict, MatchMapping and Compare nodes have special handling for a None field (which defaults to the _all virtual field for them).
  • cut: Whether to cut out the child node (if possible) or not (just copy).
  • options: See options().

Note: The field value can optionally be passed positionally in either the idx or stop parameter. If passed in idx a value of None is used for idx, which will select either just the element from a single element field or the entire slice from a list field. If passed in stop then the idx value is present and stop is assumed to be None.

Returns:

  • FST: When getting an actual node (most situations).
  • str: When getting an identifier, like from Name.id.
  • constant: When getting a constant (fst.astutil.constant), like from Constant.value or MatchSingleton.value.

Examples:

>>> FST('[0, 1, 2, 3]').get(1).src
'1'
>>> (f := FST('[0, 1, 2, 3]')).get(1, 3).src
'[1, 2]'
>>> f.src
'[0, 1, 2, 3]'
>>> (f := FST('[0, 1, 2, 3]')).get(1, 3, cut=True).src
'[1, 2]'
>>> f.src
'[0, 3]'
>>> FST('[0, 1, 2, 3]').get(0, 3).src
'[0, 1, 2]'
>>> FST('[0, 1, 2, 3]').get(-3, 'end').src
'[1, 2, 3]'
>>> FST('if 1: i = 1\nelse: j = 2').get(0).src
'i = 1'
>>> FST('if 1: i = 1\nelse: j = 2').get('orelse').src
'j = 2'
>>> FST('if 1: i = 1\nelse: j = 2; k = 3').get(1, 'orelse').src
'k = 3'
>>> FST('if 1: i = 1\nelse: j = 2; k = 3; l = 4; m = 5').get(1, 3, 'orelse').src
'k = 3; l = 4'
>>> FST('return 1').get().src  # default field is 'value'
'1'
>>> FST('[0, 1, 2, 3]').get().src  # 'elts' slice copy is made
'[0, 1, 2, 3]'
def put( self, code: Union[FST, ast.AST, list[str], str, ellipsis, int, float, complex, bytes, bool, NoneType], idx: Union[int, Literal['end'], NoneType] = None, stop: Union[int, Literal['end'], NoneType] = None, field: str | None = None, one: bool | None = True, **options) -> FST | None:

Put an individual node or a slice of nodes to self if possible. The node is passed as an existing top-level FST, AST, string or list of string lines. If passed as an FST then it should be considered "consumed" after this function returns and should not be accessed again, even on failure. If passed as an AST then it is copied and can be reused after this function returns. This is the most general form of node put function and can do everything that the other node put functions can.

WARNING! The original self node may be invalidated during the operation if using raw mode (either raw=True or if it happened as a fallback from raw='auto'). If there is a possibility of this happening then make sure to use the new self returned from this function, otherwise if no raw happens then self remains unchanged and usable.

Parameters:

  • code: The node to put as an FST (must be root node), AST, a string or list of line strings. If putting to an identifier field then this should be a string and it will be taken literally (no parsing). If putting to a constant like MatchSingleton.value or Constant.value then this should be an appropriate primitive constant value.
  • idx: The index of the field node to put to if the field being put to contains multiple elements or the start of the slice to put if putting a slice (by specifying stop). If the field being put to is an individual element then this must be None.
  • stop: The end index (exclusive) of the field node to put to if putting a slice to a field that contains multiple elements. This should be one past the last element to put (like python list indexing). If the field being put to is an individual element then this must be None.
  • field: The name of the field to put the element(s) to, which can be an individual element like a value or a list like body. If this is None then the default field for the node type is used. Most node types have a common-sense default field, e.g. body for all block statements, value for things like Return and Yield. Dict, MatchMapping and Compare nodes have special handling for a None field (which defaults to the _all virtual field for them).
  • one: Only has meaning if putting a slice and in this case True specifies that the source should be put as a single element to the range specified even if it is a valid slice. False indicates that a slice value should be put as slice and not an individual element, which must in this case be a compatible slice type.
  • options: See options().
    • to: Special option which only applies when putting a single element in raw mode (either through True or 'auto'). Instead of replacing just the target node, will replace the entire span from the target node to the node specified in to with the code passed.

Note: The field value can optionally be passed positionally in either the idx or stop parameter. If passed in idx a value of None is used for idx, which will select either just the element from a single element field or the entire slice from a list field. If passed in stop then the idx value is present and stop is assumed to be None.

Returns:

  • self or None if a raw put was done and corresponding new node could not be found.

Examples:

>>> FST('[0, 1, 2, 3]').put('x', 1).src
'[0, x, 2, 3]'
>>> FST('[0, 1, 2, 3]').put('x, y', 1, 3).src
'[0, (x, y), 3]'
>>> FST('[0, 1, 2, 3]').put('x, y', 1, 3, one=False).src
'[0, x, y, 3]'
>>> FST('[0, 1, 2, 3]').put('x, y', 0, 3).src
'[(x, y), 3]'
>>> FST('[0, 1, 2, 3]').put('x, y', -3, 'end', one=False).src
'[0, x, y]'
>>> FST('[0, 1]').put('x, y', 'end').src
'[0, 1, (x, y)]'
>>> FST('[0, 1]').put('x, y', 'end', one=False).src
'[0, 1, x, y]'
>>> print(FST('if 1: i = 1\nelse: j = 2').put('z = -1', 0).src)
if 1:
    z = -1
else: j = 2
>>> print(FST('if 1: i = 1\nelse: j = 2').put('z = -1', 0, 'orelse').src)
if 1: i = 1
else:
    z = -1
>>> print(FST('if 1: i = 1\nelse: j = 2')
...       .put('z = -1\ny = -2\nx = -3', 'orelse', one=False).src)
if 1: i = 1
else:
    z = -1
    y = -2
    x = -3
>>> f = FST('if 1: i = 1\nelse: j = 2')
>>> print(f.put('z = -1', 0, raw=True, to=f.orelse[-1]).root.src)
if 1: z = -1
def get_slice( self, start: Union[int, Literal['end']] = 0, stop: Union[int, Literal['end']] = 'end', field: str | None = None, cut: bool = False, **options) -> FST:

Copy or cut a slice of child nodes from self if possible.

Parameters:

  • start: The start index of the slice to get. You can use 'end' here but you will just wind up getting an empty slice.
  • stop: The end index (exclusive) of the slice to get. This should be one past the last element to get (like python list indexing). If this is 'end' then it indicates a slice operation to the end of the list (like python a[start:]).
  • field: The name of the field to get the elements from, which can be an individual element like a value or a list like body. If this is None then the default field for the node type is used. Most node types have a common-sense default field, e.g. body for all block statements, elts for things like List and Tuple. Dict, MatchMapping and Compare nodes have special handling for a None field (which defaults to the _all virtual field for them).
  • cut: Whether to cut out the slice or not (just copy).
  • options: See options().

Note: The field value can optionally be passed positionally in either the start or stop parameter. If passed in start then the range is assumed to be the entire field. If passed in stop then the start value is present and stop is assumed to be 'end'.

Returns:

  • FST: Slice node of nodes gotten.

Examples:

>>> FST('[0, 1, 2, 3]').get_slice(1).src
'[1, 2, 3]'
>>> FST('[0, 1, 2, 3]').get_slice(0, -1).src
'[0, 1, 2]'
>>> (f := FST('[0, 1, 2, 3]')).get_slice(1, 3, cut=True).src
'[1, 2]'
>>> f.src
'[0, 3]'
>>> f = FST('if 1: i = 1\nelse: j = 2; k = 3; l = 4; m = 5')
>>> print(f.src)
if 1: i = 1
else: j = 2; k = 3; l = 4; m = 5
>>> print(f.get_slice(1, 3, 'orelse', cut=True).src)
k = 3; l = 4
>>> print(f.src)
if 1: i = 1
else: j = 2; m = 5
def put_slice( self, code: Union[FST, ast.AST, list[str], str, NoneType], start: Union[int, Literal['end']] = 0, stop: Union[int, Literal['end']] = 'end', field: str | None = None, one: bool | None = False, **options) -> FST | None:

Put a slice of nodes to self if possible. The node is passed as an existing top-level FST, AST, string or list of string lines. If passed as an FST then it should be considered "consumed" after this function returns and is no longer valid, even on failure. AST is copied.

WARNING! The original self node may be invalidated during the operation if using raw mode (either raw=True or if it happened as a fallback from raw='auto'). If there is a possibility of this happening then make sure to use the new self returned from this function, otherwise if no raw happens then self remains unchanged and usable.

Parameters:

  • code: The slice to put as an FST (must be root node), AST, a string or list of line strings.
  • start: The start index of the slice to put. 'end' here allows extending at the end of the field.
  • stop: The end index (exclusive) of the slice. This should be one past the last element to put (like python list indexing). If this is 'end' then it indicates a slice operation to the end of the list (like python a[start:]).
  • field: The name of the field to put the elements to. If this is None then the default field for the node type is used. Most node types have a common-sense default field, e.g. body for all block statements, elts for things like List and Tuple. Dict, MatchMapping and Compare nodes have special handling for a None field (which defaults to the _all virtual field for them).
  • one: True specifies that the source should be put as a single element to the range specified even if it is a valid slice. False indicates a true slice operation replacing the range with the slice passed, which must in this case be a compatible slice type.
  • options: See options().

Note: The field value can optionally be passed positionally in either the start or stop parameter. If passed in start then the range is assumed to be the entire field. If passed in stop then the start value is present and stop is assumed to be 'end'.

Returns:

  • self or None if a raw put was done and corresponding new node could not be found.

Examples:

>>> FST('[0, 1, 2, 3]').put_slice('x', 1).src
'[0, x]'
>>> FST('[0, 1, 2, 3]').put_slice('x, y', 1, 3).src
'[0, x, y, 3]'
>>> FST('[0, 1, 2, 3]').put_slice('x, y', 1, 3, one=True).src
'[0, (x, y), 3]'
>>> FST('[0, 1, 2, 3]').put_slice('x, y', 0, 3).src
'[x, y, 3]'
>>> FST('[0, 1, 2, 3]').put_slice('x, y', -3, 'end', one=True).src
'[0, (x, y)]'
>>> print(FST('if 1: i = 1\nelse: j = 2').put_slice('z = -1', 0).src)
if 1:
    z = -1
else: j = 2
>>> print(FST('if 1: i = 1\nelse: j = 2').put_slice('z = -1', 0, 'orelse').src)
if 1: i = 1
else:
    z = -1
>>> print(FST('if 1: i = 1\nelse: j = 2')
...       .put_slice('z = -1\ny = -2\nx = -3', 'orelse').src)
if 1: i = 1
else:
    z = -1
    y = -2
    x = -3
def get_src( self, ln: Union[int, Literal['end']], col: Union[int, Literal['end']], end_ln: Union[int, Literal['end']], end_col: Union[int, Literal['end']], as_lines: bool = False) -> str | list[str]:

Get source at location, without dedenting or any other modification, returned as a string or list of individual lines.

Can call on any node in tree to access source for the whole tree.

The coordinates passed in are clipped to the whole valid source area. Negative indexing is supported and the 'end' special value means either the last line if used for ln or end_ln or one past the last column on their respective line if used for col or end_col.

Parameters:

  • ln: Start line of span to get (0 based).
  • col: Start column (character) on start line.
  • end_ln: End line of span to get (0 based, inclusive).
  • end_col: End column (character, exclusive) on end line.
  • as_lines: If False then source is returned as a single string with embedded newlines. If True then source is returned as a list of line strings (without newlines).

Returns:

  • str | list[str]: A single string or a list of lines if as_lines=True. If lines then there are no trailing newlines in the individual line strings.

Examples:

>>> FST('if 1:\n  i = 2').get_src(0, 3, 1, 5)
'1:\n  i ='
>>> FST('if 1:\n  i = 2').get_src(0, 3, 1, 5, as_lines=True)
['1:', '  i =']
>>> (f := FST('if 1:\n  i = 2')).get_src(*f.body[0].bloc)
'i = 2'
>>> FST('if 1:\n  i = 2').get_src('end', -3, 'end', 'end')
'= 2'
def put_src( self, code: Union[FST, ast.AST, list[str], str, NoneType], ln: Union[int, Literal['end']], col: Union[int, Literal['end']], end_ln: Union[int, Literal['end']], end_col: Union[int, Literal['end']], action: Optional[Literal['reparse', 'offset']] = 'reparse') -> tuple[int, int]:

Put source and maybe adjust AST tree for source modified. The adjustment may be a reparse of the area changed, an offset of nodes (assuming put source was just trivia and wouldn't affect the tree) or nothing at all. The action options are:

  • 'reparse': Put source and reparse. There are no rules on what is put, it is simply put and parse is attempted.

    The reparse that is triggered is of at least a statement level node or a statement block header, and can be multiple statements if the location spans those or even statements outside of the location if the reparse affects things like elif. FST nodes in the region of the put or even outside of it can become invalid. The only FST node guaranteed not to change is the root node (identity, the AST it holds can change).

    When putting source raw by location like this there are no automatic modifications made to the source or destination. No parenthesization, prefixes or suffixes or indentation, the source is just put and parsed so you are responsible for the correct indentation and precedence.

    After put and successful reparse a new node can be found using find_loc() functions from the original start ln and col and newly returned end_ln and end_col. It is possible that None is returned from these functions if no good candidate is found (since this can be used to delete or merge nodes).

    self doesn't matter in this case, can call on any node in tree, even one which is not touched by the source change and the appropriate nodes will be found and reparsed.

  • 'offset': Put source and offset nodes around it according to what node the put_src() was called on. In this case self matters and should be the last node down within which the location is contained. Any child nodes of this node are offset differently from this self node and its parents.

    The self node and its parents will not have their start locations offset if the put is an insert at the start (as the location is considered to be INSIDE these nodes), whereas child nodes will be moved.

    The self node and its parents will have their end locations offset if the put ends at this location whereas child nodes will not.

  • None: Nothing is reparsed or offset, the source is just put. There are few cases where this will result in a valid tree but can include removal of comments and trailing whitespace on a line or changing lines between empty and comment. Some caches are cleared on put determined by the node this is called on so best to call on the node that "ows" the source being put. For comment, block statement it is in or general statement it is on the line of. For other misc, best just node that it is "in".

If the code is passed as an AST then it is unparsed to a string and that string is put into the location. If code is an FST then the exact source of the FST is put. If passed as a string or lines then that is put directly.

The coordinates passed in are clipped to the whole valid source area. Negative indexing is supported and the 'end' special value means either the last line if used for ln or end_ln or one past the last column on their respective line if used for col or end_col.

Parameters:

  • code: The code to put as an FST (must be root node), AST, a string or list of line strings.
  • ln: Start line of span to put (0 based).
  • col: Start column (character) on start line.
  • end_ln: End line of span to put (0 based, inclusive).
  • end_col: End column (character, exclusive) on end line.
  • action: What action to take on the AST tree, the options are 'reparse', 'offset' or None.

Returns:

  • (end_ln, end_col): New end location of source put (all source after this was not modified).

Examples:

>>> f = FST('i = 1')
>>> f.put_src('2', 0, 4, 0, 5)
(0, 5)
>>> f.src
'i = 2'
>>> f = FST('i = 1')
>>> f.put_src('+= 3', 0, 2, 0, 5)
(0, 6)
>>> f.src
'i += 3'
>>> f = FST('{a: b, c: d, e: f}')
>>> f.put_src('**', 0, 7, 0, 10)
(0, 9)
>>> f.src
'{a: b, **d, e: f}'
>>> f = FST('''
... if a:
...   i = 2
... elif b:
...   j = 3
... '''.strip())
>>> print(f.src)
if a:
  i = 2
elif b:
  j = 3
>>> f.put_src('''
... else:
...   if b:
...     k = 4
... '''.strip(), *f.orelse[0].loc[:2], *f.loc[2:])
(4, 9)
>>> print(f.src)
if a:
  i = 2
else:
  if b:
    k = 4
>>> (f := FST('a, b')).put_src(' ', 0, 4, 0, 4, 'offset')
(0, 5)
>>> f.src, f.loc, f.elts[1].loc
('a, b ', fstloc(0, 0, 0, 5), fstloc(0, 3, 0, 4))
def get_docstr(self) -> str | None:

Get the unformatted docstring value of this node if it is a FunctionDef, AsyncFunctionDef, ClassDef or Module. The docstring is dedented and returned as a normal string, not a node. Keep in mind an empty docstring may exist but will be a falsey value so make sure to explicitly check for None return. If you want the actual docstring node then just check for presence with .has_docstr and get .body[0].

Returns:

  • str | None: Actual dedented docstring value, not the node or syntactic string representation with quotes. None if there is no docstring or the node cannot have a docstring.

Examples:

>>> f = FST('''
... def func():
...     \'\'\'docstring indented
...     in source with codes\\x3f\\x3f\\x3f\'\'\'
... '''.strip())
>>> print(f.src)
def func():
    '''docstring indented
    in source with codes\x3f\x3f\x3f'''
>>> f.get_docstr()
'docstring indented\nin source with codes???'
def put_docstr(self, text: str | None, reput: bool = False, **options) -> FST:

Set or delete the docstring of this node if it is a FunctionDef, AsyncFunctionDef, ClassDef or Module. Will replace, insert or delete the node as required. If setting, the text string that is passed will be formatted with triple quotes and indented as needed.

Parameters:

  • text: The string to set as a docstring or None to delete.
  • reput: If True then remove old docstring Expr first (if present) before reinserting new one. This is to allow repositioning the docstring before any comments.
  • options: The options to use for a put if a put is done, see options().

Returns:

  • self

Examples:

>>> f = FST('def func(): pass')
>>> print(f.put_docstr('docstring indented\nin source with codes\x3f\x3f\x3f').src)
def func():
    """docstring indented
    in source with codes???"""
    pass
def get_line_comment( self, field: Optional[Literal['body', 'orelse', 'finalbody']] = None, full: bool = False) -> str | None:

Get current line comment for this node.

The line comment is the single comment at the end of the last line of the location of this node, with the exception of statement block nodes where the line comment lives on the last line of the header of the node (after the :, since the comment on the last line of the location belongs to the last child).

Note: Currently this functionality is limited to statement nodes.

Parameters:

  • field: If self is a block statement then this can specify which field to operate on, only 'body', 'orelse' and 'finalbody' make sense to use and an error will be raised if the field is not present or there is nothing in it. None means use default 'body' if block statement.
  • full:
    • False: The gotten comment text is returned stripped of the '#' and any leading and trailing whitespace.
    • True: The entire gotten comment from the end of the node to the end of the line is returned with no whitespace stripped, e.g. ' # comment '.

Returns:

  • str: The current comment, with or without the leading whitespace and '#' as per the full paramenter.
  • None: There is no comment present.

Examples:

>>> FST('statement  # comment  ', 'stmt').get_line_comment()
'comment'
>>> FST('statement  # comment  ', 'stmt').get_line_comment(full=True)
'  # comment  '
>>> FST('if a:  # ifc\n  pass  # bodyc').body[0].get_line_comment()
'bodyc'
>>> FST('if a:  # ifc\n  pass  # bodyc').get_line_comment()
'ifc'
>>> FST('if a: pass\nelse:  # elsec\n  pass').get_line_comment('orelse')
'elsec'
def put_line_comment( self, comment: str | None = None, field: Optional[Literal['body', 'orelse', 'finalbody']] = None, full: bool = False) -> str | None:

Put line comment for this node returning whatever comment was there before.

The line comment is the single comment at the end of the last line of the location of this node, with the exception of statement block nodes where the line comment lives on the last line of the header of the node (after the :, since the comment on the last line of the location belongs to the last child).

Note: Currently this functionality is limited to statement nodes.

Parameters:

  • comment: The comment operation to perform after getting the current comment.
    • str: Put new comment which may or may not need to have the initial '#' according to the full parameter.
    • None: Delete current comment (if present).
  • field: If self is a block statement then this can specify which field to operate on, only 'body', 'orelse' and 'finalbody' make sense to use and an error will be raised if the field is not present or there is nothing in it. None means use default 'body' if block statement.
  • full:
    • False: The gotten comment text is returned stripped of the '#' and any leading and trailing whitespace. The put comment text is put to existing comment if is present and otherwise is prepended with ' #' and a single leading whitespace after that if needed and put after the node.
    • True: The entire gotten comment from the end of the node to the end of the line is returned with no whitespace stripped, e.g. ' # comment '. The put comment MUST start with a '#' and possible leading whitespace before that and is put verbatim with no stripping, replacing any existing comment from the end of the node to the end of the line.

Returns:

  • str: The current comment, before replacement, with or without the leading whitespace and '#' as per the full paramenter.
  • None: There was no comment present.

Examples:

>>> f = FST('statement  # comment  ', 'stmt')
>>> f.put_line_comment('new comment')
'comment'
>>> print(f.src)
statement  # new comment
>>> f = FST('if a:  # ifc\n  pass  # bodyc')
>>> f.body[0].put_line_comment('new body comment')
'bodyc'
>>> print(f.src)
if a:  # ifc
  pass  # new body comment
>>> f = FST('if a:  # ifc\n  pass  # bodyc')
>>> f.put_line_comment('new if comment')
'ifc'
>>> print(f.src)
if a:  # new if comment
  pass  # bodyc
>>> f = FST('if a: pass\nelse:  # elsec\n  pass')
>>> f.put_line_comment('new else comment', 'orelse')
'elsec'
>>> print(f.src)
if a: pass
else:  # new else comment
  pass
def pars(self, *, shared: bool | None = True) -> fst.common.fstloc | None:

Return the location of enclosing GROUPING parentheses if present. Will balance parentheses if self is an element of a tuple and not return the parentheses of the tuple. Likwise will not normally return the parentheses of an enclosing arguments parent or class bases list (unless shared=None, but that is mostly for internal use).

Only normally works on (and makes sense for) expr or pattern nodes, otherwise returns self.bloc and count of 0. Also handles special case of a single generator expression argument to a function sharing parameters with the call arguments, in which case a count of -1 and the location of the GeneratorExp without its enclosing parentheses may be returned, if this is enabled with shared=False.

This function is cached so feel free to call as often as is needed.

Note: For a Starred this will always return the location of the whole Starred since that cannot be parenthesized itself but rather its child. If you want he parenteses of the child then do starred.value.pars().

Parameters:

  • shared: If True then will include parentheses of a single call argument generator expression if they are shared with the call arguments enclosing parentheses with a count of 0. If False then does not return these and returns a count of -1, and thus the location is not a full valid GeneratorExp location. If None then returns ANY directly enclosing parentheses, whether they belong to this node or not.

Returns:

  • fstloc | None: Location of enclosing parentheses if present else self.bloc (which can be None). Negative parentheses count (from shared pars solo call arg generator expression) can also be checked in the case of shared=False via fst.pars() > fst.bloc. If only loc is returned, it will be an fstloc which will still have the count of parentheses in an attribute .n.

Examples:

>>> FST('i').pars()
fstlocn(0, 0, 0, 1, n=0)
>>> FST('(i)').pars()
fstlocn(0, 0, 0, 3, n=1)
>>> FST('((i))').pars()
fstlocn(0, 0, 0, 5, n=2)
>>> FST('(1, 2)').pars()  # tuple pars are not considered grouping pars
fstlocn(0, 0, 0, 6, n=0)
>>> FST('((1, 2))').pars()
fstlocn(0, 0, 0, 8, n=1)
>>> FST('call(a)').args[0].pars()  # any node, not just root
fstlocn(0, 5, 0, 6, n=0)
>>> FST('call((a))').args[0].pars()
fstlocn(0, 5, 0, 8, n=1)
>>> FST('call(i for i in j)').args[0].pars()
fstlocn(0, 4, 0, 18, n=0)
>>> FST('call(i for i in j)').args[0].pars(shared=False)  # exclude shared pars
fstlocn(0, 5, 0, 17, n=-1)
>>> FST('call((i for i in j))').args[0].pars(shared=False)
fstlocn(0, 5, 0, 19, n=0)
def par( self, force: Union[bool, Literal['invalid']] = False, *, whole: bool = True) -> FST:

Parenthesize node if it MAY need it. Will not parenthesize atoms which are always enclosed like List, or nodes which are not is_parenthesizable(), unless force=True. Will add intrinsic node-owned parentheses to unparenthesized Tuple and brackets to unbracketed MatchSequence, adjusting the node location. If dealing with a Starred then the parentheses are applied to the child.

WARNING! If you invalid-force-parenthesize something that shouldn't be parenthesized, and you wind up poking an eye out, that's on you.

Parameters:

  • force:
    • False: Only parenthesize if not currently parenthesized and if the node type is not an atom or is an atom which can be split over multiple lines (in which case it would need parentheses for parsability).
    • True: Add a layer of perentheses regardless if any already present or if node type is an atom, but only if allowed by syntax, e.g. this won't parenthesize an arg, withitem any kind of statement or anything which would cause a syntax error with the parentheses there, so no Slices or f-string Constants either.
    • 'invalid': Add a layer of parentheses regardless if any already present or even syntactically allowed.
  • whole: If at root then parenthesize whole source instead of just node, if False then only node.

Returns:

  • self

Examples:

>>> FST('a + b').par().src
'(a + b)'
>>> FST('(a + b)').par().src  # already parenthesized, so nothing done
'(a + b)'
>>> FST('(a + b)').par(force=True).src  # force it
'((a + b))'
>>> FST('1, 2').par().src  # parenthesize tuple
'(1, 2)'
>>> FST('i').par().src  # an atom doesn't need parentheses
'i'
>>> FST('i').par(force=True).src  # so must be forced
'(i)'
>>> FST('a:b:c', 'Slice').par(force=True).src  # syntactically wrong
'a:b:c'
>>> FST('a:b:c', 'Slice').par(force='invalid').src  # can still force
'(a:b:c)'
>>> # parethesize MatchSequence puts brackets like ast.unparse()
>>> FST('1, 2', 'pattern').par().src
'[1, 2]'
>>> FST('*a or b').par().src  # par() a Starred parenthesizes its child
'*(a or b)'
>>> FST('call(i = 1 + 2)').keywords[0].value.par().root.src  # not just root node
'call(i = (1 + 2))'
def unpar( self, node: Union[bool, Literal['invalid']] = False, *, shared: bool | None = True) -> FST:

Remove all parentheses if present. Normally removes just grouping parentheses but can also remove intrinsic Tuple node parentheses and MatchSequence parentheses or brackets if node=True. If dealing with a Starred then the parentheses are checked in and removed from the child.

If shared=None then will also remove parentheses which do not belong to this node but enclose it directly, this is meant for internal use.

WARNING! This function doesn't do any higher level parsability validation. So if you unparenthesize something that shouldn't be unparenthesized, and you wind up poking an eye out, that's on you.

Parameters:

  • node: What to remove.
    • False: Only grouping parentheses.
    • True: Grouping parentheses and also remove intrinsic parentheses from a parenthesized Tuple and parentheses / brackets from parenthesized / bracketed MatchSequence. Also from a parentesized Starred arglike-only expression which can cause it to become unparsable, e.g. *(a or b).
    • 'invalid': Remove intrinsic delimiters also from List, Set, Dict, MatchMapping, ListComp, SetComp, DictComp and GeneratorExp.
  • shared: Whether to allow merge of parentheses of single call argument generator expression with Call parentheses or not. If None then will attempt to unparenthesize ANY enclosing parentheses, whether they belong to this node or not (meant for internal use).

Returns:

  • self

Examples:

>>> FST('a + b').unpar().src  # nothing done if no pars
'a + b'
>>> FST('(a + b)').unpar().src
'a + b'
>>> FST('((a + b))').unpar().src  # removes all
'a + b'
>>> FST('(1, 2)').unpar().src  # but not from tuple
'(1, 2)'
>>> FST('(1, 2)').unpar(node=True).src  # unless explicitly specified
'1, 2'
>>> FST('(((1, 2)))').unpar().src
'(1, 2)'
>>> FST('(((1, 2)))').unpar(node=True).src
'1, 2'
>>> FST('[1, 2]', 'pattern').unpar().src
'[1, 2]'
>>> FST('[1, 2]', 'pattern').unpar(node=True).src
'1, 2'
>>> FST('*(a)').unpar().src  # Starred unparenthesizes its child
'*a'
>>> # unless it is arglike since that is syntax error, the pars belong to the node
>>> FST('*(a or b)').unpar().src
'*(a or b)'
>>> FST('*(a or b)').unpar(node=True).src  # so can force with node=True
'*a or b'

Not just root node.

>>> FST('call(i = (1 + 2))').keywords[0].value.unpar().root.src
'call(i = 1 + 2)'

By default allows sharing.

>>> FST('call(((i for i in j)))').args[0].unpar().root.src
'call(i for i in j)'

Unless told not to.

>>> FST('call(((i for i in j)))').args[0].unpar(shared=False).root.src
'call((i for i in j))'

Invalid stuff.

>>> FST('{1: a, **rest}', pattern).unpar(node='invalid').root.src
'1: a, **rest'
>>> FST('[i for i in j if i]').unpar(node='invalid').root.src
'i for i in j if i'
def scope_symbols( self, full: bool = False, *, local: bool = True, free: bool = True, import_star: bool = False) -> dict[str, list[FST]] | dict[str, dict[str, list[FST]]]:

Get the symbols accessed in the scope of self (from self on down, will not search up if self is only part of a scope and doesn't define its own). Normally should be called on something that has a scope like a module or function definition or lambda or comprehension, but can be called on anything. Returns either a simple dictionary of all the symbol names (with the respective FST nodes where they are accessed) or a categorized dictionary of these if full=True.

Note: The order of the nodes in the various dictionaries are the order in which they appear SYNTACTICALLY, not the order they will be processed in semantically by the interpreter.

Note: For AugAssign nodes the same node will appear in both the load and store dictionaries in a full=True return but only once in the single dictionary returned when full=False.

Parameters:

  • full: Whether to return a single dictionary with all names or a catgorized dictionary of dictionaries. If True then the categories are:
    • 'load': Dictionary of symbol names to list of nodes where these names are read.
    • 'store': Dictionary of symbol names to list of nodes where these names are written.
    • 'del': Dictionary of symbol names to list of nodes where these names are deleted.
    • 'global': Dictionary of symbol names to list of Global nodes where these names are declared. The nodes aren't where the symbols are used, those are still in the 'load' / 'store' / 'del' lists, just where they are DECLARED as global. Multiple symbol names will probably have the same node since a single global declaration can declare multiple names and the names themselves don't have their own individual nodes, they are just strings in the Global node.
    • 'nonlocal': Similar to global but for nonlocal declarations. This is just the declarations, these are not necessarily all the truly nonlocal symbols as there can be "free" variables which are nonlocal but not explicitly declared as such.
    • 'local': This is a dictionary of symbols determined to be local to the scope. These are all 'store' symbols which do not appear in either 'global' or 'nonlocal' explicit declarations. There are no 'load' or 'del' nodes in these lists even if they are local both because "local" symbols are defined as such by a store operation and also because it is faster. If you need any respective 'load' or 'del' nodes then look them up in their respective category dictionary.
    • 'free': This is a dictionary of symbols determined to be implicitly nonlocal to the scope. Basically all nodes which are read but never written or deleted and are not explicitly global or nonlocal. Note that this is slightly different from the python definition of a "free" variable as those can include explicitly nonlocal variables. For our purposes here it is only the IMPLICIT nonlocal symbols.
  • local: Whether to compute the 'local' category of symbols or not, set to False if you don't need this.
  • free: Whether to compute the 'free' category symbols or not, set to False if you don't need this.
  • import_star: Normally a from mod import * does not generate any symbols. If you pass this parameter as True then the * (invalid) name with its alias node is added to the 'store' category.

Examples:

>>> print(FST('def f(v=b): v = a').scope_symbols())  # notice no 'b' or 'f'
{'v': [<arg 0,6..0,7>, <Name 0,12..0,13>], 'a': [<Name 0,16..0,17>]}
>>> from pprint import pp
>>> pp(FST('''
... def func(arg: int = default) -> object:
...     global mod0, declared_global  # this will make the import global
...     nonlocal aug
...     loc = arg
...     del delete
...     aug += implicit_nonlocal
...     import mod0
...     import mod1.submod
...     import mod2 as asname1
...     from mod3 import mod_name1
...     from mod4 import mod_name2 as asname2
...     from mod5 import *
...     return str(arg)
... '''.strip()).scope_symbols(full=True, import_star=True))
{'load': {'arg': [<Name 3,10..3,13>, <Name 12,15..12,18>],
          'aug': [<Name 5,4..5,7>],
          'implicit_nonlocal': [<Name 5,11..5,28>],
          'str': [<Name 12,11..12,14>]},
 'store': {'arg': [<arg 0,9..0,17>],
           'loc': [<Name 3,4..3,7>],
           'aug': [<Name 5,4..5,7>],
           'mod0': [<alias 6,11..6,15>],
           'mod1': [<alias 7,11..7,22>],
           'asname1': [<alias 8,11..8,26>],
           'mod_name1': [<alias 9,21..9,30>],
           'asname2': [<alias 10,21..10,41>],
           '*': [<alias 11,21..11,22>]},
 'del': {'delete': [<Name 4,8..4,14>]},
 'global': {'mod0': [<Global 1,4..1,32>],
            'declared_global': [<Global 1,4..1,32>]},
 'nonlocal': {'aug': [<Nonlocal 2,4..2,16>]},
 'local': {'arg': [<arg 0,9..0,17>],
           'loc': [<Name 3,4..3,7>],
           'mod1': [<alias 7,11..7,22>],
           'asname1': [<alias 8,11..8,26>],
           'mod_name1': [<alias 9,21..9,30>],
           'asname2': [<alias 10,21..10,41>],
           '*': [<alias 11,21..11,22>]},
 'free': {'implicit_nonlocal': [<Name 5,11..5,28>],
          'str': [<Name 12,11..12,14>]}}
def walk( self: FST, all: Union[bool, Literal['loc'], type[ast.AST], Container[type[ast.AST]]] = False, *, self_: bool = True, recurse: bool = True, scope: bool = False, back: bool = False) -> Generator[FST, bool, NoneType]:

Walk self and descendants in syntactic order.

When walking, you can send(False) to the generator to skip recursion into the current child. send(True) will allow recursion into child if called with recurse=False or scope=True would otherwise disallow it. Can send multiple times, last value sent takes effect.

The walk is defined forwards or backwards in that it returns a parent, then recurses into the children and walks those in the given direction, recursing into each child's children before continuing with siblings. Walking backwards will not generate the same sequence as list(walk())[::-1] due to this behavior.

Node replacement and removal during the walk is supported with some caveats, the rules are:

  • raw operations can change a lot of nodes and cause the walk to miss some you thought would get walked, but they will not cause the walk to break.
  • The current node can always be removed, replaced or inserted before (if list field). If replaced the new children will be walked next unless you explicitly send(False) to the generator.
  • Child nodes of the current node can be replaced and they will be walked when the walk gets to them.
  • Previously walked nodes can likewise be removed, replaced or inserted before.
  • Replacing or removing a node in the current parent chain is allowed and will cause the walk to continue at its following siblings which were not modified.
  • Sibling nodes of either this node or any parents which have not been walked yet can be removed, replaced or inserted before but the new nodes will not be walked (and neither will any removed nodes).

Note: About scopes, the NamedExpr (walrus) expression is treated specially in a Comprehension (capital 'C' to differentiate from the node type comprehension). The target of the operation actually belongs to the first non-Comprehension enclosing scope. For this reason, when a walk recurses into a Comprehension scope the walrus target nodes are still returned even though everything else belongs to the Comprehension scope and is not returned (except for the first comprehension.iter, which also belongs to the enclosing scope). This remains true for whatever level of nesting of Comprehensions is recursed into. One quirk, if starting a scope walk on a Comprehension, any walrus targets WILL be returned, the first iterator though will not. This is on purpose.

Parameters:

  • all: Whether to return all nodes or only specific types.
    • True: All nodes will be returned.
    • False: Only nodes which have intrinsic AST locations and also larger calculated location nodes like comprehension, withitem, match_case and arguments (the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).
    • 'loc': Same as True but always returns arguments even if empty (as it has a location even then). Also operators with calculated locations (excluding and and or since they do not always have a well defined location).
    • type[AST]: A singe LEAF AST type to return. This will not constrain the walk, just filter which nodes are returned.
    • Container[type[AST]]: A container of LEAF AST types to return. Best container type is a set, frozenset or dict with the keys being the AST classes as those are the fastest checks. This will not constrain the walk, just filter which nodes are returned.
  • self_: If True then self will be returned first with the possibility to skip children with send(False), otherwise will start directly with children.
  • recurse: Whether to recurse past the first level of children by default, send(True) for a given node will always override this. Meaning that if False, will only return self (if allowed to by self_) and the the immediate children of self and no deeper.
  • scope: If True then will walk only within the scope of self. Meaning if called on a FunctionDef then will only walk children which are within the function scope. Will yield children which have their own scopes, and the parts of them which are visible in this scope (like default argument values), but will not recurse into them unless send(True) is done for that child.
  • back: If True then walk every node in reverse syntactic order. This is not the same as a full forwards walk reversed due to recursion (parents are still returned before children, only in reverse sibling order).

Examples:

Normal walk.

>>> f = FST('[pre, [child], post]')
>>> for g in (gen := f.walk()):
...     print(f'{g!r:<23}{g.src[:47]}')
...
...     if g.is_List and g.elts[0].src == 'send_False':
...         _ = gen.send(False)
<List ROOT 0,0..0,20>  [pre, [child], post]
<Name 0,1..0,4>        pre
<List 0,6..0,13>       [child]
<Name 0,7..0,12>       child
<Name 0,15..0,19>      post

Reject recursion into a child.

>>> f = FST('[pre, [send_False], post]')
>>> for g in (gen := f.walk()):
...     print(f'{g!r:<23}{g.src[:47]}')
...
...     if g.is_List and g.elts[0].src == 'send_False':
...         _ = gen.send(False)
<List ROOT 0,0..0,25>  [pre, [send_False], post]
<Name 0,1..0,4>        pre
<List 0,6..0,18>       [send_False]
<Name 0,20..0,24>      post

Walk nodes that are part of a scope. We walk with self_=False just to not print the top level mutliline function, the rest of the walk over the children just goes as normal.

>>> f = FST('''
... def func(func_arg=func_def) -> bool:
...     def sub(sub_arg=sub_def) -> int: pass
...     val = [i := j for j in iterator]
... '''.strip())
>>> for g in f.walk(self_=False, scope=True):
...     print(f'{g!r:<25}{g.src[:47]}')
<arguments 0,9..0,26>    func_arg=func_def
<arg 0,9..0,17>          func_arg
<FunctionDef 1,4..1,41>  def sub(sub_arg=sub_def) -> int: pass
<Name 1,20..1,27>        sub_def
<Name 1,32..1,35>        int
<Assign 2,4..2,36>       val = [i := j for j in iterator]
<Name 2,4..2,7>          val
<ListComp 2,10..2,36>    [i := j for j in iterator]
<Name 2,11..2,12>        i
<Name 2,27..2,35>        iterator

Replace all Name nodes.

>>> import ast
>>> f = FST('a * (x.y + u[v])')
>>> for g in f.walk(all=ast.Name):
...     _ = g.replace('new_' + g.id)
>>> print(f.src)
new_a * (new_x.y + new_u[new_v])

Replace nodes around us. The replacement doesn't have to be executed on the node being walked, it can be on any node. Note how replacing a node that hasn't been walked yet removes both that node AND the replacement node from the walk. After the walk though, all nodes which were replaced have their new values.

>>> f = FST('[pre_parent, [pre_self, [child], post_self], post_parent]')
>>> for g in f.walk():
...     print(f'{g!r:<23}{g.src[:57]}')
...
...     if g.src == '[child]':
...         _ = f.elts[0].replace('new_pre_parent')
...         _ = f.elts[2].replace('new_post_parent')
...         _ = f.elts[1].elts[0].replace('new_pre_self')
...         _ = f.elts[1].elts[2].replace('new_post_self')
...         _ = f.elts[1].elts[1].elts[0].replace('new_child')
<List ROOT 0,0..0,57>  [pre_parent, [pre_self, [child], post_self], post_parent]
<Name 0,1..0,11>       pre_parent
<List 0,13..0,43>      [pre_self, [child], post_self]
<Name 0,14..0,22>      pre_self
<List 0,24..0,31>      [child]
<Name 0,33..0,42>      new_child
>>> print(f.src)
[new_pre_parent, [new_pre_self, [new_child], new_post_self], new_post_parent]

Replacing or removing a parent node is allowed and the walk will continue where it can.

>>> f = FST('[pre_grand, [pre_parent, [self], post_parent], post_grand]')
>>> for g in f.walk():
...     print(f'{g!r:<23}{g.src[:58]}')
...
...     if g.src == 'self':
...         g.parent.parent.remove()  # [pre_parent, [self], post_parent]
<List ROOT 0,0..0,58>  [pre_grand, [pre_parent, [self], post_parent], post_grand]
<Name 0,1..0,10>       pre_grand
<List 0,12..0,45>      [pre_parent, [self], post_parent]
<Name 0,13..0,23>      pre_parent
<List 0,25..0,31>      [self]
<Name 0,26..0,30>      self
<Name 0,12..0,22>      post_grand
>>> print(f.src)
[pre_grand, post_grand]
def next( self: FST, all: Union[bool, Literal['loc'], type[ast.AST], Container[type[ast.AST]]] = False) -> FST | None:

Get next sibling of self in syntactic order, only within parent.

Parameters:

  • all: Whether to return all nodes or only specific types.
    • True: All nodes will be returned.
    • False: Only nodes which have intrinsic AST locations and also larger calculated location nodes like comprehension, withitem, match_case and arguments (the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).
    • 'loc': Same as True but always returns arguments even if empty (as it has a location even then). Also operators with calculated locations (excluding and and or since they do not always have a well defined location).
    • type[AST]: A singe LEAF AST type to return. This will not constrain the walk, just filter which nodes are returned.
    • Container[type[AST]]: A container of LEAF AST types to return. Best container type is a set, frozenset or dict with the keys being the AST classes as those are the fastest checks.

Returns:

  • None if last valid sibling in parent, otherwise next node.

Examples:

>>> f = FST('[[1, 2], [3, 4]]')
>>> f.elts[0].next().src
'[3, 4]'
>>> print(f.elts[1].next())
None
def prev( self: FST, all: Union[bool, Literal['loc'], type[ast.AST], Container[type[ast.AST]]] = False) -> FST | None:

Get previous sibling of self in syntactic order, only within parent.

Parameters:

  • all: Whether to return all nodes or only specific types.
    • True: All nodes will be returned.
    • False: Only nodes which have intrinsic AST locations and also larger calculated location nodes like comprehension, withitem, match_case and arguments (the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).
    • 'loc': Same as True but always returns arguments even if empty (as it has a location even then). Also operators with calculated locations (excluding and and or since they do not always have a well defined location).
    • type[AST]: A singe LEAF AST type to return. This will not constrain the walk, just filter which nodes are returned.
    • Container[type[AST]]: A container of LEAF AST types to return. Best container type is a set, frozenset or dict with the keys being the AST classes as those are the fastest checks.

Returns:

  • None if first valid sibling in parent, otherwise previous node.

Examples:

>>> f = FST('[[1, 2], [3, 4]]')
>>> f.elts[1].prev().src
'[1, 2]'
>>> print(f.elts[0].prev())
None
def first_child( self: FST, all: Union[bool, Literal['loc'], type[ast.AST], Container[type[ast.AST]]] = False) -> FST | None:

Get first valid child in syntactic order.

Parameters:

  • all: Whether to return all nodes or only specific types.
    • True: All nodes will be returned.
    • False: Only nodes which have intrinsic AST locations and also larger calculated location nodes like comprehension, withitem, match_case and arguments (the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).
    • 'loc': Same as True but always returns arguments even if empty (as it has a location even then). Also operators with calculated locations (excluding and and or since they do not always have a well defined location).
    • type[AST]: A singe LEAF AST type to return. This will not constrain the walk, just filter which nodes are returned.
    • Container[type[AST]]: A container of LEAF AST types to return. Best container type is a set, frozenset or dict with the keys being the AST classes as those are the fastest checks.

Returns:

  • None if no valid children, otherwise first valid child.

Examples:

>>> f = FST('def f(a: list[str], /, reject: int, *c, d=100, **e): pass')
>>> f.first_child().src
'a: list[str], /, reject: int, *c, d=100, **e'
>>> f.args.first_child().src
'a: list[str]'
>>> f.args.first_child().first_child().src
'list[str]'
def last_child( self: FST, all: Union[bool, Literal['loc'], type[ast.AST], Container[type[ast.AST]]] = False) -> FST | None:

Get last valid child in syntactic order.

Parameters:

  • all: Whether to return all nodes or only specific types.
    • True: All nodes will be returned.
    • False: Only nodes which have intrinsic AST locations and also larger calculated location nodes like comprehension, withitem, match_case and arguments (the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).
    • 'loc': Same as True but always returns arguments even if empty (as it has a location even then). Also operators with calculated locations (excluding and and or since they do not always have a well defined location).
    • type[AST]: A singe LEAF AST type to return. This will not constrain the walk, just filter which nodes are returned.
    • Container[type[AST]]: A container of LEAF AST types to return. Best container type is a set, frozenset or dict with the keys being the AST classes as those are the fastest checks.

Returns:

  • None if no valid children, otherwise last valid child.

Examples:

>>> f = FST('def f(a: list[str], /, reject: int, *c, d=100, **e): pass')
>>> f.last_child().src
'pass'
>>> f.args.last_child().src
'e'
def last_header_child( self: FST, all: Union[bool, Literal['loc'], type[ast.AST], Container[type[ast.AST]]] = False) -> FST | None:

Get last valid child in syntactic order in a block header (before the :), e.g. the something in if something: pass.

Parameters:

  • all: Whether to return all nodes or only specific types.
    • True: All nodes will be returned.
    • False: Only nodes which have intrinsic AST locations and also larger calculated location nodes like comprehension, withitem, match_case and arguments (the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).
    • 'loc': Same as True but always returns arguments even if empty (as it has a location even then). Also operators with calculated locations (excluding and and or since they do not always have a well defined location).
    • type[AST]: A singe LEAF AST type to return. This will not constrain the walk, just filter which nodes are returned.
    • Container[type[AST]]: A container of LEAF AST types to return. Best container type is a set, frozenset or dict with the keys being the AST classes as those are the fastest checks.

Returns:

  • None if no valid children or if self is not a block statement, otherwise last valid child in the block header.

Examples:

>>> print(FST('if something:\n    i = 2\n    i = 3')
...       .last_header_child().src)
something
>>> print(FST('try: pass\nexcept Exception as exc: pass').handlers[0]
...       .last_header_child().src)
Exception
>>> print(FST('with a, b: pass').last_header_child().src)
b
>>> print(FST('try: pass\nfinally: pass').last_header_child())
None
>>> print(FST('i = 1').last_header_child())
None
def next_child( self: FST, from_child: FST | None, all: Union[bool, Literal['loc'], type[ast.AST], Container[type[ast.AST]]] = False) -> FST | None:

Get the next child in syntactic order, meant for simple iteration.

This is a slower way to iterate vs. walk(), but will walk any modified future sibling nodes not yet walked as long as the replaced node and its parent is used for the following call.

Parameters:

  • all: Whether to return all nodes or only specific types.
    • True: All nodes will be returned.
    • False: Only nodes which have intrinsic AST locations and also larger calculated location nodes like comprehension, withitem, match_case and arguments (the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).
    • 'loc': Same as True but always returns arguments even if empty (as it has a location even then). Also operators with calculated locations (excluding and and or since they do not always have a well defined location).
    • type[AST]: A singe LEAF AST type to return. This will not constrain the walk, just filter which nodes are returned.
    • Container[type[AST]]: A container of LEAF AST types to return. Best container type is a set, frozenset or dict with the keys being the AST classes as those are the fastest checks.

Returns:

  • None if last valid child in self, otherwise next child node.

Examples:

>>> f = FST('[[1, 2], [3, 4]]')
>>> f.next_child(f.elts[0]).src
'[3, 4]'
>>> print(f.next_child(f.elts[1]))
None
>>> f = FST('[this, is_, reparsed, each, step, and_, still, walks, ok]')
>>> n = None
>>> while n := f.next_child(n):
...     if n.is_Name:
...         n = n.replace(n.id[::-1])
>>> f.src
'[siht, _si, desraper, hcae, pets, _dna, llits, sklaw, ko]'
def prev_child( self: FST, from_child: FST | None, all: Union[bool, Literal['loc'], type[ast.AST], Container[type[ast.AST]]] = False) -> FST | None:

Get the previous child in syntactic order, meant for simple iteration.

This is a slower way to iterate vs. walk() but will walk any modified future sibling nodes not yet walked as long as the replaced node and its parent is used for the following call.

Parameters:

  • all: Whether to return all nodes or only specific types.
    • True: All nodes will be returned.
    • False: Only nodes which have intrinsic AST locations and also larger calculated location nodes like comprehension, withitem, match_case and arguments (the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).
    • 'loc': Same as True but always returns arguments even if empty (as it has a location even then). Also operators with calculated locations (excluding and and or since they do not always have a well defined location).
    • type[AST]: A singe LEAF AST type to return. This will not constrain the walk, just filter which nodes are returned.
    • Container[type[AST]]: A container of LEAF AST types to return. Best container type is a set, frozenset or dict with the keys being the AST classes as those are the fastest checks.

Returns:

  • None if first valid child in self, otherwise previous child node.

Examples:

>>> f = FST('[[1, 2], [3, 4]]')
>>> f.prev_child(f.elts[1]).src
'[1, 2]'
>>> print(f.prev_child(f.elts[0]))
None
>>> f = FST('[this, is_, reparsed, each, step, and_, still, walks, ok]')
>>> n = None
>>> while n := f.prev_child(n):
...     if n.is_Name:
...         n = n.replace(n.id[::-1])
>>> f.src
'[siht, _si, desraper, hcae, pets, _dna, llits, sklaw, ko]'
def step_fwd( self: FST, all: Union[bool, Literal['loc'], type[ast.AST], Container[type[ast.AST]]] = False, recurse_self: bool = True) -> FST | None:

Step forward in the tree in syntactic order, as if walk()ing forward, NOT the inverse of step_back(). Will walk up parents and down children to get the next node, returning None only when we are at the end of the whole thing.

Parameters:

  • all: Whether to return all nodes or only specific types.
    • True: All nodes will be returned.
    • False: Only nodes which have intrinsic AST locations and also larger calculated location nodes like comprehension, withitem, match_case and arguments (the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).
    • 'loc': Same as True but always returns arguments even if empty (as it has a location even then). Also operators with calculated locations (excluding and and or since they do not always have a well defined location).
    • type[AST]: A singe LEAF AST type to return. This will not constrain the walk, just filter which nodes are returned.
    • Container[type[AST]]: A container of LEAF AST types to return. Best container type is a set, frozenset or dict with the keys being the AST classes as those are the fastest checks.
  • recurse_self: Whether to allow recursion into self to return children or move directly to next nodes of self on start.

Returns:

  • None if last valid node in tree, otherwise next node in order.

Examples:

>>> f = FST('[[1, 2], [3, 4]]')
>>> f.elts[0].src
'[1, 2]'
>>> f.elts[0].step_fwd().src
'1'
>>> f.elts[0].step_fwd(recurse_self=False).src
'[3, 4]'
>>> f.elts[0].elts[1].src
'2'
>>> f.elts[0].elts[1].step_fwd().src
'[3, 4]'
>>> f = FST('[this, [is_, [reparsed, each], step, and_, still], walks, ok]')
>>> n = f.elts[0]
>>> while True:
...     if n.is_Name:
...         n = n.replace(n.id[::-1])
...     if not (n := n.step_fwd()):
...         break
>>> f.src
'[siht, [_si, [desraper, hcae], pets, _dna, llits], sklaw, ko]'

Example from walk() but using this method all modified nodes are walked.

>>> f = FST('[pre_parent, [pre_self, [child], post_self], post_parent]')
>>> g = f
>>> while True:
...     print(f'{g!r:<23}{g.src[:57]}')
...
...     if g.src == '[child]':
...         _ = f.elts[0].replace('new_pre_parent')
...         _ = f.elts[2].replace('new_post_parent')
...         _ = f.elts[1].elts[0].replace('new_pre_self')
...         _ = f.elts[1].elts[2].replace('new_post_self')
...         _ = f.elts[1].elts[1].elts[0].replace('new_child')
...
...     if not (g := g.step_fwd()):
...         break
<List ROOT 0,0..0,57>  [pre_parent, [pre_self, [child], post_self], post_parent]
<Name 0,1..0,11>       pre_parent
<List 0,13..0,43>      [pre_self, [child], post_self]
<Name 0,14..0,22>      pre_self
<List 0,24..0,31>      [child]
<Name 0,33..0,42>      new_child
<Name 0,45..0,58>      new_post_self
<Name 0,61..0,76>      new_post_parent
>>> print(f.src)
[new_pre_parent, [new_pre_self, [new_child], new_post_self], new_post_parent]
def step_back( self: FST, all: Union[bool, Literal['loc'], type[ast.AST], Container[type[ast.AST]]] = False, recurse_self: bool = True) -> FST | None:

Step backward in the tree in syntactic order, as if walk()ing backward, NOT the inverse of step_fwd(). Will walk up parents and down children to get the next node, returning None only when we are at the beginning of the whole thing.

Parameters:

  • all: Whether to return all nodes or only specific types.
    • True: All nodes will be returned.
    • False: Only nodes which have intrinsic AST locations and also larger calculated location nodes like comprehension, withitem, match_case and arguments (the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).
    • 'loc': Same as True but always returns arguments even if empty (as it has a location even then). Also operators with calculated locations (excluding and and or since they do not always have a well defined location).
    • type[AST]: A singe LEAF AST type to return. This will not constrain the walk, just filter which nodes are returned.
    • Container[type[AST]]: A container of LEAF AST types to return. Best container type is a set, frozenset or dict with the keys being the AST classes as those are the fastest checks.
  • recurse_self: Whether to allow recursion into self to return children or move directly to previous nodes of self on start.

Returns:

  • None if first valid node in tree, otherwise previous node in order.

Examples:

>>> f = FST('[[1, 2], [3, 4]]')
>>> f.elts[1].src
'[3, 4]'
>>> f.elts[1].step_back().src
'4'
>>> f.elts[1].step_back(recurse_self=False).src
'[1, 2]'
>>> f.elts[1].elts[0].src
'3'
>>> f.elts[1].elts[0].step_back().src
'[1, 2]'
>>> f = FST('[this, [is_, [reparsed, each], step, and_, still], walks, ok]')
>>> n = f.elts[-1]
>>> while True:
...     if n.is_Name:
...         n = n.replace(n.id[::-1])
...     if not (n := n.step_back()):
...         break
>>> f.src
'[siht, [_si, [desraper, hcae], pets, _dna, llits], sklaw, ko]'
def parents(self, self_: bool = False) -> Generator[FST, NoneType, NoneType]:

Generator which yields parents all the way up to root. If self_ is True then will yield self first.

Parameters:

  • self_: Whether to yield self first.

Returns:

  • Generator: Will walk up the parent chain.

Examples:

>>> list(FST('i = (f(), g())', 'exec').body[0].value.elts[0].parents())
[<Tuple 0,4..0,14>, <Assign 0,0..0,14>, <Module ROOT 0,0..0,14>]
>>> list(FST('i = (f(), g())', 'exec').body[0].value.elts[0].parents(self_=True))
[<Call 0,5..0,8>, <Tuple 0,4..0,14>, <Assign 0,0..0,14>, <Module ROOT 0,0..0,14>]
def parent_stmt(self, self_: bool = False, mod: bool = True) -> FST | None:

The first parent which is a stmt or optionally mod node (if any). If self_ is True then will check self first (possibly returning self), otherwise only checks parents.

Parameters:

  • self_: Whether to include self in the search, if so and self matches criteria then it is returned.
  • mod: Whether to return mod nodes if found.

Returns:

  • FST | None: First stmt or optionally mod parent if present, else None.

Examples:

>>> FST('if 1: i = 1', 'exec').body[0].body[0].value.parent_stmt()
<Assign 0,6..0,11>
>>> FST('if 1: i = 1', 'exec').body[0].body[0].parent_stmt()
<If 0,0..0,11>
>>> FST('if 1: i = 1', 'exec').body[0].parent_stmt()
<Module ROOT 0,0..0,11>
>>> print(FST('if 1: i = 1', 'exec').body[0].parent_stmt(mod=False))
None
>>> FST('if 1: i = 1', 'exec').body[0].parent_stmt(self_=True)
<If 0,0..0,11>
def parent_stmtlike(self, self_: bool = False, mod: bool = True) -> FST | None:

The first parent which is a stmt, ExceptHandler, match_case or optionally mod node (if any). If self_ is True then will check self first, otherwise only checks parents.

Returns:

  • FST | None: First stmtlike or optionally mod parent if present, else None.

Examples:

>>> (FST('try: pass\nexcept: pass', 'exec')
...  .body[0].handlers[0].body[0].parent_stmtlike())
<ExceptHandler 1,0..1,12>
>>> FST('try: pass\nexcept: pass', 'exec').body[0].handlers[0].parent_stmtlike()
<Try 0,0..1,12>
>>> FST('try: pass\nexcept: pass', 'exec').body[0].parent_stmtlike()
<Module ROOT 0,0..1,12>
>>> FST('match a:\n  case 1: pass').cases[0].body[0].parent_stmtlike()
<match_case 1,2..1,14>
>>> FST('match a:\n  case 1: pass').cases[0].pattern.parent_stmtlike()
<match_case 1,2..1,14>
def parent_block(self, self_: bool = False, mod: bool = True) -> FST | None:

The first parent which opens a block that self lives in (if any). Types include FunctionDef, AsyncFunctionDef, ClassDef, For, AsyncFor, While, If, With, AsyncWith, Match, Try, TryStar, ExceptHandler, match_case or optionally mod node (if any). If self_ is True then will check self first, otherwise only checks parents.

Returns:

  • FST | None: First block stmt or optionally mod parent if present, else None.

Examples:

>>> FST('if 1: i = 1', 'exec').body[0].body[0].value.parent_block()
<If 0,0..0,11>
>>> FST('if 1: i = 1', 'exec').body[0].parent_block()
<Module ROOT 0,0..0,11>
def parent_scope(self, self_: bool = False, mod: bool = True) -> FST | None:

The first parent which opens a scope that self lives in (if any). Types include FunctionDef, AsyncFunctionDef, ClassDef, Lambda, ListComp, SetComp, DictComp, GeneratorExp or optionally mod node (if any). If self_ is True then will check self first, otherwise only checks parents.

Returns:

  • FST | None: First scope stmt or optionally mod parent if present, else None.

Examples:

>>> FST('if 1: i = 1', 'exec').body[0].body[0].value.parent_scope()
<Module ROOT 0,0..0,11>
>>> (FST('def f():\n  if 1: i = 1', 'exec')
...  .body[0].body[0].body[0].value.parent_scope())
<FunctionDef 0,0..1,13>
>>> FST('lambda: None', 'exec').body[0].value.body.parent_scope()
<Lambda 0,0..0,12>
>>> FST('[i for i in j]', 'exec').body[0].value.elt.parent_scope()
<ListComp 0,0..0,14>
def parent_named_scope(self, self_: bool = False, mod: bool = True) -> FST | None:

The first parent which opens a named scope that self lives in (if any). Types include FunctionDef, AsyncFunctionDef, ClassDef or optionally mod node (if any). If self_ is True then will check self first, otherwise only checks parents.

Returns:

  • FST | None: First named scope stmt or optionally mod parent if present, else None.

Examples:

>>> FST('if 1: i = 1', 'exec').body[0].body[0].value.parent_named_scope()
<Module ROOT 0,0..0,11>
>>> (FST('def f():\n  if 1: i = 1', 'exec')
...  .body[0].body[0].body[0].value.parent_named_scope())
<FunctionDef 0,0..1,13>
>>> (FST('def f(): lambda: None', 'exec')
...  .body[0].body[0].value.body.parent_named_scope())
<FunctionDef 0,0..0,21>
>>> (FST('class cls: [i for i in j]', 'exec')
...  .body[0].body[0].value.elt.parent_named_scope())
<ClassDef 0,0..0,25>
def parent_non_expr(self, self_: bool = False, strict: bool = False) -> FST | None:

The first parent which is not an expr. If self_ is True then will check self first (possibly returning self), otherwise only checks parents.

Parameters:

  • self_: Whether to include self in the search, if so and self matches criteria then it is returned.
  • strict: False means consider comprehension, arguments, arg and keyword nodes as expr for the sake of the walk up since these nodes can have other expr parents (meaning they will be skipped). True means only expr nodes, which means you could get an arg or comprehension node for example which still has expr parents. Also expr_context, boolop, operator, unaruop and cmpop are included if strict=False but this only makes sense if self_=True and you are calling this function on one of those.

Returns:

  • FST | None: First non-expr parent if present, possibly skipping mentioned nodes according to strict, else None.

Examples:

>>> FST('if 1: i = 1 + a[b]').body[0].value.right.value.parent_non_expr()
<Assign 0,6..0,18>
>>> (FST('match a:\n case {a.b.c: 1}: pass')
...  .cases[0].pattern.keys[0].value.value.parent_non_expr())
<MatchMapping 1,6..1,16>
>>> FST('var = call(a, b=1)').value.keywords[0].value.parent_non_expr()
<Assign ROOT 0,0..0,18>
>>> (FST('var = call(a, b=1)')
...  .value.keywords[0].value.parent_non_expr(strict=True))
<keyword 0,14..0,17>
def parent_pattern(self, self_: bool = False) -> FST | None:

The first parent which is a pattern. If self_ is True then will check self first (possibly returning self), otherwise only checks parents.

Parameters:

  • self_: Whether to include self in the search, if so and self matches criteria then it is returned.

Returns:

  • FST | None: First pattern parent if present, else None.

Examples:

>>> FST('case 1+1j: pass').pattern.value.left.parent_pattern().src
'1+1j'
>>> (FST('case 1 | {a.b: c}: pass')
...  .pattern.patterns[1].patterns[0].parent_pattern(self_=True))
<MatchAs 0,15..0,16>
>>> (FST('case 1 | {a.b: c}: pass')
...  .pattern.patterns[1].patterns[0].parent_pattern())
<MatchMapping 0,9..0,17>
>>> (FST('case 1 | {a.b: c}: pass')
...  .pattern.patterns[1].patterns[0].parent_pattern().parent_pattern())
<MatchOr 0,5..0,17>
def parent_ftstr(self, self_: bool = False) -> FST | None:

The first parent which is an f or t-string. If self_ is True then will check self first (possibly returning self), otherwise only checks parents. This can be used to determine if an expression (or anything else like lambda arguments) are ultimately inside one of these.

Returns:

  • FST | None: First JoinedStr or TemplateStr parent if present, else None.

Parameters:

  • self_: Whether to include self in the search, if so and self matches criteria then it is returned.

Examples:

>>> bool(FST('f"{a}"').values[0].value.parent_ftstr())
True
>>> bool(FST('f"{a}"').parent_ftstr())
False
>>> bool(FST('f"{a}"').parent_ftstr(self_=True))
True
>>> bool(FST('f"{a:{b}}"').values[0].format_spec.parent_ftstr(self_=True))
True
def child_path( self, child: FST, as_str: bool = False) -> list[fst.common.astfield] | str:

Get path to child node from self which can later be used on a copy of this tree to get to the same relative child node.

Note: This function is intentionally made to work for nodes which have been removed from a tree (because their parent links are not severed). This is useful for finding a node if it has been replaced by a raw reparse.

Parameters:

  • child: Child node to get path to, can be self in which case an empty path is returned.
  • as_str: If True will return the path as a python-ish string suitable for attribute access, else a list of astfields which can be used more directly.

Returns:

  • list[astfield] | str: Path to child if exists, otherwise raises.

Examples:

>>> (f := FST('[i for i in j]', 'exec')).child_path(f.body[0].value.elt)
[astfield('body', 0), astfield('value'), astfield('elt')]
>>> ((f := FST('[i for i in j]', 'exec'))
...  .child_path(f.body[0].value.elt, as_str=True))
'body[0].value.elt'
>>> (f := FST('i')).child_path(f)
[]
>>> (f := FST('i')).child_path(f, as_str=True)
''
def child_from_path( self, path: list[fst.common.astfield] | str, last_valid: bool = False) -> Union[FST, Literal[False]]:

Get child node specified by path if it exists. If succeeds then it doesn't mean that the child node is guaranteed to be the same or even same type as was originally used to get the path, just that the path is valid. For example after deleting an element from a list the item at the former element's location will be the previous next element.

Parameters:

  • path: Path to child as a list of astfields or string.
  • last_valid: If True then return the last valid node along the path, will not fail, can return self.

Returns:

  • FST: Child node if path is valid, otherwise False if path invalid. False and not None because None can be in a field that can hold an AST but False can not.

Examples:

>>> f = FST('[i for i in j]', 'exec')
>>> f.child_from_path(f.child_path(f.body[0].value.elt)).src
'i'
>>> f.child_from_path(f.child_path(f.body[0].value.elt, True)).src
'i'
>>> FST('[0, 1, 2, 3]', 'exec').child_from_path('body[0].value.elts[4]')
False
>>> (FST('[0, 1, 2, 3]', 'exec')
...  .child_from_path('body[0].value.elts[4]', last_valid=True).src)
'[0, 1, 2, 3]'
>>> (f := FST('i')).child_from_path([]) is f
True
>>> (f := FST('i')).child_from_path('') is f
True
def repath(self) -> FST:

Recalculate self from path from root. Useful if self has been replaced by another node by some operation. When nodes are deleted the corresponding FST.a and AST.f attributes are set to None. The parent and pfield attributes are left so that things like this can work. Useful when a node has been deleted but you want to know where it was and what may be there now.

Returns:

  • FST: Possibly self or the node which took our place at our relative position from root.

Examples:

>>> f = FST('[0, 1, 2, 3]')
>>> g = f.elts[1]
>>> print(type(g.a), g.root)
<class 'ast.Constant'> <List ROOT 0,0..0,12>
>>> f.put('x', 1, raw=True)  # raw forces reparse at List
<List ROOT 0,0..0,12>
>>> print(g.is_alive, g.a, g.root)
False None <List ROOT 0,0..0,12>
>>> g = g.repath()
>>> print(g.is_alive, type(g.a), g.root)
True <class 'ast.Name'> <List ROOT 0,0..0,12>
def find_contains_loc( self, ln: int, col: int, end_ln: int, end_col: int, allow_exact: Union[bool, Literal['top']] = True) -> FST | None:

Find the lowest level node which entirely contains location (starting search at self). The search will only find nodes at self or below, no parents.

Parameters:

  • ln: Start line of location to search for (0 based).
  • col: Start column (character) on start line.
  • end_ln: End line of location to search for (0 based, inclusive).
  • end_col: End column (character, inclusive with FST.end_col, exclusive with FST.col) on end line.
  • allow_exact: Whether to allow return of exact location match with node or not. True means allow return of node which matches location exactly. False means location must be inside the node but cannot be touching BOTH ends of the node. This basically determines whether you can get the exact node of the location or its parent. A value of 'top' specifies an exact match is allowed and return the highest level node with the match, otherwise the lowest level exact match is returned. This only applies to nodes like Expr which will have the same location as the contained expr or a Module which only contains a single statement without any other junk like comments or empty lines surrounding it.

Returns:

  • FST | None: Node which entirely contains location, either exactly or not, or None if no such node.

Examples:

>>> FST('i = val', 'exec').find_contains_loc(0, 6, 0, 7)
<Name 0,4..0,7>
>>> FST('i = val', 'exec').find_contains_loc(0, 4, 0, 7)
<Name 0,4..0,7>
>>> FST('i = val', 'exec').find_contains_loc(0, 4, 0, 7, allow_exact=False)
<Assign 0,0..0,7>
>>> FST('i = val', 'exec').find_contains_loc(0, 5, 0, 7, allow_exact=False)
<Name 0,4..0,7>
>>> FST('i = val', 'exec').find_contains_loc(0, 4, 0, 6, allow_exact=False)
<Name 0,4..0,7>
>>> FST('i = val', 'exec').find_contains_loc(0, 3, 0, 7)
<Assign 0,0..0,7>
>>> FST('i = val', 'exec').find_contains_loc(0, 3, 0, 7, allow_exact=False)
<Assign 0,0..0,7>
>>> print(FST('i = val', 'exec').find_contains_loc(0, 0, 0, 7, allow_exact=False))
None
>>> FST('i = val\n', 'exec').find_contains_loc(0, 0, 0, 7, allow_exact=False)
<Module ROOT 0,0..1,0>
>>> FST('i = val', 'exec').find_contains_loc(0, 0, 0, 7)
<Assign 0,0..0,7>
>>> FST('i = val', 'exec').find_contains_loc(0, 0, 0, 7, allow_exact='top')
<Module ROOT 0,0..0,7>
def find_in_loc(self, ln: int, col: int, end_ln: int, end_col: int) -> FST | None:

Find the first highest level node which is contained entirely in location (inclusive, starting search at self). The search will only find nodes at self or below, no parents.

Parameters:

  • ln: Start line of location to search (0 based).
  • col: Start column (character) on start line.
  • end_ln: End line of location to search (0 based, inclusive).
  • end_col: End column (character, inclusive with FST.end_col, exclusive with FST.col) on end line.

Returns:

  • FST | None: First node in syntactic order which is entirely contained in the location or None if no such node.

Examples:

>>> FST('i = val', 'exec').find_in_loc(0, 0, 0, 7)
<Module ROOT 0,0..0,7>
>>> FST('i = val', 'exec').find_in_loc(0, 1, 0, 7)
<Name 0,4..0,7>
>>> FST('i = val', 'exec').find_in_loc(0, 4, 0, 7)
<Name 0,4..0,7>
>>> print(FST('i = val', 'exec').find_in_loc(0, 5, 0, 7))
None
def find_loc( self, ln: int, col: int, end_ln: int, end_col: int, exact_top: bool = False) -> FST | None:

Find node which best fits location. If an exact location match is found then that is returned with the top parameter specifying which of multiple nodes at same location is returned if that is the case. Otherwise find_in_loc() is preferred if there is a match and if not then find_contains_loc(). The search is done efficiently so that the same nodes are not walked multiple times.

Parameters:

  • ln: Start line of location to search for (0 based).
  • col: Start column (character) on start line.
  • end_ln: End line of location to search for (0 based, inclusive).
  • end_col: End column (character, inclusive with FST.end_col, exclusive with FST.col) on end line.
  • exact_top: If an exact location match is found and multiple nodes share this location (Expr and expr) then this decides whether the highest level node is returned or the lowest (True means highest).

Returns:

  • FST | None: Node found if any.

Examples:

>>> FST('var', 'exec').find_loc(0, 0, 0, 3)
<Name 0,0..0,3>
>>> FST('var', 'exec').find_loc(0, 0, 0, 3, exact_top=True)
<Module ROOT 0,0..0,3>
>>> FST('var', 'exec').find_loc(0, 1, 0, 2)
<Name 0,0..0,3>
>>> FST('var', 'exec').find_loc(0, 1, 0, 2, exact_top=True)
<Name 0,0..0,3>
>>> FST('var', 'exec').find_loc(0, 0, 1, 4)
<Module ROOT 0,0..0,3>
>>> repr(FST('var', 'exec').find_loc(0, 2, 1, 4))
'None'
def is_parenthesizable(self, *, star: bool = True) -> bool:

Whether self is parenthesizable with grouping parentheses or not. True for all patterns and almost all exprs (those which which are not Slice, FormattedValue or Interpolation and are not themselves inside patterns).

Note: Starred may return True even though the Starred itself is not parenthesizable but rather its child is.

Parameters:

  • star: Whether to return top-level Starred as parenthesizable or not. It is convenient to consider it parenthesizable like a normal expression since its children are parenthesizable and that is what gets parenthesized if you try to par() a Starred.

Returns:

  • bool: Whether is syntactically legal to add grouping parentheses or not. Can always be forced.

Examples:

>>> FST('i + j').is_parenthesizable()  # expr
True
>>> FST('{a.b: c, **d}', 'pattern').is_parenthesizable()
True
>>> FST('a:b:c').is_parenthesizable()  # Slice
False
>>> FST('for i in j').is_parenthesizable()  # comprehension
False
>>> FST('a: int, b=2', 'arguments').is_parenthesizable()  # arguments
False
>>> FST('a: int', 'arg').is_parenthesizable()
False
>>> FST('key="word"', 'keyword').is_parenthesizable()
False
>>> FST('a as b', 'alias').is_parenthesizable()
False
>>> f = FST('with a: pass')
>>> f.items[0].is_parenthesizable()  # withitem is not parenthesizable
False
>>> f.items[0].context_expr.is_parenthesizable()  # the expr in it is
True
>>> f = FST('case 1: pass')
>>> f.pattern.is_parenthesizable()  # pattern is parenthesizable
True
>>> f.pattern.value.is_parenthesizable()  # expr in pattern is not
False
def is_parenthesized_tuple(self) -> bool | None:

Whether self is a parenthesized Tuple or not, or not a Tuple at all.

Returns:

  • True: Is a parenthesized Tuple node.
  • False: Is an unparenthesized Tuple node.
  • None: Is not a Tuple node.

Examples:

>>> FST('1, 2').is_parenthesized_tuple()
False
>>> FST('(1, 2)').is_parenthesized_tuple()
True
>>> print(FST('1').is_parenthesized_tuple())
None
def is_delimited_matchseq(self) -> Optional[Literal['', '[]', '()']]:

Whether self is a delimited MatchSequence or not (parenthesized or bracketed), or not a MatchSequence at all.

Returns:

  • '()' or '[]': Is a MatchSequence delimited with these delimiters.
  • '': Is an undelimited MatchSequence.
  • None: Is not a MatchSequence.

Examples:

>>> FST('case 1, 2: pass').pattern.is_delimited_matchseq()
''
>>> FST('case [1, 2]: pass').pattern.is_delimited_matchseq()
'[]'
>>> FST('case (1, 2): pass').pattern.is_delimited_matchseq()
'()'
>>> print(FST('case 1: pass').pattern.is_delimited_matchseq())
None
def is_empty_arguments(self) -> bool | None:

Is this an empty arguments node or not an arguments node at all?

Returns:

  • True: Is an empty arguments node.
  • False: Is an arguments node but not empty.
  • None: Is not an arguments node.

Examples:

>>> print(FST('def f(): pass').args.is_empty_arguments())
True
>>> print(FST('def f(a, b): pass').args.is_empty_arguments())
False
>>> print(FST('def f(): pass').body[0].is_empty_arguments())
None
def is_except_star(self) -> bool | None:

Whether self is an except* ExceptHandler or a normal ExceptHandler, or not and ExceptHandler at all.

Note: This function checks the source, not the parent Try or TryStar node so it will give the correct answer even for top-level ExceptHandler nodes cut out of their blocks.

Returns:

  • True: Is an except* ExceptHandler.
  • False: Is a normal ExceptHandler.
  • None: Is not an ExceptHandler.

Examples:

>>> import sys
>>> if sys.version_info[:2] >= (3, 11):
...     f = FST('try: pass\nexcept* Exception: pass')
...     print(f.handlers[0].is_except_star())
... else:
...     print(True)
True
>>> if sys.version_info[:2] >= (3, 11):
...     f = FST('try: pass\nexcept Exception: pass')
...     print(f.handlers[0].is_except_star())
... else:
...     print(False)
False
>>> print(FST('i = 1').is_except_star())
None
def is_elif(self) -> bool | None:

Whether self is an elif or not, or not an If at all.

Returns:

  • True: Is an elif If.
  • False: Is a normal If.
  • None: Is not an If.

Examples:

>>> FST('if 1: pass\nelif 2: pass').orelse[0].is_elif()
True
>>> FST('if 1: pass\nelse:\n  if 2: pass').orelse[0].is_elif()
False
>>> print(FST('if 1: pass\nelse:\n  i = 2').orelse[0].is_elif())
None
is_mod: bool

Is a mod node.

is_stmt: bool

Is a stmt node.

is_expr: bool

Is an expr node.

is_expr_context: bool

Is an expr_context node.

is_boolop: bool

Is a boolop node.

is_operator: bool

Is an operator node.

is_unaryop: bool

Is a unaryop node.

is_cmpop: bool

Is a cmpop node.

is_excepthandler: bool

Is a excepthandler node.

is_pattern: bool

Is a pattern node.

is_type_ignore: bool

Is a type_ignore node.

is_type_param: bool

Is a type_param node.

is_stmt_or_mod: bool

Is a stmt or mod node.

is_stmtlike: bool

Is a stmt, ExceptHandler or match_case node.

is_stmtlike_or_mod: bool

Is a stmt, ExceptHandler, match_case or mod node.

is_block: bool

Is a node which opens a block. Types include FunctionDef, AsyncFunctionDef, ClassDef, For, AsyncFor, While, If, With, AsyncWith, Match, Try, TryStar, ExceptHandler or match_case.

is_block_or_mod: bool

Is a node which opens a block. Types include FunctionDef, AsyncFunctionDef, ClassDef, For, AsyncFor, While, If, With, AsyncWith, Match, Try, TryStar, ExceptHandler, match_case or mod.

is_scope: bool

Is a node which opens a scope. Types include FunctionDef, AsyncFunctionDef, ClassDef, Lambda, ListComp, SetComp, DictComp or GeneratorExp.

is_scope_or_mod: bool

Is a node which opens a scope. Types include FunctionDef, AsyncFunctionDef, ClassDef, Lambda, ListComp, SetComp, DictComp, GeneratorExp or mod.

is_named_scope: bool

Is a node which opens a named scope. Types include FunctionDef, AsyncFunctionDef or ClassDef.

is_named_scope_or_mod: bool

Is a node which opens a named scope. Types include FunctionDef, AsyncFunctionDef, ClassDef or mod.

is_anon_scope: bool

Is a node which opens an anonymous scope. Types include Lambda, ListComp, SetComp, DictComp or GeneratorExp.

is_funcdef: bool

Is a sync or async function definition node, FunctionDef or AsyncFunctionDef.

is_def: bool

Is a sync or async function or class definition node, FunctionDef, AsyncFunctionDef or ClassDef.

is_def_or_mod: bool

Is a sync or async function or class definition node, FunctionDef, AsyncFunctionDef, ClassDef or mod.

is_for: bool

Is a sync or async for node, For or AsyncFor, different from is_For or is_AsyncFor.

is_with: bool

Is a sync or async with node, With or AsyncWith, different from is_For or is_AsyncFor.

is_try: bool

Is a Try or TryStar node, different from is_Try or is_TryStar.

is_import: bool

Is an Import or ImportFrom node, different from is_Import or is_ImportFrom.

is_ftstr: bool

Is an f-string JoinedStr or t-string TemplateStr node.

is_ftstr_fmt: bool

Is an f-string FormattedValue or t-string Interpolation node.

is__slice: bool

Is one of our own custom SPECIAL SLICE nodes.

is_Module: bool

Is a Module node.

is_Interactive: bool

Is a Interactive node.

is_Expression: bool

Is a Expression node.

is_FunctionType: bool

Is a FunctionType node.

is_FunctionDef: bool

Is a FunctionDef node.

is_AsyncFunctionDef: bool

Is a AsyncFunctionDef node.

is_ClassDef: bool

Is a ClassDef node.

is_Return: bool

Is a Return node.

is_Delete: bool

Is a Delete node.

is_Assign: bool

Is a Assign node.

is_TypeAlias: bool

Is a TypeAlias node.

is_AugAssign: bool

Is a AugAssign node.

is_AnnAssign: bool

Is a AnnAssign node.

is_For: bool

Is a For node.

is_AsyncFor: bool

Is a AsyncFor node.

is_While: bool

Is a While node.

is_If: bool

Is a If node.

is_With: bool

Is a With node.

is_AsyncWith: bool

Is a AsyncWith node.

is_Match: bool

Is a Match node.

is_Raise: bool

Is a Raise node.

is_Try: bool

Is a Try node.

is_TryStar: bool

Is a TryStar node.

is_Assert: bool

Is a Assert node.

is_Import: bool

Is a Import node.

is_ImportFrom: bool

Is a ImportFrom node.

is_Global: bool

Is a Global node.

is_Nonlocal: bool

Is a Nonlocal node.

is_Expr: bool

Is a Expr node.

is_Pass: bool

Is a Pass node.

is_Break: bool

Is a Break node.

is_Continue: bool

Is a Continue node.

is_BoolOp: bool

Is a BoolOp node.

is_NamedExpr: bool

Is a NamedExpr node.

is_BinOp: bool

Is a BinOp node.

is_UnaryOp: bool

Is a UnaryOp node.

is_Lambda: bool

Is a Lambda node.

is_IfExp: bool

Is a IfExp node.

is_Dict: bool

Is a Dict node.

is_Set: bool

Is a Set node.

is_ListComp: bool

Is a ListComp node.

is_SetComp: bool

Is a SetComp node.

is_DictComp: bool

Is a DictComp node.

is_GeneratorExp: bool

Is a GeneratorExp node.

is_Await: bool

Is a Await node.

is_Yield: bool

Is a Yield node.

is_YieldFrom: bool

Is a YieldFrom node.

is_Compare: bool

Is a Compare node.

is_Call: bool

Is a Call node.

is_FormattedValue: bool

Is a FormattedValue node.

is_Interpolation: bool

Is a Interpolation node.

is_JoinedStr: bool

Is a JoinedStr node.

is_TemplateStr: bool

Is a TemplateStr node.

is_Constant: bool

Is a Constant node.

is_Attribute: bool

Is a Attribute node.

is_Subscript: bool

Is a Subscript node.

is_Starred: bool

Is a Starred node.

is_Name: bool

Is a Name node.

is_List: bool

Is a List node.

is_Tuple: bool

Is a Tuple node.

is_Slice: bool

Is a Slice node.

is_Load: bool

Is a Load node.

is_Store: bool

Is a Store node.

is_Del: bool

Is a Del node.

is_And: bool

Is a And node.

is_Or: bool

Is a Or node.

is_Add: bool

Is a Add node.

is_Sub: bool

Is a Sub node.

is_Mult: bool

Is a Mult node.

is_MatMult: bool

Is a MatMult node.

is_Div: bool

Is a Div node.

is_Mod: bool

Is a Mod node.

is_Pow: bool

Is a Pow node.

is_LShift: bool

Is a LShift node.

is_RShift: bool

Is a RShift node.

is_BitOr: bool

Is a BitOr node.

is_BitXor: bool

Is a BitXor node.

is_BitAnd: bool

Is a BitAnd node.

is_FloorDiv: bool

Is a FloorDiv node.

is_Invert: bool

Is a Invert node.

is_Not: bool

Is a Not node.

is_UAdd: bool

Is a UAdd node.

is_USub: bool

Is a USub node.

is_Eq: bool

Is a Eq node.

is_NotEq: bool

Is a NotEq node.

is_Lt: bool

Is a Lt node.

is_LtE: bool

Is a LtE node.

is_Gt: bool

Is a Gt node.

is_GtE: bool

Is a GtE node.

is_Is: bool

Is a Is node.

is_IsNot: bool

Is a IsNot node.

is_In: bool

Is a In node.

is_NotIn: bool

Is a NotIn node.

is_comprehension: bool

Is a comprehension node.

is_ExceptHandler: bool

Is a ExceptHandler node.

is_arguments: bool

Is a arguments node.

is_arg: bool

Is a arg node.

is_keyword: bool

Is a keyword node.

is_alias: bool

Is a alias node.

is_withitem: bool

Is a withitem node.

is_match_case: bool

Is a match_case node.

is_MatchValue: bool

Is a MatchValue node.

is_MatchSingleton: bool

Is a MatchSingleton node.

is_MatchSequence: bool

Is a MatchSequence node.

is_MatchMapping: bool

Is a MatchMapping node.

is_MatchClass: bool

Is a MatchClass node.

is_MatchStar: bool

Is a MatchStar node.

is_MatchAs: bool

Is a MatchAs node.

is_MatchOr: bool

Is a MatchOr node.

is_TypeIgnore: bool

Is a TypeIgnore node.

is_TypeVar: bool

Is a TypeVar node.

is_ParamSpec: bool

Is a ParamSpec node.

is_TypeVarTuple: bool

Is a TypeVarTuple node.

is__ExceptHandlers: bool

Is a _ExceptHandlers node.

is__match_cases: bool

Is a _match_cases node.

is__Assign_targets: bool

Is a _Assign_targets node.

is__decorator_list: bool

Is a _decorator_list node.

is__arglikes: bool

Is a _arglikes node.

is__comprehensions: bool

Is a _comprehensions node.

is__comprehension_ifs: bool

Is a _comprehension_ifs node.

is__aliases: bool

Is a _aliases node.

is__withitems: bool

Is a _withitems node.

is__type_params: bool

Is a _type_params node.

Virtual _all field view for Dict, MatchMapping, Compare and arguments.

Examples:

>>> FST('{a: b, c: d, e: f}')._all[1:2].copy().src
'{c: d}'
>>> FST('{1: a, 2: b, **rest}', 'MatchMapping')._all[1:].copy().src
'{2: b, **rest}'
>>> FST('a < b == c > d')._all[1:3].copy().src
'b == c'
>>> FST('def f(a=1, *b, c: int = 3, **d): pass').args._all[-2:].copy().src
'*, c: int = 3, **d'

Virtual _body field for statements. Special field view for statements which excludes a docstring if present from the list and indexing.

Examples:

>>> f = FST('''
... def func():
...     \'\'\'docstring\'\'\'
...     first_stmt()
...     second_stmt()
... '''.strip())
>>> f._body[0].src
'first_stmt()'
>>> list(f.src for f in reversed(f._body))
['second_stmt()', 'first_stmt()']
>>> f._body[0] = 'replacement_stmt'
>>> print(f.src)
def func():
    '''docstring'''
    replacement_stmt
    second_stmt()

Virtual _args field view for merged Call.args+keywords.

Examples:

>>> list(f.src for f in FST('call(a, *not b, c=d, *e, **h, f=g)')._args)
['a', '*not b', 'c=d', '*e', '**h', 'f=g']
>>> FST('call(a, *not b, c=d, *e, **h, f=g)')._args[1:-1].copy().src
'*not b, c=d, *e, **h'
>>> FST('call(a, b, c, d)').put_slice('k=w, *s', 1, 'end').src
'call(a, k=w, *s)'
>>> FST('call(a, b)').put('k=w', 0)
Traceback (most recent call last):
...
fst.NodeError: keyword arglike cannot precede positional arglike

Virtual _bases field view for merged ClassDef.bases+keywords.

Examples:

>>> list(f.src for f in FST('class cls(a, *not b, c=d, *e, **h): pass')._bases)
['a', '*not b', 'c=d', '*e', '**h']
>>> FST('class cls(base, meta=cls, *other_bases): pass')._bases[-2:].copy().src
'meta=cls, *other_bases'
>>> f = FST('class cls(base, meta=cls): pass')
>>> del f._bases
>>> print(f.src)
class cls: pass
body: Union[fst.view.fstview, FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field body.

type_ignores: fst.view.fstview

FST accessor for AST field type_ignores.

argtypes: fst.view.fstview

FST accessor for AST field argtypes.

returns: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field returns.

decorator_list: fst.view.fstview

FST accessor for AST field decorator_list.

name: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field name.

type_params: fst.view.fstview

FST accessor for AST field type_params.

args: Union[fst.view.fstview, FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field args.

type_comment: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field type_comment.

FST accessor for AST field bases.

keywords: fst.view.fstview

FST accessor for AST field keywords.

value: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field value.

targets: fst.view.fstview

FST accessor for AST field targets.

target: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field target.

op: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field op.

annotation: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field annotation.

simple: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field simple.

iter: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field iter.

orelse: Union[fst.view.fstview, FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field orelse.

test: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field test.

FST accessor for AST field items.

subject: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field subject.

FST accessor for AST field cases.

exc: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field exc.

cause: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field cause.

handlers: fst.view.fstview

FST accessor for AST field handlers.

finalbody: fst.view.fstview

FST accessor for AST field finalbody.

msg: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field msg.

FST accessor for AST field names.

module: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field module.

level: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field level.

FST accessor for AST field values.

left: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field left.

right: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field right.

operand: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field operand.

FST accessor for AST field keys.

FST accessor for AST field elts.

elt: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field elt.

generators: fst.view.fstview

FST accessor for AST field generators.

key: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field key.

FST accessor for AST field ops.

comparators: fst.view.fstview

FST accessor for AST field comparators.

func: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field func.

conversion: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field conversion.

format_spec: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field format_spec.

str: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field str.

kind: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field kind.

attr: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field attr.

ctx: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field ctx.

slice: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field slice.

id: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field id.

lower: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field lower.

upper: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field upper.

step: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field step.

FST accessor for AST field ifs.

is_async: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field is_async.

type: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field type.

posonlyargs: fst.view.fstview

FST accessor for AST field posonlyargs.

defaults: fst.view.fstview

FST accessor for AST field defaults.

vararg: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field vararg.

kwonlyargs: fst.view.fstview

FST accessor for AST field kwonlyargs.

kw_defaults: fst.view.fstview

FST accessor for AST field kw_defaults.

kwarg: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field kwarg.

arg: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field arg.

asname: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field asname.

context_expr: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field context_expr.

optional_vars: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field optional_vars.

pattern: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field pattern.

guard: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field guard.

patterns: fst.view.fstview

FST accessor for AST field patterns.

rest: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field rest.

cls: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field cls.

kwd_attrs: fst.view.fstview

FST accessor for AST field kwd_attrs.

kwd_patterns: fst.view.fstview

FST accessor for AST field kwd_patterns.

tag: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field tag.

bound: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field bound.

default_value: Union[FST, NoneType, ellipsis, int, float, complex, str, bytes, bool]

FST accessor for AST field default_value.

arglikes: fst.view.fstview

FST accessor for AST field arglikes.