Bowler: Safe code refactoring for modern Python
pybowler.io
pybowler.io
Happy to answer any questions you might have!
Edit: Found it — it's currently at https://github.com/facebookincubator/bowler instead.
> RedBaron is a python library and tool powerful enough to be used into IPython solely that intent to make the process of writing code that modify source code as easy and as simple as possible. That include writing custom refactoring, generic refactoring, tools, IDE or directly modifying you source code into IPython with a higher and more powerful abstraction than the advanced texts modification tools that you find in advanced text editors and IDE.
1) RedBaron uses a procedural API that modifies state continuously as you call methods on the class, whereas Bowler allows you expressive a series of queries/transforms up front, and then execute those all at once on your entire codebase. This also allows Bowler to provide a fluent API, so that you can chain all method calls from each other.
2) RedBaron uses a custom AST implementation, which still lists "Python 3.7 support" on its roadmap. Building on "fissix" (a backport of lib2to3) means that Bowler had day one support for Python 3.7 syntax, and already supports the provisional Python 3.8 syntax. This means your refactoring tools will never prevent you from adopting new versions of Python as soon as they are released.
https://docs.python.org/3/library/ast.html
I've built macros using the ast library before and it works, but I also found I could adjust my DSL's grammar just a bit and express the same thing with ordinary Python code. That's why Python macro systems rarely gain much traction.
BTW Bowler looks cool. I expect to try it next time I do a big refactor.
For instance we’ve just enabled the pep3101 Flake8 plugin which enforces newstyle string formatting over % formatting. I’d love to see a transformer that automates that refactoring.
lib2to3, despite the name, is a fairly generic refactoring library.
if hasattr(obj, ‘foo’):
does that get caught or not? attr = "foo" if conditionA else "bar"
obj = A() if conditionB else B()
if hasattr(obj, attr):
...
Because of this, Bowler is focused on being more practical, and leans on the expectation that an engineer will be in the loop and validate the resulting diff, run unit tests, etc. if ConditionA:
attr = "foo"
else:
attr = "bar"
if ConditionB:
obj = A()
else:
obj = B()
if hasattr(attr, obj):
do_magic()
I haven't tested Bowler yet but I would think something that refactors code would handle that pretty easily? hasattr = lambda *args: False`
or something equally insane and difficult to statically analyse> Eg if I rename a method and elsewhere have `if hasattr(obj, ‘foo’):`
Further, even in the absence of insane dynamic code, it probably isn't possible to automatically make a correct decision without knowing the intent of the original code.
For example, maybe we're renaming the `foo` method of `Fizz` class to `barr`, and currently the program is written such that `is hasattr(obj, 'foo'):` only happens to be called on `Fizz` instances. Is it the case that the programmer only ever intended that code to be called on `Fizz` instances, or did the programmer intend it should operate on all types that have a `foo` attribute, including old-style `Fizz` instances and new types that might be encountered in future? In the former case, we should rewrite the logic to `hasattr(obj, 'barr')`, in the latter case perhaps we should leave it unchanged, so it will no longer trigger after the refactor.
i do agree it should be possible to automatically flag this as "here be something interesting to consider! please intervene and make a manual decision!"
Nope. Something that refactors code could potentially identify that as an ambiguous case reliably, but it's impossible to statically refactor code of that sort in the general case. Without external/runtime behavior verification/specification, that is.
That was Python syntax.
Python 3.6.6 (tags/v3.6.6:4cf1f54eb7, Jul 6 2018, 15:35:19)
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> conditionA = True
>>> attr = "foo" if conditionA else "bar"
>>> print(attr)
foo
>>> conditionA = False
>>> attr = "foo" if conditionA else "bar"
>>> print(attr)
bar
>>>That would be awesome.
EDIT: Sorry, I meant 'file' boundary, not 'language' boundary.
Python is a good language for for this sort of tool. There is a lot of potential for the power of Python harnessed to rewrite Python.