dcicutils 8.4.0.1b11__py3-none-any.whl → 8.4.0.1b12__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.
- dcicutils/portal_utils.py +267 -0
- dcicutils/structured_data.py +11 -247
- {dcicutils-8.4.0.1b11.dist-info → dcicutils-8.4.0.1b12.dist-info}/METADATA +1 -1
- {dcicutils-8.4.0.1b11.dist-info → dcicutils-8.4.0.1b12.dist-info}/RECORD +7 -6
- {dcicutils-8.4.0.1b11.dist-info → dcicutils-8.4.0.1b12.dist-info}/LICENSE.txt +0 -0
- {dcicutils-8.4.0.1b11.dist-info → dcicutils-8.4.0.1b12.dist-info}/WHEEL +0 -0
- {dcicutils-8.4.0.1b11.dist-info → dcicutils-8.4.0.1b12.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,267 @@
|
|
1
|
+
from collections import deque
|
2
|
+
from pyramid.paster import get_app
|
3
|
+
from pyramid.router import Router
|
4
|
+
import re
|
5
|
+
import requests
|
6
|
+
from requests.models import Response as RequestResponse
|
7
|
+
from typing import Optional, Type, Union
|
8
|
+
from webtest.app import TestApp, TestResponse
|
9
|
+
from dcicutils.common import OrchestratedApp, APP_CGAP, APP_FOURFRONT, APP_SMAHT, ORCHESTRATED_APPS
|
10
|
+
from dcicutils.creds_utils import CGAPKeyManager, FourfrontKeyManager, SMaHTKeyManager
|
11
|
+
from dcicutils.ff_utils import get_metadata, get_schema, patch_metadata, post_metadata
|
12
|
+
from dcicutils.misc_utils import to_camel_case, VirtualApp
|
13
|
+
from dcicutils.zip_utils import temporary_file
|
14
|
+
|
15
|
+
Portal = Type["Portal"] # Forward type reference for type hints.
|
16
|
+
FILE_SCHEMA_NAME = "File"
|
17
|
+
|
18
|
+
|
19
|
+
class Portal:
|
20
|
+
|
21
|
+
def __init__(self,
|
22
|
+
arg: Optional[Union[VirtualApp, TestApp, Router, Portal, dict, tuple, str]] = None,
|
23
|
+
env: Optional[str] = None, app: Optional[OrchestratedApp] = None, server: Optional[str] = None,
|
24
|
+
key: Optional[Union[dict, tuple]] = None,
|
25
|
+
vapp: Optional[Union[VirtualApp, TestApp, Router, Portal, str]] = None,
|
26
|
+
portal: Optional[Union[VirtualApp, TestApp, Router, Portal, str]] = None) -> Portal:
|
27
|
+
if vapp and not portal:
|
28
|
+
portal = vapp
|
29
|
+
if ((isinstance(arg, (VirtualApp, TestApp, Router, Portal)) or
|
30
|
+
isinstance(arg, str) and arg.endswith(".ini")) and not portal):
|
31
|
+
portal = arg
|
32
|
+
elif isinstance(arg, str) and not env:
|
33
|
+
env = arg
|
34
|
+
elif (isinstance(arg, dict) or isinstance(arg, tuple)) and not key:
|
35
|
+
key = arg
|
36
|
+
if not app and env:
|
37
|
+
if env.startswith(APP_SMAHT):
|
38
|
+
app = APP_SMAHT
|
39
|
+
elif env.startswith(APP_CGAP):
|
40
|
+
app = APP_CGAP
|
41
|
+
elif env.startswith(APP_FOURFRONT):
|
42
|
+
app = APP_FOURFRONT
|
43
|
+
if isinstance(portal, Portal):
|
44
|
+
self._vapp = portal._vapp
|
45
|
+
self._env = portal._env
|
46
|
+
self._app = portal._app
|
47
|
+
self._server = portal._server
|
48
|
+
self._key = portal._key
|
49
|
+
self._key_pair = portal._key_pair
|
50
|
+
self._key_file = portal._key_file
|
51
|
+
return
|
52
|
+
self._vapp = None
|
53
|
+
self._env = env
|
54
|
+
self._app = app
|
55
|
+
self._server = server
|
56
|
+
self._key = None
|
57
|
+
self._key_pair = None
|
58
|
+
self._key_file = None
|
59
|
+
if isinstance(portal, (VirtualApp, TestApp)):
|
60
|
+
self._vapp = portal
|
61
|
+
elif isinstance(portal, (Router, str)):
|
62
|
+
self._vapp = Portal._create_testapp(portal)
|
63
|
+
elif isinstance(key, dict):
|
64
|
+
self._key = key
|
65
|
+
self._key_pair = (key.get("key"), key.get("secret")) if key else None
|
66
|
+
if key_server := key.get("server"):
|
67
|
+
self._server = key_server
|
68
|
+
elif isinstance(key, tuple) and len(key) >= 2:
|
69
|
+
self._key = {"key": key[0], "secret": key[1]}
|
70
|
+
self._key_pair = key
|
71
|
+
elif isinstance(env, str):
|
72
|
+
key_managers = {APP_CGAP: CGAPKeyManager, APP_FOURFRONT: FourfrontKeyManager, APP_SMAHT: SMaHTKeyManager}
|
73
|
+
if not (key_manager := key_managers.get(self._app)) or not (key_manager := key_manager()):
|
74
|
+
raise Exception(f"Invalid app name: {self._app} (valid: {', '.join(ORCHESTRATED_APPS)}).")
|
75
|
+
if isinstance(env, str):
|
76
|
+
self._key = key_manager.get_keydict_for_env(env)
|
77
|
+
if key_server := self._key.get("server"):
|
78
|
+
self._server = key_server
|
79
|
+
elif isinstance(self._server, str):
|
80
|
+
self._key = key_manager.get_keydict_for_server(self._server)
|
81
|
+
self._key_pair = key_manager.keydict_to_keypair(self._key) if self._key else None
|
82
|
+
self._key_file = key_manager.keys_file
|
83
|
+
|
84
|
+
@property
|
85
|
+
def env(self):
|
86
|
+
return self._env
|
87
|
+
|
88
|
+
@property
|
89
|
+
def app(self):
|
90
|
+
return self._app
|
91
|
+
|
92
|
+
@property
|
93
|
+
def server(self):
|
94
|
+
return self._server
|
95
|
+
|
96
|
+
@property
|
97
|
+
def key(self):
|
98
|
+
return self._key
|
99
|
+
|
100
|
+
@property
|
101
|
+
def key_pair(self):
|
102
|
+
return self._key_pair
|
103
|
+
|
104
|
+
@property
|
105
|
+
def key_file(self):
|
106
|
+
return self._key_file
|
107
|
+
|
108
|
+
@property
|
109
|
+
def vapp(self):
|
110
|
+
return self._vapp
|
111
|
+
|
112
|
+
def get_metadata(self, object_id: str) -> Optional[dict]:
|
113
|
+
return get_metadata(obj_id=object_id, vapp=self._vapp, key=self._key)
|
114
|
+
|
115
|
+
def patch_metadata(self, object_id: str, data: str) -> Optional[dict]:
|
116
|
+
if self._key:
|
117
|
+
return patch_metadata(obj_id=object_id, patch_item=data, key=self._key)
|
118
|
+
return self.patch(f"/{object_id}", data)
|
119
|
+
|
120
|
+
def post_metadata(self, object_type: str, data: str) -> Optional[dict]:
|
121
|
+
if self._key:
|
122
|
+
return post_metadata(schema_name=object_type, post_item=data, key=self._key)
|
123
|
+
return self.post(f"/{object_type}", data)
|
124
|
+
|
125
|
+
def get(self, uri: str, follow: bool = True, **kwargs) -> Optional[Union[RequestResponse, TestResponse]]:
|
126
|
+
if isinstance(self._vapp, (VirtualApp, TestApp)):
|
127
|
+
response = self._vapp.get(self._uri(uri), **self._kwargs(**kwargs))
|
128
|
+
if response and response.status_code in [301, 302, 303, 307, 308] and follow:
|
129
|
+
response = response.follow()
|
130
|
+
return self._response(response)
|
131
|
+
return requests.get(self._uri(uri), allow_redirects=follow, **self._kwargs(**kwargs))
|
132
|
+
|
133
|
+
def patch(self, uri: str, data: Optional[dict] = None,
|
134
|
+
json: Optional[dict] = None, **kwargs) -> Optional[Union[RequestResponse, TestResponse]]:
|
135
|
+
if isinstance(self._vapp, (VirtualApp, TestApp)):
|
136
|
+
return self._vapp.patch_json(self._uri(uri), json or data, **self._kwargs(**kwargs))
|
137
|
+
return requests.patch(self._uri(uri), json=json or data, **self._kwargs(**kwargs))
|
138
|
+
|
139
|
+
def post(self, uri: str, data: Optional[dict] = None, json: Optional[dict] = None,
|
140
|
+
files: Optional[dict] = None, **kwargs) -> Optional[Union[RequestResponse, TestResponse]]:
|
141
|
+
if isinstance(self._vapp, (VirtualApp, TestApp)):
|
142
|
+
if files:
|
143
|
+
return self._vapp.post(self._uri(uri), json or data, upload_files=files, **self._kwargs(**kwargs))
|
144
|
+
else:
|
145
|
+
return self._vapp.post_json(self._uri(uri), json or data, upload_files=files, **self._kwargs(**kwargs))
|
146
|
+
return requests.post(self._uri(uri), json=json or data, files=files, **self._kwargs(**kwargs))
|
147
|
+
|
148
|
+
def get_schema(self, schema_name: str) -> Optional[dict]:
|
149
|
+
return get_schema(self.schema_name(schema_name), portal_vapp=self._vapp, key=self._key)
|
150
|
+
|
151
|
+
def get_schemas(self) -> dict:
|
152
|
+
return self.get("/profiles/").json()
|
153
|
+
|
154
|
+
@staticmethod
|
155
|
+
def schema_name(name: str) -> str:
|
156
|
+
return to_camel_case(name)
|
157
|
+
|
158
|
+
def is_file_schema(self, schema_name: str) -> bool:
|
159
|
+
if super_type_map := self.get_schemas_super_type_map():
|
160
|
+
if file_super_type := super_type_map.get(FILE_SCHEMA_NAME):
|
161
|
+
return self.schema_name(schema_name) in file_super_type
|
162
|
+
return False
|
163
|
+
|
164
|
+
def get_schemas_super_type_map(self) -> dict:
|
165
|
+
"""
|
166
|
+
Returns the "super type map" for all of the known schemas (via /profiles).
|
167
|
+
This is a dictionary of all types which have (one or more) sub-types whose value is
|
168
|
+
an array of all of those sub-types (direct and all descendents), in breadth first order.
|
169
|
+
"""
|
170
|
+
def breadth_first(super_type_map: dict, super_type_name: str) -> dict:
|
171
|
+
result = []
|
172
|
+
queue = deque(super_type_map.get(super_type_name, []))
|
173
|
+
while queue:
|
174
|
+
result.append(sub_type_name := queue.popleft())
|
175
|
+
if sub_type_name in super_type_map:
|
176
|
+
queue.extend(super_type_map[sub_type_name])
|
177
|
+
return result
|
178
|
+
if not (schemas := self.get_schemas()):
|
179
|
+
return {}
|
180
|
+
super_type_map = {}
|
181
|
+
for type_name in schemas:
|
182
|
+
if super_type_name := schemas[type_name].get("rdfs:subClassOf"):
|
183
|
+
super_type_name = super_type_name.replace("/profiles/", "").replace(".json", "")
|
184
|
+
if super_type_name != "Item":
|
185
|
+
if not super_type_map.get(super_type_name):
|
186
|
+
super_type_map[super_type_name] = [type_name]
|
187
|
+
elif type_name not in super_type_map[super_type_name]:
|
188
|
+
super_type_map[super_type_name].append(type_name)
|
189
|
+
super_type_map_flattened = {}
|
190
|
+
for super_type_name in super_type_map:
|
191
|
+
super_type_map_flattened[super_type_name] = breadth_first(super_type_map, super_type_name)
|
192
|
+
return super_type_map_flattened
|
193
|
+
|
194
|
+
def _uri(self, uri: str) -> str:
|
195
|
+
if not isinstance(uri, str) or not uri:
|
196
|
+
return "/"
|
197
|
+
if uri.lower().startswith("http://") or uri.lower().startswith("https://"):
|
198
|
+
return uri
|
199
|
+
uri = re.sub(r"/+", "/", uri)
|
200
|
+
return (self._server + ("/" if uri.startswith("/") else "") + uri) if self._server else uri
|
201
|
+
|
202
|
+
def _kwargs(self, **kwargs) -> dict:
|
203
|
+
result_kwargs = {"headers":
|
204
|
+
kwargs.get("headers", {"Content-type": "application/json", "Accept": "application/json"})}
|
205
|
+
if self._key_pair:
|
206
|
+
result_kwargs["auth"] = self._key_pair
|
207
|
+
if isinstance(timeout := kwargs.get("timeout"), int):
|
208
|
+
result_kwargs["timeout"] = timeout
|
209
|
+
return result_kwargs
|
210
|
+
|
211
|
+
def _response(self, response) -> Optional[RequestResponse]:
|
212
|
+
if response and isinstance(getattr(response.__class__, "json"), property):
|
213
|
+
class RequestResponseWrapper: # For consistency change json property to method.
|
214
|
+
def __init__(self, response, **kwargs):
|
215
|
+
super().__init__(**kwargs)
|
216
|
+
self._response = response
|
217
|
+
def __getattr__(self, attr): # noqa
|
218
|
+
return getattr(self._response, attr)
|
219
|
+
def json(self): # noqa
|
220
|
+
return self._response.json
|
221
|
+
response = RequestResponseWrapper(response)
|
222
|
+
return response
|
223
|
+
|
224
|
+
@staticmethod
|
225
|
+
def create_for_testing(ini_file: Optional[str] = None) -> Portal:
|
226
|
+
if isinstance(ini_file, str):
|
227
|
+
return Portal(Portal._create_testapp(ini_file))
|
228
|
+
minimal_ini_for_unit_testing = "[app:app]\nuse = egg:encoded\nsqlalchemy.url = postgresql://dummy\n"
|
229
|
+
with temporary_file(content=minimal_ini_for_unit_testing, suffix=".ini") as ini_file:
|
230
|
+
return Portal(Portal._create_testapp(ini_file))
|
231
|
+
|
232
|
+
@staticmethod
|
233
|
+
def create_for_testing_local(ini_file: Optional[str] = None) -> Portal:
|
234
|
+
if isinstance(ini_file, str) and ini_file:
|
235
|
+
return Portal(Portal._create_testapp(ini_file))
|
236
|
+
minimal_ini_for_testing_local = "\n".join([
|
237
|
+
"[app:app]\nuse = egg:encoded\nfile_upload_bucket = dummy",
|
238
|
+
"sqlalchemy.url = postgresql://postgres@localhost:5441/postgres?host=/tmp/snovault/pgdata",
|
239
|
+
"multiauth.groupfinder = encoded.authorization.smaht_groupfinder",
|
240
|
+
"multiauth.policies = auth0 session remoteuser accesskey",
|
241
|
+
"multiauth.policy.session.namespace = mailto",
|
242
|
+
"multiauth.policy.session.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
243
|
+
"multiauth.policy.session.base = pyramid.authentication.SessionAuthenticationPolicy",
|
244
|
+
"multiauth.policy.remoteuser.namespace = remoteuser",
|
245
|
+
"multiauth.policy.remoteuser.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
246
|
+
"multiauth.policy.remoteuser.base = pyramid.authentication.RemoteUserAuthenticationPolicy",
|
247
|
+
"multiauth.policy.accesskey.namespace = accesskey",
|
248
|
+
"multiauth.policy.accesskey.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
249
|
+
"multiauth.policy.accesskey.base = encoded.authentication.BasicAuthAuthenticationPolicy",
|
250
|
+
"multiauth.policy.accesskey.check = encoded.authentication.basic_auth_check",
|
251
|
+
"multiauth.policy.auth0.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
252
|
+
"multiauth.policy.auth0.namespace = auth0",
|
253
|
+
"multiauth.policy.auth0.base = encoded.authentication.Auth0AuthenticationPolicy"
|
254
|
+
])
|
255
|
+
with temporary_file(content=minimal_ini_for_testing_local, suffix=".ini") as minimal_ini_file:
|
256
|
+
return Portal(Portal._create_testapp(minimal_ini_file))
|
257
|
+
|
258
|
+
@staticmethod
|
259
|
+
def _create_testapp(value: Union[str, Router, TestApp] = "development.ini") -> TestApp:
|
260
|
+
"""
|
261
|
+
Creates and returns a TestApp. Refactored out of above loadxl code to consolidate at a
|
262
|
+
single point; also for use by the generate_local_access_key and view_local_object scripts.
|
263
|
+
"""
|
264
|
+
if isinstance(value, TestApp):
|
265
|
+
return value
|
266
|
+
app = value if isinstance(value, Router) else get_app(value, "app")
|
267
|
+
return TestApp(app, {"HTTP_ACCEPT": "application/json", "REMOTE_USER": "TEST"})
|
dcicutils/structured_data.py
CHANGED
@@ -1,24 +1,19 @@
|
|
1
|
-
from collections import deque
|
2
1
|
import copy
|
3
2
|
from functools import lru_cache
|
4
3
|
import json
|
5
4
|
from jsonschema import Draft7Validator as SchemaValidator
|
6
5
|
import os
|
7
|
-
from pyramid.paster import get_app
|
8
6
|
from pyramid.router import Router
|
9
7
|
import re
|
10
|
-
import requests
|
11
|
-
from requests.models import Response as RequestResponse
|
12
8
|
import sys
|
13
9
|
from typing import Any, Callable, List, Optional, Tuple, Type, Union
|
14
|
-
from webtest.app import TestApp
|
15
|
-
from dcicutils.common import OrchestratedApp
|
16
|
-
from dcicutils.creds_utils import CGAPKeyManager, FourfrontKeyManager, SMaHTKeyManager
|
10
|
+
from webtest.app import TestApp
|
11
|
+
from dcicutils.common import OrchestratedApp
|
17
12
|
from dcicutils.data_readers import CsvReader, Excel, RowReader
|
18
|
-
from dcicutils.ff_utils import get_metadata, get_schema, patch_metadata, post_metadata
|
19
13
|
from dcicutils.misc_utils import (create_object, load_json_if, merge_objects, remove_empty_properties, right_trim,
|
20
|
-
split_string, to_boolean,
|
21
|
-
from dcicutils.
|
14
|
+
split_string, to_boolean, to_enum, to_float, to_integer, VirtualApp)
|
15
|
+
from dcicutils.portal_utils import Portal as PortalBase
|
16
|
+
from dcicutils.zip_utils import unpack_gz_file_to_temporary_file, unpack_files
|
22
17
|
|
23
18
|
|
24
19
|
# Classes/functions to parse a CSV or Excel Spreadsheet into structured data, using a specialized
|
@@ -36,12 +31,10 @@ ARRAY_VALUE_DELIMITER_ESCAPE_CHAR = "\\"
|
|
36
31
|
ARRAY_NAME_SUFFIX_CHAR = "#"
|
37
32
|
ARRAY_NAME_SUFFIX_REGEX = re.compile(rf"{ARRAY_NAME_SUFFIX_CHAR}\d+")
|
38
33
|
DOTTED_NAME_DELIMITER_CHAR = "."
|
39
|
-
FILE_SCHEMA_NAME = "File"
|
40
34
|
FILE_SCHEMA_NAME_PROPERTY = "filename"
|
41
35
|
|
42
36
|
# Forward type references for type hints.
|
43
37
|
Portal = Type["Portal"]
|
44
|
-
PortalBase = Type["PortalBase"]
|
45
38
|
Schema = Type["Schema"]
|
46
39
|
StructuredDataSet = Type["StructuredDataSet"]
|
47
40
|
|
@@ -526,7 +519,7 @@ class Schema:
|
|
526
519
|
@staticmethod
|
527
520
|
def type_name(value: str) -> str: # File or other name.
|
528
521
|
name = os.path.basename(value).replace(" ", "") if isinstance(value, str) else ""
|
529
|
-
return
|
522
|
+
return PortalBase.schema_name(name[0:dot] if (dot := name.rfind(".")) > 0 else name)
|
530
523
|
|
531
524
|
@staticmethod
|
532
525
|
def array_indices(name: str) -> Tuple[Optional[str], Optional[List[int]]]:
|
@@ -540,211 +533,15 @@ class Schema:
|
|
540
533
|
return (name, indices) if indices else (None, None)
|
541
534
|
|
542
535
|
|
543
|
-
class PortalBase:
|
544
|
-
|
545
|
-
def __init__(self,
|
546
|
-
arg: Optional[Union[VirtualApp, TestApp, Router, PortalBase, dict, tuple, str]] = None,
|
547
|
-
env: Optional[str] = None, app: OrchestratedApp = APP_SMAHT, server: Optional[str] = None,
|
548
|
-
key: Optional[Union[dict, tuple]] = None,
|
549
|
-
portal: Optional[Union[VirtualApp, TestApp, Router, PortalBase, str]] = None) -> PortalBase:
|
550
|
-
if ((isinstance(arg, (VirtualApp, TestApp, Router, PortalBase)) or
|
551
|
-
isinstance(arg, str) and arg.endswith(".ini")) and not portal):
|
552
|
-
portal = arg
|
553
|
-
elif isinstance(arg, str) and not env:
|
554
|
-
env = arg
|
555
|
-
elif (isinstance(arg, dict) or isinstance(arg, tuple)) and not key:
|
556
|
-
key = arg
|
557
|
-
self._vapp = None
|
558
|
-
self._key = None
|
559
|
-
self._key_pair = None
|
560
|
-
self._key_file = None
|
561
|
-
self._env = env
|
562
|
-
self._app = app
|
563
|
-
self._server = None
|
564
|
-
if isinstance(portal, PortalBase):
|
565
|
-
self._vapp = portal._vapp
|
566
|
-
self._key = portal._key
|
567
|
-
self._key_pair = portal._key_pair
|
568
|
-
self._server = portal._server
|
569
|
-
elif isinstance(portal, (VirtualApp, TestApp)):
|
570
|
-
self._vapp = portal
|
571
|
-
elif isinstance(portal, (Router, str)):
|
572
|
-
self._vapp = PortalBase._create_testapp(portal)
|
573
|
-
elif isinstance(key, dict):
|
574
|
-
self._key = key
|
575
|
-
self._key_pair = (key.get("key"), key.get("secret")) if key else None
|
576
|
-
self._server = key.get("server")
|
577
|
-
elif isinstance(key, tuple) and len(key) >= 2:
|
578
|
-
self._key = {"key": key[0], "secret": key[1]}
|
579
|
-
self._key_pair = key
|
580
|
-
elif isinstance(env, str):
|
581
|
-
key_managers = {APP_CGAP: CGAPKeyManager, APP_FOURFRONT: FourfrontKeyManager, APP_SMAHT: SMaHTKeyManager}
|
582
|
-
if not (key_manager := key_managers.get(app)) or not (key_manager := key_manager()):
|
583
|
-
raise Exception(f"Invalid app name: {app} (valid: {', '.join(ORCHESTRATED_APPS)}).")
|
584
|
-
if isinstance(env, str):
|
585
|
-
self._key = key_manager.get_keydict_for_env(env)
|
586
|
-
self._server = self._key.get("server") if self._key else None
|
587
|
-
elif isinstance(server, str):
|
588
|
-
self._key = key_manager.get_keydict_for_server(server)
|
589
|
-
self._server = server
|
590
|
-
self._key_pair = key_manager.keydict_to_keypair(self._key) if self._key else None
|
591
|
-
self._key_file = key_manager.keys_file
|
592
|
-
|
593
|
-
@property
|
594
|
-
def env(self):
|
595
|
-
return self._env
|
596
|
-
|
597
|
-
@property
|
598
|
-
def app(self):
|
599
|
-
return self._app
|
600
|
-
|
601
|
-
@property
|
602
|
-
def key(self):
|
603
|
-
return self._key
|
604
|
-
|
605
|
-
@property
|
606
|
-
def key_pair(self):
|
607
|
-
return self._key_pair
|
608
|
-
|
609
|
-
@property
|
610
|
-
def key_file(self):
|
611
|
-
return self._key_file
|
612
|
-
|
613
|
-
@property
|
614
|
-
def server(self):
|
615
|
-
return self._server
|
616
|
-
|
617
|
-
@property
|
618
|
-
def vapp(self):
|
619
|
-
return self._vapp
|
620
|
-
|
621
|
-
def get_metadata(self, object_id: str) -> Optional[dict]:
|
622
|
-
return get_metadata(obj_id=object_id, vapp=self._vapp, key=self._key)
|
623
|
-
|
624
|
-
def patch_metadata(self, object_id: str, data: str) -> Optional[dict]:
|
625
|
-
if self._key:
|
626
|
-
return patch_metadata(obj_id=object_id, patch_item=data, key=self._key)
|
627
|
-
return self.patch(f"/{object_id}", data)
|
628
|
-
|
629
|
-
def post_metadata(self, object_type: str, data: str) -> Optional[dict]:
|
630
|
-
if self._key:
|
631
|
-
return post_metadata(schema_name=object_type, post_item=data, key=self._key)
|
632
|
-
return self.post(f"/{object_type}", data)
|
633
|
-
|
634
|
-
def get(self, uri: str, follow: bool = True, **kwargs) -> Optional[Union[RequestResponse, TestResponse]]:
|
635
|
-
if isinstance(self._vapp, (VirtualApp, TestApp)):
|
636
|
-
response = self._vapp.get(self._uri(uri), **self._kwargs(**kwargs))
|
637
|
-
if response and response.status_code in [301, 302, 303, 307, 308] and follow:
|
638
|
-
response = response.follow()
|
639
|
-
return self._response(response)
|
640
|
-
return requests.get(self._uri(uri), allow_redirects=follow, **self._kwargs(**kwargs))
|
641
|
-
|
642
|
-
def patch(self, uri: str, data: Optional[dict] = None,
|
643
|
-
json: Optional[dict] = None, **kwargs) -> Optional[Union[RequestResponse, TestResponse]]:
|
644
|
-
if isinstance(self._vapp, (VirtualApp, TestApp)):
|
645
|
-
return self._vapp.patch_json(self._uri(uri), json or data, **self._kwargs(**kwargs))
|
646
|
-
return requests.patch(self._uri(uri), json=json or data, **self._kwargs(**kwargs))
|
647
|
-
|
648
|
-
def post(self, uri: str, data: Optional[dict] = None, json: Optional[dict] = None,
|
649
|
-
files: Optional[dict] = None, **kwargs) -> Optional[Union[RequestResponse, TestResponse]]:
|
650
|
-
if isinstance(self._vapp, (VirtualApp, TestApp)):
|
651
|
-
if files:
|
652
|
-
return self._vapp.post(self._uri(uri), json or data, upload_files=files, **self._kwargs(**kwargs))
|
653
|
-
else:
|
654
|
-
return self._vapp.post_json(self._uri(uri), json or data, upload_files=files, **self._kwargs(**kwargs))
|
655
|
-
return requests.post(self._uri(uri), json=json or data, files=files, **self._kwargs(**kwargs))
|
656
|
-
|
657
|
-
def get_schema(self, schema_name: str) -> Optional[dict]:
|
658
|
-
return get_schema(schema_name, portal_vapp=self._vapp, key=self._key)
|
659
|
-
|
660
|
-
def get_schemas(self) -> dict:
|
661
|
-
return self.get("/profiles/").json()
|
662
|
-
|
663
|
-
def _uri(self, uri: str) -> str:
|
664
|
-
if not isinstance(uri, str) or not uri:
|
665
|
-
return "/"
|
666
|
-
if uri.lower().startswith("http://") or uri.lower().startswith("https://"):
|
667
|
-
return uri
|
668
|
-
uri = re.sub(r"/+", "/", uri)
|
669
|
-
return (self._server + ("/" if uri.startswith("/") else "") + uri) if self._server else uri
|
670
|
-
|
671
|
-
def _kwargs(self, **kwargs) -> dict:
|
672
|
-
result_kwargs = {"headers":
|
673
|
-
kwargs.get("headers", {"Content-type": "application/json", "Accept": "application/json"})}
|
674
|
-
if self._key_pair:
|
675
|
-
result_kwargs["auth"] = self._key_pair
|
676
|
-
if isinstance(timeout := kwargs.get("timeout"), int):
|
677
|
-
result_kwargs["timeout"] = timeout
|
678
|
-
return result_kwargs
|
679
|
-
|
680
|
-
def _response(self, response) -> Optional[RequestResponse]:
|
681
|
-
if response and isinstance(getattr(response.__class__, "json"), property):
|
682
|
-
class RequestResponseWrapper: # For consistency change json property to method.
|
683
|
-
def __init__(self, response, **kwargs):
|
684
|
-
super().__init__(**kwargs)
|
685
|
-
self._response = response
|
686
|
-
def __getattr__(self, attr): # noqa
|
687
|
-
return getattr(self._response, attr)
|
688
|
-
def json(self): # noqa
|
689
|
-
return self._response.json
|
690
|
-
response = RequestResponseWrapper(response)
|
691
|
-
return response
|
692
|
-
|
693
|
-
@staticmethod
|
694
|
-
def create_for_testing(ini_file: Optional[str] = None) -> PortalBase:
|
695
|
-
if isinstance(ini_file, str):
|
696
|
-
return PortalBase(PortalBase._create_testapp(ini_file))
|
697
|
-
minimal_ini_for_unit_testing = "[app:app]\nuse = egg:encoded\nsqlalchemy.url = postgresql://dummy\n"
|
698
|
-
with temporary_file(content=minimal_ini_for_unit_testing, suffix=".ini") as ini_file:
|
699
|
-
return PortalBase(PortalBase._create_testapp(ini_file))
|
700
|
-
|
701
|
-
@staticmethod
|
702
|
-
def create_for_testing_local(ini_file: Optional[str] = None) -> PortalBase:
|
703
|
-
if isinstance(ini_file, str) and ini_file:
|
704
|
-
return PortalBase(PortalBase._create_testapp(ini_file))
|
705
|
-
minimal_ini_for_testing_local = "\n".join([
|
706
|
-
"[app:app]\nuse = egg:encoded\nfile_upload_bucket = dummy",
|
707
|
-
"sqlalchemy.url = postgresql://postgres@localhost:5441/postgres?host=/tmp/snovault/pgdata",
|
708
|
-
"multiauth.groupfinder = encoded.authorization.smaht_groupfinder",
|
709
|
-
"multiauth.policies = auth0 session remoteuser accesskey",
|
710
|
-
"multiauth.policy.session.namespace = mailto",
|
711
|
-
"multiauth.policy.session.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
712
|
-
"multiauth.policy.session.base = pyramid.authentication.SessionAuthenticationPolicy",
|
713
|
-
"multiauth.policy.remoteuser.namespace = remoteuser",
|
714
|
-
"multiauth.policy.remoteuser.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
715
|
-
"multiauth.policy.remoteuser.base = pyramid.authentication.RemoteUserAuthenticationPolicy",
|
716
|
-
"multiauth.policy.accesskey.namespace = accesskey",
|
717
|
-
"multiauth.policy.accesskey.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
718
|
-
"multiauth.policy.accesskey.base = encoded.authentication.BasicAuthAuthenticationPolicy",
|
719
|
-
"multiauth.policy.accesskey.check = encoded.authentication.basic_auth_check",
|
720
|
-
"multiauth.policy.auth0.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
721
|
-
"multiauth.policy.auth0.namespace = auth0",
|
722
|
-
"multiauth.policy.auth0.base = encoded.authentication.Auth0AuthenticationPolicy"
|
723
|
-
])
|
724
|
-
with temporary_file(content=minimal_ini_for_testing_local, suffix=".ini") as minimal_ini_file:
|
725
|
-
return PortalBase(PortalBase._create_testapp(minimal_ini_file))
|
726
|
-
|
727
|
-
@staticmethod
|
728
|
-
def _create_testapp(value: Union[str, Router, TestApp] = "development.ini") -> TestApp:
|
729
|
-
"""
|
730
|
-
Creates and returns a TestApp. Refactored out of above loadxl code to consolidate at a
|
731
|
-
single point; also for use by the generate_local_access_key and view_local_object scripts.
|
732
|
-
"""
|
733
|
-
if isinstance(value, TestApp):
|
734
|
-
return value
|
735
|
-
app = value if isinstance(value, Router) else get_app(value, "app")
|
736
|
-
return TestApp(app, {"HTTP_ACCEPT": "application/json", "REMOTE_USER": "TEST"})
|
737
|
-
|
738
|
-
|
739
536
|
class Portal(PortalBase):
|
740
537
|
|
741
538
|
def __init__(self,
|
742
539
|
arg: Optional[Union[VirtualApp, TestApp, Router, Portal, dict, tuple, str]] = None,
|
743
|
-
env: Optional[str] = None, app: OrchestratedApp =
|
540
|
+
env: Optional[str] = None, app: OrchestratedApp = None, server: Optional[str] = None,
|
744
541
|
key: Optional[Union[dict, tuple]] = None,
|
745
542
|
portal: Optional[Union[VirtualApp, TestApp, Router, Portal, str]] = None,
|
746
543
|
data: Optional[dict] = None, schemas: Optional[List[dict]] = None) -> Optional[Portal]:
|
747
|
-
super(
|
544
|
+
super().__init__(arg, env=env, app=app, server=server, key=key, portal=portal)
|
748
545
|
if isinstance(arg, Portal) and not portal:
|
749
546
|
portal = arg
|
750
547
|
if isinstance(portal, Portal):
|
@@ -757,7 +554,7 @@ class Portal(PortalBase):
|
|
757
554
|
@lru_cache(maxsize=256)
|
758
555
|
def get_metadata(self, object_name: str) -> Optional[dict]:
|
759
556
|
try:
|
760
|
-
return super(
|
557
|
+
return super().get_metadata(object_name)
|
761
558
|
except Exception:
|
762
559
|
return None
|
763
560
|
|
@@ -772,7 +569,7 @@ class Portal(PortalBase):
|
|
772
569
|
|
773
570
|
@lru_cache(maxsize=1)
|
774
571
|
def get_schemas(self) -> dict:
|
775
|
-
schemas = super(
|
572
|
+
schemas = super().get_schemas()
|
776
573
|
if self._schemas:
|
777
574
|
schemas = copy.deepcopy(schemas)
|
778
575
|
for user_specified_schema in self._schemas:
|
@@ -780,42 +577,9 @@ class Portal(PortalBase):
|
|
780
577
|
schemas[user_specified_schema["title"]] = user_specified_schema
|
781
578
|
return schemas
|
782
579
|
|
783
|
-
def is_file_schema(self, schema_name: str) -> bool:
|
784
|
-
if super_type_map := self.get_schemas_super_type_map():
|
785
|
-
if file_super_type := super_type_map.get(FILE_SCHEMA_NAME):
|
786
|
-
return Schema.type_name(schema_name) in file_super_type
|
787
|
-
return False
|
788
|
-
|
789
580
|
@lru_cache(maxsize=1)
|
790
581
|
def get_schemas_super_type_map(self) -> dict:
|
791
|
-
|
792
|
-
Returns the "super type map" for all of the known schemas (via /profiles).
|
793
|
-
This is a dictionary of all types which have (one or more) sub-types whose value is
|
794
|
-
an array of all of those sub-types (direct and all descendents), in breadth first order.
|
795
|
-
"""
|
796
|
-
def breadth_first(super_type_map: dict, super_type_name: str) -> dict:
|
797
|
-
result = []
|
798
|
-
queue = deque(super_type_map.get(super_type_name, []))
|
799
|
-
while queue:
|
800
|
-
result.append(sub_type_name := queue.popleft())
|
801
|
-
if sub_type_name in super_type_map:
|
802
|
-
queue.extend(super_type_map[sub_type_name])
|
803
|
-
return result
|
804
|
-
if not (schemas := self.get_schemas()):
|
805
|
-
return {}
|
806
|
-
super_type_map = {}
|
807
|
-
for type_name in schemas:
|
808
|
-
if super_type_name := schemas[type_name].get("rdfs:subClassOf"):
|
809
|
-
super_type_name = super_type_name.replace("/profiles/", "").replace(".json", "")
|
810
|
-
if super_type_name != "Item":
|
811
|
-
if not super_type_map.get(super_type_name):
|
812
|
-
super_type_map[super_type_name] = [type_name]
|
813
|
-
elif type_name not in super_type_map[super_type_name]:
|
814
|
-
super_type_map[super_type_name].append(type_name)
|
815
|
-
super_type_map_flattened = {}
|
816
|
-
for super_type_name in super_type_map:
|
817
|
-
super_type_map_flattened[super_type_name] = breadth_first(super_type_map, super_type_name)
|
818
|
-
return super_type_map_flattened
|
582
|
+
return super().get_schemas_super_type_map()
|
819
583
|
|
820
584
|
def ref_exists(self, type_name: str, value: str) -> List[str]:
|
821
585
|
resolved = []
|
@@ -43,6 +43,7 @@ dcicutils/log_utils.py,sha256=7pWMc6vyrorUZQf-V-M3YC6zrPgNhuV_fzm9xqTPph0,10883
|
|
43
43
|
dcicutils/misc_utils.py,sha256=jfyWDrHAlx2REun51i3igEApfEMAsmakRDo2VKUr0LQ,99818
|
44
44
|
dcicutils/obfuscation_utils.py,sha256=fo2jOmDRC6xWpYX49u80bVNisqRRoPskFNX3ymFAmjw,5963
|
45
45
|
dcicutils/opensearch_utils.py,sha256=V2exmFYW8Xl2_pGFixF4I2Cc549Opwe4PhFi5twC0M8,1017
|
46
|
+
dcicutils/portal_utils.py,sha256=s918K2f8ZuMD-2-H2GM6foiGe_YtzgG23xHtYe4gjlk,13021
|
46
47
|
dcicutils/project_utils.py,sha256=qPdCaFmWUVBJw4rw342iUytwdQC0P-XKpK4mhyIulMM,31250
|
47
48
|
dcicutils/qa_checkers.py,sha256=cdXjeL0jCDFDLT8VR8Px78aS10hwNISOO5G_Zv2TZ6M,20534
|
48
49
|
dcicutils/qa_utils.py,sha256=TT0SiJWiuxYvbsIyhK9VO4uV_suxhB6CpuC4qPacCzQ,160208
|
@@ -55,14 +56,14 @@ dcicutils/secrets_utils.py,sha256=8dppXAsiHhJzI6NmOcvJV5ldvKkQZzh3Fl-cb8Wm7MI,19
|
|
55
56
|
dcicutils/sheet_utils.py,sha256=VlmzteONW5VF_Q4vo0yA5vesz1ViUah1MZ_yA1rwZ0M,33629
|
56
57
|
dcicutils/snapshot_utils.py,sha256=ymP7PXH6-yEiXAt75w0ldQFciGNqWBClNxC5gfX2FnY,22961
|
57
58
|
dcicutils/ssl_certificate_utils.py,sha256=F0ifz_wnRRN9dfrfsz7aCp4UDLgHEY8LaK7PjnNvrAQ,9707
|
58
|
-
dcicutils/structured_data.py,sha256=
|
59
|
+
dcicutils/structured_data.py,sha256=LGfNwJXYC5SHs92HA27XijbFZcCXfOatYVI1oo0tKu4,32120
|
59
60
|
dcicutils/task_utils.py,sha256=MF8ujmTD6-O2AC2gRGPHyGdUrVKgtr8epT5XU8WtNjk,8082
|
60
61
|
dcicutils/trace_utils.py,sha256=g8kwV4ebEy5kXW6oOrEAUsurBcCROvwtZqz9fczsGRE,1769
|
61
62
|
dcicutils/validation_utils.py,sha256=cMZIU2cY98FYtzK52z5WUYck7urH6JcqOuz9jkXpqzg,14797
|
62
63
|
dcicutils/variant_utils.py,sha256=2H9azNx3xAj-MySg-uZ2SFqbWs4kZvf61JnK6b-h4Qw,4343
|
63
64
|
dcicutils/zip_utils.py,sha256=0OXR0aLNwyLIZOzIFTM_5DOun7dxIv6TIZbFiithkO0,3276
|
64
|
-
dcicutils-8.4.0.
|
65
|
-
dcicutils-8.4.0.
|
66
|
-
dcicutils-8.4.0.
|
67
|
-
dcicutils-8.4.0.
|
68
|
-
dcicutils-8.4.0.
|
65
|
+
dcicutils-8.4.0.1b12.dist-info/LICENSE.txt,sha256=t0_-jIjqxNnymZoNJe-OltRIuuF8qfhN0ATlHyrUJPk,1102
|
66
|
+
dcicutils-8.4.0.1b12.dist-info/METADATA,sha256=f_Btb9g9ursc6-9DzbsrcbYRUMx4Te9rc2aotcDsphM,3315
|
67
|
+
dcicutils-8.4.0.1b12.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
|
68
|
+
dcicutils-8.4.0.1b12.dist-info/entry_points.txt,sha256=8wbw5csMIgBXhkwfgsgJeuFcoUc0WsucUxmOyml2aoA,209
|
69
|
+
dcicutils-8.4.0.1b12.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|