From 34ae56464849811c9d7636d8f6179cde871018b7 Mon Sep 17 00:00:00 2001 From: Spill-Tea Date: Fri, 26 Sep 2025 01:51:38 -0700 Subject: [PATCH 1/4] ci(workflows.python-app): Matrix os in unit testing CI workflow. --- .github/workflows/python-app.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 3f76adc..cd7bfb8 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -36,13 +36,15 @@ jobs: run: tox -e type - name: Confirm Documentation Builds + if: always() run: tox -e docs tests: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [ "3.11", "3.12" ] + os: [ ubuntu-latest, macos-latest, windows-latest ] + python-version: [ "3.11", "3.12", "3.13" ] steps: - name: Checkout DesignerDNA Project @@ -58,7 +60,16 @@ jobs: python -m pip install -U pip pip install wheel setuptools pip install tox - echo "TOX_VERSION=py$(echo ${{ matrix.python-version }} | tr -d .)-tests" >> $GITHUB_ENV + + - name: Setup tox python version + if: matrix.os != 'windows-latest' + run: | + echo "TOX_VERSION=py$(echo ${{ matrix.python-version }} | tr -d .)-tests" >> $GITHUB_ENV + + - name: Setup tox python version + if: matrix.os == 'windows-latest' + run: | + "TOX_VERSION=py$(echo ${{ matrix.python-version }} | tr -d .)-tests" >> $env:GITHUB_ENV - name: Unit Testing run: | @@ -67,7 +78,7 @@ jobs: - name: Save Coverage Data Temporarily uses: actions/upload-artifact@v4 with: - name: coverage-data-${{ matrix.python-version }} + name: coverage-data-${{ matrix.os }}-${{ matrix.python-version }} path: .coverage.* retention-days: 1 if-no-files-found: ignore From 363b5a02be61fab743cc3c170171d530ee01f9b8 Mon Sep 17 00:00:00 2001 From: Spill-Tea Date: Sat, 27 Sep 2025 20:56:00 -0700 Subject: [PATCH 2/4] feat()oligos): Finalize standardization of _oligos api with m, c, and standard python accessible functions for palindrome. --- src/designer_dna/_oligos.pyi | 13 ++++++++ src/designer_dna/_oligos.pyx | 62 ++++++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/designer_dna/_oligos.pyi b/src/designer_dna/_oligos.pyi index 3d87e04..237562c 100644 --- a/src/designer_dna/_oligos.pyi +++ b/src/designer_dna/_oligos.pyi @@ -118,6 +118,19 @@ def reverse_complement(sequence: str, dna: bool = ...) -> str: """ +def m_palindrome(sequence: array[int], dna: bool = ...) -> tuple[int, int]: + """Find the longest palindromic substring within a nucleotide sequence. + + Args: + sequence (uchar[]): Nucleotide sequence writeable memory view. + dna (bool): Sequence is DNA, else RNA. + + Returns: + (int, int) start and end indices denoting the longest found + palindromic subsequence within sequence. + + """ + def palindrome(sequence: str, dna: bool = ...) -> str: """Find the longest palindromic substring within a nucleotide sequence. diff --git a/src/designer_dna/_oligos.pyx b/src/designer_dna/_oligos.pyx index d654895..96c9427 100644 --- a/src/designer_dna/_oligos.pyx +++ b/src/designer_dna/_oligos.pyx @@ -185,9 +185,8 @@ cpdef void m_complement(unsigned char[:] sequence, bint dna = True): """ cdef: Py_ssize_t length = sequence.shape[0] - unsigned char* c_string = &sequence[0] - c_complement(c_string, length, dna) + c_complement(&sequence[0], length, dna) cpdef str complement(str sequence, bint dna = True): @@ -253,11 +252,11 @@ cdef void c_reverse_complement( Args: sequence (uchar*): Nucleotide sequence pointer. - length (Py_ssize_t): Length of seq. + length (Py_ssize_t): Length of sequence. dna (bint): Sequence is DNA, else RNA. Returns: - (void) Complement seq in place. + (void) Complement sequence in place. """ if dna: @@ -354,6 +353,18 @@ cdef inline (Py_ssize_t, Py_ssize_t) c_palindrome( Py_ssize_t size, bint dna ) noexcept: + """Find the longest palindromic substring within a nucleotide sequence. + + Args: + seq (uchar*): Nucleotide sequence pointer. + size (Py_ssize_t): Length of seq. + dna (bint): Sequence is DNA, else RNA. + + Returns: + (tuple[Py_ssize_t, Py_ssize_t]) longest palindromic subsequence within sequence, + described by start (inclusive) and end (exclusive) index. + + """ cdef: unsigned char* com = malloc((size + 1) * sizeof(unsigned char)) Py_ssize_t i, left, right, current, length = 0, start = 0, end = 0 @@ -383,6 +394,30 @@ cdef inline (Py_ssize_t, Py_ssize_t) c_palindrome( return start, end +cpdef (int, int) m_palindrome( + unsigned char[:] sequence, + bint dna = True +): + """Find the longest palindromic substring within a nucleotide sequence. + + Args: + sequence (uchar[]): Nucleotide sequence writeable memory view. + dna (bool): Sequence is DNA, else RNA. + + Returns: + (int, int) start and end indices denoting the longest found + palindromic subsequence within sequence. + + """ + cdef: + int start, end + Py_ssize_t length = sequence.shape[0] + + start, end = c_palindrome(&sequence[0], length, dna) + + return start, end + + cpdef str palindrome(str sequence, bint dna = True): """Find the longest palindromic substring within a nucleotide sequence. @@ -479,19 +514,10 @@ cpdef int stretch(str sequence): """ cdef: + int longest StringView view = str_to_view(sequence) - Py_ssize_t j - int longest = 0, current = 0 - unsigned char prev = view.ptr[0] - for j in range(1, view.size): - if view.ptr[j] == prev: - current += 1 - if current > longest: - longest = current - else: - current = 0 - prev = view.ptr[j] + longest = c_stretch(view.ptr, view.size) free(view.ptr) return longest @@ -530,7 +556,11 @@ cdef inline void _assign( count += 1 -cdef int c_nrepeats(unsigned char* sequence, int length, int n) noexcept: +cdef inline int c_nrepeats( + unsigned char* sequence, + int length, + int n +) noexcept: """Calculate the maximum observed repeats of composite pattern size n characters. Args: From 75975d76d68fa9e640fd1124162f0b62b8993bd5 Mon Sep 17 00:00:00 2001 From: Spill-Tea Date: Sat, 27 Sep 2025 23:31:13 -0700 Subject: [PATCH 3/4] ci(workflow.publish): Implement cibuild strategy to build and publish package. --- .github/workflows/python-publish.yml | 75 +++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 12 deletions(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index f174420..496a5a5 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -5,33 +5,84 @@ name: Upload Python Package on: release: - types: [published] + types: [ published ] permissions: contents: read jobs: - deploy: + sdist: runs-on: ubuntu-latest - if: github.event_name == 'release' steps: - - uses: actions/checkout@v4 + - name: Checkout Project + uses: actions/checkout@v5 + - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: - python-version: '3.x' + python-version-file: .python-version-default - name: Install dependencies run: | python -m pip install -U pip --disable-pip-version-check python -m pip install setuptools wheel build - - name: Build package - run: python -m build + - name: Build Package + run: python -m build --sdist - - name: Publish package - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + - name: Upload distributions + uses: actions/upload-artifact@v4 with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} + name: ci-sdist + path: dist/*.tar.gz + + wheels: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ + ubuntu-latest, ubuntu-24.04-arm, + windows-latest, windows-11-arm, + macos-15-intel, macos-14 + ] + + steps: + - name: Checkout Project + uses: actions/checkout@v5 + + - name: Set up QEMU + if: runner.os == 'Linux' && runner.arch == 'X64' + uses: docker/setup-qemu-action@v3 + with: + platforms: all + + - name: Build wheels + uses: pypa/cibuildwheel@v3.2.0 + env: + CIBW_BUILD_VERBOSITY: 3 + CIBW_ARCHS_LINUX: ${{ runner.arch == 'X64' && 'auto ppc64le s390x' || 'auto' }} + + - uses: actions/upload-artifact@v4 + with: + name: ci-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + publish: + runs-on: ubuntu-latest + needs: [ sdist, wheels ] + permissions: + id-token: write + + steps: + - name: Retrieve release distributions + uses: actions/download-artifact@v4 + with: + name: ci-* + path: dist/ + + - name: Publish package + uses: pypa/gh-action-pypi-publish@v.1.13.0 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} From 31fb7281422dff74b4698707ff94c9b8719c3e75 Mon Sep 17 00:00:00 2001 From: Spill-Tea Date: Sun, 28 Sep 2025 01:00:26 -0700 Subject: [PATCH 4/4] fix(workflow.publish): Fix pypa action version. --- .github/workflows/python-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 496a5a5..3fb7fac 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -82,7 +82,7 @@ jobs: path: dist/ - name: Publish package - uses: pypa/gh-action-pypi-publish@v.1.13.0 + uses: pypa/gh-action-pypi-publish@v1.13.0 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }}