├── {{ cookiecutter.project_slug }} ├── README.md ├── {{ cookiecutter.project_slug }} │ ├── _internal.pyi │ ├── typing.py │ └── __init__.py ├── requirements.txt ├── rust-toolchain.toml ├── .gitignore ├── rustfmt.toml ├── run.py ├── src │ ├── lib.rs │ └── expressions.rs ├── Cargo.toml ├── tests │ └── test_pig_latinnify.py ├── pyproject.toml ├── Makefile └── .github │ └── workflows │ └── publish_to_pypi.yml ├── .gitignore ├── cookiecutter.json ├── test_it.sh ├── LICENSE ├── .github └── workflows │ └── test.yml ├── README.md └── CODE_OF_CONDUCT.md /{{ cookiecutter.project_slug }}/README.md: -------------------------------------------------------------------------------- 1 | # {{ cookiecutter.plugin_name }} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.zip 2 | .venv 3 | venv 4 | *.pyc 5 | *.so 6 | *.pyd 7 | *.dll 8 | 9 | -------------------------------------------------------------------------------- /{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/_internal.pyi: -------------------------------------------------------------------------------- 1 | __version__: str 2 | -------------------------------------------------------------------------------- /{{ cookiecutter.project_slug }}/requirements.txt: -------------------------------------------------------------------------------- 1 | polars 2 | maturin 3 | ruff 4 | pytest 5 | mypy 6 | -------------------------------------------------------------------------------- /{{ cookiecutter.project_slug }}/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2025-10-24" 3 | 4 | -------------------------------------------------------------------------------- /{{ cookiecutter.project_slug }}/.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.pyc 3 | target 4 | venv 5 | .venv 6 | *.so 7 | *.dll 8 | *.pyd 9 | 10 | -------------------------------------------------------------------------------- /{{ cookiecutter.project_slug }}/rustfmt.toml: -------------------------------------------------------------------------------- 1 | group_imports = "StdExternalCrate" 2 | imports_granularity = "Module" 3 | match_block_trailing_comma = true 4 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugin_name": "My Plugin", 3 | "project_slug": "{{ cookiecutter.plugin_name.lower().replace(' ', '_').replace('-', '_') }}", 4 | "author": "" 5 | } 6 | -------------------------------------------------------------------------------- /{{ cookiecutter.project_slug }}/run.py: -------------------------------------------------------------------------------- 1 | import polars as pl 2 | from {{ cookiecutter.project_slug }} import pig_latinnify 3 | 4 | 5 | df = pl.DataFrame( 6 | { 7 | "english": ["this", "is", "not", "pig", "latin"], 8 | } 9 | ) 10 | result = df.with_columns(pig_latin=pig_latinnify("english")) 11 | print(result) 12 | -------------------------------------------------------------------------------- /{{ cookiecutter.project_slug }}/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod expressions; 2 | use pyo3::prelude::*; 3 | use pyo3_polars::PolarsAllocator; 4 | 5 | #[pymodule] 6 | fn _internal(_py: Python, m: &Bound) -> PyResult<()> { 7 | m.add("__version__", env!("CARGO_PKG_VERSION"))?; 8 | Ok(()) 9 | } 10 | 11 | #[global_allocator] 12 | static ALLOC: PolarsAllocator = PolarsAllocator::new(); 13 | -------------------------------------------------------------------------------- /test_it.sh: -------------------------------------------------------------------------------- 1 | # I haven't set up CI for this repo yet, so here's 2 | # a little thing I run manually to check it's set up 3 | # correctly 4 | rm -rf my_plugin 5 | cookiecutter ~/cookiecutter-polars-plugins --no-input 6 | cd my_plugin 7 | uv venv -p python3.12 8 | . .venv/bin/activate 9 | uv pip install -U polars maturin mypy pytest pip 10 | make install 11 | pytest 12 | mypy my_plugin tests 13 | python -c 'import my_plugin; print(my_plugin.__version__)' 14 | 15 | -------------------------------------------------------------------------------- /{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/typing.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Union 2 | 3 | if TYPE_CHECKING: 4 | import sys 5 | import polars as pl 6 | 7 | if sys.version_info >= (3, 10): 8 | from typing import TypeAlias 9 | else: 10 | from typing_extensions import TypeAlias 11 | from polars.datatypes import DataType, DataTypeClass 12 | 13 | IntoExprColumn: TypeAlias = Union[pl.Expr, str, pl.Series] 14 | PolarsDataType: TypeAlias = Union[DataType, DataTypeClass] 15 | -------------------------------------------------------------------------------- /{{ cookiecutter.project_slug }}/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "{{ cookiecutter.project_slug.replace('_', '-') }}" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "{{ cookiecutter.project_slug }}" 8 | crate-type= ["cdylib"] 9 | 10 | [dependencies] 11 | pyo3 = { version = "0.26.0", features = ["extension-module", "abi3-py39"] } 12 | pyo3-polars = { version = "0.25.0", features = ["derive", "dtype-struct", "dtype-array"] } 13 | serde = { version = "1", features = ["derive"] } 14 | polars = { version = "0.52.0", default-features = false } 15 | polars-arrow = { version = "0.52.0", default-features = false } 16 | 17 | -------------------------------------------------------------------------------- /{{ cookiecutter.project_slug }}/src/expressions.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unused_unit)] 2 | use polars::prelude::*; 3 | use pyo3_polars::derive::polars_expr; 4 | use std::fmt::Write; 5 | 6 | #[polars_expr(output_type=String)] 7 | fn pig_latinnify(inputs: &[Series]) -> PolarsResult { 8 | let ca: &StringChunked = inputs[0].str()?; 9 | let out: StringChunked = ca.apply_into_string_amortized(|value: &str, output: &mut String| { 10 | if let Some(first_char) = value.chars().next() { 11 | write!(output, "{}{}ay", &value[1..], first_char).unwrap() 12 | } 13 | }); 14 | Ok(out.into_series()) 15 | } 16 | -------------------------------------------------------------------------------- /{{ cookiecutter.project_slug }}/tests/test_pig_latinnify.py: -------------------------------------------------------------------------------- 1 | import polars as pl 2 | from {{ cookiecutter.project_slug }} import pig_latinnify 3 | 4 | 5 | def test_piglatinnify(): 6 | df = pl.DataFrame( 7 | { 8 | "english": ["this", "is", "not", "pig", "latin"], 9 | } 10 | ) 11 | result = df.with_columns(pig_latin=pig_latinnify("english")) 12 | 13 | expected_df = pl.DataFrame( 14 | { 15 | "english": ["this", "is", "not", "pig", "latin"], 16 | "pig_latin": ["histay", "siay", "otnay", "igpay", "atinlay"], 17 | } 18 | ) 19 | 20 | assert result.equals(expected_df) 21 | -------------------------------------------------------------------------------- /{{ cookiecutter.project_slug }}/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["maturin>=1.0,<2.0", "polars>=1.3.0"] 3 | build-backend = "maturin" 4 | 5 | [project] 6 | name = "{{ cookiecutter.project_slug.replace('_', '-') }}" 7 | requires-python = ">=3.8" 8 | classifiers = [ 9 | "Programming Language :: Rust", 10 | "Programming Language :: Python :: Implementation :: CPython", 11 | "Programming Language :: Python :: Implementation :: PyPy", 12 | ] 13 | dynamic = ["version"] 14 | 15 | [tool.maturin] 16 | module-name = "{{ cookiecutter.project_slug }}._internal" 17 | 18 | [[tool.mypy.overrides]] 19 | module = "polars.utils.udfs" 20 | ignore_missing_imports = true 21 | -------------------------------------------------------------------------------- /{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | from typing import TYPE_CHECKING 5 | 6 | import polars as pl 7 | from polars.plugins import register_plugin_function 8 | 9 | from {{ cookiecutter.project_slug }}._internal import __version__ as __version__ 10 | 11 | if TYPE_CHECKING: 12 | from {{ cookiecutter.project_slug }}.typing import IntoExprColumn 13 | 14 | LIB = Path(__file__).parent 15 | 16 | 17 | def pig_latinnify(expr: IntoExprColumn) -> pl.Expr: 18 | return register_plugin_function( 19 | args=[expr], 20 | plugin_path=LIB, 21 | function_name="pig_latinnify", 22 | is_elementwise=True, 23 | ) 24 | 25 | -------------------------------------------------------------------------------- /{{ cookiecutter.project_slug }}/Makefile: -------------------------------------------------------------------------------- 1 | SHELL=/bin/bash 2 | 3 | venv: 4 | python3 -m venv .venv 5 | .venv/bin/pip install -r requirements.txt 6 | 7 | install: 8 | unset CONDA_PREFIX && \ 9 | source .venv/bin/activate && maturin develop 10 | 11 | install-release: 12 | unset CONDA_PREFIX && \ 13 | source .venv/bin/activate && maturin develop --release 14 | 15 | pre-commit: 16 | cargo +nightly fmt --all && cargo clippy --all-features 17 | .venv/bin/python -m ruff check . --fix --exit-non-zero-on-fix 18 | .venv/bin/python -m ruff format {{ cookiecutter.project_slug }} tests 19 | .venv/bin/mypy {{ cookiecutter.project_slug }} tests 20 | 21 | test: 22 | .venv/bin/python -m pytest tests 23 | 24 | run: install 25 | source .venv/bin/activate && python run.py 26 | 27 | run-release: install-release 28 | source .venv/bin/activate && python run.py 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Marco Edward Gorelli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main] 7 | 8 | jobs: 9 | linux: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-python@v4 14 | with: 15 | python-version: "3.11" 16 | - name: Install the latest version of uv 17 | uses: astral-sh/setup-uv@v2 18 | with: 19 | version: "latest" 20 | - run: uv --version 21 | - name: Install cookiecutter tool 22 | run: | 23 | uv venv 24 | uv pip install -U cookiecutter pip 25 | - name: Create plugin 26 | run: | 27 | . .venv/bin/activate 28 | cookiecutter . --no-input 29 | - name: Create environment and install dependencies 30 | run: | 31 | uv venv --seed 32 | . .venv/bin/activate 33 | uv pip install -U polars maturin mypy pytest pip 34 | maturin develop 35 | working-directory: my_plugin 36 | - name: Run linting and tests 37 | run: | 38 | . .venv/bin/activate 39 | mypy my_plugin tests 40 | pytest 41 | working-directory: my_plugin 42 | - name: Check version 43 | run: | 44 | . .venv/bin/activate 45 | python -c 'import my_plugin; print(my_plugin.__version__)' 46 | working-directory: my_plugin 47 | windows: 48 | runs-on: windows-latest 49 | steps: 50 | - uses: actions/checkout@v4 51 | - uses: actions/setup-python@v4 52 | with: 53 | python-version: "3.11" 54 | - name: Install the latest version of uv 55 | uses: astral-sh/setup-uv@v2 56 | with: 57 | version: "latest" 58 | - run: uv --version 59 | - name: Install cookiecutter tool 60 | run: | 61 | uv venv 62 | . .venv\Scripts\activate 63 | uv pip install -U cookiecutter pip 64 | - name: Create plugin 65 | run: | 66 | . .venv\Scripts\activate 67 | cookiecutter . --no-input 68 | - name: Create environment and install dependencies 69 | run: | 70 | uv venv --seed 71 | . .venv\Scripts\activate 72 | uv pip install -U polars maturin mypy pytest pip 73 | maturin develop 74 | working-directory: my_plugin 75 | - name: Run linting and tests 76 | run: | 77 | . .venv\Scripts\activate 78 | mypy my_plugin tests 79 | pytest 80 | working-directory: my_plugin 81 | - name: Check version 82 | run: | 83 | . .venv\Scripts\activate 84 | python -c 'import my_plugin; print(my_plugin.__version__)' 85 | working-directory: my_plugin 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Polars Plugins Cookiecutter 2 | 3 |

4 | Image from https://chelsweets.com/polar-bear-cookies 9 |

10 | 11 | Easily get started with Polars Plugins - get the boilerplate 12 | out of the way and start coding! 13 | 14 | ## Usage 15 | 16 | First of all, make sure you have `cookiecutter` installed - see 17 | [here](https://cookiecutter.readthedocs.io/en/stable/installation.html) 18 | for how to do that. 19 | 20 | Then, suppose you want to create a Polars Plugin called "minimal-plugin". 21 | Let's also suppose your name is "Maja Anima". 22 | 23 | This is how you would do that: 24 | 25 | 0. Install [Rust](https://rustup.rs/) 26 | 1. In your home directory (or wherever you store your projects), run 27 | ```console 28 | cookiecutter https://github.com/MarcoGorelli/cookiecutter-polars-plugins.git 29 | ``` 30 | 2. When prompted, enter the following: 31 | ``` 32 | [1/3] plugin_name (My Plugin): Minimal Plugin 33 | [2/3] project_slug (minimal_plugin): 34 | [3/3] author (anonymous): Maja Anima 35 | ``` 36 | 3. Navigate to the directory you just created. For example, if you named your plugin "Minimal Plugin", 37 | you would do 38 | ``` 39 | cd minimal_plugin 40 | ``` 41 | 4. Create and activate a new Python 3.9+ virtual environment and install Polars and Maturin. 42 | If you're new to this, here's one way that we recommend: 43 | 44 | 1. Install uv: https://github.com/astral-sh/uv?tab=readme-ov-file#getting-started 45 | 2. Install some version of Python greater than Python3.9. For example, to install 46 | Python3.11: 47 | ``` 48 | uv python install 3.11 49 | ``` 50 | 3. Create a virtual environment: 51 | ``` 52 | uv venv -p python3.11 53 | ``` 54 | 4. Activate your virtual environment: 55 | ``` 56 | # On macOS and Linux. 57 | source .venv/bin/activate 58 | 59 | # On Windows. 60 | .venv\Scripts\activate 61 | ``` 62 | 5. Install Polars and Maturin: 63 | ``` 64 | uv pip install -U polars maturin pip 65 | ``` 66 | 5. Start compiling the Rust code! This may take a few minutes the first time you do it, but subsequent 67 | runs will be fast: 68 | ``` 69 | maturin develop # or, if you're benchmarking, maturin develop --release 70 | ``` 71 | 6. Try running 72 | ``` 73 | python run.py 74 | ``` 75 | If you see 76 | ``` 77 | shape: (5, 2) 78 | ┌─────────┬───────────┐ 79 | │ english ┆ pig_latin │ 80 | │ --- ┆ --- │ 81 | │ str ┆ str │ 82 | ╞═════════╪═══════════╡ 83 | │ this ┆ histay │ 84 | │ is ┆ siay │ 85 | │ not ┆ otnay │ 86 | │ pig ┆ igpay │ 87 | │ latin ┆ atinlay │ 88 | └─────────┴───────────┘ 89 | ``` 90 | then it means everything worked correctly! If not, please open an issue, happy 91 | to help debug. 92 | 93 | Now, writing your plugin is a different story...please go to https://marcogorelli.github.io/polars-plugins-tutorial/ 94 | for a tutorial on how to get started. 95 | 96 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | https://www.linkedin.com/in/marcogorelli/. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /{{ cookiecutter.project_slug }}/.github/workflows/publish_to_pypi.yml: -------------------------------------------------------------------------------- 1 | # This file is based on the autogenerated one by maturin v1.7.8 with: 2 | # 3 | # maturin generate-ci github 4 | # 5 | # Differences are: 6 | # - removed x86, armv7, s390x, ppc64le targets from Linux 7 | # - removed free-threaded wheels 8 | # - removed musllinux 9 | # - have separate linux-just-test and linux-min-versions-just-test jobs 10 | # - add the `RUSTFLAGS: "-Dwarnings"` env variable 11 | 12 | {% raw %} 13 | name: CI 14 | 15 | on: 16 | push: 17 | branches: 18 | - main 19 | - master 20 | tags: 21 | - '*' 22 | pull_request: 23 | workflow_dispatch: 24 | 25 | permissions: 26 | contents: read 27 | 28 | # Make sure CI fails on all warnings, including Clippy lints 29 | env: 30 | RUSTFLAGS: "-Dwarnings" 31 | 32 | jobs: 33 | linux-just-test: 34 | runs-on: ubuntu-latest 35 | strategy: 36 | matrix: 37 | target: [x86_64] 38 | python-version: ["3.9", "3.11", "3.13"] 39 | steps: 40 | - uses: actions/checkout@v4 41 | - uses: actions/setup-python@v5 42 | with: 43 | python-version: ${{ matrix.python-version }} 44 | 45 | - name: Set up Rust 46 | run: rustup show 47 | - uses: mozilla-actions/sccache-action@v0.0.6 48 | - run: make venv 49 | - run: make pre-commit 50 | - run: make install 51 | - run: make test 52 | 53 | linux-min-versions-just-test: 54 | runs-on: ubuntu-latest 55 | strategy: 56 | matrix: 57 | target: [x86_64] 58 | python-version: ["3.9"] 59 | steps: 60 | - uses: actions/checkout@v4 61 | - uses: actions/setup-python@v5 62 | with: 63 | python-version: ${{ matrix.python-version }} 64 | 65 | - name: Set up Rust 66 | run: rustup show 67 | - uses: mozilla-actions/sccache-action@v0.0.6 68 | - run: make venv 69 | - run: .venv/bin/python -m pip install polars==1.3.0 # min version 70 | - run: make install 71 | - run: make test 72 | 73 | linux: 74 | runs-on: ${{ matrix.platform.runner }} 75 | strategy: 76 | matrix: 77 | platform: 78 | - runner: ubuntu-22.04 79 | target: x86_64 80 | - runner: ubuntu-22.04 81 | target: aarch64 82 | steps: 83 | - uses: actions/checkout@v4 84 | - uses: actions/setup-python@v5 85 | with: 86 | python-version: 3.x 87 | - name: Build wheels 88 | uses: PyO3/maturin-action@v1 89 | with: 90 | target: ${{ matrix.platform.target }} 91 | args: --release --out dist 92 | sccache: 'true' 93 | manylinux: auto 94 | - name: Upload wheels 95 | uses: actions/upload-artifact@v4 96 | with: 97 | name: wheels-linux-${{ matrix.platform.target }} 98 | path: dist 99 | 100 | windows: 101 | runs-on: ${{ matrix.platform.runner }} 102 | strategy: 103 | matrix: 104 | platform: 105 | - runner: windows-latest 106 | target: x64 107 | steps: 108 | - uses: actions/checkout@v4 109 | - uses: actions/setup-python@v5 110 | with: 111 | python-version: 3.x 112 | architecture: ${{ matrix.platform.target }} 113 | - name: Build wheels 114 | uses: PyO3/maturin-action@v1 115 | with: 116 | target: ${{ matrix.platform.target }} 117 | args: --release --out dist 118 | sccache: 'true' 119 | - name: Upload wheels 120 | uses: actions/upload-artifact@v4 121 | with: 122 | name: wheels-windows-${{ matrix.platform.target }} 123 | path: dist 124 | 125 | macos: 126 | runs-on: ${{ matrix.platform.runner }} 127 | strategy: 128 | matrix: 129 | platform: 130 | - runner: macos-13 131 | target: x86_64 132 | - runner: macos-14 133 | target: aarch64 134 | steps: 135 | - uses: actions/checkout@v4 136 | - uses: actions/setup-python@v5 137 | with: 138 | python-version: 3.x 139 | - name: Build wheels 140 | uses: PyO3/maturin-action@v1 141 | with: 142 | target: ${{ matrix.platform.target }} 143 | args: --release --out dist 144 | sccache: 'true' 145 | - name: Upload wheels 146 | uses: actions/upload-artifact@v4 147 | with: 148 | name: wheels-macos-${{ matrix.platform.target }} 149 | path: dist 150 | 151 | sdist: 152 | runs-on: ubuntu-latest 153 | steps: 154 | - uses: actions/checkout@v4 155 | - name: Build sdist 156 | uses: PyO3/maturin-action@v1 157 | with: 158 | command: sdist 159 | args: --out dist 160 | - name: Upload sdist 161 | uses: actions/upload-artifact@v4 162 | with: 163 | name: wheels-sdist 164 | path: dist 165 | 166 | release: 167 | name: Release 168 | runs-on: ubuntu-latest 169 | if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} 170 | needs: [linux, windows, macos, sdist] 171 | environment: pypi 172 | permissions: 173 | # Use to sign the release artifacts 174 | id-token: write 175 | # Used to upload release artifacts 176 | contents: write 177 | # Used to generate artifact attestation 178 | attestations: write 179 | steps: 180 | - uses: actions/download-artifact@v4 181 | - name: Generate artifact attestation 182 | uses: actions/attest-build-provenance@v1 183 | with: 184 | subject-path: 'wheels-*/*' 185 | - name: Publish to PyPI 186 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 187 | uses: PyO3/maturin-action@v1 188 | env: 189 | MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} 190 | with: 191 | command: upload 192 | args: --non-interactive --skip-existing wheels-*/* 193 | {% endraw %} 194 | --------------------------------------------------------------------------------