Skip to content
This repository was archived by the owner on Nov 17, 2023. It is now read-only.

Commit 78370d3

Browse files
authored
Merge pull request #178 from VariantEffect/2.1.0-release
2.1.0 release
2 parents acaadf0 + e2e9ce6 commit 78370d3

34 files changed

+3134
-154
lines changed

Dockerfile

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ ENV APP_USER=mavedb
1919
RUN useradd -m ${APP_USER}
2020
RUN apt-get update && apt-get -y upgrade
2121
RUN apt-get install -y build-essential
22-
RUN apt-get install -y git wget curl pandoc
22+
RUN apt-get install -y git wget curl pandoc graphviz
2323

2424
# Local directory with project source
2525
ENV HOST_SRC=.
@@ -73,12 +73,28 @@ WORKDIR ${APP_SOURCE}
7373

7474
COPY ${HOST_SRC} .
7575

76-
# Build the Sphinx documentation
76+
# Build the Sphinx documentation for MaveDB
7777
WORKDIR ${APP_SOURCE}/docs
7878
RUN make html
7979

80-
#WORKDIR ${APP_SOURCE}/docs/mavehgvs
81-
#RUN make html; exit 0
80+
# Build the Sphinx documentation for mavehgvs
81+
WORKDIR /tmp
82+
# get the correct mavehgvs version by tag
83+
RUN RAW_MAVE_HGVS_VERSION=`pip3 show mavehgvs | grep Version`; \
84+
MAVE_HGVS_VERSION="v${RAW_MAVE_HGVS_VERSION##* }"; \
85+
git clone --depth 1 --branch ${MAVE_HGVS_VERSION} https://github.com/VariantEffect/mavehgvs.git
86+
WORKDIR /tmp/mavehgvs/docs/
87+
RUN make html
88+
RUN mv _build ${APP_SOURCE}/docs/build/docs/mavehgvs
89+
RUN rm -rf /tmp/mavehgvs
90+
91+
# Build the Sphinx documentation for MaveTools
92+
WORKDIR /tmp
93+
RUN git clone --depth 1 https://github.com/VariantEffect/mavetools.git
94+
WORKDIR /tmp/mavetools/docs/
95+
RUN make html
96+
RUN mv _build ${APP_SOURCE}/docs/build/docs/mavetools
97+
RUN rm -rf /tmp/mavetools
8298

8399
WORKDIR ${APP_SOURCE}
84100

accounts/templates/accounts/profile_settings.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ <h2> Profile settings
3333

3434
<div class="api-access-wrapper">
3535
<div class="h1-api-access">
36-
<h3>API Access
36+
<h3>API access
3737
<a><i class="icon fas fa-key" style="font-size: 36px;"></i></a>
3838
</h3>
3939
</div>

api/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# We should set up a periodic celery job to clear out files here
2+
# by oldest modified date or something later.
3+
BASE_RESULTS_DIR = '/tmp/results'

api/tasks.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import json
2+
import os
3+
from pathlib import Path
4+
import zipfile
5+
6+
from django.db import transaction
7+
from django.contrib.auth import get_user_model
8+
9+
from .constants import BASE_RESULTS_DIR
10+
from .utilities import format_variant_get_response
11+
from celery.utils.log import get_task_logger
12+
from core.tasks import BaseTask
13+
from mavedb import celery_app
14+
from variant.models import Variant
15+
16+
User = get_user_model()
17+
logger = get_task_logger("api.tasks")
18+
19+
@celery_app.task(ignore_result=False, base=BaseTask)
20+
def format_variant_large_get_response(results_uuid, variant_urn, offset, limit):
21+
'''
22+
For large responses, asynchronously call format_variant_get_response()
23+
and save the output to a file for the user to access/download later.
24+
'''
25+
results_dir = BASE_RESULTS_DIR
26+
Path(results_dir).mkdir(parents=True, exist_ok=True)
27+
28+
results_identifier = results_uuid
29+
results_json_filepath = f'{results_dir}/{results_identifier}.json'
30+
results = format_variant_get_response(variant_urn, offset, limit)
31+
with open(results_json_filepath, 'w') as f:
32+
json.dump(results, f)
33+
34+
# If we need more files like metadata or something, we can add them here.
35+
files_to_zip = []
36+
files_to_zip.append(results_json_filepath)
37+
38+
zip_filename = f'{BASE_RESULTS_DIR}/{results_identifier}.zip'
39+
zip_file = zipfile.ZipFile(zip_filename, 'w')
40+
41+
for filepath in files_to_zip:
42+
fdir, fname = os.path.split(filepath)
43+
zip_file.write(filepath, fname)
44+
zip_file.close()

api/tests/test_views.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,3 +684,183 @@ def test_can_download_metadata(self):
684684
).content.decode()
685685
)
686686
self.assertEqual(response, scs.extra_metadata)
687+
688+
class TestVariantAPIViews(TestCase):
689+
factory = VariantFactory
690+
url = "variants"
691+
variants_base_url = "/api/variants"
692+
693+
def setUp(self):
694+
Variant.objects.all().delete()
695+
696+
def tearDown(self):
697+
Variant.objects.all().delete()
698+
699+
def test_raises_400_error_missing_params(self):
700+
response = self.client.get(f"{self.variants_base_url}/NOT_A_REAL_URN")
701+
self.assertEqual(response.status_code, 400)
702+
variant = self.factory()
703+
variant.save()
704+
response = self.client.get(f"{self.variants_base_url}/{variant.urn}")
705+
self.assertEqual(response.status_code, 400)
706+
707+
def test_raises_404_error_missing_variant(self):
708+
variant_id = 1
709+
response = self.client.get(
710+
f"{self.variants_base_url}/NOT_A_REAL_URN?variant_id={variant_id}"
711+
)
712+
self.assertEqual(response.status_code, 404)
713+
714+
def test_can_get_response_for_variant(self):
715+
variant = self.factory()
716+
variant_urn = variant.urn
717+
variant_id = 1
718+
variant.urn = variant_urn + f'#{variant_id}'
719+
variant.save()
720+
response = self.client.get(
721+
f"{self.variants_base_url}/{variant_urn}?variant_id={variant_id}"
722+
)
723+
self.assertEqual(response.status_code, 200)
724+
response_json = json.loads(response.content.decode())
725+
self.assertEqual(
726+
len(response_json['experiment']['scoreset']['variants']), 1
727+
)
728+
729+
def test_can_use_offset(self):
730+
variant_fake_base_urn = 'tmp:abcdef123456'
731+
variants_count = 5
732+
offset = 3
733+
vs = [self.factory() for _ in range(variants_count)]
734+
for i in range(len(vs)):
735+
# Overwrite the variant's urn so they all share a urn_prefix
736+
v = vs[i]
737+
v.urn = variant_fake_base_urn + f'#{i}'
738+
v.save()
739+
v = Variant.objects.get(urn=f'{variant_fake_base_urn}#0')
740+
response = self.client.get(
741+
f"{self.variants_base_url}/{variant_fake_base_urn}?variant_id=0" \
742+
f"&offset={offset}"
743+
)
744+
response_json = json.loads(response.content.decode())
745+
self.assertEqual(
746+
len(response_json['experiment']['scoreset']['variants']),
747+
variants_count-offset
748+
)
749+
750+
def test_can_use_limit(self):
751+
variant_fake_base_urn = 'tmp:abcdef123456'
752+
variants_count = 5
753+
limit = 3
754+
vs = [self.factory() for _ in range(variants_count)]
755+
for i in range(len(vs)):
756+
v = vs[i]
757+
v.urn = variant_fake_base_urn + f'#{i}'
758+
v.save()
759+
760+
response = self.client.get(
761+
f"{self.variants_base_url}/{variant_fake_base_urn}?variant_id=0" \
762+
f"&limit={limit}"
763+
)
764+
response_json = json.loads(response.content.decode())
765+
self.assertEqual(
766+
len(response_json['experiment']['scoreset']['variants']), limit
767+
)
768+
769+
def test_requested_variant_is_always_first(self):
770+
"""
771+
The desired behavior is that the requested variant is always
772+
returned, and is always returned first.
773+
"""
774+
variant_fake_base_urn = 'tmp:abcdef123456'
775+
variants_count = 5
776+
vs = [self.factory() for _ in range(variants_count)]
777+
for i in range(len(vs)):
778+
v = vs[i]
779+
v.urn = variant_fake_base_urn + f'#{i}'
780+
v.save()
781+
782+
requested_variant_id = 1
783+
# Make sure by default (no limit, no offset), the requested variant
784+
# is returned first.
785+
response = self.client.get(
786+
f"{self.variants_base_url}/{variant_fake_base_urn}" \
787+
f"?variant_id={requested_variant_id}"
788+
)
789+
response_json = json.loads(response.content.decode())
790+
self.assertEqual(
791+
response_json['experiment']['scoreset']['variants'][0]['urn'],
792+
f'{variant_fake_base_urn}#{requested_variant_id}'
793+
)
794+
795+
# For some representative combinations of limits and offsets, make sure
796+
# the requested variant is still the first returned.
797+
limits_and_offsets = [(0, 0), (0, 3), (2, 0), (5, 0), (5, 3)]
798+
for limit_and_offset in limits_and_offsets:
799+
limit = limit_and_offset[0]
800+
offset = limit_and_offset[1]
801+
response = self.client.get(
802+
f"{self.variants_base_url}/{variant_fake_base_urn}" \
803+
f"?variant_id={requested_variant_id}" \
804+
f"&limit={limit}&offset={offset}"
805+
)
806+
response_json = json.loads(response.content.decode())
807+
self.assertEqual(
808+
response_json['experiment']['scoreset']['variants'][0]['urn'],
809+
f'{variant_fake_base_urn}#{requested_variant_id}'
810+
)
811+
812+
def test_can_get_large_response_for_variant(self):
813+
variant = self.factory()
814+
variant_urn = variant.urn
815+
variant_id = 1
816+
variant.urn = variant_urn + f'#{variant_id}'
817+
variant.save()
818+
response = self.client.get(
819+
f"{self.variants_base_url}/{variant_urn}?variant_id={variant_id}" \
820+
"&limit=51"
821+
)
822+
self.assertEqual(response.status_code, 202)
823+
response_json = json.loads(response.content.decode())
824+
self.assertTrue('results_uuid' in response_json)
825+
826+
response = self.client.get(
827+
f"{self.variants_base_url}/{variant_urn}?variant_id={variant_id}" \
828+
"&limit=-1"
829+
)
830+
self.assertEqual(response.status_code, 202)
831+
response_json = json.loads(response.content.decode())
832+
self.assertTrue('results_uuid' in response_json)
833+
834+
class TestResultsAPIViews(TestCase):
835+
factory = VariantFactory
836+
url = "results"
837+
variants_base_url = "/api/variants"
838+
results_base_url = "/api/results"
839+
840+
def setUp(self):
841+
Variant.objects.all().delete()
842+
843+
def tearDown(self):
844+
Variant.objects.all().delete()
845+
846+
def test_raises_404_missing_results_uuid(self):
847+
response = self.client.get(
848+
f"{self.results_base_url}/NOT_A_REAL_UUID"
849+
)
850+
self.assertEqual(response.status_code, 404)
851+
852+
def test_can_get_zipfile(self):
853+
variant = self.factory()
854+
variant_urn = variant.urn
855+
variant_id = 1
856+
variant.urn = variant_urn + f'#{variant_id}'
857+
variant.save()
858+
response = self.client.get(
859+
f"{self.variants_base_url}/{variant_urn}?variant_id={variant_id}" \
860+
"&limit=51"
861+
)
862+
response_json = json.loads(response.content.decode())
863+
results_uuid = response_json['results_uuid']
864+
865+
response = self.client.get(f"{self.results_base_url}/{results_uuid}")
866+
self.assertEqual(response.status_code, 200)

api/urls.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@
2222
router.register("target", views.TargetGeneViewSet)
2323
router.register("reference", views.ReferenceGenomeViewSet)
2424

25-
scoreset_urls = [
25+
urlpatterns = [
26+
url(
27+
r"^results/(?P<results_uuid>.*)$",
28+
views.ResultsView.as_view(),
29+
name="results",
30+
),
2631
url(
2732
r"^scoresets/(?P<urn>{})/scores/$".format(scoreset_url_pattern),
2833
views.scoreset_score_data,
@@ -38,7 +43,11 @@
3843
views.scoreset_metadata,
3944
name="api_download_metadata",
4045
),
46+
url(
47+
r"^variants/(?P<urn>.*)$",
48+
views.VariantView.as_view(),
49+
name="variant",
50+
),
4151
]
4252

43-
44-
urlpatterns = router.urls + scoreset_urls
53+
urlpatterns += router.urls

0 commit comments

Comments
 (0)