Skip to content

Commit

Permalink
Initial client api deployment
Browse files Browse the repository at this point in the history
  • Loading branch information
ckopanos committed Jun 22, 2016
0 parents commit f25f327
Show file tree
Hide file tree
Showing 9 changed files with 363 additions and 0 deletions.
108 changes: 108 additions & 0 deletions .gitignore
@@ -0,0 +1,108 @@
# Created by .ignore support plugin (hsz.mobi)
### VirtualEnv template
# Virtualenv
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
.Python
[Bb]in
[Ii]nclude
[Ll]ib
[Ll]ib64
[Ll]ocal
[Ss]cripts
pyvenv.cfg
.venv
pip-selfcheck.json
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# IPython Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# dotenv
.env

# virtualenv
venv/
ENV/

# Spyder project settings
.spyderproject

# Rope project settings
.ropeproject

#jetbrains ide

.idea
Empty file added LICENSE
Empty file.
7 changes: 7 additions & 0 deletions MANIFEST.in
@@ -0,0 +1,7 @@
include LICENSE
include MANIFEST.in
include README.rst
include setup.py
include run.py
recursive-exclude * __pycache__
recursive-exclude * *.py[co]
67 changes: 67 additions & 0 deletions README.md
@@ -0,0 +1,67 @@
# Variant API Client

## A basic api client implementation for [api.varsome.com](https://api.varsome.com)

This client is still in beta but it is a good start to start playing around with the API.

### Installation

Either download clone the repository from github and place the variantapi package
within your code, or do

pip install https://github.com/saphetor/variant-api-client-python/archive/master.zip

### API Documentation

Please visit the [api documentation](http://docs.testapi2127.apiary.io/) to find out how to use the api and
what values does the api provide as a response to lookup requests

### Using the client in your code

Using the api client is quite straightforward. Just install the api client package and from within
your code use

from variantapi.client import VariantAPIClient
# api key is not required for single variant lookups
api_key = 'Your token'
api = VariantAPIClient(api_key)
# fetch information about a variant into a dictionary
result = api.lookup('chr19:20082943:1:G', ref_genome=1019)
# access results e.g. the sequence around the variant
sequence = result['ref_seq']['sequence']
# fetch information for multiple variants
variants = ['chr19:20082943:1:G','chr22:39777823::CAA']
# results will be an array of dictionaries an api key will be required for this request
results = api.batch_lookup(variants, ref_genome=1019)

If errors occur while using the client an exception will be thrown.
You may wish to catch this exception and proceed with your own code logic

from variantapi.client import VariantAPIClient, VariantApiException
api = VariantAPIClient()
try:
result = api.lookup('chr19:20082943:1:G', ref_genome=1054)
except VariantApiException as e:
# proceed with your code flow e.g.
print(e) # 404 (invalid reference genome)

### Example Usage


You may download and run the run.py python file after installation of the package
to test the api client directly e.g.

./run.py -g 1019 -q 'chr19:20082943:1:G'

You may pass more than one values after the -q argument that will make a batch request
to the API but you will need a token to do that e.g.

./run.py -k 'your token' -g 1019 -q 'rs113488022' 'chr19:20082943:1:G'

Run

./run.py -h

for a list of available options


40 changes: 40 additions & 0 deletions run.py
@@ -0,0 +1,40 @@
#!/usr/bin/env python
import argparse
import json
import logging
import sys

from variantapi.client import VariantAPIClient

__author__ = 'ckopanos'
logging.basicConfig(level=logging.DEBUG,
format='[%(levelname)s] %(threadName)s %(message)s',
)


def main(argv):
parser = argparse.ArgumentParser(description='Sample Variant API calls')
parser.add_argument('-k', help='Your key to the API', type=str, metavar='API Key', required=False)
parser.add_argument('-g', help='Reference genome either 1019 or 1038', type=int, metavar='Reference Genome',
required=False, default=1019)
parser.add_argument('-q',
help='Query to lookup in the API e.g. chr19:20082943:1:G or in case of batch request '
'e.g. chr19:20082943:1:G rs113488022',
type=str, metavar='Query', required=True, nargs='+')
args = parser.parse_args()
api_key = args.k
query = args.q
ref_genome = args.g
api = VariantAPIClient(api_key)
if len(query) == 1:
result = api.lookup(query[0], ref_genome=ref_genome)
else:
if api_key is None:
sys.exit("You need to pass an api key to perform batch requests")
result = api.batch_lookup(query, ref_genome=ref_genome)
sys.stdout.write(json.dumps(result, indent=4, sort_keys=True) if result else "No result")
sys.stdout.write("\n")


if __name__ == "__main__":
main(sys.argv[1:])
2 changes: 2 additions & 0 deletions setup.cfg
@@ -0,0 +1,2 @@
[bdist_wheel]
universal=1
34 changes: 34 additions & 0 deletions setup.py
@@ -0,0 +1,34 @@
from setuptools import setup
from os import path

VERSION = (1, 0, '0b1')
__version__ = VERSION
__versionstr__ = '.'.join(map(str, VERSION))

here = path.abspath(path.dirname(__file__))

setup(
name='variant_api',
version=__versionstr__,
packages=['variantapi'],
url='https://github.com/saphetor/variant-api-client-python',
license='Apache License, Version 2.0',
author='Saphetor',
author_email='support@saphetor.com',
description='A basic python api client implementation for https://api.varsome.com',
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Operating System :: OS Independent',
'License :: OSI Approved :: Apache License',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
],
install_requires=[
'requests>=2.0.0, <3.0.0'
],
)
Empty file added variantapi/__init__.py
Empty file.
105 changes: 105 additions & 0 deletions variantapi/client.py
@@ -0,0 +1,105 @@
import logging

import requests
from requests.exceptions import HTTPError, Timeout, ConnectionError, RequestException



class VariantApiException(Exception):
ERROR_CODES = {
400: "Bad request. A parameter you have passed is not valid, or something in your request is wrong",
401: "Not Authorized: either you need to provide authentication credentials, or the credentials provided aren't"
" valid.",
403: "Bad Request: your request is invalid, and we'll return an error message that tells you why. This is the "
"status code returned if you've exceeded the rate limit (see below).",
404: "Not Found: either you're requesting an invalid URI or the resource in question doesn't exist",
500: "Internal Server Error: we did something wrong.",
501: "Not implemented.",
502: "Bad Gateway: returned if VariantAPI is down or being upgraded.",
503: "Service Unavailable: the VariantAPI servers are up, but are overloaded with requests. Try again later.",
504: "Gateway Timeout",
}

def __init__(self, status, response=None):
self.status = status
self.response = response

def __str__(self):
return "%s (%s)" % (
self.status,
self.ERROR_CODES.get(self.status, 'Unknown error.') if self.response is None else self.response)

def __repr__(self):
return "%s(status=%s)" % (self.__class__.__name__, self.status)


class VariantAPIClientBase(object):

_api_url = 'https://api.varsome.com'
_accepted_methods = ('GET', 'POST')

def __init__(self, api_key=None):
self.api_key = api_key
self._headers = {'Accept': 'application/json'}
if self.api_key is not None:
self._headers['Authorization'] = "Token " + self.api_key
self.session = requests.Session()
self.session.headers.update(self._headers)

def _make_request(self, path, method="GET", data=None, json_data=None):
if method not in self._accepted_methods:
raise VariantApiException('', "Unsupported method %s" % method)
try:
if method == "GET":
r = self.session.get(self._api_url + path, data=data)
if method == "POST":
r = self.session.post(self._api_url + path, data=data, json=json_data,
headers={'Content-Type': 'application/json'} if json_data is not None else None)
logging.debug('Time between request and response %s' % r.elapsed)
logging.debug('Content length %s' % len(r.content))
if r.status_code in VariantApiException.ERROR_CODES:
raise VariantApiException(
r.status_code,
r.json()['detail']
if r.headers['Content-Type'] == "application/json" else None)
return r
except HTTPError as e:
raise VariantApiException('', "Unknown http error %s" % e)
except Timeout as e:
raise VariantApiException('', "Request timed out %s" % e)
except ConnectionError as e:
raise VariantApiException('', "Connection failure or connection refused %s" % e)
except RequestException as e:
raise VariantApiException('', "Unknown error %s" % e.response)

def get(self, path, data=None):
response = self._make_request(path, "GET", data=data)
return response.json()

def post(self, path, data=None, json_data=None):
response = self._make_request(path, "POST", data=data, json_data=json_data)
return response.json()


class VariantAPIClient(VariantAPIClientBase):
schema_lookup_path = "/lookup/schema/"
lookup_path = "/lookup/%s/%s"
batch_lookup_path = "/lookup/batch/%s"
_max_variants_per_batch = 8000

def __init__(self, api_key=None):
super().__init__(api_key)

def schema(self):
return self.get(self.schema_lookup_path)

def lookup(self, query, ref_genome=1019):
return self.get(self.lookup_path % (query, ref_genome))

def batch_lookup(self, variants, ref_genome=1019):
results = []
for queries in [variants[x:x + self._max_variants_per_batch] for x in range(0, len(variants),
self._max_variants_per_batch)]:
data = self.post(self.batch_lookup_path % ref_genome, json_data={'variants': queries})
results.extend(data)
return results

0 comments on commit f25f327

Please sign in to comment.