Skip to content

Commit 4031135

Browse files
author
Daniele Briggi
committed
feat(python): workflow to build and upload the pypi package
1 parent adae11e commit 4031135

File tree

10 files changed

+350
-1
lines changed

10 files changed

+350
-1
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: build, test and release sqlite-ai
1+
name: Build, Test and Release SQLite AI
22
on:
33
push:
44
workflow_dispatch:
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
name: Build and Publish Python Package
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: "Version to use for the Python package (e.g. 0.5.9)"
8+
required: true
9+
type: string
10+
release:
11+
types: [published]
12+
13+
jobs:
14+
build-and-publish:
15+
runs-on: ${{ matrix.os }}
16+
strategy:
17+
matrix:
18+
include:
19+
- os: ubuntu-latest
20+
platform: linux
21+
python-version: "3.10"
22+
arch: x86_64
23+
plat_name: linux_x86_64
24+
- os: ubuntu-latest
25+
platform: linux
26+
python-version: "3.10"
27+
arch: arm64
28+
plat_name: linux_aarch64
29+
- os: windows-latest
30+
platform: windows
31+
python-version: "3.10"
32+
arch: x86_64
33+
plat_name: win_amd64
34+
- os: macos-latest
35+
platform: macos
36+
python-version: "3.10"
37+
arch: x86_64
38+
plat_name: macosx_10_9_x86_64
39+
- os: macos-latest
40+
platform: macos
41+
python-version: "3.10"
42+
arch: arm64
43+
plat_name: macosx_11_0_arm64
44+
defaults:
45+
run:
46+
shell: bash
47+
steps:
48+
- uses: actions/checkout@v4
49+
with:
50+
submodules: false
51+
52+
- name: Set up Python
53+
uses: actions/setup-python@v5
54+
with:
55+
python-version: ${{ matrix.python-version }}
56+
57+
- name: Install build dependencies
58+
run: |
59+
cd python-package
60+
python3 -m pip install --upgrade pip
61+
python3 -m pip install -r requirements-dev.txt
62+
63+
- name: Get version
64+
id: get_version
65+
run: |
66+
if [[ "${{ github.event_name }}" == "release" ]]; then
67+
VERSION="${{ github.event.release.tag_name }}"
68+
else
69+
VERSION="${{ github.event.inputs.version }}"
70+
fi
71+
VERSION=${VERSION#v}
72+
echo "version=$VERSION" >> $GITHUB_OUTPUT
73+
74+
- name: Download artifacts for current platform
75+
run: |
76+
cd python-package
77+
python3 download_artifacts.py "${{ matrix.plat_name }}" "${{ steps.get_version.outputs.version }}"
78+
79+
- name: Build wheel
80+
env:
81+
PACKAGE_VERSION: ${{ steps.get_version.outputs.version }}
82+
run: |
83+
cd python-package
84+
python setup.py clean --all bdist_wheel --plat-name "${{ matrix.plat_name }}"
85+
86+
- name: Publish to PyPI
87+
if: github.event_name == 'release'
88+
uses: pypa/gh-action-pypi-publish@release/v1
89+
with:
90+
packages-dir: python-package/dist

python-package/.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Build directories
2+
build/
3+
dist/
4+
*.egg-info/
5+
6+
# Python cache
7+
__pycache__/
8+
*.py[cod]
9+
*$py.class
10+
*.so
11+
12+
# Virtual environments
13+
venv/
14+
env/
15+
ENV/
16+
17+
# Package files
18+
*.whl
19+
*.tar.gz
20+
21+
# Temporary files
22+
*.tmp
23+
*.log

python-package/MANIFEST.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
include README.md
2+
include LICENSE
3+
recursive-include src/sqliteai/binaries *

python-package/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
## SQLite AI Python package
2+
3+
This package provides the sqlite-ai extension prebuilt binaries for multiple platforms and architectures.
4+
5+
## Usage
6+
7+
```python
8+
import importlib.resources
9+
import sqlite3
10+
11+
# Connect to your SQLite database
12+
conn = sqlite3.connect("example.db")
13+
14+
# Load the sqlite-ai extension
15+
# Pip will download the package for your platform/architecture
16+
ext_path = importlib.resources.files("sqliteai.binaries.cpu") / "ai"
17+
18+
conn.enable_load_extension(True)
19+
conn.load_extension(str(ext_path))
20+
conn.enable_load_extension(False)
21+
22+
23+
# Now you can use sqlite-ai features in your SQL queries
24+
```
25+
26+
27+
### Extension paths
28+
29+
The sqlite-ai extension binary is included in a subpackage based on your platform and architecture.
30+
Use the following subpackage names to locate the extension:
31+
32+
| Platform | Arch | Subpackage name | Binary name |
33+
| ------------- | ------------ | ----------------------- | ----------- |
34+
| Linux (CPU) | x86_64/arm64 | sqliteai.binaries.cpu | ai.so |
35+
| Linux (GPU) | x86_64/arm64 | sqliteai.binaries.gpu | ai.so |
36+
| Windows (CPU) | x86_64 | sqliteai.binaries.cpu | ai.dll |
37+
| Windows (GPU) | x86_64 | sqliteai.binaries.gpu | ai.dll |
38+
| macOS (CPU) | x86_64/arm64 | sqliteai.binaries.cpu | ai.dylib |
39+
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import sys
2+
import zipfile
3+
import requests
4+
from pathlib import Path
5+
import shutil
6+
7+
8+
# == USAGE ==
9+
# python3 download_artifacts.py PLATFORM VERSION
10+
# eg: python3 download_artifacts.py linux_x86_64 "0.5.9"
11+
12+
REPO = "sqliteai/sqlite-ai"
13+
RELEASE_URL = f"https://github.com/{REPO}/releases/download"
14+
15+
# Map Python plat_name to artifact names
16+
ARTIFACTS = {
17+
"linux_x86_64": ["ai-linux-cpu-x86_64", "ai-linux-gpu-x86_64"],
18+
"linux_aarch64": [
19+
"ai-linux-cpu-arm64",
20+
"ai-linux-gpu-arm64",
21+
],
22+
"win_amd64": ["ai-windows-cpu-x86_64", "ai-windows-gpu-x86_64"],
23+
"macosx_10_9_x86_64": ["ai-macos"],
24+
"macosx_11_0_arm64": ["ai-macos"],
25+
}
26+
27+
BINARY_NAME = {
28+
"linux_x86_64": "ai.so",
29+
"linux_aarch64": "ai.so",
30+
"win_amd64": "ai.dll",
31+
"macosx_10_9_x86_64": "ai.dylib",
32+
"macosx_11_0_arm64": "ai.dylib",
33+
}
34+
35+
BINARIES_DIR = Path(__file__).parent / "src/sqliteai/binaries"
36+
37+
38+
def download_and_extract(artifact_name, bin_name, version):
39+
artifact = f"{artifact_name}-{version}.zip"
40+
url = f"{RELEASE_URL}/{version}/{artifact}"
41+
print(f"Downloading {url}")
42+
43+
r = requests.get(url)
44+
if r.status_code != 200:
45+
print(f"Failed to download {artifact}: {r.status_code}")
46+
sys.exit(1)
47+
48+
zip_path = BINARIES_DIR / artifact
49+
with open(zip_path, "wb") as f:
50+
f.write(r.content)
51+
52+
subdir = "gpu" if "gpu" in artifact_name else "cpu"
53+
out_dir = BINARIES_DIR / subdir
54+
out_dir.mkdir(parents=True, exist_ok=True)
55+
56+
with zipfile.ZipFile(zip_path, "r") as zip_ref:
57+
for member in zip_ref.namelist():
58+
if member.endswith(bin_name):
59+
zip_ref.extract(member, out_dir)
60+
61+
# Move to expected name/location
62+
src = out_dir / member
63+
dst = out_dir / bin_name
64+
src.rename(dst)
65+
66+
print(f"Extracted {dst}")
67+
68+
zip_path.unlink()
69+
70+
71+
def main():
72+
version = None
73+
platform = None
74+
if len(sys.argv) > 2:
75+
platform = sys.argv[1].lower()
76+
version = sys.argv[2]
77+
78+
if not version or not platform:
79+
print(
80+
'Error: Version is not specified.\nUsage: \n python3 download_artifacts.py linux_x86_64 "0.5.9"'
81+
)
82+
sys.exit(1)
83+
84+
print(BINARIES_DIR)
85+
if BINARIES_DIR.exists():
86+
shutil.rmtree(BINARIES_DIR)
87+
BINARIES_DIR.mkdir(parents=True, exist_ok=True)
88+
89+
platform_artifacts = ARTIFACTS.get(platform, [])
90+
if not platform_artifacts:
91+
print(f"Error: Unknown platform '{platform}'")
92+
sys.exit(1)
93+
94+
for artifact_name in platform_artifacts:
95+
download_and_extract(artifact_name, BINARY_NAME[platform], version)
96+
97+
98+
if __name__ == "__main__":
99+
main()

python-package/pyproject.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[build-system]
2+
requires = ["setuptools>=61.0", "wheel", "toml"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "sqlite-ai"
7+
dynamic = ["version"]
8+
description = "Python bindings and prebuilt binaries for SQLite AI extension."
9+
authors = [
10+
{ name = "SQLite AI Team" }
11+
]
12+
readme = "README.md"
13+
requires-python = ">=3"
14+
classifiers = []
15+
16+
[project.urls]
17+
Homepage = "https://sqlite.ai"
18+
Documentation = "https://github.com/sqliteai/sqlite-ai/blob/main/API.md"
19+
Repository = "https://github.com/sqliteai/sqlite-ai"
20+
Issues = "https://github.com/sqliteai/sqlite-ai/issues"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
requests
2+
toml

python-package/setup.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import setuptools
2+
import toml
3+
import os
4+
import sys
5+
6+
usage = """
7+
Usage: python setup.py bdist_wheel --plat-name <platform>
8+
The PACKAGE_VERSION environment variable must be set to the desired version.
9+
10+
Example:
11+
PACKAGE_VERSION=0.5.9 python setup.py bdist_wheel --plat-name linux_x86_64
12+
"""
13+
14+
# Read pyproject.toml for metadata
15+
with open("pyproject.toml", "r") as f:
16+
pyproject = toml.load(f)
17+
18+
project = pyproject["project"]
19+
20+
# Get version from environment or default
21+
version = os.environ.get("PACKAGE_VERSION", "")
22+
if not version:
23+
print("PACKAGE_VERSION environment variable is not set.")
24+
print(usage)
25+
exit(1)
26+
27+
# Get Python platform name from --plat-name argument
28+
plat_name = None
29+
for i, arg in enumerate(sys.argv):
30+
if arg == "--plat-name" and i + 1 < len(sys.argv):
31+
plat_name = sys.argv[i + 1]
32+
break
33+
34+
if not plat_name:
35+
print("Error: --plat-name argument is required")
36+
print(usage)
37+
sys.exit(1)
38+
39+
# Map plat_name to classifier
40+
classifier_map = {
41+
"linux_x86_64": "Operating System :: POSIX :: Linux",
42+
"linux_aarch64": "Operating System :: POSIX :: Linux",
43+
"win_amd64": "Operating System :: Microsoft :: Windows",
44+
"macosx_10_9_x86_64": "Operating System :: MacOS",
45+
"macosx_11_0_arm64": "Operating System :: MacOS",
46+
}
47+
48+
classifier = classifier_map.get(plat_name)
49+
if not classifier:
50+
print(f"Unknown plat_name: {plat_name}")
51+
sys.exit(1)
52+
53+
with open("README.md", "r", encoding="utf-8") as f:
54+
long_description = f.read()
55+
56+
setuptools.setup(
57+
name=project["name"],
58+
version=version,
59+
description=project.get("description", ""),
60+
author=project["authors"][0]["name"] if project.get("authors") else "",
61+
long_description=long_description,
62+
long_description_content_type="text/markdown",
63+
url=project["urls"]["Homepage"],
64+
packages=setuptools.find_packages(where="src"),
65+
package_dir={"": "src"},
66+
include_package_data=True,
67+
python_requires=project.get("requires-python", ">=3"),
68+
classifiers=[
69+
"Programming Language :: Python :: 3",
70+
"License :: OSI Approved :: MIT License",
71+
classifier
72+
],
73+
)

python-package/src/sqliteai/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)