Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

📦⚡️Compile Package Modules as C-Extensions via Mypyc #273

17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ Table of Contents
* [:rocket: Project Standardization and Automation](#rocket-project-standardization-and-automation)
+ [Developer Workflow Automation](#developer-workflow-automation)
+ [Conditionally Rendered Python Package/Project Boilerplate](#conditionally-rendered-python-packageproject-boilerplate)
* [:zap: Performance](#zap-performance)
+ [C-Extension Compilation](#c-extension-compilation)
* [:wrench: Maintainability](#wrench-maintainability)
+ [Type Checking and Data Validation](#type-checking-and-data-validation)
+ [Testing/Coverage](#testingcoverage)
Expand Down Expand Up @@ -141,6 +143,21 @@ Features
completely reproducible execution environment
- [Optional] [Jupyter](https://jupyter.org/) support[*](#conditional-rendering)

:zap: Performance
-----------------

### C-Extension Compilation

- Python module to C-extension compilation (enabled by standard Python type hints)
with [Mypyc](https://mypyc.readthedocs.io/en/latest/index.html)
- Automatically configured for Python Package builds (see the template's
[build.py](./{{cookiecutter.project_slug}}/build.py) file)

> :warning:️ Warning
> Mypyc is currently alpha software. It’s only recommended for production use cases
> with careful testing, and if you are willing to contribute fixes or to work around
> issues you will encounter.

:wrench: Maintainability
------------------

Expand Down
25 changes: 16 additions & 9 deletions {{cookiecutter.project_slug}}/.github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,13 @@ jobs:
{%- if cookiecutter.project_type == 'package' %}
# Package build ----------------------
package-build:
strategy:
matrix:
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
python-version: ["3.7", "3.8", "3.9"]

name: Package build
runs-on: ubuntu-latest
runs-on: {{ "${{ matrix.os }}" }}
needs: get-tag-xor-dev-version
outputs:
# yamllint disable-line rule:line-length
Expand All @@ -92,7 +97,7 @@ jobs:
- name: Set up Python
uses: actions/[email protected]
with:
python-version: "3.9"
python-version: {{ "${{ matrix.python-version }}" }}

- name: Upgrade pip
run: |
Expand All @@ -119,12 +124,14 @@ jobs:
VERSION={{ "${{ needs.get-tag-xor-dev-version.outputs.dev-version }}" }}
poetry version "${VERSION}"
echo "::set-output name=is_testing::true"
shell: bash

- name: Log package version
id: log-package-version
run: |
VERSION=$(make get-project-version-number)
echo "::set-output name=version::${VERSION}"
shell: bash

- name: Build package
run: |
Expand All @@ -137,7 +144,7 @@ jobs:
path: dist

# PyPI/TestPyPI package upload ----------------------
pypi-package-upload:
pypi-packages-upload:
name: PyPI/TestPyPI package upload
runs-on: ubuntu-latest
needs: package-build
Expand All @@ -159,20 +166,20 @@ jobs:
pip install --constraint=.github/workflows/constraints.txt pip
pip --version

- name: Download the binary wheel
- name: Download the binary wheels
uses: actions/[email protected]
with:
name: python-package-distributions
path: dist

- name: Publish package on PyPI
- name: Publish packages on PyPI
if: "! needs.package-build.outputs.is-test-package"
uses: pypa/[email protected]
with:
user: __token__
password: {{ "${{ secrets.PYPI_TOKEN }}" }} # pragma: allowlist secret

- name: Publish package on TestPyPI
- name: Publish packages on TestPyPI
if: "needs.package-build.outputs.is-test-package"
uses: pypa/[email protected]
with:
Expand All @@ -189,7 +196,7 @@ jobs:

name: Verify package install as user
runs-on: {{ "${{ matrix.os }}" }}
needs: pypi-package-upload
needs: pypi-packages-upload

steps:

Expand All @@ -206,14 +213,14 @@ jobs:
function install_test_pypi() { pip install ${TEST_PYPI_PACKAGE} --no-deps && install_pypi; }
function install_pypi() { pip install --upgrade ${PYPI_PACKAGE}; }

until (install_test_pypi || install_pypi) &> /dev/null
until (install_test_pypi || install_pypi)
do
echo "Waiting for Python Package Index to serve current package-under-test: ${PYPI_PACKAGE}"
sleep 10
done
pip list -v
env:
PACKAGE_VERSION: {{ "${{ needs.pypi-package-upload.outputs.package-version }}" }}
PACKAGE_VERSION: {{ "${{ needs.pypi-packages-upload.outputs.package-version }}" }}
shell: bash
# yamllint enable rule:line-length

Expand Down
10 changes: 10 additions & 0 deletions {{cookiecutter.project_slug}}/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,16 @@ make provision-environment
> Invoking the above without `poetry` installed will emit a
> helpful error message letting you know how you can install Poetry.

Python Module to C-Extension Compilation
----------------------------------------

{%- if cookiecutter.project_type == 'package' %}
The projects's [build.py](./build.py) file specifies which modules to package.
{%- endif %}

For manual per-module compilation, see:
[Mypyc Documentation: Getting started - Compiling and running](https://mypyc.readthedocs.io/en/latest/getting_started.html#compiling-and-running)

Docker Container Image Building/Deployment Orchestration
--------------------------------------------------------

Expand Down
18 changes: 18 additions & 0 deletions {{cookiecutter.project_slug}}/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import mypyc.build


def build(setup_kwargs: dict) -> None:
"""
This function is mandatory in order to build the extensions.
"""
setup_kwargs.update(
{
"ext_modules": mypyc.build.mypycify(
[
"--disallow-untyped-defs",
"{{cookiecutter.package_name}}/__init__.py",
"{{cookiecutter.package_name}}/main.py",
]
),
}
)
7 changes: 6 additions & 1 deletion {{cookiecutter.project_slug}}/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules",
]

[tool.poetry.build]
script = "build.py"
generate-setup-file = false

[tool.poetry.urls]
Changelog = "{{ cookiecutter.project_repository_url }}/releases"

Expand Down Expand Up @@ -89,6 +93,7 @@ icontract-hypothesis = "^1.1.0"
mutmut = "^2.1.0"
xdoctest = {extras = ["all"], version = "^0.15.4"}
tox = "^3.21.4"
tox-wheel = "^0.6.0"

# Linting
## Code formatting
Expand Down Expand Up @@ -231,5 +236,5 @@ testpaths = ["tests",]
norecursedirs = [".*", "*.egg", "build", "dist",]

[build-system]
requires = ["poetry-core>=1.0.0"]
requires = ["poetry-core>=1.0.0", "mypy"]
build-backend = "poetry.core.masonry.api"
9 changes: 9 additions & 0 deletions {{cookiecutter.project_slug}}/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ commands = coverage run -m pytest \
--basetemp={envtmpdir} \
-n={env:PYTEST_XDIST_PROC_NR:auto} \
{posargs:tests}
wheel = true
wheel_pep517 = true
parallel_show_output = True

[testenv:py3{7,8,9}-benchmark]
Expand All @@ -68,6 +70,8 @@ commands =
--benchmark-sort=mean \
--benchmark-verbose \
{posargs:tests}'
wheel = true
wheel_pep517 = true
parallel_show_output = {[testenv]parallel_show_output}

[testenv:mutmut]
Expand Down Expand Up @@ -119,6 +123,8 @@ commands =
echo -e "Test report available at \033[4;36m \
{env:JUNIT_HTML_REPORT_OUTPUT_PATH} \
\033[0m"'
wheel = true
wheel_pep517 = true

[testenv:coverage]
description = [Run locally after tests]: Combine coverage data and create reports;
Expand Down Expand Up @@ -168,6 +174,8 @@ commands =
# Run after testenv since auto-generated temp `.py` files interfere with pytest
depends =
py3{7,8,9}
wheel = true
wheel_pep517 = true
parallel_show_output = {[testenv]parallel_show_output}
{%- endif %}

Expand Down Expand Up @@ -211,6 +219,7 @@ commands = pre-commit run {posargs} -vv --all-files --color always
description = Build Python package and validate its long description
deps =
poetry >= 1.1.2
mypy >= 0.910
twine >= 1.12.1
readme-renderer[md] >= 24.0
skip_install = true
Expand Down