Python to python compiler that allows you to use some Python 3.6 features in older versions, you can try it in the online demo.
Requires Python 3.3+ to run, can compile down to 2.7.
Target 3.5:
- formatted string literals like 
f'hi {x}' - variables annotations like 
x: int = 10andx: int - underscores in numeric literals like 
1_000_000(works automatically) 
Target 3.4:
- starred unpacking like 
[*range(1, 5), *range(10, 15)]andprint(*[1, 2], 3, *[4, 5]) - dict unpacking like 
{1: 2, **{3: 4}} 
Target 3.3:
- import pathlib2 instead of pathlib
 
Target 3.2:
Target 2.7:
- functions annotations like 
def fn(a: int) -> str - imports from 
__future__ - super without arguments
 - classes without base like 
class A: pass - imports from six moves
 - metaclass
 - string/unicode literals (works automatically)
 strtounicode- define encoding (not transformer)
 dbm => anydbmanddbm.ndbm => dbm
For example, if you have some python 3.6 code, like:
def returning_range(x: int):
    yield from range(x)
    return x
def x_printer(x):
    val: int
    val = yield from returning_range(x)
    print(f'val {val}')
def formatter(x: int) -> dict:
    items: list = [*x_printer(x), x]
    print(*items, *items)
    return {'items': items}
result = {'x': 10, **formatter(10)}
print(result)
class NumberManager:
    def ten(self):
        return 10
    @classmethod
    def eleven(cls):
        return 11
class ImportantNumberManager(NumberManager):
    def ten(self):
        return super().ten()
    @classmethod
    def eleven(cls):
        return super().eleven()
print(ImportantNumberManager().ten())
print(ImportantNumberManager.eleven())You can compile it for python 2.7 with:
➜ py-backwards -i input.py -o output.py -t 2.7Got some ugly code and ensure that it works:
➜ python3.6 input.py
val 10
0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10
{'x': 10, 'items': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
10
11
➜ python2 output.py                           
val 10
0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10
{'x': 10, 'items': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
10
11Installation:
pip install py-backwardsCompile code:
py-backwards -i src -o compiled -t 2.7For testing compiled code with each supported python version you can use tox and tox-py-backwards. You need to install them:
pip install tox tox-py-backwardsFill tox.ini (py_backwards = true in testenv section enables py-backwards), like:
[tox]
envlist = py27,py33,py34,py35,py36
[testenv]
deps = pytest
commands = py.test
py_backwards = trueAnd run tests with:
toxFor distributing packages compiled with py-backwards you can use py-backwards-packager. Install it with:
pip install py-backwards-packagerAnd change setup import in setup.py to:
try:
    from py_backwards_packager import setup
except ImportError:
    from setuptools import setupBy default all targets enabled, but you can limit them with:
setup(...,
      py_backwards_targets=['2.7', '3.3'])After that your code will be automatically compiled on bdist and bdist_wheel.
You can use docker for running py-backwards on systems without Python 3.3+, for example for testing on travis-ci with Python 2.7:
docker run -v $(pwd):/data/ nvbn/py-backwards -i example -o out -t 2.7Setup:
pip install .
python setup.py develop
pip install -r requirements.txtRun tests:
 py.test -vvvv --capture=sys --enable-functionalRun tests on systems without docker:
 py.test -vvvvFirst of all, you need to inherit from BaseTransformer, BaseNodeTransformer (if you want to use
NodeTransfromer interface),
or BaseImportRewrite (if you want just to change import).
If you use BaseTransformer, override class method def transform(cls, tree: ast.AST) -> TransformationResult, like:
from ..types import TransformationResult
from .base import BaseTransformer
class MyTransformer(BaseTransformer):
    @classmethod
    def transform(cls, tree: ast.AST) -> TransformationResult:
        return TransformationResult(tree=tree,
                                    tree_changed=True,
                                    dependencies=[])If you use BaseNodeTransformer, override visit_* methods, for simplification this class
have a whole tree in self._tree, you should also set self._tree_changed = True if the tree
was changed:
from .base import BaseNodeTransformer
class MyTransformer(BaseNodeTransformer):
    dependencies = []  # additional dependencies
    def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef:
        self._tree_changed = True  # Mark that transformer changed tree
        return self.generic_visit(node)If you use BaseImportRewrite, just override rewrites, like:
from .base import BaseImportRewrite
class MyTransformer(BaseImportRewrite):
    dependencies = ['pathlib2']
    rewrites = [('pathlib', 'pathlib2')]After that you need to add your transformer to transformers.__init__.transformers.
It's hard to write code in AST, because of that we have snippets:
from ..utils.snippet import snippet, let, extend
@snippet
def my_snippet(class_name, class_body):
    class class_name:  # will be replaced with `class_name`
        extend(class_body)  # body of the class will be extended with `class_body`
        
        def fn(self):
            let(x)  # x will be replaced everywhere with unique name, like `_py_backwards_x_1`
            x = 10
            return xAnd you can easily get content of snippet with:
my_snippet.get_body(class_name='MyClass',
                    class_body=[ast.Expr(...), ...])Also please look at tree utils,
it contains such useful functions like find, get_parent and etc.