From 18a0c8d536eed9ede3365ef9254c1e1f9fb156f6 Mon Sep 17 00:00:00 2001 From: Maria Nattestad Date: Tue, 23 Jun 2026 12:00:20 +0200 Subject: [PATCH 1/3] Add pylint to CI and set up PR preview deployments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pylint: - Configure via pyproject.toml: disable cosmetic/complexity checks, max-line-length=250, fail-under=9.0 in CI (currently scores 9.48/10) - Fix real issues in files touched this sprint: bare-except → Exception, singleton comparisons (== True → truthiness), undefined loop variable, unused numpy imports in nchart.py and dotplot.py - Add pylint to [dev] extras and test.yml CI step PR previews: - Switch deploy-pages.yml from actions/deploy-pages to peaceiris/actions-gh-pages with keep_files=true so PR subdirs persist - New pr-preview.yml: deploys public/ to pr-preview/pr-{N}/ on every push to a PR touching public/, posts/updates a comment with the URL, cleans up on PR close - NOTE: requires one manual settings change — GitHub Settings → Pages → Source → "Deploy from a branch" → gh-pages / (root) Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/deploy-pages.yml | 23 +++------- .github/workflows/pr-preview.yml | 71 ++++++++++++++++++++++++++++++ .github/workflows/test.yml | 3 ++ assemblytics/dot_prep.py | 6 +-- assemblytics/dotplot.py | 1 - assemblytics/nchart.py | 1 - assemblytics/uniq_anchor.py | 13 +++--- pyproject.toml | 30 ++++++++++++- 8 files changed, 117 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/pr-preview.yml diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 157d558..9ef66ee 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -8,28 +8,17 @@ on: workflow_dispatch: permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: pages - cancel-in-progress: true + contents: write jobs: deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/configure-pages@v5 - - - uses: actions/upload-pages-artifact@v3 + - name: Deploy public/ to gh-pages root + uses: peaceiris/actions-gh-pages@v4 with: - path: public/ - - - id: deployment - uses: actions/deploy-pages@v4 + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public + keep_files: true # preserve pr-preview/ subdirectories diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml new file mode 100644 index 0000000..64c826d --- /dev/null +++ b/.github/workflows/pr-preview.yml @@ -0,0 +1,71 @@ +name: PR Preview + +on: + pull_request: + paths: + - "public/**" + types: [opened, synchronize, reopened, closed] + +permissions: + contents: write + pull-requests: write + +jobs: + deploy-preview: + if: github.event.action != 'closed' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Deploy PR preview to gh-pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public + destination_dir: pr-preview/pr-${{ github.event.pull_request.number }} + keep_files: true + + - name: Comment preview URL on PR + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request.number; + const url = `https://marianattestad.github.io/assemblytics/pr-preview/pr-${pr}/`; + const body = `### Web app preview\n🔍 [${url}](${url})\n\n_Updated on every push to this PR. Removed when the PR is closed._`; + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr, + }); + const existing = comments.find(c => c.body.startsWith('### Web app preview')); + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr, + body, + }); + } + + cleanup-preview: + if: github.event.action == 'closed' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: gh-pages + + - name: Remove PR preview directory + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git rm -rf pr-preview/pr-${{ github.event.pull_request.number }} || true + git diff --cached --quiet || git commit -m "Remove preview for PR #${{ github.event.pull_request.number }}" + git push diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 534905c..0c40ecd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,3 +20,6 @@ jobs: - name: Run tests run: pytest tests/ -v + + - name: Lint + run: pylint assemblytics/ --fail-under=9.0 diff --git a/assemblytics/dot_prep.py b/assemblytics/dot_prep.py index a0dae87..acce686 100755 --- a/assemblytics/dot_prep.py +++ b/assemblytics/dot_prep.py @@ -93,7 +93,7 @@ def index_for_dot(reference_lengths, fields_by_query, output_prefix, max_overvie # orientation: flip = sum_reverse > sum_forward - flip_by_query[query_name] = "-" if (flip == True) else "+" + flip_by_query[query_name] = "-" if flip else "+" for tag in ordered_tags: @@ -103,7 +103,7 @@ def index_for_dot(reference_lengths, fields_by_query, output_prefix, max_overvie for fields in lines: if fields[8] == tag: - if flip == True: + if flip: fields[2] = int(fields[5]) - int(fields[2]) fields[3] = int(fields[5]) - int(fields[3]) @@ -138,7 +138,7 @@ def index_for_dot(reference_lengths, fields_by_query, output_prefix, max_overvie f_out_index.write("#query\n") f_out_index.write("query,query_length,orientation,bytePosition_unique,bytePosition_repetitive,bytePosition_end,unique_matching_refs,matching_refs\n") # relative_ref_position_by_query is sorted by rel_pos - for query,rel_pos in relative_ref_position_by_query: + for query,_ in relative_ref_position_by_query: f_out_index.write("%s,%d,%s,%d,%d,%d,%s,%s\n" % (query, query_lengths[query], flip_by_query[query], query_byte_positions[(query,"unique")], query_byte_positions[(query,"repetitive")] - query_byte_positions[(query,"unique")], query_byte_positions[(query,"end")] - query_byte_positions[(query,"repetitive")], "~".join(unique_references_by_query[query]), "~".join(all_references_by_query[query]))) f_out_index.write("#overview\n") diff --git a/assemblytics/dotplot.py b/assemblytics/dotplot.py index 0da19d0..df78e6f 100644 --- a/assemblytics/dotplot.py +++ b/assemblytics/dotplot.py @@ -5,7 +5,6 @@ import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt -import numpy as np import os def run(output_dir): diff --git a/assemblytics/nchart.py b/assemblytics/nchart.py index b89581f..7524380 100644 --- a/assemblytics/nchart.py +++ b/assemblytics/nchart.py @@ -5,7 +5,6 @@ import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt -import numpy as np import os def bp_format(num): diff --git a/assemblytics/uniq_anchor.py b/assemblytics/uniq_anchor.py index 7e33b19..4ca68b6 100755 --- a/assemblytics/uniq_anchor.py +++ b/assemblytics/uniq_anchor.py @@ -30,11 +30,9 @@ def run(args): try: f = gzip.open(filename, 'rt') header1 = f.readline().strip() - # Detected gzipped delta file. - except: + except Exception: # noqa: BLE001 — fallback for uncompressed delta f = open(filename, 'r') header1 = f.readline().strip() - # Detected uncompressed delta file. # Skip the second line f.readline() @@ -97,11 +95,9 @@ def run(args): try: f = gzip.open(filename, 'rt') header1 = f.readline() - # Detected gzipped delta file. - except: + except Exception: # noqa: BLE001 — fallback for uncompressed delta f = open(filename, 'r') header1 = f.readline() - # Detected uncompressed delta file. fout.write(header1) fout.write(f.readline()) @@ -112,6 +108,7 @@ def run(args): list_of_alignments_to_keep = [] alignment_counter = {} keep_printing = False + query = "" # For coords: current_query_name = "" @@ -152,7 +149,7 @@ def run(args): for index in list_of_alignments_to_keep: if line.strip() == header_lines_by_query[query][index]: header_needed = True - if header_needed == True: + if header_needed: fout.write(line) # if we have any alignments under this header, print the header alignment_counter[query] = alignment_counter.get(query,0) @@ -204,7 +201,7 @@ def run(args): ) alignment_counter[query] = alignment_counter[query] + 1 - elif keep_printing == True: + elif keep_printing: fout.write(line) fcoords_out_tab.close() diff --git a/pyproject.toml b/pyproject.toml index bf65600..e4722c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,10 +31,38 @@ Repository = "https://github.com/MariaNattestad/assemblytics" assemblytics = "assemblytics.cli:main" [project.optional-dependencies] -dev = ["pytest"] +dev = ["pytest", "pylint"] [tool.setuptools] packages = ["assemblytics"] [tool.pytest.ini_options] testpaths = ["tests"] + +[tool.pylint.format] +max-line-length = 250 + +[tool.pylint."messages control"] +disable = [ + "missing-module-docstring", + "missing-function-docstring", + "missing-class-docstring", + "trailing-whitespace", + "wrong-import-order", + "wrong-import-position", + "import-outside-toplevel", + "too-many-locals", + "too-many-branches", + "too-many-statements", + "too-many-positional-arguments", + "too-many-arguments", + "consider-using-f-string", + "consider-using-with", + "consider-using-dict-items", + "unspecified-encoding", + "duplicate-code", + "bad-indentation", + "redefined-outer-name", + "chained-comparison", + "broad-exception-caught", +] From 8892d21182194805d8030eda8b5ff30bc0f7cae5 Mon Sep 17 00:00:00 2001 From: Maria Nattestad Date: Tue, 23 Jun 2026 12:09:51 +0200 Subject: [PATCH 2/3] Revert deploy-pages.yml; remove pr-preview workflow Keeping the simpler GitHub Actions-based Pages deployment. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/deploy-pages.yml | 23 +++++++--- .github/workflows/pr-preview.yml | 71 ------------------------------ 2 files changed, 17 insertions(+), 77 deletions(-) delete mode 100644 .github/workflows/pr-preview.yml diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 9ef66ee..157d558 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -8,17 +8,28 @@ on: workflow_dispatch: permissions: - contents: write + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true jobs: deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Deploy public/ to gh-pages root - uses: peaceiris/actions-gh-pages@v4 + - uses: actions/configure-pages@v5 + + - uses: actions/upload-pages-artifact@v3 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./public - keep_files: true # preserve pr-preview/ subdirectories + path: public/ + + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml deleted file mode 100644 index 64c826d..0000000 --- a/.github/workflows/pr-preview.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: PR Preview - -on: - pull_request: - paths: - - "public/**" - types: [opened, synchronize, reopened, closed] - -permissions: - contents: write - pull-requests: write - -jobs: - deploy-preview: - if: github.event.action != 'closed' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Deploy PR preview to gh-pages - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./public - destination_dir: pr-preview/pr-${{ github.event.pull_request.number }} - keep_files: true - - - name: Comment preview URL on PR - uses: actions/github-script@v7 - with: - script: | - const pr = context.payload.pull_request.number; - const url = `https://marianattestad.github.io/assemblytics/pr-preview/pr-${pr}/`; - const body = `### Web app preview\n🔍 [${url}](${url})\n\n_Updated on every push to this PR. Removed when the PR is closed._`; - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr, - }); - const existing = comments.find(c => c.body.startsWith('### Web app preview')); - if (existing) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr, - body, - }); - } - - cleanup-preview: - if: github.event.action == 'closed' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: gh-pages - - - name: Remove PR preview directory - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git rm -rf pr-preview/pr-${{ github.event.pull_request.number }} || true - git diff --cached --quiet || git commit -m "Remove preview for PR #${{ github.event.pull_request.number }}" - git push From ddafa0b33efd940b99ac5298e83d3ee39a1fb507 Mon Sep 17 00:00:00 2001 From: Maria Nattestad Date: Tue, 23 Jun 2026 12:14:45 +0200 Subject: [PATCH 3/3] Update dev instructions in readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 9f0b942..6ddd3a9 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,9 @@ This runs `python3 -m build --wheel` and copies the result into `public/`. If yo To re-run the pipeline on each input and diff its variant calls against the matching example output: ```bash +pip3 uninstall assemblytics # remove whatever's installed +pip3 install -e . # install editable from current directory + # E. coli (uses a smaller unique anchor length since it's a small genome) assemblytics -d input_examples/ecoli.delta.gz -o /tmp/assemblytics_test/ecoli -l 1000 diff <(tail -n +2 /tmp/assemblytics_test/ecoli/assemblytics_structural_variants.bed | sort) \