Source code for ensembl_rest.core.baseclient
"""
This file is part of pyEnsembl.
Copyright (C) 2018, Andrés García
Any questions, comments or issues can be addressed to a.garcia230395@gmail.com.
"""
from .restclient import RESTClient, HTTPError
class BaseEnsemblRESTClient:
"""Base client for an Ensembl REST API."""
def __init__(self, base_url=None):
if base_url is None:
base_url = self.base_url
self.rest_client = RESTClient(base_url)
# ----
def make_request(self, resource, *args, params=None, **kwargs):
"Follow the route mapping the arguments to the correct place."
# Assemble the request
request_type, route_template = resource.split(' ')
route = self._map_arguments(route_template, *args, **kwargs)
endpoint = self.rest_client.route(*route)
# Perform the request
while True:
try:
return endpoint.do(request_type, params)
# Handle rate limit
except HTTPError as e:
if e.response.status_code == 429:
# Maximum requests rate exceded
# Need to wait
response = e.response
wait_time = float(response.headers["Retry-After"])
sys.stderr.write('Maximum requests limit reached, waiting for '
+ str(wait_time)
+ 'secs')
time.sleep(wait_time)
else:
raise
# ---
def _map_arguments(self, route, *args, **kwargs):
"""Map the arguments to the template.
The template is a string of the form:
"some/:string/with/:semicolons"
First maps the **kwargs, then maps the *args in order.
If this fails throw an exception.
"""
# First split into arguments:
# "some/:string/with/:semicolons" -> ['some', ':string', 'with', ':semicolons']
parts = route.split('/')
# Then replace the semicolons with format strings
parts = ['{' + s[1:] + '}' if s.startswith(':') else s for s in parts]
# Then map the keyword arguments
parts = [self._format_if_possible(s, **kwargs) for s in parts]
# Finally, map the positional arguments
args_iter = iter(args)
mapped_parts = []
for s in parts:
if s.startswith('{'):
try:
mapped_parts.append(next(args_iter))
except StopIteration:
# Parameter not provided, skip in case it is optional
pass
else:
mapped_parts.append(s)
return mapped_parts
# ---
def _format_if_possible(self, format_string, **kwargs):
"""Try to apply the arguments to the format string.
If not possible, return the string unchanged.
"""
try:
return format_string.format(**kwargs)
except KeyError:
return format_string
# ---
# --- BaseClient
## Now are the factories for the automated creation of the classes
## Y Y Y Y Y
## | | | | |
## v v v v v
def _create_method(method_name, endpoint):
"""Create a class method"""
def method(self, *args, **kwargs):
return self.make_request(endpoint['resource'], *args, **kwargs)
# ---
method.__doc__ = (
f"``{endpoint['resource']}``\n\n"
f"{endpoint['description']}\n"
f"- More info: {endpoint['documentation_link']}"
)
method.__name__ = method_name
return method
# ---
def build_client_class(name, api_table, doc=''):
"""Create a new class that implements the methods of the API."""
# Create the class dictionary
class_dict = {'__doc__': doc,
'base_url': api_table['base_url']}
# Create the class methods
methods = {ep_name : _create_method(ep_name, endpoint)
for ep_name, endpoint in api_table['endpoints'].items()}
class_dict.update(methods)
# Create the class (a subclass of the BaseEnsemblRESTClient)
return type(name, (BaseEnsemblRESTClient,), class_dict)
# ---