Skip to content

Commit 85f19e2

Browse files
Merge pull request #9 from sqliteai/python-build
feat(python): workflow to build and upload the pypi package
2 parents cbd3262 + 3825d94 commit 85f19e2

File tree

12 files changed

+301
-13
lines changed

12 files changed

+301
-13
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:

.github/workflows/python-package.yml

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,32 @@ on:
1313
jobs:
1414
build-and-publish:
1515
runs-on: ${{ matrix.os }}
16+
permissions:
17+
id-token: write # mandatory for Pypi trusted publishing
1618
strategy:
1719
matrix:
1820
include:
1921
- os: ubuntu-latest
2022
platform: linux
2123
python-version: "3.10"
2224
arch: x86_64
23-
plat_name: linux_x86_64
25+
plat_name: manylinux2014_x86_64
2426
- os: ubuntu-latest
2527
platform: linux
2628
python-version: "3.10"
2729
arch: arm64
28-
plat_name: linux_aarch64
29-
- os: windows-latest
30+
plat_name: manylinux2014_aarch64
31+
- os: ubuntu-latest
3032
platform: windows
3133
python-version: "3.10"
3234
arch: x86_64
3335
plat_name: win_amd64
34-
- os: macos-latest
36+
- os: ubuntu-latest
3537
platform: macos
3638
python-version: "3.10"
3739
arch: x86_64
3840
plat_name: macosx_10_9_x86_64
39-
- os: macos-latest
41+
- os: ubuntu-latest
4042
platform: macos
4143
python-version: "3.10"
4244
arch: arm64
@@ -56,9 +58,9 @@ jobs:
5658

5759
- name: Install build dependencies
5860
run: |
59-
cd python-package
61+
cd packages/python
6062
python3 -m pip install --upgrade pip
61-
pip install .[dev]
63+
python3 -m pip install -r requirements-dev.txt
6264
6365
- name: Get version
6466
id: get_version
@@ -73,17 +75,22 @@ jobs:
7375
7476
- name: Download artifacts for current platform
7577
run: |
78+
cd packages/python
7679
python3 download_artifacts.py "${{ matrix.plat_name }}" "${{ steps.get_version.outputs.version }}"
7780
7881
- name: Build wheel
7982
env:
8083
PACKAGE_VERSION: ${{ steps.get_version.outputs.version }}
8184
run: |
82-
cd python-package
83-
python setup.py clean --all bdist_wheel --plat-name "${{ matrix.plat_name }}"
85+
cd packages/python
86+
python setup.py bdist_wheel --plat-name "${{ matrix.plat_name }}"
8487
8588
- name: Publish to PyPI
86-
if: github.event_name == 'release'
8789
uses: pypa/gh-action-pypi-publish@release/v1
8890
with:
89-
packages-dir: python-package/dist
91+
packages-dir: packages/python/dist
92+
verbose: true
93+
# Avoid workflow to fail if the version has already been published
94+
skip-existing: true
95+
# Upload to Test Pypi for testing
96+
# repository-url: https://test.pypi.org/legacy/

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@ Download the appropriate pre-built binary for your platform from the official [R
4040
SELECT load_extension('./ai');
4141
```
4242

43+
### Python Package
44+
45+
Python developers can quickly get started using the ready-to-use `sqlite-ai` package available on PyPI:
46+
47+
```bash
48+
pip install sqlite-ai
49+
```
50+
51+
For usage details and examples, see the [Python package documentation](./packages/python/README.md).
52+
53+
4354
## Getting Started
4455

4556
Here's a quick example to get started with SQLite Sync:

packages/python/.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

packages/python/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 *

packages/python/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
## SQLite AI Python package
2+
3+
This package provides the sqlite-ai extension prebuilt binaries for multiple platforms and architectures.
4+
5+
### SQLite-AI
6+
7+
SQLite-AI is an extension for SQLite that brings artificial intelligence capabilities directly into the database. It enables developers to run, fine-tune, and serve AI models from within SQLite using simple SQL queries — ideal for on-device and edge applications where low-latency and offline inference are critical. The extension is actively developed by [SQLite AI](https://sqlite.ai), some API and features are still evolving.
8+
9+
More details on the official repository [sqliteai/sqlite-ai](https://github.com/sqliteai/sqlite-ai).
10+
11+
### Documentation
12+
13+
For detailed information on all available functions, their parameters, and examples, refer to the [comprehensive API Reference](https://github.com/sqliteai/sqlite-ai/blob/main/API.md).
14+
15+
### Supported Platforms and Architectures
16+
17+
| Platform | Arch | Subpackage name | Binary name |
18+
| ------------- | ------------ | ----------------------- | ----------- |
19+
| Linux (CPU) | x86_64/arm64 | sqliteai.binaries.cpu | ai.so |
20+
| Linux (GPU) | x86_64/arm64 | sqliteai.binaries.gpu | ai.so |
21+
| Windows (CPU) | x86_64 | sqliteai.binaries.cpu | ai.dll |
22+
| Windows (GPU) | x86_64 | sqliteai.binaries.gpu | ai.dll |
23+
| macOS (CPU) | x86_64/arm64 | sqliteai.binaries.cpu | ai.dylib |
24+
25+
## Usage
26+
27+
> **Note:** Some SQLite installations on certain operating systems may have extension loading disabled by default.
28+
If you encounter issues loading the extension, refer to the [sqlite-extensions-guide](https://github.com/sqliteai/sqlite-extensions-guide/) for platform-specific instructions on enabling and using SQLite extensions.
29+
30+
```python
31+
import importlib.resources
32+
import sqlite3
33+
34+
# Connect to your SQLite database
35+
conn = sqlite3.connect("example.db")
36+
37+
# Load the sqlite-ai extension
38+
# pip will install the correct binary package for your platform and architecture
39+
# Choose between CPU or GPU variant
40+
ext_path = importlib.resources.files("sqliteai.binaries.cpu") / "ai"
41+
42+
conn.enable_load_extension(True)
43+
conn.load_extension(str(ext_path))
44+
conn.enable_load_extension(False)
45+
46+
47+
# Now you can use sqlite-ai features in your SQL queries
48+
print(conn.execute("SELECT ai_version();").fetchone())
49+
```
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+
"manylinux2014_x86_64": ["ai-linux-cpu-x86_64", "ai-linux-gpu-x86_64"],
18+
"manylinux2014_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+
"manylinux2014_x86_64": "ai.so",
29+
"manylinux2014_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) == 3:
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()

packages/python/pyproject.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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 prebuilt binaries for SQLite AI extension for all supported platforms and architectures."
9+
authors = [
10+
{ name = "SQLite AI Team" }
11+
]
12+
readme = "README.md"
13+
requires-python = ">=3"
14+
classifiers = [
15+
"Programming Language :: Python :: 3",
16+
"Operating System :: POSIX :: Linux",
17+
"Operating System :: Microsoft :: Windows",
18+
"Operating System :: MacOS :: MacOS X"
19+
]
20+
21+
[project.urls]
22+
Homepage = "https://sqlite.ai"
23+
Documentation = "https://github.com/sqliteai/sqlite-ai/blob/main/API.md"
24+
Repository = "https://github.com/sqliteai/sqlite-ai"
25+
Issues = "https://github.com/sqliteai/sqlite-ai/issues"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
requests
2+
toml
3+
wheel

packages/python/setup.py

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

0 commit comments

Comments
 (0)