RedBaron
une approche bottom-up au refactoring en Python

Presenter Notes

Moi

  • Laurent Peuch
  • Bram

En beaucoup trop détaillé : http://worlddomination.be/about/about.html

Presenter Notes

Plan

  • Pourquoi ?
  • Solution au premier problème (baron)
  • Solution au deuxième problème (RedBaron)
  • Conclusion

Presenter Notes

Avant de commencer : revisions

Presenter Notes

Refactoring (eclipse)

refactoring.png

Presenter Notes

Abstract Syntax Tree (AST)

ast.png

Presenter Notes

Pourquoi ?

Presenter Notes

Refactoring custom

  • J'ai toujours voulu écrire du code pour modifier mon code
  • Très difficile : string énorme sans sens, analyse, déplacement, trop de possibilités, syntaxe
  • Frustrant, plein de cas où "ah, si seulement je pouvais scripter cette modification !"
  • Comme une blessure à la lèvre
  • Générer du code aussi
  • seulement une poignée de gens font ça
  • Hyper dur : je suis en (x,y) dans un fichier, y a quoi autour de moi ?

Presenter Notes

Ast.py

ast.py.png

Presenter Notes

Ast.py

Pas lossless !

ast_to_code(code_to_ast(code_source)) != source_code

(Commentaires, formatting)

(Et ast_to_code n'existe même pas de manière standard).

Presenter Notes

Ast.py

API Sax like

1 class KeyAttributesFinder(ast.NodeVisitor):
2     def visit_Assign(self, assign_node):
3         # ...
4 
5     def visit_FunctionDef(self, function_node):
6         # ...
7 
8     # visit_...

Super chiant, impossible à utiliser dans IPython efficacement.

Presenter Notes

pythonfmt

Auto formater du code python

Presenter Notes

Génération de code

Django (memopol et co) :

donnees.json -> models.py + import.py

Autre projet :

Générer du boiler plate en lisant des models de db

Presenter Notes

Refactoring : top to bottom

refactoring1.png

Presenter Notes

Refactoring en python

  • BycleRepairMan
  • Rope (ast.py + regexs)
  • PyCharm

Presenter Notes

Conclusion : 2 problèmes

  • Il manque la bonne abstraction
  • Il manque la bonne interface

Presenter Notes

Solution 1 : l'abstraction -> Baron

Presenter Notes

Baron

  • ast lossless !
  • source == ast_to_code(code_to_ast(source))
  • transforme un problème d'analyse de code en parcours/modification d'un graphe
  • output du json pour compatibilité maximum (+ structure de donnée simple)

Presenter Notes

Exemple

 1 from baron.helpers import show
 2 
 3 print show("1 + 2")
 4 
 5 [
 6     {
 7         "first_formatting": [
 8             {
 9                 "type": "space",
10                 "value": " "
11             }
12         ],
13         "value": "+",
14         "second_formatting": [
15             {
16                 "type": "space",
17                 "value": " "
18             }
19         ],
20         "second": {
21             "section": "number",
22             "type": "int",
23             "value": "2"
24         },
25         "type": "binary_operator",
26         "first": {
27             "section": "number",
28             "type": "int",
29             "value": "1"
30         }
31     }
32 ]

Presenter Notes

refactoring2.png

Presenter Notes

État du projet

1 an de boulot (j'ai dû apprendre)

  • +1000 tests (TDD)
  • marche sur le top 100 de pypi
  • utilities : position_to_path, position_to_node, bounding_box, walker etc...
  • entièrement documenté

Presenter Notes

Solution 2 : l'interface -> RedBaron

Presenter Notes

Plan

  • principe
  • exploration (query)
  • modification
  • abstraction des listes

Presenter Notes

RedBaron

  • Api au dessus de Baron
  • Comme BeautifulSoup/Jquery : mapping structure de donnée -> objects
  • Pour l'humain, user friendly autant que possible
  • l'interface qui fait tous les trucs chiants pour vous
  • Pensé, entre autre, pour être utilisé dans IPython (ou bpython)

Presenter Notes

RedBaron

API super simple :

1 from redbaron import RedBaron
2 
3 red = RedBaron("string représentant du code source")
4 # ...
5 red.dumps()  # code source

Presenter Notes

Intuitif (autant que possible)

Surcharge de __repr__ :

BeautifulSoup :

soup.png

Presenter Notes

Intuitif (autant que possible)

Surcharge de __repr__ :

BeautifulSoup :

soup.png

RedBaron :

repr.png

Presenter Notes

Auto descriptif

RedBaron :

at_0.png

Presenter Notes

Auto descriptif

RedBaron :

at_0.png

".help()"

help.png

Presenter Notes

Exploration

Comme BeautifulSoup :

 1 red = RedBaron("a = 42\ndef test_chocolat(): pass")
 2 red.find("name")
 3 red.find("int", value=42)
 4 red.find("def", name="g:test_*")
 5 red.find("def", name="re:test_*")
 6 red.find("assignment", lambda x: x.target.dumps() == "INSTALLED_APPS")
 7 
 8 red.find_all("name")
 9 red.find_all(("name", "int"))
10 red.find_all("def", arguments=lambda x: len(x) == 3)
11 red.find_all("def", recursive=False)

Presenter Notes

Exploration

Comme BeautifulSoup :

 1 red = RedBaron("a = 42\ndef test_chocolat(): pass")
 2 red.find("name")
 3 red.find("int", value=42)
 4 red.find("def", name="g:test_*")
 5 red.find("def", name="re:test_*")
 6 red.find("assignment", lambda x: x.target.dumps() == "INSTALLED_APPS")
 7 
 8 red.find_all("name")
 9 red.find_all(("name", "int"))
10 red.find_all("def", arguments=lambda x: len(x) == 3)
11 red.find_all("def", recursive=False)

Raccourcis (comme BeautifulSoup) :

1 red = RedBaron("a = 42\ndef test_chocolat(): pass")
2 red.name
3 red.int
4 red.else_
5 
6 red("name")
7 red(("name", "int"))
8 red("def", arguments=lambda x: len(x) == 3)

Presenter Notes

Modification

Comment modifier une node ?

1 from redbaron import RedBaron, BinaryOperatorNode
2 
3 red = RedBaron("a = 'plop'")
4 red[O].value  # 'plop'
5 red[0].value = BinaryOperatorNode({'first_formatting':[{'type': 'space',
6 'value': ' '}], 'value': '+', 'second_formatting': [{'type': 'space',
7 'value': ' '}], 'second': {'section': 'number', 'type': 'int', 'value':
8 '1'}, 'type': 'binary_operator', 'first': {'section': 'number', 'type':
9 'int', 'value': '1'}})

Pas hyper pratique ...

Presenter Notes

Magie de __setattr__

1 from redbaron import RedBaron, BinaryOperatorNode
2 
3 red = RedBaron("a = 'plop'")
4 red[0].value = "1 + 1"
5 
6 # marche aussi avec : nodes redbaron et ast

Marche pour toutes les nodes.

Presenter Notes

Modifications avancées :

Autre problème : quel est le corps/body de la fonction "bar" ?

1 class Foo():
2     def bar(self):
3         pass
4 
5     def baz(self):
6         pass

Presenter Notes

Modifications avancées :

Autre problème : quel est le corps/body de la fonction "bar" ?

1 class Foo():
2     def bar(self):
3         pass
4 
5     def baz(self):
6         pass

Expected :

expected.png

Presenter Notes

Modifications avancés :

Autre problème : quel est le corps/body de la fonction "bar" ?

1 class Foo():
2     def bar(self):
3         pass
4 
5     def baz(self):
6         pass

Expected :

expected.png

Reality :

reality.png

Presenter Notes

Solution : magie !