dcicutils 8.5.0.1b4__py3-none-any.whl → 8.5.0.1b7__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 +249 -141
- dcicutils/structured_data.py +13 -20
- {dcicutils-8.5.0.1b4.dist-info → dcicutils-8.5.0.1b7.dist-info}/METADATA +1 -1
- {dcicutils-8.5.0.1b4.dist-info → dcicutils-8.5.0.1b7.dist-info}/RECORD +7 -7
- {dcicutils-8.5.0.1b4.dist-info → dcicutils-8.5.0.1b7.dist-info}/LICENSE.txt +0 -0
- {dcicutils-8.5.0.1b4.dist-info → dcicutils-8.5.0.1b7.dist-info}/WHEEL +0 -0
- {dcicutils-8.5.0.1b4.dist-info → dcicutils-8.5.0.1b7.dist-info}/entry_points.txt +0 -0
dcicutils/portal_utils.py
CHANGED
@@ -1,19 +1,24 @@
|
|
1
1
|
from collections import deque
|
2
|
-
|
3
|
-
|
2
|
+
import io
|
3
|
+
import json
|
4
|
+
from pyramid.config import Configurator as PyramidConfigurator
|
5
|
+
from pyramid.paster import get_app as pyramid_get_app
|
6
|
+
from pyramid.response import Response as PyramidResponse
|
7
|
+
from pyramid.router import Router as PyramidRouter
|
8
|
+
import os
|
4
9
|
import re
|
5
10
|
import requests
|
6
11
|
from requests.models import Response as RequestResponse
|
7
|
-
from typing import Optional, Type, Union
|
12
|
+
from typing import Callable, Dict, List, Optional, Type, Union
|
13
|
+
from uuid import uuid4 as uuid
|
14
|
+
# from waitress import serve
|
8
15
|
from webtest.app import TestApp, TestResponse
|
9
|
-
from dcicutils.common import OrchestratedApp,
|
10
|
-
from dcicutils.creds_utils import CGAPKeyManager, FourfrontKeyManager, SMaHTKeyManager
|
16
|
+
from dcicutils.common import OrchestratedApp, ORCHESTRATED_APPS
|
11
17
|
from dcicutils.ff_utils import get_metadata, get_schema, patch_metadata, post_metadata
|
12
18
|
from dcicutils.misc_utils import to_camel_case, VirtualApp
|
13
19
|
from dcicutils.zip_utils import temporary_file
|
14
20
|
|
15
21
|
Portal = Type["Portal"] # Forward type reference for type hints.
|
16
|
-
FILE_SCHEMA_NAME = "File"
|
17
22
|
|
18
23
|
|
19
24
|
class Portal:
|
@@ -32,105 +37,166 @@ class Portal:
|
|
32
37
|
6. From a given "vapp" value (which is assumed to be a TestApp or VirtualApp).
|
33
38
|
7. From another Portal object; or from a a pyramid Router object.
|
34
39
|
"""
|
40
|
+
FILE_SCHEMA_NAME = "File"
|
41
|
+
KEYS_FILE_DIRECTORY = os.path.expanduser(f"~")
|
42
|
+
|
35
43
|
def __init__(self,
|
36
|
-
arg: Optional[Union[
|
37
|
-
env: Optional[str] = None,
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
self._vapp = portal._vapp
|
59
|
-
self._env = portal._env
|
60
|
-
self._app = portal._app
|
61
|
-
self._server = portal._server
|
44
|
+
arg: Optional[Union[Portal, TestApp, VirtualApp, PyramidRouter, dict, tuple, str]] = None,
|
45
|
+
env: Optional[str] = None, server: Optional[str] = None,
|
46
|
+
app: Optional[OrchestratedApp] = None) -> None:
|
47
|
+
|
48
|
+
def init(unspecified: Optional[list] = []) -> None:
|
49
|
+
self._ini_file = None
|
50
|
+
self._key = None
|
51
|
+
self._key_pair = None
|
52
|
+
self._key_id = None
|
53
|
+
self._secret = None
|
54
|
+
self._keys_file = None
|
55
|
+
self._env = None
|
56
|
+
self._server = None
|
57
|
+
self._app = None
|
58
|
+
self._vapp = None
|
59
|
+
for arg in unspecified:
|
60
|
+
if arg is not None:
|
61
|
+
raise Exception("Portal init error; extraneous args.")
|
62
|
+
|
63
|
+
def init_from_portal(portal: Portal, unspecified: Optional[list] = None) -> None:
|
64
|
+
init(unspecified)
|
65
|
+
self._ini_file = portal._ini_file
|
62
66
|
self._key = portal._key
|
63
67
|
self._key_pair = portal._key_pair
|
64
68
|
self._key_id = portal._key_id
|
65
|
-
self.
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
self._key = None
|
72
|
-
self._key_pair = None
|
73
|
-
self._key_id = None
|
74
|
-
self._key_file = None
|
75
|
-
if isinstance(portal, (VirtualApp, TestApp)):
|
76
|
-
self._vapp = portal
|
77
|
-
elif isinstance(portal, (Router, str)):
|
78
|
-
self._vapp = Portal._create_vapp(portal)
|
79
|
-
elif isinstance(key, dict):
|
80
|
-
self._key = key
|
81
|
-
self._key_pair = (key.get("key"), key.get("secret")) if key else None
|
82
|
-
if key_server := key.get("server"):
|
83
|
-
self._server = key_server
|
84
|
-
elif isinstance(key, tuple) and len(key) >= 2:
|
85
|
-
self._key = {"key": key[0], "secret": key[1]}
|
86
|
-
self._key_pair = key
|
87
|
-
elif isinstance(env, str):
|
88
|
-
key_managers = {APP_CGAP: CGAPKeyManager, APP_FOURFRONT: FourfrontKeyManager, APP_SMAHT: SMaHTKeyManager}
|
89
|
-
if not (key_manager := key_managers.get(self._app)) or not (key_manager := key_manager()):
|
90
|
-
raise Exception(f"Invalid app name: {self._app} (valid: {', '.join(ORCHESTRATED_APPS)}).")
|
91
|
-
if isinstance(env, str):
|
92
|
-
self._key = key_manager.get_keydict_for_env(env)
|
93
|
-
if key_server := self._key.get("server"):
|
94
|
-
self._server = key_server
|
95
|
-
elif isinstance(self._server, str):
|
96
|
-
self._key = key_manager.get_keydict_for_server(self._server)
|
97
|
-
self._key_pair = key_manager.keydict_to_keypair(self._key) if self._key else None
|
98
|
-
self._key_file = key_manager.keys_file
|
99
|
-
if self._key and (key_id := self._key.get("key")):
|
100
|
-
self._key_id = key_id
|
101
|
-
elif self._key_pair and (key_id := self._key_pair[1]):
|
102
|
-
self._key_id = key_id
|
69
|
+
self._secret = portal._secret
|
70
|
+
self._keys_file = portal._keys_file
|
71
|
+
self._env = portal._env
|
72
|
+
self._server = portal._server
|
73
|
+
self._app = portal._app
|
74
|
+
self._vapp = portal._vapp
|
103
75
|
|
104
|
-
|
105
|
-
|
106
|
-
|
76
|
+
def init_from_vapp(vapp: Union[TestApp, VirtualApp, PyramidRouter], unspecified: Optional[list] = []) -> None:
|
77
|
+
init(unspecified)
|
78
|
+
self._vapp = Portal._create_vapp(vapp)
|
107
79
|
|
108
|
-
|
109
|
-
|
110
|
-
|
80
|
+
def init_from_ini_file(ini_file: str, unspecified: Optional[list] = []) -> None:
|
81
|
+
init(unspecified)
|
82
|
+
self._ini_file = ini_file
|
83
|
+
self._vapp = Portal._create_vapp(ini_file)
|
84
|
+
|
85
|
+
def init_from_key(key: dict, server: Optional[str], unspecified: Optional[list] = []) -> None:
|
86
|
+
init(unspecified)
|
87
|
+
if (isinstance(key_id := key.get("key"), str) and key_id and
|
88
|
+
isinstance(secret := key.get("secret"), str) and secret): # noqa
|
89
|
+
self._key = {"key": key_id, "secret": secret}
|
90
|
+
self._key_id = key_id
|
91
|
+
self._secret = secret
|
92
|
+
self._key_pair = (key_id, secret)
|
93
|
+
if ((isinstance(server, str) and server) or (isinstance(server := key.get("server"), str) and server)):
|
94
|
+
if server := normalize_server(server):
|
95
|
+
self._key["server"] = self._server = server
|
96
|
+
if not self._key:
|
97
|
+
raise Exception("Portal init error; from key.")
|
98
|
+
|
99
|
+
def init_from_key_pair(key_pair: tuple, server: Optional[str], unspecified: Optional[list] = []) -> None:
|
100
|
+
if len(key_pair) == 2:
|
101
|
+
init_from_key({"key": key_pair[0], "secret": key_pair[1]}, server, unspecified)
|
102
|
+
else:
|
103
|
+
raise Exception("Portal init error; from key-pair.")
|
104
|
+
|
105
|
+
def init_from_keys_file(keys_file: str, env: Optional[str], server: Optional[str],
|
106
|
+
unspecified: Optional[list] = []) -> None:
|
107
|
+
try:
|
108
|
+
with io.open(keys_file) as f:
|
109
|
+
keys = json.load(f)
|
110
|
+
except Exception:
|
111
|
+
raise Exception(f"Portal init error; cannot open keys-file: {keys_file}")
|
112
|
+
if isinstance(env, str) and env and isinstance(key := keys.get(env), dict):
|
113
|
+
init_from_key(key, server)
|
114
|
+
self._keys_file = keys_file
|
115
|
+
self._env = env
|
116
|
+
elif isinstance(server, str) and server and (key := [k for k in keys if keys[k].get("server") == server]):
|
117
|
+
init_from_key(key, server)
|
118
|
+
self._keys_file = keys_file
|
119
|
+
elif len(keys) == 1 and (env := next(iter(keys))) and isinstance(key := keys[env], dict) and key:
|
120
|
+
init_from_key(key, server)
|
121
|
+
self._keys_file = keys_file
|
122
|
+
self._env = env
|
123
|
+
else:
|
124
|
+
raise Exception(f"Portal init error; {env or server or None} not found in keys-file: {keys_file}")
|
125
|
+
|
126
|
+
def init_from_env_server_app(env: str, server: str, app: Optional[str],
|
127
|
+
unspecified: Optional[list] = None) -> None:
|
128
|
+
return init_from_keys_file(self._default_keys_file(app, env), env, server, unspecified)
|
129
|
+
|
130
|
+
def normalize_server(server: str) -> Optional[str]:
|
131
|
+
prefix = ""
|
132
|
+
if (lserver := server.lower()).startswith("http://"):
|
133
|
+
prefix = "http://"
|
134
|
+
elif lserver.startswith("https://"):
|
135
|
+
prefix = "https://"
|
136
|
+
if prefix:
|
137
|
+
if (server := re.sub(r"/+", "/", server[len(prefix):])).startswith("/"):
|
138
|
+
server = server[1:]
|
139
|
+
if len(server) > 1 and server.endswith("/"):
|
140
|
+
server = server[:-1]
|
141
|
+
return prefix + server if server else None
|
142
|
+
|
143
|
+
if isinstance(arg, Portal):
|
144
|
+
init_from_portal(arg, unspecified=[env, server, app])
|
145
|
+
elif isinstance(arg, (TestApp, VirtualApp, PyramidRouter)):
|
146
|
+
init_from_vapp(arg, unspecified=[env, server, app])
|
147
|
+
elif isinstance(arg, str) and arg.endswith(".ini"):
|
148
|
+
init_from_ini_file(arg, unspecified=[env, server, app])
|
149
|
+
elif isinstance(arg, dict):
|
150
|
+
init_from_key(arg, server, unspecified=[env, app])
|
151
|
+
elif isinstance(arg, tuple):
|
152
|
+
init_from_key_pair(arg, server, unspecified=[env, app])
|
153
|
+
elif isinstance(arg, str) and arg.endswith(".json"):
|
154
|
+
init_from_keys_file(arg, env, server, unspecified=[app])
|
155
|
+
elif isinstance(arg, str) and arg:
|
156
|
+
init_from_env_server_app(arg, server, app, unspecified=[env])
|
157
|
+
elif isinstance(env, str) and env:
|
158
|
+
init_from_env_server_app(env, server, app, unspecified=[arg])
|
159
|
+
else:
|
160
|
+
raise Exception("Portal init error; invalid args.")
|
111
161
|
|
112
162
|
@property
|
113
|
-
def
|
114
|
-
return self.
|
163
|
+
def ini_file(self) -> Optional[str]:
|
164
|
+
return self._ini_file
|
115
165
|
|
116
166
|
@property
|
117
|
-
def key(self):
|
167
|
+
def key(self) -> Optional[dict]:
|
118
168
|
return self._key
|
119
169
|
|
120
170
|
@property
|
121
|
-
def key_pair(self):
|
171
|
+
def key_pair(self) -> Optional[tuple]:
|
122
172
|
return self._key_pair
|
123
173
|
|
124
174
|
@property
|
125
|
-
def key_id(self):
|
175
|
+
def key_id(self) -> Optional[str]:
|
126
176
|
return self._key_id
|
127
177
|
|
128
178
|
@property
|
129
|
-
def
|
130
|
-
return self.
|
179
|
+
def secret(self) -> Optional[str]:
|
180
|
+
return self._secret
|
181
|
+
|
182
|
+
@property
|
183
|
+
def keys_file(self) -> Optional[str]:
|
184
|
+
return self._keys_file
|
131
185
|
|
132
186
|
@property
|
133
|
-
def
|
187
|
+
def env(self) -> Optional[str]:
|
188
|
+
return self._env
|
189
|
+
|
190
|
+
@property
|
191
|
+
def server(self) -> Optional[str]:
|
192
|
+
return self._server
|
193
|
+
|
194
|
+
@property
|
195
|
+
def app(self) -> Optional[str]:
|
196
|
+
return self._app
|
197
|
+
|
198
|
+
@property
|
199
|
+
def vapp(self) -> Optional[TestApp]:
|
134
200
|
return self._vapp
|
135
201
|
|
136
202
|
def get_metadata(self, object_id: str) -> Optional[dict]:
|
@@ -139,35 +205,36 @@ class Portal:
|
|
139
205
|
def patch_metadata(self, object_id: str, data: str) -> Optional[dict]:
|
140
206
|
if self._key:
|
141
207
|
return patch_metadata(obj_id=object_id, patch_item=data, key=self._key)
|
142
|
-
return self.patch(f"/{object_id}", data)
|
208
|
+
return self.patch(f"/{object_id}", data).json()
|
143
209
|
|
144
210
|
def post_metadata(self, object_type: str, data: str) -> Optional[dict]:
|
145
211
|
if self._key:
|
146
212
|
return post_metadata(schema_name=object_type, post_item=data, key=self._key)
|
147
|
-
return self.post(f"/{object_type}", data)
|
213
|
+
return self.post(f"/{object_type}", data).json()
|
148
214
|
|
149
215
|
def get(self, uri: str, follow: bool = True, **kwargs) -> Optional[Union[RequestResponse, TestResponse]]:
|
150
|
-
if
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
return
|
216
|
+
if not self._vapp:
|
217
|
+
return requests.get(self.url(uri), allow_redirects=follow, **self._kwargs(**kwargs))
|
218
|
+
response = self._vapp.get(self.url(uri), **self._kwargs(**kwargs))
|
219
|
+
if response and response.status_code in [301, 302, 303, 307, 308] and follow:
|
220
|
+
response = response.follow()
|
221
|
+
return self._response(response)
|
156
222
|
|
157
223
|
def patch(self, uri: str, data: Optional[dict] = None,
|
158
224
|
json: Optional[dict] = None, **kwargs) -> Optional[Union[RequestResponse, TestResponse]]:
|
159
|
-
if
|
160
|
-
return
|
161
|
-
return
|
225
|
+
if not self._vapp:
|
226
|
+
return requests.patch(self.url(uri), data=data, json=json, **self._kwargs(**kwargs))
|
227
|
+
return self._response(self._vapp.patch_json(self.url(uri), json or data, **self._kwargs(**kwargs)))
|
162
228
|
|
163
229
|
def post(self, uri: str, data: Optional[dict] = None, json: Optional[dict] = None,
|
164
230
|
files: Optional[dict] = None, **kwargs) -> Optional[Union[RequestResponse, TestResponse]]:
|
165
|
-
if
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
231
|
+
if not self._vapp:
|
232
|
+
return requests.post(self.url(uri), data=data, json=json, files=files, **self._kwargs(**kwargs))
|
233
|
+
if files:
|
234
|
+
response = self._vapp.post(self.url(uri), json or data, upload_files=files, **self._kwargs(**kwargs))
|
235
|
+
else:
|
236
|
+
response = self._vapp.post_json(self.url(uri), json or data, upload_files=files, **self._kwargs(**kwargs))
|
237
|
+
return self._response(response)
|
171
238
|
|
172
239
|
def get_schema(self, schema_name: str) -> Optional[dict]:
|
173
240
|
return get_schema(self.schema_name(schema_name), portal_vapp=self._vapp, key=self._key)
|
@@ -181,7 +248,7 @@ class Portal:
|
|
181
248
|
|
182
249
|
def is_file_schema(self, schema_name: str) -> bool:
|
183
250
|
if super_type_map := self.get_schemas_super_type_map():
|
184
|
-
if file_super_type := super_type_map.get(FILE_SCHEMA_NAME):
|
251
|
+
if file_super_type := super_type_map.get(Portal.FILE_SCHEMA_NAME):
|
185
252
|
return self.schema_name(schema_name) in file_super_type
|
186
253
|
return False
|
187
254
|
|
@@ -221,13 +288,14 @@ class Portal:
|
|
221
288
|
except Exception:
|
222
289
|
return False
|
223
290
|
|
224
|
-
def
|
291
|
+
def url(self, uri: str) -> str:
|
225
292
|
if not isinstance(uri, str) or not uri:
|
226
293
|
return "/"
|
227
|
-
if uri.lower().startswith("http://") or
|
294
|
+
if (luri := uri.lower()).startswith("http://") or luri.startswith("https://"):
|
228
295
|
return uri
|
229
|
-
uri
|
230
|
-
|
296
|
+
if not (uri := re.sub(r"/+", "/", uri)).startswith("/"):
|
297
|
+
uri = "/"
|
298
|
+
return self._server + uri if self._server else uri
|
231
299
|
|
232
300
|
def _kwargs(self, **kwargs) -> dict:
|
233
301
|
result_kwargs = {"headers":
|
@@ -238,7 +306,17 @@ class Portal:
|
|
238
306
|
result_kwargs["timeout"] = timeout
|
239
307
|
return result_kwargs
|
240
308
|
|
241
|
-
def
|
309
|
+
def _default_keys_file(self, app: Optional[str], env: Optional[str] = None) -> Optional[str]:
|
310
|
+
def is_valid_app(app: Optional[str]) -> bool: # noqa
|
311
|
+
return app and app.lower() in [name.lower() for name in ORCHESTRATED_APPS]
|
312
|
+
def infer_app_from_env(env: str) -> Optional[str]: # noqa
|
313
|
+
if isinstance(env, str) and (lenv := env.lower()):
|
314
|
+
if app := [app for app in ORCHESTRATED_APPS if lenv.startswith(app.lower())]:
|
315
|
+
return app[0]
|
316
|
+
if is_valid_app(app) or (app := infer_app_from_env(env)):
|
317
|
+
return os.path.join(Portal.KEYS_FILE_DIRECTORY, f".{app.lower()}-keys.json")
|
318
|
+
|
319
|
+
def _response(self, response: TestResponse) -> Optional[RequestResponse]:
|
242
320
|
if response and isinstance(getattr(response.__class__, "json"), property):
|
243
321
|
class RequestResponseWrapper: # For consistency change json property to method.
|
244
322
|
def __init__(self, response, **kwargs):
|
@@ -252,42 +330,72 @@ class Portal:
|
|
252
330
|
return response
|
253
331
|
|
254
332
|
@staticmethod
|
255
|
-
def create_for_testing(
|
256
|
-
if isinstance(
|
257
|
-
return Portal(Portal.
|
258
|
-
|
259
|
-
|
333
|
+
def create_for_testing(arg: Optional[Union[str, bool, List[dict], dict, Callable]] = None) -> Portal:
|
334
|
+
if isinstance(arg, list) or isinstance(arg, dict) or isinstance(arg, Callable):
|
335
|
+
return Portal(Portal._create_router_for_testing(arg))
|
336
|
+
if isinstance(arg, str) and arg.endswith(".ini"):
|
337
|
+
return Portal(Portal._create_vapp(arg))
|
338
|
+
if arg == "local" or arg is True:
|
339
|
+
minimal_ini_for_testing = "\n".join([
|
340
|
+
"[app:app]\nuse = egg:encoded\nfile_upload_bucket = dummy",
|
341
|
+
"sqlalchemy.url = postgresql://postgres@localhost:5441/postgres?host=/tmp/snovault/pgdata",
|
342
|
+
"multiauth.groupfinder = encoded.authorization.smaht_groupfinder",
|
343
|
+
"multiauth.policies = auth0 session remoteuser accesskey",
|
344
|
+
"multiauth.policy.session.namespace = mailto",
|
345
|
+
"multiauth.policy.session.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
346
|
+
"multiauth.policy.session.base = pyramid.authentication.SessionAuthenticationPolicy",
|
347
|
+
"multiauth.policy.remoteuser.namespace = remoteuser",
|
348
|
+
"multiauth.policy.remoteuser.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
349
|
+
"multiauth.policy.remoteuser.base = pyramid.authentication.RemoteUserAuthenticationPolicy",
|
350
|
+
"multiauth.policy.accesskey.namespace = accesskey",
|
351
|
+
"multiauth.policy.accesskey.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
352
|
+
"multiauth.policy.accesskey.base = encoded.authentication.BasicAuthAuthenticationPolicy",
|
353
|
+
"multiauth.policy.accesskey.check = encoded.authentication.basic_auth_check",
|
354
|
+
"multiauth.policy.auth0.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
355
|
+
"multiauth.policy.auth0.namespace = auth0",
|
356
|
+
"multiauth.policy.auth0.base = encoded.authentication.Auth0AuthenticationPolicy"
|
357
|
+
])
|
358
|
+
else:
|
359
|
+
minimal_ini_for_testing = "[app:app]\nuse = egg:encoded\nsqlalchemy.url = postgresql://dummy\n"
|
360
|
+
with temporary_file(content=minimal_ini_for_testing, suffix=".ini") as ini_file:
|
260
361
|
return Portal(Portal._create_vapp(ini_file))
|
261
362
|
|
262
363
|
@staticmethod
|
263
|
-
def
|
264
|
-
if isinstance(
|
265
|
-
return
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
"
|
276
|
-
|
277
|
-
"multiauth.policy.accesskey.namespace = accesskey",
|
278
|
-
"multiauth.policy.accesskey.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
279
|
-
"multiauth.policy.accesskey.base = encoded.authentication.BasicAuthAuthenticationPolicy",
|
280
|
-
"multiauth.policy.accesskey.check = encoded.authentication.basic_auth_check",
|
281
|
-
"multiauth.policy.auth0.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
282
|
-
"multiauth.policy.auth0.namespace = auth0",
|
283
|
-
"multiauth.policy.auth0.base = encoded.authentication.Auth0AuthenticationPolicy"
|
284
|
-
])
|
285
|
-
with temporary_file(content=minimal_ini_for_testing_local, suffix=".ini") as minimal_ini_file:
|
286
|
-
return Portal(Portal._create_vapp(minimal_ini_file))
|
364
|
+
def _create_vapp(arg: Union[TestApp, VirtualApp, PyramidRouter, str] = None) -> TestApp:
|
365
|
+
if isinstance(arg, TestApp):
|
366
|
+
return arg
|
367
|
+
elif isinstance(arg, VirtualApp):
|
368
|
+
if not isinstance(arg.wrapped_app, TestApp):
|
369
|
+
raise Exception("Portal._create_vapp VirtualApp argument error.")
|
370
|
+
return arg.wrapped_app
|
371
|
+
if isinstance(arg, PyramidRouter):
|
372
|
+
router = arg
|
373
|
+
elif isinstance(arg, str) or not arg:
|
374
|
+
router = pyramid_get_app(arg or "development.ini", "app")
|
375
|
+
else:
|
376
|
+
raise Exception("Portal._create_vapp argument error.")
|
377
|
+
return TestApp(router, {"HTTP_ACCEPT": "application/json", "REMOTE_USER": "TEST"})
|
287
378
|
|
288
379
|
@staticmethod
|
289
|
-
def
|
290
|
-
if isinstance(
|
291
|
-
|
292
|
-
|
293
|
-
|
380
|
+
def _create_router_for_testing(endpoints: Optional[List[Dict[str, Union[str, Callable]]]] = None):
|
381
|
+
if isinstance(endpoints, dict):
|
382
|
+
endpoints = [endpoints]
|
383
|
+
elif isinstance(endpoints, Callable):
|
384
|
+
endpoints = [{"path": "/", "method": "GET", "function": endpoints}]
|
385
|
+
if not isinstance(endpoints, list) or not endpoints:
|
386
|
+
endpoints = [{"path": "/", "method": "GET", "function": lambda request: {"status": "OK"}}]
|
387
|
+
with PyramidConfigurator() as config:
|
388
|
+
nendpoints = 0
|
389
|
+
for endpoint in endpoints:
|
390
|
+
if (endpoint_path := endpoint.get("path")) and (endpoint_function := endpoint.get("function")):
|
391
|
+
endpoint_method = endpoint.get("method", "GET")
|
392
|
+
def endpoint_wrapper(request): # noqa
|
393
|
+
response = endpoint_function(request)
|
394
|
+
return PyramidResponse(json.dumps(response), content_type="application/json; charset=utf-8")
|
395
|
+
endpoint_id = str(uuid())
|
396
|
+
config.add_route(endpoint_id, endpoint_path)
|
397
|
+
config.add_view(endpoint_wrapper, route_name=endpoint_id, request_method=endpoint_method)
|
398
|
+
nendpoints += 1
|
399
|
+
if nendpoints == 0:
|
400
|
+
return Portal._create_router_for_testing([])
|
401
|
+
return config.make_wsgi_app()
|
dcicutils/structured_data.py
CHANGED
@@ -43,8 +43,8 @@ class StructuredDataSet:
|
|
43
43
|
|
44
44
|
def __init__(self, file: Optional[str] = None, portal: Optional[Union[VirtualApp, TestApp, Portal]] = None,
|
45
45
|
schemas: Optional[List[dict]] = None, autoadd: Optional[dict] = None,
|
46
|
-
|
47
|
-
self.data = {}
|
46
|
+
order: Optional[List[str]] = None, prune: bool = True) -> None:
|
47
|
+
self.data = {}
|
48
48
|
self._portal = Portal(portal, data=self.data, schemas=schemas) if portal else None
|
49
49
|
self._order = order
|
50
50
|
self._prune = prune
|
@@ -113,7 +113,7 @@ class StructuredDataSet:
|
|
113
113
|
def _load_file(self, file: str) -> None:
|
114
114
|
# Returns a dictionary where each property is the name (i.e. the type) of the data,
|
115
115
|
# and the value is array of dictionaries for the data itself. Handle these kinds of files:
|
116
|
-
# 1. Single CSV
|
116
|
+
# 1. Single CSV, TSV, or JSON file, where the (base) name of the file is the data type name.
|
117
117
|
# 2. Single Excel file containing one or more sheets, where each sheet
|
118
118
|
# represents (i.e. is named for, and contains data for) a different type.
|
119
119
|
# 3. Zip file (.zip or .tar.gz or .tgz or .tar), containing data files to load, where the
|
@@ -483,7 +483,6 @@ class Schema:
|
|
483
483
|
if unique:
|
484
484
|
typeinfo[key]["unique"] = True
|
485
485
|
result.update(typeinfo)
|
486
|
-
# result.update(self._create_typeinfo(array_property_items, parent_key=key))
|
487
486
|
continue
|
488
487
|
result[key] = {"type": property_value_type, "map": self._map_function({**property_value, "column": key})}
|
489
488
|
if ARRAY_NAME_SUFFIX_CHAR in key:
|
@@ -551,16 +550,13 @@ class Portal(PortalBase):
|
|
551
550
|
|
552
551
|
def __init__(self,
|
553
552
|
arg: Optional[Union[VirtualApp, TestApp, Router, Portal, dict, tuple, str]] = None,
|
554
|
-
env: Optional[str] = None,
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
if isinstance(portal, Portal):
|
562
|
-
self._schemas = schemas if schemas is not None else portal._schemas # Explicitly specified/known schemas.
|
563
|
-
self._data = data if data is not None else portal._data # Data set being loaded; e.g. by StructuredDataSet.
|
553
|
+
env: Optional[str] = None, server: Optional[str] = None,
|
554
|
+
app: Optional[OrchestratedApp] = None,
|
555
|
+
data: Optional[dict] = None, schemas: Optional[List[dict]] = None) -> None:
|
556
|
+
super().__init__(arg, env=env, server=server, app=app)
|
557
|
+
if isinstance(arg, Portal):
|
558
|
+
self._schemas = schemas if schemas is not None else arg._schemas # Explicitly specified/known schemas.
|
559
|
+
self._data = data if data is not None else arg._data # Data set being loaded; e.g. by StructuredDataSet.
|
564
560
|
else:
|
565
561
|
self._schemas = schemas
|
566
562
|
self._data = data
|
@@ -620,12 +616,9 @@ class Portal(PortalBase):
|
|
620
616
|
return self.get_metadata(f"/{type_name}/{value}") is not None
|
621
617
|
|
622
618
|
@staticmethod
|
623
|
-
def create_for_testing(
|
624
|
-
|
625
|
-
|
626
|
-
@staticmethod
|
627
|
-
def create_for_testing_local(ini_file: Optional[str] = None, schemas: Optional[List[dict]] = None) -> Portal:
|
628
|
-
return Portal(PortalBase.create_for_testing_local(ini_file), schemas=schemas)
|
619
|
+
def create_for_testing(arg: Optional[Union[str, bool, List[dict], dict, Callable]] = None,
|
620
|
+
schemas: Optional[List[dict]] = None) -> Portal:
|
621
|
+
return Portal(PortalBase.create_for_testing(arg), schemas=schemas)
|
629
622
|
|
630
623
|
|
631
624
|
def _split_dotted_string(value: str):
|
@@ -43,7 +43,7 @@ dcicutils/log_utils.py,sha256=7pWMc6vyrorUZQf-V-M3YC6zrPgNhuV_fzm9xqTPph0,10883
|
|
43
43
|
dcicutils/misc_utils.py,sha256=nRjLEORY35YmJwTjO0fnauBPznaI_bkVasIW8PccDYM,100179
|
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=
|
46
|
+
dcicutils/portal_utils.py,sha256=IZfwtbkirNiSSvpUzkQxHBedzaSgMiiVbkLLKzHEvVI,20043
|
47
47
|
dcicutils/project_utils.py,sha256=qPdCaFmWUVBJw4rw342iUytwdQC0P-XKpK4mhyIulMM,31250
|
48
48
|
dcicutils/qa_checkers.py,sha256=cdXjeL0jCDFDLT8VR8Px78aS10hwNISOO5G_Zv2TZ6M,20534
|
49
49
|
dcicutils/qa_utils.py,sha256=TT0SiJWiuxYvbsIyhK9VO4uV_suxhB6CpuC4qPacCzQ,160208
|
@@ -56,14 +56,14 @@ dcicutils/secrets_utils.py,sha256=8dppXAsiHhJzI6NmOcvJV5ldvKkQZzh3Fl-cb8Wm7MI,19
|
|
56
56
|
dcicutils/sheet_utils.py,sha256=VlmzteONW5VF_Q4vo0yA5vesz1ViUah1MZ_yA1rwZ0M,33629
|
57
57
|
dcicutils/snapshot_utils.py,sha256=ymP7PXH6-yEiXAt75w0ldQFciGNqWBClNxC5gfX2FnY,22961
|
58
58
|
dcicutils/ssl_certificate_utils.py,sha256=F0ifz_wnRRN9dfrfsz7aCp4UDLgHEY8LaK7PjnNvrAQ,9707
|
59
|
-
dcicutils/structured_data.py,sha256=
|
59
|
+
dcicutils/structured_data.py,sha256=ziugsIE_FlYRFQIl2GJqwduRa4yIBPbHBQRYPSeUzSk,32713
|
60
60
|
dcicutils/task_utils.py,sha256=MF8ujmTD6-O2AC2gRGPHyGdUrVKgtr8epT5XU8WtNjk,8082
|
61
61
|
dcicutils/trace_utils.py,sha256=g8kwV4ebEy5kXW6oOrEAUsurBcCROvwtZqz9fczsGRE,1769
|
62
62
|
dcicutils/validation_utils.py,sha256=cMZIU2cY98FYtzK52z5WUYck7urH6JcqOuz9jkXpqzg,14797
|
63
63
|
dcicutils/variant_utils.py,sha256=2H9azNx3xAj-MySg-uZ2SFqbWs4kZvf61JnK6b-h4Qw,4343
|
64
64
|
dcicutils/zip_utils.py,sha256=0OXR0aLNwyLIZOzIFTM_5DOun7dxIv6TIZbFiithkO0,3276
|
65
|
-
dcicutils-8.5.0.
|
66
|
-
dcicutils-8.5.0.
|
67
|
-
dcicutils-8.5.0.
|
68
|
-
dcicutils-8.5.0.
|
69
|
-
dcicutils-8.5.0.
|
65
|
+
dcicutils-8.5.0.1b7.dist-info/LICENSE.txt,sha256=t0_-jIjqxNnymZoNJe-OltRIuuF8qfhN0ATlHyrUJPk,1102
|
66
|
+
dcicutils-8.5.0.1b7.dist-info/METADATA,sha256=AZPMmFxRxXIpn3-mhRMhuS3mHK9YFWjpsoA3T2TXo-g,3314
|
67
|
+
dcicutils-8.5.0.1b7.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
|
68
|
+
dcicutils-8.5.0.1b7.dist-info/entry_points.txt,sha256=8wbw5csMIgBXhkwfgsgJeuFcoUc0WsucUxmOyml2aoA,209
|
69
|
+
dcicutils-8.5.0.1b7.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|