- workflow updates
- python stubs
This commit is contained in:
@@ -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
|
||||
@@ -8,8 +8,10 @@ permissions:
|
||||
jobs:
|
||||
publish_pytest:
|
||||
uses: ./.github/workflows/pytest.yml
|
||||
publish_cargo_test:
|
||||
uses: ./.github/workflows/cargo_test.yml
|
||||
crates_io_publish:
|
||||
needs: [ publish_pytest ]
|
||||
needs: [ publish_pytest, publish_cargo_test ]
|
||||
name: Publish (crates.io)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
@@ -50,7 +52,7 @@ jobs:
|
||||
# have passed.
|
||||
- name: "cargo release publish"
|
||||
run: |-
|
||||
export PATH="$HOME/.osxcross/bin:$PATH"
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
cargo login ${{ secrets.CRATES_IO_API_TOKEN }}
|
||||
cargo release \
|
||||
publish \
|
||||
|
||||
@@ -8,8 +8,10 @@ permissions:
|
||||
jobs:
|
||||
publish_pytest:
|
||||
uses: ./.github/workflows/pytest.yml
|
||||
publish_cargo_test:
|
||||
uses: ./.github/workflows/cargo_test.yml
|
||||
pypi_publish:
|
||||
needs: [ publish_pytest ]
|
||||
needs: [ publish_pytest, publish_cargo_test ]
|
||||
name: Publish (pypi.org)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
run: |
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
python -m pip install --upgrade pip
|
||||
pip install maturin ziglang
|
||||
pip install maturin
|
||||
if ! command -v sccache >/dev/null 2>&1; then
|
||||
cargo install sccache || pip install sccache
|
||||
fi
|
||||
|
||||
+2
-1
@@ -29,10 +29,11 @@ ndarray = "0.17"
|
||||
num = "0.4"
|
||||
numpy = { version = "0.28", 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"
|
||||
thiserror = "2"
|
||||
tokio = { version = "1", features = ["fs", "rt", "rt-multi-thread", "time"] }
|
||||
zstd = "0.13"
|
||||
|
||||
[features]
|
||||
python = ["dep:pyo3", "dep:numpy", "dep:color-eyre"]
|
||||
python = ["dep:pyo3", "dep:numpy", "dep:color-eyre", "dep:pyo3-stub-gen"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[](https://github.com/wimpomp/tiffwrite/actions/workflows/pytest.yml)
|
||||
[](https://git.wimpomp.nl/wim/tiffwrite/actions/workflows/pytest.yml)
|
||||
|
||||
# Tiffwrite
|
||||
Write BioFormats/ImageJ compatible tiffs with zstd compression in parallel using Rust.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from importlib.metadata import version
|
||||
from itertools import product
|
||||
from pathlib import Path
|
||||
@@ -10,7 +11,7 @@ import numpy as np
|
||||
from numpy.typing import ArrayLike, DTypeLike
|
||||
from tqdm.auto import tqdm
|
||||
|
||||
from . import tiffwrite_rs as rs # noqa
|
||||
from . import tiffwrite_rs as rs
|
||||
|
||||
__all__ = ["IJTiffFile", "IJTiffParallel", "FrameInfo", "Tag", "tiffwrite"]
|
||||
|
||||
@@ -236,3 +237,16 @@ try:
|
||||
|
||||
except ImportError:
|
||||
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"))
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
@@ -33,6 +33,9 @@ test = ["pytest", "tifffile", "imagecodecs"]
|
||||
homepage = "https://github.com/wimpomp/tiffwrite"
|
||||
repository = "https://github.com/wimpomp/tiffwrite"
|
||||
|
||||
[project.scripts]
|
||||
tiffwrite_generate_stub = "tiffwrite:tiffwrite_generate_stub"
|
||||
|
||||
[tool.maturin]
|
||||
python-source = "py"
|
||||
features = ["pyo3/extension-module", "python"]
|
||||
|
||||
@@ -3,6 +3,9 @@ use num::{Complex, FromPrimitive, Rational32};
|
||||
use numpy::{AllowTypeChange, PyArrayLike2};
|
||||
use pyo3::exceptions::PyValueError;
|
||||
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 {
|
||||
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)]
|
||||
#[derive(Clone, Debug)]
|
||||
struct PyTag {
|
||||
@@ -17,6 +21,7 @@ struct PyTag {
|
||||
}
|
||||
|
||||
/// Tiff tag, use one of the constructors to get a tag of a specific type
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl PyTag {
|
||||
#[staticmethod]
|
||||
@@ -162,12 +167,14 @@ impl PyTag {
|
||||
}
|
||||
}
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass(name = "IJTiffFile", module = "tiffwrite_rs", subclass)]
|
||||
#[derive(Debug)]
|
||||
struct PyIJTiffFile {
|
||||
ijtifffile: Option<IJTiffFile>,
|
||||
}
|
||||
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl PyIJTiffFile {
|
||||
#[new]
|
||||
@@ -333,10 +340,12 @@ impl PyIJTiffFile {
|
||||
macro_rules! impl_save {
|
||||
($($T:ty: $t:ident $(,)?)*) => {
|
||||
$(
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl PyIJTiffFile {
|
||||
fn $t(
|
||||
&mut self,
|
||||
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy", "numpy.typing")))]
|
||||
frame: PyArrayLike2<$T, AllowTypeChange>,
|
||||
c: usize,
|
||||
t: usize,
|
||||
@@ -365,11 +374,28 @@ impl_save! {
|
||||
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]
|
||||
#[pyo3(name = "tiffwrite_rs")]
|
||||
mod tiffwrite_rs {
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pymodule_export]
|
||||
use super::generate_stub;
|
||||
|
||||
#[pymodule_export]
|
||||
use super::PyTag;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user