dcicutils 8.14.0.1b15__tar.gz → 8.14.0.1b17__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/PKG-INFO +1 -1
  2. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/scripts/update_portal_object.py +144 -70
  3. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/pyproject.toml +1 -1
  4. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/LICENSE.txt +0 -0
  5. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/README.rst +0 -0
  6. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/__init__.py +0 -0
  7. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/base.py +0 -0
  8. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/beanstalk_utils.py +0 -0
  9. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/bundle_utils.py +0 -0
  10. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/captured_output.py +0 -0
  11. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/cloudformation_utils.py +0 -0
  12. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/codebuild_utils.py +0 -0
  13. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/command_utils.py +0 -0
  14. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/common.py +0 -0
  15. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/contribution_scripts.py +0 -0
  16. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/contribution_utils.py +0 -0
  17. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/creds_utils.py +0 -0
  18. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/data_readers.py +0 -0
  19. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/data_utils.py +0 -0
  20. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/datetime_utils.py +0 -0
  21. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/deployment_utils.py +0 -0
  22. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/diff_utils.py +0 -0
  23. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/docker_utils.py +0 -0
  24. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/ecr_scripts.py +0 -0
  25. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/ecr_utils.py +0 -0
  26. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/ecs_utils.py +0 -0
  27. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/env_base.py +0 -0
  28. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/env_manager.py +0 -0
  29. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/env_scripts.py +0 -0
  30. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/env_utils.py +0 -0
  31. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/env_utils_legacy.py +0 -0
  32. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/es_utils.py +0 -0
  33. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/exceptions.py +0 -0
  34. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/ff_mocks.py +0 -0
  35. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/ff_utils.py +0 -0
  36. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/file_utils.py +0 -0
  37. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/function_cache_decorator.py +0 -0
  38. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/glacier_utils.py +0 -0
  39. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/hack_for_elasticsearch_numpy_usage.py +0 -0
  40. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/http_utils.py +0 -0
  41. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/jh_utils.py +0 -0
  42. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/kibana/dashboards.json +0 -0
  43. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/kibana/readme.md +0 -0
  44. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/lang_utils.py +0 -0
  45. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/license_policies/c4-infrastructure.jsonc +0 -0
  46. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/license_policies/c4-python-infrastructure.jsonc +0 -0
  47. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/license_policies/park-lab-common-server.jsonc +0 -0
  48. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/license_policies/park-lab-common.jsonc +0 -0
  49. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/license_policies/park-lab-gpl-pipeline.jsonc +0 -0
  50. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/license_policies/park-lab-pipeline.jsonc +0 -0
  51. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/license_utils.py +0 -0
  52. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/log_utils.py +0 -0
  53. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/misc_utils.py +0 -0
  54. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/obfuscation_utils.py +0 -0
  55. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/opensearch_utils.py +0 -0
  56. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/portal_object_utils.py +0 -0
  57. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/portal_utils.py +0 -0
  58. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/progress_bar.py +0 -0
  59. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/project_utils.py +0 -0
  60. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/qa_checkers.py +0 -0
  61. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/qa_utils.py +0 -0
  62. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/redis_tools.py +0 -0
  63. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/redis_utils.py +0 -0
  64. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/s3_utils.py +0 -0
  65. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/schema_utils.py +0 -0
  66. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/scripts/publish_to_pypi.py +0 -0
  67. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/scripts/run_license_checker.py +0 -0
  68. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/scripts/view_portal_object.py +0 -0
  69. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/secrets_utils.py +0 -0
  70. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/sheet_utils.py +0 -0
  71. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/snapshot_utils.py +0 -0
  72. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/ssl_certificate_utils.py +0 -0
  73. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/structured_data.py +0 -0
  74. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/submitr/progress_constants.py +0 -0
  75. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/submitr/ref_lookup_strategy.py +0 -0
  76. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/task_utils.py +0 -0
  77. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/tmpfile_utils.py +0 -0
  78. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/trace_utils.py +0 -0
  79. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/validation_utils.py +0 -0
  80. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/variant_utils.py +0 -0
  81. {dcicutils-8.14.0.1b15 → dcicutils-8.14.0.1b17}/dcicutils/zip_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dcicutils
3
- Version: 8.14.0.1b15
3
+ Version: 8.14.0.1b17
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
@@ -17,10 +17,11 @@ import re
17
17
  import shutil
18
18
  import sys
19
19
  from typing import Callable, List, Optional, Tuple, Union
20
+ from dcicutils.captured_output import captured_output
20
21
  from dcicutils.command_utils import yes_or_no
21
- from dcicutils.common import ORCHESTRATED_APPS, APP_SMAHT
22
+ from dcicutils.common import ORCHESTRATED_APPS, APP_CGAP, APP_FOURFRONT, APP_SMAHT
22
23
  from dcicutils.ff_utils import delete_metadata, purge_metadata
23
- from dcicutils.misc_utils import get_error_message, ignored, PRINT, to_camel_case, to_snake_case
24
+ from dcicutils.misc_utils import get_error_message, ignored, normalize_string, PRINT, to_camel_case, to_snake_case
24
25
  from dcicutils.portal_utils import Portal as PortalFromUtils
25
26
  from dcicutils.tmpfile_utils import temporary_directory
26
27
 
@@ -145,30 +146,16 @@ def main():
145
146
  parser.print_help()
146
147
  sys.exit(1)
147
148
 
148
- if app := args.app:
149
- if (app not in ORCHESTRATED_APPS) and ((app := app.lower()) not in ORCHESTRATED_APPS):
150
- usage(f"ERROR: Unknown app name; must be one of: {' | '.join(ORCHESTRATED_APPS)}")
151
- else:
152
- app = APP_SMAHT
153
-
154
149
  if not (args.post or args.patch or args.upsert or args.delete or args.purge or args.load):
155
150
  usage()
156
151
 
152
+ if not (portal := _create_portal(env=args.env, ini=args.ini, app=args.app, load=args.load,
153
+ verbose=args.verbose, debug=args.debug, quiet=args.quiet)):
154
+ exit(1)
155
+
157
156
  if args.load:
158
- if args.post or args.patch or args.upsert or args.delete or args.purge:
159
- _print("Cannot use any other update option"
160
- "when using the --load option (to load data via snovault.loadxl).")
161
- exit(1)
162
- if args.env:
163
- if args.ini:
164
- _print("The --env is not used for the --load option (to load data via snovault.loadxl).")
165
- args.ini = args.env
166
- if not _load_data(load=args.load, ini_file=args.ini, explicit_schema_name=args.schema,
167
- verbose=args.verbose, debug=args.debug, noprogress=args.noprogress):
168
- exit(1)
169
- exit(0)
170
-
171
- portal = _create_portal(env=args.env, app=app, verbose=args.verbose, debug=args.debug)
157
+ _load_data(portal=portal, load=args.load, ini_file=args.ini, explicit_schema_name=args.schema,
158
+ verbose=args.verbose, debug=args.debug, noprogress=args.noprogress)
172
159
 
173
160
  if explicit_schema_name := args.schema:
174
161
  schema, explicit_schema_name = _get_schema(portal, explicit_schema_name)
@@ -402,17 +389,24 @@ def _upsert_data(portal: Portal, data: dict, schema_name: str,
402
389
  return
403
390
 
404
391
 
405
- def _load_data(load: str, ini_file: str, explicit_schema_name: Optional[str] = None,
392
+ def _load_data(portal: Portal, load: str, ini_file: str, explicit_schema_name: Optional[str] = None,
406
393
  verbose: bool = False, debug: bool = False, noprogress: bool = False,
407
- _portal: Optional[Portal] = None, _single_insert_file: Optional[str] = None) -> bool:
394
+ _single_insert_file: Optional[str] = None) -> bool:
408
395
 
396
+ import snovault.loadxl
409
397
  from snovault.loadxl import load_all_gen, LoadGenWrapper
410
- from dcicutils.captured_output import captured_output
411
398
  from dcicutils.progress_bar import ProgressBar
412
399
 
400
+ loadxl_summary = {}
401
+ loadxl_unresolved = {}
402
+ loadxl_output = []
403
+ loadxl_total_item_count = 0
404
+ loadxl_total_error_count = 0
405
+
413
406
  def loadxl(portal: Portal, inserts_directory: str, schema_names_to_load: dict):
414
407
 
415
- nonlocal LoadGenWrapper, load_all_gen, verbose, debug
408
+ nonlocal LoadGenWrapper, load_all_gen, loadxl_summary, verbose, debug
409
+ nonlocal loadxl_total_item_count, loadxl_total_error_count
416
410
  progress_total = sum(schema_names_to_load.values()) * 2 # loadxl does two passes
417
411
  progress_bar = ProgressBar(progress_total, interrupt_exit=True) if not noprogress else None
418
412
 
@@ -425,49 +419,75 @@ def _load_data(load: str, ini_file: str, explicit_schema_name: Optional[str] = N
425
419
  return str_or_bytes.strip()
426
420
  return ""
427
421
 
422
+ def loadxl_print(arg):
423
+ if arg:
424
+ loadxl_output.append(normalize_string(str(arg)))
425
+
426
+ snovault.loadxl.print = loadxl_print
427
+
428
428
  LOADXL_RESPONSE_PATTERN = re.compile(r"^([A-Z]+):\s*([a-zA-Z\/\d_-]+)\s*(\S+)\s*(\S+)?\s*(.*)$")
429
429
  LOADXL_ACTION_NAME = {"POST": "Create", "PATCH": "Update", "SKIP": "Check",
430
430
  "CHECK": "Validate", "ERROR": "Error"}
431
431
  current_item_type = None
432
432
  current_item_count = 0
433
433
  current_item_total = 0
434
- total_item_count = 0
434
+
435
435
  for item in LoadGenWrapper(load_all_gen(testapp=portal.vapp, inserts=inserts_directory,
436
436
  docsdir=None, overwrite=True, verbose=True)):
437
- total_item_count += 1
437
+ loadxl_total_item_count += 1
438
438
  item = decode_bytes(item)
439
439
  match = LOADXL_RESPONSE_PATTERN.match(item)
440
440
  if not match or match.re.groups < 3:
441
441
  continue
442
- action = LOADXL_ACTION_NAME[match.group(1).upper()]
443
- # response_value = match.group(0)
444
- # identifying_value = match.group(2)
442
+ if (action := LOADXL_ACTION_NAME[match.group(1).upper()]) == "Error":
443
+ identifying_value = match.group(2)
444
+ # Example message for unresolved link:
445
+ # ERROR: /22813a02-906b-4b60-b2b2-4afaea24aa28 Bad response: 422 Unprocessable Entity
446
+ # (not 200 OK or 3xx redirect for http://localhost/file_set?skip_indexing=true)b\'{"@type":
447
+ # ["ValidationFailure", "Error"], "status": "error", "code": # 422, "title": "Unprocessable Entity",
448
+ # "description": "Failed validation", "errors": [{"location": "body", "name": # "Schema: ",
449
+ # "description": "Unable to resolve link: /Library/a4e8f79f-4d47-4e85-9707-c343c940a315"},
450
+ # {"location": "body", "name": "Schema: libraries.0",
451
+ # "description": "\\\'a4e8f79f-4d47-4e85-9707-c343c940a315\\\' not found"}]}\'
452
+ unresolved_link_error_message_prefix = "Unable to resolve link:"
453
+ if (i := item.find(unresolved_link_error_message_prefix)) > 0:
454
+ unresolved_link = item[i + len(unresolved_link_error_message_prefix):].strip()
455
+ if (i := unresolved_link.find("\"")) > 0:
456
+ if (unresolved_link := unresolved_link[0:i]):
457
+ if ((error_type := re.search(r"https?://.*/(.*)\?skip_indexing=.*", item)) and
458
+ (len(error_type.groups()) == 1)): # noqa
459
+ error_type = to_camel_case(error_type.group(1))
460
+ identifying_value = f"/{error_type}{identifying_value}"
461
+ if not loadxl_unresolved.get(identifying_value):
462
+ loadxl_unresolved[identifying_value] = []
463
+ loadxl_unresolved[identifying_value].append(unresolved_link)
464
+ loadxl_total_error_count += 1
465
+ continue
445
466
  item_type = match.group(3)
446
467
  if current_item_type != item_type:
447
468
  if noprogress and debug and current_item_type is not None:
448
- print()
469
+ _print()
449
470
  current_item_type = item_type
450
471
  current_item_count = 0
451
472
  current_item_total = schema_names_to_load[item_type]
452
473
  if progress_bar:
453
474
  progress_bar.set_description(f"▶ {to_camel_case(current_item_type)}: {action}")
454
475
  current_item_count += 1
476
+ if loadxl_summary.get(current_item_type, None) is None:
477
+ loadxl_summary[current_item_type] = 0
478
+ loadxl_summary[current_item_type] += 1
455
479
  if progress_bar:
456
- progress_bar.set_progress(total_item_count)
480
+ progress_bar.set_progress(loadxl_total_item_count)
457
481
  elif debug:
458
- print(f"{current_item_type}: {current_item_count} or {current_item_total} ({action})")
482
+ _print(f"{current_item_type}: {current_item_count} or {current_item_total} ({action})")
459
483
  if progress_bar:
460
484
  progress_bar.set_description("▶ Load Complete")
461
- print()
462
-
463
- if not ini_file:
464
- ini_file = _DEFAULT_INI_FILE_FOR_LOAD
465
- if not os.path.isabs(ini_file := os.path.normpath(os.path.expanduser(ini_file))):
466
- ini_file = os.path.normpath(os.path.join(os.getcwd(), ini_file))
467
- if not os.path.exists(ini_file):
468
- _print(f"The INI file required for --load is not found: {ini_file}")
469
- exit(1)
485
+ if loadxl_total_item_count > loadxl_total_error_count:
486
+ _print()
470
487
 
488
+ if not portal.vapp:
489
+ _print("Must using INI based Portal object with --load (use --ini option to specify an INI file).")
490
+ return False
471
491
  if not os.path.isabs(load := os.path.normpath(os.path.expanduser(load))):
472
492
  load = os.path.normpath(os.path.join(os.getcwd(), load))
473
493
  if not os.path.exists(load):
@@ -480,10 +500,6 @@ def _load_data(load: str, ini_file: str, explicit_schema_name: Optional[str] = N
480
500
  inserts_directory = None
481
501
  inserts_file = load
482
502
 
483
- if not (portal := _portal):
484
- with captured_output(not debug):
485
- portal = Portal(ini_file)
486
-
487
503
  if inserts_file:
488
504
  with io.open(inserts_file, "r") as f:
489
505
  try:
@@ -503,9 +519,9 @@ def _load_data(load: str, ini_file: str, explicit_schema_name: Optional[str] = N
503
519
  file_name = os.path.join(tmpdir, f"{to_snake_case(schema_name)}.json")
504
520
  with io.open(file_name, "w") as f:
505
521
  json.dump(data, f)
506
- return _load_data(load=tmpdir, ini_file=ini_file, explicit_schema_name=schema_name,
522
+ return _load_data(portal=portal, load=tmpdir, ini_file=ini_file, explicit_schema_name=schema_name,
507
523
  verbose=verbose, debug=debug, noprogress=noprogress,
508
- _portal=portal, _single_insert_file=inserts_file)
524
+ _single_insert_file=inserts_file)
509
525
  elif isinstance(data, dict):
510
526
  if schema_name := explicit_schema_name:
511
527
  if _is_schema_name_list(portal, schema_names := list(data.keys())):
@@ -530,20 +546,20 @@ def _load_data(load: str, ini_file: str, explicit_schema_name: Optional[str] = N
530
546
  json.dump(schema_data, f)
531
547
  nfiles += 1
532
548
  if nfiles > 0:
533
- return _load_data(load=tmpdir, ini_file=ini_file,
549
+ return _load_data(portal=portal, load=tmpdir, ini_file=ini_file,
534
550
  verbose=verbose, debug=debug, noprogress=noprogress,
535
- _portal=portal, _single_insert_file=inserts_file)
551
+ _single_insert_file=inserts_file)
536
552
  return True
537
553
  else:
538
554
  _print(f"Unrecognized JSON data in file: {inserts_file}")
539
555
  return False
540
556
  return True
557
+
541
558
  if verbose:
542
559
  if _single_insert_file:
543
560
  _print(f"Loading data into Portal (via snovault.loadxl) from file: {_single_insert_file}")
544
561
  else:
545
562
  _print(f"Loading data into Portal (via snovault.loadxl) from directory: {inserts_directory}")
546
- _print(f"Portal INI file for load is: {ini_file}")
547
563
 
548
564
  schema_names = list(_get_schemas(portal).keys())
549
565
  schema_snake_case_names = [to_snake_case(item) for item in schema_names]
@@ -585,11 +601,27 @@ def _load_data(load: str, ini_file: str, explicit_schema_name: Optional[str] = N
585
601
  loadxl(portal=portal, inserts_directory=tmpdir, schema_names_to_load=schema_names_to_load)
586
602
  else:
587
603
  loadxl(portal=portal, inserts_directory=inserts_directory, schema_names_to_load=schema_names_to_load)
604
+
588
605
  if verbose:
589
606
  if _single_insert_file:
590
607
  _print(f"Done loading data into Portal (via snovault.loadxl) from file: {_single_insert_file}")
591
608
  else:
592
609
  _print(f"Done loading data into Portal (via snovault.loadxl) from directory: {inserts_directory}")
610
+ _print(f"Total items loaded: {loadxl_total_item_count}"
611
+ f"{f' (errors: {loadxl_total_error_count})' if loadxl_total_error_count else ''}")
612
+ for item in sorted(loadxl_summary.keys()):
613
+ _print(f"▷ {to_camel_case(item)}: {loadxl_summary[item]}")
614
+ if loadxl_unresolved:
615
+ _print("✗ Unresolved references:")
616
+ for item in loadxl_unresolved:
617
+ _print(f" ▶ {item}: {len(loadxl_unresolved[item])}")
618
+ for subitem in loadxl_unresolved[item]:
619
+ _print(f" ▷ {subitem}")
620
+ if debug and loadxl_output:
621
+ _print("✗ Output from loadxl:")
622
+ for item in loadxl_output:
623
+ _print(f" ▶ {item}")
624
+
593
625
  return True
594
626
 
595
627
 
@@ -611,25 +643,67 @@ def _prune_data_for_update(data: dict, noignore: bool = False, ignore: Optional[
611
643
  return {key: value for key, value in data.items() if key not in ignore_these_properties}
612
644
 
613
645
 
614
- def _create_portal(env: Optional[str] = None, app: Optional[str] = None,
615
- verbose: bool = False, debug: bool = False) -> Optional[Portal]:
646
+ def _create_portal(env: Optional[str] = None, ini: Optional[str] = None, app: Optional[str] = None,
647
+ load: Optional[str] = None, verbose: bool = False, debug: bool = False,
648
+ quiet: bool = False) -> Optional[Portal]:
649
+
650
+ if app:
651
+ if (app not in ORCHESTRATED_APPS) and ((app := app.lower()) not in ORCHESTRATED_APPS):
652
+ _print(f"Unknown app name; must be one of: {' | '.join(ORCHESTRATED_APPS)}")
653
+ return None
654
+ elif APP_SMAHT in (env or os.environ.get(_SMAHT_ENV_ENVIRON_NAME) or ""):
655
+ app = APP_SMAHT
656
+ elif APP_CGAP in (env or ""):
657
+ app = APP_CGAP
658
+ elif APP_FOURFRONT in (env or ""):
659
+ app = APP_FOURFRONT
660
+
661
+ if ini:
662
+ if env:
663
+ if not quiet:
664
+ _print("Ignoring --env option when --ini option is given.")
665
+ elif (app == _SMAHT_ENV_ENVIRON_NAME) and (env := os.environ.get(_SMAHT_ENV_ENVIRON_NAME)):
666
+ if not quiet:
667
+ _print(f"Ignoring SMAHT_ENV environment variable ({env}) when --ini option is given.")
668
+ if not os.path.isabs(ini_file := os.path.normpath(os.path.expanduser(ini))):
669
+ ini_file = os.path.normpath(os.path.join(os.getcwd(), ini_file))
670
+ if not os.path.exists(ini_file):
671
+ _print(f"Specified Portal INI file not found: {ini_file}")
672
+ return None
673
+ with captured_output(not debug):
674
+ if not (portal := Portal(ini_file, app=app)):
675
+ _print(f"Cannot create INI based Portal object: {env} ({app})")
676
+ return None
677
+ else:
678
+ env_from_environ = False
679
+ if not env and app:
680
+ # If the --load option is specified, and no --ini option is specified, then do NOT default
681
+ # to using the SMAHT_ENV environment variable (if set) for an access-key based Portal
682
+ # object; rather default to the default INI file (i.e. development.ini).
683
+ if (not load) and (app == APP_SMAHT) and (env := os.environ.get(_SMAHT_ENV_ENVIRON_NAME)):
684
+ env_from_environ = True
685
+ if not env:
686
+ if os.path.exists(ini_file := os.path.normpath(os.path.join(os.getcwd(), _DEFAULT_INI_FILE_FOR_LOAD))):
687
+ return _create_portal(ini=ini_file, app=app, verbose=verbose, debug=debug)
688
+ return None
689
+ if not (portal := Portal(env, app=app) if env or app else None):
690
+ _print(f"Cannot create access-key based Portal object: {env}{f' ({app})' if app else ''}")
691
+ return None
692
+
693
+ if (ini_file := portal.ini_file):
694
+ if not quiet:
695
+ _print(f"Portal environment: {ini_file}")
696
+ elif (env := portal.env) or (env := os.environ.get(_SMAHT_ENV_ENVIRON_NAME)):
697
+ _print(f"Portal environment"
698
+ f"{f' (from {_SMAHT_ENV_ENVIRON_NAME})' if env_from_environ else ''}: {portal.env}")
699
+ if verbose:
700
+ if portal.keys_file:
701
+ _print(f"Portal keys file: {portal.keys_file}")
702
+ if portal.key_id:
703
+ _print(f"Portal key prefix: {portal.key_id[0:2]}******")
704
+ if portal.server:
705
+ _print(f"Portal server: {portal.server}")
616
706
 
617
- env_from_environ = None
618
- if not env and (app == APP_SMAHT):
619
- if env := os.environ.get(_SMAHT_ENV_ENVIRON_NAME):
620
- env_from_environ = True
621
- if not (portal := Portal(env, app=app) if env or app else None):
622
- return None
623
- if verbose:
624
- if (env := portal.env) or (env := os.environ(_SMAHT_ENV_ENVIRON_NAME)):
625
- _print(f"Portal environment"
626
- f"{f' (from {_SMAHT_ENV_ENVIRON_NAME})' if env_from_environ else ''}: {portal.env}")
627
- if portal.keys_file:
628
- _print(f"Portal keys file: {portal.keys_file}")
629
- if portal.key_id:
630
- _print(f"Portal key prefix: {portal.key_id[0:2]}******")
631
- if portal.server:
632
- _print(f"Portal server: {portal.server}")
633
707
  return portal
634
708
 
635
709
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dcicutils"
3
- version = "8.14.0.1b15" # TODO: To become 8.14.1
3
+ version = "8.14.0.1b17" # TODO: To become 8.14.1
4
4
  description = "Utility package for interacting with the 4DN Data Portal and other 4DN resources"
5
5
  authors = ["4DN-DCIC Team <support@4dnucleome.org>"]
6
6
  license = "MIT"