diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4763b0a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +name: Continuous Integration + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + release: + types: + - published + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + # macos-13 is an intel runner, macos-14 is apple silicon + os: [ ubuntu-latest, windows-latest, macos-13, macos-14 ] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build wheels + uses: pypa/cibuildwheel@v2.18 + # configured in pyproject.toml [tool.cibuildwheel] + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build SDist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: cibw-sdist + path: dist/*.tar.gz + + upload_pypi: + name: Publish to PyPi + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write + if: github.event_name == 'release' && github.event.action == 'published' + steps: + - uses: actions/download-artifact@v4 + with: + # unpacks all CIBW artifacts into dist/ + pattern: cibw-* + path: dist + merge-multiple: true + + # trusted publishing workflow: + # https://docs.pypi.org/trusted-publishers/adding-a-publisher/ + - uses: pypa/gh-action-pypi-publish@release/v1.8 diff --git a/.gitignore b/.gitignore index 4f02d37..5f9dceb 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,9 @@ ENV/ # Pipenv Pipfile.lock + +# macOS +.DS_Store + +# PyCharm +.idea/ diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 2fc8f71..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include src/build_blurhash.py diff --git a/Pipfile b/Pipfile index d7d6e31..ef548ee 100644 --- a/Pipfile +++ b/Pipfile @@ -4,6 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] +build = "*" tox = "*" pytest = "*" blurhash-python = {editable = true,extras = ["testing"],path = "."} diff --git a/README.md b/README.md index 77d0606..ae3857c 100644 --- a/README.md +++ b/README.md @@ -59,3 +59,11 @@ Use `tox` to run test suite against all supported python versions ``` $ tox ``` + +Local Build +----------- + +Build source distribution and wheels into `dist/` directory. +``` +$ python -m build +``` \ No newline at end of file diff --git a/build.sh b/build.sh deleted file mode 100755 index 57f8e1a..0000000 --- a/build.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -set -euo pipefail - -TMPDIST="$(mktemp -d)" -USERBASE="$(mktemp -d)" -trap "rm -rf '$TMPDIST' '$USERBASE'" EXIT - -pybins=(/opt/python/cp{38,39,310,311,312}-cp*/bin) - -SRCDIST="$(ls -vr dist/blurhash-python-*.tar.gz | head -n1)" - -for pybin in ${pybins[@]}; do - "${pybin}/pip" wheel --no-cache-dir -w "$TMPDIST" "$SRCDIST[testing]" -done - -for whl in "$TMPDIST"/blurhash_python*.whl; do - auditwheel repair "$whl" --plat "$PLAT" -w dist - rm "$whl" -done - -ORIGPATH="$PATH" - -for pybin in ${pybins[@]}; do - userbindir="$USERBASE/${pybin#/opt/python/}" - export PYTHONUSERBASE="${userbindir%/bin}" - export PATH="$ORIGPATH:$userbindir" - "${pybin}/pip" install --no-cache-dir --user --no-index -f dist -f "$TMPDIST" "blurhash-python[testing]" - "${userbindir}/pytest" -done diff --git a/container-build.sh b/container-build.sh deleted file mode 100755 index 95a1322..0000000 --- a/container-build.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -set -euo pipefail - -function podman-run { - local platform="$1" - local arch="$2" - local workdir="/tmp/blurhash-python" - - podman run --rm \ - -e "PLAT=$platform" \ - -v "$(pwd)":"$workdir" \ - -w $workdir \ - --userns keep-id \ - --arch "$arch" \ - "quay.io/pypa/$platform" "${@:3}" -} - -python -m build -s -podman-run manylinux_2_28_x86_64 amd64 ./build.sh -podman-run manylinux_2_28_aarch64 arm64 ./build.sh diff --git a/pyproject.toml b/pyproject.toml index b0471b7..3b83088 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,57 @@ [build-system] -requires = ["setuptools", "wheel"] -build-backend = "setuptools.build_meta:__legacy__" \ No newline at end of file +requires = ["setuptools>=64", "setuptools-scm>=8", "wheel", "cffi"] +build-backend = "setuptools.build_meta" + +[project] +name = "blurhash-python" +description = "BlurHash encoder implementation for Python" +requires-python = ">=3.8" +readme = "README.md" +license = { file = "LICENSE" } +authors = [ + { name = "Atte Lautanala", email = "atte.lautanala@wolt.com" } +] +dynamic = ["version"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +dependencies = [ + "cffi", + "Pillow", + "six", +] + +[project.optional-dependencies] +testing = [ + "pytest", +] + +[project.urls] +Homepage = "https://blurha.sh" +Repository = "https://github.com/woltapp/blurhash-python" + +[tool.setuptools.packages.find] +where = ["src"] +include = ["blurhash*"] + +[tool.setuptools_scm] +version_file = "src/blurhash/_version.py" + +[tool.cibuildwheel] +build-frontend = "build" +# skip platforms +# pp* - don't build any PyPy variations +# win32* - don't build 32bit windows variations +# *_i686 - don't build 32bit linux variations +# *musllinux* - don't build musl linux variations +skip = "pp* *-win32 *_i686 *musllinux*" +test-requires = "pytest" +test-command = "pytest {project}/tests" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 31ad82b..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[aliases] -test = pytest diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 index a39252b..17e9d0f --- a/setup.py +++ b/setup.py @@ -1,51 +1,5 @@ -#!/usr/bin/env python from setuptools import setup - -with open('README.md', 'r') as readme_file: - long_description = readme_file.read() - - -tests_require = [ - 'pytest', -] - setup( - name='blurhash-python', - description='BlurHash encoder implementation for Python', - long_description=long_description, - long_description_content_type='text/markdown', - url='https://blurha.sh', - use_scm_version=dict( - write_to='src/blurhash/_version.py', - ), - author='Atte Lautanala', - author_email='atte.lautanala@wolt.com', - packages=['blurhash'], - package_dir={'': 'src'}, - install_requires=[ - 'cffi', - 'Pillow', - 'six', - ], - setup_requires=[ - 'cffi', - 'setuptools-scm', - ], cffi_modules=['src/build_blurhash.py:ffibuilder'], - tests_require=tests_require, - extras_require={ - 'testing': tests_require, - }, - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - ], ) diff --git a/tests/test_encode.py b/tests/test_encode.py index 5d7cb27..55a91f5 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -1,32 +1,40 @@ from __future__ import absolute_import +import os + import pytest from PIL import Image from blurhash import encode +_test_dir = os.path.dirname(__file__) + + +def _get_test_file(relative_path: str) -> str: + return os.path.join(_test_dir, relative_path) + def test_encode_file(): - with open('tests/pic2.png', 'rb') as image_file: + with open(_get_test_file('pic2.png'), 'rb') as image_file: result = encode(image_file, 4, 3) assert result == 'LlMF%n00%#MwS|WCWEM{R*bbWBbH' def test_encode_pil_image(): - with Image.open('tests/pic2.png') as image: + with Image.open(_get_test_file('pic2.png')) as image: result = encode(image, 4, 3) assert result == 'LlMF%n00%#MwS|WCWEM{R*bbWBbH' def test_encode_with_filename(): - result = encode('tests/pic2.png', 4, 3) + result = encode(_get_test_file('pic2.png'), 4, 3) assert result == 'LlMF%n00%#MwS|WCWEM{R*bbWBbH' def test_encode_black_and_white_picture(): - result = encode('tests/pic2_bw.png', 4, 3) + result = encode(_get_test_file('pic2_bw.png'), 4, 3) assert result == 'LjIY5?00?bIUofWBWBM{WBofWBj[' @@ -42,15 +50,15 @@ def test_file_does_not_exist(): def test_invalid_x_components(): with pytest.raises(ValueError): - encode('tests/pic2.png', 10, 3) + encode(_get_test_file('pic2.png'), 10, 3) with pytest.raises(ValueError): - encode('tests/pic2.png', 0, 3) + encode(_get_test_file('pic2.png'), 0, 3) def test_invalid_y_components(): with pytest.raises(ValueError): - encode('tests/pic2.png', 4, 10) + encode(_get_test_file('pic2.png'), 4, 10) with pytest.raises(ValueError): - encode('tests/pic2.png', 4, 0) + encode(_get_test_file('pic2.png'), 4, 0)