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 +41 -41
- dcicutils/structured_data.py +26 -11
- {dcicutils-8.8.6.1b1.dist-info → dcicutils-8.8.6.1b3.dist-info}/METADATA +1 -1
- {dcicutils-8.8.6.1b1.dist-info → dcicutils-8.8.6.1b3.dist-info}/RECORD +7 -7
- {dcicutils-8.8.6.1b1.dist-info → dcicutils-8.8.6.1b3.dist-info}/LICENSE.txt +0 -0
- {dcicutils-8.8.6.1b1.dist-info → dcicutils-8.8.6.1b3.dist-info}/WHEEL +0 -0
- {dcicutils-8.8.6.1b1.dist-info → dcicutils-8.8.6.1b3.dist-info}/entry_points.txt +0 -0
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):
|
dcicutils/structured_data.py
CHANGED
@@ -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
|
-
|
355
|
-
|
356
|
-
|
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.
|
360
|
-
|
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
|
365
|
-
|
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
|
-
|
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
|
@@ -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=
|
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=
|
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.
|
77
|
-
dcicutils-8.8.6.
|
78
|
-
dcicutils-8.8.6.
|
79
|
-
dcicutils-8.8.6.
|
80
|
-
dcicutils-8.8.6.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|