- workflow updates
PyTest / pytest (3.12) (push) Failing after 17s
PyTest / pytest (3.14) (push) Failing after 22s
PyTest / pytest (3.10) (push) Has been cancelled

- python stubs
This commit is contained in:
Wim Pomp
2026-05-09 13:00:47 +02:00
parent 2fc0bf8c9f
commit 91f863366d
11 changed files with 208 additions and 7 deletions
+52
View File
@@ -0,0 +1,52 @@
name: PyTest
on: [push, pull_request, workflow_call]
jobs:
pytest:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.12", "3.14"]
steps:
- uses: actions/checkout@v6
- name: Restore cache
uses: actions/cache/restore@v4
with:
path: |
~/.cache/pip
~/.cache/pip-wheel
~/.cache/sccache
~/.cache/cargo-xwin
~/.cargo
~/.osxcross
key: cache-ubuntu-maturin-cross-compile
- name: Install Rust
run: |
export PATH="$HOME/.cargo/bin:$PATH"
if ! command -v rustc >/dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
else
rustup update
fi
shell: bash
- name: "cargo test"
run: |-
export PATH="$HOME/.cargo/bin:$PATH"
cargo test
- name: Store cache
uses: actions/cache/save@v4
with:
path: |
~/.cache/pip
~/.cache/pip-wheel
~/.cache/sccache
~/.cache/cargo-xwin
~/.cargo
~/.osxcross
key: cache-ubuntu-maturin-cross-compile
+4 -2
View File
@@ -8,8 +8,10 @@ permissions:
jobs: jobs:
publish_pytest: publish_pytest:
uses: ./.github/workflows/pytest.yml uses: ./.github/workflows/pytest.yml
publish_cargo_test:
uses: ./.github/workflows/cargo_test.yml
crates_io_publish: crates_io_publish:
needs: [ publish_pytest ] needs: [ publish_pytest, publish_cargo_test ]
name: Publish (crates.io) name: Publish (crates.io)
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 25 timeout-minutes: 25
@@ -50,7 +52,7 @@ jobs:
# have passed. # have passed.
- name: "cargo release publish" - name: "cargo release publish"
run: |- run: |-
export PATH="$HOME/.osxcross/bin:$PATH" export PATH="$HOME/.cargo/bin:$PATH"
cargo login ${{ secrets.CRATES_IO_API_TOKEN }} cargo login ${{ secrets.CRATES_IO_API_TOKEN }}
cargo release \ cargo release \
publish \ publish \
+3 -1
View File
@@ -8,8 +8,10 @@ permissions:
jobs: jobs:
publish_pytest: publish_pytest:
uses: ./.github/workflows/pytest.yml uses: ./.github/workflows/pytest.yml
publish_cargo_test:
uses: ./.github/workflows/cargo_test.yml
pypi_publish: pypi_publish:
needs: [ publish_pytest ] needs: [ publish_pytest, publish_cargo_test ]
name: Publish (pypi.org) name: Publish (pypi.org)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
run: | run: |
export PATH="$HOME/.cargo/bin:$PATH" export PATH="$HOME/.cargo/bin:$PATH"
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install maturin ziglang pip install maturin
if ! command -v sccache >/dev/null 2>&1; then if ! command -v sccache >/dev/null 2>&1; then
cargo install sccache || pip install sccache cargo install sccache || pip install sccache
fi fi
+2 -1
View File
@@ -29,10 +29,11 @@ ndarray = "0.17"
num = "0.4" num = "0.4"
numpy = { version = "0.28", optional = true } numpy = { version = "0.28", optional = true }
pyo3 = { version = "0.28", features = ["abi3-py310", "eyre", "generate-import-lib", "multiple-pymethods"], optional = true } pyo3 = { version = "0.28", features = ["abi3-py310", "eyre", "generate-import-lib", "multiple-pymethods"], optional = true }
pyo3-stub-gen = { version = "0.22", optional = true }
rayon = "1" rayon = "1"
thiserror = "2" thiserror = "2"
tokio = { version = "1", features = ["fs", "rt", "rt-multi-thread", "time"] } tokio = { version = "1", features = ["fs", "rt", "rt-multi-thread", "time"] }
zstd = "0.13" zstd = "0.13"
[features] [features]
python = ["dep:pyo3", "dep:numpy", "dep:color-eyre"] python = ["dep:pyo3", "dep:numpy", "dep:color-eyre", "dep:pyo3-stub-gen"]
+1 -1
View File
@@ -1,4 +1,4 @@
[![pytest](https://github.com/wimpomp/tiffwrite/actions/workflows/pytest.yml/badge.svg)](https://github.com/wimpomp/tiffwrite/actions/workflows/pytest.yml) [![pytest](https://git.wimpomp.nl/wim/tiffwrite/actions/workflows/pytest.yml/badge.svg)](https://git.wimpomp.nl/wim/tiffwrite/actions/workflows/pytest.yml)
# Tiffwrite # Tiffwrite
Write BioFormats/ImageJ compatible tiffs with zstd compression in parallel using Rust. Write BioFormats/ImageJ compatible tiffs with zstd compression in parallel using Rust.
+15 -1
View File
@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import sys
from importlib.metadata import version from importlib.metadata import version
from itertools import product from itertools import product
from pathlib import Path from pathlib import Path
@@ -10,7 +11,7 @@ import numpy as np
from numpy.typing import ArrayLike, DTypeLike from numpy.typing import ArrayLike, DTypeLike
from tqdm.auto import tqdm from tqdm.auto import tqdm
from . import tiffwrite_rs as rs # noqa from . import tiffwrite_rs as rs
__all__ = ["IJTiffFile", "IJTiffParallel", "FrameInfo", "Tag", "tiffwrite"] __all__ = ["IJTiffFile", "IJTiffParallel", "FrameInfo", "Tag", "tiffwrite"]
@@ -236,3 +237,16 @@ try:
except ImportError: except ImportError:
IJTiffParallel = None IJTiffParallel = None
def tiffwrite_generate_stub():
if len(sys.argv) > 1:
path = Path(sys.argv[1]).resolve()
else:
path = Path.cwd().resolve()
if (path / "py" / "tiffwrite" / "__init__.py").exists():
rs.generate_stub(str(path)) # noqa
(path / "py" / "tiffwrite_rs" / "__init__.pyi").rename(path / "py" / "tiffwrite" / "tiffwrite_rs.pyi")
(path / "py" / "tiffwrite_rs").rmdir()
else:
raise ModuleNotFoundError(str(path / "py" / "tiffwrite" / "__init__.py"))
View File
+101
View File
@@ -0,0 +1,101 @@
# This file is automatically generated by pyo3_stub_gen
# ruff: noqa: E501, F401, F403, F405
import builtins
import typing
import numpy
import numpy.typing
__all__ = [
"IJTiffFile",
"Tag",
]
class IJTiffFile:
@property
def colors(self) -> typing.Optional[builtins.list[builtins.list[builtins.int]]]: ...
@colors.setter
def colors(self, value: typing.Sequence[builtins.str]) -> None: ...
@property
def colormap(self) -> typing.Optional[builtins.list[builtins.list[builtins.int]]]: ...
@colormap.setter
def colormap(self, value: builtins.str) -> None: ...
@property
def px_size(self) -> typing.Optional[builtins.float]: ...
@px_size.setter
def px_size(self, value: builtins.float) -> None: ...
@property
def delta_z(self) -> typing.Optional[builtins.float]: ...
@delta_z.setter
def delta_z(self, value: builtins.float) -> None: ...
@property
def time_interval(self) -> typing.Optional[builtins.float]: ...
@time_interval.setter
def time_interval(self, value: builtins.float) -> None: ...
@property
def comment(self) -> typing.Optional[builtins.str]: ...
@comment.setter
def comment(self, value: builtins.str) -> None: ...
def __new__(cls, path: builtins.str) -> IJTiffFile: ...
def set_compression(self, compression: builtins.int, level: builtins.int) -> None:
r"""
set zstd compression level: -7 ..= 22
"""
def append_extra_tag(
self, tag: Tag, czt: typing.Optional[tuple[builtins.int, builtins.int, builtins.int]] = None
) -> None: ...
def get_tags(
self, czt: typing.Optional[tuple[builtins.int, builtins.int, builtins.int]] = None
) -> builtins.list[Tag]: ...
def close(self) -> None: ...
def save_f64(self, frame: numpy.typing.ArrayLike, c: builtins.int, t: builtins.int, z: builtins.int) -> None: ...
def save_u32(self, frame: numpy.typing.ArrayLike, c: builtins.int, t: builtins.int, z: builtins.int) -> None: ...
def save_u16(self, frame: numpy.typing.ArrayLike, c: builtins.int, t: builtins.int, z: builtins.int) -> None: ...
def save_i64(self, frame: numpy.typing.ArrayLike, c: builtins.int, t: builtins.int, z: builtins.int) -> None: ...
def save_f32(self, frame: numpy.typing.ArrayLike, c: builtins.int, t: builtins.int, z: builtins.int) -> None: ...
def save_u8(self, frame: numpy.typing.ArrayLike, c: builtins.int, t: builtins.int, z: builtins.int) -> None: ...
def save_i32(self, frame: numpy.typing.ArrayLike, c: builtins.int, t: builtins.int, z: builtins.int) -> None: ...
def save_u64(self, frame: numpy.typing.ArrayLike, c: builtins.int, t: builtins.int, z: builtins.int) -> None: ...
def save_i16(self, frame: numpy.typing.ArrayLike, c: builtins.int, t: builtins.int, z: builtins.int) -> None: ...
def save_i8(self, frame: numpy.typing.ArrayLike, c: builtins.int, t: builtins.int, z: builtins.int) -> None: ...
class Tag:
@staticmethod
def byte(code: builtins.int, byte: typing.Sequence[builtins.int]) -> Tag: ...
@staticmethod
def ascii(code: builtins.int, ascii: builtins.str) -> Tag: ...
@staticmethod
def short(code: builtins.int, short: typing.Sequence[builtins.int]) -> Tag: ...
@staticmethod
def long(code: builtins.int, long: typing.Sequence[builtins.int]) -> Tag: ...
@staticmethod
def rational(code: builtins.int, rational: typing.Sequence[builtins.float]) -> Tag: ...
@staticmethod
def sbyte(code: builtins.int, sbyte: typing.Sequence[builtins.int]) -> Tag: ...
@staticmethod
def sshort(code: builtins.int, sshort: typing.Sequence[builtins.int]) -> Tag: ...
@staticmethod
def slong(code: builtins.int, slong: typing.Sequence[builtins.int]) -> Tag: ...
@staticmethod
def srational(code: builtins.int, srational: typing.Sequence[builtins.float]) -> Tag: ...
@staticmethod
def float(code: builtins.int, float: typing.Sequence[builtins.float]) -> Tag: ...
@staticmethod
def double(code: builtins.int, double: typing.Sequence[builtins.float]) -> Tag: ...
@staticmethod
def ifd(code: builtins.int, ifd: typing.Sequence[builtins.int]) -> Tag: ...
@staticmethod
def unicode(code: builtins.int, unicode: builtins.str) -> Tag: ...
@staticmethod
def complex(code: builtins.int, complex: typing.Sequence[tuple[builtins.float, builtins.float]]) -> Tag: ...
@staticmethod
def long8(code: builtins.int, long8: typing.Sequence[builtins.int]) -> Tag: ...
@staticmethod
def slong8(code: builtins.int, slong8: typing.Sequence[builtins.int]) -> Tag: ...
@staticmethod
def ifd8(code: builtins.int, ifd8: typing.Sequence[builtins.int]) -> Tag: ...
def count(self) -> builtins.int:
r"""
get the number of values in the tag
"""
+3
View File
@@ -33,6 +33,9 @@ test = ["pytest", "tifffile", "imagecodecs"]
homepage = "https://github.com/wimpomp/tiffwrite" homepage = "https://github.com/wimpomp/tiffwrite"
repository = "https://github.com/wimpomp/tiffwrite" repository = "https://github.com/wimpomp/tiffwrite"
[project.scripts]
tiffwrite_generate_stub = "tiffwrite:tiffwrite_generate_stub"
[tool.maturin] [tool.maturin]
python-source = "py" python-source = "py"
features = ["pyo3/extension-module", "python"] features = ["pyo3/extension-module", "python"]
+26
View File
@@ -3,6 +3,9 @@ use num::{Complex, FromPrimitive, Rational32};
use numpy::{AllowTypeChange, PyArrayLike2}; use numpy::{AllowTypeChange, PyArrayLike2};
use pyo3::exceptions::PyValueError; use pyo3::exceptions::PyValueError;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
use pyo3_stub_gen::{StubGenConfig, StubInfo};
use std::path::PathBuf;
impl From<crate::error::Error> for PyErr { impl From<crate::error::Error> for PyErr {
fn from(err: crate::error::Error) -> PyErr { fn from(err: crate::error::Error) -> PyErr {
@@ -10,6 +13,7 @@ impl From<crate::error::Error> for PyErr {
} }
} }
#[gen_stub_pyclass]
#[pyclass(name = "Tag", module = "tiffwrite_rs", subclass, from_py_object)] #[pyclass(name = "Tag", module = "tiffwrite_rs", subclass, from_py_object)]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct PyTag { struct PyTag {
@@ -17,6 +21,7 @@ struct PyTag {
} }
/// Tiff tag, use one of the constructors to get a tag of a specific type /// Tiff tag, use one of the constructors to get a tag of a specific type
#[gen_stub_pymethods]
#[pymethods] #[pymethods]
impl PyTag { impl PyTag {
#[staticmethod] #[staticmethod]
@@ -162,12 +167,14 @@ impl PyTag {
} }
} }
#[gen_stub_pyclass]
#[pyclass(name = "IJTiffFile", module = "tiffwrite_rs", subclass)] #[pyclass(name = "IJTiffFile", module = "tiffwrite_rs", subclass)]
#[derive(Debug)] #[derive(Debug)]
struct PyIJTiffFile { struct PyIJTiffFile {
ijtifffile: Option<IJTiffFile>, ijtifffile: Option<IJTiffFile>,
} }
#[gen_stub_pymethods]
#[pymethods] #[pymethods]
impl PyIJTiffFile { impl PyIJTiffFile {
#[new] #[new]
@@ -333,10 +340,12 @@ impl PyIJTiffFile {
macro_rules! impl_save { macro_rules! impl_save {
($($T:ty: $t:ident $(,)?)*) => { ($($T:ty: $t:ident $(,)?)*) => {
$( $(
#[gen_stub_pymethods]
#[pymethods] #[pymethods]
impl PyIJTiffFile { impl PyIJTiffFile {
fn $t( fn $t(
&mut self, &mut self,
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy", "numpy.typing")))]
frame: PyArrayLike2<$T, AllowTypeChange>, frame: PyArrayLike2<$T, AllowTypeChange>,
c: usize, c: usize,
t: usize, t: usize,
@@ -365,11 +374,28 @@ impl_save! {
f64: save_f64, f64: save_f64,
} }
/// generates tiffwrite/tiffwrite_rs.pyi
#[pyfunction]
fn generate_stub(dest_path: String) -> PyResult<()> {
StubInfo::from_project_root(
"tiffwrite_rs".to_string(),
PathBuf::from(dest_path).join("py"),
true,
StubGenConfig::default(),
)
.map_err(|e| PyValueError::new_err(format!("{:?}", e)))?
.generate()
.map_err(|e| PyValueError::new_err(format!("{:?}", e)))
}
#[pymodule] #[pymodule]
#[pyo3(name = "tiffwrite_rs")] #[pyo3(name = "tiffwrite_rs")]
mod tiffwrite_rs { mod tiffwrite_rs {
use pyo3::prelude::*; use pyo3::prelude::*;
#[pymodule_export]
use super::generate_stub;
#[pymodule_export] #[pymodule_export]
use super::PyTag; use super::PyTag;