dcicutils 8.7.1.1b6__py3-none-any.whl → 8.7.1.1b9__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.
@@ -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, self.type)
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, value_type: str, _path: Optional[str] = None) -> dict:
145
+ def _compare(a: Any, b: Any, _path: Optional[str] = None) -> dict:
146
146
  def diff_creating(value: Any) -> object: # noqa
147
- nonlocal value_type
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
- nonlocal value_type
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
- nonlocal value_type
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], type, _path=path))
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], value_type, _path=path))
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 init error; extraneous args.")
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 init error; from key.")
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 init error; from key-pair.")
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 init error; cannot open keys-file: {keys_file}")
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 init error; {env or server or None} not found in keys-file: {keys_file}")
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 init error; invalid app: {app}")
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 init error; insufficient args.")
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 init error; neither key nor vapp defined.")
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(Portal._create_vapp(arg))
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(Portal._create_vapp(ini_file))
475
+ return Portal(ini_file)
471
476
 
472
477
  @staticmethod
473
478
  def _create_vapp(arg: Union[TestApp, VirtualApp, PyramidRouter, str] = None) -> TestApp:
@@ -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, load_json_if, merge_objects, remove_empty_properties, right_trim,
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dcicutils
3
- Version: 8.7.1.1b6
3
+ Version: 8.7.1.1b9
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
@@ -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=Aa61u0o9OyWR7M26dGceMW9MSgDEZLqrwAkwu8FD228,12445
48
- dcicutils/portal_utils.py,sha256=8XVRCy882RrB8QT2gGvW36c4nT1RJCgUApt_wLP-EG8,26706
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=voFT8RqVXLX6-sZiEfhsgmwXEiMcJUrHneYUDXNm9wk,35647
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.1b6.dist-info/LICENSE.txt,sha256=qnwSmfnEWMl5l78VPDEzAmEbLVrRqQvfUQiHT0ehrOo,1102
70
- dcicutils-8.7.1.1b6.dist-info/METADATA,sha256=YLm-JVegaODLOnyLLQgUzFTwUPRlK-9dn0eZP1ANmHY,3314
71
- dcicutils-8.7.1.1b6.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
72
- dcicutils-8.7.1.1b6.dist-info/entry_points.txt,sha256=8wbw5csMIgBXhkwfgsgJeuFcoUc0WsucUxmOyml2aoA,209
73
- dcicutils-8.7.1.1b6.dist-info/RECORD,,
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,,