Other

List of other features of RedBaron.

.parent

Every node and node list have a .parent attribute that points to the parent node or node list. If the node doesn’t have a parent node (for example the node list returned when constructing a new instance using the RedBaron class), the parent attribute is set at None. A new node or node list created using .copy() always have its parent attribute set at None.

The attribute on which the node is assigned on the parent node is store in the on_attribute attribute. on_attribute is set at "root" if the parent is a RedBaron instance.

In [1]: red = RedBaron("a = 1 + caramba")

In [2]: red.help()
0 -----------------------------------------------------
AssignmentNode()
  # identifiers: assign, assignment, assignment_, assignmentnode
  operator=''
  target ->
    NameNode()
      # identifiers: name, name_, namenode
      value='a'
  value ->
    BinaryOperatorNode()
      # identifiers: binary_operator, binary_operator_, binaryoperator, binaryoperatornode
      value='+'
      first ->
        IntNode() ...
      second ->
        NameNode() ...

In [3]: red.parent

In [4]: red.on_attribute

In [5]: red[0].parent
Out[5]: 0   a = 1 + caramba

In [6]: red[0].on_attribute
Out[6]: 'root'

In [7]: red[0].target.parent
Out[7]: a = 1 + caramba

In [8]: red[0].target.on_attribute
Out[8]: 'target'

In [9]: red[0].value.parent
Out[9]: a = 1 + caramba

In [10]: red[0].value.on_attribute
Out[10]: 'value'

In [11]: red[0].value.first.parent
Out[11]: 1 + caramba

In [12]: red[0].value.first.on_attribute
Out[12]: 'first'

.parent_find()

A helper method that allow you to do the equivalent of the .find() method but in the chain of the parents of the node. This is the equivalent of doing: while node has a parent: if node.parent match query: return node.parent, else: node = node.parent. It returns None if no parent match the query.

In [13]: red = RedBaron("def a():\n    with b:\n        def c():\n            pass")

In [14]: red.help()
0 -----------------------------------------------------
DefNode()
  # identifiers: def, def_, defnode, funcdef, funcdef_
  # default test value: name
  async=False
  name='a'
  decorators ->
  arguments ->
  value ->
    * WithNode()
        # identifiers: with, with_, withnode
        async=False
        contexts ->
          * WithContextItemNode() ...
        value ->
          * DefNode() ...

In [15]: r = red.pass_

In [16]: r
Out[16]: pass

In [17]: r.parent
Out[17]: 
def c():
            pass

In [18]: r.parent_find('def')
Out[18]: 
def c():
            pass

In [19]: r.parent_find('def', name='a')
Out[19]: 
def a():
    with b:
        def c():
            pass

In [20]: r.parent_find('def', name='dont_exist')

.next .previous .next_recursive .previous_recursive .next_generator() .previous_generator()

In a similar fashion, nodes have a .next and .previous attributes that point to the next or previous node if the node is located in a node list. They are set at None if there is not adjacent node or if the node is not in a node list. A node list will never have a .next or .previous node, so those attributes will always be set at None.

Nodes also have a .next_generator() and .previous_generator() if you want to iterate on the neighbours of the node.

Nodes have also a .next_recursive and .previous_recursive attribute. It is similar to the non recursive function but differ in the fact that, when using .next_recursive on a node at the end of the list, it points to the first adjacent node that exist in the parent hierarchy.

In [21]: red = RedBaron("[1, 2, 3]; a = 1")

In [22]: list = red[0]

In [23]: print(list.next)
; 

In [24]: list.help()
ListNode()
  # identifiers: list, list_, listnode
  value ->
    * IntNode()
        # identifiers: int, int_, intnode
        value='1'
    * IntNode()
        # identifiers: int, int_, intnode
        value='2'
    * IntNode()
        # identifiers: int, int_, intnode
        value='3'

In [25]: assign = red[2]

In [26]: assign.help()
AssignmentNode()
  # identifiers: assign, assignment, assignment_, assignmentnode
  operator=''
  target ->
    NameNode()
      # identifiers: name, name_, namenode
      value='a'
  value ->
    IntNode()
      # identifiers: int, int_, intnode
      value='1'

In [27]: list.value[2].help(deep=1)
IntNode()
  # identifiers: int, int_, intnode
  value='3'

.next_intuitive/.previous_intuitive

Due to its tree nature, navigating in the FST might not behave as the user expect it. For example: doing a .next on a TryNode will not return the first ExceptNode (or FinallyNode) but will return the node after the try-excepts-else-finally node because it is a full node in itself in the FST.

See for yourself:

In [28]: red = RedBaron("try:\n    pass\nexcept:\n    pass\nafter")

In [29]: red.try_
Out[29]: 
try:
    pass
except:
    pass

In [30]: red.try_.next
Out[30]: after

In [31]: red.help()
0 -----------------------------------------------------
TryNode()
  # identifiers: try, try_, trynode
  else ->
    None
  finally ->
    None
  value ->
    * PassNode()
        # identifiers: pass, pass_, passnode
  excepts ->
    * ExceptNode()
        # identifiers: except, except_, exceptnode
        delimiter=''
        exception ->
          None
        target ->
          None
        value ->
          * PassNode() ...
1 -----------------------------------------------------
NameNode()
  # identifiers: name, name_, namenode
  value='after'

To solve this issue .next_intuitive and .previous_intuitive have been introduced:

In [32]: red
Out[32]: 
0   try:
        pass
    except:
        pass
    
1   after

In [33]: red.try_.next_intuitive
Out[33]: 
except:
    pass

In [34]: red.try_.next_intuitive.next_intuitive
Out[34]: after

This also applies to IfNode, ElifNode, ElseNode, ForNode and WhileNode (both of the last one can have an else statement). This also works coming from nodes outsides of those previous nodes.

For IfNode, ElifNode and ElseNode inside an IfelseblockNode:

In [35]: red = RedBaron("before\nif a:\n    pass\nelif b:\n    pass\nelse:\n    pass\nafter")

In [36]: red
Out[36]: 
0   before
1   if a:
        pass
    elif b:
        pass
    else:
        pass
    
2   after

In [37]: red[1].help()
IfelseblockNode()
  # identifiers: ifelseblock, ifelseblock_, ifelseblocknode
  value ->
    * IfNode()
        # identifiers: if, if_, ifnode
        test ->
          NameNode() ...
        value ->
          * PassNode() ...
    * ElifNode()
        # identifiers: elif, elif_, elifnode
        test ->
          NameNode() ...
        value ->
          * PassNode() ...
    * ElseNode()
        # identifiers: else, else_, elsenode
        value ->
          * PassNode() ...

In [38]: red[1]
Out[38]: 
if a:
    pass
elif b:
    pass
else:
    pass

In [39]: red.if_.next
Out[39]: 
elif b:
    pass

In [40]: red.if_.next_intuitive
Out[40]: 
elif b:
    pass

In [41]: red.if_.next_intuitive.next_intuitive
Out[41]: 
else:
    pass

In [42]: red.if_.next_intuitive.next_intuitive.next_intuitive
Out[42]: after

In [43]: red.if_.next_intuitive.next_intuitive.next_intuitive.next_intuitive

Warning

There is a subtlety: IfelseblockNode is unaffected by this behavior: you have to use next_intuitive or previous_intuitive on IfNode, ElifNode and ElseNode inside IfelseblockNode.

But, if you do a next_intuitive or previous_intuitive or a node around IfelseblockNode it will jump to the first or last node inside the IfelseblockNode.

See this example

In [44]: red = RedBaron("before\nif a:\n    pass\nelif b:\n    pass\nelse:\n    pass\nafter")

In [45]: red[1].ifelseblock.next_intuitive  # similar to .next
Out[45]: after

In [46]: red[1].ifelseblock.next.previous  # this is the IfelseblockNode
Out[46]: 
if a:
    pass
elif b:
    pass
else:
    pass

In [47]: red[1].ifelseblock.next.previous_intuitive  # this is the ElseNode
Out[47]: 
else:
    pass

In [48]: red[1].ifelseblock.previous.next  # this is the IfelseblockNode
Out[48]: 
if a:
    pass
elif b:
    pass
else:
    pass

In [49]: red[1].ifelseblock.previous.next_intuitive  # this is the IfNode
Out[49]: 
if a:
    pass

For ForNode:

In [50]: red = RedBaron("for a in b:\n    pass\nelse:\n    pass\nafter")

In [51]: red
Out[51]: 
0   for a in b:
        pass
    else:
        pass
    
1   after

In [52]: red[0].help()
ForNode()
  # identifiers: for, for_, fornode
  async=False
  iterator ->
    NameNode()
      # identifiers: name, name_, namenode
      value='a'
  target ->
    NameNode()
      # identifiers: name, name_, namenode
      value='b'
  else ->
    ElseNode()
      # identifiers: else, else_, elsenode
      value ->
        * PassNode() ...
  value ->
    * PassNode()
        # identifiers: pass, pass_, passnode

In [53]: red.for_
Out[53]: 
for a in b:
    pass
else:
    pass

In [54]: red.for_.next
Out[54]: after

In [55]: red.for_.next_intuitive
Out[55]: 
else:
    pass

In [56]: red.for_.next_intuitive.next_intuitive
Out[56]: after

For WhileNode:

In [57]: red = RedBaron("while a:\n    pass\nelse:\n    pass\nafter")

In [58]: red
Out[58]: 
0   while a:
        pass
    else:
        pass
    
1   after

In [59]: red[0].help()
WhileNode()
  # identifiers: while, while_, whilenode
  test ->
    NameNode()
      # identifiers: name, name_, namenode
      value='a'
  else ->
    ElseNode()
      # identifiers: else, else_, elsenode
      value ->
        * PassNode() ...
  value ->
    * PassNode()
        # identifiers: pass, pass_, passnode

In [60]: red.while_
Out[60]: 
while a:
    pass
else:
    pass

In [61]: red.while_.next
Out[61]: after

In [62]: red.while_.next_intuitive
Out[62]: 
else:
    pass

In [63]: red.while_.next_intuitive.next_intuitive
Out[63]: after

.root

Every node have the .root attribute (property) that returns the root node in which this node is located:

In [64]: red = RedBaron("def a(): return 42")

In [65]: red.int_
Out[65]: 42

In [66]: assert red.int_.root is red

.index_on_parent

Every node have the .index_on_parent attribute (property) that returns the index at which this node is store in its parent node list. If the node isn’t stored in a node list, it returns None. If the node is stored in a proxy list (Proxy List), it’s the index in the proxy list that is returned. to get the unproxified index use .index_on_parent_raw.

In [67]: red = RedBaron("a = [1, 2, 3]")

In [68]: red[0].value.value
Out[68]: 
0   1
1   2
2   3

In [69]: red[0].value.value[2]
Out[69]: 3

In [70]: red[0].value.value[2].index_on_parent
Out[70]: 2

In [71]: red[0].value
Out[71]: [1, 2, 3]

In [72]: red[0].value.index_on_parent

.index_on_parent_raw

Same as .index_on_parent except that it always return the unproxified whether the node is stored in a proxy list or not.

In [73]: red = RedBaron("a = [1, 2, 3]")

In [74]: red[0].value.value.node_list
Out[74]: 
0   1
1   , 
2   2
3   , 
4   3

In [75]: red[0].value.value.node_list[2]
Out[75]: 2

In [76]: red[0].value.value.node_list[2].index_on_parent_raw
Out[76]: 2

.filtered()

Node list comes with a small helper function: .filtered() that returns a tuple containing the “significant” node (nodes that aren’t comma node, dot node, space node or endl node).

In [77]: red = RedBaron("[1, 2, 3]")

In [78]: red[0].value
Out[78]: 
0   1
1   2
2   3

In [79]: red[0].value.filtered()
Out[79]: (1, 2, 3)

Note: the fact that it’s a tuple that is returned will probably evolve in the future into a node list proxy or something like that, I just don’t have the time to do something better right now.

.indentation

Every node has the property .indentation that will return the indentation level of the node:

In [80]: red = RedBaron("while a:\n    pass")

In [81]: red[0].indentation
Out[81]: ''

In [82]: red[0].test.indentation
Out[82]: ''

In [83]: red.pass_.indentation
Out[83]: '    '

In [84]: red = RedBaron("while a: pass")

In [85]: red.pass_.indentation
Out[85]: ''

.increase_indentation() and .decrease_indentation()

Those 2 methods allow you to change the indentation of a part of the tree. They expect the number of spaces to add or to remove as first argument.

In [86]: red = RedBaron("def a():\n    if plop:\n        pass")

In [87]: red
Out[87]: 
0   def a():
        if plop:
            pass
    

In [88]: red[0].value.increase_indentation(15)

In [89]: red
Out[89]: 
0   def a():
                       if plop:
                           pass
    

In [90]: red[0].value.decrease_indentation(15)

In [91]: red
Out[91]: 
0   def a():
        if plop:
            pass

.to_python()

Warning

Since RedBaron calls ast.literal_eval it can only parse the python code parsed by the python version you are using.

For example if you are using a python version inferior to 3.6, to_python will crash on 100_000 because it is only supported since python 3.6

This method safely evaluate the current selected nodes. It wraps ast.literal_eval, therefor, and for security reasons, it only works on a subset of python: numbers, strings, lists, dicts, tuples, boolean and None. Of course, using this method on a list/dict/tuple containing values that aren’t listed here will raise a ValueError.

In [92]: RedBaron("42")[0].value  # string
Out[92]: '42'

In [93]: RedBaron("42")[0].to_python()  # python int
Out[93]: 42

In [94]: RedBaron("'a' 'b'")[0].dumps()
Out[94]: "'a' 'b'"

In [95]: RedBaron("'a' 'b'")[0].to_python()
Out[95]: 'ab'

In [96]: RedBaron("u'unicode string'")[0].to_python()
Out[96]: u'unicode string'

In [97]: RedBaron("[1, 2, 3]")[0].to_python()
Out[97]: [1, 2, 3]

In [98]: RedBaron("(1, 2, 3)")[0].to_python()
Out[98]: (1, 2, 3)

In [99]: RedBaron("{'foo': 'bar'}")[0].to_python()
Out[99]: {'foo': 'bar'}

In [100]: RedBaron("False")[0].to_python()
Out[100]: False

In [101]: RedBaron("True")[0].to_python()
Out[101]: True

In [102]: print(RedBaron("None")[0].to_python())
None

.path()

Every node has a path() method that will return a Path object to it. Every path object has a .node attribute that point to the node and a .to_baron_path that returns a Baron Path namedtuple.

In [103]: red = RedBaron("while a:\n    pass")

In [104]: red.pass_
Out[104]: pass

In [105]: path = red.pass_.path()

In [106]: path
Out[106]: <Path(PassNode(pass) @ [0, 'value', 1]) object at 140122451244752>

In [107]: path.node
Out[107]: pass

In [108]: path.to_baron_path()
Out[108]: [0, 'value', 1]

Path class

RedBaron provides a Path class that represent a path to a node.

class redbaron.Path(node)

Holds the path to a FST node

Path(node): path coming from the node’s root Path.from_baron_path(node, path): path going down the node following the given path

Note that the second argument “path” is a baron path, i.e. list of keys that can be given for example by redbaron.Path(node).to_baron_path()

The second form is useful when converting a path given by baron to a redbaron node

.map .filter .apply

RedBaron nodes list have 3 helper methods .map, .filter and .apply quite similar to python builtins (except for apply). The main difference is that they return a node list instance instead of a python buildin list.

  • .map takes a callable (like a lambda or a function) that receive a node as first argument, this callable is applied on every node of the node list and a node list containing the return of those applies will be returned.
  • .filter works like .map but instead of returning a node list of the return of the callable, it returns a node list that contains the nodes for which the callable returned True (or something considered True in python)
  • .apply works like .map but instead of returning the result of the callable, it returns to original node.
In [109]: red = RedBaron("[1, 2, 3]")

In [110]: red('int')
Out[110]: 
0   1
1   2
2   3

In [111]: red('int').map(lambda x: x.to_python() + 1)
Out[111]: 
0   2
1   3
2   4

In [112]: red('int').filter(lambda x: x.to_python() % 2 == 0)
Out[112]: 0   2
In [113]: red = RedBaron("a()\nb()\nc(x=y)")

In [114]: red('call')
Out[114]: 
0   ()
1   ()
2   (x=y)

# FIXME
# red('call').map(lambda x: x.append_value("answer=42"))
In [115]: red('call')
Out[115]: 
0   ()
1   ()
2   (x=y)

In [116]: red = RedBaron("a()\nb()\nc(x=y)")

# FIXME
# red('call').apply(lambda x: x.append_value("answer=42"))

.replace()

.replace() is a method that allow to replace in place a node by another one. Like every operation of this nature, you can pass a string, a dict, a list of length one or a node instance.

In [117]: red = RedBaron("a()\nb()\nc(x=y)")

In [118]: red[2].replace("1 + 2")

In [119]: red
Out[119]: 
0   a()
1   b()
2   1 + 2

In [120]: red[-1].replace("plop")

In [121]: red
Out[121]: 
0   a()
1   b()
2   plop

.edit()

Helper method that allow to edit the code of the current node into an editor. The result is parsed and replace the code of the current node.

In [122]: red = RedBaron("def a(): return 42")

# should be used like this: (I can't execute this code here, obviously)
# red.return_.edit()

By default, the editor is taken from the variable EDITOR in the environment variables. If this variable is not present, nano is used. You can use a different editor this way: node.edit(editor="vim").

.absolute_bounding_box

The absolute bounding box of a node represents its top-left and bottom-right position relative to the fst’s root node. The position is given as a tuple (line, column) with both starting at 1.

In [123]: red = RedBaron("def a(): return 42")

In [124]: red.funcdef.value.absolute_bounding_box
Out[124]: BoundingBox (Position (1, 10), Position (2, 0))

You can also get the bounding box of “string” nodes like the left parenthesis in the example above by giving the attribute’s name to the get_absolute_bounding_box_of_attribute() method:

In [125]: red.funcdef.get_absolute_bounding_box_of_attribute('(')
Out[125]: BoundingBox (Position (1, 6), Position (1, 6))

This is impossible to do without giving the attribute’s name as an argument since the left parenthesis is not a redbaron Node.

.bounding_box

Every node has the bounding_box property which holds the top-left and bottom-right position of the node. Compared to the absolute_bounding_box property, it assumes the node is the root node so the top-left position is always (1, 1).

In [126]: red = RedBaron("def a(): return 42")

In [127]: red.funcdef.value.absolute_bounding_box
Out[127]: BoundingBox (Position (1, 10), Position (2, 0))

In [128]: red.funcdef.value.bounding_box
Out[128]: BoundingBox (Position (1, 1), Position (2, 0))

.find_by_position()

You can find which node is located at a given line and column:

In [129]: red = RedBaron("def a(): return 42")

In [130]: red.find_by_position((1, 5))
Out[130]: def a(): return 42

In [131]: red.find_by_position((1, 6)) # '(' is not a redbaron node
Out[131]: def a(): return 42

.at()

Returns first node at specific line

In [132]: red = RedBaron("def a():\n return 42")

In [133]: red.at(1) # Gives DefNode
Out[133]: 
def a():
 return 42

In [134]: red.at(2) # Gives ReturnNode
Out[134]: return 42

Node.from_fst()

Node.from_fst() is a helper class method that takes an FST node and return a RedBaron node instance. Except if you need to go down at a low level or that RedBaron doesn’t provide the helper you need, you shouldn’t use it.

In [135]: from redbaron import Node

In [136]: Node.from_fst({"type": "name", "value": "a"})
Out[136]: a

Node.from_fst() takes 2 optional keywords arguments: parent and on_attribute that should respectively be RedBaron node instance (the parent node) and a string (the attribute of the parent node on which this node is stored). See .parent doc for a better understanding of those 2 parameters.

In [137]: red = RedBaron("[1,]")

In [138]: new_name = Node.from_fst({"type": "name", "value": "a"}, parent=red[0], on_attribute="value")

In [139]: red[0].value.append(new_name)

NodeList.from_fst()

Similarly to Node.from_fst(), NodeList.from_fst() is a helper class method that takes an FST node list and return a RedBaron node list instance. Similarly, you probably don’t need to go so low level.

In [140]: from redbaron import NodeList

In [141]: NodeList.from_fst([{"type": "name", "value": "a"}, {'first_formatting': [], 'type': 'comma', 'second_formatting': [{'type': 'space', 'value': ' '}]}, {"type": "name", "value": "b"}])
Out[141]: 
0   a
1   , 
2   b

.insert_before .insert_after

One thing you often wants to do is to insert things just after or before the node you’ve just got via query. Those helpers are here for that:

In [142]: red = RedBaron("foo = 42\nprint('bar')\n")

In [143]: red
Out[143]: 
0   foo = 42
1   print('bar')

In [144]: red.print_.insert_before("baz")

In [145]: red
Out[145]: 
0   foo = 42
1   baz
2   print('bar')

In [146]: red.print_.insert_after("foobar")

In [147]: red
Out[147]: 
0   foo = 42
1   baz
2   print('bar')
3   foobar

Additionally, you can give an optional argument offset to insert more than one line after or before:

In [148]: red = RedBaron("foo = 42\nprint('bar')\n")

In [149]: red
Out[149]: 
0   foo = 42
1   print('bar')

In [150]: red.print_.insert_before("baz", offset=1)

In [151]: red
Out[151]: 
0   baz
1   foo = 42
2   print('bar')

In [152]: red[0].insert_after("foobar", offset=1)

In [153]: red
Out[153]: 
0   baz
1   foo = 42
2   foobar
3   print('bar')