Skip to content

Commit

Permalink
Adding REST API versioning (#333)
Browse files Browse the repository at this point in the history
Closes #333
  • Loading branch information
holtgrewe committed Feb 7, 2022
1 parent 23f117a commit 63a29ea
Show file tree
Hide file tree
Showing 10 changed files with 235 additions and 36 deletions.
1 change: 1 addition & 0 deletions HISTORY.rst
Expand Up @@ -91,6 +91,7 @@ Full Change List
- Fixing CADD annotation (#319)
- Adding mitochondrial inheritance to case phenotype annotation (#325)
- Fix issue with variant annotation export (#328)
- Adding REST API versioning to (#333)

-------
v0.23.9
Expand Down
6 changes: 0 additions & 6 deletions beaconsite/tests/test_views_api.py
@@ -1,11 +1,5 @@
import urllib
import datetime

import cattr
from Crypto.PublicKey import RSA
from django.shortcuts import reverse
from django.utils import timezone
from projectroles.tests.test_permissions_api import TestProjectAPIPermissionBase
from projectroles.tests.test_views_api import TestAPIViewsBase

from variants.tests.factories import SmallVariantFactory
Expand Down
63 changes: 63 additions & 0 deletions docs_manual/api_overview.rst
@@ -0,0 +1,63 @@
.. _api_overview:

=================
REST API Overview
=================

Varfish provides a growing set of REST APIs.
You can find an Python library for accessing the API and a command line interface in `varfish-cli <https://github.com/bihealth/varfish-cli>`_.

.. note::
This documentation section is under development.

-------------
Using the API
-------------

Usage of the REST API is detailed in this section.
Basic knowledge of HTTP APIs is assumed.

Authentication
==============

The API supports authentication through Knox authentication tokens as well as logging in using your SODAR username and password.
Tokens are the recommended method for security purposes.

For token access, first retrieve your token using the **API Tokens** site app on the VarFish web UI.
Note that you can you only see the token once when creating it.

Add the token in the ``Authorization`` header of your HTTP request as follows:

.. code-block:: console
Authorization: token 90c2483172515bc8f6d52fd608e5031db3fcdc06d5a83b24bec1688f39b72bcd
Versioning
==========

The SODAR REST API uses accept header versioning.
While specifying the desired API version in your HTTP requests is optional, it is **strongly recommended**.
This ensures you will get the appropriate return data and avoid running into unexpected incompatibility issues.

To enable versioning, add the ``Accept`` header to your request with the following media type and version syntax.
Replace the version number with your expected version.

Specific sections of the SODAR API may require their own accept header.
See the exact header requirement in the respective documentation on each section of the API.

Model Access and Permissions
============================

Objects in SODAR API views are accessed through their ``sodar_uuid`` field.

In the REST API documentation, *"UUID"* refers to the ``sodar_uuid`` field of each model unless otherwise noted.

For permissions the API uses the same rules which are in effect in the SODAR GUI.
That means you need to have appropriate project access for each operation.

Return Data
===========

The return data for each request will be a JSON document unless otherwise specified.

If return data is not specified in the documentation of an API view, it will return the appropriate HTTP status code along with an optional ``detail`` JSON field upon a successfully processed request.
13 changes: 13 additions & 0 deletions docs_manual/index.rst
Expand Up @@ -117,6 +117,19 @@ Currently, the main focus is on small/sequence variants called from high-througp
sop_supporting
sop_filtration

.. raw:: latex

\part{REST API}

.. toctree::
:maxdepth: 1
:caption: REST API
:name: api-docs
:hidden:
:titlesonly:

api_overview

.. raw:: latex

\part{Developer's Manual}
Expand Down
72 changes: 50 additions & 22 deletions importer/tests/test_views_api.py
@@ -1,11 +1,18 @@
from varfish import __version__ as varfish_version
from varfish.api_utils import VARFISH_API_DEFAULT_VERSION, VARFISH_API_MEDIA_TYPE

import os
from itertools import chain

from django.contrib.auth import get_user_model
from django.urls import reverse
from django.forms import model_to_dict

from variants.tests.helpers import ApiViewTestBase
from variants.tests.helpers import (
ApiViewTestBase,
VARFISH_INVALID_VERSION,
VARFISH_INVALID_MIMETYPE,
)
from variants.tests.test_views_api import transmogrify_pedigree

from ..models import CaseImportInfo, VariantSetImportInfo, CaseVariantType, BamQcFile, GenotypeFile
Expand All @@ -16,6 +23,15 @@
GenotypeFileFactory,
)

#: A known invalid MIME type.
INVALID_MIMETYPE = "application/vnd.bihealth.invalid+json"
#: A known invalid version.
INVALID_VERSION = "0.0.0"
#: The known valid MIME type.
VALID_MIMETYPE = "application/vnd.bihealth.varfish+json"
#: A known valid version.
VALID_VERSION = varfish_version

#: The User model to use.
User = get_user_model()

Expand Down Expand Up @@ -84,11 +100,12 @@ def test_create(self):
post_data = case_import_info_to_dict(obj, self.project, exclude=("sodar_uuid",))

with self.login(self.user):
response = self.client.post(
response = self.request_knox(
reverse(
"importer:api-case-import-info-list-create",
kwargs={"project": self.project.sodar_uuid},
),
method="POST",
data=post_data,
format="json",
)
Expand All @@ -105,7 +122,7 @@ def test_create(self):

def test_retrieve(self):
with self.login(self.user):
response = self.client.get(
response = self.request_knox(
reverse(
"importer:api-case-import-info-retrieve-update-destroy",
kwargs={
Expand Down Expand Up @@ -138,14 +155,15 @@ def test_update(self):
}

with self.login(self.user):
response = self.client.patch(
response = self.request_knox(
reverse(
"importer:api-case-import-info-retrieve-update-destroy",
kwargs={
"project": self.project.sodar_uuid,
"caseimportinfo": self.case_import_info.sodar_uuid,
},
),
method="PATCH",
data=post_data,
format="json",
)
Expand All @@ -166,14 +184,15 @@ def test_update(self):

def test_destroy(self):
with self.login(self.user):
response = self.client.delete(
response = self.request_knox(
reverse(
"importer:api-case-import-info-retrieve-update-destroy",
kwargs={
"project": self.project.sodar_uuid,
"caseimportinfo": self.case_import_info.sodar_uuid,
},
)
),
method="DELETE",
)

expected = None
Expand Down Expand Up @@ -204,7 +223,7 @@ def setUp(self):

def test_list(self):
with self.login(self.user):
response = self.client.get(
response = self.request_knox(
reverse(
"importer:api-variant-set-import-info-list-create",
kwargs={"caseimportinfo": self.case_import_info.sodar_uuid},
Expand Down Expand Up @@ -240,11 +259,12 @@ def test_create(self):
)

with self.login(self.user):
response = self.client.post(
response = self.request_knox(
reverse(
"importer:api-variant-set-import-info-list-create",
kwargs={"caseimportinfo": self.case_import_info.sodar_uuid},
),
method="POST",
data=post_data,
format="json",
)
Expand All @@ -259,7 +279,7 @@ def test_create(self):

def test_retrieve(self):
with self.login(self.user):
response = self.client.get(
response = self.request_knox(
reverse(
"importer:api-variant-set-import-info-retrieve-update-destroy",
kwargs={
Expand Down Expand Up @@ -287,14 +307,15 @@ def test_update(self):
}

with self.login(self.user):
response = self.client.patch(
response = self.request_knox(
reverse(
"importer:api-variant-set-import-info-retrieve-update-destroy",
kwargs={
"caseimportinfo": self.case_import_info.sodar_uuid,
"variantsetimportinfo": self.variant_set_import_info.sodar_uuid,
},
),
method="PATCH",
data=post_data,
format="json",
)
Expand All @@ -315,14 +336,15 @@ def test_update(self):

def test_destroy(self):
with self.login(self.user):
response = self.client.delete(
response = self.request_knox(
reverse(
"importer:api-variant-set-import-info-retrieve-update-destroy",
kwargs={
"caseimportinfo": self.case_import_info.sodar_uuid,
"variantsetimportinfo": self.variant_set_import_info.sodar_uuid,
},
)
),
method="DELETE",
)

expected = None
Expand All @@ -349,7 +371,7 @@ def setUp(self):

def test_list(self):
with self.login(self.user):
response = self.client.get(
response = self.request_knox(
reverse(
"importer:api-bam-qc-file-list-create",
kwargs={"caseimportinfo": self.case_import_info.sodar_uuid},
Expand All @@ -375,11 +397,13 @@ def test_create(self):
with open(
os.path.join(os.path.dirname(__file__), "data", "example.tsv"), "rb"
) as upload_file:
response = self.client.post(
response = self.request_knox(
reverse(
"importer:api-bam-qc-file-list-create",
kwargs={"caseimportinfo": self.case_import_info.sodar_uuid},
),
format="multipart",
method="POST",
data={**post_data, "file": upload_file},
)

Expand All @@ -393,7 +417,7 @@ def test_create(self):

def test_retrieve(self):
with self.login(self.user):
response = self.client.get(
response = self.request_knox(
reverse(
"importer:api-bam-qc-file-retrieve-destroy",
kwargs={
Expand All @@ -411,14 +435,15 @@ def test_retrieve(self):

def test_destroy(self):
with self.login(self.user):
response = self.client.delete(
response = self.request_knox(
reverse(
"importer:api-bam-qc-file-retrieve-destroy",
kwargs={
"caseimportinfo": self.case_import_info.sodar_uuid,
"bamqcfile": self.bam_qc_file.sodar_uuid,
},
)
),
method="DELETE",
)

expected = None
Expand Down Expand Up @@ -448,7 +473,7 @@ def setUp(self):

def test_list(self):
with self.login(self.user):
response = self.client.get(
response = self.request_knox(
reverse(
"importer:api-genotype-file-list-create",
kwargs={"variantsetimportinfo": self.variant_set_import_info.sodar_uuid},
Expand Down Expand Up @@ -476,11 +501,13 @@ def test_create(self):
with open(
os.path.join(os.path.dirname(__file__), "data", "example.tsv"), "rb"
) as upload_file:
response = self.client.post(
response = self.request_knox(
reverse(
"importer:api-genotype-file-list-create",
kwargs={"variantsetimportinfo": self.variant_set_import_info.sodar_uuid},
),
method="POST",
format="multipart",
data={**post_data, "file": upload_file},
)

Expand All @@ -494,7 +521,7 @@ def test_create(self):

def test_retrieve(self):
with self.login(self.user):
response = self.client.get(
response = self.request_knox(
reverse(
"importer:api-genotype-file-retrieve-destroy",
kwargs={
Expand All @@ -512,14 +539,15 @@ def test_retrieve(self):

def test_destroy(self):
with self.login(self.user):
response = self.client.delete(
response = self.request_knox(
reverse(
"importer:api-genotype-file-retrieve-destroy",
kwargs={
"variantsetimportinfo": self.variant_set_import_info.sodar_uuid,
"genotypefile": self.genotype_file.sodar_uuid,
},
)
),
method="DELETE",
)

expected = None
Expand Down

0 comments on commit 63a29ea

Please sign in to comment.