Skip to content

Commit 6ab35d4

Browse files
dionhaefnerxalelax
andauthored
refactor!: replace python 3.9 with 3.10 as oldest supported version (#401)
<!-- Please use a PR title that conforms to *conventional commits*: "<commit_type>: Describe your change"; for example: "fix: prevent race condition". Some other commit types are: fix, feat, ci, doc, refactor... For a full list of commit types visit https://www.conventionalcommits.org/en/v1.0.0/ --> #### Relevant issue or PR Fixes #234 #### Description of changes - Replaces 3.9 with 3.10 as lower bound, and resolves pre-commit upgrades. - Also test with Python 3.13 instead of 3.12. #### Testing done CI --------- Co-authored-by: Alessandro Angioi <[email protected]>
1 parent 6ceb18c commit 6ab35d4

25 files changed

+130
-156
lines changed

.github/workflows/run_tests.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ jobs:
2020
# test with oldest and latest supported Python versions
2121
# NOTE: If bumping the minimum Python version here, also do it in
2222
# ruff.toml, setup.py and other CI files as well.
23-
python-version: ["3.10", "3.12"]
23+
python-version: ["3.10", "3.13"]
2424
runtime-deps: ["latest"]
2525

2626
include:
2727
- os: ubuntu-latest
28-
python-version: "3.12"
28+
python-version: "3.10"
2929
runtime-deps: "oldest"
3030

3131
fail-fast: false
@@ -199,7 +199,7 @@ jobs:
199199
strategy:
200200
matrix:
201201
os: [ubuntu-24.04]
202-
python-version: ["3.12"]
202+
python-version: ["3.13"]
203203

204204
arch: ["x64"]
205205
docker-engine: ["docker"]
@@ -214,18 +214,18 @@ jobs:
214214
# Test on arm to ensure compatibility with Apple M1 chips
215215
# (OSX runners don't have access to Docker so we use Linux ARM runners instead)
216216
- os: "ubuntu-24.04"
217-
python-version: "3.12"
217+
python-version: "3.13"
218218
arch: "arm"
219219
docker-engine: "docker"
220220
unit-tesseract: "base"
221221
- os: "ubuntu-24.04"
222-
python-version: "3.12"
222+
python-version: "3.13"
223223
arch: "arm"
224224
docker-engine: "docker"
225225
unit-tesseract: "pyvista-arm64"
226226
# Run tests using Podman
227227
- os: "ubuntu-24.04"
228-
python-version: "3.12"
228+
python-version: "3.13"
229229
arch: "x64"
230230
docker-engine: "podman"
231231
unit-tesseract: "base"

examples/py39/tesseract_api.py renamed to examples/py310/tesseract_api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ class OutputSchema(BaseModel):
2525

2626

2727
def apply(inputs: InputSchema) -> OutputSchema:
28-
# Ensure that the Python version is what we expect (3.9)
28+
# Ensure that the Python version is what we expect (3.10)
2929
import sys
3030

31-
assert sys.version_info[:2] == (3, 9)
31+
assert sys.version_info[:2] == (3, 10)
3232
return OutputSchema(bar=0)
3333

3434

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name: "py310"
2+
version: "0.1.0"
3+
description: |
4+
Empty Tesseract that requires Python 3.10 (set through a custom Docker image).
5+
6+
build_config:
7+
base_image: "python:3.10-slim-bookworm"

examples/py39/tesseract_config.yaml

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

examples/qp_solve/tesseract_api.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
# Tesseract API module for lp_solve
55
# Generated by tesseract 0.9.0 on 2025-06-04T13:11:17.208977
66
import functools
7-
from typing import Optional
87

98
import jax
109
import jax.numpy as jnp
@@ -23,29 +22,29 @@ class InputSchema(BaseModel):
2322
Q: Differentiable[Array[(None, None), Float32]] = Field(
2423
description="Quadratic cost matrix Q for the quadratic program."
2524
)
26-
q: Optional[Differentiable[Array[(None,), Float32]]] = Field(
25+
q: Differentiable[Array[(None,), Float32]] | None = Field(
2726
description="Linear cost vector q for the quadratic program.", default=None
2827
)
29-
A: Optional[Differentiable[Array[(None, None), Float32]]] = Field(
28+
A: Differentiable[Array[(None, None), Float32]] | None = Field(
3029
default=None,
3130
description="Linear equality constraint matrix A for the quadratic program.",
3231
)
33-
b: Optional[Differentiable[Array[(None,), Float32]]] = Field(
32+
b: Differentiable[Array[(None,), Float32]] | None = Field(
3433
default=None,
3534
description="Linear equality constraint rhs b for the quadratic program.",
3635
)
37-
G: Optional[Differentiable[Array[(None, None), Float32]]] = Field(
36+
G: Differentiable[Array[(None, None), Float32]] | None = Field(
3837
description="Linear inequality constraint matrix G for the quadratic program.",
3938
default=None,
4039
)
41-
h: Optional[Differentiable[Array[(None,), Float32]]] = Field(
40+
h: Differentiable[Array[(None,), Float32]] | None = Field(
4241
description="Linear inequality constraint rhs h for the quadratic program.",
4342
default=None,
4443
)
45-
solver_tol: Optional[Float32] = Field(
44+
solver_tol: Float32 | None = Field(
4645
description="Tolerance for the solver convergence.", default=1e-4
4746
)
48-
target_kappa: Optional[Float32] = Field(
47+
target_kappa: Float32 | None = Field(
4948
description="QPAX parameter for QP relaxation.", default=1e-3
5049
)
5150

examples/univariate/optimize.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ def rosenbrock_gradient(x: np.ndarray) -> np.ndarray:
3737
callback=lambda xs: trajectory.append(xs.tolist()),
3838
)
3939

40-
anim = make_animation(*list(zip(*trajectory)))
40+
anim = make_animation(*list(zip(*trajectory, strict=True)))
4141
# anim.save("rosenbrock_optimization.gif", writer="pillow", fps=2, dpi=150)
4242
plt.show()

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ filterwarnings = [
5959
"ignore:numpy.ufunc size changed",
6060
# sometimes, dependencies leak resources
6161
"ignore:.*socket\\.socket.*:pytest.PytestUnraisableExceptionWarning",
62+
"ignore:.*sqlite3\\.Connection.*:pytest.PytestUnraisableExceptionWarning",
6263
]
6364

6465
[tool.coverage.run]

ruff.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# Set to the lowest supported Python version.
2-
# !! tesseract_runtime still supports Python 3.9 !!
3-
target-version = "py39"
2+
target-version = "py310"
43

54
# Set the target line length for formatting.
65
line-length = 88

tesseract_core/runtime/array_encoding.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import re
55
from collections.abc import Sequence
66
from pathlib import Path
7-
from typing import Annotated, Any, Literal, Optional, Union, get_args
7+
from typing import Annotated, Any, Literal, get_args
88
from uuid import uuid4
99

1010
import numpy as np
@@ -45,8 +45,8 @@
4545
"complex128",
4646
]
4747
EllipsisType = type(Ellipsis)
48-
ArrayLike = Union[np.ndarray, np.number, np.bool_]
49-
ShapeType = Union[tuple[Optional[int], ...], EllipsisType]
48+
ArrayLike = np.ndarray | np.number | np.bool_
49+
ShapeType = tuple[int | None, ...] | EllipsisType
5050

5151
MAX_BINREF_BUFFER_SIZE = 100 * 1024 * 1024 # 100 MB
5252

@@ -94,12 +94,12 @@ class EncodedArrayModel(BaseModel):
9494
object_type: Literal["array"]
9595
shape: tuple[PositiveInt, ...]
9696
dtype: AllowedDtypes
97-
data: Union[BinrefArrayData, Base64ArrayData, JsonArrayData]
97+
data: BinrefArrayData | Base64ArrayData | JsonArrayData
9898
model_config = ConfigDict(extra="forbid")
9999

100100

101101
def get_array_model(
102-
expected_shape: ShapeType, expected_dtype: Optional[str], flags: Sequence[str]
102+
expected_shape: ShapeType, expected_dtype: str | None, flags: Sequence[str]
103103
) -> type[EncodedArrayModel]:
104104
"""Create a Pydantic model for an encoded array that does validation on the given expected shape and dtype."""
105105
if expected_dtype is None:
@@ -177,7 +177,7 @@ def get_array_model(
177177
),
178178
# Choose the appropriate data structure based on the encoding
179179
"data": (
180-
Union[BinrefArrayData, Base64ArrayData, JsonArrayData],
180+
BinrefArrayData | Base64ArrayData | JsonArrayData,
181181
Field(discriminator="encoding"),
182182
),
183183
"model_config": (ConfigDict, config),
@@ -203,12 +203,12 @@ def get_array_model(
203203

204204

205205
def _dump_binref_arraydict(
206-
arr: Union[np.ndarray, np.number, np.bool_],
207-
base_dir: Union[Path, str],
208-
subdir: Optional[Union[Path, str]],
206+
arr: np.ndarray | np.number | np.bool_,
207+
base_dir: Path | str,
208+
subdir: Path | str | None,
209209
current_binref_uuid: str,
210210
max_file_size: int = MAX_BINREF_BUFFER_SIZE,
211-
) -> tuple[dict[str, Union[str, dict[str, str]]], str]:
211+
) -> tuple[dict[str, str | dict[str, str]], str]:
212212
"""Dump array to json+binref encoded array dict.
213213
214214
Writes a .bin file and returns json encoded data.
@@ -243,8 +243,8 @@ def _dump_binref_arraydict(
243243

244244

245245
def _dump_base64_arraydict(
246-
arr: Union[np.ndarray, np.number, np.bool_],
247-
) -> dict[str, Union[str, dict[str, str]]]:
246+
arr: np.ndarray | np.number | np.bool_,
247+
) -> dict[str, str | dict[str, str]]:
248248
"""Dump array to json+base64 encoded array dict."""
249249
data = {
250250
"buffer": pybase64.b64encode(arr.tobytes()).decode(),
@@ -260,8 +260,8 @@ def _dump_base64_arraydict(
260260

261261

262262
def _dump_json_arraydict(
263-
arr: Union[np.ndarray, np.number, np.bool_],
264-
) -> dict[str, Union[str, dict[str, str]]]:
263+
arr: np.ndarray | np.number | np.bool_,
264+
) -> dict[str, str | dict[str, str]]:
265265
"""Dump array to json encoded array dict."""
266266
data = {
267267
"buffer": arr.tolist(),
@@ -282,7 +282,7 @@ def _load_base64_arraydict(val: dict) -> np.ndarray:
282282
return np.frombuffer(buffer, dtype=val["dtype"]).reshape(val["shape"])
283283

284284

285-
def _load_binref_arraydict(val: dict, base_dir: Union[str, Path, None]) -> np.ndarray:
285+
def _load_binref_arraydict(val: dict, base_dir: str | Path | None) -> np.ndarray:
286286
"""Load array from json+binref encoded array dict."""
287287
path_match = re.match(r"^(?P<path>.+?)(\:(?P<offset>\d+))?$", val["data"]["buffer"])
288288
if not path_match:
@@ -316,7 +316,7 @@ def _load_binref_arraydict(val: dict, base_dir: Union[str, Path, None]) -> np.nd
316316

317317

318318
def _coerce_shape_dtype(
319-
arr: ArrayLike, expected_shape: ShapeType, expected_dtype: Optional[str]
319+
arr: ArrayLike, expected_shape: ShapeType, expected_dtype: str | None
320320
) -> ArrayLike:
321321
"""Coerce the shape and dtype of the passed array to the expected values."""
322322
if expected_shape is Ellipsis:
@@ -363,7 +363,7 @@ def _coerce_shape_dtype(
363363

364364

365365
def python_to_array(
366-
val: Any, expected_shape: ShapeType, expected_dtype: Optional[str]
366+
val: Any, expected_shape: ShapeType, expected_dtype: str | None
367367
) -> ArrayLike:
368368
"""Convert a Python object to a NumPy array."""
369369
val = np.asarray(val, order="C")
@@ -380,7 +380,7 @@ def decode_array(
380380
val: EncodedArrayModel,
381381
info: ValidationInfo,
382382
expected_shape: ShapeType,
383-
expected_dtype: Optional[str],
383+
expected_dtype: str | None,
384384
) -> ArrayLike:
385385
"""Decode an EncodedArrayModel to a NumPy array."""
386386
from tesseract_core.runtime.config import get_config
@@ -422,8 +422,8 @@ def decode_array(
422422

423423

424424
def encode_array(
425-
arr: ArrayLike, info: Any, expected_shape: ShapeType, expected_dtype: Optional[str]
426-
) -> Union[EncodedArrayModel, ArrayLike]:
425+
arr: ArrayLike, info: Any, expected_shape: ShapeType, expected_dtype: str | None
426+
) -> EncodedArrayModel | ArrayLike:
427427
"""Encode a NumPy array as an EncodedArrayModel."""
428428
from tesseract_core.runtime.config import get_config
429429

tesseract_core/runtime/cli.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
Annotated,
1717
Any,
1818
Literal,
19-
Optional,
2019
get_args,
2120
get_origin,
2221
)
@@ -219,23 +218,23 @@ def check_gradients(
219218
),
220219
],
221220
input_paths: Annotated[
222-
Optional[list[str]],
221+
list[str] | None,
223222
typer.Option(
224223
"--input-paths",
225224
help="Paths to differentiable inputs to check gradients for.",
226225
show_default="check all",
227226
),
228227
] = None,
229228
output_paths: Annotated[
230-
Optional[list[str]],
229+
list[str] | None,
231230
typer.Option(
232231
"--output-paths",
233232
help="Paths to differentiable outputs to check gradients for.",
234233
show_default="check all",
235234
),
236235
] = None,
237236
endpoints: Annotated[
238-
Optional[list[str]],
237+
list[str] | None,
239238
typer.Option(
240239
"--endpoints",
241240
help="Endpoints to check gradients for.",
@@ -275,7 +274,7 @@ def check_gradients(
275274
),
276275
] = 10,
277276
seed: Annotated[
278-
Optional[int],
277+
int | None,
279278
typer.Option(
280279
"--seed",
281280
help="Seed for random number generator. If not set, a random seed is used.",
@@ -367,7 +366,7 @@ def serve(
367366

368367

369368
def _create_user_defined_cli_command(
370-
app: typer.Typer, user_function: Callable, out_stream: Optional[io.TextIOBase]
369+
app: typer.Typer, user_function: Callable, out_stream: io.TextIOBase | None
371370
) -> None:
372371
"""Creates a click command which sends requests to Tesseract endpoints.
373372
@@ -459,9 +458,7 @@ def command_func():
459458
decorator(command_func)
460459

461460

462-
def _add_user_commands_to_cli(
463-
app: typer.Typer, out_stream: Optional[io.IOBase]
464-
) -> None:
461+
def _add_user_commands_to_cli(app: typer.Typer, out_stream: io.IOBase | None) -> None:
465462
tesseract_package = get_tesseract_api()
466463
endpoints = create_endpoints(tesseract_package)
467464

0 commit comments

Comments
 (0)