Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 8da0bdb7 authored by LETORT Sebastien's avatar LETORT Sebastien
Browse files

Major update, the client has been rewritten.

parent fc55d0dd
Branches
No related tags found
1 merge request!4Client api
#! /usr/bin/env python
# -*- coding: utf-8 -*-
"""
Python client for the Allgo API.
This module provides a class that simplify the use of the Allgo API
"""
# standard libs
import logging
import os
import time
try:
from urllib.parse import urlparse, urlencode
from urllib.request import urlopen, Request
from urllib.error import HTTPError
except ImportError:
from urlparse import urlparse
from urllib import urlencode
from urllib2 import urlopen, Request, HTTPError
import sys
# from json.decoder import JSONDecodeError # python3
from os.path import expanduser
import requests
log = logging.getLogger('allgo')
__version__ = '0.1.11'
__version__ = '0.2.0'
#: The default Allgo url used.
MAIN_INSTANCE_URL = "https://allgo18.inria.fr"
#: The file containing the user token.
#: It is read in last resort to get a token.
TOKEN_FILE = os.path.join(expanduser("~"), '.allgo_token')
# ===============================================================
class AllgoError(Exception):
"""Generic error generated by this module."""
class TokenError(AllgoError):
"""Raise TokenError when none is set."""
def __init__(self):
err_msg = "You must provide a token in parameter"
err_msg += " or define an environment variable 'ALLGO_TOKEN'"
err_msg += " or write your token in {}".format(TOKEN_FILE)
super(TokenError, self).__init__(err_msg)
def local_token():
from os.path import expanduser
home = expanduser("~")
filetoken = os.path.join(home, '.allgo_token')
if os.path.exists(filetoken):
with open(filetoken) as f:
class StatusError(AllgoError):
"""Raise StatusError when API does not return a 200 code.
"""
def __init__(self, status_code, response, *args, **kwargs):
"""TODO"""
self._status_code = status_code
self._response = response
AllgoError.__init__(self, *args, **kwargs)
@property
def status_code(self):
return self._status_code
@property
def msg(self):
return self._response.json()['error']
def _local_token():
"""read and return the content of TOKEN_FILE or None."""
if os.path.exists(TOKEN_FILE):
with open(TOKEN_FILE) as f:
return f.read()
return None
class App:
# ---------------------------------------------------------------
class Client:
"""
AllGo app submission object
manage connexions to the API and provide methods to deal with it.
"""
def __init__(self, name, token=None):
def __init__(self, token=None, allgo_url=MAIN_INSTANCE_URL, verify_tls=True):
"""Build an object to manage the parameters of a user-connexion to an Allgo-instance.
"""
Constructor
:param name: name of the application in lower case
:param token: if not provided, we check ALLGO_TOKEN env variable and notebook parameters
self._allgo_url = allgo_url
self._verify_tls = verify_tls
self._token = token or os.getenv('ALLGO_TOKEN') or _local_token()
if None is self._token:
raise TokenError()
@property
def token(self):
"""return the user token for the querying all instance."""
return self._token
@property
def allgo_url(self):
"""return the url of the querying allgo instance."""
return self._allgo_url
@property
def verify_tls(self):
"""return the _verify_tls attribute."""
return self._verify_tls
def _request_api(self, method, url_end, **kwargs):
"""Build and launch a http request on the Allgo instance API.
"""
self.name = name
if token:
self.token = token
elif 'ALLGO_TOKEN' in os.environ.keys():
self.token = os.environ.get('ALLGO_TOKEN')
elif local_token():
self.token = local_token()
else:
err_msg = "You must provide a token in parameter"
err_msg += " or define an environment variable 'ALLGO_TOKEN'"
raise Exception(err_msg)
def run(self, files, outputdir='.', params='', verify_tls=True):
headers = {'Authorization': 'Token token={}'.format(self.token)}
url = '{}/api/v1/{}'.format(self.allgo_url, url_end)
logging.debug("querying '{}' with method '{}'".format(url, method))
logging.debug("and with data = {}".format(kwargs))
actions = {
'post': requests.post,
'get' : requests.get,
}
try:
resp = actions[method](url, headers=headers, verify=self.verify_tls, **kwargs)
logging.debug("raw API answer : {}".format(resp.content))
if resp.status_code != requests.codes.ok:
logging.debug("req.status_code = {}".format(resp.status_code))
raise StatusError(resp.status_code, resp)
return resp.json()
except ValueError: # pure python3 should use JSONDecodeError
# this happens when response content cannot be converted to json
# for example when downloading a file.
return resp
except KeyError as err:
msg = "method {} is not allowed to request the API.".format(method)
msg += "KeyError: {}".format(str(err))
raise AllgoError(msg)
def create_job(self, app, version=None, params='', files=None):
"""Create a job for app, with params.
"""
Submit the job
:param files: input files
:param outputdir: by default current directory
:param params: a string parameters see the application documentation
:param verify_tls: [True] the value is pass to the verify arg of requests.post
:return:
data = {
"job[webapp_name]": app,
"job[webapp_id]" : app,
"job[param]" : params,
"job[version]" : version
}
file_dict = {}
if None is files:
files = []
for i, file_ in enumerate(files):
key = "file[{}]".format(i)
file_dict[key] = open(file_, 'rb')
return self._request_api('post', url_end='jobs', data=data, files=file_dict)
def job_status(self, job_id):
"""Get the status of a job as a dict.
"""
headers = {'Authorization': 'Token token={}'.format(self.token)}
data = {"job[webapp_name]": self.name,
"job[webapp_id]": self.name,
"job[param]": params}
ALLGO_URL = os.environ.get('ALLGO_URL', "https://allgo.inria.fr")
url = '%s/api/v1/jobs' % ALLGO_URL
r = requests.post(url, headers=headers, files=files, data=data, verify=verify_tls)
r.raise_for_status()
r = r.json()
if 'id' in r.keys():
jobid = r['id']
else:
jobid = list(r.keys())[0]
results = None
url = 'jobs/{}'.format(job_id)
return self._request_api('get', url)
def app_metrics(self, app_id, what, from_=None, to_=None, step=None):
"""query the instance for some metrics.
**Not yet implemented.**
"""
raise NotImplementedError("metrics API has not been integrated yet.")
# --------------------------------------------
# -- Accessory methods, to ease user work.
# --------------------------------------------
def run_job(self, app, version=None, params='', files=None,
sleep_duration=2, verbose=False):
"""Create a job and wait for it to terminate.
"""
from time import sleep
# Note: states new, and deleted are almost impossible to get.
STATES = ['NEW', 'WAITING', 'RUNNING', 'ARCHIVED', 'DONE', 'DELETED', 'ABORTING']
out_dict = self.create_job(app, version=version, params=params, files=files)
job_id = out_dict['id']
flags = { s.lower(): False for s in STATES }
while True:
url = '{}/api/v1/jobs/{}'.format(ALLGO_URL, jobid)
r = requests.get(url, headers=headers, verify=verify_tls)
r.raise_for_status()
results = r.json()
if 'status' in results.keys():
status = results['status']
else:
status = list(results.values())[0]['status']
if status in ['created', 'waiting', 'running', 'in progress']:
log.info("wait for job %s in status %s", jobid, status)
time.sleep(2)
else:
break
if status != 'done':
raise Exception('Job %s failed with status %s', (jobid, status))
elif status == 'done' and results:
if 'id' in results.keys():
files = results[str(jobid)].items()
else:
files = results[str(jobid)]['files'].items()
for filename, url in files:
filepath = os.path.join(outputdir, filename)
with requests.get(url, headers=headers, verify=verify_tls, stream=True) as r:
r.raise_for_status()
with open(filepath, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
sleep(sleep_duration)
out_dict = self.job_status(job_id)
status = out_dict['status']
if True is verbose:
if False is flags[status]:
print("\n{}\t".format(status), end='')
flags[status] = True
else:
print(".", end='')
sys.stdout.flush()
if status == 'done':
if True is verbose:
print("") # to get a return line
out_dict['id'] = job_id
return out_dict
# should never goes here
return out_dict
......@@ -5,21 +5,23 @@ import allgo
setup(
name='allgo',
version=allgo.__version__,
packages=find_packages(),
author="Sebastien Campion",
author_email="sebastien.campion@inria.fr",
description="AllGo client module",
install_requires=['requests'],
packages=find_packages(exclude=['doc', 'test*']),
author="Sebastien Letort",
author_email="sebastien.letort@inria.fr",
description="AllGo client-API module",
long_description=open('README.md').read(),
long_description_content_type='text/markdown',
include_package_data=True,
url='https://gitlab.inria.fr/allgo/client',
url='https://gitlab.inria.fr/allgo/api-clients/python_client',
classifiers=[
"Programming Language :: Python",
"Development Status :: 1 - Planning",
"Programming Language :: Python :: 3",
"Development Status :: 3 - Alpha",
"License :: OSI Approved",
"Natural Language :: French",
"Operating System :: OS Independent",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Topic :: Communications",
],
license="AGPL",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment