{"id":13501784,"url":"https://github.com/nvbn/py-backwards","last_synced_at":"2025-03-29T09:31:29.491Z","repository":{"id":50359101,"uuid":"89273390","full_name":"nvbn/py-backwards","owner":"nvbn","description":"Python to python compiler that allows you to use Python 3.6 features in older versions.","archived":false,"fork":false,"pushed_at":"2021-03-22T04:12:55.000Z","size":117,"stargazers_count":306,"open_issues_count":30,"forks_count":19,"subscribers_count":12,"default_branch":"master","last_synced_at":"2024-04-16T03:48:33.946Z","etag":null,"topics":["compiler","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nvbn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-04-24T18:24:40.000Z","updated_at":"2024-03-27T01:20:44.000Z","dependencies_parsed_at":"2022-09-06T12:01:20.632Z","dependency_job_id":null,"html_url":"https://github.com/nvbn/py-backwards","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvbn%2Fpy-backwards","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvbn%2Fpy-backwards/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvbn%2Fpy-backwards/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvbn%2Fpy-backwards/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nvbn","download_url":"https://codeload.github.com/nvbn/py-backwards/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":222481625,"owners_count":16991491,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compiler","python"],"created_at":"2024-07-31T22:01:50.214Z","updated_at":"2024-10-31T20:32:32.265Z","avatar_url":"https://github.com/nvbn.png","language":"Python","funding_links":[],"categories":["Python","Python Hacks"],"sub_categories":[],"readme":"# Py-backwards [![Build Status](https://travis-ci.org/nvbn/py-backwards.svg?branch=master)](https://travis-ci.org/nvbn/py-backwards)\n\nPython 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/).\n\nRequires Python 3.3+ to run, can compile down to 2.7.\n\n## Supported features\n\nTarget 3.5:\n* [formatted string literals](https://docs.python.org/3/whatsnew/3.6.html#pep-498-formatted-string-literals) like `f'hi {x}'`\n* [variables annotations](https://docs.python.org/3/whatsnew/3.6.html#whatsnew36-pep526) like `x: int = 10` and `x: int`\n* [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)\n\nTarget 3.4:\n* [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])`\n* [dict unpacking](https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking-generalizations) like `{1: 2, **{3: 4}}`\n\nTarget 3.3:\n* import [pathlib2](https://pypi.python.org/pypi/pathlib2/) instead of pathlib\n\nTarget 3.2:\n* [yield from](https://docs.python.org/3/whatsnew/3.3.html#pep-380)\n* [return from generator](https://docs.python.org/3/whatsnew/3.3.html#pep-380)\n\nTarget 2.7:\n* [functions annotations](https://www.python.org/dev/peps/pep-3107/) like `def fn(a: int) -\u003e str`\n* [imports from `__future__`](https://docs.python.org/3/howto/pyporting.html#prevent-compatibility-regressions)\n* [super without arguments](https://www.python.org/dev/peps/pep-3135/)\n* classes without base like `class A: pass`\n* imports from [six moves](https://pythonhosted.org/six/#module-six.moves)\n* metaclass\n* string/unicode literals (works automatically)\n* `str` to `unicode`\n* define encoding (not transformer)\n* `dbm =\u003e anydbm` and `dbm.ndbm =\u003e dbm`\n\nFor example, if you have some python 3.6 code, like:\n\n```python\ndef returning_range(x: int):\n    yield from range(x)\n    return x\n\n\ndef x_printer(x):\n    val: int\n    val = yield from returning_range(x)\n    print(f'val {val}')\n\n\ndef formatter(x: int) -\u003e dict:\n    items: list = [*x_printer(x), x]\n    print(*items, *items)\n    return {'items': items}\n\n\nresult = {'x': 10, **formatter(10)}\nprint(result)\n\n\nclass NumberManager:\n    def ten(self):\n        return 10\n\n    @classmethod\n    def eleven(cls):\n        return 11\n\n\nclass ImportantNumberManager(NumberManager):\n    def ten(self):\n        return super().ten()\n\n    @classmethod\n    def eleven(cls):\n        return super().eleven()\n\n\nprint(ImportantNumberManager().ten())\nprint(ImportantNumberManager.eleven())\n```\n\nYou can compile it for python 2.7 with:\n\n```bash\n➜ py-backwards -i input.py -o output.py -t 2.7\n```\n\nGot some [ugly code](https://gist.github.com/nvbn/51b1536dc05bddc09439f848461cef6a) and ensure that it works:\n\n```bash\n➜ python3.6 input.py\nval 10\n0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10\n{'x': 10, 'items': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}\n10\n11\n➜ python2 output.py                           \nval 10\n0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10\n{'x': 10, 'items': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}\n10\n11\n```\n\n## Usage\n\nInstallation:\n\n```bash\npip install py-backwards\n```\n\nCompile code:\n\n```bash\npy-backwards -i src -o compiled -t 2.7\n```\n\n### Testing compiled code\n\nFor testing compiled code with each supported python version you can use [tox](https://tox.readthedocs.io/en/latest/)\nand [tox-py-backwards](https://github.com/nvbn/tox-py-backwards). You need to install them:\n\n```bash\npip install tox tox-py-backwards\n```\n\nFill `tox.ini` (`py_backwards = true` in `testenv` section enables py-backwards), like:\n\n```ini\n[tox]\nenvlist = py27,py33,py34,py35,py36\n\n[testenv]\ndeps = pytest\ncommands = py.test\npy_backwards = true\n```\n\nAnd run tests with:\n\n```bash\ntox\n```\n\n### Distributing compiled code\n\nFor distributing packages compiled with py-backwards you can use [py-backwards-packager](https://github.com/nvbn/py-backwards-packager).\nInstall it with:\n\n```python\npip install py-backwards-packager\n```\n\nAnd change `setup` import in `setup.py` to:\n \n```python\ntry:\n    from py_backwards_packager import setup\nexcept ImportError:\n    from setuptools import setup\n```\n\nBy default all targets enabled, but you can limit them with:\n \n```python\nsetup(...,\n      py_backwards_targets=['2.7', '3.3'])\n```\n\nAfter that your code will be automatically compiled on `bdist` and `bdist_wheel`.\n\n### Running on systems without Python 3.3+\n\nYou can use docker for running py-backwards on systems without Python 3.3+, for example\nfor testing on travis-ci with Python 2.7:\n\n```bash\ndocker run -v $(pwd):/data/ nvbn/py-backwards -i example -o out -t 2.7\n```\n\n## Development\n\nSetup:\n\n```bash\npip install .\npython setup.py develop\npip install -r requirements.txt\n```\n\nRun tests:\n\n```bash\n py.test -vvvv --capture=sys --enable-functional\n```\n\nRun tests on systems without docker:\n\n```bash\n py.test -vvvv\n```\n\n## Writing code transformers\n\nFirst of all, you need to inherit from `BaseTransformer`, `BaseNodeTransformer` (if you want to use\n[NodeTransfromer](https://docs.python.org/3/library/ast.html#ast.NodeTransformer) interface),\nor `BaseImportRewrite` (if you want just to change import).\n\nIf you use `BaseTransformer`, override class method `def transform(cls, tree: ast.AST) -\u003e TransformationResult`, like:\n\n```python\nfrom ..types import TransformationResult\nfrom .base import BaseTransformer\n\n\nclass MyTransformer(BaseTransformer):\n    @classmethod\n    def transform(cls, tree: ast.AST) -\u003e TransformationResult:\n        return TransformationResult(tree=tree,\n                                    tree_changed=True,\n                                    dependencies=[])\n```\n\nIf you use `BaseNodeTransformer`, override `visit_*` methods, for simplification this class\nhave a whole tree in `self._tree`, you should also set `self._tree_changed = True` if the tree\nwas changed:\n\n```python\nfrom .base import BaseNodeTransformer\n\n\nclass MyTransformer(BaseNodeTransformer):\n    dependencies = []  # additional dependencies\n\n    def visit_FunctionDef(self, node: ast.FunctionDef) -\u003e ast.FunctionDef:\n        self._tree_changed = True  # Mark that transformer changed tree\n        return self.generic_visit(node)\n```\n\nIf you use `BaseImportRewrite`, just override `rewrites`, like:\n\n```python\nfrom .base import BaseImportRewrite\n\n\nclass MyTransformer(BaseImportRewrite):\n    dependencies = ['pathlib2']\n\n    rewrites = [('pathlib', 'pathlib2')]\n```\n\nAfter that you need to add your transformer to `transformers.__init__.transformers`.\n\nIt'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):\n\n```python\nfrom ..utils.snippet import snippet, let, extend\n\n\n@snippet\ndef my_snippet(class_name, class_body):\n    class class_name:  # will be replaced with `class_name`\n        extend(class_body)  # body of the class will be extended with `class_body`\n        \n        def fn(self):\n            let(x)  # x will be replaced everywhere with unique name, like `_py_backwards_x_1`\n            x = 10\n            return x\n```\n\nAnd you can easily get content of snippet with:\n\n```python\nmy_snippet.get_body(class_name='MyClass',\n                    class_body=[ast.Expr(...), ...])\n```\n\nAlso please look at [tree utils](https://github.com/nvbn/py-backwards/blob/master/py_backwards/utils/tree.py),\nit contains such useful functions like `find`, `get_parent` and etc.\n\n## Related projects\n\n* [py-backwards-astunparse](https://github.com/nvbn/py-backwards-astunparse)\n* [tox-py-backwards](https://github.com/nvbn/tox-py-backwards)\n* [py-backwards-packager](https://github.com/nvbn/py-backwards-packager)\n* [pytest-docker-pexpect](https://github.com/nvbn/pytest-docker-pexpect)\n\n## License MIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnvbn%2Fpy-backwards","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnvbn%2Fpy-backwards","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnvbn%2Fpy-backwards/lists"}