dcicutils 8.7.1.1b6__py3-none-any.whl → 8.7.1.1b9__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- dcicutils/portal_object_utils.py +7 -10
- dcicutils/portal_utils.py +16 -11
- dcicutils/structured_data.py +28 -1
- {dcicutils-8.7.1.1b6.dist-info → dcicutils-8.7.1.1b9.dist-info}/METADATA +1 -1
- {dcicutils-8.7.1.1b6.dist-info → dcicutils-8.7.1.1b9.dist-info}/RECORD +8 -8
- {dcicutils-8.7.1.1b6.dist-info → dcicutils-8.7.1.1b9.dist-info}/LICENSE.txt +0 -0
- {dcicutils-8.7.1.1b6.dist-info → dcicutils-8.7.1.1b9.dist-info}/WHEEL +0 -0
- {dcicutils-8.7.1.1b6.dist-info → dcicutils-8.7.1.1b9.dist-info}/entry_points.txt +0 -0
dcicutils/portal_object_utils.py
CHANGED
@@ -137,23 +137,20 @@ class PortalObject:
|
|
137
137
|
comparing_data = value
|
138
138
|
else:
|
139
139
|
return {}
|
140
|
-
return PortalObject._compare(this_data, comparing_data
|
140
|
+
return PortalObject._compare(this_data, comparing_data)
|
141
141
|
|
142
142
|
_ARRAY_KEY_REGULAR_EXPRESSION = re.compile(rf"^({Schema._ARRAY_NAME_SUFFIX_CHAR}\d+)$")
|
143
143
|
|
144
144
|
@staticmethod
|
145
|
-
def _compare(a: Any, b: Any,
|
145
|
+
def _compare(a: Any, b: Any, _path: Optional[str] = None) -> dict:
|
146
146
|
def diff_creating(value: Any) -> object: # noqa
|
147
|
-
|
148
|
-
return create_readonly_object(value=value, type=value_type,
|
147
|
+
return create_readonly_object(value=value,
|
149
148
|
creating_value=True, updating_value=None, deleting_value=False)
|
150
149
|
def diff_updating(value: Any, updating_value: Any) -> object: # noqa
|
151
|
-
|
152
|
-
return create_readonly_object(value=value, type=value_type,
|
150
|
+
return create_readonly_object(value=value,
|
153
151
|
creating_value=False, updating_value=updating_value, deleting_value=False)
|
154
152
|
def diff_deleting(value: Any) -> object: # noqa
|
155
|
-
|
156
|
-
return create_readonly_object(value=value, type=value_type,
|
153
|
+
return create_readonly_object(value=value,
|
157
154
|
creating_value=False, updating_value=None, deleting_value=True)
|
158
155
|
diffs = {}
|
159
156
|
if isinstance(a, dict) and isinstance(b, dict):
|
@@ -163,7 +160,7 @@ class PortalObject:
|
|
163
160
|
if a[key] != PortalObject._PROPERTY_DELETION_SENTINEL:
|
164
161
|
diffs[path] = diff_creating(a[key])
|
165
162
|
else:
|
166
|
-
diffs.update(PortalObject._compare(a[key], b[key],
|
163
|
+
diffs.update(PortalObject._compare(a[key], b[key], _path=path))
|
167
164
|
elif isinstance(a, list) and isinstance(b, list):
|
168
165
|
# Ignore order of array elements; not absolutely technically correct but suits our purpose.
|
169
166
|
for index in range(len(a)):
|
@@ -179,7 +176,7 @@ class PortalObject:
|
|
179
176
|
if index < len(b):
|
180
177
|
diffs[path] = diff_deleting(b[index])
|
181
178
|
elif len(b) < index:
|
182
|
-
diffs.update(PortalObject._compare(a[index], b[index],
|
179
|
+
diffs.update(PortalObject._compare(a[index], b[index], _path=path))
|
183
180
|
else:
|
184
181
|
diffs[path] = diff_creating(a[index])
|
185
182
|
elif a != b:
|
dcicutils/portal_utils.py
CHANGED
@@ -15,7 +15,7 @@ from typing import Callable, Dict, List, Optional, Tuple, Type, Union
|
|
15
15
|
from uuid import uuid4 as uuid
|
16
16
|
from webtest.app import TestApp, TestResponse
|
17
17
|
from wsgiref.simple_server import make_server as wsgi_make_server
|
18
|
-
from dcicutils.common import OrchestratedApp, ORCHESTRATED_APPS
|
18
|
+
from dcicutils.common import APP_SMAHT, OrchestratedApp, ORCHESTRATED_APPS
|
19
19
|
from dcicutils.ff_utils import get_metadata, get_schema, patch_metadata, post_metadata
|
20
20
|
from dcicutils.misc_utils import to_camel_case, VirtualApp
|
21
21
|
from dcicutils.tmpfile_utils import temporary_file
|
@@ -42,6 +42,7 @@ class Portal:
|
|
42
42
|
or a dcicutils.misc_utils.VirtualApp, or even a pyramid.router.Router.
|
43
43
|
8. From another Portal object (i.e. copy constructor).
|
44
44
|
"""
|
45
|
+
DEFAULT_APP = APP_SMAHT
|
45
46
|
KEYS_FILE_DIRECTORY = "~"
|
46
47
|
MIME_TYPE_JSON = "application/json"
|
47
48
|
|
@@ -60,7 +61,7 @@ class Portal:
|
|
60
61
|
self._vapp = None
|
61
62
|
for arg in unspecified:
|
62
63
|
if arg is not None:
|
63
|
-
raise Exception("Portal
|
64
|
+
raise Exception("Portal initialization error; extraneous arguments.")
|
64
65
|
|
65
66
|
def init_from_portal(portal: Portal, unspecified: Optional[list] = None) -> None:
|
66
67
|
init(unspecified=unspecified)
|
@@ -93,13 +94,13 @@ class Portal:
|
|
93
94
|
raise Exception(f"Portal server inconsistency: {server} vs {key_server}")
|
94
95
|
self._key["server"] = self._server = server
|
95
96
|
if not self._key:
|
96
|
-
raise Exception("Portal
|
97
|
+
raise Exception("Portal initialization error; from key.")
|
97
98
|
|
98
99
|
def init_from_key_pair(key_pair: tuple, server: Optional[str], unspecified: Optional[list] = []) -> None:
|
99
100
|
if len(key_pair) >= 2:
|
100
101
|
init_from_key({"key": key_pair[0], "secret": key_pair[1]}, server, unspecified=unspecified)
|
101
102
|
else:
|
102
|
-
raise Exception("Portal
|
103
|
+
raise Exception("Portal initialization error; from key-pair.")
|
103
104
|
|
104
105
|
def init_from_keys_file(keys_file: str, env: Optional[str], server: Optional[str],
|
105
106
|
unspecified: Optional[list] = []) -> None:
|
@@ -107,7 +108,7 @@ class Portal:
|
|
107
108
|
with io.open(keys_file := os.path.expanduser(keys_file)) as f:
|
108
109
|
keys = json.load(f)
|
109
110
|
except Exception:
|
110
|
-
raise Exception(f"Portal
|
111
|
+
raise Exception(f"Portal initialization error; cannot open keys-file: {keys_file}")
|
111
112
|
if isinstance(env, str) and env and isinstance(key := keys.get(env), dict):
|
112
113
|
init_from_key(key, server)
|
113
114
|
self._keys_file = keys_file
|
@@ -121,7 +122,8 @@ class Portal:
|
|
121
122
|
self._keys_file = keys_file
|
122
123
|
self._env = env
|
123
124
|
else:
|
124
|
-
raise Exception(f"Portal
|
125
|
+
raise Exception(f"Portal initialization error;"
|
126
|
+
f" {env or server or None} not found in keys-file: {keys_file}")
|
125
127
|
|
126
128
|
def init_from_env_server_app(env: str, server: str, app: Optional[str],
|
127
129
|
unspecified: Optional[list] = None) -> None:
|
@@ -146,7 +148,7 @@ class Portal:
|
|
146
148
|
return prefix + server if server else None
|
147
149
|
|
148
150
|
if (valid_app := app) and not (valid_app := Portal._valid_app(app)):
|
149
|
-
raise Exception(f"Portal
|
151
|
+
raise Exception(f"Portal initialization error; invalid app: {app}")
|
150
152
|
self._app = valid_app
|
151
153
|
if isinstance(arg, Portal):
|
152
154
|
init_from_portal(arg, unspecified=[env, server, app])
|
@@ -164,12 +166,15 @@ class Portal:
|
|
164
166
|
init_from_env_server_app(arg, server, app, unspecified=[env])
|
165
167
|
elif (isinstance(env, str) and env) or (isinstance(server, str) and server):
|
166
168
|
init_from_env_server_app(env, server, app, unspecified=[arg])
|
169
|
+
elif not arg and (keys_file := self._default_keys_file(app=self._app or Portal.DEFAULT_APP, env=env)):
|
170
|
+
# If no initial arg then look for default app keys file.
|
171
|
+
init_from_keys_file(keys_file, env, server)
|
167
172
|
elif raise_exception:
|
168
|
-
raise Exception("Portal
|
173
|
+
raise Exception("Portal initialization error; insufficient arguments.")
|
169
174
|
else:
|
170
175
|
init()
|
171
176
|
if not self.vapp and not self.key and raise_exception:
|
172
|
-
raise Exception("Portal
|
177
|
+
raise Exception("Portal initialization error; neither key nor vapp defined.")
|
173
178
|
|
174
179
|
@property
|
175
180
|
def ini_file(self) -> Optional[str]:
|
@@ -443,7 +448,7 @@ class Portal:
|
|
443
448
|
if isinstance(arg, list) or isinstance(arg, dict) or isinstance(arg, Callable):
|
444
449
|
return Portal(Portal._create_router_for_testing(arg))
|
445
450
|
elif isinstance(arg, str) and arg.endswith(".ini"):
|
446
|
-
return Portal(
|
451
|
+
return Portal(arg)
|
447
452
|
elif arg == "local" or arg is True:
|
448
453
|
minimal_ini_for_testing = "\n".join([
|
449
454
|
"[app:app]\nuse = egg:encoded\nfile_upload_bucket = dummy",
|
@@ -467,7 +472,7 @@ class Portal:
|
|
467
472
|
else:
|
468
473
|
minimal_ini_for_testing = "[app:app]\nuse = egg:encoded\nsqlalchemy.url = postgresql://dummy\n"
|
469
474
|
with temporary_file(content=minimal_ini_for_testing, suffix=".ini") as ini_file:
|
470
|
-
return Portal(
|
475
|
+
return Portal(ini_file)
|
471
476
|
|
472
477
|
@staticmethod
|
473
478
|
def _create_vapp(arg: Union[TestApp, VirtualApp, PyramidRouter, str] = None) -> TestApp:
|
dcicutils/structured_data.py
CHANGED
@@ -11,8 +11,10 @@ from webtest.app import TestApp
|
|
11
11
|
from dcicutils.common import OrchestratedApp
|
12
12
|
from dcicutils.data_readers import CsvReader, Excel, RowReader
|
13
13
|
from dcicutils.file_utils import search_for_file
|
14
|
-
from dcicutils.misc_utils import (create_dict,
|
14
|
+
from dcicutils.misc_utils import (create_dict, create_readonly_object, load_json_if,
|
15
|
+
merge_objects, remove_empty_properties, right_trim,
|
15
16
|
split_string, to_boolean, to_enum, to_float, to_integer, VirtualApp)
|
17
|
+
from dcicutils.portal_object_utils import PortalObject
|
16
18
|
from dcicutils.portal_utils import Portal as PortalBase
|
17
19
|
from dcicutils.zip_utils import unpack_gz_file_to_temporary_file, unpack_files
|
18
20
|
|
@@ -61,6 +63,10 @@ class StructuredDataSet:
|
|
61
63
|
def data(self) -> dict:
|
62
64
|
return self._data
|
63
65
|
|
66
|
+
@property
|
67
|
+
def portal(self) -> Optional[Portal]:
|
68
|
+
return self._portal
|
69
|
+
|
64
70
|
@staticmethod
|
65
71
|
def load(file: str, portal: Optional[Union[VirtualApp, TestApp, Portal]] = None,
|
66
72
|
schemas: Optional[List[dict]] = None, autoadd: Optional[dict] = None,
|
@@ -140,6 +146,27 @@ class StructuredDataSet:
|
|
140
146
|
upload_file["path"] = file_path
|
141
147
|
return upload_files
|
142
148
|
|
149
|
+
def compare(self) -> dict:
|
150
|
+
diffs = {}
|
151
|
+
if self.data or self.portal:
|
152
|
+
refs = self.resolved_refs_with_uuids
|
153
|
+
for object_type in self.data:
|
154
|
+
if not diffs.get(object_type):
|
155
|
+
diffs[object_type] = []
|
156
|
+
for portal_object in self.data[object_type]:
|
157
|
+
portal_object = PortalObject(self.portal, portal_object, object_type)
|
158
|
+
existing_object, identifying_path = portal_object.lookup(include_identifying_path=True, raw=True)
|
159
|
+
if existing_object:
|
160
|
+
object_diffs = portal_object.compare(existing_object, consider_refs=True, resolved_refs=refs)
|
161
|
+
diffs[object_type].append(create_readonly_object(path=identifying_path,
|
162
|
+
uuid=existing_object.uuid,
|
163
|
+
diffs=object_diffs or None))
|
164
|
+
else:
|
165
|
+
# If there is no existing object we still create a record for this object
|
166
|
+
# but with no uuid which will be the indication that it does not exist.
|
167
|
+
diffs[object_type].append(create_readonly_object(path=identifying_path, uuid=None, diffs=None))
|
168
|
+
return diffs
|
169
|
+
|
143
170
|
def _load_file(self, file: str) -> None:
|
144
171
|
# Returns a dictionary where each property is the name (i.e. the type) of the data,
|
145
172
|
# and the value is array of dictionaries for the data itself. Handle these kinds of files:
|
@@ -44,8 +44,8 @@ dcicutils/log_utils.py,sha256=7pWMc6vyrorUZQf-V-M3YC6zrPgNhuV_fzm9xqTPph0,10883
|
|
44
44
|
dcicutils/misc_utils.py,sha256=9JqdVjHLkZUDTryngF3Dbu0m7XcbitbR7izWnxUSWc4,101953
|
45
45
|
dcicutils/obfuscation_utils.py,sha256=fo2jOmDRC6xWpYX49u80bVNisqRRoPskFNX3ymFAmjw,5963
|
46
46
|
dcicutils/opensearch_utils.py,sha256=V2exmFYW8Xl2_pGFixF4I2Cc549Opwe4PhFi5twC0M8,1017
|
47
|
-
dcicutils/portal_object_utils.py,sha256=
|
48
|
-
dcicutils/portal_utils.py,sha256=
|
47
|
+
dcicutils/portal_object_utils.py,sha256=KsOvrC2LM3OFX8GoHkqdO88td79v_Xvj1gVptZ5IiK4,12252
|
48
|
+
dcicutils/portal_utils.py,sha256=VLTofvk9z5wAQg4PZUi9GmvMCcAeYAX1R3qU1H3CNjw,27065
|
49
49
|
dcicutils/project_utils.py,sha256=qPdCaFmWUVBJw4rw342iUytwdQC0P-XKpK4mhyIulMM,31250
|
50
50
|
dcicutils/qa_checkers.py,sha256=cdXjeL0jCDFDLT8VR8Px78aS10hwNISOO5G_Zv2TZ6M,20534
|
51
51
|
dcicutils/qa_utils.py,sha256=TT0SiJWiuxYvbsIyhK9VO4uV_suxhB6CpuC4qPacCzQ,160208
|
@@ -59,15 +59,15 @@ dcicutils/secrets_utils.py,sha256=8dppXAsiHhJzI6NmOcvJV5ldvKkQZzh3Fl-cb8Wm7MI,19
|
|
59
59
|
dcicutils/sheet_utils.py,sha256=VlmzteONW5VF_Q4vo0yA5vesz1ViUah1MZ_yA1rwZ0M,33629
|
60
60
|
dcicutils/snapshot_utils.py,sha256=ymP7PXH6-yEiXAt75w0ldQFciGNqWBClNxC5gfX2FnY,22961
|
61
61
|
dcicutils/ssl_certificate_utils.py,sha256=F0ifz_wnRRN9dfrfsz7aCp4UDLgHEY8LaK7PjnNvrAQ,9707
|
62
|
-
dcicutils/structured_data.py,sha256=
|
62
|
+
dcicutils/structured_data.py,sha256=M5y5z5rnD-rXN2Ynf3hoJ8kagABUmVX98BisXdHg1C8,37201
|
63
63
|
dcicutils/task_utils.py,sha256=MF8ujmTD6-O2AC2gRGPHyGdUrVKgtr8epT5XU8WtNjk,8082
|
64
64
|
dcicutils/tmpfile_utils.py,sha256=n95XF8dZVbQRSXBZTGToXXfSs3JUVRyN6c3ZZ0nhAWI,1403
|
65
65
|
dcicutils/trace_utils.py,sha256=g8kwV4ebEy5kXW6oOrEAUsurBcCROvwtZqz9fczsGRE,1769
|
66
66
|
dcicutils/validation_utils.py,sha256=cMZIU2cY98FYtzK52z5WUYck7urH6JcqOuz9jkXpqzg,14797
|
67
67
|
dcicutils/variant_utils.py,sha256=2H9azNx3xAj-MySg-uZ2SFqbWs4kZvf61JnK6b-h4Qw,4343
|
68
68
|
dcicutils/zip_utils.py,sha256=rnjNv_k6L9jT2SjDSgVXp4BEJYLtz9XN6Cl2Fy-tqnM,2027
|
69
|
-
dcicutils-8.7.1.
|
70
|
-
dcicutils-8.7.1.
|
71
|
-
dcicutils-8.7.1.
|
72
|
-
dcicutils-8.7.1.
|
73
|
-
dcicutils-8.7.1.
|
69
|
+
dcicutils-8.7.1.1b9.dist-info/LICENSE.txt,sha256=qnwSmfnEWMl5l78VPDEzAmEbLVrRqQvfUQiHT0ehrOo,1102
|
70
|
+
dcicutils-8.7.1.1b9.dist-info/METADATA,sha256=6NZZJZyyO_0mZfDU0xU948JwXitVSbKiup22bPx6be8,3314
|
71
|
+
dcicutils-8.7.1.1b9.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
|
72
|
+
dcicutils-8.7.1.1b9.dist-info/entry_points.txt,sha256=8wbw5csMIgBXhkwfgsgJeuFcoUc0WsucUxmOyml2aoA,209
|
73
|
+
dcicutils-8.7.1.1b9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|