From 24a336727471d7992af20805b3075cccf94d7ca2 Mon Sep 17 00:00:00 2001 From: Steve Wooster Date: Fri, 14 Nov 2025 16:44:22 -0800 Subject: [PATCH 1/4] Make wheels for Python 3.11-3.14. --- .github/workflows/build-wheels.sh | 2 +- .github/workflows/python.yml | 2 +- .github/workflows/release.yaml | 4 ++-- .github/workflows/rust.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-wheels.sh b/.github/workflows/build-wheels.sh index d49081c..11bfe42 100644 --- a/.github/workflows/build-wheels.sh +++ b/.github/workflows/build-wheels.sh @@ -3,7 +3,7 @@ set -euxo pipefail nullglob -for PY in cp38-cp38 cp39-cp39 cp310-cp310; do +for PY in cp38-cp38 cp39-cp39 cp310-cp310 cp311-cp311 cp312-cp312 cp313-cp313 cp314-cp314; do PYBIN="/opt/python/${PY}/bin" "${PYBIN}/pip" install maturin "${PYBIN}/maturin" build -i "${PYBIN}/python" --release diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index f06385c..fd84011 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v2 with: - python-version: '3.10' + python-version: '3.14' - uses: abatilo/actions-poetry@v2.1.4 - run: poetry install - run: poetry run black --check --verbose . \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b8a1d1a..156d52a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -26,7 +26,7 @@ jobs: runs-on: macos-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 @@ -75,7 +75,7 @@ jobs: - uses: actions/download-artifact@v2 # downloads all artifacts into subdir of current directory named artifact name - uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: 3.14 - name: Publish env: MATURIN_PASSWORD: ${{ secrets.MATURIN_PASSWORD }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 80e9423..52e596c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -90,7 +90,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v2 with: - python-version: '3.10' + python-version: '3.14' - uses: abatilo/actions-poetry@v2.1.4 - run: poetry install - run: poetry run maturin develop --release From 84898d348fe974f2ec318d1755733d064692725a Mon Sep 17 00:00:00 2001 From: Steve Wooster Date: Fri, 14 Nov 2025 16:59:07 -0800 Subject: [PATCH 2/4] Attempting to update poetry too to see if it fixes CI. --- .github/workflows/python.yml | 2 +- .github/workflows/rust.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index fd84011..bf4f615 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -12,6 +12,6 @@ jobs: - uses: actions/setup-python@v2 with: python-version: '3.14' - - uses: abatilo/actions-poetry@v2.1.4 + - uses: abatilo/actions-poetry@v3.0.2 - run: poetry install - run: poetry run black --check --verbose . \ No newline at end of file diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 52e596c..135b1b8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -91,7 +91,7 @@ jobs: - uses: actions/setup-python@v2 with: python-version: '3.14' - - uses: abatilo/actions-poetry@v2.1.4 + - uses: abatilo/actions-poetry@v3.0.2 - run: poetry install - run: poetry run maturin develop --release - run: poetry run pytest From 9c1b3b265dae99121c7aa0c9e04b266c52fe2c76 Mon Sep 17 00:00:00 2001 From: Steve Wooster Date: Fri, 14 Nov 2025 17:50:09 -0800 Subject: [PATCH 3/4] Attempt to upgrade PyO3 from 0.20.0 to 0.27.1 --- Cargo.toml | 2 +- src/python_api.rs | 138 ++++++++++++++++++++++------------------------ 2 files changed, 66 insertions(+), 74 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a5976f5..5ad74e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ publish = false lazy_static = "1.4.0" thiserror = "1.0.30" smallvec = "1.8.0" -pyo3 = {version = "0.20.0", features = ["extension-module"], optional = true} +pyo3 = {version = "0.27.1", features = ["extension-module"], optional = true} quickcheck = {version = "1.0.3", optional = true} serde = {version = "1.0", features = ["derive"], optional = true} diff --git a/src/python_api.rs b/src/python_api.rs index 2546419..49d21c8 100644 --- a/src/python_api.rs +++ b/src/python_api.rs @@ -1,83 +1,75 @@ -#![allow(clippy::borrow_deref_ref)] // TODO: broken clippy lint? - // Copyright 2021-2024 SecureDNA Stiftung (SecureDNA Foundation) - // SPDX-License-Identifier: MIT OR Apache-2.0 +// Copyright 2021-2024 SecureDNA Stiftung (SecureDNA Foundation) +// SPDX-License-Identifier: MIT OR Apache-2.0 -use pyo3::{exceptions::PyValueError, prelude::*, types::PyBytes}; +#[pyo3::pymodule] +mod quickdna { -use crate::{ - errors::TranslationError, - trans_table::{reverse_complement_bytes, TranslationTable}, - Nucleotide, NucleotideAmbiguous, -}; + use pyo3::{exceptions::PyValueError, prelude::*, types::PyBytes}; -impl From for PyErr { - fn from(err: TranslationError) -> PyErr { - PyValueError::new_err(err.to_string()) - } -} - -#[pyfunction] -fn _check_table(table: u8) -> PyResult<()> { - let _ = TranslationTable::try_from(table)?; - Ok(()) -} + use crate::{ + errors::TranslationError, + trans_table::{reverse_complement_bytes, TranslationTable}, + Nucleotide, NucleotideAmbiguous, + }; -/// Translate a bytestring of DNA nucleotides into a bytestring of amino acids. -/// -/// The input string is allowed to contain IUPAC ambiguity codes; ambiguous -/// codons are represented by `X` in the output. -/// -/// * `translate(b"CCNTACACK CATNCNAAT")` returns `b"PYTHXN"` -#[pyfunction] -fn _translate(py: Python, table: u8, dna: &PyBytes) -> PyResult { - let table = TranslationTable::try_from(table)?; - let bytes = table.translate_dna_bytes::(dna.as_bytes())?; - Ok(PyBytes::new(py, &bytes).into()) -} + impl From for PyErr { + fn from(err: TranslationError) -> PyErr { + PyValueError::new_err(err.to_string()) + } + } -/// Translate a bytestring of DNA nucleotides into a bytestring of amino acids. -/// -/// The input string is validated to consist of unambiguous nucleotides (no IUPAC ambiguity codes). -/// -/// * `translate_strict(b"AAACCCTTTGGG")` returns `b"KPFG"` -/// * `translate_strict(b"AAACCCTTTGGN")` is an error. -#[pyfunction] -fn _translate_strict(py: Python, table: u8, dna: &PyBytes) -> PyResult { - let table = TranslationTable::try_from(table)?; - let bytes = table.translate_dna_bytes::(dna.as_bytes())?; - Ok(PyBytes::new(py, &bytes).into()) -} + #[pyfunction] + fn _check_table(table: u8) -> PyResult<()> { + let _ = TranslationTable::try_from(table)?; + Ok(()) + } -/// Get the reverse complement of a bytestring of DNA nucleotides. -/// -/// The input string is allowed to contain IUPAC ambiguity codes. -/// -/// * `reverse_complement(b"AAAAABCCC")` returns `b"GGGVTTTTT"` -#[pyfunction] -fn _reverse_complement(py: Python, dna: &PyBytes) -> PyResult { - let bytes = reverse_complement_bytes::(dna.as_bytes())?; - Ok(PyBytes::new(py, &bytes).into()) -} + /// Translate a bytestring of DNA nucleotides into a bytestring of amino acids. + /// + /// The input string is allowed to contain IUPAC ambiguity codes; ambiguous + /// codons are represented by `X` in the output. + /// + /// * `translate(b"CCNTACACK CATNCNAAT")` returns `b"PYTHXN"` + #[pyfunction] + fn _translate(py: Python, table: u8, dna: Bound<'_, PyBytes>) -> PyResult> { + let table = TranslationTable::try_from(table)?; + let bytes = table.translate_dna_bytes::(dna.as_bytes())?; + Ok(PyBytes::new(py, &bytes).into()) + } -/// Get the reverse complement of a bytestring of DNA nucleotides. -/// -/// The input string is validated to consist of unambiguous nucleotides (no IUPAC ambiguity codes). -/// -/// * `reverse_complement_strict(b"AAAAAACCC")` returns `b"GGGTTTTTT"` -/// * `reverse_complement_strict(b"AAAAAACCN")` is an error. -#[pyfunction] -fn _reverse_complement_strict(py: Python, dna: &PyBytes) -> PyResult { - let bytes = reverse_complement_bytes::(dna.as_bytes())?; - Ok(PyBytes::new(py, &bytes).into()) -} + /// Translate a bytestring of DNA nucleotides into a bytestring of amino acids. + /// + /// The input string is validated to consist of unambiguous nucleotides (no IUPAC ambiguity codes). + /// + /// * `translate_strict(b"AAACCCTTTGGG")` returns `b"KPFG"` + /// * `translate_strict(b"AAACCCTTTGGN")` is an error. + #[pyfunction] + fn _translate_strict(py: Python, table: u8, dna: Bound<'_, PyBytes>) -> PyResult> { + let table = TranslationTable::try_from(table)?; + let bytes = table.translate_dna_bytes::(dna.as_bytes())?; + Ok(PyBytes::new(py, &bytes).into()) + } -#[pymodule] -fn quickdna(_py: Python, m: &PyModule) -> PyResult<()> { - m.add_function(wrap_pyfunction!(_check_table, m)?)?; - m.add_function(wrap_pyfunction!(_translate, m)?)?; - m.add_function(wrap_pyfunction!(_translate_strict, m)?)?; - m.add_function(wrap_pyfunction!(_reverse_complement, m)?)?; - m.add_function(wrap_pyfunction!(_reverse_complement_strict, m)?)?; + /// Get the reverse complement of a bytestring of DNA nucleotides. + /// + /// The input string is allowed to contain IUPAC ambiguity codes. + /// + /// * `reverse_complement(b"AAAAABCCC")` returns `b"GGGVTTTTT"` + #[pyfunction] + fn _reverse_complement(py: Python, dna: Bound<'_, PyBytes>) -> PyResult> { + let bytes = reverse_complement_bytes::(dna.as_bytes())?; + Ok(PyBytes::new(py, &bytes).into()) + } - Ok(()) + /// Get the reverse complement of a bytestring of DNA nucleotides. + /// + /// The input string is validated to consist of unambiguous nucleotides (no IUPAC ambiguity codes). + /// + /// * `reverse_complement_strict(b"AAAAAACCC")` returns `b"GGGTTTTTT"` + /// * `reverse_complement_strict(b"AAAAAACCN")` is an error. + #[pyfunction] + fn _reverse_complement_strict(py: Python, dna: Bound<'_, PyBytes>) -> PyResult> { + let bytes = reverse_complement_bytes::(dna.as_bytes())?; + Ok(PyBytes::new(py, &bytes).into()) + } } From 2b42f7c4c5916b4c76f17160d26e18496a292f47 Mon Sep 17 00:00:00 2001 From: Steve Wooster Date: Fri, 14 Nov 2025 19:00:53 -0800 Subject: [PATCH 4/4] Update min python version --- .github/workflows/build-wheels.sh | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-wheels.sh b/.github/workflows/build-wheels.sh index 11bfe42..72dd683 100644 --- a/.github/workflows/build-wheels.sh +++ b/.github/workflows/build-wheels.sh @@ -3,7 +3,7 @@ set -euxo pipefail nullglob -for PY in cp38-cp38 cp39-cp39 cp310-cp310 cp311-cp311 cp312-cp312 cp313-cp313 cp314-cp314; do +for PY in cp310-cp310 cp311-cp311 cp312-cp312; do PYBIN="/opt/python/${PY}/bin" "${PYBIN}/pip" install maturin "${PYBIN}/maturin" build -i "${PYBIN}/python" --release diff --git a/pyproject.toml b/pyproject.toml index 598901a..b686901 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,10 +9,10 @@ repository = "https://github.com/SecureDNA/quickdna" [tool.poetry.dependencies] -python = "^3.8" +python = "^3.10" [tool.poetry.dev-dependencies] -pytest = "^6.1" +pytest = "^9.0" pylint = "^2.6" flake8 = "^3.9" wheel = "*"