comparison venv/lib/python2.7/site-packages/bioblend/galaxy/client.py @ 0:d67268158946 draft

planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
author bcclaywell
date Mon, 12 Oct 2015 17:43:33 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:d67268158946
1 """
2 An interface the clients should implement.
3
4 This class is primarily a helper for the library and user code
5 should not use it directly.
6 """
7
8 import json
9 import time
10
11 import requests
12 try:
13 # The following import will work only for Requests >= 2.4.0 and is
14 # needed to workaround its "urllib3.exceptions.ProtocolError not
15 # wrapped" bug: https://github.com/kennethreitz/requests/issues/2192
16 # pylint: disable=E0611,F0401
17 from requests.packages.urllib3.exceptions import ProtocolError
18 # pylint: enable=E0611,F0401
19 except ImportError:
20 ProtocolError = None # pylint: disable=C0103
21
22 import bioblend as bb
23
24
25 class ConnectionError(Exception):
26 """
27 An exception class that is raised when unexpected HTTP responses come back.
28
29 Should make it easier to debug when strange HTTP things happen such as a
30 proxy server getting in the way of the request etc.
31 @see: body attribute to see the content of the http response
32 """
33 def __init__(self, message, body=None):
34 super(ConnectionError, self).__init__(message)
35 self.body = body
36
37 def __str__(self):
38 return "{0}: {1}".format(self.args[0], self.body)
39
40
41 class Client(object):
42
43 # Class variables that configure GET request retries. Note that since these
44 # are class variables their values are shared by all Client instances --
45 # i.e., HistoryClient, WorkflowClient, etc.
46 #
47 # Number of attempts before giving up on a GET request.
48 _max_get_retries = 1
49 # Delay in seconds between subsequent retries.
50 _get_retry_delay = 10
51
52 @classmethod
53 def max_get_retries(cls):
54 """
55 The maximum number of attempts for a GET request.
56 """
57 return cls._max_get_retries
58
59 @classmethod
60 def set_max_get_retries(cls, value):
61 """
62 Set the maximum number of attempts for GET requests. A value greater
63 than one causes failed GET requests to be retried `value` - 1 times.
64
65 Default: 1
66 """
67 if value < 1:
68 raise ValueError("Number of retries must be >= 1 (got: %s)" % value)
69 cls._max_get_retries = value
70 return cls
71
72 @classmethod
73 def get_retry_delay(cls):
74 """
75 The delay (in seconds) to wait before retrying a failed GET
76 request.
77 """
78 return cls._get_retry_delay
79
80 @classmethod
81 def set_get_retry_delay(cls, value):
82 """
83 Set the delay (in seconds) to wait before retrying a failed GET
84 request. Default: 1
85 """
86 if value < 0:
87 raise ValueError("Retry delay must be >= 0 (got: %s)" % value)
88 cls._get_retry_delay = value
89 return cls
90
91 def __init__(self, galaxy_instance):
92 """
93 A generic Client interface defining the common fields.
94
95 All clients *must* define the following field (which will be
96 used as part of the URL composition (e.g.,
97 ``http://<galaxy_instance>/api/libraries``): ``self.module =
98 'workflows' | 'libraries' | 'histories' | ...``
99 """
100 self.gi = galaxy_instance
101 self.url = '/'.join([self.gi.url, self.module])
102
103 def _get(self, id=None, deleted=False, contents=None, url=None,
104 params=None, json=True):
105 """
106 Do a GET request, composing the URL from ``id``, ``deleted`` and
107 ``contents``. Alternatively, an explicit ``url`` can be provided.
108 If ``json`` is set to ``True``, return a decoded JSON object
109 (and treat an empty or undecodable response as an error).
110
111 The request will optionally be retried as configured by
112 ``max_get_retries`` and ``get_retry_delay``: this offers some
113 resilience in the presence of temporary failures.
114 """
115 if not url:
116 url = self.gi._make_url(self, module_id=id, deleted=deleted,
117 contents=contents)
118 attempts_left = self.max_get_retries()
119 retry_delay = self.get_retry_delay()
120 bb.log.debug("GET - attempts left: %s; retry delay: %s",
121 attempts_left, retry_delay)
122 msg = ''
123 while attempts_left > 0:
124 attempts_left -= 1
125 try:
126 r = self.gi.make_get_request(url, params=params)
127 except (requests.exceptions.ConnectionError, ProtocolError) as e:
128 msg = str(e)
129 else:
130 if r is None:
131 msg = "GET: no response"
132 if r.status_code == 200:
133 if not json:
134 return r
135 elif not r.content:
136 msg = "GET: empty response"
137 else:
138 try:
139 return r.json()
140 except ValueError:
141 msg = "GET: invalid JSON : %r" % (r.content,)
142 else:
143 msg = "GET: error %s: %r" % (r.status_code, r.content)
144 msg = "%s, %d attempts left" % (msg, attempts_left)
145 if attempts_left <= 0:
146 bb.log.error(msg)
147 raise ConnectionError(msg)
148 else:
149 bb.log.warn(msg)
150 time.sleep(retry_delay)
151
152 def _post(self, payload, id=None, deleted=False, contents=None, url=None,
153 files_attached=False):
154 """
155 Do a generic POST request, composing the url from the contents of the
156 arguments. Alternatively, an explicit ``url`` can be provided to use
157 for the request. ``payload`` must be a dict that contains additional
158 request arguments which will be sent along with the request body.
159 The payload dict may contain file handles (in which case the
160 ``files_attached`` flag must be set to true).
161
162 If ``files_attached`` is set to ``False``, the request body will be
163 JSON-encoded; otherwise, it will be encoded as multipart/form-data.
164
165 The return value will contain the response body as a JSON object.
166 """
167 if not url:
168 url = self.gi._make_url(self, module_id=id, deleted=deleted,
169 contents=contents)
170 return self.gi.make_post_request(url, payload=payload,
171 files_attached=files_attached)
172
173 def _put(self, payload, id=None, url=None, params=None):
174 """
175 Do a generic PUT request, composing the url from the contents of the
176 arguments. Alternatively, an explicit ``url`` can be provided to use
177 for the request. ``payload`` must be a dict that contains additional
178 request arguments which will be sent along with the request body.
179
180 This method returns the HTTP request object.
181 """
182 if not url:
183 url = self.gi._make_url(self, module_id=id)
184 return self.gi.make_put_request(url, payload=payload, params=params)
185
186 def _delete(self, payload, id=None, deleted=False, contents=None, url=None):
187 """
188 Do a generic DELETE request, composing the url from the contents of the
189 arguments. Alternatively, an explicit ``url`` can be provided to use
190 for the request. ``payload`` must be a dict that can be converted
191 into a JSON object (which will be done within this method)
192 """
193 if not url:
194 url = self.gi._make_url(self, module_id=id, deleted=deleted,
195 contents=contents)
196 payload = json.dumps(payload)
197 r = self.gi.make_delete_request(url, payload=payload)
198 if r.status_code == 200:
199 return r.json()
200 # @see self.body for HTTP response body
201 raise ConnectionError(
202 "Unexpected HTTP status code: %s" % r.status_code, body=r.text
203 )