This folder contains the Django views inplemented for the InterPro7 API.
Our strategy was to have a hierarchical structure (OOP) for all our views with a single entry point.
| | <- | GeneralHandler |
| GenericAPIView | <- | CustomView |
| | <- | *BlockHandler |
The urls.py
file is rather simple and just points
every path starting with /api
to the common view: GeneralHandler
.
urlpatterns = [url(r"^api/(?P<url>.*)$", common.GeneralHandler.as_view())]
As seen above a valid URL on the InterPro7 API should start with /api
.
From then onward, we will split the URL on the parts separated by /
, and each of those
parts will be called block in this document. (e.g. /api/[block1]/[block2]/...)
A set of blocks can create an endpoint-block. For example/api/protein/reviewed
defines a
single endpoint-block formed by 2 blocks: /protein
indicating that we are using the
protein endpoint and /reviewed
indicating the database to be filtered by.
We have define a structure that allows to combine information from multiple endpoints.
The first endpoint-block will be called main-endpoint-block. Any following
endpoint-block are considered filters.
In this way, the main-endpoint-block defines the set to return, and the rest of the endpoints
filter the set. For example /api/protein/reviewed/entry/interpro
is a list of reviewed proteins
that have matches with InterPro entries; in contrast of /api/entry/interpro/protein/reviewed
,
which is a list of InterPro entries that can match reviewed proteins.
This configuration ensures that all the API requests are first managed in a single place, including all the common logic:
- Defines the available endpoints
- For the current request
- Initializes the QuerysetManager (Read More).
- Initializes the SearchController (Read More).
- Initializes the ModifierManager (Read More).
- Splits a given URL into blocks.
- If there are not blocks generates the response for the root query i.e.
/api/
. - Tries to get the response from the redis cache.
- A recursion chain gets started: it invokes the
get()
method of its parent classCustomView
and recursively finding a handler for each block.
Besides using the cache for fast responses, we use it to avoid duplication of expensive queries.
When a query is executed it has 90 seconds (by default) to get a response.
Otherwise the response will be a time put HTTP code 408
, which will be temporarily saved in the
cache.
This however won't interrupt the query, which will keep its execution in parallel.
if a duplicate request arrives before the original request finishes, it will automatically get the
408
from the cache.
When the original request completes, it saves the response in the cache, replacing the 408
one.
This way, any future duplicate request will get the value from the cache almost instantly.
All block handlers inherit from CustomView
and have to implement their get()
method.
Basically the task of the get()
method in CustomView
is to find what is the most appropriate
handler for the current block, and once it founds it invokes the get()
method of such handler.
The usual tasks of a handler and in particular of the get()
method are:
- To add more filters to the current queryset. For example in a URL
/api/entry/interpro
the handler of the/interpro
block adds a filter likesource_database="interpro"
. - To define modifiers. Which is our strategy to extend the API, for example the modifier
go_term
allows to filter a set of entries, selecting those which are annotated with a given GO ID. - To define a serializer linked to this block. The actual serializer that a response will use, it is the one linked to the last block of the main-endpoint-block.
- Finally, the
get()
method will return the result of invoking theget()
method of its parent class. This, of course is when the recursion occurs.
Once all the blocks of the main endpoint have been exhausted, is is time to process the filters:
The logic is very similar, but now the method to call in all the handlers is filter()
.
The filter()
method should be defined as static and should return the filtered queryset.
This is then repeated for the rest of the endpoint-blocks
After processing the filters the last call of the recursion occurs, and because there are not more blocks, we should finish the response, which implies the execution of any available modifiers, setting up the pagination in case of responses with many items, and finally serializing the built queryset.