dcicutils 8.10.0.0b0__tar.gz → 8.10.0.1b1__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/PKG-INFO +1 -1
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/command_utils.py +69 -1
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/license_policies/park-lab-common.jsonc +6 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/misc_utils.py +41 -10
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/portal_object_utils.py +24 -89
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/portal_utils.py +237 -36
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/schema_utils.py +0 -50
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/structured_data.py +31 -20
- dcicutils-8.10.0.1b1/dcicutils/submitr/ref_lookup_strategy.py +73 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/pyproject.toml +1 -1
- dcicutils-8.10.0.0b0/dcicutils/submitr/ref_lookup_strategy.py +0 -67
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/LICENSE.txt +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/README.rst +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/__init__.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/base.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/beanstalk_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/bundle_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/captured_output.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/cloudformation_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/codebuild_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/common.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/contribution_scripts.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/contribution_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/creds_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/data_readers.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/data_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/datetime_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/deployment_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/diff_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/docker_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/ecr_scripts.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/ecr_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/ecs_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/env_base.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/env_manager.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/env_scripts.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/env_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/env_utils_legacy.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/es_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/exceptions.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/ff_mocks.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/ff_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/file_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/function_cache_decorator.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/glacier_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/http_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/jh_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/kibana/dashboards.json +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/kibana/readme.md +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/lang_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/license_policies/c4-infrastructure.jsonc +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/license_policies/c4-python-infrastructure.jsonc +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/license_policies/park-lab-common-server.jsonc +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/license_policies/park-lab-gpl-pipeline.jsonc +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/license_policies/park-lab-pipeline.jsonc +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/license_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/log_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/obfuscation_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/opensearch_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/progress_bar.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/project_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/qa_checkers.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/qa_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/redis_tools.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/redis_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/s3_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/scripts/publish_to_pypi.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/scripts/run_license_checker.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/scripts/view_portal_object.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/secrets_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/sheet_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/snapshot_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/ssl_certificate_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/submitr/progress_constants.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/task_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/tmpfile_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/trace_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/validation_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/variant_utils.py +0 -0
- {dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/zip_utils.py +0 -0
@@ -1,3 +1,4 @@
|
|
1
|
+
from __future__ import annotations
|
1
2
|
import contextlib
|
2
3
|
import functools
|
3
4
|
import glob
|
@@ -7,7 +8,7 @@ import re
|
|
7
8
|
import requests
|
8
9
|
import subprocess
|
9
10
|
|
10
|
-
from typing import Optional
|
11
|
+
from typing import Callable, Optional
|
11
12
|
from .exceptions import InvalidParameterError
|
12
13
|
from .lang_utils import there_are
|
13
14
|
from .misc_utils import INPUT, PRINT, environ_bool, print_error_message, decorator
|
@@ -384,3 +385,70 @@ def script_catch_errors():
|
|
384
385
|
message = str(e) # Note: We ignore the type, which isn't intended to be shown.
|
385
386
|
PRINT(message)
|
386
387
|
exit(1)
|
388
|
+
|
389
|
+
|
390
|
+
class Question:
|
391
|
+
"""
|
392
|
+
Supports asking the user (via stdin) a yes/no question, possibly repeatedly; and after
|
393
|
+
some maximum number times of the same answer in a row (consecutively), then asks them
|
394
|
+
if they want to automatically give that same answer to any/all subsequent questions.
|
395
|
+
Supports static/global list of such Question instances, hashed (only) by the question text.
|
396
|
+
"""
|
397
|
+
_static_instances = {}
|
398
|
+
|
399
|
+
@staticmethod
|
400
|
+
def instance(question: Optional[str] = None,
|
401
|
+
max: Optional[int] = None, printf: Optional[Callable] = None) -> Question:
|
402
|
+
question = question if isinstance(question, str) else ""
|
403
|
+
if not (instance := Question._static_instances.get(question)):
|
404
|
+
Question._static_instances[question] = (instance := Question(question, max=max, printf=printf))
|
405
|
+
return instance
|
406
|
+
|
407
|
+
@staticmethod
|
408
|
+
def yes(question: Optional[str] = None,
|
409
|
+
max: Optional[int] = None, printf: Optional[Callable] = None) -> bool:
|
410
|
+
return Question.instance(question, max=max, printf=printf).ask()
|
411
|
+
|
412
|
+
def __init__(self, question: Optional[str] = None,
|
413
|
+
max: Optional[int] = None, printf: Optional[Callable] = None) -> None:
|
414
|
+
self._question = question if isinstance(question, str) else ""
|
415
|
+
self._max = max if isinstance(max, int) and max > 0 else None
|
416
|
+
self._print = printf if callable(printf) else print
|
417
|
+
self._yes_consecutive_count = 0
|
418
|
+
self._no_consecutive_count = 0
|
419
|
+
self._yes_automatic = False
|
420
|
+
self._no_automatic = False
|
421
|
+
|
422
|
+
def ask(self, question: Optional[str] = None) -> bool:
|
423
|
+
|
424
|
+
def question_automatic(value: str) -> bool:
|
425
|
+
nonlocal self
|
426
|
+
RARROW = "▶"
|
427
|
+
LARROW = "◀"
|
428
|
+
if yes_or_no(f"{RARROW}{RARROW}{RARROW}"
|
429
|
+
f" Do you want to answer {value} to all such questions?"
|
430
|
+
f" {LARROW}{LARROW}{LARROW}"):
|
431
|
+
return True
|
432
|
+
self._yes_consecutive_count = 0
|
433
|
+
self._no_consecutive_count = 0
|
434
|
+
|
435
|
+
if self._yes_automatic:
|
436
|
+
return True
|
437
|
+
elif self._no_automatic:
|
438
|
+
return False
|
439
|
+
elif yes_or_no((question if isinstance(question, str) else "") or self._question or "Undefined question"):
|
440
|
+
self._yes_consecutive_count += 1
|
441
|
+
self._no_consecutive_count = 0
|
442
|
+
if (self._no_consecutive_count == 0) and self._max and (self._yes_consecutive_count >= self._max):
|
443
|
+
# Have reached the maximum number of consecutive YES answers; ask if YES to all subsequent.
|
444
|
+
if question_automatic("YES"):
|
445
|
+
self._yes_automatic = True
|
446
|
+
return True
|
447
|
+
else:
|
448
|
+
self._no_consecutive_count += 1
|
449
|
+
self._yes_consecutive_count = 0
|
450
|
+
if (self._yes_consecutive_count == 0) and self._max and (self._no_consecutive_count >= self._max):
|
451
|
+
# Have reached the maximum number of consecutive NO answers; ask if NO to all subsequent.
|
452
|
+
if question_automatic("NO"):
|
453
|
+
self._no_automatic = True
|
454
|
+
return False
|
{dcicutils-8.10.0.0b0 → dcicutils-8.10.0.1b1}/dcicutils/license_policies/park-lab-common.jsonc
RENAMED
@@ -248,6 +248,12 @@
|
|
248
248
|
"docutils" // Used only privately as a separate documentation-generation task for ReadTheDocs
|
249
249
|
],
|
250
250
|
|
251
|
+
|
252
|
+
"GNU General Public License v2 (GPLv2)": [
|
253
|
+
"pyinstaller",
|
254
|
+
"pyinstaller-hooks-contrib"
|
255
|
+
],
|
256
|
+
|
251
257
|
"MIT/X11 Derivative": [
|
252
258
|
// The license used by libxkbcommon is complicated and involves numerous included licenses,
|
253
259
|
// but all are permissive.
|
@@ -4,6 +4,7 @@ This file contains functions that might be generally useful.
|
|
4
4
|
|
5
5
|
from collections import namedtuple
|
6
6
|
import appdirs
|
7
|
+
from copy import deepcopy
|
7
8
|
import contextlib
|
8
9
|
import datetime
|
9
10
|
import functools
|
@@ -2199,28 +2200,58 @@ def merge_key_value_dict_lists(x, y):
|
|
2199
2200
|
return [key_value_dict(k, v) for k, v in merged.items()]
|
2200
2201
|
|
2201
2202
|
|
2202
|
-
def merge_objects(target: Union[dict, List[Any]], source: Union[dict, List[Any]],
|
2203
|
+
def merge_objects(target: Union[dict, List[Any]], source: Union[dict, List[Any]],
|
2204
|
+
full: bool = False, # deprecated
|
2205
|
+
expand_lists: Optional[bool] = None,
|
2206
|
+
primitive_lists: bool = False,
|
2207
|
+
copy: bool = False, _recursing: bool = False) -> Union[dict, List[Any]]:
|
2203
2208
|
"""
|
2204
|
-
Merges the given source dictionary or list into the target dictionary or list
|
2205
|
-
This MAY well change the given target (dictionary or list) IN PLACE
|
2206
|
-
|
2207
|
-
|
2209
|
+
Merges the given source dictionary or list into the target dictionary or list and returns the
|
2210
|
+
result. This MAY well change the given target (dictionary or list) IN PLACE ... UNLESS the copy
|
2211
|
+
argument is True, then the given target will not change as a local copy is made (and returned).
|
2212
|
+
|
2213
|
+
If the expand_lists argument is True then any target lists longer than the
|
2214
|
+
source be will be filled out with the last element(s) of the source; the full
|
2215
|
+
argument (is deprecated and) is a synomym for this. The default is False.
|
2216
|
+
|
2217
|
+
If the primitive_lists argument is True then lists of primitives (i.e. lists in which
|
2218
|
+
NONE of its elements are dictionaries, lists, or tuples) will themselves be treated
|
2219
|
+
like primitives, meaning the whole of a source list will replace the corresponding
|
2220
|
+
target; otherwise they will be merged normally, meaning each element of a source list
|
2221
|
+
will be merged, recursively, into the corresponding target list. The default is False.
|
2208
2222
|
"""
|
2223
|
+
def is_primitive_list(value: Any) -> bool: # noqa
|
2224
|
+
if not isinstance(value, list):
|
2225
|
+
return False
|
2226
|
+
for item in value:
|
2227
|
+
if isinstance(item, (dict, list, tuple)):
|
2228
|
+
return False
|
2229
|
+
return True
|
2230
|
+
|
2209
2231
|
if target is None:
|
2210
2232
|
return source
|
2233
|
+
if expand_lists not in (True, False):
|
2234
|
+
expand_lists = full is True
|
2235
|
+
if (copy is True) and (_recursing is not True):
|
2236
|
+
target = deepcopy(target)
|
2211
2237
|
if isinstance(target, dict) and isinstance(source, dict) and source:
|
2212
2238
|
for key, value in source.items():
|
2213
|
-
|
2239
|
+
if ((primitive_lists is True) and
|
2240
|
+
(key in target) and is_primitive_list(target[key]) and is_primitive_list(value)): # noqa
|
2241
|
+
target[key] = value
|
2242
|
+
else:
|
2243
|
+
target[key] = merge_objects(target[key], value,
|
2244
|
+
expand_lists=expand_lists, _recursing=True) if key in target else value
|
2214
2245
|
elif isinstance(target, list) and isinstance(source, list) and source:
|
2215
2246
|
for i in range(max(len(source), len(target))):
|
2216
2247
|
if i < len(target):
|
2217
2248
|
if i < len(source):
|
2218
|
-
target[i] = merge_objects(target[i], source[i],
|
2219
|
-
elif
|
2220
|
-
target[i] = merge_objects(target[i], source[len(source) - 1],
|
2249
|
+
target[i] = merge_objects(target[i], source[i], expand_lists=expand_lists, _recursing=True)
|
2250
|
+
elif expand_lists is True:
|
2251
|
+
target[i] = merge_objects(target[i], source[len(source) - 1], expand_lists=expand_lists)
|
2221
2252
|
else:
|
2222
2253
|
target.append(source[i])
|
2223
|
-
elif source:
|
2254
|
+
elif source not in (None, {}, []):
|
2224
2255
|
target = source
|
2225
2256
|
return target
|
2226
2257
|
|
@@ -1,6 +1,5 @@
|
|
1
1
|
from copy import deepcopy
|
2
2
|
from functools import lru_cache
|
3
|
-
import re
|
4
3
|
from typing import Any, Callable, List, Optional, Tuple, Type, Union
|
5
4
|
from dcicutils.data_readers import RowReader
|
6
5
|
from dcicutils.misc_utils import create_readonly_object
|
@@ -14,11 +13,9 @@ class PortalObject:
|
|
14
13
|
|
15
14
|
_PROPERTY_DELETION_SENTINEL = RowReader.CELL_DELETION_SENTINEL
|
16
15
|
|
17
|
-
def __init__(self, data: dict, portal: Portal = None,
|
18
|
-
schema: Optional[Union[dict, Schema]] = None, type: Optional[str] = None) -> None:
|
16
|
+
def __init__(self, data: dict, portal: Optional[Portal] = None, type: Optional[str] = None) -> None:
|
19
17
|
self._data = data if isinstance(data, dict) else {}
|
20
18
|
self._portal = portal if isinstance(portal, Portal) else None
|
21
|
-
self._schema = schema if isinstance(schema, dict) else (schema.data if isinstance(schema, Schema) else None)
|
22
19
|
self._type = type if isinstance(type, str) else ""
|
23
20
|
|
24
21
|
@property
|
@@ -32,7 +29,7 @@ class PortalObject:
|
|
32
29
|
@property
|
33
30
|
@lru_cache(maxsize=1)
|
34
31
|
def type(self) -> str:
|
35
|
-
return self._type or Portal.get_schema_type(self._data) or
|
32
|
+
return self._type or Portal.get_schema_type(self._data) or ""
|
36
33
|
|
37
34
|
@property
|
38
35
|
@lru_cache(maxsize=1)
|
@@ -47,7 +44,7 @@ class PortalObject:
|
|
47
44
|
@property
|
48
45
|
@lru_cache(maxsize=1)
|
49
46
|
def schema(self) -> Optional[dict]:
|
50
|
-
return self.
|
47
|
+
return self._portal.get_schema(self.type) if self._portal else None
|
51
48
|
|
52
49
|
def copy(self) -> PortalObject:
|
53
50
|
return PortalObject(deepcopy(self.data), portal=self.portal, type=self.type)
|
@@ -59,39 +56,29 @@ class PortalObject:
|
|
59
56
|
Returns the list of all identifying property names of this Portal object which actually have values.
|
60
57
|
Implicitly include "uuid" and "identifier" properties as identifying properties if they are actually
|
61
58
|
properties in the object schema, and favor these (first); defavor "aliases"; no other ordering defined.
|
59
|
+
Changed (2024-05-26) to use portal_utils.get_identifying_property_names; migrating some intricate stuff there.
|
62
60
|
"""
|
63
|
-
|
64
|
-
|
65
|
-
identifying_properties = []
|
66
|
-
for identifying_property in schema_identifying_properties:
|
67
|
-
if identifying_property not in ["uuid", "identifier", "aliases"]:
|
68
|
-
if self._data.get(identifying_property):
|
69
|
-
identifying_properties.append(identifying_property)
|
70
|
-
if self._data.get("identifier"):
|
71
|
-
identifying_properties.insert(0, "identifier")
|
72
|
-
if self._data.get("uuid"):
|
73
|
-
identifying_properties.insert(0, "uuid")
|
74
|
-
if "aliases" in schema_identifying_properties and self._data.get("aliases"):
|
75
|
-
identifying_properties.append("aliases")
|
76
|
-
return identifying_properties or None
|
61
|
+
# Migrating to and unifying this in portal_utils.Portal.get_identifying_paths (2024-05-26).
|
62
|
+
return self._portal.get_identifying_property_names(self.type, portal_object=self._data) if self._portal else []
|
77
63
|
|
78
64
|
@lru_cache(maxsize=8192)
|
79
65
|
def lookup(self, raw: bool = False,
|
80
66
|
ref_lookup_strategy: Optional[Callable] = None) -> Tuple[Optional[PortalObject], Optional[str], int]:
|
67
|
+
if not (identifying_paths := self._get_identifying_paths(ref_lookup_strategy=ref_lookup_strategy)):
|
68
|
+
return None, None, 0
|
81
69
|
nlookups = 0
|
82
70
|
first_identifying_path = None
|
83
71
|
try:
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
)
|
72
|
+
for identifying_path in identifying_paths:
|
73
|
+
if not first_identifying_path:
|
74
|
+
first_identifying_path = identifying_path
|
75
|
+
nlookups += 1
|
76
|
+
if self._portal and (item := self._portal.get(identifying_path, raw=raw)) and (item.status_code == 200):
|
77
|
+
return (
|
78
|
+
PortalObject(item.json(), portal=self._portal, type=self.type if raw else None),
|
79
|
+
identifying_path,
|
80
|
+
nlookups
|
81
|
+
)
|
95
82
|
except Exception:
|
96
83
|
pass
|
97
84
|
return None, first_identifying_path, nlookups
|
@@ -159,64 +146,12 @@ class PortalObject:
|
|
159
146
|
|
160
147
|
@lru_cache(maxsize=1)
|
161
148
|
def _get_identifying_paths(self, ref_lookup_strategy: Optional[Callable] = None) -> Optional[List[str]]:
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
if self.type:
|
169
|
-
identifying_paths.append(f"/{self.type}/{self.uuid}")
|
170
|
-
identifying_paths.append(f"/{self.uuid}")
|
171
|
-
return identifying_paths
|
172
|
-
for identifying_property in identifying_properties:
|
173
|
-
if identifying_value := self._data.get(identifying_property):
|
174
|
-
if identifying_property == "uuid":
|
175
|
-
if self.type:
|
176
|
-
identifying_paths.append(f"/{self.type}/{identifying_value}")
|
177
|
-
identifying_paths.append(f"/{identifying_value}")
|
178
|
-
# For now at least we include the path both with and without the schema type component,
|
179
|
-
# as for some identifying values, it works (only) with, and some, it works (only) without.
|
180
|
-
# For example: If we have FileSet with "accession", an identifying property, with value
|
181
|
-
# SMAFSFXF1RO4 then /SMAFSFXF1RO4 works but /FileSet/SMAFSFXF1RO4 does not; and
|
182
|
-
# conversely using "submitted_id", also an identifying property, with value
|
183
|
-
# UW_FILE-SET_COLO-829BL_HI-C_1 then /UW_FILE-SET_COLO-829BL_HI-C_1 does
|
184
|
-
# not work but /FileSet/UW_FILE-SET_COLO-829BL_HI-C_1 does work.
|
185
|
-
elif isinstance(identifying_value, list):
|
186
|
-
for identifying_value_item in identifying_value:
|
187
|
-
if self.type:
|
188
|
-
identifying_paths.append(f"/{self.type}/{identifying_value_item}")
|
189
|
-
identifying_paths.append(f"/{identifying_value_item}")
|
190
|
-
else:
|
191
|
-
# TODO: Import from somewhere ...
|
192
|
-
lookup_options = 0
|
193
|
-
if schema := self.schema:
|
194
|
-
# TODO: Hook into the ref_lookup_strategy thing in structured_data to make
|
195
|
-
# sure we check accession format (since it does not have a pattern).
|
196
|
-
if callable(ref_lookup_strategy):
|
197
|
-
lookup_options, ref_validator = ref_lookup_strategy(
|
198
|
-
self._portal, self.type, schema, identifying_value)
|
199
|
-
if callable(ref_validator):
|
200
|
-
if ref_validator(schema, identifying_property, identifying_value) is False:
|
201
|
-
continue
|
202
|
-
if pattern := schema.get("properties", {}).get(identifying_property, {}).get("pattern"):
|
203
|
-
if not re.match(pattern, identifying_value):
|
204
|
-
# If this identifying value is for a (identifying) property which has a
|
205
|
-
# pattern, and the value does NOT match the pattern, then do NOT include
|
206
|
-
# this value as an identifying path, since it cannot possibly be found.
|
207
|
-
continue
|
208
|
-
if not lookup_options:
|
209
|
-
lookup_options = Portal.LOOKUP_DEFAULT
|
210
|
-
if Portal.is_lookup_root_first(lookup_options):
|
211
|
-
identifying_paths.append(f"/{identifying_value}")
|
212
|
-
if Portal.is_lookup_specified_type(lookup_options) and self.type:
|
213
|
-
identifying_paths.append(f"/{self.type}/{identifying_value}")
|
214
|
-
if Portal.is_lookup_root(lookup_options) and not Portal.is_lookup_root_first(lookup_options):
|
215
|
-
identifying_paths.append(f"/{identifying_value}")
|
216
|
-
if Portal.is_lookup_subtypes(lookup_options):
|
217
|
-
for subtype_name in self._portal.get_schema_subtype_names(self.type):
|
218
|
-
identifying_paths.append(f"/{subtype_name}/{identifying_value}")
|
219
|
-
return identifying_paths or None
|
149
|
+
if not self._portal and (uuid := self.uuid):
|
150
|
+
return [f"/{uuid}"]
|
151
|
+
# Migrating to and unifying this in portal_utils.Portal.get_identifying_paths (2024-05-26).
|
152
|
+
return self._portal.get_identifying_paths(self._data,
|
153
|
+
portal_type=self.schema,
|
154
|
+
lookup_strategy=ref_lookup_strategy) if self._portal else None
|
220
155
|
|
221
156
|
def _normalized_refs(self, refs: List[dict]) -> Tuple[PortalObject, int]:
|
222
157
|
"""
|