dcicutils 8.5.0.1b4__tar.gz → 8.5.0.1b7__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/PKG-INFO +1 -1
  2. dcicutils-8.5.0.1b7/dcicutils/portal_utils.py +401 -0
  3. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/structured_data.py +13 -20
  4. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/pyproject.toml +1 -1
  5. dcicutils-8.5.0.1b4/dcicutils/portal_utils.py +0 -293
  6. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/LICENSE.txt +0 -0
  7. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/README.rst +0 -0
  8. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/__init__.py +0 -0
  9. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/base.py +0 -0
  10. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/beanstalk_utils.py +0 -0
  11. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/bundle_utils.py +0 -0
  12. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/cloudformation_utils.py +0 -0
  13. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/codebuild_utils.py +0 -0
  14. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/command_utils.py +0 -0
  15. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/common.py +0 -0
  16. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/contribution_scripts.py +0 -0
  17. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/contribution_utils.py +0 -0
  18. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/creds_utils.py +0 -0
  19. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/data_readers.py +0 -0
  20. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/data_utils.py +0 -0
  21. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/deployment_utils.py +0 -0
  22. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/diff_utils.py +0 -0
  23. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/docker_utils.py +0 -0
  24. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/ecr_scripts.py +0 -0
  25. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/ecr_utils.py +0 -0
  26. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/ecs_utils.py +0 -0
  27. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/env_base.py +0 -0
  28. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/env_manager.py +0 -0
  29. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/env_scripts.py +0 -0
  30. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/env_utils.py +0 -0
  31. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/env_utils_legacy.py +0 -0
  32. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/es_utils.py +0 -0
  33. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/exceptions.py +0 -0
  34. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/ff_mocks.py +0 -0
  35. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/ff_utils.py +0 -0
  36. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/function_cache_decorator.py +0 -0
  37. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/glacier_utils.py +0 -0
  38. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/jh_utils.py +0 -0
  39. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/kibana/dashboards.json +0 -0
  40. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/kibana/readme.md +0 -0
  41. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/lang_utils.py +0 -0
  42. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/license_policies/c4-infrastructure.jsonc +0 -0
  43. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/license_policies/c4-python-infrastructure.jsonc +0 -0
  44. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/license_policies/park-lab-common-server.jsonc +0 -0
  45. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/license_policies/park-lab-common.jsonc +0 -0
  46. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/license_policies/park-lab-gpl-pipeline.jsonc +0 -0
  47. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/license_policies/park-lab-pipeline.jsonc +0 -0
  48. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/license_utils.py +0 -0
  49. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/log_utils.py +0 -0
  50. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/misc_utils.py +0 -0
  51. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/obfuscation_utils.py +0 -0
  52. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/opensearch_utils.py +0 -0
  53. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/project_utils.py +0 -0
  54. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/qa_checkers.py +0 -0
  55. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/qa_utils.py +0 -0
  56. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/redis_tools.py +0 -0
  57. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/redis_utils.py +0 -0
  58. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/s3_utils.py +0 -0
  59. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/scripts/publish_to_pypi.py +0 -0
  60. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/scripts/run_license_checker.py +0 -0
  61. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/secrets_utils.py +0 -0
  62. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/sheet_utils.py +0 -0
  63. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/snapshot_utils.py +0 -0
  64. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/ssl_certificate_utils.py +0 -0
  65. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/task_utils.py +0 -0
  66. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/trace_utils.py +0 -0
  67. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/validation_utils.py +0 -0
  68. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/variant_utils.py +0 -0
  69. {dcicutils-8.5.0.1b4 → dcicutils-8.5.0.1b7}/dcicutils/zip_utils.py +0 -0
@@ -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
@@ -0,0 +1,401 @@
1
+ from collections import deque
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
9
+ import re
10
+ import requests
11
+ from requests.models import Response as RequestResponse
12
+ from typing import Callable, Dict, List, Optional, Type, Union
13
+ from uuid import uuid4 as uuid
14
+ # from waitress import serve
15
+ from webtest.app import TestApp, TestResponse
16
+ from dcicutils.common import OrchestratedApp, ORCHESTRATED_APPS
17
+ from dcicutils.ff_utils import get_metadata, get_schema, patch_metadata, post_metadata
18
+ from dcicutils.misc_utils import to_camel_case, VirtualApp
19
+ from dcicutils.zip_utils import temporary_file
20
+
21
+ Portal = Type["Portal"] # Forward type reference for type hints.
22
+
23
+
24
+ class Portal:
25
+ """
26
+ This is meant to be an uber wrapper for Portal access. It can be created in a variety of ways:
27
+ 1. From a (Portal) .ini file (e.g. development.ini)
28
+ 2. From a key dictionary, containing "key" and "secret" property values.
29
+ 3. From a key tuple, containing (in order) a key and secret values.
30
+ 4. From a keys file assumed to reside in ~/.{app}-keys.json where the given "app" value is either "smaht", "cgap",
31
+ or "fourfront"; where is assumed to contain a dictionary with a key for the given "env" value, e.g. smaht-local;
32
+ and with a dictionary value containing "key" and "secret" property values, and an optional "server" property;
33
+ if an "app" value is not specified but the given "env" value begins with one of the app values then that value
34
+ will be used, i.e. e.g. if "env" is "smaht-local" and app is unspecified than it is assumed to be "smaht".
35
+ 5. From a keys file as described above (#4) but rather than be identified by the given "env" value it
36
+ is looked up via the given "server" name and the "server" key dictionary value in the key file.
37
+ 6. From a given "vapp" value (which is assumed to be a TestApp or VirtualApp).
38
+ 7. From another Portal object; or from a a pyramid Router object.
39
+ """
40
+ FILE_SCHEMA_NAME = "File"
41
+ KEYS_FILE_DIRECTORY = os.path.expanduser(f"~")
42
+
43
+ def __init__(self,
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
66
+ self._key = portal._key
67
+ self._key_pair = portal._key_pair
68
+ self._key_id = portal._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
75
+
76
+ def init_from_vapp(vapp: Union[TestApp, VirtualApp, PyramidRouter], unspecified: Optional[list] = []) -> None:
77
+ init(unspecified)
78
+ self._vapp = Portal._create_vapp(vapp)
79
+
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.")
161
+
162
+ @property
163
+ def ini_file(self) -> Optional[str]:
164
+ return self._ini_file
165
+
166
+ @property
167
+ def key(self) -> Optional[dict]:
168
+ return self._key
169
+
170
+ @property
171
+ def key_pair(self) -> Optional[tuple]:
172
+ return self._key_pair
173
+
174
+ @property
175
+ def key_id(self) -> Optional[str]:
176
+ return self._key_id
177
+
178
+ @property
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
185
+
186
+ @property
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]:
200
+ return self._vapp
201
+
202
+ def get_metadata(self, object_id: str) -> Optional[dict]:
203
+ return get_metadata(obj_id=object_id, vapp=self._vapp, key=self._key)
204
+
205
+ def patch_metadata(self, object_id: str, data: str) -> Optional[dict]:
206
+ if self._key:
207
+ return patch_metadata(obj_id=object_id, patch_item=data, key=self._key)
208
+ return self.patch(f"/{object_id}", data).json()
209
+
210
+ def post_metadata(self, object_type: str, data: str) -> Optional[dict]:
211
+ if self._key:
212
+ return post_metadata(schema_name=object_type, post_item=data, key=self._key)
213
+ return self.post(f"/{object_type}", data).json()
214
+
215
+ def get(self, uri: str, follow: bool = True, **kwargs) -> Optional[Union[RequestResponse, TestResponse]]:
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)
222
+
223
+ def patch(self, uri: str, data: Optional[dict] = None,
224
+ json: Optional[dict] = None, **kwargs) -> Optional[Union[RequestResponse, TestResponse]]:
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)))
228
+
229
+ def post(self, uri: str, data: Optional[dict] = None, json: Optional[dict] = None,
230
+ files: Optional[dict] = None, **kwargs) -> Optional[Union[RequestResponse, TestResponse]]:
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)
238
+
239
+ def get_schema(self, schema_name: str) -> Optional[dict]:
240
+ return get_schema(self.schema_name(schema_name), portal_vapp=self._vapp, key=self._key)
241
+
242
+ def get_schemas(self) -> dict:
243
+ return self.get("/profiles/").json()
244
+
245
+ @staticmethod
246
+ def schema_name(name: str) -> str:
247
+ return to_camel_case(name)
248
+
249
+ def is_file_schema(self, schema_name: str) -> bool:
250
+ if super_type_map := self.get_schemas_super_type_map():
251
+ if file_super_type := super_type_map.get(Portal.FILE_SCHEMA_NAME):
252
+ return self.schema_name(schema_name) in file_super_type
253
+ return False
254
+
255
+ def get_schemas_super_type_map(self) -> dict:
256
+ """
257
+ Returns the "super type map" for all of the known schemas (via /profiles).
258
+ This is a dictionary of all types which have (one or more) sub-types whose value is
259
+ an array of all of those sub-types (direct and all descendents), in breadth first order.
260
+ """
261
+ def breadth_first(super_type_map: dict, super_type_name: str) -> dict:
262
+ result = []
263
+ queue = deque(super_type_map.get(super_type_name, []))
264
+ while queue:
265
+ result.append(sub_type_name := queue.popleft())
266
+ if sub_type_name in super_type_map:
267
+ queue.extend(super_type_map[sub_type_name])
268
+ return result
269
+ if not (schemas := self.get_schemas()):
270
+ return {}
271
+ super_type_map = {}
272
+ for type_name in schemas:
273
+ if super_type_name := schemas[type_name].get("rdfs:subClassOf"):
274
+ super_type_name = super_type_name.replace("/profiles/", "").replace(".json", "")
275
+ if super_type_name != "Item":
276
+ if not super_type_map.get(super_type_name):
277
+ super_type_map[super_type_name] = [type_name]
278
+ elif type_name not in super_type_map[super_type_name]:
279
+ super_type_map[super_type_name].append(type_name)
280
+ super_type_map_flattened = {}
281
+ for super_type_name in super_type_map:
282
+ super_type_map_flattened[super_type_name] = breadth_first(super_type_map, super_type_name)
283
+ return super_type_map_flattened
284
+
285
+ def ping(self) -> bool:
286
+ try:
287
+ return self.get("/health").status_code == 200
288
+ except Exception:
289
+ return False
290
+
291
+ def url(self, uri: str) -> str:
292
+ if not isinstance(uri, str) or not uri:
293
+ return "/"
294
+ if (luri := uri.lower()).startswith("http://") or luri.startswith("https://"):
295
+ return uri
296
+ if not (uri := re.sub(r"/+", "/", uri)).startswith("/"):
297
+ uri = "/"
298
+ return self._server + uri if self._server else uri
299
+
300
+ def _kwargs(self, **kwargs) -> dict:
301
+ result_kwargs = {"headers":
302
+ kwargs.get("headers", {"Content-type": "application/json", "Accept": "application/json"})}
303
+ if self._key_pair:
304
+ result_kwargs["auth"] = self._key_pair
305
+ if isinstance(timeout := kwargs.get("timeout"), int):
306
+ result_kwargs["timeout"] = timeout
307
+ return result_kwargs
308
+
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]:
320
+ if response and isinstance(getattr(response.__class__, "json"), property):
321
+ class RequestResponseWrapper: # For consistency change json property to method.
322
+ def __init__(self, response, **kwargs):
323
+ super().__init__(**kwargs)
324
+ self._response = response
325
+ def __getattr__(self, attr): # noqa
326
+ return getattr(self._response, attr)
327
+ def json(self): # noqa
328
+ return self._response.json
329
+ response = RequestResponseWrapper(response)
330
+ return response
331
+
332
+ @staticmethod
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:
361
+ return Portal(Portal._create_vapp(ini_file))
362
+
363
+ @staticmethod
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"})
378
+
379
+ @staticmethod
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
  [tool.poetry]
2
2
  name = "dcicutils"
3
- version = "8.5.0.1b4" # TODO: To become 8.6.0
3
+ version = "8.5.0.1b7" # TODO: To become 8.6.0
4
4
  description = "Utility package for interacting with the 4DN Data Portal and other 4DN resources"
5
5
  authors = ["4DN-DCIC Team <support@4dnucleome.org>"]
6
6
  license = "MIT"
@@ -1,293 +0,0 @@
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
- This is meant to be an uber wrapper for Portal access. It can be created in a variety of ways:
22
- 1. From a (Portal) .ini file (e.g. development.ini)
23
- 2. From a key dictionary, containing "key" and "secret" property values.
24
- 3. From a key tuple, containing (in order) a key and secret values.
25
- 4. From a keys file assumed to reside in ~/.{app}-keys.json where the given "app" value is either "smaht", "cgap",
26
- or "fourfront"; where is assumed to contain a dictionary with a key for the given "env" value, e.g. smaht-local;
27
- and with a dictionary value containing "key" and "secret" property values, and an optional "server" property;
28
- if an "app" value is not specified but the given "env" value begins with one of the app values then that value
29
- will be used, i.e. e.g. if "env" is "smaht-local" and app is unspecified than it is assumed to be "smaht".
30
- 5. From a keys file as described above (#4) but rather than be identified by the given "env" value it
31
- is looked up via the given "server" name and the "server" key dictionary value in the key file.
32
- 6. From a given "vapp" value (which is assumed to be a TestApp or VirtualApp).
33
- 7. From another Portal object; or from a a pyramid Router object.
34
- """
35
- 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
62
- self._key = portal._key
63
- self._key_pair = portal._key_pair
64
- 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
103
-
104
- @property
105
- def env(self):
106
- return self._env
107
-
108
- @property
109
- def app(self):
110
- return self._app
111
-
112
- @property
113
- def server(self):
114
- return self._server
115
-
116
- @property
117
- def key(self):
118
- return self._key
119
-
120
- @property
121
- def key_pair(self):
122
- return self._key_pair
123
-
124
- @property
125
- def key_id(self):
126
- return self._key_id
127
-
128
- @property
129
- def key_file(self):
130
- return self._key_file
131
-
132
- @property
133
- def vapp(self):
134
- return self._vapp
135
-
136
- def get_metadata(self, object_id: str) -> Optional[dict]:
137
- return get_metadata(obj_id=object_id, vapp=self._vapp, key=self._key)
138
-
139
- def patch_metadata(self, object_id: str, data: str) -> Optional[dict]:
140
- if self._key:
141
- return patch_metadata(obj_id=object_id, patch_item=data, key=self._key)
142
- return self.patch(f"/{object_id}", data)
143
-
144
- def post_metadata(self, object_type: str, data: str) -> Optional[dict]:
145
- if self._key:
146
- return post_metadata(schema_name=object_type, post_item=data, key=self._key)
147
- return self.post(f"/{object_type}", data)
148
-
149
- 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))
156
-
157
- def patch(self, uri: str, data: Optional[dict] = None,
158
- 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))
162
-
163
- def post(self, uri: str, data: Optional[dict] = None, json: Optional[dict] = None,
164
- 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))
171
-
172
- def get_schema(self, schema_name: str) -> Optional[dict]:
173
- return get_schema(self.schema_name(schema_name), portal_vapp=self._vapp, key=self._key)
174
-
175
- def get_schemas(self) -> dict:
176
- return self.get("/profiles/").json()
177
-
178
- @staticmethod
179
- def schema_name(name: str) -> str:
180
- return to_camel_case(name)
181
-
182
- def is_file_schema(self, schema_name: str) -> bool:
183
- if super_type_map := self.get_schemas_super_type_map():
184
- if file_super_type := super_type_map.get(FILE_SCHEMA_NAME):
185
- return self.schema_name(schema_name) in file_super_type
186
- return False
187
-
188
- def get_schemas_super_type_map(self) -> dict:
189
- """
190
- Returns the "super type map" for all of the known schemas (via /profiles).
191
- This is a dictionary of all types which have (one or more) sub-types whose value is
192
- an array of all of those sub-types (direct and all descendents), in breadth first order.
193
- """
194
- def breadth_first(super_type_map: dict, super_type_name: str) -> dict:
195
- result = []
196
- queue = deque(super_type_map.get(super_type_name, []))
197
- while queue:
198
- result.append(sub_type_name := queue.popleft())
199
- if sub_type_name in super_type_map:
200
- queue.extend(super_type_map[sub_type_name])
201
- return result
202
- if not (schemas := self.get_schemas()):
203
- return {}
204
- super_type_map = {}
205
- for type_name in schemas:
206
- if super_type_name := schemas[type_name].get("rdfs:subClassOf"):
207
- super_type_name = super_type_name.replace("/profiles/", "").replace(".json", "")
208
- if super_type_name != "Item":
209
- if not super_type_map.get(super_type_name):
210
- super_type_map[super_type_name] = [type_name]
211
- elif type_name not in super_type_map[super_type_name]:
212
- super_type_map[super_type_name].append(type_name)
213
- super_type_map_flattened = {}
214
- for super_type_name in super_type_map:
215
- super_type_map_flattened[super_type_name] = breadth_first(super_type_map, super_type_name)
216
- return super_type_map_flattened
217
-
218
- def ping(self) -> bool:
219
- try:
220
- return self.get("/health").status_code == 200
221
- except Exception:
222
- return False
223
-
224
- def _uri(self, uri: str) -> str:
225
- if not isinstance(uri, str) or not uri:
226
- return "/"
227
- if uri.lower().startswith("http://") or uri.lower().startswith("https://"):
228
- return uri
229
- uri = re.sub(r"/+", "/", uri)
230
- return (self._server + ("/" if not uri.startswith("/") else "") + uri) if self._server else uri
231
-
232
- def _kwargs(self, **kwargs) -> dict:
233
- result_kwargs = {"headers":
234
- kwargs.get("headers", {"Content-type": "application/json", "Accept": "application/json"})}
235
- if self._key_pair:
236
- result_kwargs["auth"] = self._key_pair
237
- if isinstance(timeout := kwargs.get("timeout"), int):
238
- result_kwargs["timeout"] = timeout
239
- return result_kwargs
240
-
241
- def _response(self, response) -> Optional[RequestResponse]:
242
- if response and isinstance(getattr(response.__class__, "json"), property):
243
- class RequestResponseWrapper: # For consistency change json property to method.
244
- def __init__(self, response, **kwargs):
245
- super().__init__(**kwargs)
246
- self._response = response
247
- def __getattr__(self, attr): # noqa
248
- return getattr(self._response, attr)
249
- def json(self): # noqa
250
- return self._response.json
251
- response = RequestResponseWrapper(response)
252
- return response
253
-
254
- @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:
260
- return Portal(Portal._create_vapp(ini_file))
261
-
262
- @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))
287
-
288
- @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"})
File without changes
File without changes