From c4a95c67d98ebcc847a151695db2c3e6ed1f69c4 Mon Sep 17 00:00:00 2001 From: Alex Lubbock Date: Wed, 2 Oct 2024 13:41:56 +0100 Subject: [PATCH 1/9] style: fix deprecated dockerfile syntax --- frontend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index e87a9b6..6d65a50 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,6 +1,6 @@ FROM registry.hub.docker.com/library/node:22-alpine AS dev -ENV ANTIGENAPP_DIR /antigenapp +ENV ANTIGENAPP_DIR=/antigenapp COPY package-lock.json ${ANTIGENAPP_DIR}/package-lock.json COPY package.json ${ANTIGENAPP_DIR}/package.json From c2c487291f7bb040405fa0db54619313c652db8b Mon Sep 17 00:00:00 2001 From: Alex Lubbock Date: Tue, 8 Oct 2024 09:42:31 +0100 Subject: [PATCH 2/9] feat: add BLAST to backend container --- backend/Dockerfile | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 1d7d709..ad79a90 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,6 @@ -FROM python:3.11-bullseye AS builder +FROM quay.io/rosalindfranklininstitute/blast:2.16.0 AS blast + +FROM python:3.12-bookworm AS builder RUN pip install --user wheel pipenv @@ -16,15 +18,21 @@ COPY antigenapi /usr/src/antigenapi RUN DJANGO_CI=true .venv/bin/python manage.py collectstatic --noinput -FROM python:3.11-slim-bullseye AS prod +FROM python:3.12-slim-bookworm AS prod +# liblmdb-dev required by BLAST RUN apt-get update && apt-get install -y \ libxml2 \ media-types \ + liblmdb-dev \ && rm -rf /var/lib/apt/lists/* RUN mkdir -v /usr/src/.venv +# blastp and makeblastdb commands +COPY --from=blast /blast/ReleaseMT/bin/blastp /usr/local/bin/blastp +COPY --from=blast /blast/ReleaseMT/bin/makeblastdb /usr/local/bin/makeblastdb + COPY --from=builder /usr/src/.venv/ /usr/src/.venv/ COPY --from=builder /usr/src/static/ /api_data/static/ COPY --from=builder /usr/src/manage.py /usr/src/ @@ -42,6 +50,13 @@ CMD [".venv/bin/uwsgi", "--ini", "uwsgi.ini"] FROM builder AS dev +# liblmdb-dev required by BLAST +RUN apt update && apt install -y liblmdb-dev && rm -rf /var/lib/apt/lists/* + +# blastp and makeblastdb commands +COPY --from=blast /blast/ReleaseMT/bin/blastp /usr/local/bin/blastp +COPY --from=blast /blast/ReleaseMT/bin/makeblastdb /usr/local/bin/makeblastdb + ENV PATH="$PATH:/root/.local/bin:/usr/src/.venv/bin" RUN pipenv sync --dev From 69745148e1e86b928e5e2281025b97d2ce998792 Mon Sep 17 00:00:00 2001 From: Alex Lubbock Date: Wed, 9 Oct 2024 16:23:52 +0100 Subject: [PATCH 3/9] wip: all-vs-all BLAST --- backend/antigenapi/urls.py | 2 + backend/antigenapi/views_old.py | 99 +++++++++++++++++++++++++++++---- frontend/src/App.js | 8 +++ 3 files changed, 98 insertions(+), 11 deletions(-) diff --git a/backend/antigenapi/urls.py b/backend/antigenapi/urls.py index 93d849b..756a9ec 100644 --- a/backend/antigenapi/urls.py +++ b/backend/antigenapi/urls.py @@ -4,6 +4,7 @@ from antigenapi.views.dashboard import AuditLogLatestEvents, DashboardStats from antigenapi.views_old import ( AntigenViewSet, + BlastAllView, CohortViewSet, ElisaPlateViewSet, GlobalFastaView, @@ -29,4 +30,5 @@ path("fasta/", GlobalFastaView.as_view(), name="fasta"), path("dashboard/stats", DashboardStats.as_view(), name="dashboard_stats"), path("dashboard/latest", AuditLogLatestEvents.as_view(), name="dashboard_latest"), + path("blast/all", BlastAllView.as_view(), name="blast_all"), ] diff --git a/backend/antigenapi/views_old.py b/backend/antigenapi/views_old.py index ffbc5f2..68adcde 100644 --- a/backend/antigenapi/views_old.py +++ b/backend/antigenapi/views_old.py @@ -4,9 +4,10 @@ import math import os import re +import subprocess import urllib.error import urllib.parse -from tempfile import NamedTemporaryFile +from tempfile import NamedTemporaryFile, TemporaryDirectory from wsgiref.util import FileWrapper import numpy as np @@ -1053,19 +1054,24 @@ def search_sequencing_run_results(self, request, query): ) +def _get_entire_db_fasta(): + fasta_data = "" + for sr in SequencingRunResults.objects.all(): + airr_file = read_airr_file( + sr.airr_file, usecols=("sequence_id", "sequence_alignment_aa") + ) + airr_file = airr_file[airr_file.sequence_alignment_aa.notna()] + if not airr_file.empty: + for _, row in airr_file.iterrows(): + fasta_data += f"> {row.sequence_id}\n" + fasta_data += f"{row.sequence_alignment_aa.replace('.', '')}\n" + return fasta_data + + class GlobalFastaView(APIView): def get(self, request, format=None): """Download entire database as .fasta file.""" - fasta_data = "" - for sr in SequencingRunResults.objects.all(): - airr_file = read_airr_file( - sr.airr_file, usecols=("sequence_id", "sequence_alignment_aa") - ) - airr_file = airr_file[airr_file.sequence_alignment_aa.notna()] - if not airr_file.empty: - for _, row in airr_file.iterrows(): - fasta_data += f"> {row.sequence_id}\n" - fasta_data += f"{row.sequence_alignment_aa.replace('.', '')}\n" + fasta_data = _get_entire_db_fasta() fasta_filename = ( f"antigenapp_database_{datetime.datetime.now().isoformat()}.fasta" @@ -1078,3 +1084,74 @@ def get(self, request, format=None): ) response["Content-Disposition"] = f'attachment; filename="{fasta_filename}"' return response + + +class BlastAllView(APIView): + def get(self, request, format=None): + """Run all-vs-all BLAST on entire DB.""" + fasta_data = _get_entire_db_fasta() + + # Write the DB to disk as .fasta format + with TemporaryDirectory() as tmp_dir: + fasta_filename = os.path.join(tmp_dir, "antigen.fasta") + with open(fasta_filename, "w") as f: + f.write(fasta_data) + + # Run makeblastdb + mkdb_proc = subprocess.run( + [ + "makeblastdb", + "-in", + "antigen.fasta", + "-dbtype", + "prot", + "-out", + "antigen.db", + ], + cwd=tmp_dir, + ) + + if mkdb_proc.returncode != 0: + raise Exception( + f"makeblastdb returned exit code of " f"{mkdb_proc.returncode}" + ) + + # Run blastp + blastp_proc = subprocess.run( + [ + "blastp", + "-db", + "antigen.db", + "-query", + "antigen.fasta", + "-html", + "-out", + "antigen.html", + "-num_threads", + "4", + ], + cwd=tmp_dir, + ) + + if blastp_proc.returncode != 0: + raise Exception( + f"blastp returned exit code of " f"{blastp_proc.returncode}" + ) + + # Read in the results file + with open(os.path.join(tmp_dir, "antigen.html"), "r") as f: + results = f.read() + + # blast_filename = ( + # f"antigenapp_database_blast_allvsall_" + # f"{datetime.datetime.now().isoformat()}.tsv" + # ) + # response = FileResponse( + # results, + # as_attachment=True, + # content_type="text/tab-separated-values", + # filename=blast_filename, + # ) + # response["Content-Disposition"] = f'attachment; filename="{blast_filename}"' + response = HttpResponse(results) + return response diff --git a/frontend/src/App.js b/frontend/src/App.js index 4130fdf..2cccaeb 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -685,6 +685,14 @@ const App = () => { onSetError={setError} extraButton={ <> + + + -