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 CHANGED
@@ -1,19 +1,24 @@
1
1
  from collections import deque
2
- from pyramid.paster import get_app
3
- from pyramid.router import Router
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, APP_CGAP, APP_FOURFRONT, APP_SMAHT, ORCHESTRATED_APPS
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[VirtualApp, TestApp, Router, Portal, dict, tuple, str]] = None,
37
- env: Optional[str] = None, app: Optional[OrchestratedApp] = None, server: Optional[str] = None,
38
- key: Optional[Union[dict, tuple]] = None,
39
- vapp: Optional[Union[VirtualApp, TestApp, Router, Portal, str]] = None,
40
- portal: Optional[Union[VirtualApp, TestApp, Router, Portal, str]] = None) -> Portal:
41
- if vapp and not portal:
42
- portal = vapp
43
- if ((isinstance(arg, (VirtualApp, TestApp, Router, Portal)) or
44
- isinstance(arg, str) and arg.endswith(".ini")) and not portal):
45
- portal = arg
46
- elif isinstance(arg, str) and not env:
47
- env = arg
48
- elif (isinstance(arg, dict) or isinstance(arg, tuple)) and not key:
49
- key = arg
50
- if not app and env:
51
- if env.startswith(APP_SMAHT):
52
- app = APP_SMAHT
53
- elif env.startswith(APP_CGAP):
54
- app = APP_CGAP
55
- elif env.startswith(APP_FOURFRONT):
56
- app = APP_FOURFRONT
57
- if isinstance(portal, Portal):
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._key_file = portal._key_file
66
- return
67
- self._vapp = None
68
- self._env = env
69
- self._app = app
70
- self._server = server
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
- @property
105
- def env(self):
106
- return self._env
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
- @property
109
- def app(self):
110
- return self._app
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 server(self):
114
- return self._server
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 key_file(self):
130
- return self._key_file
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 vapp(self):
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 isinstance(self._vapp, (VirtualApp, TestApp)):
151
- response = self._vapp.get(self._uri(uri), **self._kwargs(**kwargs))
152
- if response and response.status_code in [301, 302, 303, 307, 308] and follow:
153
- response = response.follow()
154
- return self._response(response)
155
- return requests.get(self._uri(uri), allow_redirects=follow, **self._kwargs(**kwargs))
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 isinstance(self._vapp, (VirtualApp, TestApp)):
160
- return self._vapp.patch_json(self._uri(uri), json or data, **self._kwargs(**kwargs))
161
- return requests.patch(self._uri(uri), json=json or data, **self._kwargs(**kwargs))
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 isinstance(self._vapp, (VirtualApp, TestApp)):
166
- if files:
167
- return self._vapp.post(self._uri(uri), json or data, upload_files=files, **self._kwargs(**kwargs))
168
- else:
169
- return self._vapp.post_json(self._uri(uri), json or data, upload_files=files, **self._kwargs(**kwargs))
170
- return requests.post(self._uri(uri), json=json or data, files=files, **self._kwargs(**kwargs))
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 _uri(self, uri: str) -> str:
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 uri.lower().startswith("https://"):
294
+ if (luri := uri.lower()).startswith("http://") or luri.startswith("https://"):
228
295
  return uri
229
- uri = re.sub(r"/+", "/", uri)
230
- return (self._server + ("/" if not uri.startswith("/") else "") + uri) if self._server else uri
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 _response(self, response) -> Optional[RequestResponse]:
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(ini_file: Optional[str] = None) -> Portal:
256
- if isinstance(ini_file, str):
257
- return Portal(Portal._create_vapp(ini_file))
258
- minimal_ini_for_unit_testing = "[app:app]\nuse = egg:encoded\nsqlalchemy.url = postgresql://dummy\n"
259
- with temporary_file(content=minimal_ini_for_unit_testing, suffix=".ini") as ini_file:
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 create_for_testing_local(ini_file: Optional[str] = None) -> Portal:
264
- if isinstance(ini_file, str) and ini_file:
265
- return Portal(Portal._create_vapp(ini_file))
266
- minimal_ini_for_testing_local = "\n".join([
267
- "[app:app]\nuse = egg:encoded\nfile_upload_bucket = dummy",
268
- "sqlalchemy.url = postgresql://postgres@localhost:5441/postgres?host=/tmp/snovault/pgdata",
269
- "multiauth.groupfinder = encoded.authorization.smaht_groupfinder",
270
- "multiauth.policies = auth0 session remoteuser accesskey",
271
- "multiauth.policy.session.namespace = mailto",
272
- "multiauth.policy.session.use = encoded.authentication.NamespacedAuthenticationPolicy",
273
- "multiauth.policy.session.base = pyramid.authentication.SessionAuthenticationPolicy",
274
- "multiauth.policy.remoteuser.namespace = remoteuser",
275
- "multiauth.policy.remoteuser.use = encoded.authentication.NamespacedAuthenticationPolicy",
276
- "multiauth.policy.remoteuser.base = pyramid.authentication.RemoteUserAuthenticationPolicy",
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 _create_vapp(value: Union[str, Router, TestApp] = "development.ini", app_name: str = "app") -> TestApp:
290
- if isinstance(value, TestApp):
291
- return value
292
- app = value if isinstance(value, Router) else get_app(value, app_name)
293
- return TestApp(app, {"HTTP_ACCEPT": "application/json", "REMOTE_USER": "TEST"})
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()
@@ -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
- data: Optional[List[dict]] = None, order: Optional[List[str]] = None, prune: bool = True) -> None:
47
- self.data = {} if not data else data # If portal is None then no schemas nor refs.
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 of JSON file, where the (base) name of the file is the data type name.
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, app: OrchestratedApp = None, server: Optional[str] = None,
555
- key: Optional[Union[dict, tuple]] = None,
556
- portal: Optional[Union[VirtualApp, TestApp, Router, Portal, str]] = None,
557
- data: Optional[dict] = None, schemas: Optional[List[dict]] = None) -> Optional[Portal]:
558
- super().__init__(arg, env=env, app=app, server=server, key=key, portal=portal)
559
- if isinstance(arg, Portal) and not portal:
560
- portal = arg
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(ini_file: Optional[str] = None, schemas: Optional[List[dict]] = None) -> Portal:
624
- return Portal(PortalBase.create_for_testing(ini_file), schemas=schemas)
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):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dcicutils
3
- Version: 8.5.0.1b4
3
+ Version: 8.5.0.1b7
4
4
  Summary: Utility package for interacting with the 4DN Data Portal and other 4DN resources
5
5
  Home-page: https://github.com/4dn-dcic/utils
6
6
  License: MIT
@@ -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=Wh918ZKpUJFbt7w6Bn5G7v-0k6ImOUWMFmE9Oxt1ZN4,14560
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=tMKBhq6OuM2t6iDxIPBFcxV66MiwmOHTBeACxhvkbiQ,33307
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.1b4.dist-info/LICENSE.txt,sha256=t0_-jIjqxNnymZoNJe-OltRIuuF8qfhN0ATlHyrUJPk,1102
66
- dcicutils-8.5.0.1b4.dist-info/METADATA,sha256=Zpf8fTsgsuRfo8WDGkONHxtcf4SPWOFK_4iCNVAhB78,3314
67
- dcicutils-8.5.0.1b4.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
68
- dcicutils-8.5.0.1b4.dist-info/entry_points.txt,sha256=8wbw5csMIgBXhkwfgsgJeuFcoUc0WsucUxmOyml2aoA,209
69
- dcicutils-8.5.0.1b4.dist-info/RECORD,,
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,,