pyxetabase 3.1.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyxetabase might be problematic. Click here for more details.

Files changed (63) hide show
  1. pyopencga/__init__.py +0 -0
  2. pyopencga/commons.py +347 -0
  3. pyopencga/exceptions.py +8 -0
  4. pyopencga/opencga_client.py +334 -0
  5. pyopencga/opencga_config.py +160 -0
  6. pyopencga/rest_clients/__init__.py +0 -0
  7. pyopencga/rest_clients/_parent_rest_clients.py +110 -0
  8. pyopencga/rest_clients/admin_client.py +173 -0
  9. pyopencga/rest_clients/alignment_client.py +373 -0
  10. pyopencga/rest_clients/clinical_analysis_client.py +1279 -0
  11. pyopencga/rest_clients/cohort_client.py +338 -0
  12. pyopencga/rest_clients/disease_panel_client.py +352 -0
  13. pyopencga/rest_clients/family_client.py +355 -0
  14. pyopencga/rest_clients/file_client.py +698 -0
  15. pyopencga/rest_clients/ga4gh_client.py +86 -0
  16. pyopencga/rest_clients/individual_client.py +435 -0
  17. pyopencga/rest_clients/job_client.py +415 -0
  18. pyopencga/rest_clients/meta_client.py +83 -0
  19. pyopencga/rest_clients/organization_client.py +206 -0
  20. pyopencga/rest_clients/project_client.py +128 -0
  21. pyopencga/rest_clients/sample_client.py +446 -0
  22. pyopencga/rest_clients/study_client.py +433 -0
  23. pyopencga/rest_clients/user_client.py +192 -0
  24. pyopencga/rest_clients/variant_client.py +1378 -0
  25. pyopencga/rest_clients/variant_operation_client.py +719 -0
  26. pyopencga/rest_clients/workflow_client.py +263 -0
  27. pyopencga/rest_response.py +220 -0
  28. pyopencga/retry.py +57 -0
  29. pyxetabase/__init__.py +0 -0
  30. pyxetabase/commons.py +347 -0
  31. pyxetabase/exceptions.py +8 -0
  32. pyxetabase/opencga_client.py +344 -0
  33. pyxetabase/opencga_config.py +160 -0
  34. pyxetabase/rest_clients/__init__.py +0 -0
  35. pyxetabase/rest_clients/_parent_rest_clients.py +110 -0
  36. pyxetabase/rest_clients/admin_client.py +173 -0
  37. pyxetabase/rest_clients/alignment_client.py +373 -0
  38. pyxetabase/rest_clients/clinical_analysis_client.py +1273 -0
  39. pyxetabase/rest_clients/cohort_client.py +338 -0
  40. pyxetabase/rest_clients/cvdb_client.py +2258 -0
  41. pyxetabase/rest_clients/disease_panel_client.py +352 -0
  42. pyxetabase/rest_clients/family_client.py +355 -0
  43. pyxetabase/rest_clients/federation_client.py +133 -0
  44. pyxetabase/rest_clients/file_client.py +698 -0
  45. pyxetabase/rest_clients/ga4gh_client.py +86 -0
  46. pyxetabase/rest_clients/individual_client.py +435 -0
  47. pyxetabase/rest_clients/job_client.py +415 -0
  48. pyxetabase/rest_clients/meta_client.py +83 -0
  49. pyxetabase/rest_clients/organization_client.py +206 -0
  50. pyxetabase/rest_clients/project_client.py +128 -0
  51. pyxetabase/rest_clients/sample_client.py +446 -0
  52. pyxetabase/rest_clients/study_client.py +433 -0
  53. pyxetabase/rest_clients/user_client.py +212 -0
  54. pyxetabase/rest_clients/variant_client.py +1378 -0
  55. pyxetabase/rest_clients/variant_operation_client.py +719 -0
  56. pyxetabase/rest_clients/workflow_client.py +263 -0
  57. pyxetabase/rest_response.py +220 -0
  58. pyxetabase/retry.py +57 -0
  59. pyxetabase-3.1.0.dev0.dist-info/METADATA +159 -0
  60. pyxetabase-3.1.0.dev0.dist-info/RECORD +63 -0
  61. pyxetabase-3.1.0.dev0.dist-info/WHEEL +5 -0
  62. pyxetabase-3.1.0.dev0.dist-info/licenses/LICENSE +202 -0
  63. pyxetabase-3.1.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,344 @@
1
+ import getpass
2
+ import time
3
+ import sys
4
+ import re
5
+ if sys.version_info >= (3, 8):
6
+ from importlib.metadata import version
7
+ else:
8
+ from importlib_metadata import version
9
+
10
+ from pyxetabase.opencga_config import ClientConfiguration
11
+ from pyxetabase.rest_clients.admin_client import Admin
12
+ from pyxetabase.rest_clients.alignment_client import Alignment
13
+ from pyxetabase.rest_clients.clinical_analysis_client import ClinicalAnalysis
14
+ from pyxetabase.rest_clients.cohort_client import Cohort
15
+ from pyxetabase.rest_clients.family_client import Family
16
+ from pyxetabase.rest_clients.file_client import File
17
+ from pyxetabase.rest_clients.ga4gh_client import GA4GH
18
+ from pyxetabase.rest_clients.individual_client import Individual
19
+ from pyxetabase.rest_clients.job_client import Job
20
+ from pyxetabase.rest_clients.meta_client import Meta
21
+ from pyxetabase.rest_clients.disease_panel_client import DiseasePanel
22
+ from pyxetabase.rest_clients.project_client import Project
23
+ from pyxetabase.rest_clients.sample_client import Sample
24
+ from pyxetabase.rest_clients.study_client import Study
25
+ from pyxetabase.rest_clients.variant_operation_client import VariantOperation
26
+ from pyxetabase.rest_clients.user_client import User
27
+ from pyxetabase.rest_clients.variant_client import Variant
28
+ from pyxetabase.rest_clients.organization_client import Organization
29
+ from pyxetabase.rest_clients.workflow_client import Workflow
30
+ from pyxetabase.rest_clients.federation_client import Federation
31
+ from pyxetabase.rest_clients.cvdb_client import CVDB
32
+
33
+
34
+ class OpencgaClient(object):
35
+ def __init__(self, configuration, token=None, refresh_token=None, on_retry=None, auto_refresh=True):
36
+ """
37
+ :param on_retry: callback to be called with client retries an operation.
38
+ It must accept parameters: client, exc_type, exc_val, exc_tb, call
39
+ """
40
+
41
+ if not isinstance(configuration, ClientConfiguration):
42
+ raise ValueError('Expected ClientConfiguration instance')
43
+
44
+ self.configuration = configuration
45
+ self.auto_refresh = auto_refresh
46
+ self.on_retry = on_retry
47
+ self.clients = []
48
+ self._login_handler = None
49
+ self.token = token
50
+ self.refresh_token = refresh_token
51
+ self._create_clients()
52
+ self._check_versions()
53
+ if self.configuration.sso_login:
54
+ self._sso_login()
55
+
56
+ def __enter__(self):
57
+ return self
58
+
59
+ def __exit__(self, exc_type, exc_val, exc_tb):
60
+ self.logout()
61
+
62
+ def _check_versions(self):
63
+ # Getting client and server versions
64
+ client_version = version("pyopencga")
65
+ server_version = self.meta.about().get_result(0)['Version'].split('-')[0]
66
+
67
+ ansi_reset = "\033[0m"
68
+ ansi_red = "\033[31m"
69
+ ansi_yellow = "\033[33m"
70
+ if tuple(server_version.split('.')[:2]) < tuple(client_version.split('.')[:2]):
71
+ msg = '[WARNING]: Client version ({}) is higher than server version ({}).' \
72
+ ' Some client features may not be implemented in the server.\n'.format(client_version, server_version)
73
+ sys.stdout.write('{}{}{}'.format(ansi_red, msg, ansi_reset))
74
+ elif tuple(server_version.split('.')[:2]) > tuple(client_version.split('.')[:2]):
75
+ msg = '[INFO]: Client version ({}) is lower than server version ({}).' \
76
+ ' Some client features may not work as intended.\n'.format(client_version, server_version)
77
+ sys.stdout.write('{}{}{}'.format(ansi_yellow, msg, ansi_reset))
78
+
79
+ def _create_clients(self):
80
+ self.organizations = Organization(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
81
+ self.users = User(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
82
+ self.projects = Project(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
83
+ self.studies = Study(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
84
+ self.files = File(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
85
+ self.jobs = Job(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
86
+ self.samples = Sample(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
87
+ self.individuals = Individual(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
88
+ self.families = Family(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
89
+ self.cohorts = Cohort(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
90
+ self.disease_panels = DiseasePanel(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
91
+ self.alignments = Alignment(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
92
+ self.variants = Variant(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
93
+ self.clinical = ClinicalAnalysis(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
94
+ self.operations = VariantOperation(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
95
+ self.variant_operations = self.operations # DEPRECATED: use 'self.operations'
96
+ self.meta = Meta(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
97
+ self.ga4gh = GA4GH(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
98
+ self.admin = Admin(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
99
+ self.workflows = Workflow(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
100
+ self.federations = Federation(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
101
+ self.cvdb = CVDB(self.configuration, self.token, self._login_handler, auto_refresh=self.auto_refresh)
102
+
103
+ self.clients = [
104
+ self.organizations, self.users, self.projects, self.studies, self.files, self.jobs,
105
+ self.samples, self.individuals, self.families, self.cohorts,
106
+ self.disease_panels, self.alignments, self.variants, self.clinical, self.operations,
107
+ self.variant_operations, self.meta, self.ga4gh, self.admin, self.workflows, self.federations, self.cvdb
108
+ ]
109
+
110
+ for client in self.clients:
111
+ client.on_retry = self.on_retry
112
+
113
+ def _make_login_handler(self, user=None, password=None, organization=None):
114
+ """
115
+ Returns a closure that performs the log-in. This will be called on retries
116
+ if the current session ever expires.
117
+ The reason for using a closure and not a normal function is that a normal
118
+ function would require storing the password in a field. It is more secure
119
+ not to do so. This way, the password stored in the closure is inaccessible
120
+ to other code
121
+ """
122
+ def login_handler(refresh=False):
123
+ if refresh:
124
+ data = {'refreshToken': self.refresh_token}
125
+ else:
126
+ data = {'user': user, 'password': password}
127
+ if organization:
128
+ data.update({'organization': organization})
129
+ tokens = User(self.configuration).login(data=data).get_result(0)
130
+ self.token = tokens['token']
131
+ self.refresh_token = tokens['refreshToken']
132
+ return self.token, self.refresh_token
133
+ return login_handler
134
+
135
+ def login(self, user=None, password=None, organization=None):
136
+ if user is not None and password is None:
137
+ password = getpass.getpass()
138
+ if not (user and password):
139
+ raise ValueError('User and password required')
140
+
141
+ self._login_handler = self._make_login_handler(user=user, password=password, organization=organization)
142
+ self._login_handler()
143
+ for client in self.clients:
144
+ client.token = self.token
145
+ client.refresh_token = self.refresh_token
146
+ client.login_handler = self._login_handler
147
+
148
+ def _sso_login(self):
149
+ # Getting token and refresh token from configuration
150
+ self.token = self.configuration.token
151
+ self.refresh_token = self.configuration.token
152
+
153
+ self._login_handler = self._make_login_handler()
154
+ for client in self.clients:
155
+ client.token = self.token
156
+ client.refresh_token = self.refresh_token
157
+ client.login_handler = self._login_handler
158
+
159
+ def logout(self):
160
+ self.token = None
161
+ self.refresh_token = None
162
+ for client in self.clients:
163
+ client.token = self.token
164
+ client.refresh_token = self.refresh_token
165
+
166
+ def wait_for_job(self, response=None, study_id=None, job_id=None, retry_seconds=10):
167
+ if response is not None:
168
+ study_id = response['results'][0]['study']['id']
169
+ job_id = response['results'][0]['id']
170
+
171
+ if response is None and (study_id is None or job_id is None):
172
+ raise ValueError('Argument "response" or arguments "study" and "job_id" must be provided')
173
+
174
+ if len(job_id.split(',')) > 1:
175
+ raise ValueError('Only one job ID is allowed')
176
+
177
+ retry_seconds = retry_seconds if retry_seconds >= 10 else 10
178
+ while True:
179
+ job_info = self.jobs.info(study=study_id, jobs=job_id).get_result(0)
180
+ if job_info['internal']['status']['id'] in ['ERROR', 'ABORTED']:
181
+ raise ValueError('{} ({}): {}'.format(
182
+ job_info['internal']['status']['id'],
183
+ job_info['internal']['status']['date'],
184
+ job_info['internal']['status']['description']
185
+ ))
186
+ elif job_info['internal']['status']['id'] in ['DONE']:
187
+ break
188
+ time.sleep(retry_seconds)
189
+
190
+ def _get_help_info(self, client_name=None, parameters=False):
191
+ info = []
192
+ for client in self.clients:
193
+ # Name
194
+ cls_name = type(client).__name__
195
+ client_method = re.sub(r'(?<!^)(?=[A-Z])', '_', cls_name).lower() \
196
+ if cls_name != 'GA4GH' else cls_name.lower()
197
+ client_method = 'get_' + client_method + '_client'
198
+
199
+ if client_name is not None and client_name != cls_name:
200
+ continue
201
+
202
+ # Description and path
203
+ class_docstring = client.__doc__
204
+ cls_desc = re.findall('(This class contains methods .+)\n', class_docstring)[0]
205
+ cls_path = re.findall('PATH: (.+)\n', class_docstring)[0]
206
+
207
+ # Methods
208
+ methods = []
209
+ method_names = [method_name for method_name in dir(client)
210
+ if callable(getattr(client, method_name))
211
+ and not method_name.startswith('_') and method_name != 'login_handler']
212
+ for method_name in method_names:
213
+ if client_name is None:
214
+ continue
215
+ method_docstring = getattr(client, method_name).__doc__
216
+ desc = re.findall('(.+)\n +PATH', method_docstring, re.DOTALL)
217
+ desc = re.sub(' +', ' ', desc[0].replace('\n', ' ').strip())
218
+ path = re.findall('PATH: (.+)\n', method_docstring)[0]
219
+
220
+ args = []
221
+ arguments = re.findall(
222
+ ' +:param (.+)', method_docstring, re.DOTALL
223
+ )
224
+ if arguments and parameters:
225
+ arguments = arguments[0].replace('\n', ' ').strip()
226
+ arguments = re.sub(' +', ' ', arguments)
227
+ arguments = arguments.split(' :param ')
228
+ for parameter in arguments:
229
+ param_info = parameter.split(' ', 2)
230
+ args.append({
231
+ 'name': param_info[1].rstrip(':'),
232
+ 'type': param_info[0],
233
+ 'desc': param_info[2]
234
+ })
235
+ methods.append({
236
+ 'name': method_name,
237
+ 'desc': desc,
238
+ 'path': path,
239
+ 'params': args
240
+ })
241
+
242
+ info.append(
243
+ {'class_name': cls_name, 'client_method': client_method,
244
+ 'desc': cls_desc, 'path': cls_path, 'methods': methods}
245
+ )
246
+ return info
247
+
248
+ def help(self, client_name=None, show_parameters=False):
249
+ help_txt = []
250
+
251
+ info = self._get_help_info(client_name, show_parameters)
252
+ if client_name is None:
253
+ help_txt += ['Available clients:']
254
+ for client in info:
255
+ txt = '{}- {}: {} ({}). USAGE: opencga_client.{}()'
256
+ help_txt += [txt.format(
257
+ ' '*4, client['class_name'], client['desc'],
258
+ client['path'], client['client_method']
259
+ )]
260
+ else:
261
+ for client in info:
262
+ help_txt += ['{}: {} ({}). USAGE: opencga_client.{}()'.format(
263
+ client['class_name'], client['desc'], client['path'],
264
+ client['client_method']
265
+ )]
266
+ help_txt += ['{}Available methods:'.format(' '*4)]
267
+ for method in client['methods']:
268
+ help_txt += ['{}- {}: {} ({})'.format(
269
+ ' '*8, method['name'], method['desc'], method['path']
270
+ )]
271
+ if not show_parameters:
272
+ continue
273
+ for param in method['params']:
274
+ help_txt += ['{}* {} ({}): {}'.format(
275
+ ' ' * 12, param['name'], param['type'],
276
+ param['desc']
277
+ )]
278
+ sys.stdout.write('\n'.join(help_txt) + '\n')
279
+
280
+ def get_organization_client(self):
281
+ return self.organizations
282
+
283
+ def get_user_client(self):
284
+ return self.users
285
+
286
+ def get_project_client(self):
287
+ return self.projects
288
+
289
+ def get_study_client(self):
290
+ return self.studies
291
+
292
+ def get_file_client(self):
293
+ return self.files
294
+
295
+ def get_job_client(self):
296
+ return self.jobs
297
+
298
+ def get_sample_client(self):
299
+ return self.samples
300
+
301
+ def get_individual_client(self):
302
+ return self.individuals
303
+
304
+ def get_family_client(self):
305
+ return self.families
306
+
307
+ def get_cohort_client(self):
308
+ return self.cohorts
309
+
310
+ def get_disease_panel_client(self):
311
+ return self.disease_panels
312
+
313
+ def get_alignment_client(self):
314
+ return self.alignments
315
+
316
+ def get_variant_client(self):
317
+ return self.variants
318
+
319
+ def get_clinical_client(self):
320
+ return self.clinical
321
+
322
+ def get_operations_client(self):
323
+ return self.operations
324
+
325
+ def get_variant_operation_client(self):
326
+ return self.variant_operations
327
+
328
+ def get_meta_client(self):
329
+ return self.meta
330
+
331
+ def get_ga4gh_client(self):
332
+ return self.ga4gh
333
+
334
+ def get_admin_client(self):
335
+ return self.admin
336
+
337
+ def get_workflows_client(self):
338
+ return self.workflows
339
+
340
+ def get_federations_client(self):
341
+ return self.federations
342
+
343
+ def get_cvdb_client(self):
344
+ return self.cvdb
@@ -0,0 +1,160 @@
1
+ import os
2
+ import json
3
+ import requests
4
+ import yaml
5
+
6
+
7
+ class ClientConfiguration(object):
8
+ """PyOpenCGA configuration"""
9
+
10
+ def __init__(self, config_input):
11
+ """
12
+ :param dict or str config_input: a dict, or a yaml/json file containing an OpenCGA valid client configuration.
13
+ """
14
+
15
+ if isinstance(config_input, dict):
16
+ self._config = config_input
17
+ else:
18
+ self._config = self._get_dictionary_from_file(config_input)
19
+
20
+ self.get_sso_login_info()
21
+
22
+ self._validate_configuration()
23
+
24
+ @staticmethod
25
+ def _get_dictionary_from_file(config_fpath):
26
+ config_fhand = open(config_fpath, 'r')
27
+
28
+ if config_fpath.endswith('.yml') or config_fpath.endswith('.yaml'):
29
+ config_dict = yaml.safe_load(config_fhand)
30
+ elif config_fpath.endswith('.json'):
31
+ config_dict = json.loads(config_fhand.read())
32
+ else:
33
+ raise NotImplementedError('Input must be a dictionary, a yaml file (.yml or .yaml) or a json file (.json)')
34
+
35
+ config_fhand.close()
36
+
37
+ return config_dict
38
+
39
+ def get_sso_login_info(self):
40
+ if (('sso_login' in self._config and self._config['sso_login']) or
41
+ ('cookies' in self._config and self._config['cookies'])):
42
+ python_session_fhand = open(os.path.expanduser("~/.opencga/session.json"), 'r')
43
+ session_info = json.loads(python_session_fhand.read())
44
+ self._config['sso_login'] = True
45
+ self._config['cookies'] = session_info['cookies']
46
+ self._config['token'] = session_info['token']
47
+
48
+ def _validate_configuration(self):
49
+ if self._config is None:
50
+ raise ValueError('Missing configuration dictionary.')
51
+
52
+ if 'rest' not in self._config:
53
+ raise ValueError('Missing configuration field "rest".')
54
+
55
+ if 'host' not in self._config['rest'] or not self._config['rest']['host']:
56
+ raise ValueError('Missing configuration field "host".')
57
+
58
+ self._validate_host(self._config['rest']['host'])
59
+
60
+ @staticmethod
61
+ def _validate_host(host):
62
+ try:
63
+ r = requests.head(host, timeout=2)
64
+ if r.status_code == 302:
65
+ return
66
+ except requests.ConnectionError:
67
+ raise Exception('Unreachable host "{}"'.format(host))
68
+
69
+ @property
70
+ def configuration(self):
71
+ return self._config
72
+
73
+ @property
74
+ def host(self):
75
+ return self._config['rest']['host']
76
+
77
+ @host.setter
78
+ def host(self, host):
79
+ self._config['rest']['host'] = host
80
+ self._validate_host(host)
81
+
82
+ @property
83
+ def version(self):
84
+ return self._config['version'] if 'version' in self._config else 'v2'
85
+
86
+ @version.setter
87
+ def version(self, version):
88
+ self._config['version'] = version
89
+
90
+ @property
91
+ def sso_login(self):
92
+ if 'sso_login' in self._config and self._config['sso_login']:
93
+ return self._config['sso_login']
94
+ else:
95
+ return False
96
+
97
+ @sso_login.setter
98
+ def sso_login(self, sso_login):
99
+ if isinstance(str, sso_login):
100
+ self._config['sso_login'] = sso_login.lower() == 'true'
101
+ if isinstance(bool, sso_login):
102
+ self._config['sso_login'] = sso_login
103
+
104
+ @property
105
+ def cookies(self):
106
+ if 'cookies' in self._config and self._config['cookies']:
107
+ return self._config['cookies']
108
+ else:
109
+ return None
110
+
111
+ @cookies.setter
112
+ def cookies(self, cookies):
113
+ self._config['cookies'] = cookies
114
+
115
+ @property
116
+ def token(self):
117
+ if 'token' in self._config and self._config['token']:
118
+ return self._config['token']
119
+ else:
120
+ return None
121
+
122
+ @token.setter
123
+ def token(self, token):
124
+ self._config['token'] = token
125
+
126
+ @property
127
+ def max_attempts(self):
128
+ """Returns configured max_attempts or 1 if not configured"""
129
+ if 'max_attempts' in self._config and self._config['max_attempts']:
130
+ return self._config['max_attempts']
131
+ else:
132
+ return 1
133
+
134
+ @max_attempts.setter
135
+ def max_attempts(self, max_attempts):
136
+ self._config['max_attempts'] = max_attempts
137
+
138
+ @property
139
+ def min_retry_secs(self):
140
+ """Returns configured min_retry_seconds or 0 if not configured"""
141
+ if 'min_retry_secs' in self._config and self._config['min_retry_secs']:
142
+ return self._config['min_retry_secs']
143
+ else:
144
+ return 0
145
+
146
+ @min_retry_secs.setter
147
+ def min_retry_secs(self, min_retry_secs):
148
+ self._config['min_retry_secs'] = min_retry_secs
149
+
150
+ @property
151
+ def max_retry_secs(self):
152
+ """Returns configured max_retry_seconds or 0 if not configured"""
153
+ if 'max_retry_secs' in self._config and self._config['max_retry_secs']:
154
+ return self._config['max_retry_secs']
155
+ else:
156
+ return 0
157
+
158
+ @max_retry_secs.setter
159
+ def max_retry_secs(self, max_retry_secs):
160
+ self._config['max_retry_secs'] = max_retry_secs
File without changes
@@ -0,0 +1,110 @@
1
+ import json
2
+
3
+ from pyxetabase.commons import execute
4
+ from pyxetabase.rest_response import RestResponse
5
+ from pyxetabase.retry import retry
6
+
7
+
8
+ class _ParentRestClient(object):
9
+ """Queries the REST service given the different query params"""
10
+
11
+ def __init__(self, configuration, token=None, login_handler=None,
12
+ auto_refresh=True):
13
+ self.auto_refresh = auto_refresh
14
+ self._cfg = configuration
15
+ self.token = token
16
+ self.refresh_token = None
17
+ self.login_handler = login_handler
18
+ self.on_retry = None
19
+
20
+ def _client_login_handler(self):
21
+ if self.login_handler:
22
+ self.token, self.refresh_token = self.login_handler()
23
+
24
+ def _refresh_token_client(self):
25
+ if self.login_handler:
26
+ self.token, self.refresh_token = self.login_handler(refresh=True)
27
+
28
+ @staticmethod
29
+ def _get_query_id_str(query_ids):
30
+ if query_ids is None:
31
+ return None
32
+ elif isinstance(query_ids, list):
33
+ return ','.join(map(str, query_ids))
34
+ else:
35
+ return str(query_ids)
36
+
37
+ def _rest_retry(self, method, category, resource, query_id=None, subcategory=None,
38
+ second_query_id=None, data=None, dont_retry=None,
39
+ **options):
40
+
41
+ query_ids_str = self._get_query_id_str(query_id)
42
+
43
+ def exec_retry():
44
+ return execute(config=self._cfg,
45
+ sid=self.token,
46
+ category=category,
47
+ subcategory=subcategory,
48
+ method=method,
49
+ query_id=query_ids_str,
50
+ second_query_id=second_query_id,
51
+ resource=resource,
52
+ data=data,
53
+ options=options)
54
+
55
+ def notify_retry(exc_type, exc_val, exc_tb):
56
+ if self.on_retry is not None:
57
+ self.on_retry(self, exc_type, exc_val, exc_tb, dict(
58
+ method=method, resource=resource, query_id=query_id,
59
+ category=category, subcategory=subcategory,
60
+ second_query_id=second_query_id, data=data,
61
+ options=options
62
+ ))
63
+
64
+ response = retry(
65
+ exec_retry, self._cfg.max_attempts, self._cfg.min_retry_secs,
66
+ self._cfg.max_retry_secs, login_handler=self.login_handler,
67
+ on_retry=notify_retry, dont_retry=dont_retry
68
+ )
69
+
70
+ if self.auto_refresh:
71
+ self._refresh_token_client()
72
+
73
+ if isinstance(response, dict):
74
+ return RestResponse(response)
75
+ else: # Not a JSON (e.g. /{apiVersion}/files/{file}/download)
76
+ return response
77
+
78
+ def _get(self, category, resource, query_id=None, subcategory=None,
79
+ second_query_id=None, **options):
80
+ """Queries the REST service and returns the result"""
81
+ return self._rest_retry(
82
+ method='get', category=category, resource=resource, query_id=query_id,
83
+ subcategory=subcategory, second_query_id=second_query_id,
84
+ **options
85
+ )
86
+
87
+ def _post(self, category, resource, data=None, query_id=None, subcategory=None,
88
+ second_query_id=None, **options):
89
+ """Queries the REST service and returns the result"""
90
+ if data is not None:
91
+ return self._rest_retry(
92
+ method='post', category=category, resource=resource, query_id=query_id,
93
+ subcategory=subcategory, second_query_id=second_query_id,
94
+ data=data, **options
95
+ )
96
+ else:
97
+ return self._rest_retry(
98
+ method='post', category=category, resource=resource, query_id=query_id,
99
+ subcategory=subcategory, second_query_id=second_query_id,
100
+ **options
101
+ )
102
+
103
+ def _delete(self, category, resource, query_id=None, subcategory=None,
104
+ second_query_id=None, **options):
105
+ """Queries the REST service and returns the result"""
106
+ return self._rest_retry(
107
+ method='delete', category=category, resource=resource, query_id=query_id,
108
+ subcategory=subcategory, second_query_id=second_query_id,
109
+ **options
110
+ )