Skip to content

Commit 1507b21

Browse files
authored
refactor(xmlupload): handling of upload errors (DEV-1505) (#250)
1 parent d4feb68 commit 1507b21

1 file changed

Lines changed: 100 additions & 129 deletions

File tree

‎knora/dsplib/utils/xml_upload.py‎

Lines changed: 100 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import json
66
import os
77
import re
8+
import sys
89
import uuid
910
from datetime import datetime
1011
from pathlib import Path
@@ -304,6 +305,14 @@ def xml_upload(input_file: str, server: str, user: str, password: str, imgdir: s
304305
f"{err.message}")
305306
quit(0)
306307

308+
if sys.platform.startswith("darwin") or sys.platform.startswith("linux"):
309+
save_location = f"{os.path.expanduser('~')}/.dsp-tools"
310+
elif sys.platform.startswith("win"):
311+
save_location = "."
312+
else:
313+
save_location = "."
314+
# TODO: use the home directory provided by Pathlib
315+
307316
# Connect to the DaSCH Service Platform API and get the project context
308317
con = Connection(server)
309318
try_network_action(failure_msg="Unable to login to DSP server", action=lambda: con.login(user, password))
@@ -351,42 +360,68 @@ def xml_upload(input_file: str, server: str, user: str, password: str, imgdir: s
351360
id2iri_mapping, failed_uploads = _upload_resources(resources, imgdir, sipi_server, permissions_lookup,
352361
resclass_name_2_type, id2iri_mapping, con, failed_uploads)
353362
except BaseException as err:
354-
_handle_upload_error(err, input_file, id2iri_mapping, failed_uploads, stashed_xml_texts, stashed_resptr_props)
363+
_handle_upload_error(
364+
err=err,
365+
id2iri_mapping=id2iri_mapping,
366+
failed_uploads=failed_uploads,
367+
stashed_xml_texts=stashed_xml_texts,
368+
stashed_resptr_props=stashed_resptr_props,
369+
proj_shortcode=shortcode,
370+
onto_name=default_ontology,
371+
server=server,
372+
save_location=save_location
373+
)
355374

356375
# update the resources with the stashed XML texts
357-
nonapplied_xml_texts = {}
358376
if len(stashed_xml_texts) > 0:
359377
try:
360378
nonapplied_xml_texts = _upload_stashed_xml_texts(verbose, id2iri_mapping, con, stashed_xml_texts)
361-
nonapplied_xml_texts = _purge_stashed_xml_texts(nonapplied_xml_texts, id2iri_mapping)
379+
if len(nonapplied_xml_texts) > 0:
380+
raise BaseError(f"Error while trying to upload the stashed xml texts")
362381
except BaseException as err:
363-
_handle_upload_error(err, input_file, id2iri_mapping, failed_uploads, stashed_xml_texts, stashed_resptr_props)
382+
_handle_upload_error(
383+
err=err,
384+
id2iri_mapping=id2iri_mapping,
385+
failed_uploads=failed_uploads,
386+
stashed_xml_texts=stashed_xml_texts,
387+
stashed_resptr_props=stashed_resptr_props,
388+
proj_shortcode=shortcode,
389+
onto_name=default_ontology,
390+
server=server,
391+
save_location=save_location
392+
)
364393

365394
# update the resources with the stashed resptrs
366-
nonapplied_resptr_props = {}
367395
if len(stashed_resptr_props) > 0:
368396
try:
369397
nonapplied_resptr_props = _upload_stashed_resptr_props(verbose, id2iri_mapping, con, stashed_resptr_props)
370-
nonapplied_resptr_props = _purge_stashed_resptr_props(stashed_resptr_props, id2iri_mapping)
398+
if len(nonapplied_resptr_props) > 0:
399+
raise BaseError(f"Error while trying to upload the stashed resptr props")
371400
except BaseException as err:
372-
_handle_upload_error(err, input_file, id2iri_mapping, failed_uploads, stashed_xml_texts, stashed_resptr_props)
401+
_handle_upload_error(
402+
err=err,
403+
id2iri_mapping=id2iri_mapping,
404+
failed_uploads=failed_uploads,
405+
stashed_xml_texts=stashed_xml_texts,
406+
stashed_resptr_props=stashed_resptr_props,
407+
proj_shortcode=shortcode,
408+
onto_name=default_ontology,
409+
server=server,
410+
save_location=save_location
411+
)
373412

374413
# write log files
375414
success = True
376-
timestamp_str = datetime.now().strftime("%Y%m%d-%H%M%S")
377-
if len(nonapplied_xml_texts) > 0:
378-
_write_stashed_xml_texts(nonapplied_xml_texts, timestamp_str)
379-
success = False
380-
if len(nonapplied_resptr_props) > 0:
381-
_write_stashed_resptr_props(nonapplied_resptr_props, timestamp_str)
382-
success = False
415+
timestamp_str = datetime.now().strftime("%Y-%m-%d_%H%M%S")
416+
id2iri_mapping_file = f"id2iri_{Path(input_file).stem}_mapping_{timestamp_str}.json"
417+
with open(id2iri_mapping_file, "x") as f:
418+
json.dump(id2iri_mapping, f, ensure_ascii=False, indent=4)
419+
print(f"The mapping of internal IDs to IRIs was written to {id2iri_mapping_file}")
383420
if failed_uploads:
384421
print(f"\nWARNING: Could not upload the following resources: {failed_uploads}\n")
385422
success = False
386423
if success:
387424
print("All resources have successfully been uploaded.")
388-
if len(id2iri_mapping) > 0:
389-
_write_id2iri_mapping(input_file, id2iri_mapping, timestamp_str)
390425

391426
return success
392427

@@ -706,11 +741,14 @@ def _purge_stashed_resptr_props(
706741

707742
def _handle_upload_error(
708743
err: BaseException,
709-
input_file: str,
710744
id2iri_mapping: dict[str, str],
711745
failed_uploads: list[str],
712746
stashed_xml_texts: dict[XMLResource, dict[XMLProperty, dict[str, KnoraStandoffXml]]],
713-
stashed_resptr_props: dict[XMLResource, dict[XMLProperty, list[str]]]
747+
stashed_resptr_props: dict[XMLResource, dict[XMLProperty, list[str]]],
748+
proj_shortcode: str,
749+
onto_name: str,
750+
server: str,
751+
save_location: str
714752
) -> None:
715753
"""
716754
In case the xmlupload must be interrupted, e.g. because of an error that could not be handled, or due to keyboard
@@ -720,33 +758,64 @@ def _handle_upload_error(
720758
721759
Args:
722760
err: error that was the cause of the abort
723-
input_file: file name of the original XML file
724761
id2iri_mapping: mapping of ids from the XML file to IRIs in DSP (only successful uploads appear here)
725762
failed_uploads: resources that caused an error when uploading to DSP
726763
stashed_xml_texts: all xml texts that have been stashed
727764
stashed_resptr_props: all resptr props that have been stashed
765+
proj_shortcode: shortcode of the project the data belongs to
766+
onto_name: name of the ontology the data references
767+
server: the server which the data is uploaded onto
768+
save_location: path to the directory where dsp-tools should save logs (OS dependent)
728769
729770
Returns:
730771
None
731772
"""
732773

733774
print(f'\n=========================================='
734775
f'\nxmlupload must be aborted because of an error')
735-
timestamp_str = datetime.now().strftime("%Y%m%d-%H%M%S")
776+
timestamp_str = datetime.now().strftime("%Y-%m-%d_%H%M%S")
777+
778+
server_substitutions = {
779+
r"https?://": "",
780+
r"^api\..+": "",
781+
r":\d{4}/?$": "",
782+
r"0.0.0.0": "localhost"
783+
}
784+
for pattern, repl in server_substitutions.items():
785+
server = re.sub(pattern, repl, server)
786+
787+
if save_location == ".":
788+
save_location_full = f"xmluploads/{server}/{proj_shortcode}/{onto_name}"
789+
else:
790+
save_location_full = f"{save_location}/xmluploads/{server}/{proj_shortcode}/{onto_name}"
791+
os.makedirs(save_location_full, exist_ok=True)
736792

737-
# write id2iri_mapping of the resources that are already in DSP
738-
if len(id2iri_mapping) > 0:
739-
_write_id2iri_mapping(input_file, id2iri_mapping, timestamp_str)
793+
# only stashed properties of resources that already exist in DSP are of interest
794+
stashed_xml_texts = _purge_stashed_xml_texts(stashed_xml_texts, id2iri_mapping)
795+
stashed_resptr_props = _purge_stashed_resptr_props(stashed_resptr_props, id2iri_mapping)
740796

741-
# Both stashes are purged from resources that have not been uploaded yet. Only stashed properties of resources that
742-
# already exist in DSP are of interest.
743-
stashed_xml_texts_purged = _purge_stashed_xml_texts(stashed_xml_texts, id2iri_mapping)
744-
if len(stashed_xml_texts_purged) > 0:
745-
_write_stashed_xml_texts(stashed_xml_texts_purged, timestamp_str)
797+
if id2iri_mapping:
798+
id2iri_mapping_file = f"{save_location_full}/{timestamp_str}_id2iri_mapping.json"
799+
with open(id2iri_mapping_file, "x") as f:
800+
json.dump(id2iri_mapping, f, ensure_ascii=False, indent=4)
801+
print(f"The mapping of internal IDs to IRIs was written to {id2iri_mapping_file}")
746802

747-
stashed_resptr_props_purged = _purge_stashed_resptr_props(stashed_resptr_props, id2iri_mapping)
748-
if len(stashed_resptr_props_purged) > 0:
749-
_write_stashed_resptr_props(stashed_resptr_props_purged, timestamp_str)
803+
if stashed_xml_texts:
804+
stashed_xml_texts_serializable = {r.id: {p.name: xml for p, xml in rdict.items()} for r, rdict in stashed_xml_texts.items()}
805+
xml_filename = f"{save_location_full}/{timestamp_str}_stashed_text_properties.json"
806+
with open(xml_filename, "x") as f:
807+
json.dump(stashed_xml_texts_serializable, f, ensure_ascii=False, indent=4, cls=KnoraStandoffXmlEncoder)
808+
print(f"There are stashed text properties that could not be reapplied to the resources they were stripped "
809+
f"from. They were saved to {xml_filename}.")
810+
811+
if stashed_resptr_props:
812+
stashed_resptr_props_serializable = {r.id: {p.name: plist for p, plist in rdict.items()} for r, rdict in stashed_resptr_props.items()}
813+
resptr_filename = f"{save_location_full}/{timestamp_str}_stashed_resptr_properties.json"
814+
with open(resptr_filename, "x") as f:
815+
json.dump(stashed_resptr_props_serializable, f, ensure_ascii=False, indent=4)
816+
print(
817+
f"There are stashed resptr properties that could not be reapplied to the resources they were stripped "
818+
f"from. They were saved to {resptr_filename}")
750819

751820
# print the resources that threw an error when they were tried to be uploaded
752821
if failed_uploads:
@@ -759,101 +828,3 @@ def _handle_upload_error(
759828
print('The error will now be raised again:\n'
760829
'==========================================\n')
761830
raise err
762-
763-
764-
def _write_id2iri_mapping(input_file: str, id2iri_mapping: dict[str, str], timestamp_str: str) -> None:
765-
"""
766-
Write the id2iri mapping into a file. The timestamp must be created by the caller, so that different log files can
767-
have an identical timestamp.
768-
769-
Args:
770-
input_file: the file name of the original XML file
771-
id2iri_mapping: mapping of ids from the XML file to IRIs in DSP
772-
timestamp_str: timestamp for log file identification
773-
774-
Returns:
775-
None
776-
"""
777-
778-
id2iri_mapping_file = "id2iri_" + Path(input_file).stem + "_mapping_" + timestamp_str + ".json"
779-
with open(id2iri_mapping_file, "w") as outfile:
780-
print(f"The mapping of internal IDs to IRIs was written to {id2iri_mapping_file}")
781-
outfile.write(json.dumps(id2iri_mapping))
782-
783-
784-
def _write_stashed_xml_texts(
785-
stashed_xml_texts: dict[XMLResource, dict[XMLProperty, dict[str, KnoraStandoffXml]]],
786-
timestamp_str: str
787-
) -> None:
788-
"""
789-
Write the stashed_xml_texts into a file. The timestamp must be created by the caller, so that different log files
790-
can have an identical timestamp.
791-
792-
Args:
793-
stashed_xml_texts: all xml texts that have been stashed
794-
timestamp_str: timestamp for log file identification
795-
796-
Returns:
797-
None
798-
"""
799-
800-
filename = f'stashed_text_properties_{timestamp_str}.txt'
801-
print(f'There are stashed text properties that could not be reapplied to the resources they were stripped from. '
802-
f'They were saved to {filename}')
803-
with open(filename, 'a') as f:
804-
f.write('Stashed text properties that could not be reapplied\n')
805-
f.write('***************************************************\n')
806-
f.write('During the xmlupload, some text properties had to be stashed away, because the salsah-links in \n'
807-
'their XML text formed a circle. The xmlupload can only be done if these circles are broken up, by \n'
808-
'stashing away some chain elements of the circle. \n'
809-
'Some resources that have been stripped from some of their text properties have been created in DSP, \n'
810-
'but the stashed text properties could not be reapplied to them, because the xmlupload was \n'
811-
'unexpectedly interrupted. \n'
812-
'This file is a list of all text properties that are now missing in DSP. The texts have been \n'
813-
'replaced by a hash number that now stands in the text field in DSP. \n'
814-
'(Not listed are the stripped resources that haven\'t been created in DSP yet.) \n')
815-
for res, props in stashed_xml_texts.items():
816-
f.write(f'\n{res.id}')
817-
f.write('\n' + '=' * len(res.id))
818-
for prop, stashed_texts in props.items():
819-
if len(stashed_texts) > 0:
820-
f.write(f'\n{prop.name}')
821-
f.write('\n' + '-' * len(prop.name))
822-
for hash, standoff in stashed_texts.items():
823-
f.write(f'\ntext with hash {hash}:\n{str(standoff).strip()}\n')
824-
825-
826-
def _write_stashed_resptr_props(
827-
stashed_resptr_props: dict[XMLResource, dict[XMLProperty, list[str]]],
828-
timestamp_str: str
829-
) -> None:
830-
"""
831-
Write the stashed_resptr_props into a file. The timestamp must be created by the caller, so that different log files
832-
can have an identical timestamp.
833-
834-
Args:
835-
stashed_resptr_props: all resptr props that have been stashed
836-
timestamp_str: timestamp for log file identification
837-
838-
Returns:
839-
None
840-
"""
841-
842-
filename = f'stashed_resptr_properties_{timestamp_str}.txt'
843-
print(f'There are stashed resptr properties that could not be reapplied to the resources they were stripped from. '
844-
f'They were saved to {filename}')
845-
with open(filename, 'a') as f:
846-
f.write('Stashed resptr properties that could not be reapplied\n')
847-
f.write('*****************************************************\n')
848-
f.write('During the xmlupload, some resptr-props had to be stashed away, because they formed a circle. The \n'
849-
'xmlupload can only be done if these circles are broken up, by stashing away some chain elements of \n'
850-
'the circle. \n'
851-
'Some resources that have been stripped from some of their resptr-props have been created in DSP, \n'
852-
'but the stashed resptr-props could not be reapplied to them, because the xmlupload was unexpectedly \n'
853-
'interrupted. \n'
854-
'This file is a list of all resptr-props that are now missing in DSP. (Not listed are the stripped \n'
855-
'resources that haven\'t been created in DSP yet. \n')
856-
for res, props_ in stashed_resptr_props.items():
857-
f.write(f'\n{res.id}\n---------\n')
858-
for prop, stashed_props in props_.items():
859-
f.write(f'{prop.name}\n\t{stashed_props}\n')

0 commit comments

Comments
 (0)