Skip to content

Commit 47c9a1c

Browse files
committed
fix: support CPython 3.10 release parity
1 parent dce7943 commit 47c9a1c

13 files changed

Lines changed: 325 additions & 30 deletions

File tree

.github/workflows/ci.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,12 @@ jobs:
6868
# Lint & Test (Python)
6969
# ============================================================================
7070
lint-and-test:
71-
name: Lint & Test
71+
name: Lint & Test (Python ${{ matrix.python }})
7272
if: github.event_name == 'pull_request' || github.event_name == 'push'
73+
strategy:
74+
fail-fast: false
75+
matrix:
76+
python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
7377
runs-on: ubuntu-latest
7478
steps:
7579
- name: Harden Runner
@@ -81,7 +85,7 @@ jobs:
8185
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
8286
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
8387
with:
84-
python-version: "3.13"
88+
python-version: ${{ matrix.python }}
8589

8690
- run: uv sync --locked --group dev
8791
- run: uv run ruff check .
@@ -91,7 +95,7 @@ jobs:
9195
- name: Run tests with coverage
9296
run: |
9397
if [ -d "tests" ] && [ "$(ls -A tests/*.py 2>/dev/null)" ]; then
94-
uv run pytest --tb=short --cov=fastretrieval --cov-report=xml
98+
uv run pytest -m "not integration" --tb=short --cov=fastretrieval --cov-report=xml
9599
else
96100
echo "No tests found, skipping..."
97101
fi

AGENTS.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# AGENTS.md - fastretrieval
22

3-
Fast multi-model retrieval runtime: ONNX and GGUF embeddings, reranking, and a declarative model contract. Python >= 3.11 (tested 3.11-3.14), uv.
3+
Fast multi-model retrieval runtime: ONNX and GGUF embeddings, reranking, and a declarative model contract. Python 3.10-3.14, uv.
44

55
## Package Identity
66

@@ -48,7 +48,7 @@ mise run fix # ruff check --fix + ruff format
4848
- `testpaths = ["tests"]`, `pythonpath = ["."]`
4949
- Integration marker: `@pytest.mark.integration` (requires model downloads: ONNX ~1.2 GB, Q4F16 ~1 GB, GGUF ~756 MB)
5050
- Integration test files: `test_integration.py` (ONNX), `test_integration_q4f16.py`, `test_integration_gguf.py`
51-
- CI runs: `uv run pytest -m "not integration" --tb=short`
51+
- CI runs: `uv run pytest -m "not integration" --tb=short` on CPython 3.10-3.14.
5252

5353
## Code Style
5454

@@ -57,7 +57,7 @@ mise run fix # ruff check --fix + ruff format
5757
- **Line length**: 99
5858
- **Quotes**: Double quotes
5959
- **Indent**: 4 spaces
60-
- **Target**: Python 3.13
60+
- **Target**: Python 3.10
6161

6262
### Ruff Rules
6363

@@ -92,9 +92,9 @@ from fastretrieval.common.types import PathInput, Device
9292
### Type Hints
9393

9494
- Full type hints everywhere: parameters, return types, variables
95-
- **Python 3.12+ type alias syntax**: `type PathInput = str | Path`
96-
- **Python 3.12+ generics**: `class ModelManagement[T: BaseModelDescription]:`, `def iter_batch[T](...)`
97-
- Union types: `str | None` (not `Optional`), `list[str]` (not `List`)
95+
- Built-in generics: `list[str]` and `dict[str, Any]`
96+
- Union syntax: `str | None` (Python 3.10+)
97+
- Do not introduce syntax or standard-library APIs newer than Python 3.10.
9898
- `py.typed` marker file present
9999

100100
### Naming Conventions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ uses a declarative model contract so built-in Qwen3 reference models and custom
5252
share the same runtime, and supports Matryoshka (MRL) truncation, instruction-aware queries,
5353
and optional GPU acceleration. It is derived from [fastembed](https://github.com/qdrant/fastembed)
5454
and keeps Qwen3 model names as model identifiers rather than as the package boundary.
55+
Supported runtimes are CPython 3.10, 3.11, 3.12, 3.13, and 3.14.
5556

5657
## What it does
5758

fastretrieval/common/compat.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""Compatibility shims for the oldest supported CPython runtime."""
2+
3+
from enum import Enum
4+
5+
6+
class StrEnum(str, Enum):
7+
"""Backport the small ``enum.StrEnum`` surface used by the package."""
8+
9+
def __str__(self) -> str:
10+
return str(self.value)

fastretrieval/common/model_description.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from dataclasses import dataclass, field
2-
from enum import StrEnum
32
from typing import Any
43

4+
from fastretrieval.common.compat import StrEnum
5+
56

67
@dataclass(frozen=True)
78
class ModelSource:

fastretrieval/common/types.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
from enum import StrEnum
21
from pathlib import Path
32
from typing import Any, TypeAlias
43

54
import numpy as np
65
from numpy.typing import NDArray
76

7+
from fastretrieval.common.compat import StrEnum
8+
89

910
class Device(StrEnum):
1011
CPU = "cpu"

fastretrieval/common/utils.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from collections.abc import Iterable
66
from itertools import islice
77
from pathlib import Path
8-
from typing import TypeVar
8+
from typing import TypeVar, cast
99

1010
import numpy as np
1111
from numpy.typing import NDArray
@@ -77,7 +77,10 @@ def last_token_pool(input_array: NumpyArray, attention_mask: NDArray[np.int64])
7777
"""
7878
batch_size, seq_len = attention_mask.shape
7979
if seq_len == 0:
80-
return np.zeros((batch_size,) + input_array.shape[2:], dtype=input_array.dtype)
80+
return cast(
81+
NumpyArray,
82+
np.zeros((batch_size,) + input_array.shape[2:], dtype=input_array.dtype),
83+
)
8184

8285
# ⚡ Bolt: Fast path if all samples end with a valid token (e.g. left-padding or no padding)
8386
# Fast boolean reduction using .all() (~15% faster than .sum() == shape[0])

fastretrieval/convert/onnx.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,21 @@ def quantize_q4f16(fp32_path: Path, out_path: Path) -> float:
113113
require_convert_deps("onnx", "onnxconverter_common", "onnxruntime", "onnx_ir")
114114
import onnx
115115
from onnxconverter_common import float16
116-
from onnxruntime.quantization.matmul_nbits_quantizer import MatMulNBitsQuantizer
116+
from onnxruntime.quantization.matmul_nbits_quantizer import (
117+
DefaultWeightOnlyQuantConfig,
118+
MatMulNBitsQuantizer,
119+
)
117120

118121
out_path.parent.mkdir(parents=True, exist_ok=True)
119122
logger.info("quantizing to Q4F16: {}", out_path)
120123
quantizer = MatMulNBitsQuantizer(
121124
model=str(fp32_path),
122-
bits=4,
123-
block_size=128,
124-
is_symmetric=True,
125-
accuracy_level=4,
125+
algo_config=DefaultWeightOnlyQuantConfig(
126+
block_size=128,
127+
is_symmetric=True,
128+
accuracy_level=4,
129+
bits=4,
130+
),
126131
)
127132
quantizer.process()
128133

fastretrieval/parallel_processor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
from collections.abc import Iterable
44
from copy import deepcopy
55
from dataclasses import dataclass
6-
from enum import StrEnum
76
from multiprocessing import Queue, get_context
87
from multiprocessing.context import BaseContext
98
from multiprocessing.process import BaseProcess
109
from multiprocessing.sharedctypes import Synchronized as BaseValue
1110
from queue import Empty
1211
from typing import Any
1312

13+
from fastretrieval.common.compat import StrEnum
1414
from fastretrieval.common.types import Device
1515

1616
# Single item should be processed in less than:

pyproject.toml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ version = "1.0.0"
44
description = "Fast multi-model retrieval runtime: ONNX and GGUF embeddings, reranking, and a declarative model contract"
55
readme = "README.md"
66
license = "Apache-2.0"
7-
requires-python = ">=3.11"
7+
requires-python = ">=3.10"
88
authors = [{ name = "n24q02m" }]
99
keywords = ["vector", "embedding", "reranking", "qwen3", "onnx", "onnxruntime"]
1010
classifiers = [
1111
"Development Status :: 5 - Production/Stable",
1212
"Intended Audience :: Developers",
1313
"License :: OSI Approved :: Apache Software License",
1414
"Programming Language :: Python :: 3",
15+
"Programming Language :: Python :: 3.10",
1516
"Programming Language :: Python :: 3.11",
1617
"Programming Language :: Python :: 3.12",
1718
"Programming Language :: Python :: 3.13",
@@ -20,7 +21,10 @@ classifiers = [
2021
]
2122

2223
dependencies = [
23-
"numpy>=2.4.6,<2.5",
24+
"numpy>=2.2.0,<2.5",
25+
# ORT 1.24 dropped cp310 wheels; keep the last cp310-compatible line for
26+
# Python 3.10 while newer interpreters use the current compatible release.
27+
"onnxruntime>=1.23.2,<1.24; python_version == '3.10'",
2428
"onnxruntime>1.20.0; python_version >= '3.11'",
2529
"tqdm>=4.70.0",
2630
"requests>=2.34.2",
@@ -64,6 +68,10 @@ packages = ["fastretrieval"]
6468
version_toml = ["pyproject.toml:project.version"]
6569
tag_format = "v{version}"
6670
commit_message = "chore(release): v{version}"
71+
build_command = """
72+
uv lock --upgrade-package "$PACKAGE_NAME"
73+
uv build
74+
"""
6775
major_on_zero = false
6876

6977
[tool.semantic_release.changelog]
@@ -88,7 +96,7 @@ call-non-callable = "warn"
8896

8997
[tool.ruff]
9098
line-length = 99
91-
target-version = "py311"
99+
target-version = "py310"
92100

93101
[tool.ruff.lint]
94102
select = ["E", "F", "I", "UP", "B", "SIM"]

0 commit comments

Comments
 (0)