From ccd2e8fbdef6557be595400a6199511c1422bd64 Mon Sep 17 00:00:00 2001 From: Vinay Parakala Date: Wed, 17 Jun 2026 14:40:42 -0400 Subject: [PATCH 1/7] Build native arm64 macOS wheels --- .circleci/config.yml | 2 +- CHANGELOG.md | 7 ++++++- pyproject.toml | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 739e1e04..63c995c4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -151,7 +151,7 @@ jobs: python3 -m pip install cibuildwheel softwareupdate --install-rosetta --agree-to-license || true - export CIBW_ARCHS_MACOS="universal2" + export CIBW_ARCHS_MACOS="arm64" export CIBW_BUILD="cp310-macosx_* cp311-macosx_* cp312-macosx_* cp313-macosx_* cp314-macosx_*" export CIBW_SKIP="pp*" export CIBW_TEST_COMMAND='python -c "import nucleus._native_dedup as native; assert native.deduplicate_phashes([0, 1023, 2047], 10) == [0, 2]"' diff --git a/CHANGELOG.md b/CHANGELOG.md index 1726d8e4..6390c083 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to the [Nucleus Python Client](https://github.com/scaleapi/n The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.18.8](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.18.8) - 2026-06-17 + +### Fixed +- Build macOS wheels as native `arm64` wheels on the CircleCI Apple Silicon runner instead of requesting `universal2`, which produced an `arm64` wheel that cibuildwheel then tried to test under `x86_64`. + ## [0.18.7](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.18.7) - 2026-06-17 ### Fixed @@ -16,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Native C acceleration for `deduplicate_by_phash`. When the compiled extension is available, all threshold values are handled in native code: thresholds `0` through `11` use the chunked Hamming index, thresholds `12` through `63` use a native linear scan, and threshold `64` uses the keep-first fast path. The public Python API is unchanged and falls back to the pure-Python implementation when the native extension is unavailable. ### Tooling / CI -- Publish Linux `x86_64`, macOS `universal2`, and Windows `amd64` wheels for Python 3.10 through 3.14 using `cibuildwheel`, alongside the source distribution. +- Publish Linux `x86_64`, macOS `arm64`, and Windows `amd64` wheels for Python 3.10 through 3.14 using `cibuildwheel`, alongside the source distribution. ## [0.18.5](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.18.5) - 2026-05-28 diff --git a/pyproject.toml b/pyproject.toml index 3e1d5634..794811da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ ignore = ["E501", "E741", "E731", "F401"] # Easy ignore for getting it running [tool.poetry] name = "scale-nucleus" -version = "0.18.7" +version = "0.18.8" description = "The official Python client library for Nucleus, the Data Platform for AI" license = "MIT" authors = ["Scale AI Nucleus Team "] From 24aebc9bd3a4321d1b6393ab5152562a9075e8a0 Mon Sep 17 00:00:00 2001 From: Vinay Parakala Date: Wed, 17 Jun 2026 15:15:50 -0400 Subject: [PATCH 2/7] Run release wheel checks on PRs --- .circleci/config.yml | 52 +++++++++--- CHANGELOG.md | 3 + scripts/validate_release_artifacts.py | 117 ++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 scripts/validate_release_artifacts.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 63c995c4..6a676ddf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -89,6 +89,10 @@ jobs: - run: name: Validate Tag Version # Check if the tag name matches the package version command: | + if [[ -z "$CIRCLE_TAG" ]]; then + echo "Not a tag build; skipping tag/version match validation." + exit 0 + fi PKG_VERSION=$(sed -n 's/^version = //p' pyproject.toml | sed -e 's/^"//' -e 's/"$//') if [[ "$CIRCLE_TAG" != "v${PKG_VERSION}" ]]; then echo "ERROR: Tag name ($CIRCLE_TAG) must match package version (v${PKG_VERSION})." @@ -100,6 +104,11 @@ jobs: PKG_VERSION=$(sed -n 's/^version = //p' pyproject.toml | sed -e 's/^"//' -e 's/"$//') if pip install "scale-nucleus>=${PKG_VERSION}" > /dev/null 2>&1; then + if [[ -z "$CIRCLE_TAG" ]]; then + echo "WARNING: Version (${PKG_VERSION}) already exists on PyPI." + echo "This is only enforced for tag builds so regular PRs are not blocked after a release." + exit 0; + fi echo "ERROR: You need to increment to a new version before publishing!" echo "Version (${PKG_VERSION}) already exists on PyPI." exit 1; @@ -149,7 +158,6 @@ jobs: command: | python3 -m pip install --upgrade pip python3 -m pip install cibuildwheel - softwareupdate --install-rosetta --agree-to-license || true export CIBW_ARCHS_MACOS="arm64" export CIBW_BUILD="cp310-macosx_* cp311-macosx_* cp312-macosx_* cp313-macosx_* cp314-macosx_*" @@ -186,6 +194,27 @@ jobs: paths: - dist + validate_release_artifacts: + docker: + - image: cimg/python:3.10 + steps: + - checkout # checkout source code to working directory + - attach_workspace: + at: . + - run: + name: Install Publish Tools + command: | + pip install --upgrade pip + pip install poetry + - run: + name: Validate Release Artifact Bundle + command: | + python scripts/validate_release_artifacts.py + - run: + name: Poetry Publish Dry Run + command: | + poetry publish --dry-run --no-interaction --username dry-run --password dry-run + pypi_publish: docker: - image: cimg/python:3.10 @@ -227,7 +256,7 @@ jobs: echo "ERROR: Please assign PYPI_USERNAME and PYPI_PASSWORD as environment variables" exit 1 fi - poetry publish --username=$PYPI_USERNAME --password=$PYPI_PASSWORD + poetry publish --no-interaction --username=$PYPI_USERNAME --password=$PYPI_PASSWORD test_client_installation: parameters: python_version: @@ -288,8 +317,6 @@ workflows: requires: - build_test filters: - branches: - ignore: /.*/ # Runs for none of the branches tags: only: /^v\d+\.\d+\.\d+$/ # Runs only for tags with the format [v1.2.3] - build_linux_wheels: @@ -297,8 +324,6 @@ workflows: requires: - build_test filters: - branches: - ignore: /.*/ # Runs for none of the branches tags: only: /^v\d+\.\d+\.\d+$/ # Runs only for tags with the format [v1.2.3] - build_macos_wheels: @@ -306,8 +331,6 @@ workflows: requires: - build_test filters: - branches: - ignore: /.*/ # Runs for none of the branches tags: only: /^v\d+\.\d+\.\d+$/ # Runs only for tags with the format [v1.2.3] - build_windows_wheels: @@ -315,8 +338,16 @@ workflows: requires: - build_test filters: - branches: - ignore: /.*/ # Runs for none of the branches + tags: + only: /^v\d+\.\d+\.\d+$/ # Runs only for tags with the format [v1.2.3] + - validate_release_artifacts: + context: Nucleus + requires: + - build_sdist + - build_linux_wheels + - build_macos_wheels + - build_windows_wheels + filters: tags: only: /^v\d+\.\d+\.\d+$/ # Runs only for tags with the format [v1.2.3] - pypi_publish: @@ -326,6 +357,7 @@ workflows: - build_linux_wheels - build_macos_wheels - build_windows_wheels + - validate_release_artifacts filters: branches: ignore: /.*/ # Runs for none of the branches diff --git a/CHANGELOG.md b/CHANGELOG.md index 6390c083..d42c9b0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Build macOS wheels as native `arm64` wheels on the CircleCI Apple Silicon runner instead of requesting `universal2`, which produced an `arm64` wheel that cibuildwheel then tried to test under `x86_64`. +### Tooling / CI +- Run the full source distribution, Linux wheel, macOS wheel, Windows wheel, release artifact validation, and `poetry publish --dry-run` checks on PRs so release packaging failures are caught before tagging and publishing. + ## [0.18.7](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.18.7) - 2026-06-17 ### Fixed diff --git a/scripts/validate_release_artifacts.py b/scripts/validate_release_artifacts.py new file mode 100644 index 00000000..be13bfa6 --- /dev/null +++ b/scripts/validate_release_artifacts.py @@ -0,0 +1,117 @@ +"""Validate that the dist directory contains the expected release bundle.""" + +import argparse +import re +import sys +import zipfile +from pathlib import Path +from typing import Callable, List, Optional + +PYTHON_TAGS = ("cp310", "cp311", "cp312", "cp313", "cp314") + + +NATIVE_EXTENSION_PATTERNS = { + "manylinux": re.compile(r"^nucleus/_native_dedup\..*-linux-gnu\.so$"), + "macosx": re.compile(r"^nucleus/_native_dedup\..*-darwin\.so$"), + "win_amd64": re.compile(r"^nucleus/_native_dedup\..*\.pyd$"), +} + + +def _version_from_pyproject(pyproject_path: Path) -> str: + for line in pyproject_path.read_text().splitlines(): + if line.startswith("version = "): + return line.split("=", 1)[1].strip().strip('"') + raise ValueError(f"{pyproject_path} is missing a version field") + + +def _matching_platform_pattern(wheel_name: str) -> Optional[re.Pattern[str]]: + for platform_key, pattern in NATIVE_EXTENSION_PATTERNS.items(): + if platform_key in wheel_name: + return pattern + return None + + +def _has_matching_wheel( + wheels: List[str], + python_tag: str, + platform_match: Callable[[str], bool], +) -> bool: + return any( + f"-{python_tag}-{python_tag}-" in wheel and platform_match(wheel) + for wheel in wheels + ) + + +def validate_release_artifacts( + dist_dir: Path, + pyproject_path: Path, +) -> List[str]: + version = _version_from_pyproject(pyproject_path) + files = sorted(path.name for path in dist_dir.iterdir() if path.is_file()) + wheels = [filename for filename in files if filename.endswith(".whl")] + errors = [] + + print("Release artifacts:") + for filename in files: + print(f" {filename}") + + expected_sdist = f"scale_nucleus-{version}.tar.gz" + if expected_sdist not in files: + errors.append(f"missing sdist {expected_sdist}") + + if len(wheels) != 15: + errors.append(f"expected 15 wheels, found {len(wheels)}") + + expected_platforms = { + "linux": lambda name: "manylinux" in name, + "macos": lambda name: "macosx_" in name + and name.endswith("_arm64.whl"), + "windows": lambda name: name.endswith("win_amd64.whl"), + } + for python_tag in PYTHON_TAGS: + for platform_name, platform_match in expected_platforms.items(): + if not _has_matching_wheel(wheels, python_tag, platform_match): + errors.append(f"missing {python_tag} {platform_name} wheel") + + for wheel in wheels: + wheel_path = dist_dir / wheel + with zipfile.ZipFile(wheel_path) as wheel_zip: + native_files = [ + name + for name in wheel_zip.namelist() + if "_native_dedup" in name + and (name.endswith(".so") or name.endswith(".pyd")) + ] + + if len(native_files) != 1: + errors.append( + f"{wheel} should contain one native binary, found {native_files}" + ) + continue + + expected_pattern = _matching_platform_pattern(wheel) + if expected_pattern is None: + errors.append(f"{wheel} has an unexpected platform tag") + elif not expected_pattern.match(native_files[0]): + errors.append( + f"{wheel} contains unexpected native binary {native_files[0]}" + ) + + return errors + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--dist-dir", default="dist", type=Path) + parser.add_argument("--pyproject", default="pyproject.toml", type=Path) + args = parser.parse_args() + + errors = validate_release_artifacts(args.dist_dir, args.pyproject) + if errors: + for error in errors: + print(f"ERROR: {error}") + sys.exit(1) + + +if __name__ == "__main__": + main() From 44f68261f449ce12a8af7cfc2803d5858c7538cd Mon Sep 17 00:00:00 2001 From: Vinay Parakala Date: Wed, 17 Jun 2026 16:13:54 -0400 Subject: [PATCH 3/7] Pin release wheel builder --- .circleci/config.yml | 8 ++++---- CHANGELOG.md | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6a676ddf..425883ef 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -125,7 +125,7 @@ jobs: build_linux_wheels: docker: - - image: cimg/python:3.10 + - image: cimg/python:3.12 steps: - checkout # checkout source code to working directory - setup_remote_docker @@ -133,7 +133,7 @@ jobs: name: Build Linux wheels command: | pip install --upgrade pip - pip install cibuildwheel + pip install "cibuildwheel==4.1.0" export CIBW_ARCHS_LINUX="x86_64" export CIBW_BUILD="cp310-manylinux_x86_64 cp311-manylinux_x86_64 cp312-manylinux_x86_64 cp313-manylinux_x86_64 cp314-manylinux_x86_64" @@ -157,7 +157,7 @@ jobs: name: Build macOS wheels command: | python3 -m pip install --upgrade pip - python3 -m pip install cibuildwheel + python3 -m pip install "cibuildwheel==4.1.0" export CIBW_ARCHS_MACOS="arm64" export CIBW_BUILD="cp310-macosx_* cp311-macosx_* cp312-macosx_* cp313-macosx_* cp314-macosx_*" @@ -181,7 +181,7 @@ jobs: name: Build Windows wheels command: | python -m pip install --upgrade pip - python -m pip install cibuildwheel + python -m pip install "cibuildwheel==4.1.0" $env:CIBW_BUILD = "cp310-win_amd64 cp311-win_amd64 cp312-win_amd64 cp313-win_amd64 cp314-win_amd64" $env:CIBW_SKIP = "pp*" diff --git a/CHANGELOG.md b/CHANGELOG.md index d42c9b0c..73bdb619 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Tooling / CI - Run the full source distribution, Linux wheel, macOS wheel, Windows wheel, release artifact validation, and `poetry publish --dry-run` checks on PRs so release packaging failures are caught before tagging and publishing. +- Pin `cibuildwheel` in release wheel jobs so the Python 3.10 through 3.14 wheel matrix is deterministic across CI host images. ## [0.18.7](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.18.7) - 2026-06-17 From 434511f89eb78b1f234c73a6e8de3e6f7a474123 Mon Sep 17 00:00:00 2001 From: Vinay Parakala Date: Wed, 17 Jun 2026 17:01:12 -0400 Subject: [PATCH 4/7] Remove release artifact validation job --- .circleci/config.yml | 32 ------- CHANGELOG.md | 2 +- scripts/validate_release_artifacts.py | 117 -------------------------- 3 files changed, 1 insertion(+), 150 deletions(-) delete mode 100644 scripts/validate_release_artifacts.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 425883ef..d8de7fb3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -194,27 +194,6 @@ jobs: paths: - dist - validate_release_artifacts: - docker: - - image: cimg/python:3.10 - steps: - - checkout # checkout source code to working directory - - attach_workspace: - at: . - - run: - name: Install Publish Tools - command: | - pip install --upgrade pip - pip install poetry - - run: - name: Validate Release Artifact Bundle - command: | - python scripts/validate_release_artifacts.py - - run: - name: Poetry Publish Dry Run - command: | - poetry publish --dry-run --no-interaction --username dry-run --password dry-run - pypi_publish: docker: - image: cimg/python:3.10 @@ -340,16 +319,6 @@ workflows: filters: tags: only: /^v\d+\.\d+\.\d+$/ # Runs only for tags with the format [v1.2.3] - - validate_release_artifacts: - context: Nucleus - requires: - - build_sdist - - build_linux_wheels - - build_macos_wheels - - build_windows_wheels - filters: - tags: - only: /^v\d+\.\d+\.\d+$/ # Runs only for tags with the format [v1.2.3] - pypi_publish: context: Nucleus requires: @@ -357,7 +326,6 @@ workflows: - build_linux_wheels - build_macos_wheels - build_windows_wheels - - validate_release_artifacts filters: branches: ignore: /.*/ # Runs for none of the branches diff --git a/CHANGELOG.md b/CHANGELOG.md index 73bdb619..83ebd2b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Build macOS wheels as native `arm64` wheels on the CircleCI Apple Silicon runner instead of requesting `universal2`, which produced an `arm64` wheel that cibuildwheel then tried to test under `x86_64`. ### Tooling / CI -- Run the full source distribution, Linux wheel, macOS wheel, Windows wheel, release artifact validation, and `poetry publish --dry-run` checks on PRs so release packaging failures are caught before tagging and publishing. +- Run the source distribution, Linux wheel, macOS wheel, and Windows wheel checks on PRs so platform packaging failures are caught before tagging and publishing. - Pin `cibuildwheel` in release wheel jobs so the Python 3.10 through 3.14 wheel matrix is deterministic across CI host images. ## [0.18.7](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.18.7) - 2026-06-17 diff --git a/scripts/validate_release_artifacts.py b/scripts/validate_release_artifacts.py deleted file mode 100644 index be13bfa6..00000000 --- a/scripts/validate_release_artifacts.py +++ /dev/null @@ -1,117 +0,0 @@ -"""Validate that the dist directory contains the expected release bundle.""" - -import argparse -import re -import sys -import zipfile -from pathlib import Path -from typing import Callable, List, Optional - -PYTHON_TAGS = ("cp310", "cp311", "cp312", "cp313", "cp314") - - -NATIVE_EXTENSION_PATTERNS = { - "manylinux": re.compile(r"^nucleus/_native_dedup\..*-linux-gnu\.so$"), - "macosx": re.compile(r"^nucleus/_native_dedup\..*-darwin\.so$"), - "win_amd64": re.compile(r"^nucleus/_native_dedup\..*\.pyd$"), -} - - -def _version_from_pyproject(pyproject_path: Path) -> str: - for line in pyproject_path.read_text().splitlines(): - if line.startswith("version = "): - return line.split("=", 1)[1].strip().strip('"') - raise ValueError(f"{pyproject_path} is missing a version field") - - -def _matching_platform_pattern(wheel_name: str) -> Optional[re.Pattern[str]]: - for platform_key, pattern in NATIVE_EXTENSION_PATTERNS.items(): - if platform_key in wheel_name: - return pattern - return None - - -def _has_matching_wheel( - wheels: List[str], - python_tag: str, - platform_match: Callable[[str], bool], -) -> bool: - return any( - f"-{python_tag}-{python_tag}-" in wheel and platform_match(wheel) - for wheel in wheels - ) - - -def validate_release_artifacts( - dist_dir: Path, - pyproject_path: Path, -) -> List[str]: - version = _version_from_pyproject(pyproject_path) - files = sorted(path.name for path in dist_dir.iterdir() if path.is_file()) - wheels = [filename for filename in files if filename.endswith(".whl")] - errors = [] - - print("Release artifacts:") - for filename in files: - print(f" {filename}") - - expected_sdist = f"scale_nucleus-{version}.tar.gz" - if expected_sdist not in files: - errors.append(f"missing sdist {expected_sdist}") - - if len(wheels) != 15: - errors.append(f"expected 15 wheels, found {len(wheels)}") - - expected_platforms = { - "linux": lambda name: "manylinux" in name, - "macos": lambda name: "macosx_" in name - and name.endswith("_arm64.whl"), - "windows": lambda name: name.endswith("win_amd64.whl"), - } - for python_tag in PYTHON_TAGS: - for platform_name, platform_match in expected_platforms.items(): - if not _has_matching_wheel(wheels, python_tag, platform_match): - errors.append(f"missing {python_tag} {platform_name} wheel") - - for wheel in wheels: - wheel_path = dist_dir / wheel - with zipfile.ZipFile(wheel_path) as wheel_zip: - native_files = [ - name - for name in wheel_zip.namelist() - if "_native_dedup" in name - and (name.endswith(".so") or name.endswith(".pyd")) - ] - - if len(native_files) != 1: - errors.append( - f"{wheel} should contain one native binary, found {native_files}" - ) - continue - - expected_pattern = _matching_platform_pattern(wheel) - if expected_pattern is None: - errors.append(f"{wheel} has an unexpected platform tag") - elif not expected_pattern.match(native_files[0]): - errors.append( - f"{wheel} contains unexpected native binary {native_files[0]}" - ) - - return errors - - -def main() -> None: - parser = argparse.ArgumentParser() - parser.add_argument("--dist-dir", default="dist", type=Path) - parser.add_argument("--pyproject", default="pyproject.toml", type=Path) - args = parser.parse_args() - - errors = validate_release_artifacts(args.dist_dir, args.pyproject) - if errors: - for error in errors: - print(f"ERROR: {error}") - sys.exit(1) - - -if __name__ == "__main__": - main() From 65cbeb1303d18601fa99ce08da99051b57228f68 Mon Sep 17 00:00:00 2001 From: Vinay Parakala Date: Wed, 17 Jun 2026 17:06:50 -0400 Subject: [PATCH 5/7] Keep tag validation in publish job --- .circleci/config.yml | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d8de7fb3..ef9d5054 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -86,33 +86,6 @@ jobs: command: | pip install --upgrade pip pip install poetry - - run: - name: Validate Tag Version # Check if the tag name matches the package version - command: | - if [[ -z "$CIRCLE_TAG" ]]; then - echo "Not a tag build; skipping tag/version match validation." - exit 0 - fi - PKG_VERSION=$(sed -n 's/^version = //p' pyproject.toml | sed -e 's/^"//' -e 's/"$//') - if [[ "$CIRCLE_TAG" != "v${PKG_VERSION}" ]]; then - echo "ERROR: Tag name ($CIRCLE_TAG) must match package version (v${PKG_VERSION})." - exit 1; - fi - - run: - name: Validate SDK Version Increment # Check if the version is already on PyPI - command: | - PKG_VERSION=$(sed -n 's/^version = //p' pyproject.toml | sed -e 's/^"//' -e 's/"$//') - if pip install "scale-nucleus>=${PKG_VERSION}" > /dev/null 2>&1; - then - if [[ -z "$CIRCLE_TAG" ]]; then - echo "WARNING: Version (${PKG_VERSION}) already exists on PyPI." - echo "This is only enforced for tag builds so regular PRs are not blocked after a release." - exit 0; - fi - echo "ERROR: You need to increment to a new version before publishing!" - echo "Version (${PKG_VERSION}) already exists on PyPI." - exit 1; - fi - run: name: Build sdist command: | # install env dependencies From df3ead31a9b4af4509d9eb12693f2d11963ea978 Mon Sep 17 00:00:00 2001 From: Vinay Parakala Date: Wed, 17 Jun 2026 17:10:07 -0400 Subject: [PATCH 6/7] Narrow publish workflow fix --- .circleci/config.yml | 28 +++++++++++++++++++++++++++- CHANGELOG.md | 5 ++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ef9d5054..eb3ca186 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -86,6 +86,24 @@ jobs: command: | pip install --upgrade pip pip install poetry + - run: + name: Validate Tag Version # Check if the tag name matches the package version + command: | + PKG_VERSION=$(sed -n 's/^version = //p' pyproject.toml | sed -e 's/^"//' -e 's/"$//') + if [[ "$CIRCLE_TAG" != "v${PKG_VERSION}" ]]; then + echo "ERROR: Tag name ($CIRCLE_TAG) must match package version (v${PKG_VERSION})." + exit 1; + fi + - run: + name: Validate SDK Version Increment # Check if the version is already on PyPI + command: | + PKG_VERSION=$(sed -n 's/^version = //p' pyproject.toml | sed -e 's/^"//' -e 's/"$//') + if pip install "scale-nucleus>=${PKG_VERSION}" > /dev/null 2>&1; + then + echo "ERROR: You need to increment to a new version before publishing!" + echo "Version (${PKG_VERSION}) already exists on PyPI." + exit 1; + fi - run: name: Build sdist command: | # install env dependencies @@ -208,7 +226,7 @@ jobs: echo "ERROR: Please assign PYPI_USERNAME and PYPI_PASSWORD as environment variables" exit 1 fi - poetry publish --no-interaction --username=$PYPI_USERNAME --password=$PYPI_PASSWORD + poetry publish --username=$PYPI_USERNAME --password=$PYPI_PASSWORD test_client_installation: parameters: python_version: @@ -269,6 +287,8 @@ workflows: requires: - build_test filters: + branches: + ignore: /.*/ # Runs for none of the branches tags: only: /^v\d+\.\d+\.\d+$/ # Runs only for tags with the format [v1.2.3] - build_linux_wheels: @@ -276,6 +296,8 @@ workflows: requires: - build_test filters: + branches: + ignore: /.*/ # Runs for none of the branches tags: only: /^v\d+\.\d+\.\d+$/ # Runs only for tags with the format [v1.2.3] - build_macos_wheels: @@ -283,6 +305,8 @@ workflows: requires: - build_test filters: + branches: + ignore: /.*/ # Runs for none of the branches tags: only: /^v\d+\.\d+\.\d+$/ # Runs only for tags with the format [v1.2.3] - build_windows_wheels: @@ -290,6 +314,8 @@ workflows: requires: - build_test filters: + branches: + ignore: /.*/ # Runs for none of the branches tags: only: /^v\d+\.\d+\.\d+$/ # Runs only for tags with the format [v1.2.3] - pypi_publish: diff --git a/CHANGELOG.md b/CHANGELOG.md index 83ebd2b6..b629de77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Build macOS wheels as native `arm64` wheels on the CircleCI Apple Silicon runner instead of requesting `universal2`, which produced an `arm64` wheel that cibuildwheel then tried to test under `x86_64`. ### Tooling / CI -- Run the source distribution, Linux wheel, macOS wheel, and Windows wheel checks on PRs so platform packaging failures are caught before tagging and publishing. -- Pin `cibuildwheel` in release wheel jobs so the Python 3.10 through 3.14 wheel matrix is deterministic across CI host images. +- Pin `cibuildwheel` in release wheel jobs and run the Linux wheel builder from a compatible Python host so the Python 3.10 through 3.14 wheel matrix is deterministic. ## [0.18.7](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.18.7) - 2026-06-17 @@ -25,7 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Native C acceleration for `deduplicate_by_phash`. When the compiled extension is available, all threshold values are handled in native code: thresholds `0` through `11` use the chunked Hamming index, thresholds `12` through `63` use a native linear scan, and threshold `64` uses the keep-first fast path. The public Python API is unchanged and falls back to the pure-Python implementation when the native extension is unavailable. ### Tooling / CI -- Publish Linux `x86_64`, macOS `arm64`, and Windows `amd64` wheels for Python 3.10 through 3.14 using `cibuildwheel`, alongside the source distribution. +- Publish Linux `x86_64`, macOS `universal2`, and Windows `amd64` wheels for Python 3.10 through 3.14 using `cibuildwheel`, alongside the source distribution. ## [0.18.5](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.18.5) - 2026-05-28 From 834f710bdc5281d073d49dc27ff0e079d9fd3e78 Mon Sep 17 00:00:00 2001 From: Vinay Parakala Date: Wed, 17 Jun 2026 17:22:35 -0400 Subject: [PATCH 7/7] Select Windows host Python for wheel builds --- .circleci/config.yml | 17 ++++++++++++++--- CHANGELOG.md | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index eb3ca186..486b697b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -171,13 +171,24 @@ jobs: - run: name: Build Windows wheels command: | - python -m pip install --upgrade pip - python -m pip install "cibuildwheel==4.1.0" + $PythonHost = Get-ChildItem -Path "C:\Python3*\python.exe" | + Where-Object { + & $_.FullName -c "import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)" + $LASTEXITCODE -eq 0 + } | + Sort-Object FullName -Descending | + Select-Object -First 1 -ExpandProperty FullName + if (-not $PythonHost) { + throw "No Python 3.11+ host interpreter found for cibuildwheel" + } + & $PythonHost --version + & $PythonHost -m pip install --upgrade pip + & $PythonHost -m pip install "cibuildwheel==4.1.0" $env:CIBW_BUILD = "cp310-win_amd64 cp311-win_amd64 cp312-win_amd64 cp313-win_amd64 cp314-win_amd64" $env:CIBW_SKIP = "pp*" $env:CIBW_TEST_COMMAND = 'python -c "import nucleus._native_dedup as native; assert native.deduplicate_phashes([0, 1023, 2047], 10) == [0, 2]"' - python -m cibuildwheel --platform windows --output-dir dist + & $PythonHost -m cibuildwheel --platform windows --output-dir dist Get-ChildItem dist - persist_to_workspace: diff --git a/CHANGELOG.md b/CHANGELOG.md index b629de77..4212614c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Build macOS wheels as native `arm64` wheels on the CircleCI Apple Silicon runner instead of requesting `universal2`, which produced an `arm64` wheel that cibuildwheel then tried to test under `x86_64`. ### Tooling / CI -- Pin `cibuildwheel` in release wheel jobs and run the Linux wheel builder from a compatible Python host so the Python 3.10 through 3.14 wheel matrix is deterministic. +- Pin `cibuildwheel` in release wheel jobs, run the Linux wheel builder from a compatible Python host, and select a Python 3.11+ Windows host interpreter so the Python 3.10 through 3.14 wheel matrix is deterministic. ## [0.18.7](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.18.7) - 2026-06-17