Mercurial > repos > bcclaywell > argo_navis
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 ) |