PyVerCompat is a tool to convert (downgrading) Python 3.10+ codes' semantic features to Python <=3.9 code. This tool is designed to make it easier for heavy users of new Python language features to deploy code on older environments with minimal pain.
NOTICE#1: This tool must run on
Python>=3.10to parsematch...case...semantics.
NOTICE#2: In the current stage,
PyVerCompatdoes not support converting APIs to older version. So please make sure your library is using the stable APIs from both standard and third-party libraries. In the future, we will implement the automatic conversions towards "typings" module.
- Converting single Python3.10+
.pyfile - Packing up one Python project into multiple wheels that compatible with both older or newer versions.
- Pattern-Matching (
match...case...): Supported now- Currently the magic property
__match_args__is not supported.
- Currently the magic property
- Walrus Operator: Developing...
git clone https://github.com/hzyrc6011/pyvercompat
cd pyvercompat
(This repository is also hosted on gitee at: https://gitee.com/mole-h-6011/pyvercompat)
Go to the path demos/wheels-packup, the file match_case_demo.py features match-case semantic:
# match_case_demo.py
def f(a):
match a:
case 1:
return
case [1, x]:
return x
case {"a": 1}:
return a
case _:
passThen run:
python -m pyvercompat convert-file -i match_case_demo.py -o converted_to_if_else.py --encoding utf8 File converted_to_if_else.py will be generated, and its content is shown in the code block below ↓. The original match...case... semantics was converted automatically to a series of if...elif...else statements.
# converted_to_if_else.py
def f(a):
if a == 1:
return
elif len(a) == 2 and a[0] == 1 and True:
x = a[1]
return x
elif 'a' in a and a['a'] == 1:
return a
else:
passPyVerCompat can build wheels for lower Python versions on Python>=3.10. Currently 3.8 and 3.9 are supported.
For example, go to the path demos/wheels-packup, there is a small python project with
heavy usage on match-case semantics, especially in UppaalLTLParser/ltl.py
Run the command below to packup the project:
NOTICE:
pyvercompat create-wheelcommand must be launched under the project root that have asetup.pyorpyproject.toml.
python -m pyvercompat create-wheel --tag-types 38-39,310+ --wheel-src .\UppaalLTLParser\,.\README.md,.\setup.py --ignored-files .pyc-
--tag-types: The types of python version tag on the wheel's filename to be generated. Currently supporting:
38-39: Python 3.8 and 3.9, indicating python version tagpy38.py39310+:Python >=3.10, indicating python version tagpy310.py311.py312.py313(Will be changed if a newer version of python is released)
-
--wheel-src: The source files or directories to be packed up.
-
--ignored-files: The files to be ignored when packing up.
Then just check the generated wheel file in pyvercompat-dist directory, you will find the two wheels below:
pyvercompat-dist
|
|--UppaalLTLParser-0.1.0-py310.py311.py312.py313-none-any.whl
|--UppaalLTLParser-0.1.0-py38.py39-none-any.whlNOTICE1: If you are sure that your project has no walrus operator like
print(a:=b), you can manually change the package nameUppaalLTLParser-0.1.0-py38.py39-none-any.whlltoUppaalLTLParser-0.1.0-py37.py38.py39-none-any.whlto make it compatible with Python 3.7.
NOTICE2: Though the wheel for newer Python version is tagged
py310.py311.py312.py313, for interpreter versions, eg,3.14, the wheel could also be used because pip regardspy<VERSION>tags as compatible with the Python Interpreter >=VERSION, according to Thomas Kluyver's post
Feel free to open an issue if you have any questions or suggestions.
Please refer to Developing Documentations to understand this project, and feel free to submit a new pull request!
The structure of this project is shown below:
├─demos
│ ├─convert_single_file # Demo project to convert single file
│ └─wheels-packup # Demo project to packup wheels
├─docs
├─py-tests
│ ├─match_case_conversion # Test semantic conversion
│ └─packup # Test packing up wheel package
└─pyvercompat # Main directory for the package
├─converter.py # Semantic converters
├─utils.py # Some auxiliary functions for various purposes
├─wheel_packer.py # Wheel builder and packer
├─__init__.py
└─__main__.py # Containing the clientThe major functionality of this tool is semantic conversion based on the built-in module ast. We provide the conversion procedure of match...case... as an example, the conversion procedure is implemented in pyvercompat/converter.py, and the conversion process is shown in the flowchart below:
graph TD
cmd[Command line input]
convert_file["call `convert_file()`"]
invoke_transformer["Invoke `TransformerToLegacy(ast.NodeTransformer)` for semantics replacement"]
main_converter["Invoke `MainConverter(ast.NodeVisitor)` to visit the structure of `match...case...`<br>1. Create a new variable to store the match subject value to avoid multiple-times evaluations<br>2. Extract the match subject value and all cases"]
parse_cases["Invoke `PatternVisitor(ast.NodeVisitor)` to recursively parse every case;<br>Then generate the corresponding if-condition and if-body"]
link_ifs["The `MainConverter` links all generated if-conditions and if-bodys to a series of if-elif-...-else statements"]
replace_structure["Replace the original `match...case...` structure with the converted `if-elif-elif...-else` structure"]
finish["Finish and write out converted python file"]
cmd --> convert_file --> invoke_transformer --> main_converter -->parse_cases --> link_ifs --> replace_structure --> finish

