Compile a package in one .so file and remove source code
Project description
Compile automatically pythons package in one shared library and remove the source code when we build a distribution. Forked from cythonpackage.
Introduction
- Cython can compile a module but not a package ( a collection of modules )
- Sometime, you want to publish an optimized version of your python package/module.
- Sometime, you don't want to publish the source code. You can use PyArmor, or Pyminifier but if you want to compile the API, it's not a good approach.
Using cython
Cython is complex for the normal python developer.
- To compile the code, you must use a
.pyxfile. It's break your simple usage : edit, test, edit, test. Now, you must use : edit, package, test, edit, package, test. - Cython propose to create a shared library for each module. If in you package, you have ten python file, you will have ten shared library.
- The wheel package merge the python source code and the shared library. It's big and not confidential.
We can do better.
Based of the idea presented here, this component can help you to continue to use a classical python source code, and generate a single optimized module when you create the wheel.
And, it's VERY SIMPLE to use !
Usage
For each package where you want to compile and/or hide the source code,
add a file __compile__.py with nothing.
With that, you can continue to use your classical python file (or add some .pyx files).
For the developer point of vue, you continue to use the interpreted python code.
Then, to be compatible with PEP-0517,
- in the
pyproject.toml
[build-system]
requires = ["setuptools>=42", "wheel", "cypack[build]"]
build-backend = "setuptools.build_meta"
- in the
setup.py:
from setuptools import setup, find_packages
setup(
setup_requires=['cypack[build]'],
cypack=True,
...
packages=find_packages(),
)
- in the
setup.cfg, add other projects information:
[metadata]
name = my_compiled_project
...
Now, you can build your compiled wheel.
python3 -m pip install --upgrade build
python -m build
You can check inside the dist/*.whl to see if the source code in __compile__ package are removed
and replaced with a shared library. All others .py were pre-compiled.
In another virtualenv and directory, try to install this wheel.
$ mkdir -p tmp
$ cd tmp
$ virtualenv test
$ source test/bin/activate
$ pip install ../dist/*.whl
$ python ... # use your package
We use another virtualenv and another directory to remove the confusion between your local python source code
and the compiled version. Sometime, you use the interpreted version
if the source code are accessible with the PYTHONPATH.
You can check the running context with:
if cython.compiled:
print("Use compiled version")
else:
print("Use slow interpreted version")
Use can try the sample present here: https://github.com/pprados/test-cythonpackage
Try to rename the pyproject.toml or setup.py to test different kind of builds.
Multiple architecture
The wheel file generated is specific for an architecture and a Python version.
The name describe that : test_cythonpackage-0.0.0-cp38-cp38-manylinux_2_31_x86_64.whl
To be compatible with multiple combinaison of python version or architecture,
you must build wheels for all architecture.
Try cibuildwheel
to generate a version of each Operating System and python versions.
$ pip install cibuildwheel
$ python3 -m cibuildwheel --output-dir dist --platform linux
To create a classical version of your package, without compilation and with source code, use:
$ CYTHONPACKAGE=false python3 -m setup.py bdist_wheel
Use a CI action like github action. You can generate something like:
test_cythonpackage-0.0.0-cp37-cp37m-macosx_10_15_x86_64.whl
test_cythonpackage-0.0.0-cp37-cp37m-manylinux_2_12_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2010_i686.whl
test_cythonpackage-0.0.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2010_x86_64.whl
test_cythonpackage-0.0.0-cp37-cp37m-musllinux_1_1_i686.whl
test_cythonpackage-0.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl
test_cythonpackage-0.0.0-cp37-cp37m-win32.whl
test_cythonpackage-0.0.0-cp37-cp37m-win_amd64.whl
test_cythonpackage-0.0.0-cp38-cp38-macosx_10_15_x86_64.whl
test_cythonpackage-0.0.0-cp38-cp38-manylinux_2_12_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2010_i686.whl
test_cythonpackage-0.0.0-cp38-cp38-manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2010_x86_64.whl
test_cythonpackage-0.0.0-cp38-cp38-musllinux_1_1_i686.whl
test_cythonpackage-0.0.0-cp38-cp38-musllinux_1_1_x86_64.whl
test_cythonpackage-0.0.0-cp38-cp38-win32.whl
test_cythonpackage-0.0.0-cp38-cp38-win_amd64.whl
test_cythonpackage-0.0.0-cp39-cp39-macosx_10_15_x86_64.whl
test_cythonpackage-0.0.0-cp39-cp39-manylinux_2_12_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2010_i686.whl
test_cythonpackage-0.0.0-cp39-cp39-manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2010_x86_64.whl
test_cythonpackage-0.0.0-cp39-cp39-musllinux_1_1_i686.whl
test_cythonpackage-0.0.0-cp39-cp39-musllinux_1_1_x86_64.whl
test_cythonpackage-0.0.0-cp39-cp39-win32.whl
test_cythonpackage-0.0.0-cp39-cp39-win_amd64.whl
test_cythonpackage-0.0.0-pp37-pypy37_pp73-macosx_10_15_x86_64.whl
test_cythonpackage-0.0.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2010_i686.whl
test_cythonpackage-0.0.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2010_x86_64.whl
and add a fallback with a classical version with source code.
test_cythonpackage-0.0.0-py3-none-any.whl
The PIP-0425 explain how the best package are selected.
Using standard PEP-517
Don't forget to add a file __compile__.py with nothing in package to compile.
With classical setuptools,
- in
pyproject.toml
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
- in
setup.py
from setuptools import setup, find_packages
setup(
setup_requires=['cypack[build]'],
cypack=True,
packages=find_packages(),
)
- in the
setup.cfg, add other projects information:
[metadata]
name = my_compiled_project
...
and
```shell
$ pip wheel --use-pep517 .
Using Poetry
Poetry propose a new approach to build a wheel, compatible with PEP 517. At this time, the last version (1.1.*) is not compatible with cython and it's not possible to add a plugin with enough features, but you can use it in a more complex approach:
Don't forget to add a file __compile__.py with nothing in package to compiled.
Create a pyproject.toml file with something like this:
[build-system]
build-backend = "poetry.core.masonry.api"
requires = ["cypack[poetry]"]
[tool.poetry]
name = "test"
version = "0.0.0"
description = "Test"
authors = ["Me <me@me.org>"]
packages = [
{ include = "foo" },
{ include = "foo2" },
{ include = "foo3" },
]
exclude = ["**/[!__]*.py"] # Remove source code
build = 'poetry_build.py'
[tool.poetry.dependencies]
python = "^3.7"
cypack = "*"
[build-system]
requires = ["poetry-core>=1.2.0a2"]
build-backend = "poetry.core.masonry.api"
and a file ./poetry_build.py:
import cypack
def build(setup_kw):
cypack.build_cypack(setup_kw)
Then, you can build the package with:
python -m build -w
or
pip wheel -w dist .
Using setup.py only (obsolete)
Don't forget to add a file __compile__.py with nothing in package to compiled.
In the setup.py, add setup_requires and cypack=True:
from setuptools import setup, find_packages
setup(
name="myname",
version="v0.0.0",
setup_requires=['cypack[build]'],
install_requires=['cypack'],
cypack=True,
packages=find_packages(),
)
Now, you can build your compiled wheel.
python setup.py bdist_wheel
Using PBR (obsolete)
PBR is a library for managing setuptools packaging needs in a consistent manner.
Don't forget to add a file __compile__.py with nothing in package to compiled.
You can use use PBR with cypack in setup.cfg:
[metadata]
name = my_compiled_project
setup_requires=cypack,pbr
cypack=True
pbr=True
...
[options]
install_requires =
cypack
or setup.py
from setuptools import setup
setup(
setup_requires=['pbr','cypack[build]'],
pbr=True,
cypack=True,
)
Now, you can build your compiled wheel.
python setup.py bdist_wheel
Sample
The project test-cythonpackage propose a tiny exemple to use cypack, and generate all binary version, with a GitHub Action.
Advanced usage
To make this magic, we manipulate some special parameters at different levels.
You can remove some manipulation with a dictionary in cypack parameter.
from setuptools import setup, find_packages
setup(
setup_requires=['pbr','cypack'],
pbr=True,
cypack={
"inject_ext_modules": True,
"inject_init": True,
"remove_source": True,
"compile_py": True,
"optimize": 1,
"exclude": [] # List of glob
},
)
and you can de-activate cypack with the environment variable CYPACK=false
CYPACK=false python setup.py bdist_wheel
to generate a classical version, without compilation and with python source code.
inject_ext_modules
Detect all packages with __compile__.py, and generate a list of Extension to generate the compiled version of
the module __compile__ with all the source code. But with this, it's important to add an extension in
sys.meta_path to use only one shared library.
It's done at the begining in all __init__.py files.
If you set this parameter to False, you must write yourself the ext_modules parameter
setup(...
ext_modules=cythonize(
[
Extension(
name=f"foo.__compile__",
sources=['foo/*.py']
)
],
build_dir="build/cypack",
compiler_directives={'language_level': 3}
)
)
inject_init
By default, during the build process, all the __init__.py file for the package with __compile__.py are patched
on the fly, to inject two line:
import cypack
cypack.init(__name__)
If this manipulation break something, set this parameter to False and add yourself these two lines
at THE BEGINNING of the file, before others import.
remove_source
Because the shared library is enough to use the package, the source code is not necessary. And, sometime, it's possible to have a confusion between the compiled version and the interpreted version. To be sure to use the compiled version, we remove all the source code of the compiled files.
If you set this parameter to False, the source code were inside the wheel.
compile_py / optimize
The objectif of this kind of build, it to optimize the usage of the package.
Normally, it's possible to compile the source code to python-byte-code
with python setup.py build_py --compile.
But the bdist_wheel can not receive the --compile parameter.
With cypack, by default, the compile is set to True,
and the optimize is set to 1 to remove the assertions.
You can change these parameters.
exclude
Sometime, a specific python source code can not be compiled with Cython. You can exclude this file with a list of glob pattern.
setup(
setup_requires=['pbr','cypack'],
pbr=True,
cypack={
"exclude": ["foo/*special.py"] # List of glob
},
install_requires = ["cypack"]
)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cypack2-0.0.3-py3-none-any.whl.
File metadata
- Download URL: cypack2-0.0.3-py3-none-any.whl
- Upload date:
- Size: 12.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.19
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d7312d1b791c0fed39a1da43c50c7cdcc2f47a0bb08733fe5e872a688bc065b2
|
|
| MD5 |
53be375ab7245d0ca0b0bcdb696f8b67
|
|
| BLAKE2b-256 |
93e1527caba14e78776afbf3438dcc83f5dc45304438bf069a172071172c0ffe
|