Skip to content

Commit 59060e3

Browse files
authored
feat: abi3 wheels for py3.10+ and drop unused exports (#1702) (#1)
Build a single CPython stable-ABI (abi3-py310) wheel per platform via pyo3's abi3-py310 feature, so a new Python release (3.14 and beyond) never leaves ormar without an installable wheel — the root cause of the missing cp314 build. Remove four exports that ormar does not use (extract_prefixed_columns, prepare_model_to_save, translate_columns_to_aliases, translate_aliases_to_columns) along with their modules and tests; ormar's full suite passes against the trimmed build. Production hardening: mandatory CI lint (cargo fmt --check, clippy, ruff), test matrix extended to Python 3.14 and Windows, abi3 single-wheel build plus sdist, and the broken repository URLs are corrected.
1 parent 6e1d0c9 commit 59060e3

14 files changed

Lines changed: 100 additions & 258 deletions

.github/workflows/ci.yml

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,52 +12,55 @@ permissions:
1212

1313
jobs:
1414
lint:
15+
name: Lint (rustfmt, clippy, ruff)
1516
runs-on: ubuntu-latest
1617
steps:
1718
- uses: actions/checkout@v4
1819
- name: Install Rust toolchain
1920
uses: dtolnay/rust-toolchain@stable
2021
with:
21-
components: clippy
22-
- name: Run lint checks
23-
run: make check
22+
components: clippy, rustfmt
23+
- name: Rust format check
24+
run: cargo fmt --check
25+
- name: Clippy
26+
run: cargo clippy --all-targets -- -D warnings
27+
- uses: actions/setup-python@v5
28+
with:
29+
python-version: "3.12"
30+
- name: Install Ruff
31+
run: pip install ruff
32+
- name: Ruff lint
33+
run: ruff check tests
34+
- name: Ruff format check
35+
run: ruff format --check tests
2436

2537
test:
38+
name: Test (py${{ matrix.python-version }} on ${{ matrix.os }})
2639
needs: lint
2740
runs-on: ${{ matrix.os }}
2841
strategy:
2942
fail-fast: false
3043
matrix:
31-
os: [ubuntu-latest, macos-latest]
32-
python-version: ["3.10", "3.11", "3.12", "3.13"]
44+
os: [ubuntu-latest, macos-latest, windows-latest]
45+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
3346
steps:
3447
- uses: actions/checkout@v4
3548
- uses: actions/setup-python@v5
3649
with:
3750
python-version: ${{ matrix.python-version }}
3851
- name: Install Rust toolchain
3952
uses: dtolnay/rust-toolchain@stable
40-
- name: Install Poetry
41-
uses: snok/install-poetry@v1
42-
with:
43-
version: latest
44-
virtualenvs-in-project: true
45-
- name: Cache Poetry virtualenv
46-
uses: actions/cache@v4
47-
with:
48-
path: .venv
49-
key: poetry-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
50-
restore-keys: |
51-
poetry-${{ runner.os }}-py${{ matrix.python-version }}-
52-
- name: Install dependencies
53-
run: poetry install
54-
- name: Build with maturin
55-
run: make build
53+
- name: Install build/test tooling
54+
run: pip install "maturin>=1.0,<2.0" pytest
55+
- name: Build abi3 wheel
56+
run: maturin build --release --out dist
57+
- name: Install wheel
58+
run: pip install --no-index --find-links dist ormar-utils
5659
- name: Run tests
57-
run: make test
60+
run: pytest tests/ -v
5861

5962
build:
60-
name: Build wheels
63+
name: Build wheels (${{ matrix.os }}-${{ matrix.target }})
6164
needs: test
6265
if: startsWith(github.ref, 'refs/tags/v')
6366
runs-on: ${{ matrix.os }}
@@ -79,21 +82,41 @@ jobs:
7982
- uses: actions/setup-python@v5
8083
with:
8184
python-version: "3.12"
85+
# abi3-py310 produces a single stable-ABI wheel per platform that works on
86+
# CPython 3.10+ (including future releases), so no per-interpreter matrix.
8287
- name: Build wheels
8388
uses: PyO3/maturin-action@v1
8489
with:
8590
target: ${{ matrix.target }}
86-
args: --release --out dist -i python3.10 python3.11 python3.12 python3.13
91+
args: --release --out dist
8792
manylinux: auto
8893
- name: Upload wheels
8994
uses: actions/upload-artifact@v4
9095
with:
9196
name: wheels-${{ matrix.os }}-${{ matrix.target }}
9297
path: dist
9398

99+
sdist:
100+
name: Build sdist
101+
needs: test
102+
if: startsWith(github.ref, 'refs/tags/v')
103+
runs-on: ubuntu-latest
104+
steps:
105+
- uses: actions/checkout@v4
106+
- name: Build sdist
107+
uses: PyO3/maturin-action@v1
108+
with:
109+
command: sdist
110+
args: --out dist
111+
- name: Upload sdist
112+
uses: actions/upload-artifact@v4
113+
with:
114+
name: wheels-sdist
115+
path: dist
116+
94117
publish:
95118
name: Publish to PyPI
96-
needs: build
119+
needs: [build, sdist]
97120
if: startsWith(github.ref, 'refs/tags/v')
98121
runs-on: ubuntu-latest
99122
environment: pypi

.pre-commit-config.yaml

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,25 @@
11
repos:
2-
- repo: https://github.com/rust-lang/rust-clippy
3-
rev: v1.77.0
4-
hooks:
5-
- id: clippy
6-
name: clippy
7-
entry: cargo clippy -- -D warnings
8-
language: system
9-
types: [rust]
10-
pass_filenames: false
11-
stages: [commit]
12-
132
- repo: local
143
hooks:
154
- id: cargo-fmt
165
name: cargo fmt
17-
entry: cargo fmt
6+
entry: cargo fmt --check
187
language: system
198
types: [rust]
209
pass_filenames: false
21-
stages: [commit]
2210

23-
- id: cargo-check
24-
name: cargo check
25-
entry: cargo check
11+
- id: clippy
12+
name: clippy
13+
entry: cargo clippy --all-targets -- -D warnings
2614
language: system
2715
types: [rust]
2816
pass_filenames: false
29-
stages: [commit]
17+
18+
- repo: https://github.com/astral-sh/ruff-pre-commit
19+
rev: v0.15.16
20+
hooks:
21+
- id: ruff
22+
args: [--fix]
23+
files: ^tests/
24+
- id: ruff-format
25+
files: ^tests/

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ormar_rust_utils"
3-
version = "0.1.1"
3+
version = "0.2.0"
44
edition = "2021"
55
description = "Rust-accelerated utility functions for the ormar ORM"
66
license = "MIT"
@@ -10,7 +10,10 @@ name = "ormar_rust_utils"
1010
crate-type = ["cdylib"]
1111

1212
[dependencies]
13-
pyo3 = { version = "0.23.5", features = ["extension-module"] }
13+
# abi3-py310 builds a single stable-ABI wheel that works on CPython 3.10 and
14+
# every newer version (3.14, 3.15, ...) without a per-version rebuild, so a new
15+
# Python release never leaves ormar without an installable wheel.
16+
pyo3 = { version = "0.23.5", features = ["extension-module", "abi3-py310"] }
1417
base64 = "0.22"
1518
serde_json = "1.0"
1619
indexmap = "2.0"

Makefile

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
.PHONY: fmt check test build clean
1+
.PHONY: fmt fmt-check check lint test build clean
22

33
fmt:
44
cargo fmt
5+
poetry run ruff format tests
6+
7+
fmt-check:
8+
cargo fmt --check
9+
poetry run ruff format --check tests
510

611
check:
7-
cargo clippy -- -D warnings
12+
cargo clippy --all-targets -- -D warnings
13+
14+
lint: fmt-check check
15+
poetry run ruff check tests
816

917
test:
1018
poetry run pytest tests/ -v
@@ -20,8 +28,10 @@ clean:
2028

2129
help:
2230
@echo "Available targets:"
23-
@echo " fmt - Format code with rustfmt"
31+
@echo " fmt - Format Rust (rustfmt) and Python (ruff) code"
32+
@echo " fmt-check - Check formatting without modifying files"
2433
@echo " check - Run clippy linter with strict warnings"
34+
@echo " lint - Run all format checks, clippy and ruff lint"
2535
@echo " test - Run tests with pytest"
2636
@echo " build - Build the extension module with maturin"
2737
@echo " clean - Clean build artifacts"

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ormar-utils
22

3-
Rust-accelerated utility functions for the [ormar](https://github.com/collerek/ormar) async ORM.
3+
Rust-accelerated utility functions for the [ormar](https://github.com/ormar-orm/ormar) async ORM.
44

55
This package provides optional Rust implementations of performance-critical operations used internally by ormar. When installed, ormar automatically uses these faster implementations.
66

@@ -21,6 +21,10 @@ pip install ormar[rust]
2121
- Python >= 3.10
2222
- A Rust toolchain (for building from source)
2323

24+
Wheels are built against the CPython stable ABI (`abi3`, Python 3.10+), so a
25+
single wheel per platform works on current and future Python releases without a
26+
per-version rebuild.
27+
2428
## API Reference
2529

2630
All functions are exposed from the `ormar_rust_utils` module:
@@ -40,9 +44,8 @@ All functions are exposed from the `ormar_rust_utils` module:
4044
### Collections
4145
- `UniqueList(initial=None)` - A list that prevents duplicates using hash-based O(1) lookups
4246

43-
### Row Processing
44-
- `extract_prefixed_columns(column_mappings, selected_columns, row, column_prefix, item)` - Extract prefixed columns from a database row
45-
- `prepare_model_to_save(new_kwargs, aliases_map, fields_to_keep)` - Consolidate column alias translation and field filtering
47+
### Alias Utilities
48+
- `build_reverse_alias_map(field_alias_map)` - Build a cached alias -> field_name lookup (with identity entries) from a field_name -> alias mapping
4649

4750
### Merge Infrastructure
4851
- `group_by_pk(pks)` - Group items by PK hash, preserving insertion order

pyproject.toml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "maturin"
44

55
[project]
66
name = "ormar-utils"
7-
version = "0.1.1"
7+
version = "0.2.0"
88
description = "Rust-accelerated utility functions for the ormar ORM"
99
readme = "README.md"
1010
license = { text = "MIT" }
@@ -22,20 +22,22 @@ classifiers = [
2222
"Programming Language :: Python :: 3.11",
2323
"Programming Language :: Python :: 3.12",
2424
"Programming Language :: Python :: 3.13",
25+
"Programming Language :: Python :: 3.14",
26+
"Programming Language :: Python :: 3 :: Only",
2527
"License :: OSI Approved :: MIT License",
2628
"Topic :: Database",
2729
]
2830

2931
[project.urls]
30-
Homepage = "https://github.com/collerek/ormar-utils"
31-
Repository = "https://github.com/collerek/ormar-utils"
32+
Homepage = "https://github.com/ormar-orm/ormar-utils"
33+
Repository = "https://github.com/ormar-orm/ormar-utils"
3234

3335
[tool.maturin]
3436
features = ["pyo3/extension-module"]
3537

3638
[tool.poetry]
3739
name = "ormar-utils"
38-
version = "0.1.1"
40+
version = "0.2.0"
3941
description = "Rust-accelerated utility functions for the ormar ORM"
4042
authors = ["Radosław Drążkiewicz <collerek@gmail.com>"]
4143
package-mode = false
@@ -47,6 +49,10 @@ python = "^3.10"
4749
pytest = "^7.0"
4850
maturin = "^1.0"
4951
pre-commit = "^3.0"
52+
ruff = "^0.15"
5053

5154
[tool.pytest.ini_options]
5255
testpaths = ["tests"]
56+
57+
[tool.ruff]
58+
target-version = "py310"

src/alias_utils.rs

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use pyo3::prelude::*;
22
use pyo3::types::PyDict;
3-
use std::collections::HashMap;
43

54
/// Build a reverse mapping from alias -> field_name given a forward mapping
65
/// of field_name -> alias. If a field has no alias (alias == field_name),
@@ -25,52 +24,3 @@ pub fn build_reverse_alias_map<'py>(
2524
}
2625
Ok(result)
2726
}
28-
29-
/// Translate dict keys from field names to their database aliases.
30-
/// Takes the dict to translate and a field_name->alias mapping.
31-
/// Returns a new dict with aliased keys.
32-
#[pyfunction]
33-
pub fn translate_columns_to_aliases<'py>(
34-
py: Python<'py>,
35-
new_kwargs: &Bound<'py, PyDict>,
36-
field_to_alias: &Bound<'py, PyDict>,
37-
) -> PyResult<Bound<'py, PyDict>> {
38-
let result = PyDict::new(py);
39-
// Pre-extract the mapping into a Rust HashMap for fast lookup
40-
let mut alias_map: HashMap<String, String> = HashMap::new();
41-
for (k, v) in field_to_alias.iter() {
42-
let key: String = k.extract()?;
43-
let val: String = v.extract()?;
44-
alias_map.insert(key, val);
45-
}
46-
47-
for (key, value) in new_kwargs.iter() {
48-
let field_name: String = key.extract()?;
49-
if let Some(alias) = alias_map.get(&field_name) {
50-
result.set_item(alias, value)?;
51-
} else {
52-
result.set_item(key, value)?;
53-
}
54-
}
55-
Ok(result)
56-
}
57-
58-
/// Translate dict keys from database aliases to field names.
59-
/// Takes the dict to translate and an alias->field_name mapping.
60-
/// Returns a new dict with field name keys.
61-
#[pyfunction]
62-
pub fn translate_aliases_to_columns<'py>(
63-
py: Python<'py>,
64-
new_kwargs: &Bound<'py, PyDict>,
65-
alias_to_field: &Bound<'py, PyDict>,
66-
) -> PyResult<Bound<'py, PyDict>> {
67-
let result = PyDict::new(py);
68-
for (key, value) in new_kwargs.iter() {
69-
if let Some(field_name) = alias_to_field.get_item(&key)? {
70-
result.set_item(field_name, value)?;
71-
} else {
72-
result.set_item(key, value)?;
73-
}
74-
}
75-
Ok(result)
76-
}

src/extract_columns.rs

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)