Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/polars-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Polars CI

on:
pull_request:
types: [review_requested, ready_for_review]
paths:
- ".github/workflows/polars-*.yml"
- "Cargo.toml"
- "Makefile"
- "core/**"
- "polars/**"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
validate:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- uses: astral-sh/setup-uv@v5
- uses: dtolnay/rust-toolchain@stable

- name: Sync polars dependencies
working-directory: polars
run: uv sync --group dev --no-install-project

- name: Test techr-core
run: cargo test -p techr-core

- name: Build local extension for tests
working-directory: polars
run: uv run maturin develop --uv

- name: Test polars package
working-directory: polars
run: uv run pytest

- name: Build wheel and sdist
working-directory: polars
run: uv run maturin build --release --sdist --out dist

- name: Check artifact contents
run: python polars/scripts/check_artifacts.py polars/dist

- name: Smoke test built wheel
run: |
wheel="$(python - <<'PY'
from pathlib import Path

wheels = sorted(Path('polars/dist').glob('*.whl'))
if len(wheels) != 1:
raise SystemExit(f"expected exactly one wheel, found {len(wheels)}")
print(wheels[0].resolve().as_posix())
PY
)"
uv run --isolated --python 3.10 --with "$wheel" python polars/scripts/smoke_import.py
219 changes: 219 additions & 0 deletions .github/workflows/polars-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
name: Polars Release

on:
push:
tags:
- "polars-v*"
workflow_dispatch:

permissions:
contents: read

jobs:
build-linux:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- target: x86_64
artifact: wheels-linux-x86_64
- target: aarch64
artifact: wheels-linux-aarch64
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- uses: astral-sh/setup-uv@v5
- name: Build wheel
uses: PyO3/maturin-action@v1
with:
working-directory: polars
target: ${{ matrix.target }}
manylinux: "2014"
sccache: "true"
args: --release --out dist -i python3.10

- name: Check artifact contents
run: python polars/scripts/check_artifacts.py polars/dist

- name: Smoke test x86_64 wheel
if: matrix.target == 'x86_64'
run: |
wheel="$(python - <<'PY'
from pathlib import Path

wheels = sorted(Path('polars/dist').glob('*.whl'))
if len(wheels) != 1:
raise SystemExit(f"expected exactly one wheel, found {len(wheels)}")
print(wheels[0].resolve().as_posix())
PY
)"
uv run --isolated --python 3.10 --with "$wheel" python polars/scripts/smoke_import.py

- name: Smoke test aarch64 wheel
if: matrix.target == 'aarch64'
uses: uraimo/run-on-arch-action@v2
with:
arch: aarch64
distro: ubuntu22.04
githubToken: ${{ github.token }}
install: |
apt-get update
apt-get install -y --no-install-recommends python3 python3-pip
pip3 install -U pip
run: |
set -e
pip3 install polars/dist/*.whl --force-reinstall
python3 polars/scripts/smoke_import.py

- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: polars/dist/*

build-macos:
runs-on: macos-14
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- uses: astral-sh/setup-uv@v5
- uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-apple-darwin
- name: Build universal2 wheel
uses: PyO3/maturin-action@v1
with:
working-directory: polars
sccache: "true"
args: --release --out dist --universal2

- name: Check artifact contents
run: python polars/scripts/check_artifacts.py polars/dist

- name: Smoke test built wheel
run: |
wheel="$(python - <<'PY'
from pathlib import Path

wheels = sorted(Path('polars/dist').glob('*.whl'))
if len(wheels) != 1:
raise SystemExit(f"expected exactly one wheel, found {len(wheels)}")
print(wheels[0].resolve().as_posix())
PY
)"
uv run --isolated --python 3.10 --with "$wheel" python polars/scripts/smoke_import.py

- uses: actions/upload-artifact@v4
with:
name: wheels-macos-universal2
path: polars/dist/*

build-windows:
runs-on: windows-latest
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
architecture: "x64"
- uses: astral-sh/setup-uv@v5
- name: Build wheel
uses: PyO3/maturin-action@v1
with:
working-directory: polars
target: x86_64-pc-windows-msvc
sccache: "true"
args: --release --out dist

- name: Check artifact contents
run: python polars/scripts/check_artifacts.py polars/dist

- name: Smoke test built wheel
run: |
wheel="$(python - <<'PY'
from pathlib import Path

wheels = sorted(Path('polars/dist').glob('*.whl'))
if len(wheels) != 1:
raise SystemExit(f"expected exactly one wheel, found {len(wheels)}")
print(wheels[0].resolve().as_posix())
PY
)"
uv run --isolated --python 3.10 --with "$wheel" python polars/scripts/smoke_import.py

- uses: actions/upload-artifact@v4
with:
name: wheels-windows-x64
path: polars/dist/*

build-sdist:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Build sdist
uses: PyO3/maturin-action@v1
with:
working-directory: polars
command: sdist
args: --out dist

- name: Check artifact contents
run: python polars/scripts/check_artifacts.py polars/dist

- uses: actions/upload-artifact@v4
with:
name: wheels-sdist
path: polars/dist/*

publish-testpypi:
if: github.event_name == 'workflow_dispatch'
needs: [build-linux, build-macos, build-windows, build-sdist]
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
packages-dir: dist
skip-existing: true

publish-pypi:
if: github.event_name == 'push'
needs: [build-linux, build-macos, build-windows, build-sdist]
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist
8 changes: 5 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ export CARGO_TERM_COLOR=$(shell (test -t 0 && echo "always") || echo "auto")

.PHONY: build-dev-polars
build-dev-polars:
@rm -f polars/polars_techr/*.so
@rm -f polars/polars_techr/*.so polars/polars_techr/*.pyd
cd polars && uv run maturin develop --uv


.PHONY: build-prod-polars
build-prod-polars:
@rm -f polars/polars_techr/*.so
cd polars && uv run maturin build --release
@rm -f polars/polars_techr/*.so polars/polars_techr/*.pyd
@rm -rf polars/dist
cd polars && uv run maturin build --release --sdist --out dist


.PHONY: test-core
Expand All @@ -22,6 +23,7 @@ test-core:

.PHONY: test-polars
test-polars:
cd polars && uv run maturin develop --uv
cd polars && uv run pytest

.PHONY: test
Expand Down
5 changes: 5 additions & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
name = "techr-core"
version = "0.1.0"
edition = "2021"
description = "Rust core implementation for techr technical indicators"
license = "MIT"
readme = "../README.md"
homepage = "https://github.com/alphaprime-dev/techr"
repository = "https://github.com/alphaprime-dev/techr"

[lib]
name = "techr"
Expand Down
10 changes: 9 additions & 1 deletion polars/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@
name = "polars_techr"
version = "0.1.0"
edition = "2021"
description = "Polars expression plugins for techr indicators"
license = "MIT"
readme = "README.md"
homepage = "https://github.com/alphaprime-dev/techr"
repository = "https://github.com/alphaprime-dev/techr"

[lib]
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.22.2", features = ["extension-module", "abi3-py38"] }
pyo3 = { version = "0.22.2", features = ["extension-module", "abi3-py310"] }
pyo3-polars = { version = "0.17.0", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
polars = { version = "0.43.1", default-features = false }
Expand Down
34 changes: 33 additions & 1 deletion polars/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# polars-techr

Python wrapper for `techr` indicators on top of Polars plugins.
`polars-techr` exposes `techr` indicators as Polars expression plugins.

## Installation

```bash
uv add polars-techr
```

## Supported indicators

Expand Down Expand Up @@ -46,3 +52,29 @@ result = df.select(
- `ichimoku_leading_span_b` uses `period` for the rolling window and `base_line_period` for the forward displacement. The Python wrapper defaults `base_line_period` to `26`.
- `ichimoku_lagging_span` uses `base_line_period` for its backward displacement.
- Polars plugins keep the output row-aligned with the input, so `ichimoku_leading_span_a` and `ichimoku_leading_span_b` truncate the forward-projected tail from the core result.

## Development

```bash
cd polars
uv sync --group dev
uv run maturin develop --uv
uv run pytest
```

Build distributable artifacts locally with:

```bash
cd polars
uv run maturin build --release --sdist --out dist
uv run python scripts/check_artifacts.py dist
```

## Release

1. Update the version in `Cargo.toml`.
2. Optionally build release artifacts locally for a final preflight check.
3. Optionally run the `Polars Release` workflow manually to publish the current ref to TestPyPI.
4. Create and push a `polars-vX.Y.Z` tag to publish to PyPI.

Before the first release, configure Trusted Publishers for both PyPI and TestPyPI on `alphaprime-dev/techr`.
Loading
Loading