61
61
"null" ,
62
62
]
63
63
64
+ _DTS_INSTRUCTIONS_KEY = "instructions"
65
+ _DTS_INSTRUCTIONS_DATATYPE_KEY = "data_type"
66
+ _DTS_INSTRUCTIONS_PARAMETERS_KEY = "parameters"
67
+ _DTS_INSTRUCTIONS_REQUIRED_KEYS = [_DTS_INSTRUCTIONS_DATATYPE_KEY , _DTS_INSTRUCTIONS_PARAMETERS_KEY ]
68
+ _DTS_INSTRUCTIONS_PROTOCOL_KEY = "protocol"
69
+ _DTS_INSTRUCTIONS_PROTOCOL = "KBase narrative import"
70
+ _DTS_INSTRUCTIONS_OBJECTS_KEY = "objects"
71
+
64
72
65
73
class _ParseException (Exception ):
66
74
pass
@@ -361,10 +369,18 @@ def parse_dts_manifest(path: Path, dts_manifest_schema: dict) -> ParseResults:
361
369
err_str = err .message
362
370
err_path = err .absolute_path
363
371
if err_path :
364
- if isinstance (err_path [- 1 ], int ):
365
- err_path [- 1 ] = f"item { err_path [- 1 ]} "
366
- err_str += f" at { '/' .join (err_path )} "
372
+ # paths can look like, say, ["instructions", "objects", 0, "data_type"]
373
+ # convert that '0' to "item 0" to be slightly more readable to users.
374
+ # kind of a mouthful below, but does that conversion in place
375
+ err_path = [f"item { elem } " if isinstance (elem , int ) else elem for elem in err_path ]
376
+ prep = "for"
377
+ if len (err_path ) > 1 :
378
+ prep = "at"
379
+ err_str += f" { prep } { '/' .join (err_path )} "
367
380
errors .append (Error (ErrorType .PARSE_FAIL , err_str , spcsrc ))
381
+ if not errors :
382
+ results = _process_dts_manifest (manifest_json , spcsrc )
383
+
368
384
except jsonschema .exceptions .SchemaError :
369
385
return _error (Error (ErrorType .OTHER , "Manifest schema is invalid" , spcsrc ))
370
386
except json .JSONDecodeError :
@@ -373,9 +389,44 @@ def parse_dts_manifest(path: Path, dts_manifest_schema: dict) -> ParseResults:
373
389
return _error (Error (ErrorType .FILE_NOT_FOUND , source_1 = spcsrc ))
374
390
except IsADirectoryError :
375
391
return _error (Error (ErrorType .PARSE_FAIL , "The given path is a directory" , spcsrc ))
392
+ except _ParseException as err :
393
+ return _error (err .args [0 ])
376
394
if errors :
377
395
return ParseResults (errors = tuple (errors ))
378
396
elif results :
379
397
return ParseResults (frozendict (results ))
380
398
else :
381
399
return _error (Error (ErrorType .PARSE_FAIL , "No import specification data in file" , spcsrc ))
400
+
401
+
402
+ def _process_dts_manifest (
403
+ manifest : dict [str , Any ], spcsrc : SpecificationSource
404
+ ) -> Tuple [dict [str , ParseResult ]]:
405
+ """Parse the DTS manifest file and return the results and a list of errors if applicable.
406
+
407
+ Results are returned as a dictionary where keys are data types, and values are ParseResults for that data type.
408
+ This assumes that the manifest has the correct structure, i.e. is validated via jsonschema.
409
+ Will raise KeyErrors otherwise.
410
+ """
411
+ results = {}
412
+ instructions = manifest [_DTS_INSTRUCTIONS_KEY ]
413
+ # Make sure the protocol value matches.
414
+ if instructions [_DTS_INSTRUCTIONS_PROTOCOL_KEY ] != _DTS_INSTRUCTIONS_PROTOCOL :
415
+ raise _ParseException (
416
+ Error (
417
+ ErrorType .PARSE_FAIL ,
418
+ f"The instructions protocol must be '{ _DTS_INSTRUCTIONS_PROTOCOL } '" ,
419
+ spcsrc ,
420
+ )
421
+ )
422
+ for resource_obj in instructions [_DTS_INSTRUCTIONS_OBJECTS_KEY ]:
423
+ datatype = resource_obj [_DTS_INSTRUCTIONS_DATATYPE_KEY ]
424
+ parameters = frozendict (resource_obj [_DTS_INSTRUCTIONS_PARAMETERS_KEY ])
425
+ if datatype not in results :
426
+ results [datatype ] = []
427
+ results [datatype ].append (parameters )
428
+ # Package results as a dict of {datatype: ParseResult}
429
+ parsed_result = {
430
+ source : ParseResult (spcsrc , tuple (parsed )) for source , parsed in results .items ()
431
+ }
432
+ return parsed_result
0 commit comments