dcicutils 8.13.2__py3-none-any.whl → 8.13.3__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/scripts/update_portal_object.py +430 -0
- dcicutils/scripts/view_portal_object.py +146 -102
- {dcicutils-8.13.2.dist-info → dcicutils-8.13.3.dist-info}/METADATA +1 -1
- {dcicutils-8.13.2.dist-info → dcicutils-8.13.3.dist-info}/RECORD +7 -6
- {dcicutils-8.13.2.dist-info → dcicutils-8.13.3.dist-info}/entry_points.txt +1 -0
- {dcicutils-8.13.2.dist-info → dcicutils-8.13.3.dist-info}/LICENSE.txt +0 -0
- {dcicutils-8.13.2.dist-info → dcicutils-8.13.3.dist-info}/WHEEL +0 -0
@@ -0,0 +1,430 @@
|
|
1
|
+
# ------------------------------------------------------------------------------------------------------
|
2
|
+
# Command-line utility to update (post, patch, upsert) portal objects for SMaHT/CGAP/Fourfront.
|
3
|
+
# ------------------------------------------------------------------------------------------------------
|
4
|
+
# Example commands:
|
5
|
+
# update-portal-object --post file_format.json
|
6
|
+
# update-portal-object --upsert directory-with-schema-named-dot-json-files
|
7
|
+
# update-portal-object --patch file-not-named-for-schema-name.json --schema UnalignedReads
|
8
|
+
# --------------------------------------------------------------------------------------------------
|
9
|
+
|
10
|
+
import argparse
|
11
|
+
from functools import lru_cache
|
12
|
+
import glob
|
13
|
+
import io
|
14
|
+
import json
|
15
|
+
import os
|
16
|
+
import sys
|
17
|
+
from typing import Callable, List, Optional, Tuple, Union
|
18
|
+
from dcicutils.command_utils import yes_or_no
|
19
|
+
from dcicutils.common import ORCHESTRATED_APPS, APP_SMAHT
|
20
|
+
from dcicutils.ff_utils import delete_metadata, purge_metadata
|
21
|
+
from dcicutils.misc_utils import get_error_message, PRINT
|
22
|
+
from dcicutils.portal_utils import Portal as PortalFromUtils
|
23
|
+
|
24
|
+
|
25
|
+
class Portal(PortalFromUtils):
|
26
|
+
|
27
|
+
def delete_metadata(self, object_id: str) -> Optional[dict]:
|
28
|
+
if isinstance(object_id, str) and object_id and self.key:
|
29
|
+
return delete_metadata(obj_id=object_id, key=self.key)
|
30
|
+
return None
|
31
|
+
|
32
|
+
def purge_metadata(self, object_id: str) -> Optional[dict]:
|
33
|
+
if isinstance(object_id, str) and object_id and self.key:
|
34
|
+
return purge_metadata(obj_id=object_id, key=self.key)
|
35
|
+
return None
|
36
|
+
|
37
|
+
|
38
|
+
_DEFAULT_APP = "smaht"
|
39
|
+
_SMAHT_ENV_ENVIRON_NAME = "SMAHT_ENV"
|
40
|
+
|
41
|
+
# Schema properties to ignore (by default) for the view schema usage.
|
42
|
+
_SCHEMAS_IGNORE_PROPERTIES = [
|
43
|
+
"date_created",
|
44
|
+
"last_modified",
|
45
|
+
"principals_allowed",
|
46
|
+
"submitted_by",
|
47
|
+
"schema_version"
|
48
|
+
]
|
49
|
+
|
50
|
+
_SCHEMA_ORDER = [ # See: smaht-portal/src/encoded/project/loadxl.py
|
51
|
+
"access_key",
|
52
|
+
"user",
|
53
|
+
"consortium",
|
54
|
+
"submission_center",
|
55
|
+
"file_format",
|
56
|
+
"quality_metric",
|
57
|
+
"output_file",
|
58
|
+
"reference_file",
|
59
|
+
"reference_genome",
|
60
|
+
"software",
|
61
|
+
"tracking_item",
|
62
|
+
"workflow",
|
63
|
+
"workflow_run",
|
64
|
+
"meta_workflow",
|
65
|
+
"meta_workflow_run",
|
66
|
+
"image",
|
67
|
+
"document",
|
68
|
+
"static_section",
|
69
|
+
"page",
|
70
|
+
"filter_set",
|
71
|
+
"higlass_view_config",
|
72
|
+
"ingestion_submission",
|
73
|
+
"ontology_term",
|
74
|
+
"protocol",
|
75
|
+
"donor",
|
76
|
+
"demographic",
|
77
|
+
"medical_history",
|
78
|
+
"diagnosis",
|
79
|
+
"exposure",
|
80
|
+
"family_history",
|
81
|
+
"medical_treatment",
|
82
|
+
"death_circumstances",
|
83
|
+
"tissue_collection",
|
84
|
+
"tissue",
|
85
|
+
"histology",
|
86
|
+
"cell_line",
|
87
|
+
"cell_culture",
|
88
|
+
"cell_culture_mixture",
|
89
|
+
"preparation_kit",
|
90
|
+
"treatment",
|
91
|
+
"sample_preparation",
|
92
|
+
"tissue_sample",
|
93
|
+
"cell_culture_sample",
|
94
|
+
"cell_sample",
|
95
|
+
"analyte",
|
96
|
+
"analyte_preparation",
|
97
|
+
"assay",
|
98
|
+
"library",
|
99
|
+
"library_preparation",
|
100
|
+
"sequencer",
|
101
|
+
"basecalling",
|
102
|
+
"sequencing",
|
103
|
+
"file_set",
|
104
|
+
"unaligned_reads",
|
105
|
+
"aligned_reads",
|
106
|
+
"variant_calls",
|
107
|
+
]
|
108
|
+
|
109
|
+
|
110
|
+
def main():
|
111
|
+
|
112
|
+
parser = argparse.ArgumentParser(description="View Portal object.")
|
113
|
+
parser.add_argument("--env", "-e", type=str, required=False, default=None,
|
114
|
+
help=f"Environment name (key from ~/.smaht-keys.json).")
|
115
|
+
parser.add_argument("--app", type=str, required=False, default=None,
|
116
|
+
help=f"Application name (one of: smaht, cgap, fourfront).")
|
117
|
+
parser.add_argument("--schema", type=str, required=False, default=None,
|
118
|
+
help="Use named schema rather than infer from post/patch/upsert file name.")
|
119
|
+
parser.add_argument("--post", type=str, required=False, default=None, help="POST data.")
|
120
|
+
parser.add_argument("--patch", type=str, required=False, default=None, help="PATCH data.")
|
121
|
+
parser.add_argument("--upsert", type=str, required=False, default=None, help="Upsert data.")
|
122
|
+
parser.add_argument("--delete", type=str, required=False, default=None, help="Delete data.")
|
123
|
+
parser.add_argument("--purge", type=str, required=False, default=None, help="Purge data.")
|
124
|
+
parser.add_argument("--confirm", action="store_true", required=False, default=False, help="Confirm before action.")
|
125
|
+
parser.add_argument("--verbose", action="store_true", required=False, default=False, help="Verbose output.")
|
126
|
+
parser.add_argument("--quiet", action="store_true", required=False, default=False, help="Quiet output.")
|
127
|
+
parser.add_argument("--debug", action="store_true", required=False, default=False, help="Debugging output.")
|
128
|
+
args = parser.parse_args()
|
129
|
+
|
130
|
+
def usage(message: Optional[str] = None) -> None:
|
131
|
+
nonlocal parser
|
132
|
+
_print(message) if isinstance(message, str) else None
|
133
|
+
parser.print_help()
|
134
|
+
sys.exit(1)
|
135
|
+
|
136
|
+
if app := args.app:
|
137
|
+
if (app not in ORCHESTRATED_APPS) and ((app := app.lower()) not in ORCHESTRATED_APPS):
|
138
|
+
usage(f"ERROR: Unknown app name; must be one of: {' | '.join(ORCHESTRATED_APPS)}")
|
139
|
+
else:
|
140
|
+
app = APP_SMAHT
|
141
|
+
|
142
|
+
portal = _create_portal(env=args.env, app=app, verbose=args.verbose, debug=args.debug)
|
143
|
+
|
144
|
+
if explicit_schema_name := args.schema:
|
145
|
+
schema, explicit_schema_name = _get_schema(portal, explicit_schema_name)
|
146
|
+
if not schema:
|
147
|
+
usage(f"ERROR: Unknown schema name: {args.schema}")
|
148
|
+
|
149
|
+
if not (args.post or args.patch or args.upsert or args.delete or args.purge):
|
150
|
+
usage()
|
151
|
+
|
152
|
+
if args.post:
|
153
|
+
_post_or_patch_or_upsert(portal=portal,
|
154
|
+
file_or_directory=args.post,
|
155
|
+
explicit_schema_name=explicit_schema_name,
|
156
|
+
update_function=post_data,
|
157
|
+
update_action_name="POST",
|
158
|
+
confirm=args.confirm, verbose=args.verbose, quiet=args.quiet, debug=args.debug)
|
159
|
+
if args.patch:
|
160
|
+
_post_or_patch_or_upsert(portal=portal,
|
161
|
+
file_or_directory=args.patch,
|
162
|
+
explicit_schema_name=explicit_schema_name,
|
163
|
+
update_function=patch_data,
|
164
|
+
update_action_name="PATCH",
|
165
|
+
confirm=args.confirm, verbose=args.verbose, quiet=args.quiet, debug=args.debug)
|
166
|
+
if args.upsert:
|
167
|
+
_post_or_patch_or_upsert(portal=portal,
|
168
|
+
file_or_directory=args.upsert,
|
169
|
+
explicit_schema_name=explicit_schema_name,
|
170
|
+
update_function=upsert_data,
|
171
|
+
update_action_name="UPSERT",
|
172
|
+
confirm=args.confirm, verbose=args.verbose, quiet=args.quiet, debug=args.debug)
|
173
|
+
|
174
|
+
if args.delete:
|
175
|
+
if not portal.get_metadata(args.delete, raise_exception=False):
|
176
|
+
_print(f"Cannot find given object: {args.delete}")
|
177
|
+
sys.exit(1)
|
178
|
+
if yes_or_no(f"Do you really want to delete this item: {args.delete} ?"):
|
179
|
+
portal.delete_metadata(args.delete)
|
180
|
+
|
181
|
+
if args.purge:
|
182
|
+
if not portal.get_metadata(args.purge, raise_exception=False):
|
183
|
+
_print(f"Cannot find given object: {args.purge}")
|
184
|
+
sys.exit(1)
|
185
|
+
if yes_or_no(f"Do you really want to purge this item: {args.purge} ?"):
|
186
|
+
portal.delete_metadata(args.purge)
|
187
|
+
portal.purge_metadata(args.purge)
|
188
|
+
|
189
|
+
|
190
|
+
def _post_or_patch_or_upsert(portal: Portal, file_or_directory: str,
|
191
|
+
explicit_schema_name: str,
|
192
|
+
update_function: Callable, update_action_name: str,
|
193
|
+
confirm: bool = False, verbose: bool = False,
|
194
|
+
quiet: bool = False, debug: bool = False) -> None:
|
195
|
+
|
196
|
+
def is_schema_name_list(portal: Portal, keys: list) -> bool:
|
197
|
+
if isinstance(keys, list):
|
198
|
+
for key in keys:
|
199
|
+
if portal.get_schema(key) is None:
|
200
|
+
return False
|
201
|
+
return True
|
202
|
+
return False
|
203
|
+
|
204
|
+
def post_or_patch_or_upsert(portal: Portal, file: str, schema_name: Optional[str],
|
205
|
+
confirm: bool = False, verbose: bool = False,
|
206
|
+
quiet: bool = False, debug: bool = False) -> None:
|
207
|
+
|
208
|
+
nonlocal update_function, update_action_name
|
209
|
+
if not quiet:
|
210
|
+
_print(f"Processing {update_action_name} file: {file}")
|
211
|
+
if data := _read_json_from_file(file):
|
212
|
+
if isinstance(data, dict):
|
213
|
+
if isinstance(schema_name, str) and schema_name:
|
214
|
+
if debug:
|
215
|
+
_print(f"DEBUG: File ({file}) contains an object of type: {schema_name}")
|
216
|
+
update_function(portal, data, schema_name, confirm=confirm,
|
217
|
+
file=file, verbose=verbose, debug=debug)
|
218
|
+
elif is_schema_name_list(portal, list(data.keys())):
|
219
|
+
if debug:
|
220
|
+
_print(f"DEBUG: File ({file}) contains a dictionary of schema names.")
|
221
|
+
for schema_name in data:
|
222
|
+
if isinstance(schema_data := data[schema_name], list):
|
223
|
+
if debug:
|
224
|
+
_print(f"DEBUG: Processing {update_action_name}s for type: {schema_name}")
|
225
|
+
for index, item in enumerate(schema_data):
|
226
|
+
update_function(portal, item, schema_name, confirm=confirm,
|
227
|
+
file=file, index=index, verbose=verbose, debug=debug)
|
228
|
+
else:
|
229
|
+
_print(f"WARNING: File ({file}) contains schema item which is not a list: {schema_name}")
|
230
|
+
else:
|
231
|
+
_print(f"WARNING: File ({file}) contains unknown item type.")
|
232
|
+
elif isinstance(data, list):
|
233
|
+
if debug:
|
234
|
+
_print(f"DEBUG: File ({file}) contains a list of objects of type: {schema_name}")
|
235
|
+
for index, item in enumerate(data):
|
236
|
+
update_function(portal, item, schema_name, confirm=confirm,
|
237
|
+
file=file, index=index, verbose=verbose, debug=debug)
|
238
|
+
if debug:
|
239
|
+
_print(f"DEBUG: Processing {update_action_name} file done: {file}")
|
240
|
+
|
241
|
+
if os.path.isdir(file_or_directory):
|
242
|
+
if ((files := glob.glob(os.path.join(file_or_directory, "*.json"))) and
|
243
|
+
(files_and_schemas := _file_names_to_ordered_file_and_schema_names(portal, files))): # noqa
|
244
|
+
for file_and_schema in files_and_schemas:
|
245
|
+
if not (file := file_and_schema[0]):
|
246
|
+
continue
|
247
|
+
if not (schema_name := file_and_schema[1]) and not (schema_name := explicit_schema_name):
|
248
|
+
_print(f"ERROR: Schema cannot be inferred from file name and --schema not specified: {file}")
|
249
|
+
continue
|
250
|
+
post_or_patch_or_upsert(portal, file_and_schema[0], schema_name=schema_name,
|
251
|
+
confirm=confirm, quiet=quiet, verbose=verbose, debug=debug)
|
252
|
+
elif os.path.isfile(file := file_or_directory):
|
253
|
+
if ((schema_name := _get_schema_name_from_schema_named_json_file_name(portal, file)) or
|
254
|
+
(schema_name := explicit_schema_name)): # noqa
|
255
|
+
post_or_patch_or_upsert(portal, file, schema_name=schema_name,
|
256
|
+
confirm=confirm, quiet=quiet, verbose=verbose, debug=debug)
|
257
|
+
else:
|
258
|
+
post_or_patch_or_upsert(portal, file, schema_name=schema_name,
|
259
|
+
confirm=confirm, quiet=quiet, verbose=verbose, debug=debug)
|
260
|
+
# _print(f"ERROR: Schema cannot be inferred from file name and --schema not specified: {file}")
|
261
|
+
# return
|
262
|
+
else:
|
263
|
+
_print(f"ERROR: Cannot find file or directory: {file_or_directory}")
|
264
|
+
|
265
|
+
|
266
|
+
def post_data(portal: Portal, data: dict, schema_name: str, confirm: bool = False,
|
267
|
+
file: Optional[str] = None, index: int = 0,
|
268
|
+
verbose: bool = False, debug: bool = False) -> None:
|
269
|
+
if not (identifying_path := portal.get_identifying_path(data, portal_type=schema_name)):
|
270
|
+
if isinstance(file, str) and isinstance(index, int):
|
271
|
+
_print(f"ERROR: Item for POST has no identifying property: {file} (#{index + 1})")
|
272
|
+
else:
|
273
|
+
_print(f"ERROR: Item for POST has no identifying property.")
|
274
|
+
return
|
275
|
+
if portal.get_metadata(identifying_path, raise_exception=False):
|
276
|
+
_print(f"ERROR: Item for POST already exists: {identifying_path}")
|
277
|
+
return
|
278
|
+
if (confirm is True) and not yes_or_no(f"POST data for: {identifying_path} ?"):
|
279
|
+
return
|
280
|
+
if verbose:
|
281
|
+
_print(f"POST {schema_name} item: {identifying_path}")
|
282
|
+
try:
|
283
|
+
portal.post_metadata(schema_name, data)
|
284
|
+
if debug:
|
285
|
+
_print(f"DEBUG: POST {schema_name} item done: {identifying_path}")
|
286
|
+
except Exception as e:
|
287
|
+
_print(f"ERROR: Cannot POST {schema_name} item: {identifying_path}")
|
288
|
+
_print(get_error_message(e))
|
289
|
+
return
|
290
|
+
|
291
|
+
|
292
|
+
def patch_data(portal: Portal, data: dict, schema_name: str, confirm: bool = False,
|
293
|
+
file: Optional[str] = None, index: int = 0,
|
294
|
+
verbose: bool = False, debug: bool = False) -> None:
|
295
|
+
if not (identifying_path := portal.get_identifying_path(data, portal_type=schema_name)):
|
296
|
+
if isinstance(file, str) and isinstance(index, int):
|
297
|
+
_print(f"ERROR: Item for PATCH has no identifying property: {file} (#{index + 1})")
|
298
|
+
else:
|
299
|
+
_print(f"ERROR: Item for PATCH has no identifying property.")
|
300
|
+
return
|
301
|
+
if not portal.get_metadata(identifying_path, raise_exception=False):
|
302
|
+
_print(f"ERROR: Item for PATCH does not already exist: {identifying_path}")
|
303
|
+
return
|
304
|
+
if (confirm is True) and not yes_or_no(f"PATCH data for: {identifying_path}"):
|
305
|
+
return
|
306
|
+
if verbose:
|
307
|
+
_print(f"PATCH {schema_name} item: {identifying_path}")
|
308
|
+
try:
|
309
|
+
portal.patch_metadata(identifying_path, data)
|
310
|
+
if debug:
|
311
|
+
_print(f"DEBUG: PATCH {schema_name} item OK: {identifying_path}")
|
312
|
+
except Exception as e:
|
313
|
+
_print(f"ERROR: Cannot PATCH {schema_name} item: {identifying_path}")
|
314
|
+
_print(e)
|
315
|
+
return
|
316
|
+
|
317
|
+
|
318
|
+
def upsert_data(portal: Portal, data: dict, schema_name: str, confirm: bool = False,
|
319
|
+
file: Optional[str] = None, index: int = 0,
|
320
|
+
verbose: bool = False, debug: bool = False) -> None:
|
321
|
+
if not (identifying_path := portal.get_identifying_path(data, portal_type=schema_name)):
|
322
|
+
if isinstance(file, str) and isinstance(index, int):
|
323
|
+
_print(f"ERROR: Item for UPSERT has no identifying property: {file} (#{index + 1})")
|
324
|
+
else:
|
325
|
+
_print(f"ERROR: Item for UPSERT has no identifying property.")
|
326
|
+
return
|
327
|
+
exists = portal.get_metadata(identifying_path, raise_exception=False)
|
328
|
+
if ((confirm is True) and not yes_or_no(f"{'PATCH' if exists else 'POST'} data for: {identifying_path} ?")):
|
329
|
+
return
|
330
|
+
if verbose:
|
331
|
+
_print(f"{'PATCH' if exists else 'POST'} {schema_name} item: {identifying_path}")
|
332
|
+
try:
|
333
|
+
portal.post_metadata(schema_name, data) if not exists else portal.patch_metadata(identifying_path, data)
|
334
|
+
if debug:
|
335
|
+
_print(f"DEBUG: UPSERT {schema_name} item OK: {identifying_path}")
|
336
|
+
except Exception as e:
|
337
|
+
_print(f"ERROR: Cannot UPSERT {schema_name} item: {identifying_path}")
|
338
|
+
_print(e)
|
339
|
+
return
|
340
|
+
|
341
|
+
|
342
|
+
def _create_portal(env: Optional[str] = None, app: Optional[str] = None,
|
343
|
+
verbose: bool = False, debug: bool = False) -> Optional[Portal]:
|
344
|
+
|
345
|
+
env_from_environ = None
|
346
|
+
if not env and (app == APP_SMAHT):
|
347
|
+
if env := os.environ.get(_SMAHT_ENV_ENVIRON_NAME):
|
348
|
+
env_from_environ = True
|
349
|
+
if not (portal := Portal(env, app=app) if env or app else None):
|
350
|
+
return None
|
351
|
+
if verbose:
|
352
|
+
if (env := portal.env) or (env := os.environ(_SMAHT_ENV_ENVIRON_NAME)):
|
353
|
+
_print(f"Portal environment"
|
354
|
+
f"{f' (from {_SMAHT_ENV_ENVIRON_NAME})' if env_from_environ else ''}: {portal.env}")
|
355
|
+
if portal.keys_file:
|
356
|
+
_print(f"Portal keys file: {portal.keys_file}")
|
357
|
+
if portal.key_id:
|
358
|
+
_print(f"Portal key prefix: {portal.key_id[0:2]}******")
|
359
|
+
if portal.server:
|
360
|
+
_print(f"Portal server: {portal.server}")
|
361
|
+
return portal
|
362
|
+
|
363
|
+
|
364
|
+
def _read_json_from_file(file: str) -> Optional[dict]:
|
365
|
+
try:
|
366
|
+
if not os.path.exists(file):
|
367
|
+
return None
|
368
|
+
with io.open(file, "r") as f:
|
369
|
+
try:
|
370
|
+
return json.load(f)
|
371
|
+
except Exception:
|
372
|
+
_print(f"ERROR: Cannot load JSON from file: {file}")
|
373
|
+
return None
|
374
|
+
except Exception:
|
375
|
+
_print(f"ERROR: Cannot open file: {file}")
|
376
|
+
return None
|
377
|
+
|
378
|
+
|
379
|
+
def _file_names_to_ordered_file_and_schema_names(portal: Portal,
|
380
|
+
files: Union[List[str], str]) -> List[Tuple[str, Optional[str]]]:
|
381
|
+
results = []
|
382
|
+
if isinstance(files, str):
|
383
|
+
files = [files]
|
384
|
+
if not isinstance(files, list):
|
385
|
+
return results
|
386
|
+
for file in files:
|
387
|
+
if isinstance(file, str) and file:
|
388
|
+
results.append((file, _get_schema_name_from_schema_named_json_file_name(portal, file)))
|
389
|
+
ordered_results = []
|
390
|
+
for schema_name in _SCHEMA_ORDER:
|
391
|
+
schema_name = portal.schema_name(schema_name)
|
392
|
+
if result := next((item for item in results if item[1] == schema_name), None):
|
393
|
+
ordered_results.append(result)
|
394
|
+
results.remove(result)
|
395
|
+
ordered_results.extend(results) if results else None
|
396
|
+
return ordered_results
|
397
|
+
|
398
|
+
|
399
|
+
def _get_schema_name_from_schema_named_json_file_name(portal: Portal, value: str) -> Optional[str]:
|
400
|
+
try:
|
401
|
+
if not value.endswith(".json"):
|
402
|
+
return None
|
403
|
+
_, schema_name = _get_schema(portal, os.path.basename(value[:-5]))
|
404
|
+
return schema_name
|
405
|
+
except Exception:
|
406
|
+
return False
|
407
|
+
|
408
|
+
|
409
|
+
@lru_cache(maxsize=1)
|
410
|
+
def _get_schemas(portal: Portal) -> Optional[dict]:
|
411
|
+
return portal.get_schemas()
|
412
|
+
|
413
|
+
|
414
|
+
@lru_cache(maxsize=100)
|
415
|
+
def _get_schema(portal: Portal, name: str) -> Tuple[Optional[dict], Optional[str]]:
|
416
|
+
if portal and name and (name := name.replace("_", "").replace("-", "").strip().lower()):
|
417
|
+
if schemas := _get_schemas(portal):
|
418
|
+
for schema_name in schemas:
|
419
|
+
if schema_name.replace("_", "").replace("-", "").strip().lower() == name.lower():
|
420
|
+
return schemas[schema_name], schema_name
|
421
|
+
return None, None
|
422
|
+
|
423
|
+
|
424
|
+
def _print(*args, **kwargs) -> None:
|
425
|
+
PRINT(*args, **kwargs)
|
426
|
+
sys.stdout.flush()
|
427
|
+
|
428
|
+
|
429
|
+
if __name__ == "__main__":
|
430
|
+
main()
|
@@ -62,9 +62,10 @@ import json
|
|
62
62
|
import pyperclip
|
63
63
|
import os
|
64
64
|
import sys
|
65
|
-
from typing import Callable, List, Optional, Tuple
|
65
|
+
from typing import Callable, List, Optional, TextIO, Tuple, Union
|
66
66
|
import yaml
|
67
67
|
from dcicutils.captured_output import captured_output, uncaptured_output
|
68
|
+
from dcicutils.command_utils import yes_or_no
|
68
69
|
from dcicutils.misc_utils import get_error_message, is_uuid, PRINT
|
69
70
|
from dcicutils.portal_utils import Portal
|
70
71
|
|
@@ -78,11 +79,15 @@ _SCHEMAS_IGNORE_PROPERTIES = [
|
|
78
79
|
"schema_version"
|
79
80
|
]
|
80
81
|
|
82
|
+
_output_file: TextIO = None
|
83
|
+
|
81
84
|
|
82
85
|
def main():
|
83
86
|
|
87
|
+
global _output_file
|
88
|
+
|
84
89
|
parser = argparse.ArgumentParser(description="View Portal object.")
|
85
|
-
parser.add_argument("uuid", type=str,
|
90
|
+
parser.add_argument("uuid", nargs="?", type=str,
|
86
91
|
help=f"The uuid (or path) of the object to fetch and view. ")
|
87
92
|
parser.add_argument("--ini", type=str, required=False, default=None,
|
88
93
|
help=f"Name of the application .ini file.")
|
@@ -97,11 +102,9 @@ def main():
|
|
97
102
|
parser.add_argument("--all", action="store_true", required=False, default=False,
|
98
103
|
help="Include all properties for schema usage.")
|
99
104
|
parser.add_argument("--raw", action="store_true", required=False, default=False, help="Raw output.")
|
105
|
+
parser.add_argument("--inserts", action="store_true", required=False, default=False,
|
106
|
+
help="Format output for subsequent inserts.")
|
100
107
|
parser.add_argument("--tree", action="store_true", required=False, default=False, help="Tree output for schemas.")
|
101
|
-
parser.add_argument("--post", type=str, required=False, default=None,
|
102
|
-
help="POST data of the main arg type with data from file specified with this option.")
|
103
|
-
parser.add_argument("--patch", type=str, required=False, default=None,
|
104
|
-
help="PATCH data of the main arg type with data from file specified with this option.")
|
105
108
|
parser.add_argument("--database", action="store_true", required=False, default=False,
|
106
109
|
help="Read from database output.")
|
107
110
|
parser.add_argument("--bool", action="store_true", required=False,
|
@@ -109,6 +112,7 @@ def main():
|
|
109
112
|
parser.add_argument("--yaml", action="store_true", required=False, default=False, help="YAML output.")
|
110
113
|
parser.add_argument("--copy", "-c", action="store_true", required=False, default=False,
|
111
114
|
help="Copy object data to clipboard.")
|
115
|
+
parser.add_argument("--output", required=False, help="Output file.", type=str)
|
112
116
|
parser.add_argument("--indent", required=False, default=False, help="Indent output.", type=int)
|
113
117
|
parser.add_argument("--details", action="store_true", required=False, default=False, help="Detailed output.")
|
114
118
|
parser.add_argument("--more-details", action="store_true", required=False, default=False,
|
@@ -123,54 +127,57 @@ def main():
|
|
123
127
|
portal = _create_portal(ini=args.ini, env=args.env or os.environ.get("SMAHT_ENV"),
|
124
128
|
server=args.server, app=args.app, verbose=args.verbose, debug=args.debug)
|
125
129
|
|
126
|
-
if
|
130
|
+
if not args.uuid:
|
131
|
+
_print("UUID or schema or path required.")
|
132
|
+
_exit(1)
|
133
|
+
|
134
|
+
if args.output:
|
135
|
+
if os.path.exists(args.output):
|
136
|
+
if os.path.isdir(args.output):
|
137
|
+
_print(f"Specified output file already exists as a directory: {args.output}")
|
138
|
+
_exit(1)
|
139
|
+
elif os.path.isfile(args.output):
|
140
|
+
_print(f"Specified output file already exists: {args.output}")
|
141
|
+
if not yes_or_no(f"Do you want to overwrite this file?"):
|
142
|
+
_exit(0)
|
143
|
+
_output_file = io.open(args.output, "w")
|
144
|
+
|
145
|
+
if args.uuid and ((args.uuid.lower() == "schemas") or (args.uuid.lower() == "schema")):
|
127
146
|
_print_all_schema_names(portal=portal, details=args.details,
|
128
147
|
more_details=args.more_details, all=args.all,
|
129
148
|
tree=args.tree, raw=args.raw, raw_yaml=args.yaml)
|
130
149
|
return
|
131
|
-
elif args.uuid.lower() == "info":
|
150
|
+
elif args.uuid and (args.uuid.lower() == "info"):
|
132
151
|
if consortia := portal.get_metadata("/consortia?limit=1000"):
|
133
|
-
|
152
|
+
_print_output("Known Consortia:")
|
134
153
|
consortia = sorted(consortia.get("@graph", []), key=lambda key: key.get("identifier"))
|
135
154
|
for consortium in consortia:
|
136
155
|
if ((consortium_name := consortium.get("identifier")) and
|
137
156
|
(consortium_uuid := consortium.get("uuid"))): # noqa
|
138
|
-
|
157
|
+
_print_output(f"- {consortium_name}: {consortium_uuid}")
|
139
158
|
if submission_centers := portal.get_metadata("/submission-centers?limit=1000"):
|
140
|
-
|
159
|
+
_print_output("Known Submission Centers:")
|
141
160
|
submission_centers = sorted(submission_centers.get("@graph", []), key=lambda key: key.get("identifier"))
|
142
161
|
for submission_center in submission_centers:
|
143
162
|
if ((submission_center_name := submission_center.get("identifier")) and
|
144
163
|
(submission_center_uuid := submission_center.get("uuid"))): # noqa
|
145
|
-
|
164
|
+
_print_output(f"- {submission_center_name}: {submission_center_uuid}")
|
146
165
|
try:
|
147
166
|
if file_formats := portal.get_metadata("/file-formats?limit=1000"):
|
148
|
-
|
167
|
+
_print_output("Known File Formats:")
|
149
168
|
file_formats = sorted(file_formats.get("@graph", []), key=lambda key: key.get("identifier"))
|
150
169
|
for file_format in file_formats:
|
151
170
|
if ((file_format_name := file_format.get("identifier")) and
|
152
171
|
(file_format_uuid := file_format.get("uuid"))): # noqa
|
153
|
-
|
172
|
+
_print_output(f"- {file_format_name}: {file_format_uuid}")
|
154
173
|
except Exception:
|
155
|
-
|
174
|
+
_print_output("Known File Formats: None")
|
156
175
|
return
|
157
176
|
|
158
177
|
if _is_maybe_schema_name(args.uuid):
|
159
178
|
args.schema = True
|
160
179
|
|
161
180
|
if args.schema:
|
162
|
-
if args.post:
|
163
|
-
if post_data := _read_json_from_file(args.post):
|
164
|
-
if args.verbose:
|
165
|
-
_print(f"POSTing data from file ({args.post}) as type: {args.uuid}")
|
166
|
-
if isinstance(post_data, dict):
|
167
|
-
post_data = [post_data]
|
168
|
-
elif not isinstance(post_data, list):
|
169
|
-
_print(f"POST data neither list nor dictionary: {args.post}")
|
170
|
-
for item in post_data:
|
171
|
-
portal.post_metadata(args.uuid, item)
|
172
|
-
if args.verbose:
|
173
|
-
_print(f"Done POSTing data from file ({args.post}) as type: {args.uuid}")
|
174
181
|
schema, schema_name = _get_schema(portal, args.uuid)
|
175
182
|
if schema:
|
176
183
|
if args.copy:
|
@@ -178,49 +185,33 @@ def main():
|
|
178
185
|
if not args.raw:
|
179
186
|
if parent_schema_name := _get_parent_schema_name(schema):
|
180
187
|
if schema.get("isAbstract") is True:
|
181
|
-
|
188
|
+
_print_output(f"{schema_name} | parent: {parent_schema_name} | abstract")
|
182
189
|
else:
|
183
|
-
|
190
|
+
_print_output(f"{schema_name} | parent: {parent_schema_name}")
|
184
191
|
else:
|
185
|
-
|
192
|
+
_print_output(schema_name)
|
186
193
|
_print_schema(schema, details=args.details, more_details=args.details,
|
187
194
|
all=args.all, raw=args.raw, raw_yaml=args.yaml)
|
188
195
|
return
|
189
|
-
elif args.patch:
|
190
|
-
if patch_data := _read_json_from_file(args.patch):
|
191
|
-
if args.verbose:
|
192
|
-
_print(f"PATCHing data from file ({args.patch}) for object: {args.uuid}")
|
193
|
-
if isinstance(patch_data, dict):
|
194
|
-
patch_data = [patch_data]
|
195
|
-
elif not isinstance(patch_data, list):
|
196
|
-
_print(f"PATCH data neither list nor dictionary: {args.patch}")
|
197
|
-
for item in patch_data:
|
198
|
-
portal.patch_metadata(args.uuid, item)
|
199
|
-
if args.verbose:
|
200
|
-
_print(f"Done PATCHing data from file ({args.patch}) as type: {args.uuid}")
|
201
|
-
return
|
202
|
-
else:
|
203
|
-
_print(f"No PATCH data found in file: {args.patch}")
|
204
|
-
sys.exit(1)
|
205
196
|
|
206
|
-
data = _get_portal_object(portal=portal, uuid=args.uuid, raw=args.raw,
|
197
|
+
data = _get_portal_object(portal=portal, uuid=args.uuid, raw=args.raw, inserts=args.inserts,
|
207
198
|
database=args.database, check=args.bool, verbose=args.verbose)
|
208
199
|
if args.bool:
|
209
200
|
if data:
|
210
201
|
_print(f"{args.uuid}: found")
|
211
|
-
|
202
|
+
_exit(0)
|
212
203
|
else:
|
213
204
|
_print(f"{args.uuid}: not found")
|
214
|
-
|
205
|
+
_exit(1)
|
215
206
|
if args.copy:
|
216
207
|
pyperclip.copy(json.dumps(data, indent=4))
|
217
208
|
if args.yaml:
|
218
|
-
|
209
|
+
_print_output(yaml.dump(data))
|
219
210
|
else:
|
220
211
|
if args.indent > 0:
|
221
|
-
|
212
|
+
_print_output(_format_json_with_indent(data, indent=args.indent))
|
222
213
|
else:
|
223
|
-
|
214
|
+
_print_output(json.dumps(data, default=str, indent=4))
|
224
215
|
|
225
216
|
|
226
217
|
def _format_json_with_indent(value: dict, indent: int = 0) -> Optional[str]:
|
@@ -254,7 +245,7 @@ def _create_portal(ini: str, env: Optional[str] = None,
|
|
254
245
|
|
255
246
|
|
256
247
|
def _get_portal_object(portal: Portal, uuid: str,
|
257
|
-
raw: bool = False, database: bool = False,
|
248
|
+
raw: bool = False, inserts: bool = False, database: bool = False,
|
258
249
|
check: bool = False, verbose: bool = False) -> dict:
|
259
250
|
response = None
|
260
251
|
try:
|
@@ -262,7 +253,7 @@ def _get_portal_object(portal: Portal, uuid: str,
|
|
262
253
|
path = f"/{uuid}"
|
263
254
|
else:
|
264
255
|
path = uuid
|
265
|
-
response = portal.get(path, raw=raw, database=database)
|
256
|
+
response = portal.get(path, raw=raw or inserts, database=database)
|
266
257
|
except Exception as e:
|
267
258
|
if "404" in str(e) and "not found" in str(e).lower():
|
268
259
|
_print(f"Portal object not found at {portal.server}: {uuid}")
|
@@ -278,7 +269,21 @@ def _get_portal_object(portal: Portal, uuid: str,
|
|
278
269
|
if not response.json:
|
279
270
|
_exit(f"Invalid JSON getting Portal object: {uuid}")
|
280
271
|
response = response.json()
|
281
|
-
if
|
272
|
+
if inserts:
|
273
|
+
# Format results as suitable for inserts (e.g. via update-portal-object).
|
274
|
+
response.pop("schema_version", None)
|
275
|
+
if ((isinstance(results := response.get("@graph"), list) and results) and
|
276
|
+
(isinstance(results_type := response.get("@type"), list) and results_type) and
|
277
|
+
(isinstance(results_type := results_type[0], str) and results_type.endswith("SearchResults")) and
|
278
|
+
(results_type := results_type[0:-len("SearchResults")])): # noqa
|
279
|
+
for result in results:
|
280
|
+
result.pop("schema_version", None)
|
281
|
+
response = {f"{results_type}": results}
|
282
|
+
# Get the result as non-raw so we can get its type.
|
283
|
+
elif ((response_cooked := portal.get(path, database=database)) and
|
284
|
+
(isinstance(response_type := response_cooked.json().get("@type"), list) and response_type)):
|
285
|
+
response = {f"{response_type[0]}": [response]}
|
286
|
+
elif raw:
|
282
287
|
response.pop("schema_version", None)
|
283
288
|
return response
|
284
289
|
|
@@ -292,7 +297,7 @@ def _get_schema(portal: Portal, name: str) -> Tuple[Optional[dict], Optional[str
|
|
292
297
|
if portal and name and (name := name.replace("_", "").replace("-", "").strip().lower()):
|
293
298
|
if schemas := _get_schemas(portal):
|
294
299
|
for schema_name in schemas:
|
295
|
-
if schema_name.replace("_", "").replace("-", "").strip().lower() == name:
|
300
|
+
if schema_name.replace("_", "").replace("-", "").strip().lower() == name.lower():
|
296
301
|
return schemas[schema_name], schema_name
|
297
302
|
return None, None
|
298
303
|
|
@@ -303,13 +308,37 @@ def _is_maybe_schema_name(value: str) -> bool:
|
|
303
308
|
return False
|
304
309
|
|
305
310
|
|
311
|
+
def _is_schema_name(portal: Portal, value: str) -> bool:
|
312
|
+
try:
|
313
|
+
return _get_schema(portal, value)[0] is not None
|
314
|
+
except Exception:
|
315
|
+
return False
|
316
|
+
|
317
|
+
|
318
|
+
def _is_schema_named_json_file_name(portal: Portal, value: str) -> bool:
|
319
|
+
try:
|
320
|
+
return value.endswith(".json") and _is_schema_name(portal, os.path.basename(value[:-5]))
|
321
|
+
except Exception:
|
322
|
+
return False
|
323
|
+
|
324
|
+
|
325
|
+
def _get_schema_name_from_schema_named_json_file_name(portal: Portal, value: str) -> Optional[str]:
|
326
|
+
try:
|
327
|
+
if not value.endswith(".json"):
|
328
|
+
return None
|
329
|
+
_, schema_name = _get_schema(portal, os.path.basename(value[:-5]))
|
330
|
+
return schema_name
|
331
|
+
except Exception:
|
332
|
+
return False
|
333
|
+
|
334
|
+
|
306
335
|
def _print_schema(schema: dict, details: bool = False, more_details: bool = False, all: bool = False,
|
307
336
|
raw: bool = False, raw_yaml: bool = False) -> None:
|
308
337
|
if raw:
|
309
338
|
if raw_yaml:
|
310
|
-
|
339
|
+
_print_output(yaml.dump(schema))
|
311
340
|
else:
|
312
|
-
|
341
|
+
_print_output(json.dumps(schema, indent=4))
|
313
342
|
return
|
314
343
|
_print_schema_info(schema, details=details, more_details=more_details, all=all)
|
315
344
|
|
@@ -322,37 +351,37 @@ def _print_schema_info(schema: dict, level: int = 0,
|
|
322
351
|
identifying_properties = schema.get("identifyingProperties")
|
323
352
|
if level == 0:
|
324
353
|
if required_properties := schema.get("required"):
|
325
|
-
|
354
|
+
_print_output("- required properties:")
|
326
355
|
for required_property in sorted(list(set(required_properties))):
|
327
356
|
if not all and required_property in _SCHEMAS_IGNORE_PROPERTIES:
|
328
357
|
continue
|
329
358
|
if property_type := (info := schema.get("properties", {}).get(required_property, {})).get("type"):
|
330
359
|
if property_type == "array" and (array_type := info.get("items", {}).get("type")):
|
331
|
-
|
360
|
+
_print_output(f" - {required_property}: {property_type} of {array_type}")
|
332
361
|
else:
|
333
|
-
|
362
|
+
_print_output(f" - {required_property}: {property_type}")
|
334
363
|
else:
|
335
|
-
|
364
|
+
_print_output(f" - {required_property}")
|
336
365
|
if isinstance(any_of := schema.get("anyOf"), list):
|
337
366
|
if ((any_of == [{"required": ["submission_centers"]}, {"required": ["consortia"]}]) or
|
338
367
|
(any_of == [{"required": ["consortia"]}, {"required": ["submission_centers"]}])): # noqa
|
339
368
|
# Very very special case.
|
340
|
-
|
341
|
-
|
342
|
-
|
369
|
+
_print_output(f" - at least one of:")
|
370
|
+
_print_output(f" - consortia: array of string")
|
371
|
+
_print_output(f" - submission_centers: array of string")
|
343
372
|
required = required_properties
|
344
373
|
if identifying_properties := schema.get("identifyingProperties"):
|
345
|
-
|
374
|
+
_print_output("- identifying properties:")
|
346
375
|
for identifying_property in sorted(list(set(identifying_properties))):
|
347
376
|
if not all and identifying_property in _SCHEMAS_IGNORE_PROPERTIES:
|
348
377
|
continue
|
349
378
|
if property_type := (info := schema.get("properties", {}).get(identifying_property, {})).get("type"):
|
350
379
|
if property_type == "array" and (array_type := info.get("items", {}).get("type")):
|
351
|
-
|
380
|
+
_print_output(f" - {identifying_property}: {property_type} of {array_type}")
|
352
381
|
else:
|
353
|
-
|
382
|
+
_print_output(f" - {identifying_property}: {property_type}")
|
354
383
|
else:
|
355
|
-
|
384
|
+
_print_output(f" - {identifying_property}")
|
356
385
|
if properties := schema.get("properties"):
|
357
386
|
reference_properties = []
|
358
387
|
for property_name in properties:
|
@@ -362,16 +391,16 @@ def _print_schema_info(schema: dict, level: int = 0,
|
|
362
391
|
if link_to := property.get("linkTo"):
|
363
392
|
reference_properties.append({"name": property_name, "ref": link_to})
|
364
393
|
if reference_properties:
|
365
|
-
|
394
|
+
_print_output("- reference properties:")
|
366
395
|
for reference_property in sorted(reference_properties, key=lambda key: key["name"]):
|
367
|
-
|
396
|
+
_print_output(f" - {reference_property['name']}: {reference_property['ref']}")
|
368
397
|
if schema.get("additionalProperties") is True:
|
369
|
-
|
398
|
+
_print_output(f" - additional properties are allowed")
|
370
399
|
if not more_details:
|
371
400
|
return
|
372
401
|
if properties := (schema.get("properties") if level == 0 else schema):
|
373
402
|
if level == 0:
|
374
|
-
|
403
|
+
_print_output("- properties:")
|
375
404
|
for property_name in sorted(properties):
|
376
405
|
if not all and property_name in _SCHEMAS_IGNORE_PROPERTIES:
|
377
406
|
continue
|
@@ -392,7 +421,7 @@ def _print_schema_info(schema: dict, level: int = 0,
|
|
392
421
|
property_type = "open ended object"
|
393
422
|
if property.get("calculatedProperty"):
|
394
423
|
suffix += f" | calculated"
|
395
|
-
|
424
|
+
_print_output(f"{spaces}- {property_name}: {property_type}{suffix}")
|
396
425
|
_print_schema_info(object_properties, level=level + 1,
|
397
426
|
details=details, more_details=more_details, all=all,
|
398
427
|
required=property.get("required"))
|
@@ -416,28 +445,28 @@ def _print_schema_info(schema: dict, level: int = 0,
|
|
416
445
|
if property_type := property_items.get("type"):
|
417
446
|
if property_type == "object":
|
418
447
|
suffix = ""
|
419
|
-
|
448
|
+
_print_output(f"{spaces}- {property_name}: array of object{suffix}")
|
420
449
|
_print_schema_info(property_items.get("properties"), level=level + 1,
|
421
450
|
details=details, more_details=more_details, all=all,
|
422
451
|
required=property_items.get("required"))
|
423
452
|
elif property_type == "array":
|
424
453
|
# This (array-of-array) never happens to occur at this time (February 2024).
|
425
|
-
|
454
|
+
_print_output(f"{spaces}- {property_name}: array of array{suffix}")
|
426
455
|
else:
|
427
|
-
|
456
|
+
_print_output(f"{spaces}- {property_name}: array of {property_type}{suffix}")
|
428
457
|
else:
|
429
|
-
|
458
|
+
_print_output(f"{spaces}- {property_name}: array{suffix}")
|
430
459
|
else:
|
431
|
-
|
460
|
+
_print_output(f"{spaces}- {property_name}: array{suffix}")
|
432
461
|
if enumeration:
|
433
462
|
nenums = 0
|
434
463
|
maxenums = 15
|
435
464
|
for enum in sorted(enumeration):
|
436
465
|
if (nenums := nenums + 1) >= maxenums:
|
437
466
|
if (remaining := len(enumeration) - nenums) > 0:
|
438
|
-
|
467
|
+
_print_output(f"{spaces} - [{remaining} more ...]")
|
439
468
|
break
|
440
|
-
|
469
|
+
_print_output(f"{spaces} - {enum}")
|
441
470
|
else:
|
442
471
|
if isinstance(property_type, list):
|
443
472
|
property_type = " or ".join(sorted(property_type))
|
@@ -479,18 +508,18 @@ def _print_schema_info(schema: dict, level: int = 0,
|
|
479
508
|
suffix += f" | max length: {max_length}"
|
480
509
|
if (min_length := property.get("minLength")) is not None:
|
481
510
|
suffix += f" | min length: {min_length}"
|
482
|
-
|
511
|
+
_print_output(f"{spaces}- {property_name}: {property_type}{suffix}")
|
483
512
|
if enumeration:
|
484
513
|
nenums = 0
|
485
514
|
maxenums = 15
|
486
515
|
for enum in sorted(enumeration):
|
487
516
|
if (nenums := nenums + 1) >= maxenums:
|
488
517
|
if (remaining := len(enumeration) - nenums) > 0:
|
489
|
-
|
518
|
+
_print_output(f"{spaces} - [{remaining} more ...]")
|
490
519
|
break
|
491
|
-
|
520
|
+
_print_output(f"{spaces} - {enum}")
|
492
521
|
else:
|
493
|
-
|
522
|
+
_print_output(f"{spaces}- {property_name}")
|
494
523
|
|
495
524
|
|
496
525
|
def _print_all_schema_names(portal: Portal,
|
@@ -501,9 +530,9 @@ def _print_all_schema_names(portal: Portal,
|
|
501
530
|
|
502
531
|
if raw:
|
503
532
|
if raw_yaml:
|
504
|
-
|
533
|
+
_print_output(yaml.dump(schemas))
|
505
534
|
else:
|
506
|
-
|
535
|
+
_print_output(json.dumps(schemas, indent=4))
|
507
536
|
return
|
508
537
|
|
509
538
|
if tree:
|
@@ -513,14 +542,14 @@ def _print_all_schema_names(portal: Portal,
|
|
513
542
|
for schema_name in sorted(schemas.keys()):
|
514
543
|
if parent_schema_name := _get_parent_schema_name(schemas[schema_name]):
|
515
544
|
if schemas[schema_name].get("isAbstract") is True:
|
516
|
-
|
545
|
+
_print_output(f"{schema_name} | parent: {parent_schema_name} | abstract")
|
517
546
|
else:
|
518
|
-
|
547
|
+
_print_output(f"{schema_name} | parent: {parent_schema_name}")
|
519
548
|
else:
|
520
549
|
if schemas[schema_name].get("isAbstract") is True:
|
521
|
-
|
550
|
+
_print_output(f"{schema_name} | abstract")
|
522
551
|
else:
|
523
|
-
|
552
|
+
_print_output(schema_name)
|
524
553
|
if details:
|
525
554
|
_print_schema(schemas[schema_name], details=details, more_details=more_details, all=all)
|
526
555
|
|
@@ -559,8 +588,7 @@ def _print_schemas_tree(schemas: dict) -> None:
|
|
559
588
|
def _print_tree(root_name: Optional[str],
|
560
589
|
children_of: Callable,
|
561
590
|
has_children: Optional[Callable] = None,
|
562
|
-
name_of: Optional[Callable] = None
|
563
|
-
print: Callable = print) -> None:
|
591
|
+
name_of: Optional[Callable] = None) -> None:
|
564
592
|
"""
|
565
593
|
Recursively prints as a tree structure the given root name and any of its
|
566
594
|
children (again, recursively) as specified by the given children_of callable;
|
@@ -589,26 +617,26 @@ def _print_tree(root_name: Optional[str],
|
|
589
617
|
if has_children(path):
|
590
618
|
extension = branch if pointer == tee else space
|
591
619
|
yield from tree_generator(path, prefix=prefix+extension)
|
592
|
-
|
620
|
+
_print_output(first + ((name_of(root_name) if callable(name_of) else root_name) or "root"))
|
593
621
|
for line in tree_generator(root_name, prefix=" "):
|
594
|
-
|
622
|
+
_print_output(line)
|
595
623
|
|
596
624
|
|
597
625
|
def _read_json_from_file(file: str) -> Optional[dict]:
|
598
626
|
if not os.path.exists(file):
|
599
627
|
_print(f"Cannot find file: {file}")
|
600
|
-
|
628
|
+
_exit(1)
|
601
629
|
try:
|
602
630
|
with io.open(file, "r") as f:
|
603
631
|
try:
|
604
632
|
return json.load(f)
|
605
633
|
except Exception:
|
606
634
|
_print(f"Cannot parse JSON in file: {file}")
|
607
|
-
|
635
|
+
_exit(1)
|
608
636
|
except Exception as e:
|
609
|
-
|
637
|
+
_print(e)
|
610
638
|
_print(f"Cannot open file: {file}")
|
611
|
-
|
639
|
+
_exit(1)
|
612
640
|
|
613
641
|
|
614
642
|
def _print(*args, **kwargs):
|
@@ -617,10 +645,26 @@ def _print(*args, **kwargs):
|
|
617
645
|
sys.stdout.flush()
|
618
646
|
|
619
647
|
|
620
|
-
def
|
621
|
-
|
648
|
+
def _print_output(value: str):
|
649
|
+
global _output_file
|
650
|
+
if _output_file:
|
651
|
+
_output_file.write(value)
|
652
|
+
_output_file.write("\n")
|
653
|
+
else:
|
654
|
+
with uncaptured_output():
|
655
|
+
PRINT(value)
|
656
|
+
sys.stdout.flush()
|
657
|
+
|
658
|
+
|
659
|
+
def _exit(message: Optional[Union[str, int]] = None, status: Optional[int] = None) -> None:
|
660
|
+
global _output_file
|
661
|
+
if isinstance(message, str):
|
622
662
|
_print(f"ERROR: {message}")
|
623
|
-
|
663
|
+
elif isinstance(message, int) and not isinstance(status, int):
|
664
|
+
status = message
|
665
|
+
if _output_file:
|
666
|
+
_output_file.close()
|
667
|
+
sys.exit(status if isinstance(status, int) else (0 if status is None else 1))
|
624
668
|
|
625
669
|
|
626
670
|
if __name__ == "__main__":
|
@@ -60,7 +60,8 @@ dcicutils/s3_utils.py,sha256=LauLFQGvZLfpBJ81tYMikjLd3SJRz2R_FrL1n4xSlyI,28868
|
|
60
60
|
dcicutils/schema_utils.py,sha256=GmRm-XqZKJ6qine16SQF1txcby9WougDav_sYmKNs9E,12400
|
61
61
|
dcicutils/scripts/publish_to_pypi.py,sha256=sMd4WASQGlxlh7uLrt2eGkFRXYgONVmvIg8mClMS5RQ,13903
|
62
62
|
dcicutils/scripts/run_license_checker.py,sha256=z2keYnRDZsHQbTeo1XORAXSXNJK5axVzL5LjiNqZ7jE,4184
|
63
|
-
dcicutils/scripts/
|
63
|
+
dcicutils/scripts/update_portal_object.py,sha256=p9pFkoA3ZZOWvh-GMDpgR8qOfx_jQppOVNOjsuZndAU,18810
|
64
|
+
dcicutils/scripts/view_portal_object.py,sha256=ddZdOuSsYD-4VlsWth0EBTD_2TycQ4Ktgh-IdzKHweM,31490
|
64
65
|
dcicutils/secrets_utils.py,sha256=8dppXAsiHhJzI6NmOcvJV5ldvKkQZzh3Fl-cb8Wm7MI,19745
|
65
66
|
dcicutils/sheet_utils.py,sha256=VlmzteONW5VF_Q4vo0yA5vesz1ViUah1MZ_yA1rwZ0M,33629
|
66
67
|
dcicutils/snapshot_utils.py,sha256=YDeI3vD-MhAtHwKDzfEm2q-n3l-da2yRpRR3xp0Ah1M,23021
|
@@ -74,8 +75,8 @@ dcicutils/trace_utils.py,sha256=g8kwV4ebEy5kXW6oOrEAUsurBcCROvwtZqz9fczsGRE,1769
|
|
74
75
|
dcicutils/validation_utils.py,sha256=cMZIU2cY98FYtzK52z5WUYck7urH6JcqOuz9jkXpqzg,14797
|
75
76
|
dcicutils/variant_utils.py,sha256=2H9azNx3xAj-MySg-uZ2SFqbWs4kZvf61JnK6b-h4Qw,4343
|
76
77
|
dcicutils/zip_utils.py,sha256=_Y9EmL3D2dUZhxucxHvrtmmlbZmK4FpSsHEb7rGSJLU,3265
|
77
|
-
dcicutils-8.13.
|
78
|
-
dcicutils-8.13.
|
79
|
-
dcicutils-8.13.
|
80
|
-
dcicutils-8.13.
|
81
|
-
dcicutils-8.13.
|
78
|
+
dcicutils-8.13.3.dist-info/LICENSE.txt,sha256=qnwSmfnEWMl5l78VPDEzAmEbLVrRqQvfUQiHT0ehrOo,1102
|
79
|
+
dcicutils-8.13.3.dist-info/METADATA,sha256=B583S5ausZLy7zA73GFhcTCgX3KJVnhy008WzM0H6uk,3442
|
80
|
+
dcicutils-8.13.3.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
|
81
|
+
dcicutils-8.13.3.dist-info/entry_points.txt,sha256=W6kEWdUJk9tQ4myAgpehPdebcwvCAZ7UgB-wyPgDUMg,335
|
82
|
+
dcicutils-8.13.3.dist-info/RECORD,,
|
@@ -2,5 +2,6 @@
|
|
2
2
|
publish-to-pypi=dcicutils.scripts.publish_to_pypi:main
|
3
3
|
run-license-checker=dcicutils.scripts.run_license_checker:main
|
4
4
|
show-contributors=dcicutils.contribution_scripts:show_contributors_main
|
5
|
+
update-portal-object=dcicutils.scripts.update_portal_object:main
|
5
6
|
view-portal-object=dcicutils.scripts.view_portal_object:main
|
6
7
|
|
File without changes
|
File without changes
|