dsp-tools 0.9.13__py3-none-any.whl → 18.3.0.post13__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.
- dsp_tools/__init__.py +5 -0
- dsp_tools/cli/args.py +47 -0
- dsp_tools/cli/call_action.py +85 -0
- dsp_tools/cli/call_action_files_only.py +101 -0
- dsp_tools/cli/call_action_with_network.py +207 -0
- dsp_tools/cli/create_parsers.py +479 -0
- dsp_tools/cli/entry_point.py +322 -0
- dsp_tools/cli/utils.py +87 -0
- dsp_tools/clients/CLAUDE.md +420 -0
- dsp_tools/clients/authentication_client.py +14 -0
- dsp_tools/clients/authentication_client_live.py +66 -0
- dsp_tools/clients/connection.py +35 -0
- dsp_tools/clients/connection_live.py +233 -0
- dsp_tools/clients/fuseki_metrics.py +60 -0
- dsp_tools/clients/group_user_clients.py +35 -0
- dsp_tools/clients/group_user_clients_live.py +181 -0
- dsp_tools/clients/legal_info_client.py +23 -0
- dsp_tools/clients/legal_info_client_live.py +132 -0
- dsp_tools/clients/list_client.py +49 -0
- dsp_tools/clients/list_client_live.py +166 -0
- dsp_tools/clients/metadata_client.py +24 -0
- dsp_tools/clients/metadata_client_live.py +47 -0
- dsp_tools/clients/ontology_clients.py +49 -0
- dsp_tools/clients/ontology_create_client_live.py +166 -0
- dsp_tools/clients/ontology_get_client_live.py +80 -0
- dsp_tools/clients/permissions_client.py +68 -0
- dsp_tools/clients/project_client.py +16 -0
- dsp_tools/clients/project_client_live.py +66 -0
- dsp_tools/commands/create/communicate_problems.py +24 -0
- dsp_tools/commands/create/create.py +134 -0
- dsp_tools/commands/create/create_on_server/cardinalities.py +111 -0
- dsp_tools/commands/create/create_on_server/classes.py +99 -0
- dsp_tools/commands/create/create_on_server/complete_ontologies.py +116 -0
- dsp_tools/commands/create/create_on_server/default_permissions.py +134 -0
- dsp_tools/commands/create/create_on_server/group_users.py +165 -0
- dsp_tools/commands/create/create_on_server/lists.py +163 -0
- dsp_tools/commands/create/create_on_server/mappers.py +12 -0
- dsp_tools/commands/create/create_on_server/onto_utils.py +74 -0
- dsp_tools/commands/create/create_on_server/ontology.py +52 -0
- dsp_tools/commands/create/create_on_server/project.py +68 -0
- dsp_tools/commands/create/create_on_server/properties.py +119 -0
- dsp_tools/commands/create/exceptions.py +29 -0
- dsp_tools/commands/create/lists_only.py +66 -0
- dsp_tools/commands/create/models/create_problems.py +87 -0
- dsp_tools/commands/create/models/parsed_ontology.py +88 -0
- dsp_tools/commands/create/models/parsed_project.py +81 -0
- dsp_tools/commands/create/models/rdf_ontology.py +12 -0
- dsp_tools/commands/create/models/server_project_info.py +100 -0
- dsp_tools/commands/create/parsing/parse_lists.py +45 -0
- dsp_tools/commands/create/parsing/parse_ontology.py +243 -0
- dsp_tools/commands/create/parsing/parse_project.py +149 -0
- dsp_tools/commands/create/parsing/parsing_utils.py +40 -0
- dsp_tools/commands/create/project_validate.py +595 -0
- dsp_tools/commands/create/serialisation/ontology.py +119 -0
- dsp_tools/commands/create/serialisation/project.py +44 -0
- dsp_tools/commands/excel2json/CLAUDE.md +101 -0
- dsp_tools/commands/excel2json/json_header.py +321 -0
- dsp_tools/commands/excel2json/lists/__init__.py +0 -0
- dsp_tools/commands/excel2json/lists/compliance_checks.py +292 -0
- dsp_tools/commands/excel2json/lists/make_lists.py +247 -0
- dsp_tools/commands/excel2json/lists/models/__init__.py +0 -0
- dsp_tools/commands/excel2json/lists/models/deserialise.py +30 -0
- dsp_tools/commands/excel2json/lists/models/input_error.py +216 -0
- dsp_tools/commands/excel2json/lists/models/serialise.py +57 -0
- dsp_tools/commands/excel2json/lists/utils.py +81 -0
- dsp_tools/commands/excel2json/models/__init__.py +0 -0
- dsp_tools/commands/excel2json/models/input_error.py +416 -0
- dsp_tools/commands/excel2json/models/json_header.py +175 -0
- dsp_tools/commands/excel2json/models/list_node_name.py +16 -0
- dsp_tools/commands/excel2json/models/ontology.py +76 -0
- dsp_tools/commands/excel2json/old_lists.py +328 -0
- dsp_tools/commands/excel2json/project.py +280 -0
- dsp_tools/commands/excel2json/properties.py +370 -0
- dsp_tools/commands/excel2json/resources.py +336 -0
- dsp_tools/commands/excel2json/utils.py +352 -0
- dsp_tools/commands/excel2xml/__init__.py +7 -0
- dsp_tools/commands/excel2xml/excel2xml_cli.py +523 -0
- dsp_tools/commands/excel2xml/excel2xml_lib.py +1953 -0
- dsp_tools/commands/excel2xml/propertyelement.py +47 -0
- dsp_tools/commands/get/__init__.py +0 -0
- dsp_tools/commands/get/get.py +166 -0
- dsp_tools/commands/get/get_permissions.py +257 -0
- dsp_tools/commands/get/get_permissions_legacy.py +89 -0
- dsp_tools/commands/get/legacy_models/__init__.py +0 -0
- dsp_tools/commands/get/legacy_models/context.py +318 -0
- dsp_tools/commands/get/legacy_models/group.py +241 -0
- dsp_tools/commands/get/legacy_models/helpers.py +47 -0
- dsp_tools/commands/get/legacy_models/listnode.py +390 -0
- dsp_tools/commands/get/legacy_models/model.py +12 -0
- dsp_tools/commands/get/legacy_models/ontology.py +324 -0
- dsp_tools/commands/get/legacy_models/project.py +366 -0
- dsp_tools/commands/get/legacy_models/propertyclass.py +417 -0
- dsp_tools/commands/get/legacy_models/resourceclass.py +676 -0
- dsp_tools/commands/get/legacy_models/user.py +438 -0
- dsp_tools/commands/get/models/__init__.py +0 -0
- dsp_tools/commands/get/models/permissions_models.py +10 -0
- dsp_tools/commands/id2iri.py +258 -0
- dsp_tools/commands/ingest_xmlupload/__init__.py +0 -0
- dsp_tools/commands/ingest_xmlupload/bulk_ingest_client.py +178 -0
- dsp_tools/commands/ingest_xmlupload/create_resources/__init__.py +0 -0
- dsp_tools/commands/ingest_xmlupload/create_resources/apply_ingest_id.py +69 -0
- dsp_tools/commands/ingest_xmlupload/create_resources/upload_xml.py +166 -0
- dsp_tools/commands/ingest_xmlupload/create_resources/user_information.py +121 -0
- dsp_tools/commands/ingest_xmlupload/ingest_files/__init__.py +0 -0
- dsp_tools/commands/ingest_xmlupload/ingest_files/ingest_files.py +64 -0
- dsp_tools/commands/ingest_xmlupload/upload_files/__init__.py +0 -0
- dsp_tools/commands/ingest_xmlupload/upload_files/filechecker.py +20 -0
- dsp_tools/commands/ingest_xmlupload/upload_files/input_error.py +57 -0
- dsp_tools/commands/ingest_xmlupload/upload_files/upload_failures.py +66 -0
- dsp_tools/commands/ingest_xmlupload/upload_files/upload_files.py +67 -0
- dsp_tools/commands/resume_xmlupload/__init__.py +0 -0
- dsp_tools/commands/resume_xmlupload/resume_xmlupload.py +96 -0
- dsp_tools/commands/start_stack.py +428 -0
- dsp_tools/commands/update_legal/CLAUDE.md +344 -0
- dsp_tools/commands/update_legal/__init__.py +0 -0
- dsp_tools/commands/update_legal/core.py +182 -0
- dsp_tools/commands/update_legal/csv_operations.py +135 -0
- dsp_tools/commands/update_legal/models.py +87 -0
- dsp_tools/commands/update_legal/xml_operations.py +247 -0
- dsp_tools/commands/validate_data/CLAUDE.md +159 -0
- dsp_tools/commands/validate_data/__init__.py +0 -0
- dsp_tools/commands/validate_data/constants.py +59 -0
- dsp_tools/commands/validate_data/mappers.py +143 -0
- dsp_tools/commands/validate_data/models/__init__.py +0 -0
- dsp_tools/commands/validate_data/models/api_responses.py +45 -0
- dsp_tools/commands/validate_data/models/input_problems.py +119 -0
- dsp_tools/commands/validate_data/models/rdf_like_data.py +117 -0
- dsp_tools/commands/validate_data/models/validation.py +106 -0
- dsp_tools/commands/validate_data/prepare_data/__init__.py +0 -0
- dsp_tools/commands/validate_data/prepare_data/get_rdf_like_data.py +296 -0
- dsp_tools/commands/validate_data/prepare_data/make_data_graph.py +91 -0
- dsp_tools/commands/validate_data/prepare_data/prepare_data.py +184 -0
- dsp_tools/commands/validate_data/process_validation_report/__init__.py +0 -0
- dsp_tools/commands/validate_data/process_validation_report/get_user_validation_message.py +358 -0
- dsp_tools/commands/validate_data/process_validation_report/query_validation_result.py +507 -0
- dsp_tools/commands/validate_data/process_validation_report/reformat_validation_results.py +150 -0
- dsp_tools/commands/validate_data/shacl_cli_validator.py +70 -0
- dsp_tools/commands/validate_data/sparql/__init__.py +0 -0
- dsp_tools/commands/validate_data/sparql/cardinality_shacl.py +209 -0
- dsp_tools/commands/validate_data/sparql/construct_shacl.py +92 -0
- dsp_tools/commands/validate_data/sparql/legal_info_shacl.py +36 -0
- dsp_tools/commands/validate_data/sparql/value_shacl.py +357 -0
- dsp_tools/commands/validate_data/utils.py +59 -0
- dsp_tools/commands/validate_data/validate_data.py +283 -0
- dsp_tools/commands/validate_data/validation/__init__.py +0 -0
- dsp_tools/commands/validate_data/validation/check_duplicate_files.py +55 -0
- dsp_tools/commands/validate_data/validation/check_for_unknown_classes.py +67 -0
- dsp_tools/commands/validate_data/validation/get_validation_report.py +94 -0
- dsp_tools/commands/validate_data/validation/validate_ontology.py +107 -0
- dsp_tools/commands/xmlupload/CLAUDE.md +292 -0
- dsp_tools/commands/xmlupload/__init__.py +0 -0
- dsp_tools/commands/xmlupload/iri_resolver.py +21 -0
- dsp_tools/commands/xmlupload/make_rdf_graph/__init__.py +0 -0
- dsp_tools/commands/xmlupload/make_rdf_graph/constants.py +63 -0
- dsp_tools/commands/xmlupload/make_rdf_graph/jsonld_utils.py +44 -0
- dsp_tools/commands/xmlupload/make_rdf_graph/make_file_value.py +77 -0
- dsp_tools/commands/xmlupload/make_rdf_graph/make_resource_and_values.py +114 -0
- dsp_tools/commands/xmlupload/make_rdf_graph/make_values.py +262 -0
- dsp_tools/commands/xmlupload/models/__init__.py +0 -0
- dsp_tools/commands/xmlupload/models/bitstream_info.py +18 -0
- dsp_tools/commands/xmlupload/models/formatted_text_value.py +10 -0
- dsp_tools/commands/xmlupload/models/ingest.py +143 -0
- dsp_tools/commands/xmlupload/models/input_problems.py +58 -0
- dsp_tools/commands/xmlupload/models/lookup_models.py +21 -0
- dsp_tools/commands/xmlupload/models/permission.py +45 -0
- dsp_tools/commands/xmlupload/models/permissions_parsed.py +93 -0
- dsp_tools/commands/xmlupload/models/processed/__init__.py +0 -0
- dsp_tools/commands/xmlupload/models/processed/file_values.py +29 -0
- dsp_tools/commands/xmlupload/models/processed/res.py +27 -0
- dsp_tools/commands/xmlupload/models/processed/values.py +101 -0
- dsp_tools/commands/xmlupload/models/rdf_models.py +26 -0
- dsp_tools/commands/xmlupload/models/upload_clients.py +14 -0
- dsp_tools/commands/xmlupload/models/upload_state.py +20 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/__init__.py +0 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/ark2iri.py +55 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/get_processed_resources.py +252 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/iiif_uri_validator.py +50 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/list_client.py +120 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/prepare_xml_input.py +67 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/read_validate_xml_file.py +58 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/transform_input_values.py +118 -0
- dsp_tools/commands/xmlupload/resource_create_client.py +25 -0
- dsp_tools/commands/xmlupload/richtext_id2iri.py +37 -0
- dsp_tools/commands/xmlupload/stash/__init__.py +0 -0
- dsp_tools/commands/xmlupload/stash/analyse_circular_reference_graph.py +236 -0
- dsp_tools/commands/xmlupload/stash/create_info_for_graph.py +53 -0
- dsp_tools/commands/xmlupload/stash/graph_models.py +87 -0
- dsp_tools/commands/xmlupload/stash/stash_circular_references.py +68 -0
- dsp_tools/commands/xmlupload/stash/stash_models.py +109 -0
- dsp_tools/commands/xmlupload/stash/upload_stashed_resptr_props.py +106 -0
- dsp_tools/commands/xmlupload/stash/upload_stashed_xml_texts.py +196 -0
- dsp_tools/commands/xmlupload/upload_config.py +76 -0
- dsp_tools/commands/xmlupload/write_diagnostic_info.py +27 -0
- dsp_tools/commands/xmlupload/xmlupload.py +516 -0
- dsp_tools/config/__init__.py +0 -0
- dsp_tools/config/logger_config.py +69 -0
- dsp_tools/config/warnings_config.py +32 -0
- dsp_tools/error/__init__.py +0 -0
- dsp_tools/error/custom_warnings.py +39 -0
- dsp_tools/error/exceptions.py +204 -0
- dsp_tools/error/problems.py +10 -0
- dsp_tools/error/xmllib_errors.py +20 -0
- dsp_tools/error/xmllib_warnings.py +54 -0
- dsp_tools/error/xmllib_warnings_util.py +159 -0
- dsp_tools/error/xsd_validation_error_msg.py +19 -0
- dsp_tools/legacy_models/__init__.py +0 -0
- dsp_tools/legacy_models/datetimestamp.py +81 -0
- dsp_tools/legacy_models/langstring.py +253 -0
- dsp_tools/legacy_models/projectContext.py +49 -0
- dsp_tools/py.typed +0 -0
- dsp_tools/resources/schema/data.xsd +648 -0
- dsp_tools/resources/schema/lists-only.json +72 -0
- dsp_tools/resources/schema/project.json +1258 -0
- dsp_tools/resources/schema/properties-only.json +874 -0
- dsp_tools/resources/schema/resources-only.json +140 -0
- dsp_tools/resources/start-stack/docker-compose.override-host.j2 +11 -0
- dsp_tools/resources/start-stack/docker-compose.override.yml +11 -0
- dsp_tools/resources/start-stack/docker-compose.yml +88 -0
- dsp_tools/resources/start-stack/dsp-app-config.json +45 -0
- dsp_tools/resources/start-stack/dsp-app-config.override-host.j2 +26 -0
- dsp_tools/resources/validate_data/api-shapes-resource-cardinalities.ttl +191 -0
- dsp_tools/resources/validate_data/api-shapes.ttl +804 -0
- dsp_tools/resources/validate_data/shacl-cli-image.yml +4 -0
- dsp_tools/resources/validate_data/validate-ontology.ttl +99 -0
- dsp_tools/utils/__init__.py +0 -0
- dsp_tools/utils/ansi_colors.py +32 -0
- dsp_tools/utils/data_formats/__init__.py +0 -0
- dsp_tools/utils/data_formats/date_util.py +166 -0
- dsp_tools/utils/data_formats/iri_util.py +30 -0
- dsp_tools/utils/data_formats/shared.py +81 -0
- dsp_tools/utils/data_formats/uri_util.py +76 -0
- dsp_tools/utils/fuseki_bloating.py +63 -0
- dsp_tools/utils/json_parsing.py +22 -0
- dsp_tools/utils/rdf_constants.py +42 -0
- dsp_tools/utils/rdflib_utils.py +10 -0
- dsp_tools/utils/replace_id_with_iri.py +66 -0
- dsp_tools/utils/request_utils.py +238 -0
- dsp_tools/utils/xml_parsing/__init__.py +0 -0
- dsp_tools/utils/xml_parsing/get_lookups.py +32 -0
- dsp_tools/utils/xml_parsing/get_parsed_resources.py +325 -0
- dsp_tools/utils/xml_parsing/models/__init__.py +0 -0
- dsp_tools/utils/xml_parsing/models/parsed_resource.py +76 -0
- dsp_tools/utils/xml_parsing/parse_clean_validate_xml.py +137 -0
- dsp_tools/xmllib/CLAUDE.md +302 -0
- dsp_tools/xmllib/__init__.py +49 -0
- dsp_tools/xmllib/general_functions.py +877 -0
- dsp_tools/xmllib/internal/__init__.py +0 -0
- dsp_tools/xmllib/internal/checkers.py +162 -0
- dsp_tools/xmllib/internal/circumvent_circular_imports.py +36 -0
- dsp_tools/xmllib/internal/constants.py +46 -0
- dsp_tools/xmllib/internal/input_converters.py +155 -0
- dsp_tools/xmllib/internal/serialise_file_value.py +57 -0
- dsp_tools/xmllib/internal/serialise_resource.py +177 -0
- dsp_tools/xmllib/internal/serialise_values.py +152 -0
- dsp_tools/xmllib/internal/type_aliases.py +11 -0
- dsp_tools/xmllib/models/__init__.py +0 -0
- dsp_tools/xmllib/models/config_options.py +28 -0
- dsp_tools/xmllib/models/date_formats.py +48 -0
- dsp_tools/xmllib/models/dsp_base_resources.py +1542 -0
- dsp_tools/xmllib/models/internal/__init__.py +0 -0
- dsp_tools/xmllib/models/internal/file_values.py +172 -0
- dsp_tools/xmllib/models/internal/geometry.py +162 -0
- dsp_tools/xmllib/models/internal/migration_metadata.py +55 -0
- dsp_tools/xmllib/models/internal/serialise_permissions.py +66 -0
- dsp_tools/xmllib/models/internal/values.py +342 -0
- dsp_tools/xmllib/models/licenses/__init__.py +0 -0
- dsp_tools/xmllib/models/licenses/other.py +59 -0
- dsp_tools/xmllib/models/licenses/recommended.py +107 -0
- dsp_tools/xmllib/models/permissions.py +41 -0
- dsp_tools/xmllib/models/res.py +1782 -0
- dsp_tools/xmllib/models/root.py +348 -0
- dsp_tools/xmllib/value_checkers.py +434 -0
- dsp_tools/xmllib/value_converters.py +777 -0
- dsp_tools-18.3.0.post13.dist-info/METADATA +90 -0
- dsp_tools-18.3.0.post13.dist-info/RECORD +286 -0
- dsp_tools-18.3.0.post13.dist-info/WHEEL +4 -0
- dsp_tools-18.3.0.post13.dist-info/entry_points.txt +3 -0
- dsp_tools-0.9.13.dist-info/LICENSE +0 -674
- dsp_tools-0.9.13.dist-info/METADATA +0 -144
- dsp_tools-0.9.13.dist-info/RECORD +0 -71
- dsp_tools-0.9.13.dist-info/WHEEL +0 -5
- dsp_tools-0.9.13.dist-info/entry_points.txt +0 -3
- dsp_tools-0.9.13.dist-info/top_level.txt +0 -1
- dsplib/models/connection.py +0 -272
- dsplib/models/group.py +0 -296
- dsplib/models/helpers.py +0 -505
- dsplib/models/langstring.py +0 -277
- dsplib/models/listnode.py +0 -578
- dsplib/models/model.py +0 -20
- dsplib/models/ontology.py +0 -448
- dsplib/models/permission.py +0 -112
- dsplib/models/project.py +0 -547
- dsplib/models/propertyclass.py +0 -505
- dsplib/models/resource.py +0 -366
- dsplib/models/resourceclass.py +0 -810
- dsplib/models/sipi.py +0 -30
- dsplib/models/user.py +0 -731
- dsplib/models/value.py +0 -1000
- dsplib/utils/knora-data-schema.xsd +0 -454
- dsplib/utils/knora-schema-lists.json +0 -83
- dsplib/utils/knora-schema.json +0 -434
- dsplib/utils/onto_commons.py +0 -24
- dsplib/utils/onto_create_lists.py +0 -73
- dsplib/utils/onto_create_ontology.py +0 -442
- dsplib/utils/onto_get.py +0 -58
- dsplib/utils/onto_validate.py +0 -33
- dsplib/utils/xml_upload.py +0 -539
- dsplib/widgets/doublepassword.py +0 -80
- knora/MLS-import-libraries.py +0 -84
- knora/dsp_tools.py +0 -96
- knora/dsplib/models/connection.py +0 -272
- knora/dsplib/models/group.py +0 -296
- knora/dsplib/models/helpers.py +0 -506
- knora/dsplib/models/langstring.py +0 -277
- knora/dsplib/models/listnode.py +0 -578
- knora/dsplib/models/model.py +0 -20
- knora/dsplib/models/ontology.py +0 -448
- knora/dsplib/models/permission.py +0 -112
- knora/dsplib/models/project.py +0 -583
- knora/dsplib/models/propertyclass.py +0 -505
- knora/dsplib/models/resource.py +0 -416
- knora/dsplib/models/resourceclass.py +0 -811
- knora/dsplib/models/sipi.py +0 -35
- knora/dsplib/models/user.py +0 -731
- knora/dsplib/models/value.py +0 -1000
- knora/dsplib/utils/knora-data-schema.xsd +0 -464
- knora/dsplib/utils/knora-schema-lists.json +0 -83
- knora/dsplib/utils/knora-schema.json +0 -444
- knora/dsplib/utils/onto_commons.py +0 -24
- knora/dsplib/utils/onto_create_lists.py +0 -73
- knora/dsplib/utils/onto_create_ontology.py +0 -451
- knora/dsplib/utils/onto_get.py +0 -58
- knora/dsplib/utils/onto_validate.py +0 -33
- knora/dsplib/utils/xml_upload.py +0 -540
- knora/dsplib/widgets/doublepassword.py +0 -80
- knora/knora.py +0 -2108
- knora/test.py +0 -99
- knora/testit.py +0 -76
- knora/xml2knora.py +0 -633
- {dsplib → dsp_tools/cli}/__init__.py +0 -0
- {dsplib/models → dsp_tools/clients}/__init__.py +0 -0
- {dsplib/utils → dsp_tools/commands}/__init__.py +0 -0
- {dsplib/widgets → dsp_tools/commands/create}/__init__.py +0 -0
- {knora → dsp_tools/commands/create/create_on_server}/__init__.py +0 -0
- {knora/dsplib → dsp_tools/commands/create/models}/__init__.py +0 -0
- {knora/dsplib/models → dsp_tools/commands/create/parsing}/__init__.py +0 -0
- {knora/dsplib/utils → dsp_tools/commands/create/serialisation}/__init__.py +0 -0
- {knora/dsplib/widgets → dsp_tools/commands/excel2json}/__init__.py +0 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pickle
|
|
4
|
+
import sys
|
|
5
|
+
import warnings
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Never
|
|
9
|
+
|
|
10
|
+
from loguru import logger
|
|
11
|
+
from rdflib import URIRef
|
|
12
|
+
from tqdm import tqdm
|
|
13
|
+
|
|
14
|
+
from dsp_tools.cli.args import ServerCredentials
|
|
15
|
+
from dsp_tools.cli.args import ValidateDataConfig
|
|
16
|
+
from dsp_tools.cli.args import ValidationSeverity
|
|
17
|
+
from dsp_tools.clients.authentication_client import AuthenticationClient
|
|
18
|
+
from dsp_tools.clients.authentication_client_live import AuthenticationClientLive
|
|
19
|
+
from dsp_tools.clients.connection import Connection
|
|
20
|
+
from dsp_tools.clients.connection_live import ConnectionLive
|
|
21
|
+
from dsp_tools.clients.fuseki_metrics import FusekiMetrics
|
|
22
|
+
from dsp_tools.clients.legal_info_client import LegalInfoClient
|
|
23
|
+
from dsp_tools.clients.legal_info_client_live import LegalInfoClientLive
|
|
24
|
+
from dsp_tools.clients.project_client import ProjectClient
|
|
25
|
+
from dsp_tools.clients.project_client_live import ProjectClientLive
|
|
26
|
+
from dsp_tools.commands.validate_data.validate_data import validate_parsed_resources
|
|
27
|
+
from dsp_tools.commands.xmlupload.make_rdf_graph.make_resource_and_values import create_resource_with_values
|
|
28
|
+
from dsp_tools.commands.xmlupload.models.ingest import AssetClient
|
|
29
|
+
from dsp_tools.commands.xmlupload.models.ingest import DspIngestClientLive
|
|
30
|
+
from dsp_tools.commands.xmlupload.models.lookup_models import IRILookups
|
|
31
|
+
from dsp_tools.commands.xmlupload.models.lookup_models import XmlReferenceLookups
|
|
32
|
+
from dsp_tools.commands.xmlupload.models.processed.res import ProcessedResource
|
|
33
|
+
from dsp_tools.commands.xmlupload.models.upload_clients import UploadClients
|
|
34
|
+
from dsp_tools.commands.xmlupload.models.upload_state import UploadState
|
|
35
|
+
from dsp_tools.commands.xmlupload.prepare_xml_input.get_processed_resources import get_processed_resources
|
|
36
|
+
from dsp_tools.commands.xmlupload.prepare_xml_input.list_client import ListClient
|
|
37
|
+
from dsp_tools.commands.xmlupload.prepare_xml_input.list_client import ListClientLive
|
|
38
|
+
from dsp_tools.commands.xmlupload.prepare_xml_input.prepare_xml_input import get_parsed_resources_and_mappers
|
|
39
|
+
from dsp_tools.commands.xmlupload.prepare_xml_input.prepare_xml_input import get_stash_and_upload_order
|
|
40
|
+
from dsp_tools.commands.xmlupload.prepare_xml_input.read_validate_xml_file import check_if_bitstreams_exist
|
|
41
|
+
from dsp_tools.commands.xmlupload.prepare_xml_input.read_validate_xml_file import validate_iiif_uris
|
|
42
|
+
from dsp_tools.commands.xmlupload.resource_create_client import ResourceCreateClient
|
|
43
|
+
from dsp_tools.commands.xmlupload.stash.upload_stashed_resptr_props import upload_stashed_resptr_props
|
|
44
|
+
from dsp_tools.commands.xmlupload.stash.upload_stashed_xml_texts import upload_stashed_xml_texts
|
|
45
|
+
from dsp_tools.commands.xmlupload.upload_config import UploadConfig
|
|
46
|
+
from dsp_tools.commands.xmlupload.write_diagnostic_info import write_id2iri_mapping
|
|
47
|
+
from dsp_tools.config.logger_config import WARNINGS_SAVEPATH
|
|
48
|
+
from dsp_tools.error.custom_warnings import DspToolsUserWarning
|
|
49
|
+
from dsp_tools.error.exceptions import BaseError
|
|
50
|
+
from dsp_tools.error.exceptions import PermanentConnectionError
|
|
51
|
+
from dsp_tools.error.exceptions import PermanentTimeOutError
|
|
52
|
+
from dsp_tools.error.exceptions import XmlUploadInterruptedError
|
|
53
|
+
from dsp_tools.utils.ansi_colors import BOLD_RED
|
|
54
|
+
from dsp_tools.utils.ansi_colors import BOLD_YELLOW
|
|
55
|
+
from dsp_tools.utils.ansi_colors import RESET_TO_DEFAULT
|
|
56
|
+
from dsp_tools.utils.data_formats.uri_util import is_prod_like_server
|
|
57
|
+
from dsp_tools.utils.fuseki_bloating import communicate_fuseki_bloating
|
|
58
|
+
from dsp_tools.utils.replace_id_with_iri import use_id2iri_mapping_to_replace_ids
|
|
59
|
+
from dsp_tools.utils.xml_parsing.models.parsed_resource import ParsedResource
|
|
60
|
+
from dsp_tools.utils.xml_parsing.parse_clean_validate_xml import parse_and_clean_xml_file
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def xmlupload(
|
|
64
|
+
input_file: Path,
|
|
65
|
+
creds: ServerCredentials,
|
|
66
|
+
imgdir: str,
|
|
67
|
+
config: UploadConfig = UploadConfig(),
|
|
68
|
+
) -> bool:
|
|
69
|
+
"""
|
|
70
|
+
This function reads an XML file and imports the data described in it onto the DSP server.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
input_file: path to XML file containing the resources
|
|
74
|
+
creds: the credentials to access the DSP server
|
|
75
|
+
imgdir: the image directory
|
|
76
|
+
config: the configuration for the upload
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
BaseError: in case of permanent network or software failure
|
|
80
|
+
InputError: in case of permanent network or software failure, or if the XML file is invalid
|
|
81
|
+
InputError: in case of permanent network or software failure, or if the XML file is invalid
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
True if all resources could be uploaded without errors; False if one of the resources could not be
|
|
85
|
+
uploaded because there is an error in it
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
root = parse_and_clean_xml_file(input_file)
|
|
89
|
+
shortcode = root.attrib["shortcode"]
|
|
90
|
+
|
|
91
|
+
auth = AuthenticationClientLive(server=creds.server, email=creds.user, password=creds.password)
|
|
92
|
+
con = ConnectionLive(creds.server, auth)
|
|
93
|
+
config = config.with_server_info(server=creds.server, shortcode=shortcode)
|
|
94
|
+
clients = _get_live_clients(con, auth, creds, shortcode, imgdir)
|
|
95
|
+
|
|
96
|
+
parsed_resources, lookups = get_parsed_resources_and_mappers(root, clients)
|
|
97
|
+
if config.id2iri_file:
|
|
98
|
+
parsed_resources = use_id2iri_mapping_to_replace_ids(parsed_resources, Path(config.id2iri_file))
|
|
99
|
+
|
|
100
|
+
is_on_prod_like_server = is_prod_like_server(creds.server)
|
|
101
|
+
|
|
102
|
+
validation_ok = _handle_validation(
|
|
103
|
+
parsed_resources=parsed_resources,
|
|
104
|
+
lookups=lookups,
|
|
105
|
+
config=config,
|
|
106
|
+
is_on_prod_like_server=is_on_prod_like_server,
|
|
107
|
+
auth=auth,
|
|
108
|
+
input_file=input_file,
|
|
109
|
+
)
|
|
110
|
+
if not validation_ok:
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
check_if_bitstreams_exist(root, imgdir)
|
|
114
|
+
if not config.skip_iiif_validation:
|
|
115
|
+
validate_iiif_uris(root)
|
|
116
|
+
|
|
117
|
+
if not is_on_prod_like_server:
|
|
118
|
+
enable_unknown_license_if_any_are_missing(clients.legal_info_client, parsed_resources)
|
|
119
|
+
|
|
120
|
+
processed_resources = get_processed_resources(parsed_resources, lookups, is_on_prod_like_server)
|
|
121
|
+
|
|
122
|
+
sorted_resources, stash = get_stash_and_upload_order(processed_resources)
|
|
123
|
+
state = UploadState(
|
|
124
|
+
pending_resources=sorted_resources,
|
|
125
|
+
pending_stash=stash,
|
|
126
|
+
config=config,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return execute_upload(clients, state)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _handle_validation(
|
|
133
|
+
parsed_resources: list[ParsedResource],
|
|
134
|
+
lookups: XmlReferenceLookups,
|
|
135
|
+
config: UploadConfig,
|
|
136
|
+
is_on_prod_like_server: bool,
|
|
137
|
+
auth: AuthenticationClient,
|
|
138
|
+
input_file: Path,
|
|
139
|
+
) -> bool:
|
|
140
|
+
validation_should_be_skipped = config.skip_validation
|
|
141
|
+
if is_on_prod_like_server and config.skip_validation:
|
|
142
|
+
msg = (
|
|
143
|
+
"You set the flag '--skip-validation' to circumvent the SHACL schema validation. "
|
|
144
|
+
"This means that the upload may fail due to undetected errors. "
|
|
145
|
+
"Do you wish to skip the validation (yes/no)? "
|
|
146
|
+
)
|
|
147
|
+
resp = ""
|
|
148
|
+
while resp not in ["yes", "no"]:
|
|
149
|
+
resp = input(BOLD_RED + msg + RESET_TO_DEFAULT)
|
|
150
|
+
if str(resp) == "no":
|
|
151
|
+
validation_should_be_skipped = False
|
|
152
|
+
if not validation_should_be_skipped:
|
|
153
|
+
ignore_duplicates = config.ignore_duplicate_files_warning
|
|
154
|
+
if is_on_prod_like_server and ignore_duplicates:
|
|
155
|
+
msg = (
|
|
156
|
+
"You set the flag '--ignore-duplicate-files-warning'. "
|
|
157
|
+
"This means that duplicate multimedia files will not be detected. "
|
|
158
|
+
"Are you sure you want to exclude this from the validation? (yes/no)"
|
|
159
|
+
)
|
|
160
|
+
resp = ""
|
|
161
|
+
while resp not in ["yes", "no"]:
|
|
162
|
+
resp = input(BOLD_RED + msg + RESET_TO_DEFAULT)
|
|
163
|
+
if str(resp) == "no":
|
|
164
|
+
ignore_duplicates = False
|
|
165
|
+
v_severity = config.validation_severity
|
|
166
|
+
if is_on_prod_like_server:
|
|
167
|
+
v_severity = ValidationSeverity.INFO
|
|
168
|
+
validation_passed = validate_parsed_resources(
|
|
169
|
+
parsed_resources=parsed_resources,
|
|
170
|
+
authorship_lookup=lookups.authorships,
|
|
171
|
+
permission_ids=list(lookups.permissions.keys()),
|
|
172
|
+
shortcode=config.shortcode,
|
|
173
|
+
config=ValidateDataConfig(
|
|
174
|
+
input_file,
|
|
175
|
+
save_graph_dir=None,
|
|
176
|
+
severity=v_severity,
|
|
177
|
+
ignore_duplicate_files_warning=ignore_duplicates,
|
|
178
|
+
is_on_prod_server=is_on_prod_like_server,
|
|
179
|
+
skip_ontology_validation=config.skip_ontology_validation,
|
|
180
|
+
do_not_request_resource_metadata_from_db=config.do_not_request_resource_metadata_from_db,
|
|
181
|
+
),
|
|
182
|
+
auth=auth,
|
|
183
|
+
)
|
|
184
|
+
if not validation_passed:
|
|
185
|
+
return False
|
|
186
|
+
else:
|
|
187
|
+
logger.debug("SHACL validation was skipped.")
|
|
188
|
+
return True
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _get_live_clients(
|
|
192
|
+
con: Connection,
|
|
193
|
+
auth: AuthenticationClient,
|
|
194
|
+
creds: ServerCredentials,
|
|
195
|
+
shortcode: str,
|
|
196
|
+
imgdir: str,
|
|
197
|
+
) -> UploadClients:
|
|
198
|
+
ingest_client: AssetClient
|
|
199
|
+
ingest_client = DspIngestClientLive(creds.dsp_ingest_url, auth, shortcode, imgdir)
|
|
200
|
+
project_client: ProjectClient = ProjectClientLive(auth.server, auth)
|
|
201
|
+
list_client: ListClient = ListClientLive(con, project_client.get_project_iri(shortcode))
|
|
202
|
+
legal_info_client: LegalInfoClient = LegalInfoClientLive(creds.server, shortcode, auth)
|
|
203
|
+
return UploadClients(
|
|
204
|
+
asset_client=ingest_client,
|
|
205
|
+
list_client=list_client,
|
|
206
|
+
legal_info_client=legal_info_client,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def enable_unknown_license_if_any_are_missing(
|
|
211
|
+
legal_info_client: LegalInfoClient, parsed_resources: list[ParsedResource]
|
|
212
|
+
) -> None:
|
|
213
|
+
all_license_infos = [x.file_value.metadata.license_iri for x in parsed_resources if x.file_value]
|
|
214
|
+
if not all(all_license_infos):
|
|
215
|
+
legal_info_client.enable_unknown_license()
|
|
216
|
+
msg = (
|
|
217
|
+
"The files or iiif-uris in your data are missing some legal information. "
|
|
218
|
+
"To facilitate an upload on a test environment we are adding dummy information.\n"
|
|
219
|
+
"In order to be able to use the license 'unknown' in place of missing licenses, "
|
|
220
|
+
"we are enabling it for your project."
|
|
221
|
+
)
|
|
222
|
+
print(BOLD_YELLOW, msg, RESET_TO_DEFAULT)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def execute_upload(clients: UploadClients, upload_state: UploadState) -> bool:
|
|
226
|
+
"""Execute an upload from an upload state, and clean up afterwards.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
clients: the clients needed for the upload
|
|
230
|
+
upload_state: the initial state of the upload to execute
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
True if all resources could be uploaded without errors; False if any resource could not be uploaded
|
|
234
|
+
"""
|
|
235
|
+
logger.debug("Start uploading data")
|
|
236
|
+
db_metrics = None
|
|
237
|
+
if clients.legal_info_client.server == "http://0.0.0.0:3333":
|
|
238
|
+
db_metrics = FusekiMetrics()
|
|
239
|
+
db_metrics.try_get_start_size()
|
|
240
|
+
_upload_copyright_holders(upload_state.pending_resources, clients.legal_info_client)
|
|
241
|
+
_upload_resources(clients, upload_state)
|
|
242
|
+
if db_metrics is not None:
|
|
243
|
+
db_metrics.try_get_end_size()
|
|
244
|
+
communicate_fuseki_bloating(db_metrics)
|
|
245
|
+
return _cleanup_upload(upload_state)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _upload_copyright_holders(resources: list[ProcessedResource], legal_info_client: LegalInfoClient) -> None:
|
|
249
|
+
logger.debug("Get and upload copyright holders")
|
|
250
|
+
copyright_holders = _get_copyright_holders(resources)
|
|
251
|
+
legal_info_client.post_copyright_holders(copyright_holders)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _get_copyright_holders(resources: list[ProcessedResource]) -> list[str]:
|
|
255
|
+
copyright_holders = set()
|
|
256
|
+
for res in resources:
|
|
257
|
+
if res.file_value:
|
|
258
|
+
copyright_holders.add(res.file_value.metadata.copyright_holder)
|
|
259
|
+
elif res.iiif_uri:
|
|
260
|
+
copyright_holders.add(res.iiif_uri.metadata.copyright_holder)
|
|
261
|
+
return [x for x in copyright_holders if x]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _cleanup_upload(upload_state: UploadState) -> bool:
|
|
265
|
+
"""
|
|
266
|
+
Write the id2iri mapping to a file and print a message to the console.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
upload_state: the current state of the upload
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
success status (deduced from failed_uploads and non-applied stash)
|
|
273
|
+
"""
|
|
274
|
+
write_id2iri_mapping(
|
|
275
|
+
id2iri_mapping=upload_state.iri_resolver.lookup,
|
|
276
|
+
shortcode=upload_state.config.shortcode,
|
|
277
|
+
diagnostics=upload_state.config.diagnostics,
|
|
278
|
+
)
|
|
279
|
+
has_stash_failed = upload_state.pending_stash and not upload_state.pending_stash.is_empty()
|
|
280
|
+
if not upload_state.failed_uploads and not has_stash_failed:
|
|
281
|
+
success = True
|
|
282
|
+
print(f"{datetime.now()}: All resources have successfully been uploaded.")
|
|
283
|
+
logger.info("All resources have successfully been uploaded.")
|
|
284
|
+
else:
|
|
285
|
+
success = False
|
|
286
|
+
if upload_state.failed_uploads:
|
|
287
|
+
res_msg = f"Could not upload the following resources: {upload_state.failed_uploads}"
|
|
288
|
+
print(f"\n{datetime.now()}: WARNING: {res_msg}\n")
|
|
289
|
+
print(f"See {WARNINGS_SAVEPATH} for more information\n")
|
|
290
|
+
logger.warning(res_msg)
|
|
291
|
+
if has_stash_failed:
|
|
292
|
+
stash_msg = f"Could not reapply the following stash items: {upload_state.pending_stash}"
|
|
293
|
+
print(f"\n{datetime.now()}: WARNING: {stash_msg}\n")
|
|
294
|
+
print(f"See {WARNINGS_SAVEPATH} for more information\n")
|
|
295
|
+
logger.warning(stash_msg)
|
|
296
|
+
msg = _save_upload_state(upload_state)
|
|
297
|
+
print(msg)
|
|
298
|
+
|
|
299
|
+
upload_state.config.diagnostics.save_location.unlink(missing_ok=True)
|
|
300
|
+
return success
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _upload_resources(clients: UploadClients, upload_state: UploadState) -> None:
|
|
304
|
+
"""
|
|
305
|
+
Iterates through all resources and tries to upload them to DSP.
|
|
306
|
+
If a temporary exception occurs, the action is repeated until success,
|
|
307
|
+
and if a permanent exception occurs, the resource is skipped.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
clients: the clients needed for the upload
|
|
311
|
+
upload_state: the current state of the upload
|
|
312
|
+
|
|
313
|
+
Raises:
|
|
314
|
+
BaseException: in case of an unhandled exception during resource creation
|
|
315
|
+
XmlUploadInterruptedError: if the number of resources created is equal to the interrupt_after value
|
|
316
|
+
"""
|
|
317
|
+
project_iri = clients.list_client.project_iri
|
|
318
|
+
|
|
319
|
+
iri_lookup = IRILookups(
|
|
320
|
+
project_iri=URIRef(project_iri),
|
|
321
|
+
id_to_iri=upload_state.iri_resolver,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
resource_create_client = ResourceCreateClient(
|
|
325
|
+
con=clients.list_client.con,
|
|
326
|
+
)
|
|
327
|
+
progress_bar = tqdm(upload_state.pending_resources.copy(), desc="Creating Resources", dynamic_ncols=True)
|
|
328
|
+
try:
|
|
329
|
+
for creation_attempts_of_this_round, resource in enumerate(progress_bar):
|
|
330
|
+
_upload_one_resource(
|
|
331
|
+
upload_state=upload_state,
|
|
332
|
+
resource=resource,
|
|
333
|
+
ingest_client=clients.asset_client,
|
|
334
|
+
resource_create_client=resource_create_client,
|
|
335
|
+
iri_lookups=iri_lookup,
|
|
336
|
+
creation_attempts_of_this_round=creation_attempts_of_this_round,
|
|
337
|
+
)
|
|
338
|
+
progress_bar.set_description(f"Creating Resources (failed: {len(upload_state.failed_uploads)})")
|
|
339
|
+
if upload_state.pending_stash:
|
|
340
|
+
_upload_stash(upload_state, clients.list_client.con)
|
|
341
|
+
except XmlUploadInterruptedError as err:
|
|
342
|
+
_handle_upload_error(err, upload_state)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _upload_stash(
|
|
346
|
+
upload_state: UploadState,
|
|
347
|
+
con: Connection,
|
|
348
|
+
) -> None:
|
|
349
|
+
if upload_state.pending_stash and upload_state.pending_stash.standoff_stash:
|
|
350
|
+
upload_stashed_xml_texts(upload_state, con)
|
|
351
|
+
if upload_state.pending_stash and upload_state.pending_stash.link_value_stash:
|
|
352
|
+
upload_stashed_resptr_props(upload_state, con)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _upload_one_resource(
|
|
356
|
+
upload_state: UploadState,
|
|
357
|
+
resource: ProcessedResource,
|
|
358
|
+
ingest_client: AssetClient,
|
|
359
|
+
resource_create_client: ResourceCreateClient,
|
|
360
|
+
iri_lookups: IRILookups,
|
|
361
|
+
creation_attempts_of_this_round: int,
|
|
362
|
+
) -> None:
|
|
363
|
+
media_info = None
|
|
364
|
+
if resource.file_value:
|
|
365
|
+
try:
|
|
366
|
+
ingest_result = ingest_client.get_bitstream_info(resource.file_value)
|
|
367
|
+
except PermanentConnectionError as err:
|
|
368
|
+
_handle_permanent_connection_error(err)
|
|
369
|
+
except KeyboardInterrupt:
|
|
370
|
+
_handle_keyboard_interrupt()
|
|
371
|
+
if not ingest_result:
|
|
372
|
+
upload_state.failed_uploads.append(resource.res_id)
|
|
373
|
+
return
|
|
374
|
+
media_info = ingest_result
|
|
375
|
+
|
|
376
|
+
iri = None
|
|
377
|
+
try:
|
|
378
|
+
serialised_resource = create_resource_with_values(
|
|
379
|
+
resource=resource, bitstream_information=media_info, lookups=iri_lookups
|
|
380
|
+
)
|
|
381
|
+
logger.info(f"Attempting to create resource {resource.res_id} (label: {resource.label})...")
|
|
382
|
+
iri = resource_create_client.create_resource(serialised_resource, resource_has_bitstream=bool(media_info))
|
|
383
|
+
except (PermanentTimeOutError, KeyboardInterrupt) as err:
|
|
384
|
+
_handle_permanent_timeout_or_keyboard_interrupt(err, resource.res_id)
|
|
385
|
+
except PermanentConnectionError as err:
|
|
386
|
+
_handle_permanent_connection_error(err)
|
|
387
|
+
except Exception as err: # noqa: BLE001 (blind-except)
|
|
388
|
+
err_msg = err.message if isinstance(err, BaseError) else None
|
|
389
|
+
_inform_about_resource_creation_failure(resource, err_msg)
|
|
390
|
+
|
|
391
|
+
try:
|
|
392
|
+
_tidy_up_resource_creation_idempotent(upload_state, iri, resource)
|
|
393
|
+
_interrupt_if_indicated(upload_state, creation_attempts_of_this_round)
|
|
394
|
+
except KeyboardInterrupt:
|
|
395
|
+
_tidy_up_resource_creation_idempotent(upload_state, iri, resource)
|
|
396
|
+
_handle_keyboard_interrupt()
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _handle_permanent_connection_error(err: PermanentConnectionError) -> Never:
|
|
400
|
+
msg = "Lost connection to DSP server, probably because the server is down. "
|
|
401
|
+
msg += f"Please continue later with 'resume-xmlupload'. Reason for this failure: {err.message}"
|
|
402
|
+
logger.error(msg)
|
|
403
|
+
msg += f"\nSee {WARNINGS_SAVEPATH} for more information."
|
|
404
|
+
raise XmlUploadInterruptedError(msg) from None
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _handle_keyboard_interrupt() -> Never:
|
|
408
|
+
warnings.warn(DspToolsUserWarning("xmlupload manually interrupted. Tidying up, then exit..."))
|
|
409
|
+
msg = "xmlupload manually interrupted. Please continue later with 'resume-xmlupload'"
|
|
410
|
+
raise XmlUploadInterruptedError(msg) from None
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _handle_permanent_timeout_or_keyboard_interrupt(
|
|
414
|
+
err: PermanentTimeOutError | KeyboardInterrupt, res_id: str
|
|
415
|
+
) -> Never:
|
|
416
|
+
warnings.warn(DspToolsUserWarning(f"{type(err).__name__}: Tidying up, then exit..."))
|
|
417
|
+
msg = (
|
|
418
|
+
f"There was a {type(err).__name__} while trying to create resource '{res_id}'.\n"
|
|
419
|
+
f"It is unclear if the resource '{res_id}' was created successfully or not.\n"
|
|
420
|
+
f"Please check manually in the DSP-APP or DB.\n"
|
|
421
|
+
f"In case of successful creation, call 'resume-xmlupload' with the flag "
|
|
422
|
+
f"'--skip-first-resource' to prevent duplication.\n"
|
|
423
|
+
f"If not, a normal 'resume-xmlupload' can be started."
|
|
424
|
+
)
|
|
425
|
+
logger.error(msg)
|
|
426
|
+
raise XmlUploadInterruptedError(msg) from None
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _interrupt_if_indicated(upload_state: UploadState, creation_attempts_of_this_round: int) -> None:
|
|
430
|
+
# if the interrupt_after value is not set, the upload will not be interrupted
|
|
431
|
+
interrupt_after = upload_state.config.interrupt_after or 999_999_999
|
|
432
|
+
if creation_attempts_of_this_round + 1 >= interrupt_after:
|
|
433
|
+
msg = f"Interrupted: Maximum number of resources was reached ({upload_state.config.interrupt_after})"
|
|
434
|
+
raise XmlUploadInterruptedError(msg)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _tidy_up_resource_creation_idempotent(
|
|
438
|
+
upload_state: UploadState,
|
|
439
|
+
iri: str | None,
|
|
440
|
+
resource: ProcessedResource,
|
|
441
|
+
) -> None:
|
|
442
|
+
previous_successful = len(upload_state.iri_resolver.lookup)
|
|
443
|
+
previous_failed = len(upload_state.failed_uploads)
|
|
444
|
+
upcoming = len(upload_state.pending_resources)
|
|
445
|
+
current_res = previous_successful + previous_failed + 1
|
|
446
|
+
total_res = previous_successful + previous_failed + upcoming
|
|
447
|
+
if iri:
|
|
448
|
+
# resource creation succeeded: update the iri_resolver
|
|
449
|
+
upload_state.iri_resolver.lookup[resource.res_id] = iri
|
|
450
|
+
msg = f"Created resource {current_res}/{total_res}: '{resource.label}' (ID: '{resource.res_id}', IRI: '{iri}')"
|
|
451
|
+
logger.info(msg)
|
|
452
|
+
else: # noqa: PLR5501
|
|
453
|
+
# resource creation failed gracefully: register it as failed
|
|
454
|
+
if resource.res_id not in upload_state.failed_uploads:
|
|
455
|
+
upload_state.failed_uploads.append(resource.res_id)
|
|
456
|
+
|
|
457
|
+
if resource in upload_state.pending_resources:
|
|
458
|
+
upload_state.pending_resources.remove(resource)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _inform_about_resource_creation_failure(resource: ProcessedResource, err_msg: str | None) -> None:
|
|
462
|
+
log_msg = f"Unable to create resource '{resource.label}' ({resource.res_id})\n"
|
|
463
|
+
if err_msg:
|
|
464
|
+
log_msg += err_msg
|
|
465
|
+
logger.exception(log_msg)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def _handle_upload_error(err: BaseException, upload_state: UploadState) -> None:
|
|
469
|
+
"""
|
|
470
|
+
In case the xmlupload must be interrupted,
|
|
471
|
+
e.g. because of an error that could not be handled,
|
|
472
|
+
or due to keyboard interrupt,
|
|
473
|
+
this method ensures
|
|
474
|
+
that all information about what is already in DSP
|
|
475
|
+
is written into diagnostic files.
|
|
476
|
+
|
|
477
|
+
It then quits the Python interpreter with exit code 1.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
err: the error that was the cause of the abort
|
|
481
|
+
upload_state: the current state of the upload
|
|
482
|
+
"""
|
|
483
|
+
if isinstance(err, XmlUploadInterruptedError):
|
|
484
|
+
msg = "\n==========================================\n" + err.message + "\n"
|
|
485
|
+
exit_code = 0
|
|
486
|
+
else:
|
|
487
|
+
msg = (
|
|
488
|
+
f"\n==========================================\n"
|
|
489
|
+
f"{datetime.now()}: xmlupload must be aborted because of an error.\n"
|
|
490
|
+
f"Error message: '{err}'\n"
|
|
491
|
+
f"See {WARNINGS_SAVEPATH} for more information\n"
|
|
492
|
+
)
|
|
493
|
+
exit_code = 1
|
|
494
|
+
|
|
495
|
+
msg += _save_upload_state(upload_state)
|
|
496
|
+
|
|
497
|
+
if failed := upload_state.failed_uploads:
|
|
498
|
+
msg += f"Independently from this, there were some resources that could not be uploaded: {failed}\n"
|
|
499
|
+
|
|
500
|
+
if exit_code == 1:
|
|
501
|
+
logger.error(msg)
|
|
502
|
+
else:
|
|
503
|
+
logger.info(msg)
|
|
504
|
+
print(msg)
|
|
505
|
+
|
|
506
|
+
sys.exit(exit_code)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def _save_upload_state(upload_state: UploadState) -> str:
|
|
510
|
+
save_location = upload_state.config.diagnostics.save_location
|
|
511
|
+
save_location.unlink(missing_ok=True)
|
|
512
|
+
save_location.touch(exist_ok=True)
|
|
513
|
+
with open(save_location, "wb") as file:
|
|
514
|
+
pickle.dump(upload_state, file)
|
|
515
|
+
logger.info(f"Saved the current upload state to {save_location}")
|
|
516
|
+
return f"Saved the current upload state to {save_location}.\n"
|
|
File without changes
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from dotenv import find_dotenv
|
|
6
|
+
from dotenv import load_dotenv
|
|
7
|
+
from loguru import logger
|
|
8
|
+
|
|
9
|
+
load_dotenv(dotenv_path=find_dotenv(usecwd=True))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _make_and_get_logs_directory() -> Path:
|
|
13
|
+
"""Get the base .dsp-tools directory, creating it if it doesn't exist."""
|
|
14
|
+
base_dir = Path.home() / ".dsp-tools" / "logs"
|
|
15
|
+
base_dir.mkdir(exist_ok=True, parents=True)
|
|
16
|
+
return base_dir
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S-%f")[:-3]
|
|
20
|
+
|
|
21
|
+
LOGGER_SAVEPATH = (_make_and_get_logs_directory() / f"{timestamp}_logging.log").absolute()
|
|
22
|
+
WARNINGS_SAVEPATH = Path("warnings.log")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def logger_config() -> None:
|
|
26
|
+
"""
|
|
27
|
+
This function configures the log files.
|
|
28
|
+
Currently, there are three sinks:
|
|
29
|
+
- timestamp_logging.log in ~/.dsp-tools/logs/ contains the entire stack-trace
|
|
30
|
+
- warnings.log in the cwd only with level warning and higher for the user (no stack-trace)
|
|
31
|
+
OR a complete logging.log file with the stack-trace if configured in the .env
|
|
32
|
+
- print output on the terminal, formatted the same as the warnings.log
|
|
33
|
+
"""
|
|
34
|
+
# If this is not removed, the default formatting is also printed out on the terminal
|
|
35
|
+
logger.remove()
|
|
36
|
+
|
|
37
|
+
text_format = "<level>{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {message}</level>"
|
|
38
|
+
rotation_size = "100 MB"
|
|
39
|
+
|
|
40
|
+
logger.add(
|
|
41
|
+
sink=LOGGER_SAVEPATH,
|
|
42
|
+
format=text_format,
|
|
43
|
+
backtrace=True,
|
|
44
|
+
diagnose=True,
|
|
45
|
+
delay=True,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
additional_log = str(os.getenv("DSP_TOOLS_SAVE_ADDITIONAL_LOG_FILE_IN_CWD"))
|
|
49
|
+
if additional_log.lower() == "true":
|
|
50
|
+
logger.add(
|
|
51
|
+
sink=Path("logging.log"),
|
|
52
|
+
format=text_format,
|
|
53
|
+
backtrace=True,
|
|
54
|
+
diagnose=True,
|
|
55
|
+
rotation=rotation_size,
|
|
56
|
+
retention=2,
|
|
57
|
+
delay=True,
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
logger.add(
|
|
61
|
+
sink=WARNINGS_SAVEPATH,
|
|
62
|
+
level="WARNING",
|
|
63
|
+
format=text_format,
|
|
64
|
+
backtrace=False,
|
|
65
|
+
diagnose=False,
|
|
66
|
+
rotation=rotation_size,
|
|
67
|
+
retention=2,
|
|
68
|
+
delay=True,
|
|
69
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from typing import TextIO
|
|
3
|
+
|
|
4
|
+
from dsp_tools.error.custom_warnings import DspToolsWarning
|
|
5
|
+
from dsp_tools.error.xmllib_warnings import XmllibUserInfoBase
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def initialize_warnings() -> None:
|
|
9
|
+
"""
|
|
10
|
+
This function makes sure that DSP-TOOLS internal warnings are displayed in their custom way how they specify it.
|
|
11
|
+
This is done by monkeypatching the behavior of the warnings module, as officially recommended by the Python docs:
|
|
12
|
+
https://docs.python.org/3/library/warnings.html#warnings.showwarning
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
built_in_showwarning = warnings.showwarning
|
|
16
|
+
|
|
17
|
+
def _custom_showwarning(
|
|
18
|
+
message: Warning | str,
|
|
19
|
+
category: type[Warning],
|
|
20
|
+
filename: str,
|
|
21
|
+
lineno: int,
|
|
22
|
+
file: TextIO | None = None,
|
|
23
|
+
line: str | None = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
if issubclass(category, DspToolsWarning):
|
|
26
|
+
category.showwarning(str(message))
|
|
27
|
+
elif issubclass(category, XmllibUserInfoBase):
|
|
28
|
+
category.showwarning(str(message))
|
|
29
|
+
else:
|
|
30
|
+
built_in_showwarning(message, category, filename, lineno, file, line)
|
|
31
|
+
|
|
32
|
+
warnings.showwarning = _custom_showwarning
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from abc import abstractmethod
|
|
3
|
+
|
|
4
|
+
from dsp_tools.utils.ansi_colors import BOLD_RED
|
|
5
|
+
from dsp_tools.utils.ansi_colors import RESET_TO_DEFAULT
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DspToolsWarning(Warning, ABC):
|
|
9
|
+
"""Abstract base class for warnings that implement a custom showwarnings() function"""
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def showwarning(cls, message: str) -> None:
|
|
14
|
+
"""Functionality that should be executed when a warning of this class is emitted"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DspToolsUserWarning(DspToolsWarning):
|
|
18
|
+
"""Class for general user-facing warnings"""
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def showwarning(cls, message: str) -> None:
|
|
22
|
+
"""Print the warning, without context"""
|
|
23
|
+
print(BOLD_RED + f"WARNING: {message}" + RESET_TO_DEFAULT)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DspToolsFutureWarning(DspToolsWarning, FutureWarning):
|
|
27
|
+
"""Class for user-facing deprecation warnings"""
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def showwarning(cls, message: str) -> None:
|
|
31
|
+
"""Print the warning, without context"""
|
|
32
|
+
print(BOLD_RED + f"DEPRECATION WARNING: {message}" + RESET_TO_DEFAULT)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DspToolsUnexpectedStatusCodeWarning(DspToolsWarning):
|
|
36
|
+
@classmethod
|
|
37
|
+
def showwarning(cls, message: str) -> None:
|
|
38
|
+
"""Print the warning, without context"""
|
|
39
|
+
print(BOLD_RED + f"ERROR: {message}" + RESET_TO_DEFAULT)
|