dcicutils 8.5.0.1b6__tar.gz → 8.6.0.0b0__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/PKG-INFO +1 -1
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/misc_utils.py +7 -16
- dcicutils-8.6.0.0b0/dcicutils/portal_utils.py +278 -0
- dcicutils-8.6.0.0b0/dcicutils/schema_utils.py +142 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/structured_data.py +34 -47
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/pyproject.toml +1 -1
- dcicutils-8.5.0.1b6/dcicutils/portal_utils.py +0 -375
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/LICENSE.txt +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/README.rst +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/__init__.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/base.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/beanstalk_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/bundle_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/cloudformation_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/codebuild_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/command_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/common.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/contribution_scripts.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/contribution_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/creds_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/data_readers.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/data_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/deployment_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/diff_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/docker_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/ecr_scripts.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/ecr_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/ecs_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/env_base.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/env_manager.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/env_scripts.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/env_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/env_utils_legacy.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/es_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/exceptions.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/ff_mocks.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/ff_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/function_cache_decorator.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/glacier_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/jh_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/kibana/dashboards.json +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/kibana/readme.md +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/lang_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/license_policies/c4-infrastructure.jsonc +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/license_policies/c4-python-infrastructure.jsonc +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/license_policies/park-lab-common-server.jsonc +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/license_policies/park-lab-common.jsonc +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/license_policies/park-lab-gpl-pipeline.jsonc +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/license_policies/park-lab-pipeline.jsonc +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/license_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/log_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/obfuscation_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/opensearch_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/project_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/qa_checkers.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/qa_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/redis_tools.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/redis_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/s3_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/scripts/publish_to_pypi.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/scripts/run_license_checker.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/secrets_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/sheet_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/snapshot_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/ssl_certificate_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/task_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/trace_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/validation_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/variant_utils.py +0 -0
- {dcicutils-8.5.0.1b6 → dcicutils-8.6.0.0b0}/dcicutils/zip_utils.py +0 -0
@@ -982,11 +982,7 @@ def to_integer(value: str, fallback: Optional[Any] = None) -> Optional[Any]:
|
|
982
982
|
try:
|
983
983
|
return int(value)
|
984
984
|
except Exception:
|
985
|
-
|
986
|
-
return int(float(value))
|
987
|
-
except Exception:
|
988
|
-
pass
|
989
|
-
return fallback
|
985
|
+
return fallback
|
990
986
|
|
991
987
|
|
992
988
|
def to_float(value: str, fallback: Optional[Any] = None) -> Optional[Any]:
|
@@ -1469,33 +1465,28 @@ def string_list(s):
|
|
1469
1465
|
return [p for p in [part.strip() for part in s.split(",")] if p]
|
1470
1466
|
|
1471
1467
|
|
1472
|
-
def split_string(value: str, delimiter: str, escape: Optional[str] = None
|
1468
|
+
def split_string(value: str, delimiter: str, escape: Optional[str] = None) -> List[str]:
|
1473
1469
|
"""
|
1474
1470
|
Splits the given string into an array of string based on the given delimiter, and an optional escape character.
|
1475
1471
|
"""
|
1476
1472
|
if not isinstance(value, str) or not (value := value.strip()):
|
1477
1473
|
return []
|
1478
|
-
result = []
|
1479
1474
|
if not isinstance(escape, str) or not escape:
|
1480
|
-
for item in value.split(delimiter)
|
1481
|
-
|
1482
|
-
result.append(item)
|
1483
|
-
return result
|
1475
|
+
return [item.strip() for item in value.split(delimiter)]
|
1476
|
+
result = []
|
1484
1477
|
item = r""
|
1485
1478
|
escaped = False
|
1486
1479
|
for c in value:
|
1487
1480
|
if c == delimiter and not escaped:
|
1488
|
-
|
1489
|
-
result.append(item)
|
1481
|
+
result.append(item.strip())
|
1490
1482
|
item = r""
|
1491
1483
|
elif c == escape and not escaped:
|
1492
1484
|
escaped = True
|
1493
1485
|
else:
|
1494
1486
|
item += c
|
1495
1487
|
escaped = False
|
1496
|
-
|
1497
|
-
|
1498
|
-
return result
|
1488
|
+
result.append(item.strip())
|
1489
|
+
return [item for item in result if item]
|
1499
1490
|
|
1500
1491
|
|
1501
1492
|
def right_trim(list_or_tuple: Union[List[Any], Tuple[Any]],
|
@@ -0,0 +1,278 @@
|
|
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"; and where this file is assumed to contain a dictionary with a key equal to the given "env"
|
27
|
+
value (e.g. smaht-localhost) and with a dictionary value containing "key" and "secret" property values; if
|
28
|
+
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-localhost" 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 by 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.
|
34
|
+
8. From a a pyramid Router object.
|
35
|
+
"""
|
36
|
+
def __init__(self,
|
37
|
+
arg: Optional[Union[VirtualApp, TestApp, Router, Portal, dict, tuple, str]] = None,
|
38
|
+
env: Optional[str] = None, app: Optional[OrchestratedApp] = None, server: Optional[str] = None,
|
39
|
+
key: Optional[Union[dict, tuple]] = None,
|
40
|
+
vapp: Optional[Union[VirtualApp, TestApp, Router, Portal, str]] = None,
|
41
|
+
portal: Optional[Union[VirtualApp, TestApp, Router, Portal, str]] = None) -> Portal:
|
42
|
+
if vapp and not portal:
|
43
|
+
portal = vapp
|
44
|
+
if ((isinstance(arg, (VirtualApp, TestApp, Router, Portal)) or
|
45
|
+
isinstance(arg, str) and arg.endswith(".ini")) and not portal):
|
46
|
+
portal = arg
|
47
|
+
elif isinstance(arg, str) and not env:
|
48
|
+
env = arg
|
49
|
+
elif (isinstance(arg, dict) or isinstance(arg, tuple)) and not key:
|
50
|
+
key = arg
|
51
|
+
if not app and env:
|
52
|
+
if env.startswith(APP_SMAHT):
|
53
|
+
app = APP_SMAHT
|
54
|
+
elif env.startswith(APP_CGAP):
|
55
|
+
app = APP_CGAP
|
56
|
+
elif env.startswith(APP_FOURFRONT):
|
57
|
+
app = APP_FOURFRONT
|
58
|
+
if isinstance(portal, Portal):
|
59
|
+
self._vapp = portal._vapp
|
60
|
+
self._env = portal._env
|
61
|
+
self._app = portal._app
|
62
|
+
self._server = portal._server
|
63
|
+
self._key = portal._key
|
64
|
+
self._key_pair = portal._key_pair
|
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_file = None
|
74
|
+
if isinstance(portal, (VirtualApp, TestApp)):
|
75
|
+
self._vapp = portal
|
76
|
+
elif isinstance(portal, (Router, str)):
|
77
|
+
self._vapp = Portal._create_vapp(portal)
|
78
|
+
elif isinstance(key, dict):
|
79
|
+
self._key = key
|
80
|
+
self._key_pair = (key.get("key"), key.get("secret")) if key else None
|
81
|
+
if key_server := key.get("server"):
|
82
|
+
self._server = key_server
|
83
|
+
elif isinstance(key, tuple) and len(key) >= 2:
|
84
|
+
self._key = {"key": key[0], "secret": key[1]}
|
85
|
+
self._key_pair = key
|
86
|
+
elif isinstance(env, str):
|
87
|
+
key_managers = {APP_CGAP: CGAPKeyManager, APP_FOURFRONT: FourfrontKeyManager, APP_SMAHT: SMaHTKeyManager}
|
88
|
+
if not (key_manager := key_managers.get(self._app)) or not (key_manager := key_manager()):
|
89
|
+
raise Exception(f"Invalid app name: {self._app} (valid: {', '.join(ORCHESTRATED_APPS)}).")
|
90
|
+
if isinstance(env, str):
|
91
|
+
self._key = key_manager.get_keydict_for_env(env)
|
92
|
+
if key_server := self._key.get("server"):
|
93
|
+
self._server = key_server
|
94
|
+
elif isinstance(self._server, str):
|
95
|
+
self._key = key_manager.get_keydict_for_server(self._server)
|
96
|
+
self._key_pair = key_manager.keydict_to_keypair(self._key) if self._key else None
|
97
|
+
self._key_file = key_manager.keys_file
|
98
|
+
|
99
|
+
@property
|
100
|
+
def env(self):
|
101
|
+
return self._env
|
102
|
+
|
103
|
+
@property
|
104
|
+
def app(self):
|
105
|
+
return self._app
|
106
|
+
|
107
|
+
@property
|
108
|
+
def server(self):
|
109
|
+
return self._server
|
110
|
+
|
111
|
+
@property
|
112
|
+
def key(self):
|
113
|
+
return self._key
|
114
|
+
|
115
|
+
@property
|
116
|
+
def key_pair(self):
|
117
|
+
return self._key_pair
|
118
|
+
|
119
|
+
@property
|
120
|
+
def key_file(self):
|
121
|
+
return self._key_file
|
122
|
+
|
123
|
+
@property
|
124
|
+
def vapp(self):
|
125
|
+
return self._vapp
|
126
|
+
|
127
|
+
def get_metadata(self, object_id: str) -> Optional[dict]:
|
128
|
+
return get_metadata(obj_id=object_id, vapp=self._vapp, key=self._key)
|
129
|
+
|
130
|
+
def patch_metadata(self, object_id: str, data: str) -> Optional[dict]:
|
131
|
+
if self._key:
|
132
|
+
return patch_metadata(obj_id=object_id, patch_item=data, key=self._key)
|
133
|
+
return self.patch(f"/{object_id}", data)
|
134
|
+
|
135
|
+
def post_metadata(self, object_type: str, data: str) -> Optional[dict]:
|
136
|
+
if self._key:
|
137
|
+
return post_metadata(schema_name=object_type, post_item=data, key=self._key)
|
138
|
+
return self.post(f"/{object_type}", data)
|
139
|
+
|
140
|
+
def get(self, uri: str, follow: bool = True, **kwargs) -> Optional[Union[RequestResponse, TestResponse]]:
|
141
|
+
if isinstance(self._vapp, (VirtualApp, TestApp)):
|
142
|
+
response = self._vapp.get(self._uri(uri), **self._kwargs(**kwargs))
|
143
|
+
if response and response.status_code in [301, 302, 303, 307, 308] and follow:
|
144
|
+
response = response.follow()
|
145
|
+
return self._response(response)
|
146
|
+
return requests.get(self._uri(uri), allow_redirects=follow, **self._kwargs(**kwargs))
|
147
|
+
|
148
|
+
def patch(self, uri: str, data: Optional[dict] = None,
|
149
|
+
json: Optional[dict] = None, **kwargs) -> Optional[Union[RequestResponse, TestResponse]]:
|
150
|
+
if isinstance(self._vapp, (VirtualApp, TestApp)):
|
151
|
+
return self._vapp.patch_json(self._uri(uri), json or data, **self._kwargs(**kwargs))
|
152
|
+
return requests.patch(self._uri(uri), json=json or data, **self._kwargs(**kwargs))
|
153
|
+
|
154
|
+
def post(self, uri: str, data: Optional[dict] = None, json: Optional[dict] = None,
|
155
|
+
files: Optional[dict] = None, **kwargs) -> Optional[Union[RequestResponse, TestResponse]]:
|
156
|
+
if isinstance(self._vapp, (VirtualApp, TestApp)):
|
157
|
+
if files:
|
158
|
+
return self._vapp.post(self._uri(uri), json or data, upload_files=files, **self._kwargs(**kwargs))
|
159
|
+
else:
|
160
|
+
return self._vapp.post_json(self._uri(uri), json or data, upload_files=files, **self._kwargs(**kwargs))
|
161
|
+
return requests.post(self._uri(uri), json=json or data, files=files, **self._kwargs(**kwargs))
|
162
|
+
|
163
|
+
def get_schema(self, schema_name: str) -> Optional[dict]:
|
164
|
+
return get_schema(self.schema_name(schema_name), portal_vapp=self._vapp, key=self._key)
|
165
|
+
|
166
|
+
def get_schemas(self) -> dict:
|
167
|
+
return self.get("/profiles/").json()
|
168
|
+
|
169
|
+
@staticmethod
|
170
|
+
def schema_name(name: str) -> str:
|
171
|
+
return to_camel_case(name)
|
172
|
+
|
173
|
+
def is_file_schema(self, schema_name: str) -> bool:
|
174
|
+
if super_type_map := self.get_schemas_super_type_map():
|
175
|
+
if file_super_type := super_type_map.get(FILE_SCHEMA_NAME):
|
176
|
+
return self.schema_name(schema_name) in file_super_type
|
177
|
+
return False
|
178
|
+
|
179
|
+
def get_schemas_super_type_map(self) -> dict:
|
180
|
+
"""
|
181
|
+
Returns the "super type map" for all of the known schemas (via /profiles).
|
182
|
+
This is a dictionary of all types which have (one or more) sub-types whose value is
|
183
|
+
an array of all of those sub-types (direct and all descendents), in breadth first order.
|
184
|
+
"""
|
185
|
+
def breadth_first(super_type_map: dict, super_type_name: str) -> dict:
|
186
|
+
result = []
|
187
|
+
queue = deque(super_type_map.get(super_type_name, []))
|
188
|
+
while queue:
|
189
|
+
result.append(sub_type_name := queue.popleft())
|
190
|
+
if sub_type_name in super_type_map:
|
191
|
+
queue.extend(super_type_map[sub_type_name])
|
192
|
+
return result
|
193
|
+
if not (schemas := self.get_schemas()):
|
194
|
+
return {}
|
195
|
+
super_type_map = {}
|
196
|
+
for type_name in schemas:
|
197
|
+
if super_type_name := schemas[type_name].get("rdfs:subClassOf"):
|
198
|
+
super_type_name = super_type_name.replace("/profiles/", "").replace(".json", "")
|
199
|
+
if super_type_name != "Item":
|
200
|
+
if not super_type_map.get(super_type_name):
|
201
|
+
super_type_map[super_type_name] = [type_name]
|
202
|
+
elif type_name not in super_type_map[super_type_name]:
|
203
|
+
super_type_map[super_type_name].append(type_name)
|
204
|
+
super_type_map_flattened = {}
|
205
|
+
for super_type_name in super_type_map:
|
206
|
+
super_type_map_flattened[super_type_name] = breadth_first(super_type_map, super_type_name)
|
207
|
+
return super_type_map_flattened
|
208
|
+
|
209
|
+
def _uri(self, uri: str) -> str:
|
210
|
+
if not isinstance(uri, str) or not uri:
|
211
|
+
return "/"
|
212
|
+
if uri.lower().startswith("http://") or uri.lower().startswith("https://"):
|
213
|
+
return uri
|
214
|
+
uri = re.sub(r"/+", "/", uri)
|
215
|
+
return (self._server + ("/" if uri.startswith("/") else "") + uri) if self._server else uri
|
216
|
+
|
217
|
+
def _kwargs(self, **kwargs) -> dict:
|
218
|
+
result_kwargs = {"headers":
|
219
|
+
kwargs.get("headers", {"Content-type": "application/json", "Accept": "application/json"})}
|
220
|
+
if self._key_pair:
|
221
|
+
result_kwargs["auth"] = self._key_pair
|
222
|
+
if isinstance(timeout := kwargs.get("timeout"), int):
|
223
|
+
result_kwargs["timeout"] = timeout
|
224
|
+
return result_kwargs
|
225
|
+
|
226
|
+
def _response(self, response) -> Optional[RequestResponse]:
|
227
|
+
if response and isinstance(getattr(response.__class__, "json"), property):
|
228
|
+
class RequestResponseWrapper: # For consistency change json property to method.
|
229
|
+
def __init__(self, response, **kwargs):
|
230
|
+
super().__init__(**kwargs)
|
231
|
+
self._response = response
|
232
|
+
def __getattr__(self, attr): # noqa
|
233
|
+
return getattr(self._response, attr)
|
234
|
+
def json(self): # noqa
|
235
|
+
return self._response.json
|
236
|
+
response = RequestResponseWrapper(response)
|
237
|
+
return response
|
238
|
+
|
239
|
+
@staticmethod
|
240
|
+
def create_for_testing(ini_file: Optional[str] = None) -> Portal:
|
241
|
+
if isinstance(ini_file, str):
|
242
|
+
return Portal(Portal._create_vapp(ini_file))
|
243
|
+
minimal_ini_for_unit_testing = "[app:app]\nuse = egg:encoded\nsqlalchemy.url = postgresql://dummy\n"
|
244
|
+
with temporary_file(content=minimal_ini_for_unit_testing, suffix=".ini") as ini_file:
|
245
|
+
return Portal(Portal._create_vapp(ini_file))
|
246
|
+
|
247
|
+
@staticmethod
|
248
|
+
def create_for_testing_local(ini_file: Optional[str] = None) -> Portal:
|
249
|
+
if isinstance(ini_file, str) and ini_file:
|
250
|
+
return Portal(Portal._create_vapp(ini_file))
|
251
|
+
minimal_ini_for_testing_local = "\n".join([
|
252
|
+
"[app:app]\nuse = egg:encoded\nfile_upload_bucket = dummy",
|
253
|
+
"sqlalchemy.url = postgresql://postgres@localhost:5441/postgres?host=/tmp/snovault/pgdata",
|
254
|
+
"multiauth.groupfinder = encoded.authorization.smaht_groupfinder",
|
255
|
+
"multiauth.policies = auth0 session remoteuser accesskey",
|
256
|
+
"multiauth.policy.session.namespace = mailto",
|
257
|
+
"multiauth.policy.session.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
258
|
+
"multiauth.policy.session.base = pyramid.authentication.SessionAuthenticationPolicy",
|
259
|
+
"multiauth.policy.remoteuser.namespace = remoteuser",
|
260
|
+
"multiauth.policy.remoteuser.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
261
|
+
"multiauth.policy.remoteuser.base = pyramid.authentication.RemoteUserAuthenticationPolicy",
|
262
|
+
"multiauth.policy.accesskey.namespace = accesskey",
|
263
|
+
"multiauth.policy.accesskey.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
264
|
+
"multiauth.policy.accesskey.base = encoded.authentication.BasicAuthAuthenticationPolicy",
|
265
|
+
"multiauth.policy.accesskey.check = encoded.authentication.basic_auth_check",
|
266
|
+
"multiauth.policy.auth0.use = encoded.authentication.NamespacedAuthenticationPolicy",
|
267
|
+
"multiauth.policy.auth0.namespace = auth0",
|
268
|
+
"multiauth.policy.auth0.base = encoded.authentication.Auth0AuthenticationPolicy"
|
269
|
+
])
|
270
|
+
with temporary_file(content=minimal_ini_for_testing_local, suffix=".ini") as minimal_ini_file:
|
271
|
+
return Portal(Portal._create_vapp(minimal_ini_file))
|
272
|
+
|
273
|
+
@staticmethod
|
274
|
+
def _create_vapp(value: Union[str, Router, TestApp] = "development.ini", app_name: str = "app") -> TestApp:
|
275
|
+
if isinstance(value, TestApp):
|
276
|
+
return value
|
277
|
+
app = value if isinstance(value, Router) else get_app(value, app_name)
|
278
|
+
return TestApp(app, {"HTTP_ACCEPT": "application/json", "REMOTE_USER": "TEST"})
|
@@ -0,0 +1,142 @@
|
|
1
|
+
from typing import Any, Dict, List
|
2
|
+
|
3
|
+
|
4
|
+
class JsonSchemaConstants:
|
5
|
+
ANY_OF = "anyOf"
|
6
|
+
ARRAY = "array"
|
7
|
+
BOOLEAN = "boolean"
|
8
|
+
ENUM = "enum"
|
9
|
+
INTEGER = "integer"
|
10
|
+
ITEMS = "items"
|
11
|
+
NUMBER = "number"
|
12
|
+
OBJECT = "object"
|
13
|
+
ONE_OF = "oneOf"
|
14
|
+
PATTERN = "pattern"
|
15
|
+
PROPERTIES = "properties"
|
16
|
+
REQUIRED = "required"
|
17
|
+
STRING = "string"
|
18
|
+
TYPE = "type"
|
19
|
+
|
20
|
+
|
21
|
+
class EncodedSchemaConstants:
|
22
|
+
DEFAULT = "default"
|
23
|
+
FORMAT = "format"
|
24
|
+
IDENTIFYING_PROPERTIES = "identifyingProperties"
|
25
|
+
LINK_TO = "linkTo"
|
26
|
+
MERGE_REF = "$merge"
|
27
|
+
MIXIN_PROPERTIES = "mixinProperties"
|
28
|
+
REF = "$ref"
|
29
|
+
UNIQUE_KEY = "uniqueKey"
|
30
|
+
|
31
|
+
|
32
|
+
class SchemaConstants(JsonSchemaConstants, EncodedSchemaConstants):
|
33
|
+
pass
|
34
|
+
|
35
|
+
|
36
|
+
def get_properties(schema: Dict[str, Any]) -> Dict[str, Any]:
|
37
|
+
"""Return the properties of a schema."""
|
38
|
+
return schema.get(SchemaConstants.PROPERTIES, {})
|
39
|
+
|
40
|
+
|
41
|
+
def get_required(schema: Dict[str, Any]) -> List[str]:
|
42
|
+
"""Return the required properties of a schema."""
|
43
|
+
return schema.get(SchemaConstants.REQUIRED, [])
|
44
|
+
|
45
|
+
|
46
|
+
def get_any_of(schema: Dict[str, Any]) -> List[Dict[str, Any]]:
|
47
|
+
"""Return the anyOf properties of a schema."""
|
48
|
+
return schema.get(SchemaConstants.ANY_OF, [])
|
49
|
+
|
50
|
+
|
51
|
+
def get_one_of(schema: Dict[str, Any]) -> List[Dict[str, Any]]:
|
52
|
+
"""Return the oneOf properties of a schema."""
|
53
|
+
return schema.get(SchemaConstants.ONE_OF, [])
|
54
|
+
|
55
|
+
|
56
|
+
def get_conditionally_required_properties(schema: Dict[str, Any]) -> List[str]:
|
57
|
+
"""Get required + possibly required properties.
|
58
|
+
|
59
|
+
Using heuristics here; update as needed.
|
60
|
+
"""
|
61
|
+
return sorted(
|
62
|
+
list(
|
63
|
+
set(
|
64
|
+
get_required(schema)
|
65
|
+
+ get_any_of_required_properties(schema)
|
66
|
+
+ get_one_of_required_properties(schema)
|
67
|
+
)
|
68
|
+
)
|
69
|
+
)
|
70
|
+
|
71
|
+
|
72
|
+
def get_any_of_required_properties(schema: Dict[str, Any]) -> List[str]:
|
73
|
+
"""Get required properties from anyOf."""
|
74
|
+
return [
|
75
|
+
property_name
|
76
|
+
for any_of_schema in get_any_of(schema)
|
77
|
+
for property_name in get_required(any_of_schema)
|
78
|
+
]
|
79
|
+
|
80
|
+
|
81
|
+
def get_one_of_required_properties(schema: Dict[str, Any]) -> List[str]:
|
82
|
+
"""Get required properties from oneOf."""
|
83
|
+
return [
|
84
|
+
property_name
|
85
|
+
for one_of_schema in get_one_of(schema)
|
86
|
+
for property_name in get_required(one_of_schema)
|
87
|
+
]
|
88
|
+
|
89
|
+
|
90
|
+
def get_mixin_properties(schema: Dict[str, Any]) -> List[Dict[str, Any]]:
|
91
|
+
"""Return the mixin properties of a schema."""
|
92
|
+
return schema.get(EncodedSchemaConstants.MIXIN_PROPERTIES, [])
|
93
|
+
|
94
|
+
|
95
|
+
def get_identifying_properties(schema: Dict[str, Any]) -> List[str]:
|
96
|
+
"""Return the identifying properties of a schema."""
|
97
|
+
return schema.get(EncodedSchemaConstants.IDENTIFYING_PROPERTIES, [])
|
98
|
+
|
99
|
+
|
100
|
+
def get_schema_type(schema: Dict[str, Any]) -> str:
|
101
|
+
"""Return the type of a schema."""
|
102
|
+
return schema.get(SchemaConstants.TYPE, "")
|
103
|
+
|
104
|
+
|
105
|
+
def is_array_schema(schema: Dict[str, Any]) -> bool:
|
106
|
+
"""Return True if the schema is an array."""
|
107
|
+
return get_schema_type(schema) == SchemaConstants.ARRAY
|
108
|
+
|
109
|
+
|
110
|
+
def is_object_schema(schema: Dict[str, Any]) -> bool:
|
111
|
+
"""Return True if the schema is an object."""
|
112
|
+
return get_schema_type(schema) == SchemaConstants.OBJECT
|
113
|
+
|
114
|
+
|
115
|
+
def is_string_schema(schema: Dict[str, Any]) -> bool:
|
116
|
+
"""Return True if the schema is a string."""
|
117
|
+
return get_schema_type(schema) == SchemaConstants.STRING
|
118
|
+
|
119
|
+
|
120
|
+
def is_number_schema(schema: Dict[str, Any]) -> bool:
|
121
|
+
"""Return True if the schema is a number."""
|
122
|
+
return get_schema_type(schema) == SchemaConstants.NUMBER
|
123
|
+
|
124
|
+
|
125
|
+
def is_integer_schema(schema: Dict[str, Any]) -> bool:
|
126
|
+
"""Return True if the schema is an integer."""
|
127
|
+
return get_schema_type(schema) == SchemaConstants.INTEGER
|
128
|
+
|
129
|
+
|
130
|
+
def is_boolean_schema(schema: Dict[str, Any]) -> bool:
|
131
|
+
"""Return True if the schema is a boolean."""
|
132
|
+
return get_schema_type(schema) == SchemaConstants.BOOLEAN
|
133
|
+
|
134
|
+
|
135
|
+
def get_items(schema: Dict[str, Any]) -> Dict[str, Any]:
|
136
|
+
"""Return the items of a schema."""
|
137
|
+
return schema.get(SchemaConstants.ITEMS, {})
|
138
|
+
|
139
|
+
|
140
|
+
def has_property(schema: Dict[str, Any], property_name: str) -> bool:
|
141
|
+
"""Return True if the schema has the given property."""
|
142
|
+
return property_name in get_properties(schema)
|