dcicutils 8.8.6__py3-none-any.whl → 8.8.6.1b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dcicutils/portal_utils.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from collections import deque
2
2
  from functools import lru_cache
3
+ from dcicutils.function_cache_decorator import function_cache
3
4
  import io
4
5
  import json
5
6
  from pyramid.config import Configurator as PyramidConfigurator
@@ -18,6 +19,7 @@ from wsgiref.simple_server import make_server as wsgi_make_server
18
19
  from dcicutils.common import APP_SMAHT, OrchestratedApp, ORCHESTRATED_APPS
19
20
  from dcicutils.ff_utils import get_metadata, get_schema, patch_metadata, post_metadata
20
21
  from dcicutils.misc_utils import to_camel_case, VirtualApp
22
+ from dcicutils.schema_utils import get_identifying_properties
21
23
  from dcicutils.tmpfile_utils import temporary_file
22
24
 
23
25
  Portal = Type["Portal"] # Forward type reference for type hints.
@@ -441,6 +443,54 @@ class Portal:
441
443
  result_kwargs["timeout"] = timeout
442
444
  return result_kwargs
443
445
 
446
+ @function_cache(maxsize=100, serialize_key=True)
447
+ def get_identifying_paths(self, portal_object: dict, portal_type: Optional[str] = None) -> List[str]:
448
+ """
449
+ Returns the list of the identifying Portal (URL) paths for the given Portal object. Favors any
450
+ uuid based path and defavors aliases based paths (ala self.get_identifying_property_names);
451
+ no other ordering defined. Returns empty list of none or otherwise not found.
452
+ """
453
+ results = []
454
+ if not isinstance(portal_object, dict):
455
+ return results
456
+ if not isinstance(portal_type, str) or not portal_type:
457
+ if not (portal_type := self.get_schema_type(portal_object)):
458
+ return results
459
+ for identifying_property in self.get_identifying_property_names(portal_type):
460
+ if identifying_value := portal_object.get(identifying_property):
461
+ if isinstance(identifying_value, list):
462
+ for identifying_value_item in identifying_value:
463
+ results.append(f"/{portal_type}/{identifying_value_item}")
464
+ elif identifying_property == "uuid":
465
+ results.append(f"/{identifying_value}")
466
+ else:
467
+ results.append(f"/{portal_type}/{identifying_value}")
468
+ return results
469
+
470
+ @function_cache(maxsize=100, serialize_key=True)
471
+ def get_identifying_property_names(self, schema: Union[str, dict]) -> List[str]:
472
+ """
473
+ Returns the list of identifying property names for the given Portal schema, which may
474
+ be either a schema name or a schema object; empty list of none or otherwise not found.
475
+ """
476
+ results = []
477
+ if isinstance(schema, str):
478
+ try:
479
+ if not (schema := self.get_schema(schema)):
480
+ return results
481
+ except Exception:
482
+ return results
483
+ elif not isinstance(schema, dict):
484
+ return results
485
+ if not (identifying_properties := get_identifying_properties(schema)):
486
+ return results
487
+ identifying_properties = [*identifying_properties]
488
+ for favored_identifying_property in reversed(["uuid", "identifier"]):
489
+ if favored_identifying_property in identifying_properties:
490
+ identifying_properties.remove(favored_identifying_property)
491
+ identifying_properties.insert(0, favored_identifying_property)
492
+ return identifying_properties
493
+
444
494
  @staticmethod
445
495
  def _default_keys_file(app: Optional[str], env: Optional[str], server: Optional[str]) -> Optional[str]:
446
496
  def infer_app_from_env(env: str) -> Optional[str]: # noqa
@@ -56,7 +56,7 @@ class StructuredDataSet:
56
56
  remove_empty_objects_from_lists: bool = True,
57
57
  ref_lookup_strategy: Optional[Callable] = None,
58
58
  ref_lookup_nocache: bool = False,
59
- norefs: bool = False,
59
+ norefs: bool = False, merge: bool = False,
60
60
  progress: Optional[Callable] = None,
61
61
  debug_sleep: Optional[str] = None) -> None:
62
62
  self._progress = progress if callable(progress) else None
@@ -75,6 +75,7 @@ class StructuredDataSet:
75
75
  self._nrows = 0
76
76
  self._autoadd_properties = autoadd if isinstance(autoadd, dict) and autoadd else None
77
77
  self._norefs = True if norefs is True else False
78
+ self._merge = True if merge is True else False
78
79
  self._debug_sleep = None
79
80
  if debug_sleep:
80
81
  try:
@@ -98,13 +99,13 @@ class StructuredDataSet:
98
99
  remove_empty_objects_from_lists: bool = True,
99
100
  ref_lookup_strategy: Optional[Callable] = None,
100
101
  ref_lookup_nocache: bool = False,
101
- norefs: bool = False,
102
+ norefs: bool = False, merge: bool = False,
102
103
  progress: Optional[Callable] = None,
103
104
  debug_sleep: Optional[str] = None) -> StructuredDataSet:
104
105
  return StructuredDataSet(file=file, portal=portal, schemas=schemas, autoadd=autoadd, order=order, prune=prune,
105
106
  remove_empty_objects_from_lists=remove_empty_objects_from_lists,
106
107
  ref_lookup_strategy=ref_lookup_strategy, ref_lookup_nocache=ref_lookup_nocache,
107
- norefs=norefs, progress=progress, debug_sleep=debug_sleep)
108
+ norefs=norefs, merge=merge, progress=progress, debug_sleep=debug_sleep)
108
109
 
109
110
  def validate(self, force: bool = False) -> None:
110
111
  def data_without_deleted_properties(data: dict) -> dict:
@@ -383,6 +384,11 @@ class StructuredDataSet:
383
384
  structured_row_template.set_value(structured_row, column_name, value, reader.file, reader.row_number)
384
385
  if self._autoadd_properties:
385
386
  self._add_properties(structured_row, self._autoadd_properties, schema)
387
+ # New merge functionality (2024-05-25).
388
+ if self._merge:
389
+ for identifying_path in self.get_identifying_paths(self._portal, structured_row, type_name):
390
+ if existing_portal_object := self._portal.get_metadata(identifying_path):
391
+ structured_row = merge_objects(existing_portal_object, structured_row)
386
392
  if (prune_error := self._prune_structured_row(structured_row)) is not None:
387
393
  self._note_error({"src": create_dict(type=schema_name, row=reader.row_number),
388
394
  "error": prune_error}, "validation")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dcicutils
3
- Version: 8.8.6
3
+ Version: 8.8.6.1b1
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
@@ -48,7 +48,7 @@ dcicutils/misc_utils.py,sha256=zHwsxxEn24muLBP7mDvMa8I9VdMejwW8HMuCL5xbhhw,10769
48
48
  dcicutils/obfuscation_utils.py,sha256=fo2jOmDRC6xWpYX49u80bVNisqRRoPskFNX3ymFAmjw,5963
49
49
  dcicutils/opensearch_utils.py,sha256=V2exmFYW8Xl2_pGFixF4I2Cc549Opwe4PhFi5twC0M8,1017
50
50
  dcicutils/portal_object_utils.py,sha256=gDXRgPsRvqCFwbC8WatsuflAxNiigOnqr0Hi93k3AgE,15422
51
- dcicutils/portal_utils.py,sha256=DYyE5o15GekDgzpJWas9iS7klAYbjJZUPW0G42McArk,30779
51
+ dcicutils/portal_utils.py,sha256=_uLdB-ulmFqmClA_Dkpxe4gfEfzSMJSu4yDmtpmqXwQ,33402
52
52
  dcicutils/progress_bar.py,sha256=UT7lxb-rVF_gp4yjY2Tg4eun1naaH__hB4_v3O85bcE,19468
53
53
  dcicutils/project_utils.py,sha256=qPdCaFmWUVBJw4rw342iUytwdQC0P-XKpK4mhyIulMM,31250
54
54
  dcicutils/qa_checkers.py,sha256=cdXjeL0jCDFDLT8VR8Px78aS10hwNISOO5G_Zv2TZ6M,20534
@@ -64,7 +64,7 @@ dcicutils/secrets_utils.py,sha256=8dppXAsiHhJzI6NmOcvJV5ldvKkQZzh3Fl-cb8Wm7MI,19
64
64
  dcicutils/sheet_utils.py,sha256=VlmzteONW5VF_Q4vo0yA5vesz1ViUah1MZ_yA1rwZ0M,33629
65
65
  dcicutils/snapshot_utils.py,sha256=ymP7PXH6-yEiXAt75w0ldQFciGNqWBClNxC5gfX2FnY,22961
66
66
  dcicutils/ssl_certificate_utils.py,sha256=F0ifz_wnRRN9dfrfsz7aCp4UDLgHEY8LaK7PjnNvrAQ,9707
67
- dcicutils/structured_data.py,sha256=XOMxrmkJohdCAyCJU09uI8ivthTKrtSSYReFbC9VYMs,63058
67
+ dcicutils/structured_data.py,sha256=z0QMgbFafJWoOUMwJJDVufgeT7r7fTTasx1ry5axMVM,63546
68
68
  dcicutils/submitr/progress_constants.py,sha256=5bxyX77ql8qEJearfHEvsvXl7D0GuUODW0T65mbRmnE,2895
69
69
  dcicutils/submitr/ref_lookup_strategy.py,sha256=Js2cVznTmgjciLWBPLCvMiwLIHXjDn3jww-gJPjYuFw,3467
70
70
  dcicutils/task_utils.py,sha256=MF8ujmTD6-O2AC2gRGPHyGdUrVKgtr8epT5XU8WtNjk,8082
@@ -73,8 +73,8 @@ dcicutils/trace_utils.py,sha256=g8kwV4ebEy5kXW6oOrEAUsurBcCROvwtZqz9fczsGRE,1769
73
73
  dcicutils/validation_utils.py,sha256=cMZIU2cY98FYtzK52z5WUYck7urH6JcqOuz9jkXpqzg,14797
74
74
  dcicutils/variant_utils.py,sha256=2H9azNx3xAj-MySg-uZ2SFqbWs4kZvf61JnK6b-h4Qw,4343
75
75
  dcicutils/zip_utils.py,sha256=_Y9EmL3D2dUZhxucxHvrtmmlbZmK4FpSsHEb7rGSJLU,3265
76
- dcicutils-8.8.6.dist-info/LICENSE.txt,sha256=qnwSmfnEWMl5l78VPDEzAmEbLVrRqQvfUQiHT0ehrOo,1102
77
- dcicutils-8.8.6.dist-info/METADATA,sha256=7VdJjqhSwF0whauIEE6ky-Dh5EE9we5MdC_1GHnV8sA,3435
78
- dcicutils-8.8.6.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
79
- dcicutils-8.8.6.dist-info/entry_points.txt,sha256=51Q4F_2V10L0282W7HFjP4jdzW4K8lnWDARJQVFy_hw,270
80
- dcicutils-8.8.6.dist-info/RECORD,,
76
+ dcicutils-8.8.6.1b1.dist-info/LICENSE.txt,sha256=qnwSmfnEWMl5l78VPDEzAmEbLVrRqQvfUQiHT0ehrOo,1102
77
+ dcicutils-8.8.6.1b1.dist-info/METADATA,sha256=5iidFphml1M_LwEmfPJdZnLcaSqT2FflV9Ld2JmeCWk,3439
78
+ dcicutils-8.8.6.1b1.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
79
+ dcicutils-8.8.6.1b1.dist-info/entry_points.txt,sha256=51Q4F_2V10L0282W7HFjP4jdzW4K8lnWDARJQVFy_hw,270
80
+ dcicutils-8.8.6.1b1.dist-info/RECORD,,