fst.fst
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 normalastmodule parse modes'exec','eval'or'single', or an extendedfstparse mode parameter which allows parsing things theastmodule cannot, Seefst.parsex.Mode.type_comments:ast.parse()parameter.feature_version:ast.parse()parameter.
Returns:
AST: Tree with anFST.fattribute added to eachASTnode.
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
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: TheASTto unparse.
Returns:
str: The unparsed source code, formatted if it came fromFST.
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'
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.
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'
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 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.
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 anASTorFSTnode.mode: Seefst.parsex.Mode. Has the following meanings when thesrc_or_ast_or_fstparameter is:src: IfmodeisNonethen 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: IfmodeisNonethen defaults to the type of theAST, which will always succeed in generating valid source unless theASTitself is in an invalid state (like an emptySet). Anything else you pass here will be passed on as themodetofromast(). In which case if theASTis this then theFSTtree is just build for it and otherwise coercion will be attempted to this type.FST:modeshould be specified and will be the type of node you want to coerce theFSTyou pass in to. If you leave it at the defaultNonethen you will just get either the sameFSTor a copy of it, depending on thecopykwarg.
kwargs:optionsif coercing from anotherFSTor extra parameters, mostly for internal use documented below. One extra parameter relevant to this use case 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:ASTnode forFSTor source code in the form of astror a list of lines. If anASTthen will be processed differently depending on if creating child node, top level node or using this as a shortcut for a fullfromsrc()orfromast(). If left as empty default then just creates a new emptyModule.mode: Is reallymode_or_lines_or_parent. Parent node for this child node or lines for a root node creating a new tree. IfpfieldisFalsethen this is a shortcut to create a full tree from anASTnode or source provided insrc_or_ast_or_fst.pfield:fst.common.astfieldindication position in parent of this node. If provided then creating a simple child node and it is created with theself.parentset tomodenode andself.pfieldset to this. IfNonethen it means the creation of a full newFSTtree and this is the root node withmodeproviding the source. IfFalsethen this is a shortcut forFST.fromsrc()orFST.fromast().kwargs: Contextual parameters:from_: If this is provided then it must be anFSTnode 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: Adictwith values forfilename,type_commentsandfeature_versionwhich will be used for anyASTreparse 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 fromfrom_then indentation will be inferred from source. Only valid when creating a root node.filename,type_commentsandfeature_version: If creating from anASTor source only then these are the parameteres passed to the respective.fromsrc()or.fromast()functions. Only valid whenpfieldisFalse, meaning a shortcut use ofFST().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 offst.astutil.bistrand this node takes ownership of the list.tmake: Whether to do_make_fst_tree()onselfor not. Turning this off can be used to create a root node for a specific existingFSTtree. Useful for optimizing some operations.
The fst.common.astfield location of this node in the parent, None in root node.
The parameters to use for any ast.parse() (filename, type_comments, feature_version). Exists mostly for passing filename and future-proofing.
The default single level block indentation string for this tree when not available from context.
Allows to quickly differentiate between actual FST nodes vs. views or locations.
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.
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.
Whole source location, from (0, 0) to end of source. Works from any node (not just root).
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.
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)
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.
AST-style line number of the first line of this node (1 based), available for all nodes which have loc
(otherwise None).
AST-style BYTE index of the start of this node (0 based), available for all nodes which have loc
(otherwise None).
AST-style line number of the LAST LINE of this node (1 based), available for all nodes which have loc
(otherwise None).
AST-style BYTE index one past the end of this node (0 based), available for all nodes which have loc
(otherwise None).
Indicates whether this node has a docstring. Can only be True for FunctionDef, AsyncFunctionDef,
ClassDef or Module. For quick use as start index.
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 singlestror list of individual line strings (without newlines).mode: Parse mode, extendedast.parse()parameter, seefst.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:
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
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 rootASTnode. This is NOT consumed and will NOT become the actualASTnode of theFSTtree.mode: Parse mode to enable coercion, seefst.parsex.Mode. IfNonethen will just use the givenasttype and make anFSTusing 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:
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
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}
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: Theoptionvalue from the passedoptionsdict, if passed and notNonethere, else the global default value foroption.
Examples:
>>> FST.get_option('pars')
'auto'
>>> FST.get_option('pars', {'pars': True})
True
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, seeoptions().
Returns:
old_options:dictof previous values of changed parameters, reset withset_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}
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. DEFAULTTrue: 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 areNonein 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 compatibleAST/FSTnode types on put. For example allow put a non-sliceexpras a slice to something that expects a slice ofexprs or allowing use ofargwhereargumentsis expected.False: Do not allow node type coercion, meant for strict type control.True: Allow coercion between similar types. DEFAULT
elif_: How to handle loneIfstatements as the only statements in anIfstatementorelsefield.False: Always put as a standaloneIfstatement on put.True: If putting a singleIfstatement to anorelsefield of a parentIfstatement then put it as anelif. 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. DEFAULT1: One empty line in all scopes.
docstr: Which docstrings are indentable / dedentable.False: None.True: AllExprstring 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 beFalse,Trueor'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 forparsbehavior 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 asTrueexcept 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 / copiedNamedExprnodes or not. If parentheses were already copied due topars=Truethen setting this toFalsewill not remove them. This is more of an aesthetic option asfstcan deal with unparenthesized walruses at root level.True: Parenthesize cut / copiedNamedExprwalrus expressions.False: Do not parenthesize cut / copiedNamedExprwalrus expressions. DEFAULTNone: Parenthesize according to theparsoption (Trueand'auto'parenthesize,Falsedoes 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 toFalsewill not remove them. Unlikepars_walrusthis is NOT mostly an aesthetic option as unparenthesized arglike-only expressions are invalid everywhere except inCall.args,ClassDef.basesor an unparenthesizedSubscript.sliceTuple.True: Parenthesize cut / copied argumentlike expressions. DEFAULTFalse: Do not parenthesize cut / copied argumentlike expressions.None: Parenthesize according to theparsoption (Trueand'auto'parenthesize,Falsedoes not).
norm: Default normalize option for return fromget()functions andselftarget. Determines howASTs 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-lengthSet. This option can be overridden individually for the two cases ofnorm_self(target) andnorm_get(return from any get operation).False: Allow theASTto go to an unsupported length or state and become invalid. ASetwill result in empty curlies which reparse to aDict. AMatchOrcan go to 1 or zero length. OtherASTtypes can also go to zero length. Useful for easier editing. DEFAULTTrue: Do not allow theASTto go to an unsupported length or state. Can result in an exception being thrown if no alternative exists. ASetwhich results in zero length gets converted to{*()}.str: In some cases an alternate representation can be specified instead of justTrue, e.g.'call'forSetoperations.
norm_self: Override fornormwhich only applies to a targetselfon which an operation is being carried out. If this isNonethennormis used.norm_get: Override fornormwhich only applies to the return value from any get operation. If this isNonethennormis used.set_norm: The alternate representation for an emptySetnormalization bynorm. This can also be set toFalseto disable normalization for all operations on aSet(unless one of thenormoptions is set to one of these string modes).'star': Starred sequence{*()}returned or used for emptyself. DEFAULT'call':set()call returned and used for emptyself.False: NoSetnormalization regardless ofnormornorm_*options, just leave or return an invalidSetobject.
op_side: When doing slice operations on aBoolOpor aCompareit 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 aComparea non-globalopoption 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 onargumentnode slice operations. This is mostly meant for per-call use but is a global option in order to allowwith 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 toposonlyargsif possible, if not then error. Ifvarargorkwargpresent then will error.'arg': Convert all arguments toargsif possible, if not then error. Avarargis allowed but if present then will prevent any presentkwonlyargsfrom being converted and in this case will error. Ifkwargis present then will error.'kw': Convert all arguments tokwonlyargsif possible, if not then error. Akwargis allowed and will not prevent any conversion as it is always follows all other arguments. Ifvarargis present then will error.'arg_only': Same asargexcept does not allow avarargand if present will error.'kw_only': Same askwexcept does not allow akwargand if present will error.'pos_maybe': Attempt to convert all arguments toposonlyargs.argscan always be converted but ifkwonlyargscannot be because of avarargor default value incompatibilities then they will not be converted and there will not be an error.kwargis left in place.'arg_maybe': Attempt to convert all arguments toargs.posonlyargscan always be converted but ifkwonlyargscannot be because of avarargor default value incompatibilities then they will not be converted and there will not be an error.kwargis left in place.'kw_maybe': Attempt to convert all arguments tokwonlyargs. Ifvarargis present theposonlyargsandargsare not converted, butposonlyargswill be converted in this case toargs.kwargis 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
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,Nonemeans no coerce and just returnself, seefst.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: Seeoptions(). 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
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: Newselfafter reparse, if possible, otherwiseNoneif could not be found. May also be another neighbor node if the source ofselfwas structurally changed too much. If used on root node thenselfwill 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
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 ifNonethen use the top levelASTnode 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 (Nonespecifies this). Seefst.parsex.Mode.reparse: Whether to reparse the source and compareASTs (including location). Otherwise the check is limited to a structure check that all children haveFSTnodes which are all liked correctly to their parents.reparse=Trueonly allowed on root node.locs: Whether to compare locations after reparse or not.ctx: Whether to comparectxnodes after reparse or not.raise_: Whether to raise an exception on verify failed or returnNone.
Returns:
Noneon failure to verify (if notraise_), otherwiseself.
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 ('?', '?', '?', '?') / ('?', '?', '?', '?')
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 (includingExceptHandlerandmatch_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+'.Nonedoes 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+'meanssrc='stmt'orsrc='stmt+''S'same as's+''n'or'n+'meanssrc='node'orsrc='node+''N'same as'N+''f'meansfull=True'e'meansexpand=True'i'meanslist_indent=indent'I'meanslist_indent=False'L'meansloc=False'c'meanscolor=True'C'meanscolor=False
full: IfTruethen will list all fields in nodes including empty ones, otherwise will exclude most empty fields.expand: IfFalsethen the output is a nice compact representation. IfTruethen 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). IfTruethen will be same asindent.loc: Whether to dump locations of nodes or not.color:TrueorFalsemeans whether to use ANSI color codes or not. IfNonethen will autodetect, which can be overridden with environment variablesFORCE_COLORandNO_COLOR.out:'print'means print to stdout,'lines'returns a list of lines and'str'returns a whole string. ATextIOobject here will use thewritemethod for each line of output. Otherwise aCallable[[str], None]which is called for each line of output individually. If'str'or'lines'then that is returned, otherwiseselfis returned for chaining.eol: What to put at the end of each text line,Nonemeans newline for aTextIOout and nothing for other types ofout.
Returns:
str: If was requested without='str', string of entire dump, lines ended witheol.list[str]: If was requested without='lines', list of lines ended wiitheol.self: Ifoutis 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"]
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
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: Thetriviaoption to use when putting anASTnode. SinceASTnodes have no trivia in practice this means how much of the existing trivia at the target node is deleted.Nonemeans use default.trivia_fst_put: Thetriviaoption to use when putting anFSTnode, which may or may not have attached trivia depending on thetrivia_fst_getoption. 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.Nonemeans use default.trivia_fst_get: Thetriviaoption to use when gettingFSTnodes 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.Nonemeans use default.cleanup: By default after a successful reconcileselfis cleanued up and no more reconcile can be attempted on it. It can be left in place to continue working on it andreconcile()again by passingcleanup=Falsebut 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 areelif_andpep8space. Seeoptions().
Returns:
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
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 orbuiltins.slicewhere to get the element(s) from.
Returns:
fstview | FST | str | None: Either a singleFSTnode if accessing a single item or a newfstviewview according to the slice passed.builtins.strcan also be returned from a view ofGlobal.namesorNonefrom aDict.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'
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 orbuiltins.slicewhere 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]'
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 orbuiltins.sliceto 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
'[]'
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 allExprmultiline strings (as they serve no coding purpose).'strict': Only dedentExprmultiline strings in standard docstring locations.None: Use the global default for thedocstroption.
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 andwhole=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',"
']'
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 allExprmultiline strings (as they serve no coding purpose).'strict': Only dedentExprmultiline strings in standard docstring locations.None: Use the global default for thedocstroption.
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 andwhole=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',
]
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
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: CopiedASTtree 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())
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 alloptionsrules apply.True: Copies the entire tree and source without any modifications (including leading and trailing source which is not part of the node), nooptionsare honored.False: For statementlike nodes will honor thetriviaoption and only copy allowed trivia source along with the node. For expressions and patterns the variousparsoptions 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: Seeoptions().
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'
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,ASTor sourcestrorlist[str]to put at this location.Noneto delete this node.one: DefaultTruemeans replace with a single element. IfFalseand field allows it then can replace single element with a slice.options: Seeoptions().to: Special option which only applies replacing inrawmode (either throughTrueor'auto'). Instead of replacing just this node, will replace the entire span from this node to the node specified intowith thecodepassed.
Returns:
FSTorNone: Returns the new node if successfully replaced orNoneif 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'
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,ASTor sourcestrorlist[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 isNone. Must be a list field.one: IfTruethen will insertcodeas a single item. OtherwiseFalsewill attempt a slice insertion (type must be compatible).options: Seefst.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]'
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,ASTor sourcestrorlist[str]to append.field: Field to operate on or the default field if isNone. Must be a list field.options: Seefst.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]'
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,ASTor sourcestrorlist[str]slice to extend.field: Field to operate on or the default field if isNone. Must be a list field.options: Seefst.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]'
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:
code:FST,ASTor sourcestrorlist[str]to preappend.options: Seefst.fst.FST.options().
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]'
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,ASTor sourcestrorlist[str]to extend at the start.field: Field to operate on or the default field if isNone. Must be a list field.options: Seefst.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]'
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 specifyingstop). If the field being gotten from is an individual element then this must beNone.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 beNone.field: The name of the field to get the element(s) from, which can be an individual element like avalueor a list likebody. If this isNonethen the default field for the node type is used. Most node types have a common-sense default field, e.g.bodyfor all block statements,valuefor things likeReturnandYield.Dict,MatchMappingandComparenodes have special handling for aNonefield (which defaults to the_allvirtual field for them).cut: Whether to cut out the child node (if possible) or not (just copy).options: Seeoptions().
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 fromName.id.constant: When getting a constant (fst.astutil.constant), like fromConstant.valueorMatchSingleton.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]'
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 anFST(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 likeMatchSingleton.valueorConstant.valuethen 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 specifyingstop). If the field being put to is an individual element then this must beNone.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 beNone.field: The name of the field to put the element(s) to, which can be an individual element like avalueor a list likebody. If this isNonethen the default field for the node type is used. Most node types have a common-sense default field, e.g.bodyfor all block statements,valuefor things likeReturnandYield.Dict,MatchMappingandComparenodes have special handling for aNonefield (which defaults to the_allvirtual field for them).one: Only has meaning if putting a slice and in this caseTruespecifies that the source should be put as a single element to the range specified even if it is a valid slice.Falseindicates 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: Seeoptions().to: Special option which only applies when putting a single element inrawmode (either throughTrueor'auto'). Instead of replacing just the target node, will replace the entire span from the target node to the node specified intowith thecodepassed.
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:
selforNoneif 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
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 pythona[start:]).field: The name of the field to get the elements from, which can be an individual element like avalueor a list likebody. If this isNonethen the default field for the node type is used. Most node types have a common-sense default field, e.g.bodyfor all block statements,eltsfor things likeListandTuple.Dict,MatchMappingandComparenodes have special handling for aNonefield (which defaults to the_allvirtual field for them).cut: Whether to cut out the slice or not (just copy).options: Seeoptions().
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
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 anFST(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 pythona[start:]).field: The name of the field to put the elements to. If this isNonethen the default field for the node type is used. Most node types have a common-sense default field, e.g.bodyfor all block statements,eltsfor things likeListandTuple.Dict,MatchMappingandComparenodes have special handling for aNonefield (which defaults to the_allvirtual field for them).one:Truespecifies that the source should be put as a single element to the range specified even if it is a valid slice.Falseindicates a true slice operation replacing the range with the slice passed, which must in this case be a compatible slice type.options: Seeoptions().
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:
selforNoneif 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
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: IfFalsethen source is returned as a single string with embedded newlines. IfTruethen source is returned as a list of line strings (without newlines).
Returns:
str | list[str]: A single string or a list of lines ifas_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'
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.FSTnodes in the region of the put or even outside of it can become invalid. The onlyFSTnode guaranteed not to change is the root node (identity, theASTit 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 startlnandcoland newly returnedend_lnandend_col. It is possible thatNoneis returned from these functions if no good candidate is found (since this can be used to delete or merge nodes).selfdoesn'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 theput_src()was called on. In this caseselfmatters and should be the last node down within which the location is contained. Any child nodes of this node are offset differently from thisselfnode and its parents.The
selfnode 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
selfnode 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 anFST(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 theASTtree, the options are'reparse','offset'orNone.
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))
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.Noneif 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???'
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 orNoneto delete.reput: IfTruethen remove old docstringExprfirst (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, seeoptions().
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
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: Ifselfis 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.Nonemeans 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 thefullparamenter.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'
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 thefullparameter.None: Delete current comment (if present).
field: Ifselfis 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.Nonemeans use default'body'if block statement.full:False: The gotten comment text is returned stripped of the'#'and any leading and trailing whitespace. The putcommenttext 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 putcommentMUST 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 thefullparamenter.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
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: IfTruethen 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. IfFalsethen does not return these and returns a count of -1, and thus the location is not a full validGeneratorExplocation. IfNonethen returns ANY directly enclosing parentheses, whether they belong to this node or not.
Returns:
fstloc | None: Location of enclosing parentheses if present elseself.bloc(which can beNone). Negative parentheses count (from shared pars solo call arg generator expression) can also be checked in the case ofshared=Falseviafst.pars() > fst.bloc. If only loc is returned, it will be anfstlocwhich 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)
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 anarg,withitemany kind ofstatementor anything which would cause a syntax error with the parentheses there, so noSlices or f-stringConstants 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, ifFalsethen 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))'
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 parenthesizedTupleand parentheses / brackets from parenthesized / bracketedMatchSequence. Also from a parentesizedStarredarglike-only expression which can cause it to become unparsable, e.g.*(a or b).'invalid': Remove intrinsic delimiters also fromList,Set,Dict,MatchMapping,ListComp,SetComp,DictCompandGeneratorExp.
shared: Whether to allow merge of parentheses of single call argument generator expression withCallparentheses or not. IfNonethen 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'
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. IfTruethen 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 ofGlobalnodes 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 singleglobaldeclaration can declare multiple names and the names themselves don't have their own individual nodes, they are just strings in theGlobalnode.'nonlocal': Similar toglobalbut fornonlocaldeclarations. 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 explicitlyglobalornonlocal. Note that this is slightly different from the python definition of a "free" variable as those can include explicitlynonlocalvariables. For our purposes here it is only the IMPLICIT nonlocal symbols.
local: Whether to compute the'local'category of symbols or not, set toFalseif you don't need this.free: Whether to compute the'free'category symbols or not, set toFalseif you don't need this.import_star: Normally afrom mod import *does not generate any symbols. If you pass this parameter asTruethen the*(invalid) name with itsaliasnode 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>]}}
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:
rawoperations 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 intrinsicASTlocations and also larger calculated location nodes likecomprehension,withitem,match_caseandarguments(the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).'loc': Same asTruebut always returnsargumentseven if empty (as it has a location even then). Also operators with calculated locations (excludingandandorsince they do not always have a well defined location).type[AST]: A singe LEAFASTtype to return. This will not constrain the walk, just filter which nodes are returned.Container[type[AST]]: A container of LEAFASTtypes to return. Best container type is aset,frozensetordictwith the keys being theASTclasses as those are the fastest checks. This will not constrain the walk, just filter which nodes are returned.
self_: IfTruethen self will be returned first with the possibility to skip children withsend(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 ifFalse, will only returnself(if allowed to byself_) and the the immediate children ofselfand no deeper.scope: IfTruethen will walk only within the scope ofself. Meaning if called on aFunctionDefthen 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 unlesssend(True)is done for that child.back: IfTruethen 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]
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 intrinsicASTlocations and also larger calculated location nodes likecomprehension,withitem,match_caseandarguments(the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).'loc': Same asTruebut always returnsargumentseven if empty (as it has a location even then). Also operators with calculated locations (excludingandandorsince they do not always have a well defined location).type[AST]: A singe LEAFASTtype to return. This will not constrain the walk, just filter which nodes are returned.Container[type[AST]]: A container of LEAFASTtypes to return. Best container type is aset,frozensetordictwith the keys being theASTclasses as those are the fastest checks.
Returns:
Noneif 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
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 intrinsicASTlocations and also larger calculated location nodes likecomprehension,withitem,match_caseandarguments(the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).'loc': Same asTruebut always returnsargumentseven if empty (as it has a location even then). Also operators with calculated locations (excludingandandorsince they do not always have a well defined location).type[AST]: A singe LEAFASTtype to return. This will not constrain the walk, just filter which nodes are returned.Container[type[AST]]: A container of LEAFASTtypes to return. Best container type is aset,frozensetordictwith the keys being theASTclasses as those are the fastest checks.
Returns:
Noneif 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
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 intrinsicASTlocations and also larger calculated location nodes likecomprehension,withitem,match_caseandarguments(the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).'loc': Same asTruebut always returnsargumentseven if empty (as it has a location even then). Also operators with calculated locations (excludingandandorsince they do not always have a well defined location).type[AST]: A singe LEAFASTtype to return. This will not constrain the walk, just filter which nodes are returned.Container[type[AST]]: A container of LEAFASTtypes to return. Best container type is aset,frozensetordictwith the keys being theASTclasses as those are the fastest checks.
Returns:
Noneif 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]'
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 intrinsicASTlocations and also larger calculated location nodes likecomprehension,withitem,match_caseandarguments(the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).'loc': Same asTruebut always returnsargumentseven if empty (as it has a location even then). Also operators with calculated locations (excludingandandorsince they do not always have a well defined location).type[AST]: A singe LEAFASTtype to return. This will not constrain the walk, just filter which nodes are returned.Container[type[AST]]: A container of LEAFASTtypes to return. Best container type is aset,frozensetordictwith the keys being theASTclasses as those are the fastest checks.
Returns:
Noneif 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'
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 intrinsicASTlocations and also larger calculated location nodes likecomprehension,withitem,match_caseandarguments(the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).'loc': Same asTruebut always returnsargumentseven if empty (as it has a location even then). Also operators with calculated locations (excludingandandorsince they do not always have a well defined location).type[AST]: A singe LEAFASTtype to return. This will not constrain the walk, just filter which nodes are returned.Container[type[AST]]: A container of LEAFASTtypes to return. Best container type is aset,frozensetordictwith the keys being theASTclasses as those are the fastest checks.
Returns:
Noneif no valid children or ifselfis 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
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 intrinsicASTlocations and also larger calculated location nodes likecomprehension,withitem,match_caseandarguments(the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).'loc': Same asTruebut always returnsargumentseven if empty (as it has a location even then). Also operators with calculated locations (excludingandandorsince they do not always have a well defined location).type[AST]: A singe LEAFASTtype to return. This will not constrain the walk, just filter which nodes are returned.Container[type[AST]]: A container of LEAFASTtypes to return. Best container type is aset,frozensetordictwith the keys being theASTclasses as those are the fastest checks.
Returns:
Noneif last valid child inself, 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]'
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 intrinsicASTlocations and also larger calculated location nodes likecomprehension,withitem,match_caseandarguments(the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).'loc': Same asTruebut always returnsargumentseven if empty (as it has a location even then). Also operators with calculated locations (excludingandandorsince they do not always have a well defined location).type[AST]: A singe LEAFASTtype to return. This will not constrain the walk, just filter which nodes are returned.Container[type[AST]]: A container of LEAFASTtypes to return. Best container type is aset,frozensetordictwith the keys being theASTclasses as those are the fastest checks.
Returns:
Noneif first valid child inself, 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]'
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 intrinsicASTlocations and also larger calculated location nodes likecomprehension,withitem,match_caseandarguments(the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).'loc': Same asTruebut always returnsargumentseven if empty (as it has a location even then). Also operators with calculated locations (excludingandandorsince they do not always have a well defined location).type[AST]: A singe LEAFASTtype to return. This will not constrain the walk, just filter which nodes are returned.Container[type[AST]]: A container of LEAFASTtypes to return. Best container type is aset,frozensetordictwith the keys being theASTclasses as those are the fastest checks.
recurse_self: Whether to allow recursion intoselfto return children or move directly to next nodes ofselfon start.
Returns:
Noneif 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]
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 intrinsicASTlocations and also larger calculated location nodes likecomprehension,withitem,match_caseandarguments(the last one only if there are actually arguments present). Operators are not returned (even though they have calculated location).'loc': Same asTruebut always returnsargumentseven if empty (as it has a location even then). Also operators with calculated locations (excludingandandorsince they do not always have a well defined location).type[AST]: A singe LEAFASTtype to return. This will not constrain the walk, just filter which nodes are returned.Container[type[AST]]: A container of LEAFASTtypes to return. Best container type is aset,frozensetordictwith the keys being theASTclasses as those are the fastest checks.
recurse_self: Whether to allow recursion intoselfto return children or move directly to previous nodes ofselfon start.
Returns:
Noneif 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]'
Generator which yields parents all the way up to root. If self_ is True then will yield self first.
Parameters:
self_: Whether to yieldselffirst.
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>]
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 includeselfin the search, if so andselfmatches criteria then it is returned.mod: Whether to returnmodnodes if found.
Returns:
FST | None: Firststmtor optionallymodparent if present, elseNone.
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>
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: Firststmtlikeor optionallymodparent if present, elseNone.
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>
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 blockstmtor optionallymodparent if present, elseNone.
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>
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 scopestmtor optionallymodparent if present, elseNone.
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>
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 scopestmtor optionallymodparent if present, elseNone.
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>
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 includeselfin the search, if so andselfmatches criteria then it is returned.strict:Falsemeans considercomprehension,arguments,argandkeywordnodes asexprfor the sake of the walk up since these nodes can have otherexprparents (meaning they will be skipped).Truemeans onlyexprnodes, which means you could get anargorcomprehensionnode for example which still hasexprparents. Alsoexpr_context,boolop,operator,unaruopandcmpopare included ifstrict=Falsebut this only makes sense ifself_=Trueand you are calling this function on one of those.
Returns:
FST | None: First non-exprparent if present, possibly skipping mentioned nodes according tostrict, elseNone.
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>
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 includeselfin the search, if so andselfmatches criteria then it is returned.
Returns:
FST | None: Firstpatternparent if present, elseNone.
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>
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: FirstJoinedStrorTemplateStrparent if present, elseNone.
Parameters:
self_: Whether to includeselfin the search, if so andselfmatches 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
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 beselfin which case an empty path is returned.as_str: IfTruewill return the path as a python-ish string suitable for attribute access, else a list ofastfields 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)
''
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 ofastfields or string.last_valid: IfTruethen return the last valid node along the path, will not fail, can returnself.
Returns:
FST: Child node if path is valid, otherwiseFalseif path invalid.Falseand notNonebecauseNonecan be in a field that can hold anASTbutFalsecan 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
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:
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>
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 withFST.end_col, exclusive withFST.col) on end line.allow_exact: Whether to allow return of exact location match with node or not.Truemeans allow return of node which matches location exactly.Falsemeans 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 likeExprwhich will have the same location as the containedexpror aModulewhich 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, orNoneif 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>
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 withFST.end_col, exclusive withFST.col) on end line.
Returns:
FST | None: First node in syntactic order which is entirely contained in the location orNoneif 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
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 withFST.end_col, exclusive withFST.col) on end line.exact_top: If an exact location match is found and multiple nodes share this location (Exprandexpr) then this decides whether the highest level node is returned or the lowest (Truemeans 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'
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-levelStarredas 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 topar()aStarred.
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
Whether self is a parenthesized Tuple or not, or not a Tuple at all.
Returns:
True: Is a parenthesizedTuplenode.False: Is an unparenthesizedTuplenode.None: Is not aTuplenode.
Examples:
>>> FST('1, 2').is_parenthesized_tuple()
False
>>> FST('(1, 2)').is_parenthesized_tuple()
True
>>> print(FST('1').is_parenthesized_tuple())
None
Whether self is a delimited MatchSequence or not (parenthesized or bracketed), or not a MatchSequence
at all.
Returns:
'()'or'[]': Is aMatchSequencedelimited with these delimiters.'': Is an undelimitedMatchSequence.None: Is not aMatchSequence.
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
Is this an empty arguments node or not an arguments node at all?
Returns:
True: Is an emptyargumentsnode.False: Is anargumentsnode but not empty.None: Is not anargumentsnode.
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
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 anexcept*ExceptHandler.False: Is a normalExceptHandler.None: Is not anExceptHandler.
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
Whether self is an elif or not, or not an If at all.
Returns:
True: Is anelifIf.False: Is a normalIf.None: Is not anIf.
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 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 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 a node which opens a scope. Types include FunctionDef, AsyncFunctionDef, ClassDef, Lambda,
ListComp, SetComp, DictComp or GeneratorExp.
Is a node which opens a scope. Types include FunctionDef, AsyncFunctionDef, ClassDef, Lambda,
ListComp, SetComp, DictComp, GeneratorExp or mod.
Is a node which opens a named scope. Types include FunctionDef, AsyncFunctionDef or ClassDef.
Is a node which opens a named scope. Types include FunctionDef, AsyncFunctionDef, ClassDef or mod.
Is a node which opens an anonymous scope. Types include Lambda, ListComp, SetComp, DictComp or
GeneratorExp.
Is a sync or async function or class definition node, FunctionDef, AsyncFunctionDef or ClassDef.
Is a sync or async function or class definition node, FunctionDef, AsyncFunctionDef, ClassDef or
mod.
Is a sync or async with node, With or AsyncWith, different from is_For or is_AsyncFor.
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
FST accessor for AST field type_comment.
FST accessor for AST field annotation.
FST accessor for AST field conversion.
FST accessor for AST field format_spec.
FST accessor for AST field context_expr.
FST accessor for AST field optional_vars.
FST accessor for AST field default_value.