Commit b0ae5b9e authored by Kenzo-Hugo Hillion's avatar Kenzo-Hugo Hillion
Browse files

Merge branch '43-doc-swagger' into 'dev'

Generate Documentation for the backend API

Closes #43

See merge request !10
parents 70c740a1 ba20e779
Pipeline #13634 passed with stages
in 2 minutes and 11 seconds
......@@ -4,50 +4,52 @@ url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
atomicwrites = "==1.3.0"
attrs = "==19.1.0"
coverage = "==4.5.3"
entrypoints = "==0.3"
flake8 = "==3.7.7"
importlib-metadata = "==0.18"
kiwisolver = "==1.1.0"
mccabe = "==0.6.1"
more-itertools = "==7.0.0"
pluggy = "==0.12.0"
py = "==1.8.0"
pycodestyle = "==2.5.0"
pyflakes = "==2.1.1"
pyparsing = "==2.4.0"
pytest = "==4.6.3"
pytest-cov = "==2.7.1"
pytest-django = "==3.5.0"
wcwidth = "==0.1.7"
zipp = "==0.5.1"
Cycler = "==0.10.0"
atomicwrites = "*"
attrs = "*"
coverage = "*"
entrypoints = "*"
flake8 = "*"
importlib-metadata = "*"
kiwisolver = "*"
mccabe = "*"
more-itertools = "*"
pluggy = "*"
py = "*"
pycodestyle = "*"
pyflakes = "*"
pyparsing = "*"
pytest = "*"
pytest-cov = "*"
pytest-django = "*"
wcwidth = "*"
zipp = "*"
Cycler = "*"
jupyter = "*"
[packages]
certifi = "==2019.6.16"
chardet = "==3.0.4"
django-cors-headers = "==3.0.2"
django-environ = "==0.4.5"
django-extensions = "==2.1.7"
django-filter = "==2.1.0"
djangorestframework = "==3.9.4"
djangorestframework-jwt = "==1.11.0"
idna = "==2.8"
numpy = "==1.16.4"
pandas = "==0.24.2"
psycopg2 = "==2.8.2"
python-dateutil = "==2.8.0"
pytz = "==2019.1"
requests = "==2.22.0"
six = "==1.12.0"
sqlparse = "==0.3.0"
urllib3 = "==1.25.3"
Django = "==2.2.1"
PyJWT = "==1.7.1"
certifi = "*"
chardet = "*"
django-cors-headers = "*"
django-environ = "*"
django-extensions = "*"
django-filter = "*"
djangorestframework = "*"
djangorestframework-jwt = "*"
idna = "*"
numpy = "*"
pandas = "*"
psycopg2 = "*"
python-dateutil = "*"
pytz = "*"
requests = "*"
six = "*"
sqlparse = "*"
urllib3 = "*"
Django = "*"
PyJWT = "*"
metagenedb = {editable = true,path = "."}
drf-yasg = "*"
packaging = "*"
[requires]
python_version = "3.7"
This diff is collapsed.
import pandas as pd
from rest_framework.viewsets import GenericViewSet
from rest_framework import mixins
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from metagenedb.common.utils.df_operations import get_mask
from metagenedb.apps.catalog.models import Gene
from metagenedb.apps.catalog.serializers import GeneSerializer
class GeneViewSet(mixins.ListModelMixin,
mixins.RetrieveModelMixin,
GenericViewSet):
class DocGeneLength(object):
"""
Define response for API documentation of gene length distribution method
{
"results": {
"counts": [
0,
887,
],
"labels": [
"0-9999",
"10000-19999",
"20000-29999",
]
}
}
"""
window_size_param = openapi.Parameter('window_size', in_=openapi.IN_QUERY, description='Size of the window.',
type=openapi.TYPE_INTEGER, default=10000)
counts = openapi.Schema(type="array", items=openapi.Schema(type="int"),
description="Counts for every window_size")
labels = openapi.Schema(type="array", items=openapi.Schema(type="char"),
description="Corresponding windows")
results = openapi.Schema(type="object", properties={'counts': counts, 'labels': labels},
description="results of your request")
gene_length_schema = openapi.Schema(type="object", properties={'results': results})
gene_length_response = openapi.Response('Get the distribution of gene length for a given window size',
schema=gene_length_schema)
class GeneViewSet(ModelViewSet):
queryset = Gene.objects.all()
serializer_class = GeneSerializer
GENE_LENGTH_COL = 'gene_length'
GENE_LENGTH_COL = 'length'
def get_permissions(self):
if self.action == 'create':
self.permission_classes = [IsAdminUser, ]
return super(self.__class__, self).get_permissions()
def _count_windows(self, df, window_size=10000, window_col=GENE_LENGTH_COL):
"""
......@@ -36,6 +71,14 @@ class GeneViewSet(mixins.ListModelMixin,
'labels': labels
}
@swagger_auto_schema(
manual_parameters=[DocGeneLength.window_size_param],
responses={
'200': DocGeneLength.gene_length_response,
'204': 'No genes on the catalog to build the distribution'
},
operation_id='Gene length distribution',
)
@action(methods=['get'], detail=False)
def gene_length(self, request, window_size=10000):
if 'window_size' in request.query_params:
......
......@@ -42,7 +42,7 @@ class TestGenes(TestCase):
class TestCountWindows(TestCase):
def setUp(self):
self.window_col = "gene_length"
self.window_col = "length"
self.df = pd.DataFrame(
[22, 29, 35],
columns=[self.window_col]
......
......@@ -6,7 +6,7 @@ from metagenedb.apps.catalog.models import Gene
@admin.register(Gene)
class GeneAdmin(admin.ModelAdmin):
list_display = ('gene_id', 'gene_length', 'get_functions', 'get_taxonomy')
list_display = ('gene_id', 'length', 'get_functions', 'get_taxonomy')
search_fields = ('gene_id',)
def get_functions(self, obj):
......
# Generated by Django 2.2.1 on 2019-08-07 14:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('catalog', '0006_gene_taxonomy'),
]
operations = [
migrations.AlterField(
model_name='gene',
name='gene_length',
field=models.PositiveIntegerField(),
),
migrations.RenameField(
model_name='gene',
old_name='gene_length',
new_name='length',
),
]
......@@ -5,7 +5,7 @@ from .function import Function
class Gene(models.Model):
gene_id = models.CharField(max_length=100, unique=True, db_index=True)
gene_length = models.IntegerField()
length = models.PositiveIntegerField()
functions = models.ManyToManyField(Function)
taxonomy = models.ForeignKey(
'Taxonomy', related_name='genes',
......
......@@ -4,7 +4,7 @@ from metagenedb.apps.catalog.serializers import FunctionSerializer
class GeneSerializer(serializers.ModelSerializer):
functions = FunctionSerializer(many=True, read_only=True)
functions = FunctionSerializer(many=True, required=False)
taxonomy = serializers.SlugRelatedField(
queryset=Taxonomy.objects.all(),
slug_field='tax_id',
......@@ -13,4 +13,4 @@ class GeneSerializer(serializers.ModelSerializer):
class Meta:
model = Gene
fields = ('gene_id', 'gene_length', 'functions', 'taxonomy')
fields = ('gene_id', 'length', 'functions', 'taxonomy')
......@@ -33,7 +33,7 @@ class IGCLineParser(object):
return {
'igc_id': gene_info[0],
'gene_id': gene_info[1],
'gene_length': gene_info[2],
'length': gene_info[2],
'gene_completeness_status': gene_info[3],
'cohort_origin': gene_info[4],
'taxo_phylum': gene_info[5],
......
......@@ -9,7 +9,7 @@ class TestIGCLineParser(TestCase):
raw_data = [
'gene_id',
'gene_name',
'gene_length',
'length',
'gene_completeness_status',
'cohort_origin',
'taxo_phylum',
......@@ -26,7 +26,7 @@ class TestIGCLineParser(TestCase):
expected_dict = {
'igc_id': raw_data[0],
'gene_id': raw_data[1],
'gene_length': raw_data[2],
'length': raw_data[2],
'gene_completeness_status': raw_data[3],
'cohort_origin': raw_data[4],
'taxo_phylum': raw_data[5],
......
......@@ -19,6 +19,7 @@ INSTALLED_APPS = [
'rest_framework',
'django_extensions',
'corsheaders',
'drf_yasg',
]
MIDDLEWARE = [
......@@ -105,7 +106,7 @@ REST_FRAMEWORK = {
# 'rest_framework.authentication.BasicAuthentication',
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 100
'PAGE_SIZE': 100,
}
......
......@@ -15,9 +15,29 @@ Including another URLconf
"""
from django.contrib import admin
from django.urls import include, path
from django.conf.urls import url
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
schema_view = get_schema_view(
openapi.Info(
title="Metagenedb API",
default_version='v1',
description="API documentation for metagenedb",
contact=openapi.Contact(email="kehillio@pasteur.fr"),
license=openapi.License(name="License not defined"),
),
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
path('api/', include(('metagenedb.api.urls', 'api'))),
path('admin/', admin.site.urls),
url(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]
......@@ -22,7 +22,7 @@ _LOGGER = logging.getLogger(__name__)
PHYLUM_COL = 'taxo_phylum'
GENUS_COL = 'taxo_genus'
SELECTED_KEYS = ['gene_id', 'gene_length', 'kegg_ko', PHYLUM_COL, GENUS_COL]
SELECTED_KEYS = ['gene_id', 'length', 'kegg_ko', PHYLUM_COL, GENUS_COL]
def parse_gene(raw_line, selected_keys=SELECTED_KEYS):
......
......@@ -13,7 +13,7 @@ class TestParseGene(TestCase):
raw_data = [
'gene_id',
'gene_name',
'gene_length',
'length',
'gene_completeness_status',
'cohort_origin',
'taxo_phylum',
......@@ -34,7 +34,7 @@ class TestParseGene(TestCase):
"""
expected_dict = {
'gene_id': 'gene_name',
'gene_length': 'gene_length',
'length': 'length',
'kegg_ko': 'kegg',
'taxo_phylum': 'taxo_phylum',
'taxo_genus': 'taxo_genus',
......@@ -46,10 +46,10 @@ class TestParseGene(TestCase):
"""
This test should failed and need to be updated when SELECTED_KEYS are changed
"""
selected_keys = ['gene_id', 'gene_length']
selected_keys = ['gene_id', 'length']
expected_dict = {
'gene_id': 'gene_name',
'gene_length': 'gene_length'
'length': 'length'
}
tested_dict = parse_gene(self.raw_line, selected_keys=selected_keys)
self.assertDictEqual(tested_dict, expected_dict)
......@@ -58,10 +58,10 @@ class TestParseGene(TestCase):
"""
Unknown key should be ignored
"""
selected_keys = ['gene_id', 'gene_length', 'secret_code']
selected_keys = ['gene_id', 'length', 'secret_code']
expected_dict = {
'gene_id': 'gene_name',
'gene_length': 'gene_length'
'length': 'length'
}
tested_dict = parse_gene(self.raw_line, selected_keys=selected_keys)
self.assertDictEqual(tested_dict, expected_dict)
......@@ -72,15 +72,15 @@ class TestUpsertGene(APITestCase):
def test_insert_valid_gene_no_kegg(self):
valid_gene = {
'gene_id': 'test_gene01',
'gene_length': 3556
'length': 3556
}
upsert_gene(valid_gene)
self.assertEqual(Gene.objects.all().count(), 1)
def test_insert_invalid_gene_length(self):
def test_insert_invalid_length(self):
invalid_gene = {
'gene_id': 'test_gene01',
'gene_length': 'wrong_format'
'length': 'wrong_format'
}
with self.assertRaises(ValidationError) as context: # noqa
upsert_gene(invalid_gene)
......@@ -88,16 +88,16 @@ class TestUpsertGene(APITestCase):
def test_update_gene(self):
valid_gene = {
'gene_id': 'test_gene01',
'gene_length': 3556
'length': 3556
}
updated_gene = {
'gene_id': 'test_gene01',
'gene_length': 356
'length': 356
}
upsert_gene(valid_gene)
self.assertEqual(Gene.objects.get(gene_id="test_gene01").gene_length, 3556)
self.assertEqual(Gene.objects.get(gene_id="test_gene01").length, 3556)
upsert_gene(updated_gene)
self.assertEqual(Gene.objects.get(gene_id="test_gene01").gene_length, 356)
self.assertEqual(Gene.objects.get(gene_id="test_gene01").length, 356)
class TestSelectTaxonomy(TestCase):
......@@ -114,13 +114,13 @@ class TestSelectTaxonomy(TestCase):
def test_both_unknown(self):
gene_dict = {
'gene_id': 'gene',
'gene_length': 135,
'length': 135,
'taxo_phylum': 'unknown',
'taxo_genus': 'unknown'
}
expected_dict = {
'gene_id': 'gene',
'gene_length': 135
'length': 135
}
tested_dict = select_taxonomy(gene_dict)
self.assertDictEqual(tested_dict, expected_dict)
......@@ -24,6 +24,14 @@ spec:
serviceName: backend
servicePort: 8000
- path: /api
backend:
serviceName: backend
servicePort: 8000
- path: /swagger
backend:
serviceName: backend
servicePort: 8000
- path: /redoc
backend:
serviceName: backend
servicePort: 8000
\ No newline at end of file
......@@ -48,9 +48,7 @@ services:
- NODE_ENV=development
nginx:
build:
context: .
dockerfile: nginx/dev/Dockerfile
image: nginx:1.13.12-alpine
ports:
- "80:80"
depends_on:
......
......@@ -22,7 +22,7 @@
>
<template v-slot:items="props">
<td>{{ props.item.gene_id }}</td>
<td class="text-xs">{{ props.item.gene_length }}</td>
<td class="text-xs">{{ props.item.length }}</td>
<td class="text-xs"></td>
<td class="text-xs">{{ props.item.functions[0] }}</td>
</template>
......@@ -48,7 +48,7 @@ export default {
sortable: false,
value: 'name',
},
{ text: 'Length', value: 'gene_length' },
{ text: 'Length', value: 'length' },
{ text: 'Taxo', sortable: false },
{ text: 'Functions', value: 'functions', sortable: false },
],
......
......@@ -41,18 +41,12 @@ http {
}
# backend urls
location ~ ^/(admin|api|catalog|auth) {
location ~ ^/(admin|api|swagger|redoc|static) {
proxy_redirect off;
proxy_pass http://backend;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
}
# static files
location /static {
autoindex on;
alias /usr/src/app/static;
}
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment