pyxetabase 3.1.0.dev21__py3-none-any.whl → 4.0.0.dev29__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.
- pyxetabase/opencga_config.py +29 -0
- pyxetabase/rest_clients/_parent_rest_clients.py +47 -13
- pyxetabase/rest_clients/admin_client.py +1 -2
- pyxetabase/rest_clients/meta_client.py +4 -3
- pyxetabase/rest_clients/study_client.py +28 -0
- pyxetabase/rest_clients/variant_operation_client.py +26 -27
- {pyxetabase-3.1.0.dev21.dist-info → pyxetabase-4.0.0.dev29.dist-info}/METADATA +1 -1
- pyxetabase-4.0.0.dev29.dist-info/RECORD +35 -0
- pyopencga/__init__.py +0 -0
- pyopencga/commons.py +0 -347
- pyopencga/exceptions.py +0 -8
- pyopencga/opencga_client.py +0 -334
- pyopencga/opencga_config.py +0 -182
- pyopencga/rest_clients/__init__.py +0 -0
- pyopencga/rest_clients/_parent_rest_clients.py +0 -110
- pyopencga/rest_clients/admin_client.py +0 -173
- pyopencga/rest_clients/alignment_client.py +0 -373
- pyopencga/rest_clients/clinical_analysis_client.py +0 -1279
- pyopencga/rest_clients/cohort_client.py +0 -338
- pyopencga/rest_clients/disease_panel_client.py +0 -352
- pyopencga/rest_clients/family_client.py +0 -355
- pyopencga/rest_clients/file_client.py +0 -698
- pyopencga/rest_clients/ga4gh_client.py +0 -86
- pyopencga/rest_clients/individual_client.py +0 -435
- pyopencga/rest_clients/job_client.py +0 -415
- pyopencga/rest_clients/meta_client.py +0 -85
- pyopencga/rest_clients/organization_client.py +0 -216
- pyopencga/rest_clients/project_client.py +0 -128
- pyopencga/rest_clients/sample_client.py +0 -446
- pyopencga/rest_clients/study_client.py +0 -433
- pyopencga/rest_clients/user_client.py +0 -192
- pyopencga/rest_clients/variant_client.py +0 -1378
- pyopencga/rest_clients/variant_operation_client.py +0 -719
- pyopencga/rest_clients/workflow_client.py +0 -263
- pyopencga/rest_response.py +0 -220
- pyopencga/retry.py +0 -57
- pyxetabase-3.1.0.dev21.dist-info/RECORD +0 -63
- {pyxetabase-3.1.0.dev21.dist-info → pyxetabase-4.0.0.dev29.dist-info}/WHEEL +0 -0
- {pyxetabase-3.1.0.dev21.dist-info → pyxetabase-4.0.0.dev29.dist-info}/licenses/LICENSE +0 -0
- {pyxetabase-3.1.0.dev21.dist-info → pyxetabase-4.0.0.dev29.dist-info}/top_level.txt +0 -0
pyxetabase/opencga_config.py
CHANGED
|
@@ -76,6 +76,35 @@ class ClientConfiguration(object):
|
|
|
76
76
|
except requests.ConnectionError:
|
|
77
77
|
raise Exception('Unreachable host "{}"'.format(self.host))
|
|
78
78
|
|
|
79
|
+
@property
|
|
80
|
+
def host(self):
|
|
81
|
+
return self._config['rest']['host']
|
|
82
|
+
|
|
83
|
+
@host.setter
|
|
84
|
+
def host(self, new_host):
|
|
85
|
+
self._config['rest']['host'] = new_host
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def tlsAllowInvalidCertificates(self):
|
|
89
|
+
if ('tlsAllowInvalidCertificates' in self._config['rest']
|
|
90
|
+
and self._config['rest']['tlsAllowInvalidCertificates'] is not None):
|
|
91
|
+
return self._config['rest']['tlsAllowInvalidCertificates']
|
|
92
|
+
else:
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def version(self):
|
|
97
|
+
return self._config['version'] if 'version' in self._config else 'v2'
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def cookies(self):
|
|
101
|
+
if 'cookies' in self._config and self._config['cookies']:
|
|
102
|
+
python_session_fhand = open(os.path.expanduser("~/.opencga/python_session.json"), 'r')
|
|
103
|
+
session_info = json.loads(python_session_fhand.read())
|
|
104
|
+
return session_info['cookies']
|
|
105
|
+
else:
|
|
106
|
+
return None
|
|
107
|
+
|
|
79
108
|
@property
|
|
80
109
|
def configuration(self):
|
|
81
110
|
return self._config
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import os
|
|
2
|
+
import requests
|
|
2
3
|
|
|
3
|
-
from pyxetabase.commons import execute
|
|
4
|
+
from pyxetabase.commons import execute, _create_rest_url, snake_to_camel_case
|
|
4
5
|
from pyxetabase.rest_response import RestResponse
|
|
5
6
|
from pyxetabase.retry import retry
|
|
6
7
|
|
|
@@ -87,18 +88,23 @@ class _ParentRestClient(object):
|
|
|
87
88
|
def _post(self, category, resource, data=None, query_id=None, subcategory=None,
|
|
88
89
|
second_query_id=None, **options):
|
|
89
90
|
"""Queries the REST service and returns the result"""
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
data=data, **options
|
|
95
|
-
)
|
|
91
|
+
# Special treatment for the "/{apiVersion}/files/upload" endpoint
|
|
92
|
+
if category == 'files' and resource == 'upload':
|
|
93
|
+
response = self._upload(category=category, resource=resource, **options)
|
|
94
|
+
return RestResponse(response)
|
|
96
95
|
else:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
96
|
+
if data is not None:
|
|
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
|
+
data=data, **options
|
|
101
|
+
)
|
|
102
|
+
else:
|
|
103
|
+
return self._rest_retry(
|
|
104
|
+
method='post', category=category, resource=resource, query_id=query_id,
|
|
105
|
+
subcategory=subcategory, second_query_id=second_query_id,
|
|
106
|
+
**options
|
|
107
|
+
)
|
|
102
108
|
|
|
103
109
|
def _delete(self, category, resource, query_id=None, subcategory=None,
|
|
104
110
|
second_query_id=None, **options):
|
|
@@ -108,3 +114,31 @@ class _ParentRestClient(object):
|
|
|
108
114
|
subcategory=subcategory, second_query_id=second_query_id,
|
|
109
115
|
**options
|
|
110
116
|
)
|
|
117
|
+
|
|
118
|
+
def _upload(self, category, resource, **options):
|
|
119
|
+
"""Upload files"""
|
|
120
|
+
|
|
121
|
+
# Checking that the parameter file contains the file path to upload
|
|
122
|
+
if 'file' not in options or not isinstance(options['file'], str):
|
|
123
|
+
raise ValueError('To upload a file, please specify the file path as the "file" parameter.')
|
|
124
|
+
|
|
125
|
+
# Creating URL and headers
|
|
126
|
+
url, header = _create_rest_url(host=self._cfg.host, version=self._cfg.version, sid=self.token,
|
|
127
|
+
category=category, resource=resource, options=options)
|
|
128
|
+
|
|
129
|
+
# Creating data
|
|
130
|
+
data = {}
|
|
131
|
+
for k, v in options.items():
|
|
132
|
+
if k == 'file': # Param "file" is not included in data
|
|
133
|
+
continue
|
|
134
|
+
data[snake_to_camel_case(k)] = v
|
|
135
|
+
|
|
136
|
+
# Uploading
|
|
137
|
+
fpath = os.path.realpath(os.path.expanduser(options['file']))
|
|
138
|
+
with open(fpath, "rb") as f:
|
|
139
|
+
fhand = {"file": (fpath, f, "application/octet-stream")}
|
|
140
|
+
response = requests.post(url, headers=header, files=fhand, data=data or None)
|
|
141
|
+
if response.status_code != 200:
|
|
142
|
+
raise Exception(response.content)
|
|
143
|
+
|
|
144
|
+
return response.json()
|
|
@@ -147,8 +147,7 @@ class Admin(_ParentRestClient):
|
|
|
147
147
|
|
|
148
148
|
def sync_users(self, data=None, **options):
|
|
149
149
|
"""
|
|
150
|
-
|
|
151
|
-
group in a study from catalog.
|
|
150
|
+
[DEPRECATED] Moved to /users/sync.
|
|
152
151
|
PATH: /{apiVersion}/admin/users/sync
|
|
153
152
|
|
|
154
153
|
:param dict data: JSON containing the parameters. (REQUIRED)
|
|
@@ -55,15 +55,16 @@ class Meta(_ParentRestClient):
|
|
|
55
55
|
|
|
56
56
|
return self._get(category='meta', resource='model', **options)
|
|
57
57
|
|
|
58
|
-
def openapi(self, **options):
|
|
58
|
+
def openapi(self, url, **options):
|
|
59
59
|
"""
|
|
60
60
|
Opencga openapi json.
|
|
61
61
|
PATH: /{apiVersion}/meta/openapi
|
|
62
62
|
|
|
63
|
-
:param str
|
|
64
|
-
:param str
|
|
63
|
+
:param str url: Opencga host with environment. (REQUIRED)
|
|
64
|
+
:param str study: Opencga study to be default in queries.
|
|
65
65
|
"""
|
|
66
66
|
|
|
67
|
+
options['url'] = url
|
|
67
68
|
return self._get(category='meta', resource='openapi', **options)
|
|
68
69
|
|
|
69
70
|
def ping(self, **options):
|
|
@@ -166,6 +166,19 @@ class Study(_ParentRestClient):
|
|
|
166
166
|
|
|
167
167
|
return self._get(category='studies', resource='groups', query_id=study, **options)
|
|
168
168
|
|
|
169
|
+
def sync_groups(self, study, data=None, **options):
|
|
170
|
+
"""
|
|
171
|
+
Associate a remote group from an authentication origin with a local
|
|
172
|
+
group in a study.
|
|
173
|
+
PATH: /{apiVersion}/studies/{study}/groups/sync
|
|
174
|
+
|
|
175
|
+
:param dict data: JSON containing the parameters. (REQUIRED)
|
|
176
|
+
:param str study: Study [[organization@]project:]study where study and
|
|
177
|
+
project can be either the ID or UUID. (REQUIRED)
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
return self._post(category='studies', resource='sync', query_id=study, subcategory='groups', data=data, **options)
|
|
181
|
+
|
|
169
182
|
def update_groups(self, study, data=None, **options):
|
|
170
183
|
"""
|
|
171
184
|
Add or remove a group.
|
|
@@ -386,6 +399,21 @@ class Study(_ParentRestClient):
|
|
|
386
399
|
|
|
387
400
|
return self._post(category='studies', resource='update', query_id=study, data=data, **options)
|
|
388
401
|
|
|
402
|
+
def sync_users(self, study, authentication_origin_id, **options):
|
|
403
|
+
"""
|
|
404
|
+
Synchronize all users from the remote groups of a given authentication
|
|
405
|
+
origin.
|
|
406
|
+
PATH: /{apiVersion}/studies/{study}/users/sync
|
|
407
|
+
|
|
408
|
+
:param str authentication_origin_id: Authentication origin ID.
|
|
409
|
+
(REQUIRED)
|
|
410
|
+
:param str study: Study [[organization@]project:]study where study and
|
|
411
|
+
project can be either the ID or UUID. (REQUIRED)
|
|
412
|
+
"""
|
|
413
|
+
|
|
414
|
+
options['authenticationOriginId'] = authentication_origin_id
|
|
415
|
+
return self._post(category='studies', resource='sync', query_id=study, subcategory='users', **options)
|
|
416
|
+
|
|
389
417
|
def variable_sets(self, study, **options):
|
|
390
418
|
"""
|
|
391
419
|
Fetch variableSets from a study.
|
|
@@ -86,6 +86,32 @@ class VariantOperation(_ParentRestClient):
|
|
|
86
86
|
|
|
87
87
|
return self._delete(category='operation', resource='delete', subcategory='variant/annotation', **options)
|
|
88
88
|
|
|
89
|
+
def variant_annotation_extension_configure(self, data=None, **options):
|
|
90
|
+
"""
|
|
91
|
+
Install a variant annotation extension and configure the project to
|
|
92
|
+
use it.
|
|
93
|
+
PATH: /{apiVersion}/operation/variant/annotation/extension/configure
|
|
94
|
+
|
|
95
|
+
:param str job_id: Job ID. It must be a unique string within the
|
|
96
|
+
study. An ID will be autogenerated automatically if not provided.
|
|
97
|
+
:param str job_description: Job description.
|
|
98
|
+
:param str job_depends_on: Comma separated list of existing job IDs
|
|
99
|
+
the job will depend on.
|
|
100
|
+
:param str job_tags: Job tags.
|
|
101
|
+
:param str job_scheduled_start_time: Time when the job is scheduled to
|
|
102
|
+
start.
|
|
103
|
+
:param str job_priority: Priority of the job.
|
|
104
|
+
:param bool job_dry_run: Flag indicating that the job will be executed
|
|
105
|
+
in dry-run mode. In this mode, OpenCGA will validate that all
|
|
106
|
+
parameters and prerequisites are correctly set for successful
|
|
107
|
+
execution, but the job will not actually run.
|
|
108
|
+
:param str project: project.
|
|
109
|
+
:param dict data: Parameters to configure a variant annotation
|
|
110
|
+
extension.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
return self._post(category='operation', resource='configure', subcategory='variant/annotation/extension', data=data, **options)
|
|
114
|
+
|
|
89
115
|
def index_variant_annotation(self, data=None, **options):
|
|
90
116
|
"""
|
|
91
117
|
Create and load variant annotations into the database.
|
|
@@ -601,33 +627,6 @@ class VariantOperation(_ParentRestClient):
|
|
|
601
627
|
|
|
602
628
|
return self._post(category='operation', resource='secondaryIndex', subcategory='variant', data=data, **options)
|
|
603
629
|
|
|
604
|
-
def delete_variant_secondary_index(self, **options):
|
|
605
|
-
"""
|
|
606
|
-
Remove a secondary index from the search engine for a specific set of
|
|
607
|
-
samples.
|
|
608
|
-
PATH: /{apiVersion}/operation/variant/secondaryIndex/delete
|
|
609
|
-
|
|
610
|
-
:param str job_id: Job ID. It must be a unique string within the
|
|
611
|
-
study. An ID will be autogenerated automatically if not provided.
|
|
612
|
-
:param str job_description: Job description.
|
|
613
|
-
:param str job_depends_on: Comma separated list of existing job IDs
|
|
614
|
-
the job will depend on.
|
|
615
|
-
:param str job_tags: Job tags.
|
|
616
|
-
:param str job_scheduled_start_time: Time when the job is scheduled to
|
|
617
|
-
start.
|
|
618
|
-
:param str job_priority: Priority of the job.
|
|
619
|
-
:param bool job_dry_run: Flag indicating that the job will be executed
|
|
620
|
-
in dry-run mode. In this mode, OpenCGA will validate that all
|
|
621
|
-
parameters and prerequisites are correctly set for successful
|
|
622
|
-
execution, but the job will not actually run.
|
|
623
|
-
:param str study: Study [[organization@]project:]study where study and
|
|
624
|
-
project can be either the ID or UUID.
|
|
625
|
-
:param str samples: Samples to remove. Needs to provide all the
|
|
626
|
-
samples in the secondary index.
|
|
627
|
-
"""
|
|
628
|
-
|
|
629
|
-
return self._delete(category='operation', resource='delete', subcategory='variant/secondaryIndex', **options)
|
|
630
|
-
|
|
631
630
|
def setup_variant(self, data=None, **options):
|
|
632
631
|
"""
|
|
633
632
|
Execute Variant Setup to allow using the variant engine. This setup is
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
pyxetabase/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
pyxetabase/commons.py,sha256=Okt5DgvSpZW9PUsxoLNpROb0BDrdr_zhgry4X3dQpfk,14308
|
|
3
|
+
pyxetabase/exceptions.py,sha256=GmtDcurD3d_fzaa8AEntF4-sZR4Elgg4Iz3z-UUqjYU,274
|
|
4
|
+
pyxetabase/opencga_client.py,sha256=V-M9i28z3A-Vgw5BnG_Ddm0MUdjQ0_XG38gohGtzavI,15386
|
|
5
|
+
pyxetabase/opencga_config.py,sha256=RK23fextK79S5wq4FaQygwuPnE4p4J9GbjY6hJ0Krxc,7009
|
|
6
|
+
pyxetabase/rest_response.py,sha256=TgwTI2LZFF_jV9-HSawGkF_qZ88n-dxEtIKiFcfPyDk,8635
|
|
7
|
+
pyxetabase/retry.py,sha256=LjViQOaa_GkpDFkcRq9jIS183mE9t4Rq0uls9PV_mfI,2297
|
|
8
|
+
pyxetabase/rest_clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
pyxetabase/rest_clients/_parent_rest_clients.py,sha256=OuCnmaziUCENpmpA3CzKJGEhMY_Z-7Q_rlkH75QDi7I,5854
|
|
10
|
+
pyxetabase/rest_clients/admin_client.py,sha256=WebfbQVa7CjiaNcp5MqwAkgDTqCfW-v5AgTaxmL5EF0,6927
|
|
11
|
+
pyxetabase/rest_clients/alignment_client.py,sha256=srY1fmjMZCPWlbqhrD2gYVhS2zu7fgPx61tZOqQi4Wc,18509
|
|
12
|
+
pyxetabase/rest_clients/clinical_analysis_client.py,sha256=j2LmLT8wg3C9srRMB2mksEVFnqN22Anb7HxZCbdF50c,69126
|
|
13
|
+
pyxetabase/rest_clients/cohort_client.py,sha256=D-su-AFOziztVMI-oAcr5G2Uq_psbLV8Wt0bqZ7zZgI,16324
|
|
14
|
+
pyxetabase/rest_clients/cvdb_client.py,sha256=V7ny5nQEeJqsbALAe7JLyoV6b0XyYF7c9lsn-qK-i8o,143102
|
|
15
|
+
pyxetabase/rest_clients/disease_panel_client.py,sha256=GAYRyAfoB_VunskXOhAzMNbsrFupb6M2QbRK3x1Y5vo,18513
|
|
16
|
+
pyxetabase/rest_clients/family_client.py,sha256=n-kyjnrV09R50JxyiHjFs8rCQkOKFU0Z1O7oVYuaKxg,18586
|
|
17
|
+
pyxetabase/rest_clients/federation_client.py,sha256=p5B8dRq9gj-fdgIfIPhmkT7mNU65cRFtH06kaGtmbWE,5066
|
|
18
|
+
pyxetabase/rest_clients/file_client.py,sha256=fwTWnZtprYNDUBvRetLLe6PTBxW_M8LxN6OlAMGWXC4,33730
|
|
19
|
+
pyxetabase/rest_clients/ga4gh_client.py,sha256=fPnmiblnfpz_zcZPJblGqOFksrdiF3MUer3fv2nwQwk,3506
|
|
20
|
+
pyxetabase/rest_clients/individual_client.py,sha256=C59zr_t7tTxNR7ZBeAPY6fyYl59fE7KY05MaLyXWmXw,23225
|
|
21
|
+
pyxetabase/rest_clients/job_client.py,sha256=amoBW3SL56l6sAPCUB55rZhLR8DcdIIfb5ymOZ9NkLQ,19811
|
|
22
|
+
pyxetabase/rest_clients/meta_client.py,sha256=uExVRfdSKiXteLYe7R3q9x3zCp9mMWWMyPDU56TtoDo,2308
|
|
23
|
+
pyxetabase/rest_clients/organization_client.py,sha256=tnwUTCqJoGiKXMQLMv4ymHA16JxnR5iL2SMLaJDBRs8,9465
|
|
24
|
+
pyxetabase/rest_clients/project_client.py,sha256=M8naPsj47z2ylTrJNU_JyHReKzXrB038PoEtKKrtxmc,5212
|
|
25
|
+
pyxetabase/rest_clients/sample_client.py,sha256=VOsPAhw9HwaEHzzw_5gcVQ1v2xSesvzN3TO4z2opaNo,23621
|
|
26
|
+
pyxetabase/rest_clients/study_client.py,sha256=STtboEiGP-lQ1UptjHxk-ANd3uG6cA6U3srvOO4UQIE,21491
|
|
27
|
+
pyxetabase/rest_clients/user_client.py,sha256=frA7-rMii-yoRyca_Orkj1T80OeEe-zCdWZCHKn1sio,7683
|
|
28
|
+
pyxetabase/rest_clients/variant_client.py,sha256=mmBuVE0JBThJr5zsLGci5nykNcCKyfZXRKl-h3HT9PA,75436
|
|
29
|
+
pyxetabase/rest_clients/variant_operation_client.py,sha256=ceZ2-ii0D1oCDMRz_EURwUf_VjtsV5lzJuY-ZwG1b6Q,36840
|
|
30
|
+
pyxetabase/rest_clients/workflow_client.py,sha256=QYnyI17aNCjq-uXlguaSj78F0xupeWwmf8uYK1Y5tf4,12482
|
|
31
|
+
pyxetabase-4.0.0.dev29.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
32
|
+
pyxetabase-4.0.0.dev29.dist-info/METADATA,sha256=TL6HOzwe313tz-0JeNMPB2aGt2WsY0qniQ1Xl2oxiUc,5540
|
|
33
|
+
pyxetabase-4.0.0.dev29.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
34
|
+
pyxetabase-4.0.0.dev29.dist-info/top_level.txt,sha256=0m5pDpBX-lM8QpPl7bTpTQAm4kgu2-nr-pcaEu4Tn_8,11
|
|
35
|
+
pyxetabase-4.0.0.dev29.dist-info/RECORD,,
|
pyopencga/__init__.py
DELETED
|
File without changes
|
pyopencga/commons.py
DELETED
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import threading
|
|
3
|
-
from time import sleep
|
|
4
|
-
import warnings
|
|
5
|
-
|
|
6
|
-
import requests
|
|
7
|
-
|
|
8
|
-
from pyopencga.exceptions import OpencgaInvalidToken, OpencgaAuthorisationError
|
|
9
|
-
|
|
10
|
-
try:
|
|
11
|
-
from Queue import Queue
|
|
12
|
-
except ImportError:
|
|
13
|
-
from queue import Queue
|
|
14
|
-
|
|
15
|
-
_CALL_BATCH_SIZE = 2000
|
|
16
|
-
_NUM_THREADS_DEFAULT = 4
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def deprecated(func):
|
|
20
|
-
"""Prints a warning for functions marked as deprecated"""
|
|
21
|
-
def new_func(*args, **kwargs):
|
|
22
|
-
warnings.simplefilter('always', DeprecationWarning) # turn off filter
|
|
23
|
-
warnings.warn('Call to deprecated function "{}".'.format(func.__name__),
|
|
24
|
-
category=DeprecationWarning, stacklevel=2)
|
|
25
|
-
warnings.simplefilter('default', DeprecationWarning) # reset filter
|
|
26
|
-
return func(*args, **kwargs)
|
|
27
|
-
return new_func
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def snake_to_camel_case(text):
|
|
31
|
-
"""Converts snake_case to camelCase"""
|
|
32
|
-
components = text.split('_')
|
|
33
|
-
return components[0] + ''.join(x.title() for x in components[1:])
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def _create_rest_url(host, version, sid, category, resource, subcategory=None, query_id=None,
|
|
37
|
-
second_query_id=None, options=None):
|
|
38
|
-
"""Creates the URL for querying the REST service"""
|
|
39
|
-
|
|
40
|
-
# Creating the basic URL
|
|
41
|
-
url = ('/'.join([host,
|
|
42
|
-
'webservices/rest',
|
|
43
|
-
version,
|
|
44
|
-
category
|
|
45
|
-
]))
|
|
46
|
-
|
|
47
|
-
# If subcategory is queried, query_id can be absent
|
|
48
|
-
if query_id is not None:
|
|
49
|
-
url += '/' + query_id
|
|
50
|
-
|
|
51
|
-
if subcategory is not None:
|
|
52
|
-
url += '/' + subcategory
|
|
53
|
-
|
|
54
|
-
if second_query_id is not None:
|
|
55
|
-
url += '/' + second_query_id
|
|
56
|
-
|
|
57
|
-
url += '/' + resource
|
|
58
|
-
|
|
59
|
-
header = {"Accept-Encoding": "gzip"}
|
|
60
|
-
if sid is not None:
|
|
61
|
-
header['Authorization'] = 'Bearer {}'.format(sid)
|
|
62
|
-
|
|
63
|
-
# Checking optional params
|
|
64
|
-
if options is not None:
|
|
65
|
-
opts = []
|
|
66
|
-
for k, v in options.items():
|
|
67
|
-
k = snake_to_camel_case(k)
|
|
68
|
-
if k == 'debug':
|
|
69
|
-
continue
|
|
70
|
-
if isinstance(v, list):
|
|
71
|
-
opts.append(k + '=' + ','.join(map(str, v)))
|
|
72
|
-
else:
|
|
73
|
-
opts.append(k + '=' + str(v))
|
|
74
|
-
if opts:
|
|
75
|
-
url += '?' + '&'.join(opts)
|
|
76
|
-
return url, header
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def _fetch(config, sid, category, resource, method, subcategory=None, query_id=None,
|
|
80
|
-
second_query_id=None, data=None, options=None):
|
|
81
|
-
"""Queries the REST service retrieving results until exhaustion or limit"""
|
|
82
|
-
# HERE BE DRAGONS
|
|
83
|
-
final_response = None
|
|
84
|
-
|
|
85
|
-
# Setting up skip and limit default parameters
|
|
86
|
-
call_skip = 0
|
|
87
|
-
call_limit = 1000
|
|
88
|
-
max_limit = None
|
|
89
|
-
if options is None:
|
|
90
|
-
opts = {'skip': call_skip, 'limit': call_limit}
|
|
91
|
-
else:
|
|
92
|
-
opts = options.copy() # Do not modify original data!
|
|
93
|
-
if 'skip' not in opts:
|
|
94
|
-
opts['skip'] = call_skip
|
|
95
|
-
# If 'limit' is specified, a maximum of 'limit' results will be returned
|
|
96
|
-
if 'limit' in opts:
|
|
97
|
-
max_limit = int(opts['limit'])
|
|
98
|
-
# Server must be always queried for results in groups of 1000
|
|
99
|
-
opts['limit'] = call_limit
|
|
100
|
-
|
|
101
|
-
# If there is a query_id, the next variables will be used
|
|
102
|
-
total_id_list = [] # All initial ids
|
|
103
|
-
next_id_list = [] # Ids which should be queried again for more results
|
|
104
|
-
next_id_indexes = [] # Ids position in the final response
|
|
105
|
-
if query_id is not None:
|
|
106
|
-
total_id_list = query_id.split(',')
|
|
107
|
-
|
|
108
|
-
# If some query has more than 'call_limit' results, the server will be
|
|
109
|
-
# queried again to retrieve the next 'call_limit results'
|
|
110
|
-
call = True
|
|
111
|
-
current_query_id = None # Current REST query
|
|
112
|
-
current_id_list = None # Current list of ids
|
|
113
|
-
time_out_counter = 0 # Number of times a query is repeated due to time-out
|
|
114
|
-
while call:
|
|
115
|
-
# Check 'limit' parameter if there is a maximum limit of results
|
|
116
|
-
if max_limit is not None and max_limit <= call_limit:
|
|
117
|
-
opts['limit'] = max_limit
|
|
118
|
-
|
|
119
|
-
# Updating query_id and list of ids to query
|
|
120
|
-
if query_id is not None:
|
|
121
|
-
if current_query_id is None:
|
|
122
|
-
current_query_id = query_id
|
|
123
|
-
current_id_list = total_id_list
|
|
124
|
-
current_id_indexes = range(len(total_id_list))
|
|
125
|
-
else:
|
|
126
|
-
current_query_id = ','.join(next_id_list)
|
|
127
|
-
current_id_list = next_id_list
|
|
128
|
-
current_id_indexes = next_id_indexes
|
|
129
|
-
|
|
130
|
-
# Retrieving url
|
|
131
|
-
url, header = _create_rest_url(host=config.host,
|
|
132
|
-
version=config.version,
|
|
133
|
-
category=category,
|
|
134
|
-
sid=sid,
|
|
135
|
-
subcategory=subcategory,
|
|
136
|
-
query_id=current_query_id,
|
|
137
|
-
second_query_id=second_query_id,
|
|
138
|
-
resource=resource,
|
|
139
|
-
options=opts)
|
|
140
|
-
|
|
141
|
-
# DEBUG param
|
|
142
|
-
if opts is not None and 'debug' in opts and opts['debug']:
|
|
143
|
-
sys.stderr.write(url + '\n')
|
|
144
|
-
|
|
145
|
-
# Getting REST response
|
|
146
|
-
if method == 'get':
|
|
147
|
-
try:
|
|
148
|
-
r = requests.get(url, headers=header, cookies=config.cookies, verify=not config.tlsAllowInvalidCertificates)
|
|
149
|
-
except requests.exceptions.ConnectionError:
|
|
150
|
-
sleep(1)
|
|
151
|
-
r = requests.get(url, headers=header, cookies=config.cookies, verify=not config.tlsAllowInvalidCertificates)
|
|
152
|
-
elif method == 'post':
|
|
153
|
-
try:
|
|
154
|
-
r = requests.post(url, json=data, headers=header, cookies=config.cookies, verify=not config.tlsAllowInvalidCertificates)
|
|
155
|
-
except requests.exceptions.ConnectionError:
|
|
156
|
-
sleep(1)
|
|
157
|
-
r = requests.post(url, json=data, headers=header, cookies=config.cookies, verify=not config.tlsAllowInvalidCertificates)
|
|
158
|
-
elif method == 'delete':
|
|
159
|
-
try:
|
|
160
|
-
r = requests.delete(url, headers=header, cookies=config.cookies, verify=not config.tlsAllowInvalidCertificates)
|
|
161
|
-
except requests.exceptions.ConnectionError:
|
|
162
|
-
sleep(1)
|
|
163
|
-
r = requests.delete(url, headers=header, cookies=config.cookies, verify=not config.tlsAllowInvalidCertificates)
|
|
164
|
-
else:
|
|
165
|
-
raise NotImplementedError('method: ' + method + ' not implemented.')
|
|
166
|
-
|
|
167
|
-
if r.status_code == 504: # Gateway Time-out
|
|
168
|
-
if time_out_counter == 99:
|
|
169
|
-
msg = 'Server not responding in time'
|
|
170
|
-
raise requests.ConnectionError(msg)
|
|
171
|
-
time_out_counter += 1
|
|
172
|
-
continue
|
|
173
|
-
time_out_counter = 0
|
|
174
|
-
|
|
175
|
-
if r.status_code == 401:
|
|
176
|
-
raise OpencgaInvalidToken(r.content)
|
|
177
|
-
elif r.status_code == 403:
|
|
178
|
-
raise OpencgaAuthorisationError(r.content)
|
|
179
|
-
elif r.status_code != 200:
|
|
180
|
-
raise Exception(r.content)
|
|
181
|
-
|
|
182
|
-
if r.headers['Content-Type'] == 'application/json':
|
|
183
|
-
try:
|
|
184
|
-
response = r.json()
|
|
185
|
-
|
|
186
|
-
# TODO Remove deprecated response and result in future release. Added for backwards compatibility
|
|
187
|
-
if 'response' in response:
|
|
188
|
-
response['responses'] = response['response']
|
|
189
|
-
for query_result in response['responses']:
|
|
190
|
-
if 'result' in query_result:
|
|
191
|
-
query_result['results'] = query_result['result']
|
|
192
|
-
|
|
193
|
-
except ValueError:
|
|
194
|
-
raise ValueError('Bad JSON format retrieved from server')
|
|
195
|
-
elif r.headers['Content-Type'] == 'application/octet-stream':
|
|
196
|
-
return r.content
|
|
197
|
-
else:
|
|
198
|
-
raise ValueError('Unexpected content type retrieved from server ("{}"): "{}"'.format(
|
|
199
|
-
r.headers['Content-Type'], r.content)
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
# Setting up final_response
|
|
203
|
-
if final_response is None:
|
|
204
|
-
final_response = response
|
|
205
|
-
# Concatenating results
|
|
206
|
-
else:
|
|
207
|
-
if query_id is not None:
|
|
208
|
-
for index, res in enumerate(response['responses']):
|
|
209
|
-
id_index = current_id_indexes[index]
|
|
210
|
-
final_response[id_index]['results'] += res['results']
|
|
211
|
-
else:
|
|
212
|
-
final_response['responses'][0]['results'] += response['responses'][0]['results']
|
|
213
|
-
|
|
214
|
-
if query_id is not None:
|
|
215
|
-
# Checking which ids are completely retrieved
|
|
216
|
-
next_id_list = []
|
|
217
|
-
next_id_indexes = []
|
|
218
|
-
for index, res in enumerate(response['responses']):
|
|
219
|
-
if res['numResults'] == call_limit:
|
|
220
|
-
next_id_list.append(current_id_list[index])
|
|
221
|
-
next_id_indexes.append(current_id_indexes[index])
|
|
222
|
-
# Ending REST calling when there are no more ids to retrieve
|
|
223
|
-
if not next_id_list:
|
|
224
|
-
call = False
|
|
225
|
-
else:
|
|
226
|
-
# Ending REST calling when there are no more results to retrieve
|
|
227
|
-
if response['responses'][0]['numResults'] != call_limit:
|
|
228
|
-
call = False
|
|
229
|
-
|
|
230
|
-
# Skipping the first 'limit' results to retrieve the next ones
|
|
231
|
-
opts['skip'] += call_limit
|
|
232
|
-
|
|
233
|
-
# Subtracting the number of returned results from the maximum goal
|
|
234
|
-
if max_limit is not None:
|
|
235
|
-
max_limit -= call_limit
|
|
236
|
-
# When 'limit' is 0 returns all the results. So, break the loop if 0
|
|
237
|
-
if max_limit == 0:
|
|
238
|
-
break
|
|
239
|
-
|
|
240
|
-
return final_response
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
def _worker(queue, results, config, sid, category, resource, method, subcategory=None,
|
|
244
|
-
second_query_id=None, data=None, options=None):
|
|
245
|
-
|
|
246
|
-
"""Manages the queue system for the threads"""
|
|
247
|
-
while True:
|
|
248
|
-
# Fetching new element from the queue
|
|
249
|
-
index, query_id = queue.get()
|
|
250
|
-
response = _fetch(config=config, sid=sid, category=category, subcategory=subcategory,
|
|
251
|
-
resource=resource, method=method, data=data, query_id=query_id,
|
|
252
|
-
second_query_id=second_query_id, options=options)
|
|
253
|
-
# Store data in results at correct index
|
|
254
|
-
results[index] = response
|
|
255
|
-
# Signaling to the queue that task has been processed
|
|
256
|
-
queue.task_done()
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
def merge_query_responses(query_response_list):
|
|
260
|
-
final_response = query_response_list[0]
|
|
261
|
-
for i, query_response in enumerate(query_response_list):
|
|
262
|
-
if i != 0:
|
|
263
|
-
final_response['events'] += query_response['events']
|
|
264
|
-
final_response['time'] += query_response['time']
|
|
265
|
-
# final_response['responses'] += response['responses']
|
|
266
|
-
|
|
267
|
-
for key in query_response['params']:
|
|
268
|
-
if final_response['params'][key] != query_response['params'][key]:
|
|
269
|
-
final_response['params'][key] += ',' + query_response['params'][key]
|
|
270
|
-
|
|
271
|
-
for j, query_result in enumerate(query_response['responses']):
|
|
272
|
-
if len(final_response['responses'])-1 < j:
|
|
273
|
-
final_response['responses'] += []
|
|
274
|
-
for key in query_result:
|
|
275
|
-
if key not in final_response['responses'][j]:
|
|
276
|
-
final_response['responses'][j][key] = query_result[key]
|
|
277
|
-
else:
|
|
278
|
-
if isinstance(query_result[key], (int, list)):
|
|
279
|
-
final_response['responses'][j][key] += query_result[key]
|
|
280
|
-
return final_response
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
def execute(config, sid, category, resource, method, subcategory=None, query_id=None,
|
|
284
|
-
second_query_id=None, data=None, options=None):
|
|
285
|
-
"""Queries the REST service using multiple threads if needed"""
|
|
286
|
-
|
|
287
|
-
# If query_id is an array, convert to comma-separated string
|
|
288
|
-
if query_id is not None:
|
|
289
|
-
if isinstance(query_id, list):
|
|
290
|
-
query_id = ','.join([str(item) for item in query_id])
|
|
291
|
-
else:
|
|
292
|
-
query_id = str(query_id) # convert to string so we can call this method with int ids
|
|
293
|
-
|
|
294
|
-
# Multithread if the number of queries is greater than _CALL_BATCH_SIZE
|
|
295
|
-
if query_id is None or len(query_id.split(',')) <= _CALL_BATCH_SIZE:
|
|
296
|
-
response = _fetch(config=config, sid=sid, category=category, subcategory=subcategory,
|
|
297
|
-
resource=resource, method=method, data=data, query_id=query_id,
|
|
298
|
-
second_query_id=second_query_id, options=options)
|
|
299
|
-
return response
|
|
300
|
-
else:
|
|
301
|
-
if options is not None and 'num_threads' in options:
|
|
302
|
-
num_threads = options['num_threads']
|
|
303
|
-
else:
|
|
304
|
-
num_threads = _NUM_THREADS_DEFAULT
|
|
305
|
-
|
|
306
|
-
# Splitting query_id into batches depending on the call batch size
|
|
307
|
-
id_list = query_id.split(',')
|
|
308
|
-
id_batches = [','.join(id_list[x:x + _CALL_BATCH_SIZE])
|
|
309
|
-
for x in range(0, len(id_list), _CALL_BATCH_SIZE)]
|
|
310
|
-
|
|
311
|
-
# Setting up the queue to hold all the id batches
|
|
312
|
-
q = Queue(maxsize=0)
|
|
313
|
-
# Creating a size defined list to store thread results
|
|
314
|
-
res = [''] * len(id_batches)
|
|
315
|
-
|
|
316
|
-
# Setting up the threads
|
|
317
|
-
for thread in range(num_threads):
|
|
318
|
-
t = threading.Thread(target=_worker,
|
|
319
|
-
kwargs={'queue': q,
|
|
320
|
-
'results': res,
|
|
321
|
-
'config': config,
|
|
322
|
-
'sid': sid,
|
|
323
|
-
'category': category,
|
|
324
|
-
'subcategory': subcategory,
|
|
325
|
-
'second_query_id': second_query_id,
|
|
326
|
-
'resource': resource,
|
|
327
|
-
'method': method,
|
|
328
|
-
'data': data,
|
|
329
|
-
'options': options})
|
|
330
|
-
# Setting threads as "daemon" allows main program to exit eventually
|
|
331
|
-
# even if these do not finish correctly
|
|
332
|
-
t.daemon = True
|
|
333
|
-
t.start()
|
|
334
|
-
|
|
335
|
-
# Loading up the queue with index and id batches for each job
|
|
336
|
-
for index, batch in enumerate(id_batches):
|
|
337
|
-
q.put((index, batch)) # Notice this is a tuple
|
|
338
|
-
|
|
339
|
-
# Waiting until the queue has been processed
|
|
340
|
-
q.join()
|
|
341
|
-
|
|
342
|
-
# Joining all the responses into a one final response
|
|
343
|
-
final_query_response = merge_query_responses(res)
|
|
344
|
-
|
|
345
|
-
return final_query_response
|
|
346
|
-
|
|
347
|
-
|
pyopencga/exceptions.py
DELETED