Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/nvbn/py-backwards
Python to python compiler that allows you to use Python 3.6 features in older versions.
https://github.com/nvbn/py-backwards
compiler python
Last synced: about 1 month ago
JSON representation
Python to python compiler that allows you to use Python 3.6 features in older versions.
- Host: GitHub
- URL: https://github.com/nvbn/py-backwards
- Owner: nvbn
- Created: 2017-04-24T18:24:40.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2021-03-22T04:12:55.000Z (over 3 years ago)
- Last Synced: 2024-04-16T03:48:33.946Z (8 months ago)
- Topics: compiler, python
- Language: Python
- Homepage:
- Size: 114 KB
- Stars: 306
- Watchers: 12
- Forks: 19
- Open Issues: 30
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
- awesome-discoveries - py-backwards - python to python compiler that allows you to use Python 3.6 features in older versions _(`Python`)_ (Python Hacks)
README
# Py-backwards [![Build Status](https://travis-ci.org/nvbn/py-backwards.svg?branch=master)](https://travis-ci.org/nvbn/py-backwards)
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](https://py-backwards.herokuapp.com/).
Requires Python 3.3+ to run, can compile down to 2.7.
## Supported features
Target 3.5:
* [formatted string literals](https://docs.python.org/3/whatsnew/3.6.html#pep-498-formatted-string-literals) like `f'hi {x}'`
* [variables annotations](https://docs.python.org/3/whatsnew/3.6.html#whatsnew36-pep526) like `x: int = 10` and `x: int`
* [underscores in numeric literals](https://docs.python.org/3/whatsnew/3.6.html#pep-515-underscores-in-numeric-literals) like `1_000_000` (works automatically)Target 3.4:
* [starred unpacking](https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations) like `[*range(1, 5), *range(10, 15)]` and `print(*[1, 2], 3, *[4, 5])`
* [dict unpacking](https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations) like `{1: 2, **{3: 4}}`Target 3.3:
* import [pathlib2](https://pypi.python.org/pypi/pathlib2/) instead of pathlibTarget 3.2:
* [yield from](https://docs.python.org/3/whatsnew/3.3.html#pep-380)
* [return from generator](https://docs.python.org/3/whatsnew/3.3.html#pep-380)Target 2.7:
* [functions annotations](https://www.python.org/dev/peps/pep-3107/) like `def fn(a: int) -> str`
* [imports from `__future__`](https://docs.python.org/3/howto/pyporting.html#prevent-compatibility-regressions)
* [super without arguments](https://www.python.org/dev/peps/pep-3135/)
* classes without base like `class A: pass`
* imports from [six moves](https://pythonhosted.org/six/#module-six.moves)
* metaclass
* string/unicode literals (works automatically)
* `str` to `unicode`
* define encoding (not transformer)
* `dbm => anydbm` and `dbm.ndbm => dbm`For example, if you have some python 3.6 code, like:
```python
def returning_range(x: int):
yield from range(x)
return xdef 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 11class 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:
```bash
➜ py-backwards -i input.py -o output.py -t 2.7
```Got some [ugly code](https://gist.github.com/nvbn/51b1536dc05bddc09439f848461cef6a) and ensure that it works:
```bash
➜ 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
11
```## Usage
Installation:
```bash
pip install py-backwards
```Compile code:
```bash
py-backwards -i src -o compiled -t 2.7
```### Testing compiled code
For testing compiled code with each supported python version you can use [tox](https://tox.readthedocs.io/en/latest/)
and [tox-py-backwards](https://github.com/nvbn/tox-py-backwards). You need to install them:```bash
pip install tox tox-py-backwards
```Fill `tox.ini` (`py_backwards = true` in `testenv` section enables py-backwards), like:
```ini
[tox]
envlist = py27,py33,py34,py35,py36[testenv]
deps = pytest
commands = py.test
py_backwards = true
```And run tests with:
```bash
tox
```### Distributing compiled code
For distributing packages compiled with py-backwards you can use [py-backwards-packager](https://github.com/nvbn/py-backwards-packager).
Install it with:```python
pip install py-backwards-packager
```And change `setup` import in `setup.py` to:
```python
try:
from py_backwards_packager import setup
except ImportError:
from setuptools import setup
```By default all targets enabled, but you can limit them with:
```python
setup(...,
py_backwards_targets=['2.7', '3.3'])
```After that your code will be automatically compiled on `bdist` and `bdist_wheel`.
### Running on systems without Python 3.3+
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:```bash
docker run -v $(pwd):/data/ nvbn/py-backwards -i example -o out -t 2.7
```## Development
Setup:
```bash
pip install .
python setup.py develop
pip install -r requirements.txt
```Run tests:
```bash
py.test -vvvv --capture=sys --enable-functional
```Run tests on systems without docker:
```bash
py.test -vvvv
```## Writing code transformers
First of all, you need to inherit from `BaseTransformer`, `BaseNodeTransformer` (if you want to use
[NodeTransfromer](https://docs.python.org/3/library/ast.html#ast.NodeTransformer) 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:
```python
from ..types import TransformationResult
from .base import BaseTransformerclass 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:```python
from .base import BaseNodeTransformerclass MyTransformer(BaseNodeTransformer):
dependencies = [] # additional dependenciesdef 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:
```python
from .base import BaseImportRewriteclass 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](https://github.com/nvbn/py-backwards/blob/master/py_backwards/utils/snippet.py#L102):
```python
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 x
```And you can easily get content of snippet with:
```python
my_snippet.get_body(class_name='MyClass',
class_body=[ast.Expr(...), ...])
```Also please look at [tree utils](https://github.com/nvbn/py-backwards/blob/master/py_backwards/utils/tree.py),
it contains such useful functions like `find`, `get_parent` and etc.## Related projects
* [py-backwards-astunparse](https://github.com/nvbn/py-backwards-astunparse)
* [tox-py-backwards](https://github.com/nvbn/tox-py-backwards)
* [py-backwards-packager](https://github.com/nvbn/py-backwards-packager)
* [pytest-docker-pexpect](https://github.com/nvbn/pytest-docker-pexpect)## License MIT