comparison env/lib/python3.7/site-packages/bioblend/galaxyclient.py @ 5:9b1c78e6ba9c draft default tip

"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author shellac
date Mon, 01 Jun 2020 08:59:25 -0400
parents 79f47841a781
children
comparison
equal deleted inserted replaced
4:79f47841a781 5:9b1c78e6ba9c
1 """
2 Helper class for Galaxy and ToolShed Instance object
3
4 This class is primarily a helper for the library and user code
5 should not use it directly.
6 A base representation of an instance
7 """
8 import base64
9 import json
10
11 import requests
12 import six
13 from requests_toolbelt import MultipartEncoder
14 from six.moves.urllib.parse import urljoin, urlparse
15
16 from bioblend import ConnectionError
17 from bioblend.util import FileStream
18
19
20 class GalaxyClient(object):
21
22 def __init__(self, url, key=None, email=None, password=None, verify=True, timeout=None):
23 """
24 :param verify: Whether to verify the server's TLS certificate
25 :type verify: bool
26 :param timeout: Timeout for requests operations, set to None for no timeout (the default).
27 :type timeout: float
28 """
29 # Make sure the url scheme is defined (otherwise requests will not work)
30 if not urlparse(url).scheme:
31 url = "http://" + url
32 # All of Galaxy's and ToolShed's API's are rooted at <url>/api so make that the url
33 self.base_url = url
34 self.url = urljoin(url, 'api')
35 # If key has been supplied, use it; otherwise just set email and
36 # password and grab user's key before first request.
37 if key:
38 self._key = key
39 else:
40 self._key = None
41 self.email = email
42 self.password = password
43 self.json_headers = {'Content-Type': 'application/json'}
44 self.verify = verify
45 self.timeout = timeout
46
47 def _make_url(self, module, module_id=None, deleted=False, contents=False):
48 """
49 Compose a URL based on the provided arguments.
50
51 :type module: :class:`~.galaxy.Client` subclass
52 :param module: The base module for which to make the URL. For
53 example: an object of class LibraryClient, WorkflowClient,
54 HistoryClient, ToolShedClient
55
56 :type module_id: str
57 :param module_id: The encoded ID for a specific module (eg, library ID)
58
59 :type deleted: bool
60 :param deleted: If ``True``, include ``deleted`` in the URL, after the module
61 name (eg, ``<base_url>/api/libraries/deleted``)
62
63 :type contents: bool
64 :param contents: If ``True``, include 'contents' in the URL, after the module ID:
65 ``<base_url>/api/libraries/<encoded_library_id>/contents``
66 """
67 c_url = '/'.join([self.url, module.module])
68 if deleted is True:
69 c_url = '/'.join([c_url, 'deleted'])
70 if module_id is not None:
71 c_url = '/'.join([c_url, module_id])
72 if contents is True:
73 c_url = '/'.join([c_url, 'contents'])
74 return c_url
75
76 def make_get_request(self, url, **kwargs):
77 """
78 Make a GET request using the provided ``url``.
79
80 Keyword arguments are the same as in requests.request.
81
82 If ``verify`` is not provided, ``self.verify`` will be used.
83
84 If the ``params`` are not provided, use ``default_params`` class field.
85 If params are provided and the provided dict does not have ``key`` key,
86 the default ``self.key`` value will be included in what's passed to
87 the server via the request.
88
89 :rtype: requests.Response
90 :return: the response object.
91 """
92 params = kwargs.get('params')
93 if params is not None and params.get('key', False) is False:
94 params['key'] = self.key
95 else:
96 params = self.default_params
97 kwargs['params'] = params
98 kwargs.setdefault('verify', self.verify)
99 kwargs.setdefault('timeout', self.timeout)
100 r = requests.get(url, **kwargs)
101 return r
102
103 def make_post_request(self, url, payload, params=None, files_attached=False):
104 """
105 Make a POST request using the provided ``url`` and ``payload``.
106 The ``payload`` must be a dict that contains the request values.
107 The payload dict may contain file handles (in which case the files_attached
108 flag must be set to true).
109
110 If the ``params`` are not provided, use ``default_params`` class field.
111 If params are provided and the provided dict does not have ``key`` key,
112 the default ``self.key`` value will be included in what's passed to
113 the server via the request.
114
115 :return: The decoded response.
116 """
117
118 def my_dumps(d):
119 """
120 Apply ``json.dumps()`` to the values of the dict ``d`` if they are
121 not of type ``FileStream``.
122 """
123 for k, v in d.items():
124 if not isinstance(v, FileStream):
125 d[k] = json.dumps(v)
126 return d
127
128 if params is not None and params.get('key', False) is False:
129 params['key'] = self.key
130 else:
131 params = self.default_params
132
133 # Compute data, headers, params arguments for request.post,
134 # leveraging the requests-toolbelt library if any files have
135 # been attached.
136 if files_attached:
137 payload = my_dumps(payload)
138 payload.update(params)
139 payload = MultipartEncoder(fields=payload)
140 headers = self.json_headers.copy()
141 headers['Content-Type'] = payload.content_type
142 post_params = {}
143 else:
144 payload = json.dumps(payload)
145 headers = self.json_headers
146 post_params = params
147
148 r = requests.post(url, data=payload, headers=headers,
149 verify=self.verify, params=post_params,
150 timeout=self.timeout)
151 if r.status_code == 200:
152 try:
153 return r.json()
154 except Exception as e:
155 raise ConnectionError("Request was successful, but cannot decode the response content: %s" %
156 e, body=r.content, status_code=r.status_code)
157 # @see self.body for HTTP response body
158 raise ConnectionError("Unexpected HTTP status code: %s" % r.status_code,
159 body=r.text, status_code=r.status_code)
160
161 def make_delete_request(self, url, payload=None, params=None):
162 """
163 Make a DELETE request using the provided ``url`` and the optional
164 arguments.
165
166 If the ``params`` are not provided, use ``default_params`` class field.
167 If params are provided and the provided dict does not have ``key`` key,
168 the default ``self.key`` value will be included in what's passed to
169 the server via the request.
170
171 :type payload: dict
172 :param payload: a JSON-serializable dictionary
173
174 :rtype: requests.Response
175 :return: the response object.
176 """
177 if params is not None and params.get('key', False) is False:
178 params['key'] = self.key
179 else:
180 params = self.default_params
181 if payload is not None:
182 payload = json.dumps(payload)
183 headers = self.json_headers
184 r = requests.delete(url, verify=self.verify, data=payload, params=params,
185 headers=headers, timeout=self.timeout)
186 return r
187
188 def make_put_request(self, url, payload=None, params=None):
189 """
190 Make a PUT request using the provided ``url`` with required payload.
191
192 :type payload: dict
193 :param payload: a JSON-serializable dictionary
194
195 :return: The decoded response.
196 """
197 if params is not None and params.get('key', False) is False:
198 params['key'] = self.key
199 else:
200 params = self.default_params
201
202 payload = json.dumps(payload)
203 headers = self.json_headers
204 r = requests.put(url, data=payload, params=params, headers=headers,
205 verify=self.verify, timeout=self.timeout)
206 if r.status_code == 200:
207 try:
208 return r.json()
209 except Exception as e:
210 raise ConnectionError("Request was successful, but cannot decode the response content: %s" %
211 e, body=r.content, status_code=r.status_code)
212 # @see self.body for HTTP response body
213 raise ConnectionError("Unexpected HTTP status code: %s" % r.status_code,
214 body=r.text, status_code=r.status_code)
215
216 def make_patch_request(self, url, payload=None, params=None):
217 """
218 Make a PATCH request using the provided ``url`` with required payload.
219
220 :type payload: dict
221 :param payload: a JSON-serializable dictionary
222
223 :return: The decoded response.
224 """
225 if params is not None and params.get('key', False) is False:
226 params['key'] = self.key
227 else:
228 params = self.default_params
229
230 payload = json.dumps(payload)
231 headers = self.json_headers
232 r = requests.patch(url, data=payload, params=params, headers=headers,
233 verify=self.verify, timeout=self.timeout)
234 if r.status_code == 200:
235 try:
236 return r.json()
237 except Exception as e:
238 raise ConnectionError("Request was successful, but cannot decode the response content: %s" %
239 e, body=r.content, status_code=r.status_code)
240 # @see self.body for HTTP response body
241 raise ConnectionError("Unexpected HTTP status code: %s" % r.status_code,
242 body=r.text, status_code=r.status_code)
243
244 @property
245 def key(self):
246 if not self._key and self.email is not None and self.password is not None:
247 unencoded_credentials = "%s:%s" % (self.email, self.password)
248 authorization = base64.b64encode(unencoded_credentials.encode())
249 headers = self.json_headers.copy()
250 headers["Authorization"] = authorization
251 auth_url = "%s/authenticate/baseauth" % self.url
252 # make_post_request uses default_params, which uses this and
253 # sets wrong headers - so using lower level method.
254 r = requests.get(auth_url, verify=self.verify, headers=headers)
255 if r.status_code != 200:
256 raise Exception("Failed to authenticate user.")
257 response = r.json()
258 if isinstance(response, (six.string_types, six.text_type)):
259 # bug in Tool Shed
260 response = json.loads(response)
261 self._key = response["api_key"]
262 return self._key
263
264 @property
265 def default_params(self):
266 return {'key': self.key}