This is an automated email from the ASF dual-hosted git repository.

paleolimbot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-nanoarrow.git


The following commit(s) were added to refs/heads/main by this push:
     new b636a8f8 ci(python): Add cibuildwheel setup for Python wheels (#353)
b636a8f8 is described below

commit b636a8f88d6a529ff31da6847ac44b584847e5f6
Author: Dewey Dunnington <[email protected]>
AuthorDate: Wed Jan 10 19:52:51 2024 +0000

    ci(python): Add cibuildwheel setup for Python wheels (#353)
    
    This PR adds a weekly/workflow dispatch job for building and testing
    Python wheels. This required a few housekeeping items:
    
    - Versioning the python package. I used the approach from ADBC, which is
    a modified 'miniver'. Basically, just set the version as a string using
    a regex replace when needed.
    - The bootstrap.py logic was updated to use a proper temporary directory
    - Tests were updated to skip instead of fail when pyarrow/numpy are not
    available (because I can never remember which platforms they will or
    won't install on and the default cibuildwheel grid is large).
    - I hadn't tested install from sdist, so a few files were missing from
    the manifest.
    
    At least one test doesn't pass on 32-bit Windows (already fixed in
    #340). For now I just enabled the version tests to make sure everything
    built/linked properly.
    
    ---------
    
    Co-authored-by: Joris Van den Bossche <[email protected]>
---
 .github/workflows/python-wheels.yaml               | 111 +++++++++++++++++++++
 dev/release/utils-prepare.sh                       |   8 ++
 python/.gitattributes                              |   1 +
 python/MANIFEST.in                                 |   3 +
 python/bootstrap.py                                |  77 +++++++-------
 python/pyproject.toml                              |   4 +-
 python/setup.py                                    |  23 ++++-
 python/src/nanoarrow/__init__.py                   |   1 +
 .../nanoarrow/{__init__.py => _static_version.py}  |  10 +-
 python/src/nanoarrow/_version.py                   |  50 ++++++++++
 python/tests/test_capsules.py                      |   4 +-
 python/tests/test_device.py                        |   4 +-
 python/tests/test_nanoarrow.py                     |   9 +-
 python/{MANIFEST.in => tests/test_version.py}      |  17 +++-
 14 files changed, 270 insertions(+), 52 deletions(-)

diff --git a/.github/workflows/python-wheels.yaml 
b/.github/workflows/python-wheels.yaml
new file mode 100644
index 00000000..4168b96f
--- /dev/null
+++ b/.github/workflows/python-wheels.yaml
@@ -0,0 +1,111 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: Build Python Wheels
+
+# Build wheels weekly, on commit to main, or when requested
+on:
+  pull_request:
+    branches:
+      - main
+    paths:
+      - '.github/workflows/python-wheels.yaml'
+      - 'python/setup.py'
+      - 'python/pyproject.toml'
+      - 'python/bootstrap.py'
+      - 'python/MANIFEST.in'
+  push:
+    branches:
+      - main
+  workflow_dispatch:
+  schedule:
+    - cron: '6 0 * * 0'
+
+jobs:
+  build_sdist:
+    runs-on: "ubuntu-20.04"
+    steps:
+    - uses: actions/checkout@v4
+    - uses: actions/setup-python@v5
+    - name: Check that cmake is installed
+      run: |
+        cmake --version
+
+    - name: Install packaging tools
+      run: |
+        pip install build twine
+
+    - name: Build sdist
+      run: |
+        cd python
+        python -m build --sdist
+
+    - name: Check install from sdist
+      run: |
+        pip install python/dist/nanoarrow-*.tar.gz
+
+    - name: Test import
+      run: |
+        python -c "import nanoarrow; print(nanoarrow.__version__)"
+
+    - name: Run twine check
+      run: |
+        twine check --strict python/dist/*
+
+    - uses: actions/upload-artifact@v4
+      with:
+        name: sdist
+        path: ./python/dist/nanoarrow-*.tar.gz
+
+  build_wheels:
+    needs: ["build_sdist"]
+    name: Build wheels on ${{ matrix.config.label }}
+    runs-on: ${{ matrix.config.os }}
+    strategy:
+      matrix:
+        config:
+          - {os: "ubuntu-20.04", label: "linux"}
+          - {os: "windows-2019", label: "windows"}
+          - {os: "macOS-11", label: "macOS"}
+          - {os: ["self-hosted", "arm"], label: "linux-arm64"}
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/setup-python@v5
+        if: matrix.config.label != 'linux-arm64'
+        with:
+          python-version: "3.11"
+
+      - name: Install cibuildwheel
+        run: python -m pip install cibuildwheel==2.15.0
+
+      - name: Build wheels
+        run: |
+          python -m cibuildwheel --output-dir wheelhouse python
+        env:
+          CIBW_ARCHS_MACOS: x86_64 arm64
+          # Optional (test suite will pass if these are not available)
+          # Commenting this for now because not all the tests pass yet (fixes 
in another PR)
+          # CIBW_BEFORE_TEST: pip install --only-binary ":all:" pyarrow numpy 
|| pip install --only-binary ":all:" numpy || true
+          CIBW_TEST_REQUIRES: pytest
+          CIBW_TEST_COMMAND: pytest {package}/tests
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: wheels-${{ matrix.config.label }}
+          path: ./wheelhouse/*.whl
diff --git a/dev/release/utils-prepare.sh b/dev/release/utils-prepare.sh
index dafab506..97b5d869 100755
--- a/dev/release/utils-prepare.sh
+++ b/dev/release/utils-prepare.sh
@@ -26,11 +26,13 @@ update_versions() {
     release)
       local version=${base_version}
       local docs_version=${base_version}
+      local python_version=${base_version}
       local r_version=${base_version}
       ;;
     snapshot)
       local version=${next_version}-SNAPSHOT
       local docs_version="${next_version} (dev)"
+      local python_version="${next_version}dev"
       local r_version="${base_version}.9000"
       ;;
   esac
@@ -46,6 +48,12 @@ update_versions() {
   Rscript -e "desc::desc_set(Version = '${r_version}')"
   git add DESCRIPTION
   popd
+
+  pushd "${NANOARROW_DIR}/python/src/nanoarrow"
+  sed -i.bak -E "s/version = \".+\"/version = \"${python_version}\"/" 
_static_version.py
+  rm _static_version.py.bak
+  git add _static_version.py
+  popd
 }
 
 set_resolved_issues() {
diff --git a/python/.gitattributes b/python/.gitattributes
new file mode 100644
index 00000000..fea5bc24
--- /dev/null
+++ b/python/.gitattributes
@@ -0,0 +1 @@
+src/nanoarrow/_static_version.py export-subst
diff --git a/python/MANIFEST.in b/python/MANIFEST.in
index 9fc29372..21fdc935 100644
--- a/python/MANIFEST.in
+++ b/python/MANIFEST.in
@@ -19,3 +19,6 @@ exclude bootstrap.py
 include src/nanoarrow/nanoarrow.c
 include src/nanoarrow/nanoarrow.h
 include src/nanoarrow/nanoarrow_c.pxd
+include src/nanoarrow/nanoarrow_device.c
+include src/nanoarrow/nanoarrow_device.h
+include src/nanoarrow/nanoarrow_device_c.pxd
diff --git a/python/bootstrap.py b/python/bootstrap.py
index bbb5d669..b540058a 100644
--- a/python/bootstrap.py
+++ b/python/bootstrap.py
@@ -18,6 +18,9 @@
 import os
 import re
 import shutil
+import subprocess
+import tempfile
+import warnings
 
 
 # Generate the nanoarrow_c.pxd file used by the Cython extension
@@ -158,7 +161,6 @@ class NanoarrowPxdGenerator:
 # any changes from nanoarrow C library sources in the checkout but is not
 # strictly necessary for things like installing from GitHub.
 def copy_or_generate_nanoarrow_c():
-    this_wd = os.getcwd()
     this_dir = os.path.abspath(os.path.dirname(__file__))
     source_dir = os.path.dirname(this_dir)
 
@@ -185,42 +187,49 @@ def copy_or_generate_nanoarrow_c():
         os.path.join(source_dir, "src", "nanoarrow")
     )
     has_cmake = os.system("cmake --version") == 0
-    build_dir = os.path.join(this_dir, "_cmake")
 
-    if is_in_nanoarrow_repo:
-        device_ext_src = os.path.join(
-            source_dir, "extensions/nanoarrow_device/src/nanoarrow"
-        )
-        shutil.copyfile(
-            os.path.join(device_ext_src, "nanoarrow_device.h"), 
maybe_nanoarrow_device_h
-        )
-        shutil.copyfile(
-            os.path.join(device_ext_src, "nanoarrow_device.c"), 
maybe_nanoarrow_device_c
-        )
+    with tempfile.TemporaryDirectory() as build_dir:
+        if is_in_nanoarrow_repo:
+            device_ext_src = os.path.join(
+                source_dir, "extensions/nanoarrow_device/src/nanoarrow"
+            )
+            shutil.copyfile(
+                os.path.join(device_ext_src, "nanoarrow_device.h"),
+                maybe_nanoarrow_device_h,
+            )
+            shutil.copyfile(
+                os.path.join(device_ext_src, "nanoarrow_device.c"),
+                maybe_nanoarrow_device_c,
+            )
 
-    if has_cmake and is_cmake_dir and is_in_nanoarrow_repo:
-        try:
-            os.mkdir(build_dir)
-            os.chdir(build_dir)
-            os.system(
-                "cmake ../.. -DNANOARROW_BUNDLE=ON 
-DNANOARROW_NAMESPACE=PythonPkg"
+        if has_cmake and is_cmake_dir and is_in_nanoarrow_repo:
+            try:
+                subprocess.run(
+                    [
+                        "cmake",
+                        "-B",
+                        build_dir,
+                        "-S",
+                        source_dir,
+                        "-DNANOARROW_BUNDLE=ON",
+                        "-DNANOARROW_NAMESPACE=PythonPkg",
+                    ]
+                )
+                subprocess.run(
+                    [
+                        "cmake",
+                        "--install",
+                        build_dir,
+                        "--prefix",
+                        os.path.join(this_dir, "src", "nanoarrow"),
+                    ]
+                )
+            except Exception as e:
+                warnings.warn(f"cmake call failed: {e}")
+        else:
+            raise ValueError(
+                "Attempt to build source distribution outside the nanoarrow 
repo"
             )
-            os.system("cmake --install . --prefix=../src/nanoarrow")
-        finally:
-            if os.path.exists(build_dir):
-                # Can fail on Windows with permission issues
-                try:
-                    shutil.rmtree(build_dir)
-                except Exception as e:
-                    print(f"Failed to remove _cmake temp directory: {str(e)}")
-            os.chdir(this_wd)
-
-    elif is_in_nanoarrow_repo:
-        shutil.copyfile()
-    else:
-        raise ValueError(
-            "Attempt to build source distribution outside the nanoarrow repo"
-        )
 
     if not os.path.exists(os.path.join(this_dir, "src/nanoarrow/nanoarrow.h")):
         raise ValueError("Attempt to vendor nanoarrow.c/h failed")
diff --git a/python/pyproject.toml b/python/pyproject.toml
index 743cebe0..7850a09f 100644
--- a/python/pyproject.toml
+++ b/python/pyproject.toml
@@ -18,7 +18,8 @@
 
 [project]
 name = "nanoarrow"
-version = "1.0.0-alpha0"
+dynamic = ["version"]
+readme = "README.md"
 description = "Python bindings to the nanoarrow C library"
 authors = [{name = "Apache Arrow Developers", email = "[email protected]"}]
 license = {text = "Apache-2.0"}
@@ -34,7 +35,6 @@ repository = "https://github.com/apache/arrow-nanoarrow";
 [build-system]
 requires = [
     "setuptools >= 61.0.0",
-    "setuptools-scm",
     "Cython"
 ]
 build-backend = "setuptools.build_meta"
diff --git a/python/setup.py b/python/setup.py
index f99d9994..92f8abc9 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -23,6 +23,25 @@ import sys
 
 from setuptools import Extension, setup
 
+
+# https://github.com/jbweston/miniver
+def get_version(pkg_path):
+    """
+    Load version.py module without importing the whole package.
+
+    Template code from miniver.
+    """
+    from importlib.util import module_from_spec, spec_from_file_location
+
+    spec = spec_from_file_location("version", os.path.join(pkg_path, 
"_version.py"))
+    module = module_from_spec(spec)
+    spec.loader.exec_module(module)
+    return module.__version__
+
+
+version = get_version("src/nanoarrow")
+
+
 # Run bootstrap.py to run cmake generating a fresh bundle based on this
 # checkout or copy from ../dist if the caller doesn't have cmake available.
 # Note that bootstrap.py won't exist if building from sdist.
@@ -46,6 +65,7 @@ else:
     extra_link_args = []
     extra_define_macros = []
 
+
 setup(
     ext_modules=[
         Extension(
@@ -61,5 +81,6 @@ setup(
             extra_link_args=extra_link_args,
             define_macros=extra_define_macros,
         )
-    ]
+    ],
+    version=version,
 )
diff --git a/python/src/nanoarrow/__init__.py b/python/src/nanoarrow/__init__.py
index 46204d3f..789f4597 100644
--- a/python/src/nanoarrow/__init__.py
+++ b/python/src/nanoarrow/__init__.py
@@ -17,3 +17,4 @@
 
 from ._lib import Array, ArrayStream, ArrayView, Schema, c_version  # noqa: 
F401
 from .lib import array, array_stream, schema, array_view  # noqa: F401
+from ._version import __version__  # noqa: F401
diff --git a/python/src/nanoarrow/__init__.py 
b/python/src/nanoarrow/_static_version.py
similarity index 73%
copy from python/src/nanoarrow/__init__.py
copy to python/src/nanoarrow/_static_version.py
index 46204d3f..53569eff 100644
--- a/python/src/nanoarrow/__init__.py
+++ b/python/src/nanoarrow/_static_version.py
@@ -14,6 +14,12 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+#
+# This file is part of 'miniver': https://github.com/jbweston/miniver
+
+# Replaced by version-bumping scripts at release time
+version = "0.4.0dev0"
 
-from ._lib import Array, ArrayStream, ArrayView, Schema, c_version  # noqa: 
F401
-from .lib import array, array_stream, schema, array_view  # noqa: F401
+# These values are only set if the distribution was created with 'git archive'
+refnames = "$Format:%D$"
+git_hash = "$Format:%h$"
diff --git a/python/src/nanoarrow/_version.py b/python/src/nanoarrow/_version.py
new file mode 100644
index 00000000..750dbb5b
--- /dev/null
+++ b/python/src/nanoarrow/_version.py
@@ -0,0 +1,50 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+# This file is part of 'miniver': https://github.com/jbweston/miniver
+
+import os
+
+# No public API
+__all__ = []
+
+package_root = os.path.dirname(os.path.realpath(__file__))
+package_name = os.path.basename(package_root)
+
+STATIC_VERSION_FILE = "_static_version.py"
+
+
+def get_version(version_file=STATIC_VERSION_FILE):
+    override = os.environ.get("SETUPTOOLS_SCM_PRETEND_VERSION")
+    if override is not None and override != "":
+        return override
+    version_info = get_static_version_info(version_file)
+    version = version_info["version"]
+    return version
+
+
+def get_static_version_info(version_file=STATIC_VERSION_FILE):
+    version_info = {}
+    with open(os.path.join(package_root, version_file), "rb") as f:
+        exec(f.read(), {}, version_info)
+    return version_info
+
+
+__version__ = get_version()
+
+if __name__ == "__main__":
+    print("Version: ", get_version())
diff --git a/python/tests/test_capsules.py b/python/tests/test_capsules.py
index f124dec6..aef037f9 100644
--- a/python/tests/test_capsules.py
+++ b/python/tests/test_capsules.py
@@ -14,11 +14,13 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-import pyarrow as pa
+
 import pytest
 
 import nanoarrow as na
 
+pa = pytest.importorskip("pyarrow")
+
 
 class SchemaWrapper:
     def __init__(self, schema):
diff --git a/python/tests/test_device.py b/python/tests/test_device.py
index 1426e360..6ba6773c 100644
--- a/python/tests/test_device.py
+++ b/python/tests/test_device.py
@@ -15,10 +15,12 @@
 # specific language governing permissions and limitations
 # under the License.
 
-import pyarrow as pa
+import pytest
 
 from nanoarrow import device
 
+pa = pytest.importorskip("pyarrow")
+
 
 def test_cpu_device():
     cpu = device.Device.cpu()
diff --git a/python/tests/test_nanoarrow.py b/python/tests/test_nanoarrow.py
index 816ff141..5d0d9840 100644
--- a/python/tests/test_nanoarrow.py
+++ b/python/tests/test_nanoarrow.py
@@ -15,19 +15,14 @@
 # specific language governing permissions and limitations
 # under the License.
 
-import re
 import sys
 
-import numpy as np
-import pyarrow as pa
 import pytest
 
 import nanoarrow as na
 
-
-def test_c_version():
-    re_version = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(-SNAPSHOT)?$")
-    assert re_version.match(na.c_version()) is not None
+np = pytest.importorskip("numpy")
+pa = pytest.importorskip("pyarrow")
 
 
 def test_schema_helper():
diff --git a/python/MANIFEST.in b/python/tests/test_version.py
similarity index 70%
copy from python/MANIFEST.in
copy to python/tests/test_version.py
index 9fc29372..2b080d64 100644
--- a/python/MANIFEST.in
+++ b/python/tests/test_version.py
@@ -15,7 +15,16 @@
 # specific language governing permissions and limitations
 # under the License.
 
-exclude bootstrap.py
-include src/nanoarrow/nanoarrow.c
-include src/nanoarrow/nanoarrow.h
-include src/nanoarrow/nanoarrow_c.pxd
+import re
+
+import nanoarrow as na
+
+
+def test_version():
+    re_py_version = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(dev[0-9+])?$")
+    assert re_py_version.match(na.__version__) is not None
+
+
+def test_c_version():
+    re_version = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(-SNAPSHOT)?$")
+    assert re_version.match(na.c_version()) is not None

Reply via email to