dcicutils 8.8.6.1b1__py3-none-any.whl → 8.8.6.1b3__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
@@ -418,31 +418,6 @@ class Portal:
418
418
  return []
419
419
  return schemas_super_type_map.get(type_name, [])
420
420
 
421
- def url(self, url: str, raw: bool = False, database: bool = False) -> str:
422
- if not isinstance(url, str) or not url:
423
- return "/"
424
- elif (lowercase_url := url.lower()).startswith("http://") or lowercase_url.startswith("https://"):
425
- return url
426
- elif not (url := re.sub(r"/+", "/", url)).startswith("/"):
427
- url = "/"
428
- url = self.server + url if self.server else url
429
- if isinstance(raw, bool) and raw:
430
- url += ("&" if "?" in url else "?") + "frame=raw"
431
- if isinstance(database, bool) and database:
432
- url += ("&" if "?" in url else "?") + "datastore=database"
433
- return url
434
-
435
- def _kwargs(self, **kwargs) -> dict:
436
- if "headers" in kwargs:
437
- result_kwargs = {"headers": kwargs["headers"]}
438
- else:
439
- result_kwargs = {"headers": {"Content-type": Portal.MIME_TYPE_JSON, "Accept": Portal.MIME_TYPE_JSON}}
440
- if self.key_pair:
441
- result_kwargs["auth"] = self.key_pair
442
- if isinstance(timeout := kwargs.get("timeout"), int):
443
- result_kwargs["timeout"] = timeout
444
- return result_kwargs
445
-
446
421
  @function_cache(maxsize=100, serialize_key=True)
447
422
  def get_identifying_paths(self, portal_object: dict, portal_type: Optional[str] = None) -> List[str]:
448
423
  """
@@ -491,6 +466,31 @@ class Portal:
491
466
  identifying_properties.insert(0, favored_identifying_property)
492
467
  return identifying_properties
493
468
 
469
+ def url(self, url: str, raw: bool = False, database: bool = False) -> str:
470
+ if not isinstance(url, str) or not url:
471
+ return "/"
472
+ elif (lowercase_url := url.lower()).startswith("http://") or lowercase_url.startswith("https://"):
473
+ return url
474
+ elif not (url := re.sub(r"/+", "/", url)).startswith("/"):
475
+ url = "/"
476
+ url = self.server + url if self.server else url
477
+ if isinstance(raw, bool) and raw:
478
+ url += ("&" if "?" in url else "?") + "frame=raw"
479
+ if isinstance(database, bool) and database:
480
+ url += ("&" if "?" in url else "?") + "datastore=database"
481
+ return url
482
+
483
+ def _kwargs(self, **kwargs) -> dict:
484
+ if "headers" in kwargs:
485
+ result_kwargs = {"headers": kwargs["headers"]}
486
+ else:
487
+ result_kwargs = {"headers": {"Content-type": Portal.MIME_TYPE_JSON, "Accept": Portal.MIME_TYPE_JSON}}
488
+ if self.key_pair:
489
+ result_kwargs["auth"] = self.key_pair
490
+ if isinstance(timeout := kwargs.get("timeout"), int):
491
+ result_kwargs["timeout"] = timeout
492
+ return result_kwargs
493
+
494
494
  @staticmethod
495
495
  def _default_keys_file(app: Optional[str], env: Optional[str], server: Optional[str]) -> Optional[str]:
496
496
  def infer_app_from_env(env: str) -> Optional[str]: # noqa
@@ -566,6 +566,22 @@ class Portal:
566
566
  response = TestResponseWrapper(response)
567
567
  return response
568
568
 
569
+ @staticmethod
570
+ def _create_vapp(arg: Union[TestApp, VirtualApp, PyramidRouter, str] = None) -> TestApp:
571
+ if isinstance(arg, TestApp):
572
+ return arg
573
+ elif isinstance(arg, VirtualApp):
574
+ if not isinstance(arg.wrapped_app, TestApp):
575
+ raise Exception("Portal._create_vapp VirtualApp argument error.")
576
+ return arg.wrapped_app
577
+ if isinstance(arg, PyramidRouter):
578
+ router = arg
579
+ elif isinstance(arg, str) or not arg:
580
+ router = pyramid_get_app(arg or "development.ini", "app")
581
+ else:
582
+ raise Exception("Portal._create_vapp argument error.")
583
+ return TestApp(router, {"HTTP_ACCEPT": Portal.MIME_TYPE_JSON, "REMOTE_USER": "TEST"})
584
+
569
585
  @staticmethod
570
586
  def create_for_testing(arg: Optional[Union[str, bool, List[dict], dict, Callable]] = None) -> Portal:
571
587
  if isinstance(arg, list) or isinstance(arg, dict) or isinstance(arg, Callable):
@@ -597,22 +613,6 @@ class Portal:
597
613
  with temporary_file(content=minimal_ini_for_testing, suffix=".ini") as ini_file:
598
614
  return Portal(ini_file)
599
615
 
600
- @staticmethod
601
- def _create_vapp(arg: Union[TestApp, VirtualApp, PyramidRouter, str] = None) -> TestApp:
602
- if isinstance(arg, TestApp):
603
- return arg
604
- elif isinstance(arg, VirtualApp):
605
- if not isinstance(arg.wrapped_app, TestApp):
606
- raise Exception("Portal._create_vapp VirtualApp argument error.")
607
- return arg.wrapped_app
608
- if isinstance(arg, PyramidRouter):
609
- router = arg
610
- elif isinstance(arg, str) or not arg:
611
- router = pyramid_get_app(arg or "development.ini", "app")
612
- else:
613
- raise Exception("Portal._create_vapp argument error.")
614
- return TestApp(router, {"HTTP_ACCEPT": Portal.MIME_TYPE_JSON, "REMOTE_USER": "TEST"})
615
-
616
616
  @staticmethod
617
617
  def _create_router_for_testing(endpoints: Optional[List[Dict[str, Union[str, Callable]]]] = None) -> PyramidRouter:
618
618
  if isinstance(endpoints, dict):
@@ -351,18 +351,23 @@ class StructuredDataSet:
351
351
 
352
352
  def _load_json_file(self, file: str) -> None:
353
353
  with open(file) as f:
354
- file_json = json.load(f)
355
- schema_inferred_from_file_name = Schema.type_name(file)
356
- if self._portal.get_schema(schema_inferred_from_file_name) is not None:
354
+ item = json.load(f)
355
+ if ((schema_name_inferred_from_file_name := Schema.type_name(file)) and
356
+ (self._portal.get_schema(schema_name_inferred_from_file_name) is not None)): # noqa
357
357
  # If the JSON file name looks like a schema name then assume it
358
358
  # contains an object or an array of object of that schema type.
359
- self._add(Schema.type_name(file), file_json)
360
- elif isinstance(file_json, dict):
359
+ if self._merge:
360
+ item = self._merge_with_existing_portal_object(item, schema_name_inferred_from_file_name)
361
+ self._add(Schema.type_name(file), item)
362
+ elif isinstance(item, dict):
361
363
  # Otherwise if the JSON file name does not look like a schema name then
362
364
  # assume it a dictionary where each property is the name of a schema, and
363
365
  # which (each property) contains a list of object of that schema type.
364
- for schema_name in file_json:
365
- self._add(schema_name, file_json[schema_name])
366
+ for schema_name in item:
367
+ item = item[schema_name]
368
+ if self._merge:
369
+ item = self._merge_with_existing_portal_object(item, schema_name)
370
+ self._add(schema_name, item)
366
371
 
367
372
  def _load_reader(self, reader: RowReader, type_name: str) -> None:
368
373
  schema = None
@@ -386,14 +391,12 @@ class StructuredDataSet:
386
391
  self._add_properties(structured_row, self._autoadd_properties, schema)
387
392
  # New merge functionality (2024-05-25).
388
393
  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)
394
+ structured_row = self._merge_with_existing_portal_object(structured_row, schema_name)
392
395
  if (prune_error := self._prune_structured_row(structured_row)) is not None:
393
396
  self._note_error({"src": create_dict(type=schema_name, row=reader.row_number),
394
397
  "error": prune_error}, "validation")
395
398
  else:
396
- self._add(type_name, structured_row)
399
+ self._add(type_name, structured_row) # TODO: why type_name and not schema_name?
397
400
  if self._progress:
398
401
  self._progress({
399
402
  PROGRESS.LOAD_ITEM: self._nrows,
@@ -434,6 +437,18 @@ class StructuredDataSet:
434
437
  if name not in structured_row and (not schema or schema.data.get("properties", {}).get(name)):
435
438
  structured_row[name] = properties[name]
436
439
 
440
+ def _merge_with_existing_portal_object(self, portal_object: dict, portal_type: str) -> dict:
441
+ """
442
+ Given a Portal object (presumably/in-practice from the given metadata), if there is
443
+ an existing Portal item, identified by the identifying properties for the given object,
444
+ then merges the given object into the existing one and returns the result; otherwise
445
+ just returns the given object. Note that the given object may be CHANGED in place.
446
+ """
447
+ for identifying_path in self._portal.get_identifying_paths(portal_object, portal_type):
448
+ if existing_portal_object := self._portal.get_metadata(identifying_path, raw=True, raise_exception=False):
449
+ return merge_objects(existing_portal_object, portal_object)
450
+ return portal_object
451
+
437
452
  def _is_ref_lookup_specified_type(ref_lookup_flags: int) -> bool:
438
453
  return (ref_lookup_flags &
439
454
  Portal.LOOKUP_SPECIFIED_TYPE) == Portal.LOOKUP_SPECIFIED_TYPE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dcicutils
3
- Version: 8.8.6.1b1
3
+ Version: 8.8.6.1b3
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=_uLdB-ulmFqmClA_Dkpxe4gfEfzSMJSu4yDmtpmqXwQ,33402
51
+ dcicutils/portal_utils.py,sha256=54e0utkLQxQv2_bD37P1ZGeyG63b2W7nCte6KT9eCY0,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=z0QMgbFafJWoOUMwJJDVufgeT7r7fTTasx1ry5axMVM,63546
67
+ dcicutils/structured_data.py,sha256=sm8x08ckPZcIcyBaSlQRGrOD3YL9d09gz-xB3_TAWGE,64516
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.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,,
76
+ dcicutils-8.8.6.1b3.dist-info/LICENSE.txt,sha256=qnwSmfnEWMl5l78VPDEzAmEbLVrRqQvfUQiHT0ehrOo,1102
77
+ dcicutils-8.8.6.1b3.dist-info/METADATA,sha256=yvHwO7ib7srO20JLRYmG_InTcRd7LnrOZu1A6AYdLU0,3439
78
+ dcicutils-8.8.6.1b3.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
79
+ dcicutils-8.8.6.1b3.dist-info/entry_points.txt,sha256=51Q4F_2V10L0282W7HFjP4jdzW4K8lnWDARJQVFy_hw,270
80
+ dcicutils-8.8.6.1b3.dist-info/RECORD,,