diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml new file mode 100644 index 000000000..a5b447032 --- /dev/null +++ b/.github/workflows/pypi-publish.yml @@ -0,0 +1,98 @@ +name: Publish wren-core-py to PyPI + +permissions: + contents: read + id-token: write # Required for trusted publishing (OIDC) + +on: + workflow_dispatch: + inputs: + target: + description: "Publish target" + required: true + default: "testpypi" + type: choice + options: + - testpypi + - pypi + +jobs: + build-wheels: + name: Build wheel — ${{ matrix.os }} (${{ matrix.target }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + # Linux x86_64 + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + # Linux aarch64 + - os: ubuntu-latest + target: aarch64-unknown-linux-gnu + # macOS x86_64 + - os: macos-15-intel + target: x86_64-apple-darwin + # macOS arm64 + - os: macos-15 + target: aarch64-apple-darwin + # Windows x86_64 + - os: windows-latest + target: x86_64-pc-windows-msvc + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Build wheel + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: --release --out dist + manylinux: auto + working-directory: wren-core-py + - name: Upload wheel + uses: actions/upload-artifact@v4 + with: + name: wheel-${{ matrix.target }} + path: wren-core-py/dist/*.whl + + build-sdist: + name: Build sdist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + working-directory: wren-core-py + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: sdist + path: wren-core-py/dist/*.tar.gz + + publish: + name: Publish to ${{ github.event.inputs.target || 'testpypi' }} + needs: [build-wheels, build-sdist] + runs-on: ubuntu-latest + environment: + name: ${{ github.event.inputs.target || 'testpypi' }} + url: ${{ github.event.inputs.target == 'pypi' && 'https://pypi.org/project/wren-core-py/' || 'https://test.pypi.org/project/wren-core-py/' }} + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - name: List artifacts + run: ls -lhR dist/ + + - name: Publish to ${{ github.event.inputs.target }} + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: ${{ github.event.inputs.target == 'testpypi' && 'https://test.pypi.org/legacy/' || 'https://upload.pypi.org/legacy/' }} + packages-dir: dist/ diff --git a/.gitignore b/.gitignore index bb1f50ef9..19039934b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ __pycache__/ venv/ **/.env* **/*.so +wren-core-py/dict/ \ No newline at end of file diff --git a/wren-core-py/Cargo.toml b/wren-core-py/Cargo.toml index 43785d191..70b55f014 100644 --- a/wren-core-py/Cargo.toml +++ b/wren-core-py/Cargo.toml @@ -2,6 +2,9 @@ name = "wren-core-py" version = "0.1.0" edition = "2021" +description = "Python bindings for Wren Engine semantic layer (wren-core)" +license = "Apache-2.0" +repository = "https://github.com/Canner/wren-engine" include = ["/src", "pyproject.toml", "Cargo.toml", "Cargo.lock"] [lib] diff --git a/wren-core-py/README.md b/wren-core-py/README.md index 57ef83dde..fd31b7cf4 100644 --- a/wren-core-py/README.md +++ b/wren-core-py/README.md @@ -1,6 +1,32 @@ -# Wren Core Python binding +# Wren Core Python Binding -This is a python binding for [wren-core](../wren-core). It uses [PyO3](https://github.com/PyO3/pyo3) to build the required wheel for [ibis-server](../ibis-server/). +Python bindings for [wren-core](../wren-core), the Rust semantic engine behind [Wren Engine](https://github.com/Canner/wren-engine). Built with [PyO3](https://github.com/PyO3/pyo3) and [Maturin](https://github.com/PyO3/maturin). + +Wren Engine translates SQL queries through a semantic layer (MDL - Modeling Definition Language) and executes them against 22+ data sources (PostgreSQL, BigQuery, Snowflake, etc.). + +## Installation + +```bash +pip install wren-core-py +``` + +Requires Python >= 3.11. + +## Quick Start + +```python +from wren_core import SessionContext, to_manifest + +# Load an MDL manifest from a base64-encoded JSON string +base64_mdl_json = "" +manifest = to_manifest(base64_mdl_json) + +# Create a session context for query planning +ctx = SessionContext(manifest, remote_functions=[]) + +# Transform a SQL query through the semantic layer +planned_sql = ctx.transform_sql("SELECT * FROM my_model") +``` ## Developer Guide @@ -11,14 +37,31 @@ This is a python binding for [wren-core](../wren-core). It uses [PyO3](https://g - Install [poetry](https://github.com/python-poetry/poetry) - Install [casey/just](https://github.com/casey/just) -### Test and build -After install `casey/just`, you can use the following command to build or test: -- Execute `just install` to create Python venv and install dependencies. -- **Important**: Before testing Python, you need to build the Rust package by running `just develop`. -- Use `just test-rs` to test Rust only, and `just test-py` to test Python only. -- Use `just test` to test Rust and Python. -- Execute `just build` to build the Python package. You can find the wheel in the `target/wheels/` directory. +### Test and Build + +After installing `casey/just`, you can use the following commands: + +- `just install` — Create Python venv and install dependencies. +- `just develop` — Build the Rust package for local development (**required before running Python tests**). +- `just test-rs` — Run Rust tests only. +- `just test-py` — Run Python tests only. +- `just test` — Run both Rust and Python tests. +- `just build` — Build the Python wheel. Output goes to `target/wheels/`. ### Coding Style -Format via `just format` +Format via `just format`. + +### Publishing + +See `scripts/publish.sh` for local publishing to PyPI/TestPyPI: + +```bash +./scripts/publish.sh --build # Build wheel only +./scripts/publish.sh --test # Build + publish to TestPyPI +./scripts/publish.sh # Build + publish to PyPI +``` + +## License + +Apache-2.0 diff --git a/wren-core-py/poetry.lock b/wren-core-py/poetry.lock index 16310bf4c..9976d6104 100644 --- a/wren-core-py/poetry.lock +++ b/wren-core-py/poetry.lock @@ -150,4 +150,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">=3.11" -content-hash = "038d17d7d6e90435edd346fa7552b6dcc7193d0f4b1a257e8b0da8c2720fe2bd" +content-hash = "3560f376f36040614abcd7694537b1de69e8fda39be75a63b213c2c2c857541f" diff --git a/wren-core-py/pyproject.toml b/wren-core-py/pyproject.toml index f5d27b9e5..81be99a8b 100644 --- a/wren-core-py/pyproject.toml +++ b/wren-core-py/pyproject.toml @@ -1,13 +1,34 @@ [project] name = "wren-core-py" -classifiers = ["Programming Language :: Python :: 3.11"] version = "0.1.0" +description = "Python bindings for Wren Engine semantic layer (wren-core)" +readme = "README.md" +requires-python = ">=3.11" +license = { text = "Apache-2.0" } +authors = [{ name = "Wren AI", email = "contact@getwren.ai" }] +keywords = ["sql", "semantic-layer", "data-modeling", "analytics", "wren", "wrenai", "datafusion"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Rust", + "Topic :: Database", + "Topic :: Software Development :: Libraries :: Python Modules", +] + +[project.urls] +Homepage = "https://www.getwren.ai/" +Repository = "https://github.com/Canner/wren-engine" +Issues = "https://github.com/Canner/wren-engine/issues" [tool.poetry] name = "wren-core-py" version = "0.1.0" -description = "" -authors = ["Canner "] +description = "Python bindings for Wren Engine semantic layer (wren-core)" +authors = ["Wren AI "] [tool.poetry.dependencies] python = ">=3.11" diff --git a/wren-core-py/scripts/publish.sh b/wren-core-py/scripts/publish.sh new file mode 100755 index 000000000..962c71c31 --- /dev/null +++ b/wren-core-py/scripts/publish.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# +# Build and publish wren-core-py to PyPI or TestPyPI. +# +# Usage: +# ./scripts/publish.sh # build + publish to PyPI +# ./scripts/publish.sh --test # build + publish to TestPyPI +# ./scripts/publish.sh --build # build only (no publish) +# +# Prerequisites: +# - Rust toolchain (rustup, cargo) +# - maturin (pip install maturin) +# - twine (pip install twine) +# +# Environment variables (optional): +# MATURIN_ARGS — extra args passed to maturin build (e.g. "--target x86_64-unknown-linux-gnu") +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +DIST_DIR="$PROJECT_DIR/dist" + +MODE="publish" # publish | test | build +REPOSITORY="pypi" + +while [[ $# -gt 0 ]]; do + case "$1" in + --test) + MODE="test" + REPOSITORY="testpypi" + shift + ;; + --build) + MODE="build" + shift + ;; + -h|--help) + echo "Usage: $0 [--test | --build | -h]" + echo "" + echo " (no flag) Build and publish to PyPI" + echo " --test Build and publish to TestPyPI" + echo " --build Build only, no publish" + echo " -h, --help Show this help" + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + exit 1 + ;; + esac +done + +cd "$PROJECT_DIR" + +# --- Check prerequisites --- +for cmd in cargo maturin; do + if ! command -v "$cmd" &>/dev/null; then + echo "Error: '$cmd' is not installed." >&2 + exit 1 + fi +done + +if [[ "$MODE" != "build" ]]; then + if ! command -v twine &>/dev/null; then + echo "Error: 'twine' is not installed. Run: pip install twine" >&2 + exit 1 + fi +fi + +# --- Read version from Cargo.toml --- +VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/') +echo "==> Building wren-core-py v${VERSION}" + +# --- Clean previous dist --- +rm -rf "$DIST_DIR" +mkdir -p "$DIST_DIR" + +# --- Build wheel --- +echo "==> Running maturin build --release" +maturin build --release --out "$DIST_DIR" ${MATURIN_ARGS:-} + +# --- Build sdist --- +echo "==> Running maturin sdist" +maturin sdist --out "$DIST_DIR" + +# --- List artifacts --- +echo "" +echo "==> Built artifacts:" +ls -lh "$DIST_DIR" + +# --- Validate --- +echo "" +echo "==> Validating with twine check" +if command -v twine &>/dev/null; then + twine check "$DIST_DIR"/* +elif [[ "$MODE" == "build" ]]; then + echo "Warning: twine not installed; skipping validation in build-only mode" +else + echo "Error: 'twine' is not installed. Run: pip install twine" >&2 + exit 1 +fi + +if [[ "$MODE" == "build" ]]; then + echo "" + echo "==> Build complete. Artifacts in: $DIST_DIR" + exit 0 +fi + +# --- Publish --- +echo "" +if [[ "$REPOSITORY" == "testpypi" ]]; then + echo "==> Publishing to TestPyPI" + echo " After upload, install with:" + echo " pip install --index-url https://test.pypi.org/simple/ wren-core-py" +else + echo "==> Publishing to PyPI" +fi +echo "" + +twine upload --repository "$REPOSITORY" "$DIST_DIR"/* + +echo "" +echo "==> Done! Published wren-core-py v${VERSION} to ${REPOSITORY}"