webapi.py 5.28 KB
Newer Older
André Anjos's avatar
André Anjos committed
1
2
3
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :

Samuel GAIST's avatar
Samuel GAIST committed
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
###################################################################################
#                                                                                 #
# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/               #
# Contact: beat.support@idiap.ch                                                  #
#                                                                                 #
# Redistribution and use in source and binary forms, with or without              #
# modification, are permitted provided that the following conditions are met:     #
#                                                                                 #
# 1. Redistributions of source code must retain the above copyright notice, this  #
# list of conditions and the following disclaimer.                                #
#                                                                                 #
# 2. Redistributions in binary form must reproduce the above copyright notice,    #
# this list of conditions and the following disclaimer in the documentation       #
# and/or other materials provided with the distribution.                          #
#                                                                                 #
# 3. Neither the name of the copyright holder nor the names of its contributors   #
# may be used to endorse or promote products derived from this software without   #
# specific prior written permission.                                              #
#                                                                                 #
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND #
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED   #
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE          #
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE    #
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL      #
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR      #
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER      #
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,   #
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   #
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.            #
#                                                                                 #
###################################################################################
André Anjos's avatar
André Anjos committed
35
36


37
38
import requests
import simplejson as json
André Anjos's avatar
André Anjos committed
39

40
from urllib.parse import urlparse
André Anjos's avatar
André Anjos committed
41
42


43
44
45
46
47
48
49
50
class WebAPIError(RuntimeError):
    def __init__(self, cmd, url, answer):
        message = "{cmd} error {status}: {url}\nError:{text}".format(
            cmd=cmd, status=answer.status_code, url=url, text=answer.text
        )
        super().__init__(message)


Samuel GAIST's avatar
Samuel GAIST committed
51
class WebAPI(object):
52
    """Central class for all remote service related calls"""
André Anjos's avatar
André Anjos committed
53

54
55
    API_VERSION = "v1"
    API_PATH = "/api/{}".format(API_VERSION)
André Anjos's avatar
André Anjos committed
56

57
58
59
60
61
    def __init__(self, platform, user, token=None):
        self.platform = platform
        self.parsed = urlparse(self.platform)
        self.user = "**anonymous**" if token is None else user
        self.token = token
André Anjos's avatar
André Anjos committed
62

63
64
    def is_anonymous(self):
        return self.token is None
André Anjos's avatar
André Anjos committed
65

66
67
    def __enter__(self):
        return self
André Anjos's avatar
André Anjos committed
68

69
70
    def __exit__(self, *exc):
        return
André Anjos's avatar
André Anjos committed
71

72
73
74
    def _make_headers(self):
        if self.token is None:
            return None
André Anjos's avatar
André Anjos committed
75

76
77
78
79
        return {
            "Authorization": "Token %s" % (self.token),
            "Content-Type": "application/json",
        }
André Anjos's avatar
André Anjos committed
80

81
    def __build_url(self, path):
Samuel GAIST's avatar
Samuel GAIST committed
82
83
84
85
86
87
88
89
        platform = self.parsed.geturl()
        if platform.endswith("/"):
            platform = platform[:-1]

        if path.startswith("/"):
            path = path[1:]

        url = "{platform}/{path}".format(platform=platform, path=path)
90
        return url
André Anjos's avatar
André Anjos committed
91

92
93
94
    def get(self, path):
        url = self.__build_url(path)
        answer = requests.get(url, headers=self._make_headers())
André Anjos's avatar
André Anjos committed
95

96
97
        # if answer.status_code < 200 or answer.status_code >= 300:
        if answer.status_code not in [200, 204]:
98
            raise WebAPIError("GET", url, answer)
André Anjos's avatar
André Anjos committed
99

100
        return answer.json()
André Anjos's avatar
André Anjos committed
101

102
103
104
    def post(self, path, data=None):
        url = self.__build_url(path)
        answer = requests.post(url, json=data, headers=self._make_headers())
André Anjos's avatar
André Anjos committed
105

106
        if answer.status_code not in [200, 201]:
107
            raise WebAPIError("POST", url, answer)
André Anjos's avatar
André Anjos committed
108

109
110
111
112
        try:
            return answer.json()
        except json.JSONDecodeError:
            return answer.text
André Anjos's avatar
André Anjos committed
113

114
115
116
    def put(self, path, data=None):
        url = self.__build_url(path)
        answer = requests.put(url, json=data, headers=self._make_headers())
André Anjos's avatar
André Anjos committed
117

118
        if answer.status_code not in [200, 204]:
119
            raise WebAPIError("PUT", url, answer)
André Anjos's avatar
André Anjos committed
120

121
122
123
124
        try:
            return answer.json()
        except json.JSONDecodeError:
            return answer.text
André Anjos's avatar
André Anjos committed
125

126
127
128
    def delete(self, path):
        url = self.__build_url(path)
        answer = requests.delete(url, headers=self._make_headers())
André Anjos's avatar
André Anjos committed
129

130
131
        # Should respond a 204 status and an empty body
        if answer.status_code not in [200, 204]:
132
            raise WebAPIError("DELETE", url, answer)