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
knora/knora.py
DELETED
|
@@ -1,2108 +0,0 @@
|
|
|
1
|
-
from typing import List, Set, Dict, Tuple, Optional, Any, Union
|
|
2
|
-
from urllib.parse import quote_plus
|
|
3
|
-
from rdflib import Graph
|
|
4
|
-
from lxml import etree
|
|
5
|
-
import requests
|
|
6
|
-
import json
|
|
7
|
-
import urllib
|
|
8
|
-
import pprint
|
|
9
|
-
import validators
|
|
10
|
-
import re
|
|
11
|
-
from rfc3987 import parse
|
|
12
|
-
from pprint import pprint
|
|
13
|
-
import sys
|
|
14
|
-
|
|
15
|
-
# TODO: recheck all the documentation of this file
|
|
16
|
-
"""
|
|
17
|
-
Properties in knora-api:
|
|
18
|
-
|
|
19
|
-
- :hasValue
|
|
20
|
-
- :hasColor
|
|
21
|
-
- :hasComment
|
|
22
|
-
- :hasGeometry
|
|
23
|
-
- :hasLinkTo
|
|
24
|
-
- :isPartOf
|
|
25
|
-
- :isRegionOf
|
|
26
|
-
- :isAnnotationOf
|
|
27
|
-
- :seqnum
|
|
28
|
-
|
|
29
|
-
Classes in knora-api:
|
|
30
|
-
- :Resource
|
|
31
|
-
- :StillImageRepresentation
|
|
32
|
-
- :TextRepresentation
|
|
33
|
-
- :AudioRepresentation
|
|
34
|
-
- :DDDRepresentation
|
|
35
|
-
- :DocumentRepresentation
|
|
36
|
-
- :MovingImageRepresentation
|
|
37
|
-
- :Annotation -> :hasComment, :isAnnotationOf, :isAnnotationOfValue
|
|
38
|
-
- :LinkObj -> :hasComment, :hasLinkTo, :hasLinkToValue
|
|
39
|
-
- :LinkValue [reification node]
|
|
40
|
-
- :Region -> :hasColor, :isRegionOf, :hasGeometry, :isRegionOfValue, :hasComment
|
|
41
|
-
|
|
42
|
-
For lists:
|
|
43
|
-
|
|
44
|
-
- :ListNode -> :hasSubListNode, :listNodePosition, :listNodeName, :isRootNode, :hasRootNode, :attachedToProject
|
|
45
|
-
|
|
46
|
-
Values in knora-api:
|
|
47
|
-
|
|
48
|
-
- :Value
|
|
49
|
-
- :TextValue -> :SimpleText, :TextArea
|
|
50
|
-
- :ColorValue -> :Colorpicker
|
|
51
|
-
- :DateValue -> :Date
|
|
52
|
-
- :DecimalValue -> :SimpleText
|
|
53
|
-
- :GeomValue -> :Geometry
|
|
54
|
-
- :GeonameValue -> :Geonames
|
|
55
|
-
- :IntValue -> :SimpleText, :Spinbox, :Slider
|
|
56
|
-
- :BooleanValue -> :Checkbox
|
|
57
|
-
- :UriValue -> :SimpleText
|
|
58
|
-
- :IntervalValue
|
|
59
|
-
- :ListValue -> :Pulldown
|
|
60
|
-
|
|
61
|
-
GUI elements
|
|
62
|
-
|
|
63
|
-
- :Colorpicker -> ncolors=integer
|
|
64
|
-
- :Date
|
|
65
|
-
- :Geometry
|
|
66
|
-
- :Geonames
|
|
67
|
-
- :Interval
|
|
68
|
-
- :List -> hlist(required)=<iri>
|
|
69
|
-
- :Pulldown -> hlist(required)=<iri>
|
|
70
|
-
- :Radio -> hlist(required)=<iri>
|
|
71
|
-
- :Richtext
|
|
72
|
-
- :Searchbox -> numprops=integer
|
|
73
|
-
- :SimpleText -> maxlength=integer, size=integer
|
|
74
|
-
- :Slider -> max(required)=decimal, min(required)=decimal
|
|
75
|
-
- :Spinbox -> max=decimal, min=decimal
|
|
76
|
-
- :Textarea -> cols=integer, rows=integer, width=percent, wrap=string(soft|hard)
|
|
77
|
-
- :Checkbox
|
|
78
|
-
- :Fileupload
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class KnoraError(Exception):
|
|
83
|
-
"""Handles errors happening in this file"""
|
|
84
|
-
|
|
85
|
-
def __init__(self, message):
|
|
86
|
-
self.message = message
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
class KnoraStandoffXml:
|
|
90
|
-
"""Used to handle XML strings for standoff markup"""
|
|
91
|
-
|
|
92
|
-
iriregexp = re.compile(r'IRI:[^:]*:IRI')
|
|
93
|
-
|
|
94
|
-
def __init__(self, xmlstr: str):
|
|
95
|
-
self.xmlstr = xmlstr
|
|
96
|
-
|
|
97
|
-
def getXml(self):
|
|
98
|
-
return self.xmlstr
|
|
99
|
-
|
|
100
|
-
def findall(self):
|
|
101
|
-
return KnoraStandoffXml.iriregexp.findall(self.xmlstr)
|
|
102
|
-
|
|
103
|
-
def replace(self, fromStr: str, toStr: str):
|
|
104
|
-
self.xmlstr.replace(fromStr, toStr)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
class KnoraStandoffXmlEncoder(json.JSONEncoder):
|
|
108
|
-
"""Classes used as wrapper for knora standoff-XML"""
|
|
109
|
-
def default(self, obj):
|
|
110
|
-
if isinstance(obj, KnoraStandoffXml):
|
|
111
|
-
return obj.getXml()
|
|
112
|
-
return json.JSONEncoder.default(self, obj)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
class Knora:
|
|
116
|
-
"""
|
|
117
|
-
This is the main class which holds all the methods for communication with the Knora backend.
|
|
118
|
-
"""
|
|
119
|
-
|
|
120
|
-
def __init__(self, server: str, prefixes: Dict[str, str] = None):
|
|
121
|
-
"""
|
|
122
|
-
Constructor requiring the server address, the user and password of KNORA
|
|
123
|
-
:param server: Address of the server, e.g https://api.dasch.swiss
|
|
124
|
-
:param prefixes: Ontology prefixes used
|
|
125
|
-
"""
|
|
126
|
-
self.server = server
|
|
127
|
-
self.prefixes = prefixes
|
|
128
|
-
self.token = None
|
|
129
|
-
|
|
130
|
-
def login(self, email: str, password: str) -> None:
|
|
131
|
-
"""
|
|
132
|
-
Method to login into KNORA which creates a session token.
|
|
133
|
-
:param email: Email of user, e.g., root@example.com
|
|
134
|
-
:param password: Password of the user, e.g. test
|
|
135
|
-
"""
|
|
136
|
-
credentials = {
|
|
137
|
-
"email": email,
|
|
138
|
-
"password": password
|
|
139
|
-
}
|
|
140
|
-
jsondata = json.dumps(credentials)
|
|
141
|
-
|
|
142
|
-
req = requests.post(
|
|
143
|
-
self.server + '/v2/authentication',
|
|
144
|
-
headers={'Content-Type': 'application/json; charset=UTF-8'},
|
|
145
|
-
data=jsondata
|
|
146
|
-
)
|
|
147
|
-
self.on_api_error(req)
|
|
148
|
-
result = req.json()
|
|
149
|
-
self.token = result["token"]
|
|
150
|
-
|
|
151
|
-
def get_token(self) -> str:
|
|
152
|
-
return self.token
|
|
153
|
-
|
|
154
|
-
def logout(self) -> None:
|
|
155
|
-
if self.token is not None:
|
|
156
|
-
req = requests.delete(
|
|
157
|
-
self.server + '/v2/authentication',
|
|
158
|
-
headers={'Authorization': 'Bearer ' + self.token}
|
|
159
|
-
)
|
|
160
|
-
self.on_api_error(req)
|
|
161
|
-
self.token = None
|
|
162
|
-
|
|
163
|
-
def __del__(self):
|
|
164
|
-
self.logout()
|
|
165
|
-
|
|
166
|
-
def on_api_error(self, res) -> None:
|
|
167
|
-
"""
|
|
168
|
-
Method to check for any API errors
|
|
169
|
-
:param res: The input to check, usually JSON format
|
|
170
|
-
:return: Possible KnoraError that is being raised
|
|
171
|
-
"""
|
|
172
|
-
|
|
173
|
-
if (res.status_code != 200):
|
|
174
|
-
raise KnoraError("KNORA-ERROR: status code=" + str(res.status_code) + "\nMessage:" + res.text)
|
|
175
|
-
|
|
176
|
-
if 'error' in res:
|
|
177
|
-
raise KnoraError("KNORA-ERROR: API error: " + res.error)
|
|
178
|
-
|
|
179
|
-
#==========================================================================
|
|
180
|
-
# project related methods
|
|
181
|
-
#
|
|
182
|
-
|
|
183
|
-
def get_existing_projects(self, full: bool = False) -> List[Any]:
|
|
184
|
-
"""Returns a list of existing projects
|
|
185
|
-
|
|
186
|
-
:return: List of existing projects
|
|
187
|
-
"""
|
|
188
|
-
|
|
189
|
-
req = requests.get(self.server + '/admin/projects',
|
|
190
|
-
headers={'Authorization': 'Bearer ' + self.token})
|
|
191
|
-
self.on_api_error(req)
|
|
192
|
-
result = req.json()
|
|
193
|
-
|
|
194
|
-
if 'projects' not in result:
|
|
195
|
-
raise KnoraError("KNORA-ERROR:\n Request got no projects!")
|
|
196
|
-
else:
|
|
197
|
-
if full:
|
|
198
|
-
return result['projects']
|
|
199
|
-
else:
|
|
200
|
-
return list(map(lambda a: a['id'], result['projects']))
|
|
201
|
-
|
|
202
|
-
def get_project(self, shortcode: str) -> Dict[str,Any]:
|
|
203
|
-
"""Returns project data of given project
|
|
204
|
-
|
|
205
|
-
:param shortcode: Shortcode of object
|
|
206
|
-
:return: JSON containing the project information
|
|
207
|
-
"""
|
|
208
|
-
|
|
209
|
-
url = self.server + '/admin/projects/shortcode/' + shortcode
|
|
210
|
-
req = requests.get(url, headers={'Authorization': 'Bearer ' + self.token})
|
|
211
|
-
self.on_api_error(req)
|
|
212
|
-
|
|
213
|
-
result = req.json()
|
|
214
|
-
|
|
215
|
-
return result["project"]
|
|
216
|
-
|
|
217
|
-
def project_exists(self, proj_iri: str) -> bool:
|
|
218
|
-
"""Checks if a given project exists
|
|
219
|
-
|
|
220
|
-
:return: Boolean
|
|
221
|
-
"""
|
|
222
|
-
|
|
223
|
-
projects = self.get_existing_projects()
|
|
224
|
-
return proj_iri in projects
|
|
225
|
-
|
|
226
|
-
def create_project(
|
|
227
|
-
self,
|
|
228
|
-
shortcode: str,
|
|
229
|
-
shortname: str,
|
|
230
|
-
longname: str,
|
|
231
|
-
descriptions: Optional[Dict[str, str]] = None,
|
|
232
|
-
keywords: Optional[List[str]] = None,
|
|
233
|
-
logo: Optional[str] = None) -> str:
|
|
234
|
-
"""
|
|
235
|
-
Create a new project
|
|
236
|
-
|
|
237
|
-
:param shortcode: Dedicated shortcode of project
|
|
238
|
-
:param shortname: Short name of the project (e.g acronym)
|
|
239
|
-
:param longname: Long name of project
|
|
240
|
-
:param descriptions: Dict of the form {lang: descr, …} for the description of the project [Default: None]
|
|
241
|
-
:param keywords: List of keywords
|
|
242
|
-
:param logo: Link to the project logo [default: None]
|
|
243
|
-
:return: Project IRI
|
|
244
|
-
"""
|
|
245
|
-
|
|
246
|
-
descriptions = list(map(lambda p: {"language": p[0], "value": p[1]}, descriptions.items()))
|
|
247
|
-
|
|
248
|
-
project = {
|
|
249
|
-
"shortname": shortname,
|
|
250
|
-
"shortcode": shortcode,
|
|
251
|
-
"longname": longname,
|
|
252
|
-
"status": True,
|
|
253
|
-
"selfjoin": False
|
|
254
|
-
}
|
|
255
|
-
if descriptions is not None:
|
|
256
|
-
project['description'] = descriptions
|
|
257
|
-
if keywords is not None:
|
|
258
|
-
project['keywords'] = keywords
|
|
259
|
-
if logo is not None:
|
|
260
|
-
project['logo'] = logo
|
|
261
|
-
|
|
262
|
-
jsondata = json.dumps(project)
|
|
263
|
-
# print(jsondata)
|
|
264
|
-
|
|
265
|
-
req = requests.post(self.server + "/admin/projects",
|
|
266
|
-
headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
267
|
-
'Authorization': 'Bearer ' + self.token},
|
|
268
|
-
data=jsondata)
|
|
269
|
-
self.on_api_error(req)
|
|
270
|
-
|
|
271
|
-
res = req.json()
|
|
272
|
-
return res["project"]["id"]
|
|
273
|
-
|
|
274
|
-
def update_project(
|
|
275
|
-
self,
|
|
276
|
-
shortcode: str,
|
|
277
|
-
shortname: Optional[str] = None,
|
|
278
|
-
longname: Optional[str] = None,
|
|
279
|
-
descriptions: Optional[Dict[str, str]] = None,
|
|
280
|
-
keywords: Optional[List[str]] = None,
|
|
281
|
-
logo: Optional[str] = None) -> str:
|
|
282
|
-
"""
|
|
283
|
-
Update project information
|
|
284
|
-
|
|
285
|
-
:param shortcode:
|
|
286
|
-
:param shortname:
|
|
287
|
-
:param longname:
|
|
288
|
-
:param descriptions:
|
|
289
|
-
:param keywords:
|
|
290
|
-
:param logo:
|
|
291
|
-
:return:
|
|
292
|
-
"""
|
|
293
|
-
|
|
294
|
-
descriptions = list(map(lambda p: {"language": p[0], "value": p[1]}, descriptions.items()))
|
|
295
|
-
|
|
296
|
-
project = {
|
|
297
|
-
"longname": longname,
|
|
298
|
-
"description": descriptions,
|
|
299
|
-
"keywords": keywords,
|
|
300
|
-
"logo": logo,
|
|
301
|
-
"status": True,
|
|
302
|
-
"selfjoin": False
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
jsondata = json.dumps(project)
|
|
306
|
-
url = self.server + '/admin/projects/iri/' + quote_plus("http://rdfh.ch/projects/" + shortcode)
|
|
307
|
-
|
|
308
|
-
req = requests.put(url,
|
|
309
|
-
headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
310
|
-
'Authorization': 'Bearer ' + self.token},
|
|
311
|
-
data=jsondata)
|
|
312
|
-
self.on_api_error(req)
|
|
313
|
-
|
|
314
|
-
res = req.json()
|
|
315
|
-
return res['project']['id']
|
|
316
|
-
|
|
317
|
-
#==========================================================================
|
|
318
|
-
# Group related methods
|
|
319
|
-
#
|
|
320
|
-
|
|
321
|
-
def get_groups(self) -> List[Dict[str,Any]]:
|
|
322
|
-
"""
|
|
323
|
-
Returns the list of existing groups
|
|
324
|
-
|
|
325
|
-
:return: List of projects
|
|
326
|
-
"""
|
|
327
|
-
url = self.server + '/admin/groups'
|
|
328
|
-
|
|
329
|
-
req = requests.get(url, headers={'Authorization': 'Bearer ' + self.token})
|
|
330
|
-
|
|
331
|
-
self.on_api_error(req)
|
|
332
|
-
res = req.json()
|
|
333
|
-
|
|
334
|
-
return res['groups']
|
|
335
|
-
|
|
336
|
-
def get_group_by_iri(self, group_iri: str) -> Dict[str,Any]:
|
|
337
|
-
"""
|
|
338
|
-
Returns information about the given group
|
|
339
|
-
:param group_iri: IRI of the group
|
|
340
|
-
:return: Information about the specific group
|
|
341
|
-
"""
|
|
342
|
-
url = self.server + '/admin/groups/' + quote_plus(group_iri)
|
|
343
|
-
|
|
344
|
-
req = requests.get(url, headers={'Authorization': 'Bearer ' + self.token})
|
|
345
|
-
|
|
346
|
-
self.on_api_error(req)
|
|
347
|
-
res = req.json()
|
|
348
|
-
|
|
349
|
-
return res['group']
|
|
350
|
-
|
|
351
|
-
def get_group_by_pshortname_and_gname(self,
|
|
352
|
-
project_shortname: str,
|
|
353
|
-
group_name: str) -> Union[str,None]:
|
|
354
|
-
"""
|
|
355
|
-
Get a group by project shortname and group name
|
|
356
|
-
|
|
357
|
-
:param project_shortname: Project shortname
|
|
358
|
-
:param group_name: Group name
|
|
359
|
-
:return: IRI of the group
|
|
360
|
-
"""
|
|
361
|
-
groupinfos = self.get_groups()
|
|
362
|
-
for groupinfo in groupinfos:
|
|
363
|
-
if groupinfo["project"]["shortname"] == project_shortname and groupinfo["name"] == group_name:
|
|
364
|
-
return groupinfo["id"]
|
|
365
|
-
return None
|
|
366
|
-
|
|
367
|
-
def get_group_by_pshortcode_and_gname(self,
|
|
368
|
-
project_shortcode: str,
|
|
369
|
-
group_name: str) -> Union[str, None]:
|
|
370
|
-
"""
|
|
371
|
-
Get a group by project shortcode and group name
|
|
372
|
-
|
|
373
|
-
:param project_shortname: Project shortcode
|
|
374
|
-
:param group_name: Group name
|
|
375
|
-
:return: IRI of the group
|
|
376
|
-
"""
|
|
377
|
-
groupinfos = self.get_groups()
|
|
378
|
-
for groupinfo in groupinfos:
|
|
379
|
-
if groupinfo["project"]["shortcode"] == project_shortcode and groupinfo["name"] == group_name:
|
|
380
|
-
return groupinfo["id"]
|
|
381
|
-
return None
|
|
382
|
-
|
|
383
|
-
def get_group_by_piri_and_gname(self,
|
|
384
|
-
project_iri: str,
|
|
385
|
-
group_name: str) -> Union[str, None]:
|
|
386
|
-
"""
|
|
387
|
-
Get a group by project shortcode and group name
|
|
388
|
-
|
|
389
|
-
:param project_shortname: Project shortcode
|
|
390
|
-
:param group_name: Group name
|
|
391
|
-
:return: IRI of the group
|
|
392
|
-
"""
|
|
393
|
-
groupinfos = self.get_groups()
|
|
394
|
-
for groupinfo in groupinfos:
|
|
395
|
-
if groupinfo["project"]["id"] == project_iri and groupinfo["name"] == group_name:
|
|
396
|
-
return groupinfo["id"]
|
|
397
|
-
return None
|
|
398
|
-
|
|
399
|
-
def create_group(self,
|
|
400
|
-
project_iri: str,
|
|
401
|
-
name: str,
|
|
402
|
-
description: Union[str, Dict[str,str]],
|
|
403
|
-
status: bool = True,
|
|
404
|
-
selfjoin: bool = False) -> str:
|
|
405
|
-
"""
|
|
406
|
-
Create a new group
|
|
407
|
-
|
|
408
|
-
:param name: Name of the group
|
|
409
|
-
:param description: Either a string with the descrioption, or a List of Dicts in the form [{"value": "descr", "language": "lang"},…]
|
|
410
|
-
:param project_iri: IRI of the project where the group belongs to
|
|
411
|
-
:param status: Active (True) or not active (False) [default: True]
|
|
412
|
-
:param selfjoin: ?? [default: False]
|
|
413
|
-
:return: IRI of the group
|
|
414
|
-
"""
|
|
415
|
-
|
|
416
|
-
groupinfo = {
|
|
417
|
-
"name": name,
|
|
418
|
-
"description": description if isinstance(description, str) else list(map(lambda p: {"@language": p[0], "@value": p[1]}, description.items())),
|
|
419
|
-
"project": project_iri,
|
|
420
|
-
"status": status,
|
|
421
|
-
"selfjoin": selfjoin
|
|
422
|
-
}
|
|
423
|
-
jsondata = json.dumps(groupinfo)
|
|
424
|
-
|
|
425
|
-
url = self.server + '/admin/groups'
|
|
426
|
-
|
|
427
|
-
req = requests.post(url,
|
|
428
|
-
headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
429
|
-
'Authorization': 'Bearer ' + self.token},
|
|
430
|
-
data=jsondata)
|
|
431
|
-
|
|
432
|
-
self.on_api_error(req)
|
|
433
|
-
res = req.json()
|
|
434
|
-
return res['group']['id']
|
|
435
|
-
|
|
436
|
-
def update_group(self,
|
|
437
|
-
group_iri: str,
|
|
438
|
-
name: Optional[str] = None,
|
|
439
|
-
description: Optional[Union[str, Dict[str,str]]] = None,
|
|
440
|
-
selfjoin: Optional[bool] = None) -> Union[str,None]:
|
|
441
|
-
"""
|
|
442
|
-
Modify the data about a group. Only parameters that have to be changed must be indicated
|
|
443
|
-
:param group_iri: IRI of the grouo to be modified
|
|
444
|
-
:param name: New name of the group [optional]
|
|
445
|
-
:param description: Either a string with the descrioption, or a List of Dicts in the form [{"value": "descr", "language": "lang"},…] [optional]
|
|
446
|
-
:param selfjoin: True or False [optional]
|
|
447
|
-
:return: ???
|
|
448
|
-
"""
|
|
449
|
-
|
|
450
|
-
groupinfo = {}
|
|
451
|
-
done = False
|
|
452
|
-
if name is not None:
|
|
453
|
-
groupinfo['name'] = name
|
|
454
|
-
done = True
|
|
455
|
-
if description is not None:
|
|
456
|
-
groupinfo['description'] = description if isinstance(description, str) else list(map(lambda p: {"@language": p[0], "@value": p[1]}, description.items()))
|
|
457
|
-
done = True
|
|
458
|
-
if selfjoin is not None:
|
|
459
|
-
groupinfo['selfjoin'] = selfjoin
|
|
460
|
-
done = True
|
|
461
|
-
if done:
|
|
462
|
-
jsondata = json.dumps(groupinfo)
|
|
463
|
-
|
|
464
|
-
url = self.server + '/admin/groups/' + quote_plus(group_iri)
|
|
465
|
-
|
|
466
|
-
req = requests.put(url,
|
|
467
|
-
headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
468
|
-
'Authorization': 'Bearer ' + self.token},
|
|
469
|
-
data=jsondata)
|
|
470
|
-
self.on_api_error(req)
|
|
471
|
-
res = req.json()
|
|
472
|
-
pprint(res)
|
|
473
|
-
return res['group']['id']
|
|
474
|
-
else:
|
|
475
|
-
return None
|
|
476
|
-
|
|
477
|
-
def change_group_status(self,
|
|
478
|
-
group_iri: str,
|
|
479
|
-
status: bool) -> None:
|
|
480
|
-
"""
|
|
481
|
-
Change the status of th group
|
|
482
|
-
|
|
483
|
-
:param group_iri: IRI of the group
|
|
484
|
-
:param status: Status (active: True, inactive: False)
|
|
485
|
-
|
|
486
|
-
:return: None
|
|
487
|
-
"""
|
|
488
|
-
|
|
489
|
-
statusinfo = {
|
|
490
|
-
"status": status
|
|
491
|
-
}
|
|
492
|
-
jsondata = json.dumps(statusinfo)
|
|
493
|
-
|
|
494
|
-
url = self.server + '/admin/groups/' + quote_plus(group_iri) + '/status'
|
|
495
|
-
|
|
496
|
-
req = requests.put(url,
|
|
497
|
-
headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
498
|
-
'Authorization': 'Bearer ' + self.token},
|
|
499
|
-
data=jsondata)
|
|
500
|
-
self.on_api_error(req)
|
|
501
|
-
res = req.json()
|
|
502
|
-
pprint(res)
|
|
503
|
-
|
|
504
|
-
def delete_group(self,
|
|
505
|
-
group_iri: str) -> None:
|
|
506
|
-
"""
|
|
507
|
-
Delete a group
|
|
508
|
-
:param group_iri: IRI of the group
|
|
509
|
-
:return:
|
|
510
|
-
"""
|
|
511
|
-
url = self.server + '/admin/groups/' + quote_plus(group_iri)
|
|
512
|
-
|
|
513
|
-
req = requests.delete(url,
|
|
514
|
-
headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
515
|
-
'Authorization': 'Bearer ' + self.token})
|
|
516
|
-
self.on_api_error(req)
|
|
517
|
-
res = req.json()
|
|
518
|
-
pprint(res)
|
|
519
|
-
|
|
520
|
-
#==========================================================================
|
|
521
|
-
# User related methods
|
|
522
|
-
#
|
|
523
|
-
|
|
524
|
-
def get_users(self) -> List[Dict[str,Any]]:
|
|
525
|
-
"""
|
|
526
|
-
Get a list of all users
|
|
527
|
-
|
|
528
|
-
:return: Json result.
|
|
529
|
-
"""
|
|
530
|
-
url = self.server + '/admin/users'
|
|
531
|
-
req = requests.get(url, headers={'Authorization': 'Bearer ' + self.token})
|
|
532
|
-
|
|
533
|
-
self.on_api_error(req)
|
|
534
|
-
res = req.json()
|
|
535
|
-
return res['users']
|
|
536
|
-
|
|
537
|
-
def get_user_by_iri(self, user_iri: str):
|
|
538
|
-
"""
|
|
539
|
-
Get single user
|
|
540
|
-
|
|
541
|
-
:return:
|
|
542
|
-
"""
|
|
543
|
-
url = self.server + '/admin/users/iri/' + quote_plus(user_iri)
|
|
544
|
-
req = requests.get(url, headers={'Authorization': 'Bearer ' + self.token})
|
|
545
|
-
|
|
546
|
-
self.on_api_error(req)
|
|
547
|
-
res = req.json()
|
|
548
|
-
return res['user']
|
|
549
|
-
|
|
550
|
-
def get_user_by_email(self, email: str):
|
|
551
|
-
"""
|
|
552
|
-
Get a list of all users
|
|
553
|
-
|
|
554
|
-
:return:
|
|
555
|
-
"""
|
|
556
|
-
url = self.server + '/admin/users/email/' + quote_plus(email)
|
|
557
|
-
req = requests.get(url, headers={'Authorization': 'Bearer ' + self.token})
|
|
558
|
-
|
|
559
|
-
self.on_api_error(req)
|
|
560
|
-
res = req.json()
|
|
561
|
-
return res['user']
|
|
562
|
-
|
|
563
|
-
def create_user(self,
|
|
564
|
-
username: str,
|
|
565
|
-
email: str,
|
|
566
|
-
given_name: str,
|
|
567
|
-
family_name: str,
|
|
568
|
-
password: str,
|
|
569
|
-
lang: str = "en",
|
|
570
|
-
sysadmin: bool = False):
|
|
571
|
-
"""
|
|
572
|
-
Create a new user
|
|
573
|
-
|
|
574
|
-
:param username: The username for login purposes (must be unique)
|
|
575
|
-
:param email: The email address of the user
|
|
576
|
-
:param given_name: The given name (surname, "Vorname", ...)
|
|
577
|
-
:param family_name: The family name
|
|
578
|
-
:param password: The password for the user
|
|
579
|
-
:param lang: language code, either "en", "de", "fr", "it" [default: "en"]
|
|
580
|
-
:param sysadmin: True if the user has system admin rights
|
|
581
|
-
:return: The user ID as IRI
|
|
582
|
-
"""
|
|
583
|
-
|
|
584
|
-
userinfo = {
|
|
585
|
-
"username": username,
|
|
586
|
-
"email": email,
|
|
587
|
-
"givenName": given_name,
|
|
588
|
-
"familyName": family_name,
|
|
589
|
-
"password": password,
|
|
590
|
-
"status": True,
|
|
591
|
-
"lang": lang,
|
|
592
|
-
"systemAdmin": sysadmin
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
jsondata = json.dumps(userinfo)
|
|
596
|
-
url = self.server + '/admin/users'
|
|
597
|
-
|
|
598
|
-
req = requests.post(url,
|
|
599
|
-
headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
600
|
-
'Authorization': 'Bearer ' + self.token},
|
|
601
|
-
data=jsondata)
|
|
602
|
-
|
|
603
|
-
self.on_api_error(req)
|
|
604
|
-
res = req.json()
|
|
605
|
-
|
|
606
|
-
return res['user']['id']
|
|
607
|
-
|
|
608
|
-
def update_user(self,
|
|
609
|
-
user_iri: str,
|
|
610
|
-
username: Optional[str] = None,
|
|
611
|
-
email: Optional[str] = None,
|
|
612
|
-
given_name: Optional[str] = None,
|
|
613
|
-
family_name: Optional[str] = None,
|
|
614
|
-
password: Optional[str] = None,
|
|
615
|
-
lang: Optional[str] = None):
|
|
616
|
-
userinfo: Dict[str,Any] = {};
|
|
617
|
-
if username is not None:
|
|
618
|
-
userinfo["username"] = username
|
|
619
|
-
if email is not None:
|
|
620
|
-
userinfo["email"] = email
|
|
621
|
-
if given_name is not None:
|
|
622
|
-
userinfo["givenName"] = given_name
|
|
623
|
-
if family_name is not None:
|
|
624
|
-
userinfo["familyName"] = family_name
|
|
625
|
-
#if password is not None:
|
|
626
|
-
# update_user["password"] = password
|
|
627
|
-
if lang is not None:
|
|
628
|
-
userinfo["lang"] = lang
|
|
629
|
-
if len(userinfo) > 0:
|
|
630
|
-
url = self.server + '/admin/users/iri/' + quote_plus(user_iri) + '/BasicUserInformation'
|
|
631
|
-
jsondata = json.dumps(userinfo)
|
|
632
|
-
req = requests.put(url,
|
|
633
|
-
headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
634
|
-
'Authorization': 'Bearer ' + self.token},
|
|
635
|
-
data=jsondata)
|
|
636
|
-
self.on_api_error(req)
|
|
637
|
-
|
|
638
|
-
def change_user_password(self,
|
|
639
|
-
user_iri: str,
|
|
640
|
-
admin_password: str,
|
|
641
|
-
new_password: str):
|
|
642
|
-
data = {
|
|
643
|
-
"requesterPassword": admin_password,
|
|
644
|
-
"newPassword": new_password
|
|
645
|
-
}
|
|
646
|
-
url = self.server + '/admin/users/iri/' + quote_plus(user_iri) + '/Password'
|
|
647
|
-
jsondata = json.dumps(data)
|
|
648
|
-
req = requests.put(url,
|
|
649
|
-
headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
650
|
-
'Authorization': 'Bearer ' + self.token},
|
|
651
|
-
data=jsondata)
|
|
652
|
-
self.on_api_error(req)
|
|
653
|
-
|
|
654
|
-
def add_user_to_project(self,
|
|
655
|
-
user_iri: str,
|
|
656
|
-
project_iri: str):
|
|
657
|
-
"""
|
|
658
|
-
Add a user to a project
|
|
659
|
-
|
|
660
|
-
:param user_iri: IRI of the user
|
|
661
|
-
:param project_iri: IRI of the project
|
|
662
|
-
:return: None
|
|
663
|
-
"""
|
|
664
|
-
url = self.server + '/admin/users/iri/' + quote_plus(user_iri) + '/project-memberships/'\
|
|
665
|
-
+ quote_plus(project_iri)
|
|
666
|
-
req = requests.post(url, headers={'Authorization': 'Bearer ' + self.token})
|
|
667
|
-
self.on_api_error(req)
|
|
668
|
-
|
|
669
|
-
return None
|
|
670
|
-
|
|
671
|
-
def rm_user_from_project(self,
|
|
672
|
-
user_iri: str,
|
|
673
|
-
project_iri: str):
|
|
674
|
-
"""
|
|
675
|
-
Remove a user from a project
|
|
676
|
-
|
|
677
|
-
:param user_iri: IRI of the user
|
|
678
|
-
:param project_iri: IRI of the project
|
|
679
|
-
:return: None
|
|
680
|
-
"""
|
|
681
|
-
url = self.server + '/admin/users/iri/' + quote_plus(user_iri) + '/project-memberships/' + quote_plus(
|
|
682
|
-
project_iri)
|
|
683
|
-
req = requests.delete(url, headers={'Authorization': 'Bearer ' + self.token})
|
|
684
|
-
self.on_api_error(req)
|
|
685
|
-
|
|
686
|
-
return None
|
|
687
|
-
|
|
688
|
-
def add_user_to_project_admin(self,
|
|
689
|
-
user_iri: str,
|
|
690
|
-
project_iri: str) -> None:
|
|
691
|
-
"""
|
|
692
|
-
Add a user to the project admin group (knora-admin:ProjectAdmin)
|
|
693
|
-
|
|
694
|
-
:param user_iri: IRI of user
|
|
695
|
-
:param project_iri: IRI of project
|
|
696
|
-
:return: None
|
|
697
|
-
"""
|
|
698
|
-
url = self.server + '/admin/users/iri/' + quote_plus(user_iri) + '/project-admin-memberships/' + quote_plus(
|
|
699
|
-
project_iri)
|
|
700
|
-
req = requests.post(url, headers={'Authorization': 'Bearer ' + self.token})
|
|
701
|
-
self.on_api_error(req)
|
|
702
|
-
return None
|
|
703
|
-
|
|
704
|
-
def rm_user_from_project_admin(self,
|
|
705
|
-
user_iri: str,
|
|
706
|
-
project_iri: str) -> None:
|
|
707
|
-
"""
|
|
708
|
-
Remove a user from the project admin group
|
|
709
|
-
:param user_iri: IRI of user
|
|
710
|
-
:param project_iri: IRI of project
|
|
711
|
-
:return: None
|
|
712
|
-
"""
|
|
713
|
-
url = self.server + '/admin/users/iri/' + quote_plus(user_iri) + '/project-admin-memberships/' + quote_plus(
|
|
714
|
-
project_iri)
|
|
715
|
-
req = requests.delete(url, headers={'Authorization': 'Bearer ' + self.token})
|
|
716
|
-
self.on_api_error(req)
|
|
717
|
-
return None
|
|
718
|
-
|
|
719
|
-
def add_user_to_sysadmin(self, user_iri: str) -> None:
|
|
720
|
-
"""
|
|
721
|
-
Add a user to the project admin group (knora-admin:ProjectAdmin)
|
|
722
|
-
|
|
723
|
-
:param user_iri: IRI of user
|
|
724
|
-
:param project_iri: IRI of project
|
|
725
|
-
:return: None
|
|
726
|
-
"""
|
|
727
|
-
data = {
|
|
728
|
-
"systemAdmin": True
|
|
729
|
-
}
|
|
730
|
-
url = self.server + '/admin/users/iri/' + quote_plus(user_iri) + '/SystemAdmin'
|
|
731
|
-
jsondata = json.dumps(data)
|
|
732
|
-
req = requests.put(url, headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
733
|
-
'Authorization': 'Bearer ' + self.token},
|
|
734
|
-
data=jsondata)
|
|
735
|
-
self.on_api_error(req)
|
|
736
|
-
return None
|
|
737
|
-
|
|
738
|
-
def rm_user_from_sysadmin(self, user_iri: str) -> None:
|
|
739
|
-
"""
|
|
740
|
-
Remove a user from the system admin group
|
|
741
|
-
|
|
742
|
-
:param user_iri: IRI of user
|
|
743
|
-
:param project_iri: IRI of project
|
|
744
|
-
:return: None
|
|
745
|
-
"""
|
|
746
|
-
data = {
|
|
747
|
-
"systemAdmin": False
|
|
748
|
-
}
|
|
749
|
-
url = self.server + '/admin/users/iri/' + quote_plus(user_iri) + '/SystemAdmin'
|
|
750
|
-
jsondata = json.dumps(data)
|
|
751
|
-
req = requests.put(url, headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
752
|
-
'Authorization': 'Bearer ' + self.token},
|
|
753
|
-
data=jsondata)
|
|
754
|
-
self.on_api_error(req)
|
|
755
|
-
return None
|
|
756
|
-
|
|
757
|
-
def add_user_to_group(self,
|
|
758
|
-
user_iri: str,
|
|
759
|
-
group_iri: str) -> None:
|
|
760
|
-
url = self.server + '/admin/users/iri/' + quote_plus(user_iri) + '/group-memberships/' + quote_plus(group_iri)
|
|
761
|
-
|
|
762
|
-
req = requests.post(url, headers={'Authorization': 'Bearer ' + self.token})
|
|
763
|
-
self.on_api_error(req)
|
|
764
|
-
return None
|
|
765
|
-
|
|
766
|
-
def rm_user_from_group(self,
|
|
767
|
-
user_iri: str,
|
|
768
|
-
group_iri: str) -> None:
|
|
769
|
-
url = self.server + '/admin/users/iri/' + quote_plus(user_iri) + '/group-memberships/' + quote_plus(group_iri)
|
|
770
|
-
|
|
771
|
-
req = requests.delete(url, headers={'Authorization': 'Bearer ' + self.token})
|
|
772
|
-
self.on_api_error(req)
|
|
773
|
-
return None
|
|
774
|
-
|
|
775
|
-
#==========================================================================
|
|
776
|
-
# Ontology methods
|
|
777
|
-
#
|
|
778
|
-
|
|
779
|
-
def get_existing_ontologies(self):
|
|
780
|
-
"""
|
|
781
|
-
|
|
782
|
-
:return: Returns the metadata of all existing ontologies on v2/ontologies
|
|
783
|
-
"""
|
|
784
|
-
|
|
785
|
-
req = requests.get(self.server + '/v2/ontologies/metadata',
|
|
786
|
-
headers={'Authorization': 'Bearer ' + self.token})
|
|
787
|
-
result = req.json()
|
|
788
|
-
|
|
789
|
-
if not '@graph' in result:
|
|
790
|
-
raise KnoraError("KNORA-ERROR:\n Request got no graph!")
|
|
791
|
-
else:
|
|
792
|
-
names = list(map(lambda a: a['@id'], result['@graph']))
|
|
793
|
-
return names
|
|
794
|
-
|
|
795
|
-
def get_project_ontologies(self, project_code: str) -> Optional[dict]:
|
|
796
|
-
"""
|
|
797
|
-
|
|
798
|
-
:param project_code:
|
|
799
|
-
:return:
|
|
800
|
-
"""
|
|
801
|
-
|
|
802
|
-
proj = quote_plus("http://rdfh.ch/projects/" + project_code)
|
|
803
|
-
req = requests.get(self.server + "/v2/ontologies/metadata/" + proj,
|
|
804
|
-
headers={'Authorization': 'Bearer ' + self.token})
|
|
805
|
-
self.on_api_error(req)
|
|
806
|
-
result = req.json()
|
|
807
|
-
|
|
808
|
-
if '@graph' in result: # multiple ontologies
|
|
809
|
-
ontos = list(map(lambda a: {
|
|
810
|
-
'iri': a['@id'],
|
|
811
|
-
'label': a['rdfs:label'],
|
|
812
|
-
'moddate': a.get('knora-api:lastModificationDate')
|
|
813
|
-
}, result['@graph']))
|
|
814
|
-
return ontos
|
|
815
|
-
elif '@id' in result: # single ontology
|
|
816
|
-
return [{
|
|
817
|
-
'iri': result['@id'],
|
|
818
|
-
'label': result['rdfs:label'],
|
|
819
|
-
'moddate': result.get('knora-api:lastModificationDate')
|
|
820
|
-
}]
|
|
821
|
-
else:
|
|
822
|
-
return None
|
|
823
|
-
|
|
824
|
-
def ontology_exists(self, onto_iri: str):
|
|
825
|
-
"""
|
|
826
|
-
Checks if an ontology exists
|
|
827
|
-
|
|
828
|
-
:param onto_iri: The possible ontology iri
|
|
829
|
-
:return: boolean
|
|
830
|
-
"""
|
|
831
|
-
|
|
832
|
-
ontos = self.get_existing_ontologies()
|
|
833
|
-
|
|
834
|
-
return onto_iri in ontos
|
|
835
|
-
|
|
836
|
-
def get_ontology_lastmoddate(self, onto_iri: str):
|
|
837
|
-
"""
|
|
838
|
-
Retrieves the lastModificationDate of a Ontology
|
|
839
|
-
|
|
840
|
-
:param onto_iri: The ontology to retrieve the lastModificationDate from.
|
|
841
|
-
:return: The lastModificationDate if it exists. Else, this method returns a dict with (id, None). If the ontology does not exist, it return None.
|
|
842
|
-
"""
|
|
843
|
-
|
|
844
|
-
req = requests.get(self.server + '/v2/ontologies/metadata',
|
|
845
|
-
headers={'Authorization': 'Bearer ' + self.token})
|
|
846
|
-
result = req.json()
|
|
847
|
-
|
|
848
|
-
all_ontos = {}
|
|
849
|
-
|
|
850
|
-
for onto in result['@graph']:
|
|
851
|
-
if 'knora-api:lastModificationDate' in onto:
|
|
852
|
-
all_ontos.__setitem__(onto['@id'], onto['knora-api:lastModificationDate'])
|
|
853
|
-
else:
|
|
854
|
-
all_ontos.__setitem__(onto['@id'], None)
|
|
855
|
-
|
|
856
|
-
return all_ontos[onto_iri]
|
|
857
|
-
|
|
858
|
-
def create_ontology(self,
|
|
859
|
-
onto_name: str,
|
|
860
|
-
project_iri: str,
|
|
861
|
-
label: str) -> Dict[str, str]:
|
|
862
|
-
"""
|
|
863
|
-
Create a new ontology
|
|
864
|
-
|
|
865
|
-
:param onto_name: Name of the omntology
|
|
866
|
-
:param project_iri: IRI of the project
|
|
867
|
-
:param label: A label property for this ontology
|
|
868
|
-
:return: Dict with "onto_iri" and "last_onto_date"
|
|
869
|
-
"""
|
|
870
|
-
|
|
871
|
-
ontology = {
|
|
872
|
-
"knora-api:ontologyName": onto_name,
|
|
873
|
-
"knora-api:attachedToProject": {
|
|
874
|
-
"@id": project_iri
|
|
875
|
-
},
|
|
876
|
-
"rdfs:label": label,
|
|
877
|
-
"@context": {
|
|
878
|
-
"rdfs": 'http://www.w3.org/2000/01/rdf-schema#',
|
|
879
|
-
"knora-api": 'http://api.knora.org/ontology/knora-api/v2#'
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
jsondata = json.dumps(ontology)
|
|
884
|
-
|
|
885
|
-
req = requests.post(self.server + "/v2/ontologies",
|
|
886
|
-
headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
887
|
-
'Authorization': 'Bearer ' + self.token},
|
|
888
|
-
data=jsondata)
|
|
889
|
-
|
|
890
|
-
self.on_api_error(req)
|
|
891
|
-
|
|
892
|
-
res = req.json()
|
|
893
|
-
# TODO: return also ontology name
|
|
894
|
-
return {"onto_iri": res['@id'], "last_onto_date": res['knora-api:lastModificationDate']}
|
|
895
|
-
|
|
896
|
-
def delete_ontology(self, onto_iri: str, last_onto_date=None):
|
|
897
|
-
"""
|
|
898
|
-
A method to delete an ontology from /v2/ontologies
|
|
899
|
-
|
|
900
|
-
:param onto_iri: The ontology to delete
|
|
901
|
-
:param last_onto_date: the lastModificationDate of an ontology. None by default
|
|
902
|
-
:return:
|
|
903
|
-
""""" # TODO: add return documentation
|
|
904
|
-
url = self.server + "/v2/ontologies/" + urllib.parse.quote_plus(onto_iri)
|
|
905
|
-
req = requests.delete(url,
|
|
906
|
-
params={"lastModificationDate": last_onto_date},
|
|
907
|
-
headers={'Authorization': 'Bearer ' + self.token})
|
|
908
|
-
self.on_api_error(req)
|
|
909
|
-
res = req.json()
|
|
910
|
-
return res
|
|
911
|
-
|
|
912
|
-
def get_ontology_graph(self,
|
|
913
|
-
shortcode: str,
|
|
914
|
-
name: str):
|
|
915
|
-
"""
|
|
916
|
-
Returns the turtle definition of the ontology.
|
|
917
|
-
|
|
918
|
-
:param shortcode: Shortcode of the project
|
|
919
|
-
:param name: Name of the ontology
|
|
920
|
-
:return:
|
|
921
|
-
"""
|
|
922
|
-
url = self.server + "/ontology/" + shortcode + "/" + name + "/v2"
|
|
923
|
-
turtle = requests.get(url,
|
|
924
|
-
headers={"Accept": "text/turtle",
|
|
925
|
-
'Authorization': 'Bearer ' + self.token})
|
|
926
|
-
self.on_api_error(turtle)
|
|
927
|
-
return turtle.text
|
|
928
|
-
|
|
929
|
-
def create_res_class(self,
|
|
930
|
-
onto_iri: str,
|
|
931
|
-
onto_name: str,
|
|
932
|
-
last_onto_date: str,
|
|
933
|
-
class_name: str,
|
|
934
|
-
super_class: List[str],
|
|
935
|
-
labels: Dict[str, str],
|
|
936
|
-
comments: Optional[Dict[str, str]] = None,
|
|
937
|
-
permissions: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
|
938
|
-
"""Creates a knora resource class
|
|
939
|
-
|
|
940
|
-
:param onto_iri: IRI of the ontology
|
|
941
|
-
:param onto_name: Name of the ontology
|
|
942
|
-
:param last_onto_date: Last modification date as returned by last call
|
|
943
|
-
:param class_name: Name of the class to be created
|
|
944
|
-
:param super_class: List of super classes
|
|
945
|
-
:param labels: Dict with labels in the form { lang: labeltext }
|
|
946
|
-
:param comments: Dict with comments in the form { lang: commenttext }
|
|
947
|
-
:param permissions: Dict with permissions in the form
|
|
948
|
-
:return: Dict with "class_iri" and "last_onto_date"
|
|
949
|
-
"""
|
|
950
|
-
|
|
951
|
-
#
|
|
952
|
-
# using map and iterable to get the proper format
|
|
953
|
-
#
|
|
954
|
-
labels = list(map(lambda p: {"@language": p[0], "@value": p[1]}, labels.items()))
|
|
955
|
-
|
|
956
|
-
if not comments:
|
|
957
|
-
comments = {"en": "none"}
|
|
958
|
-
|
|
959
|
-
#
|
|
960
|
-
# using map and iterable to get the proper format
|
|
961
|
-
#
|
|
962
|
-
comments = list(map(lambda p: {"@language": p[0], "@value": p[1]}, comments.items()))
|
|
963
|
-
|
|
964
|
-
res_class = {
|
|
965
|
-
"@id": onto_iri,
|
|
966
|
-
"@type": "owl:Ontology",
|
|
967
|
-
"knora-api:lastModificationDate": last_onto_date,
|
|
968
|
-
"@graph": [{
|
|
969
|
-
"@id": onto_name + ":" + class_name,
|
|
970
|
-
"@type": "owl:Class",
|
|
971
|
-
"rdfs:label": labels,
|
|
972
|
-
"rdfs:comment": comments,
|
|
973
|
-
"rdfs:subClassOf": {
|
|
974
|
-
"@id": super_class
|
|
975
|
-
}
|
|
976
|
-
}],
|
|
977
|
-
"@context": {
|
|
978
|
-
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
|
979
|
-
"knora-api": "http://api.knora.org/ontology/knora-api/v2#",
|
|
980
|
-
"owl": "http://www.w3.org/2002/07/owl#",
|
|
981
|
-
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
|
|
982
|
-
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
|
983
|
-
onto_name: onto_iri + "#"
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
jsondata = json.dumps(res_class, indent=3, separators=(',', ': '))
|
|
988
|
-
|
|
989
|
-
req = requests.post(self.server + "/v2/ontologies/classes",
|
|
990
|
-
headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
991
|
-
'Authorization': 'Bearer ' + self.token},
|
|
992
|
-
data=jsondata)
|
|
993
|
-
self.on_api_error(req)
|
|
994
|
-
|
|
995
|
-
res = req.json()
|
|
996
|
-
return {"class_iri": res['@graph'][0]['@id'], "last_onto_date": res['knora-api:lastModificationDate']}
|
|
997
|
-
|
|
998
|
-
def create_property(
|
|
999
|
-
self,
|
|
1000
|
-
onto_iri: str,
|
|
1001
|
-
onto_name: str,
|
|
1002
|
-
last_onto_date: str,
|
|
1003
|
-
prop_name: str,
|
|
1004
|
-
super_props: List[str],
|
|
1005
|
-
labels: Dict[str, str],
|
|
1006
|
-
gui_element: str,
|
|
1007
|
-
gui_attributes: List[str] = None,
|
|
1008
|
-
subject: Optional[str] = None,
|
|
1009
|
-
object: Optional[str] = None,
|
|
1010
|
-
comments: Optional[Dict[str, str]] = None
|
|
1011
|
-
) -> Dict[str, str]:
|
|
1012
|
-
"""Create a Knora property
|
|
1013
|
-
|
|
1014
|
-
:param onto_iri: IRI of the ontology
|
|
1015
|
-
:param onto_name: Name of the Ontology (prefix)
|
|
1016
|
-
:param last_onto_date: Last modification date as returned by last call
|
|
1017
|
-
:param prop_name: Name of the property
|
|
1018
|
-
:param super_props: List of super-properties
|
|
1019
|
-
:param labels: Dict with labels in the form { lang: labeltext }
|
|
1020
|
-
:param gui_element: Valid GUI-Element
|
|
1021
|
-
:param gui_attributes: Valid GUI-Attributes (or None)
|
|
1022
|
-
:param subject: Full name (prefix:name) of subject resource class
|
|
1023
|
-
:param object: Full name (prefix:name) of object resource class
|
|
1024
|
-
:param comments: Dict with comments in the form { lang: commenttext }
|
|
1025
|
-
:return: Dict with "prop_iri" and "last_onto_date" keys
|
|
1026
|
-
"""
|
|
1027
|
-
#
|
|
1028
|
-
# using map and iterable to get the proper format
|
|
1029
|
-
#
|
|
1030
|
-
labels = list(map(lambda p: {"@language": p[0], "@value": p[1]}, labels.items()))
|
|
1031
|
-
|
|
1032
|
-
if not comments:
|
|
1033
|
-
comments = {"en": "none"}
|
|
1034
|
-
|
|
1035
|
-
#
|
|
1036
|
-
# using map and iterable to get the proper format
|
|
1037
|
-
#
|
|
1038
|
-
comments = list(map(lambda p: {"@language": p[0], "@value": p[1]}, comments.items()))
|
|
1039
|
-
|
|
1040
|
-
additional_context = {}
|
|
1041
|
-
for sprop in super_props:
|
|
1042
|
-
pp = sprop.split(':')
|
|
1043
|
-
if pp[0] != "knora-api":
|
|
1044
|
-
additional_context[pp[0]] = self.prefixes[pp[0]]
|
|
1045
|
-
|
|
1046
|
-
#
|
|
1047
|
-
# using map and iterable to get the proper format
|
|
1048
|
-
#
|
|
1049
|
-
super_props = list(map(lambda x: {"@id": x}, super_props))
|
|
1050
|
-
if len(super_props) == 1:
|
|
1051
|
-
super_props = super_props[0]
|
|
1052
|
-
|
|
1053
|
-
propdata = {
|
|
1054
|
-
"@id": onto_name + ":" + prop_name,
|
|
1055
|
-
"@type": "owl:ObjectProperty",
|
|
1056
|
-
"rdfs:label": labels,
|
|
1057
|
-
"rdfs:comment": comments,
|
|
1058
|
-
"rdfs:subPropertyOf": super_props,
|
|
1059
|
-
"salsah-gui:guiElement": {
|
|
1060
|
-
"@id": gui_element
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
if subject:
|
|
1064
|
-
propdata["knora-api:subjectType"] = {
|
|
1065
|
-
"@id": subject
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
if object:
|
|
1069
|
-
propdata["knora-api:objectType"] = {
|
|
1070
|
-
"@id": object
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
if gui_attributes:
|
|
1074
|
-
propdata["salsah-gui:guiAttribute"] = gui_attributes
|
|
1075
|
-
|
|
1076
|
-
property = {
|
|
1077
|
-
"@id": onto_iri,
|
|
1078
|
-
"@type": "owl:Ontology",
|
|
1079
|
-
"knora-api:lastModificationDate": last_onto_date,
|
|
1080
|
-
"@graph": [
|
|
1081
|
-
propdata
|
|
1082
|
-
],
|
|
1083
|
-
"@context": {
|
|
1084
|
-
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
|
1085
|
-
"knora-api": "http://api.knora.org/ontology/knora-api/v2#",
|
|
1086
|
-
"salsah-gui": "http://api.knora.org/ontology/salsah-gui/v2#",
|
|
1087
|
-
"owl": "http://www.w3.org/2002/07/owl#",
|
|
1088
|
-
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
|
|
1089
|
-
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
|
1090
|
-
onto_name: onto_iri + "#"
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
property["@context"].update(additional_context)
|
|
1094
|
-
jsondata = json.dumps(property, indent=3, separators=(',', ': '))
|
|
1095
|
-
req = requests.post(self.server + "/v2/ontologies/properties",
|
|
1096
|
-
headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
1097
|
-
'Authorization': 'Bearer ' + self.token},
|
|
1098
|
-
data=jsondata)
|
|
1099
|
-
self.on_api_error(req)
|
|
1100
|
-
|
|
1101
|
-
res = req.json()
|
|
1102
|
-
return {"prop_iri": res['@graph'][0]['@id'], "last_onto_date": res['knora-api:lastModificationDate']}
|
|
1103
|
-
|
|
1104
|
-
def create_cardinality(
|
|
1105
|
-
self,
|
|
1106
|
-
onto_iri: str,
|
|
1107
|
-
onto_name: str,
|
|
1108
|
-
last_onto_date: str,
|
|
1109
|
-
class_iri: str,
|
|
1110
|
-
prop_iri: str,
|
|
1111
|
-
occurrence: str,
|
|
1112
|
-
gui_order: Optional[int] = None
|
|
1113
|
-
) -> Dict[str, str]:
|
|
1114
|
-
"""Add a property with a given cardinality to a class
|
|
1115
|
-
|
|
1116
|
-
:param onto_iri: IRI of the ontology
|
|
1117
|
-
:param onto_name: Name of the ontology (prefix)
|
|
1118
|
-
:param last_onto_date: Last modification date as returned by last call
|
|
1119
|
-
:param class_iri: IRI of the class to which the property will be added
|
|
1120
|
-
:param prop_iri: IRI of the property that should be added
|
|
1121
|
-
:param occurrence: Occurrence: "1", "0-1", "0-n" or "1-n"
|
|
1122
|
-
:param gui_order: Ordering of properties in GUI
|
|
1123
|
-
:return: Dict with "last_onto_date" key
|
|
1124
|
-
"""
|
|
1125
|
-
switcher = {
|
|
1126
|
-
"1": ("owl:cardinality", 1),
|
|
1127
|
-
"0-1": ("owl:maxCardinality", 1),
|
|
1128
|
-
"0-n": ("owl:minCardinality", 0),
|
|
1129
|
-
"1-n": ("owl:minCardinality", 1)
|
|
1130
|
-
}
|
|
1131
|
-
occurrence = switcher.get(occurrence)
|
|
1132
|
-
if not occurrence:
|
|
1133
|
-
KnoraError("KNORA-ERROR:\n Invalid occurrence!")
|
|
1134
|
-
|
|
1135
|
-
cardinality = {
|
|
1136
|
-
"@id": onto_iri,
|
|
1137
|
-
"@type": "owl:Ontology",
|
|
1138
|
-
"knora-api:lastModificationDate": last_onto_date,
|
|
1139
|
-
"@graph": [{
|
|
1140
|
-
"@id": class_iri,
|
|
1141
|
-
"@type": "owl:Class",
|
|
1142
|
-
"rdfs:subClassOf": {
|
|
1143
|
-
"@type": "owl:Restriction",
|
|
1144
|
-
occurrence[0]: occurrence[1],
|
|
1145
|
-
"owl:onProperty": {
|
|
1146
|
-
"@id": prop_iri
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
}],
|
|
1150
|
-
"@context": {
|
|
1151
|
-
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
|
1152
|
-
"owl": "http://www.w3.org/2002/07/owl#",
|
|
1153
|
-
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
|
|
1154
|
-
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
|
1155
|
-
"knora-api": "http://api.knora.org/ontology/knora-api/v2#",
|
|
1156
|
-
"salsah-gui": "http://api.knora.org/ontology/salsah-gui/v2#",
|
|
1157
|
-
onto_name: onto_iri + "#"
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
if gui_order is not None:
|
|
1161
|
-
cardinality['@graph'][0]["rdfs:subClassOf"]["salsah-gui:guiOrder"] = int(gui_order)
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
jsondata = json.dumps(cardinality, indent=3, separators=(',', ': '))
|
|
1167
|
-
|
|
1168
|
-
req = requests.post(self.server + "/v2/ontologies/cardinalities",
|
|
1169
|
-
headers={'Content-Type': 'application/ld+json; charset=UTF-8',
|
|
1170
|
-
'Authorization': 'Bearer ' + self.token},
|
|
1171
|
-
data=jsondata)
|
|
1172
|
-
self.on_api_error(req)
|
|
1173
|
-
|
|
1174
|
-
res = req.json()
|
|
1175
|
-
|
|
1176
|
-
return {"last_onto_date": res["knora-api:lastModificationDate"]}
|
|
1177
|
-
|
|
1178
|
-
def create_list_node(self,
|
|
1179
|
-
project_iri: str,
|
|
1180
|
-
labels: Dict[str, str],
|
|
1181
|
-
comments: Optional[Dict[str, str]] = None,
|
|
1182
|
-
name: Optional[str] = None,
|
|
1183
|
-
parent_iri: Optional[str] = None) -> str:
|
|
1184
|
-
"""
|
|
1185
|
-
Creates a new list node. If there is no parent, a root node is created
|
|
1186
|
-
|
|
1187
|
-
:param project_iri: IRI of the project
|
|
1188
|
-
:param labels: Dict in the form {lang: label, …} giving the label(s)
|
|
1189
|
-
:param comments: Dict in the form {lang: comment, …} giving the comment(s)
|
|
1190
|
-
:param name: Name of the list node
|
|
1191
|
-
:param parent_iri: None for root node (or omit), otherwise IRI of parent node
|
|
1192
|
-
:return: IRI of list node
|
|
1193
|
-
"""
|
|
1194
|
-
|
|
1195
|
-
#
|
|
1196
|
-
# using map and iterable to get the proper format
|
|
1197
|
-
#
|
|
1198
|
-
labels = list(map(lambda p: {"language": p[0], "value": p[1]}, labels.items()))
|
|
1199
|
-
|
|
1200
|
-
listnode = {
|
|
1201
|
-
"projectIri": project_iri,
|
|
1202
|
-
"labels": labels,
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
#
|
|
1206
|
-
# using map and iterable to get the proper format
|
|
1207
|
-
#
|
|
1208
|
-
if comments is not None:
|
|
1209
|
-
listnode["comments"] = list(map(lambda p: {"language": p[0], "value": p[1]}, comments.items()))
|
|
1210
|
-
else:
|
|
1211
|
-
listnode["comments"] = []
|
|
1212
|
-
|
|
1213
|
-
if name is not None:
|
|
1214
|
-
listnode["name"] = name
|
|
1215
|
-
|
|
1216
|
-
if parent_iri is not None:
|
|
1217
|
-
listnode["parentNodeIri"] = parent_iri
|
|
1218
|
-
url = self.server + "/admin/lists/" + quote_plus(parent_iri)
|
|
1219
|
-
else:
|
|
1220
|
-
url = self.server + "/admin/lists"
|
|
1221
|
-
|
|
1222
|
-
jsondata = json.dumps(listnode, indent=3, separators=(',', ': '))
|
|
1223
|
-
|
|
1224
|
-
req = requests.post(url,
|
|
1225
|
-
headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
1226
|
-
'Authorization': 'Bearer ' + self.token},
|
|
1227
|
-
data=jsondata)
|
|
1228
|
-
self.on_api_error(req)
|
|
1229
|
-
|
|
1230
|
-
res = req.json()
|
|
1231
|
-
|
|
1232
|
-
if parent_iri is not None:
|
|
1233
|
-
return res['nodeinfo']['id']
|
|
1234
|
-
else:
|
|
1235
|
-
return res['list']['listinfo']['id']
|
|
1236
|
-
|
|
1237
|
-
def get_lists(self, shortcode: str):
|
|
1238
|
-
"""
|
|
1239
|
-
Get the lists belonging to a certain project identified by its shortcode
|
|
1240
|
-
:param shortcode: Project shortcode
|
|
1241
|
-
|
|
1242
|
-
:return: JSON with the lists
|
|
1243
|
-
"""
|
|
1244
|
-
url = self.server + "/admin/lists?projectIri=" + quote_plus("http://rdfh.ch/projects/" + shortcode)
|
|
1245
|
-
req = requests.get(url, headers={'Authorization': 'Bearer ' + self.token})
|
|
1246
|
-
self.on_api_error(req)
|
|
1247
|
-
return req.json()
|
|
1248
|
-
|
|
1249
|
-
def get_complete_list(self, list_iri: str):
|
|
1250
|
-
"""
|
|
1251
|
-
Get all the data (nodes) of a specific list
|
|
1252
|
-
|
|
1253
|
-
:param list_iri: IRI of the list
|
|
1254
|
-
:return: JSON containing the list info including all nodes
|
|
1255
|
-
"""
|
|
1256
|
-
url = self.server + "/admin/lists/" + quote_plus(list_iri)
|
|
1257
|
-
req = requests.get(url, headers={'Authorization': 'Bearer ' + self.token})
|
|
1258
|
-
self.on_api_error(req)
|
|
1259
|
-
return req.json()
|
|
1260
|
-
|
|
1261
|
-
def get_resource_by_label(self,
|
|
1262
|
-
label: str,
|
|
1263
|
-
res_class: Optional[str] = None,
|
|
1264
|
-
limit_to_project: Optional[str] = None,
|
|
1265
|
-
offset: Optional[int] = None):
|
|
1266
|
-
url = self.server + "/v2/searchbylabel/" + label
|
|
1267
|
-
option = False
|
|
1268
|
-
if res_class is not None:
|
|
1269
|
-
url += '?limitToResourceClass=' + quote_plus(res_class)
|
|
1270
|
-
option = True
|
|
1271
|
-
if limit_to_project is not None:
|
|
1272
|
-
if option:
|
|
1273
|
-
url += '&limitToProject=' + quote_plus(limit_to_project)
|
|
1274
|
-
else:
|
|
1275
|
-
url += '?limitToProject=' + quote_plus(limit_to_project)
|
|
1276
|
-
option = True;
|
|
1277
|
-
if offset is not None:
|
|
1278
|
-
if option:
|
|
1279
|
-
url += '&offset=' + quote_plus(limit_to_project)
|
|
1280
|
-
else:
|
|
1281
|
-
url += '&offset=' + quote_plus(limit_to_project)
|
|
1282
|
-
req = requests.get(url, headers={'Authorization': 'Bearer ' + self.token})
|
|
1283
|
-
self.on_api_error(req)
|
|
1284
|
-
return req.json()
|
|
1285
|
-
|
|
1286
|
-
def create_resource(self,
|
|
1287
|
-
schema: Dict,
|
|
1288
|
-
res_class: str,
|
|
1289
|
-
label: str,
|
|
1290
|
-
values: Dict,
|
|
1291
|
-
permissions: Optional[str] = None,
|
|
1292
|
-
stillimage: Optional[str] = None):
|
|
1293
|
-
"""
|
|
1294
|
-
This method creates a new resource (instance of a resource class) with the
|
|
1295
|
-
default permissions.
|
|
1296
|
-
|
|
1297
|
-
:param schema: The schema of the ontology as returned by the method "create_schema()"
|
|
1298
|
-
:param res_class: The resource class of the resource to be created
|
|
1299
|
-
:param label: The "rdfs:label" to be given to the new resource
|
|
1300
|
-
:param values: A dict with the property values. It has the form
|
|
1301
|
-
{ property_name: value, property_name: value,… } or { property_name: [value1, value2,…],… }
|
|
1302
|
-
The format of the values depends on the value types. E.g. a calendar date has the form
|
|
1303
|
-
"GREGORIAN:CE:1920-03-12:CE:1921:05:21" where all values except the start year are optional.
|
|
1304
|
-
:param stillimage: Path to a still image...
|
|
1305
|
-
:return: A dict in the form { 'iri': resource_iri, 'ark': ark_id, 'vark': dated_ark_id }
|
|
1306
|
-
"""
|
|
1307
|
-
|
|
1308
|
-
ontoname = schema["ontoname"]
|
|
1309
|
-
props = schema['resources'][res_class] # this is an array of all properties defined in the ontology
|
|
1310
|
-
|
|
1311
|
-
# we start building the dict that will be transformed into the JSON-LD
|
|
1312
|
-
jsondata = {
|
|
1313
|
-
'@type': ontoname + ":" + res_class,
|
|
1314
|
-
'rdfs:label': label,
|
|
1315
|
-
"knora-api:attachedToProject": {
|
|
1316
|
-
"@id": schema['proj_iri']
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
if permissions is not None:
|
|
1321
|
-
jsondata["knora-api:hasPermissions"] = permissions
|
|
1322
|
-
|
|
1323
|
-
if stillimage is not None:
|
|
1324
|
-
jsondata["knora-api:hasStillImageFileValue"] = {
|
|
1325
|
-
"@type": "knora-api:StillImageFileValue",
|
|
1326
|
-
"knora-api:fileValueHasFilename": stillimage
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
def create_valdict(val):
|
|
1330
|
-
"""
|
|
1331
|
-
Internal function to create the JSON-LD for one value
|
|
1332
|
-
:param val: the value
|
|
1333
|
-
:return: Dict propared for the JSON-LD for one value
|
|
1334
|
-
|
|
1335
|
-
"""
|
|
1336
|
-
|
|
1337
|
-
if type(val) is dict:
|
|
1338
|
-
pprint(val)
|
|
1339
|
-
comment = val.get('comment')
|
|
1340
|
-
permissions = val.get('permissions')
|
|
1341
|
-
mapping = val.get('mapping')
|
|
1342
|
-
val = val.get('value')
|
|
1343
|
-
else:
|
|
1344
|
-
comment = None
|
|
1345
|
-
permissions = None
|
|
1346
|
-
mapping = None
|
|
1347
|
-
|
|
1348
|
-
valdict = {
|
|
1349
|
-
'@type': 'knora-api:' + prop["otype"]
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
if permissions is not None:
|
|
1353
|
-
valdict["knora-api:hasPermissions"] = permissions
|
|
1354
|
-
|
|
1355
|
-
if comment is not None:
|
|
1356
|
-
valdict["knora-api:valueHasComment"] = comment
|
|
1357
|
-
|
|
1358
|
-
if prop["otype"] == "TextValue":
|
|
1359
|
-
if isinstance(val, KnoraStandoffXml): # text with XML markup
|
|
1360
|
-
valdict['knora-api:textValueAsXml'] = val # no conversion to string
|
|
1361
|
-
valdict['knora-api:textValueHasMapping'] = {
|
|
1362
|
-
'@id': 'http://rdfh.ch/standoff/mappings/StandardMapping' if mapping is None else mapping
|
|
1363
|
-
}
|
|
1364
|
-
else: # normal text string without markup
|
|
1365
|
-
valdict['knora-api:valueAsString'] = str(val)
|
|
1366
|
-
elif prop["otype"] == "ColorValue":
|
|
1367
|
-
#
|
|
1368
|
-
# a color value as used in HTML (e.g. "#aaccff"
|
|
1369
|
-
#
|
|
1370
|
-
res = re.match('^#(?:[0-9a-fA-F]{3}){1,2}$', str(val))
|
|
1371
|
-
if res is None:
|
|
1372
|
-
raise KnoraError("Invalid ColorValue format! " + str(val))
|
|
1373
|
-
valdict['knora-api:colorValueAsColor'] = str(val)
|
|
1374
|
-
elif prop["otype"] == "DateValue":
|
|
1375
|
-
#
|
|
1376
|
-
# A knora date value
|
|
1377
|
-
#
|
|
1378
|
-
res = re.match(
|
|
1379
|
-
'(GREGORIAN:|JULIAN:)?(CE:|BCE:)?(\d{4})?(-\d{1,2})?(-\d{1,2})?(:CE|:BCE)?(:\d{4})?(-\d{1,2})?(-\d{1,2})?',
|
|
1380
|
-
str(val))
|
|
1381
|
-
if res is None:
|
|
1382
|
-
raise KnoraError("Invalid date format! " + str(val))
|
|
1383
|
-
dp = res.groups()
|
|
1384
|
-
calendar = 'GREGORIAN' if dp[0] is None else dp[0].strip('-: ')
|
|
1385
|
-
e1 = 'CE' if dp[1] is None else dp[1].strip('-: ')
|
|
1386
|
-
y1 = None if dp[2] is None else int(dp[2].strip('-: '))
|
|
1387
|
-
m1 = None if dp[3] is None else int(dp[3].strip('-: '))
|
|
1388
|
-
d1 = None if dp[4] is None else int(dp[4].strip('-: '))
|
|
1389
|
-
e2 = 'CE' if dp[5] is None else dp[5].strip('-: ')
|
|
1390
|
-
y2 = None if dp[6] is None else int(dp[6].strip('-: '))
|
|
1391
|
-
m2 = None if dp[7] is None else int(dp[7].strip('-: '))
|
|
1392
|
-
d2 = None if dp[8] is None else int(dp[8].strip('-: '))
|
|
1393
|
-
if y1 is None:
|
|
1394
|
-
raise KnoraError("Invalid date format! " + str(val))
|
|
1395
|
-
if y2 is not None:
|
|
1396
|
-
date1 = y1 * 10000
|
|
1397
|
-
if m1 is not None:
|
|
1398
|
-
date1 += m1 * 100
|
|
1399
|
-
if d1 is not None:
|
|
1400
|
-
date1 += d1
|
|
1401
|
-
date2 = y2 * 10000
|
|
1402
|
-
if m2 is not None:
|
|
1403
|
-
date2 += m2 * 100
|
|
1404
|
-
if d2 is not None:
|
|
1405
|
-
date2 += d2
|
|
1406
|
-
if date1 > date2:
|
|
1407
|
-
y1, y2 = y2, y1
|
|
1408
|
-
m1, m2 = m2, m1
|
|
1409
|
-
d1, d2 = d2, d1
|
|
1410
|
-
valdict["knora-api:dateValueHasCalendar"] = calendar
|
|
1411
|
-
valdict["knora-api:dateValueHasStartEra"] = e1
|
|
1412
|
-
valdict["knora-api:dateValueHasStartYear"] = int(y1)
|
|
1413
|
-
if m1 is not None:
|
|
1414
|
-
valdict["knora-api:dateValueHasStartMonth"] = int(m1)
|
|
1415
|
-
if d1 is not None:
|
|
1416
|
-
valdict["knora-api:dateValueHasStartDay"] = int(d1)
|
|
1417
|
-
valdict["knora-api:dateValueHasEndEra"] = e2
|
|
1418
|
-
if y2 is not None:
|
|
1419
|
-
valdict["knora-api:dateValueHasEndYear"] = int(y2)
|
|
1420
|
-
else:
|
|
1421
|
-
valdict["knora-api:dateValueHasEndYear"] = int(y1)
|
|
1422
|
-
if m2 is not None:
|
|
1423
|
-
valdict["knora-api:dateValueHasEndMonth"] = int(m2)
|
|
1424
|
-
if d2 is not None:
|
|
1425
|
-
valdict["knora-api:dateValueHasEndDay"] = int(d2)
|
|
1426
|
-
elif prop["otype"] == "DecimalValue":
|
|
1427
|
-
#
|
|
1428
|
-
# a decimal value
|
|
1429
|
-
#
|
|
1430
|
-
valdict['knora-api:decimalValueAsDecimal'] = {
|
|
1431
|
-
'@type': 'xsd:decimal',
|
|
1432
|
-
'@value': str(val)
|
|
1433
|
-
}
|
|
1434
|
-
elif prop["otype"] == "GeomValue":
|
|
1435
|
-
#
|
|
1436
|
-
# A geometry ID
|
|
1437
|
-
#
|
|
1438
|
-
valdict['knora-api:geometryValueAsGeometry'] = str(val)
|
|
1439
|
-
elif prop["otype"] == "GeonameValue":
|
|
1440
|
-
#
|
|
1441
|
-
# A geoname ID
|
|
1442
|
-
#
|
|
1443
|
-
valdict['knora-api:geonameValueAsGeonameCode'] = str(val)
|
|
1444
|
-
elif prop["otype"] == "IntValue":
|
|
1445
|
-
#
|
|
1446
|
-
# an integer value
|
|
1447
|
-
#
|
|
1448
|
-
valdict['knora-api:intValueAsInt'] = int(val)
|
|
1449
|
-
elif prop["otype"] == "BooleanValue":
|
|
1450
|
-
#
|
|
1451
|
-
# a boolean value
|
|
1452
|
-
#
|
|
1453
|
-
if type(val) == bool:
|
|
1454
|
-
valdict['knora-api:booleanValueAsBoolean'] = val
|
|
1455
|
-
elif type(val) == str:
|
|
1456
|
-
if val.upper() == 'TRUE':
|
|
1457
|
-
valdict['knora-api:booleanValueAsBoolean'] = True
|
|
1458
|
-
elif val.upper() == 'FALSE':
|
|
1459
|
-
valdict['knora-api:booleanValueAsBoolean'] = False
|
|
1460
|
-
else:
|
|
1461
|
-
raise KnoraError("Invalid boolean format! " + str(val))
|
|
1462
|
-
elif type(val) == int:
|
|
1463
|
-
if val == 0:
|
|
1464
|
-
valdict['knora-api:booleanValueAsBoolean'] = False
|
|
1465
|
-
else:
|
|
1466
|
-
valdict['knora-api:booleanValueAsBoolean'] = True
|
|
1467
|
-
elif prop["otype"] == "UriValue":
|
|
1468
|
-
#
|
|
1469
|
-
# an URI
|
|
1470
|
-
#
|
|
1471
|
-
valdict['knora-api:uriValueAsUri'] = {
|
|
1472
|
-
"@type": "xsd:anyURI",
|
|
1473
|
-
"@value": str(val)
|
|
1474
|
-
}
|
|
1475
|
-
elif prop["otype"] == "TimeValue":
|
|
1476
|
-
#
|
|
1477
|
-
# an URI
|
|
1478
|
-
#
|
|
1479
|
-
valdict['knora-api:timeValueAsTime'] = {
|
|
1480
|
-
"@type": "xsd:dateTime",
|
|
1481
|
-
"@value": str(val)
|
|
1482
|
-
}
|
|
1483
|
-
elif prop["otype"] == "IntervalValue":
|
|
1484
|
-
#
|
|
1485
|
-
# an interval in the form "1.356:2.456"
|
|
1486
|
-
#
|
|
1487
|
-
iv = val.split(':')
|
|
1488
|
-
valdict["knora-api:intervalValueHasEnd"] = {
|
|
1489
|
-
"@type": "xsd:decimal",
|
|
1490
|
-
"@value": str(iv[0])
|
|
1491
|
-
}
|
|
1492
|
-
valdict["knora-api:intervalValueHasStart"] = {
|
|
1493
|
-
"@type": "xsd:decimal",
|
|
1494
|
-
"@value": str(iv[1])
|
|
1495
|
-
}
|
|
1496
|
-
elif prop["otype"] == "ListValue":
|
|
1497
|
-
try:
|
|
1498
|
-
iriparts = parse(str(val), rule='IRI')
|
|
1499
|
-
if iriparts['scheme'] == 'http' or iriparts['scheme'] == 'https':
|
|
1500
|
-
valdict['knora-api:listValueAsListNode'] = {
|
|
1501
|
-
'@id': str(val)
|
|
1502
|
-
}
|
|
1503
|
-
else:
|
|
1504
|
-
if iriparts['authority'] is not None:
|
|
1505
|
-
raise KnoraError("Invalid list node: \"" + str(val) + "\" !")
|
|
1506
|
-
listname = iriparts['scheme']
|
|
1507
|
-
nodename = iriparts['path']
|
|
1508
|
-
for node in schema['lists'][listname]['nodes']:
|
|
1509
|
-
found = False
|
|
1510
|
-
if node['name'] == nodename:
|
|
1511
|
-
valdict['knora-api:listValueAsListNode'] = {
|
|
1512
|
-
'@id': node['id']
|
|
1513
|
-
}
|
|
1514
|
-
found = True
|
|
1515
|
-
break
|
|
1516
|
-
if not found:
|
|
1517
|
-
raise KnoraError("Invalid list node: \"" + str(val) + "\" !")
|
|
1518
|
-
except ValueError as err:
|
|
1519
|
-
raise KnoraError("Invalid list node: \"" + str(val) + "\" !")
|
|
1520
|
-
|
|
1521
|
-
elif prop["otype"] == "LinkValue":
|
|
1522
|
-
valdict['@type'] = 'knora-api:LinkValue'
|
|
1523
|
-
valdict['knora-api:linkValueHasTargetIri'] = {
|
|
1524
|
-
'@id': str(val)
|
|
1525
|
-
}
|
|
1526
|
-
else:
|
|
1527
|
-
if prop['otype'] in schema['link_otypes']:
|
|
1528
|
-
valdict['@type'] = 'knora-api:LinkValue'
|
|
1529
|
-
valdict['knora-api:linkValueHasTargetIri'] = {
|
|
1530
|
-
'@id': str(val)
|
|
1531
|
-
}
|
|
1532
|
-
else:
|
|
1533
|
-
raise KnoraError("Invalid otype: " + prop['otype'])
|
|
1534
|
-
|
|
1535
|
-
return valdict
|
|
1536
|
-
|
|
1537
|
-
for key, value in values.items():
|
|
1538
|
-
prop = None
|
|
1539
|
-
for tmpprop in props:
|
|
1540
|
-
if tmpprop['propname'] == key:
|
|
1541
|
-
prop = tmpprop
|
|
1542
|
-
if prop is None:
|
|
1543
|
-
raise KnoraError("Property " + key + " not known!")
|
|
1544
|
-
|
|
1545
|
-
if prop['otype'] == "LinkValue" or prop['otype'] in schema['link_otypes']:
|
|
1546
|
-
nkey = key + "Value"
|
|
1547
|
-
else:
|
|
1548
|
-
nkey = key
|
|
1549
|
-
if type(value) is list:
|
|
1550
|
-
valarr = []
|
|
1551
|
-
for val in value:
|
|
1552
|
-
valarr.append(create_valdict(val))
|
|
1553
|
-
jsondata[ontoname + ':' + nkey] = valarr
|
|
1554
|
-
else:
|
|
1555
|
-
jsondata[ontoname + ':' + nkey] = create_valdict(value)
|
|
1556
|
-
|
|
1557
|
-
jsondata['@context'] = {
|
|
1558
|
-
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
|
1559
|
-
"knora-api": "http://api.knora.org/ontology/knora-api/v2#",
|
|
1560
|
-
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
|
|
1561
|
-
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
|
1562
|
-
ontoname: schema['onto_iri'] + '#'
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
jsonstr = json.dumps(jsondata, indent=3, separators=(',', ': '), cls=KnoraStandoffXmlEncoder)
|
|
1566
|
-
print(jsonstr)
|
|
1567
|
-
url = self.server + "/v2/resources"
|
|
1568
|
-
req = requests.post(url,
|
|
1569
|
-
headers={'Content-Type': 'application/json; charset=UTF-8',
|
|
1570
|
-
'Authorization': 'Bearer ' + self.token},
|
|
1571
|
-
data=jsonstr)
|
|
1572
|
-
self.on_api_error(req)
|
|
1573
|
-
|
|
1574
|
-
res = req.json()
|
|
1575
|
-
|
|
1576
|
-
return {
|
|
1577
|
-
'iri': res['@id'],
|
|
1578
|
-
'ark': res['knora-api:arkUrl']['@value'],
|
|
1579
|
-
'vark': res['knora-api:versionArkUrl']['@value']
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
def list_creator(self, children: List):
|
|
1583
|
-
"""
|
|
1584
|
-
internal Helper function
|
|
1585
|
-
|
|
1586
|
-
:param children:
|
|
1587
|
-
:return:
|
|
1588
|
-
"""
|
|
1589
|
-
if len(children) == 0:
|
|
1590
|
-
res = list(map(lambda a: {"name": a["name"], "id": a["id"]}, children))
|
|
1591
|
-
else:
|
|
1592
|
-
res = list(
|
|
1593
|
-
map(lambda a: {"name": a["name"], "id": a["id"], "nodes": self.list_creator(a["children"])}, children))
|
|
1594
|
-
return res
|
|
1595
|
-
|
|
1596
|
-
def create_schema(self, shortcode: str, shortname: str):
|
|
1597
|
-
"""
|
|
1598
|
-
This method extracts the ontology from the ontology information it gets from Knora. It
|
|
1599
|
-
gets the ontology information as n3-data using the Knora API and concerts into a convenient
|
|
1600
|
-
python dict that can be used for further processing. It is required by the bulk import processing
|
|
1601
|
-
routines.
|
|
1602
|
-
|
|
1603
|
-
:param shortcode: Shortcode of the project
|
|
1604
|
-
:param shortname: Short name of the ontolopgy
|
|
1605
|
-
:return: Dict with a simple description of the ontology
|
|
1606
|
-
"""
|
|
1607
|
-
turtle = self.get_ontology_graph(shortcode, shortname)
|
|
1608
|
-
# print(turtle)
|
|
1609
|
-
g = Graph()
|
|
1610
|
-
g.parse(format='n3', data=turtle)
|
|
1611
|
-
|
|
1612
|
-
# Get project and ontology IRI's
|
|
1613
|
-
sparql = """
|
|
1614
|
-
SELECT ?onto ?proj
|
|
1615
|
-
WHERE {
|
|
1616
|
-
?onto a owl:Ontology .
|
|
1617
|
-
?onto knora-api:attachedToProject ?proj
|
|
1618
|
-
}
|
|
1619
|
-
"""
|
|
1620
|
-
qres = g.query(sparql)
|
|
1621
|
-
for row in qres:
|
|
1622
|
-
proj_iri = row.proj.toPython() # project IRI
|
|
1623
|
-
onto_iri = row.onto.toPython() # ontology IRI
|
|
1624
|
-
|
|
1625
|
-
sparql = """
|
|
1626
|
-
SELECT ?res ?prop ?superprop ?otype ?guiele ?attr ?card ?cardval
|
|
1627
|
-
WHERE {
|
|
1628
|
-
?res a owl:Class .
|
|
1629
|
-
?res rdfs:subClassOf ?restriction .
|
|
1630
|
-
?restriction a owl:Restriction .
|
|
1631
|
-
?restriction owl:onProperty ?prop .
|
|
1632
|
-
?restriction ?card ?cardval .
|
|
1633
|
-
?prop a owl:ObjectProperty .
|
|
1634
|
-
?prop knora-api:objectType ?otype .
|
|
1635
|
-
?prop salsah-gui:guiElement ?guiele .
|
|
1636
|
-
?prop rdfs:subPropertyOf ?superprop .
|
|
1637
|
-
OPTIONAL { ?prop salsah-gui:guiAttribute ?attr } .
|
|
1638
|
-
FILTER(?card = owl:cardinality || ?card = owl:maxCardinality || ?card = owl:minCardinality)
|
|
1639
|
-
}
|
|
1640
|
-
ORDER BY ?res ?prop
|
|
1641
|
-
"""
|
|
1642
|
-
qres = g.query(sparql)
|
|
1643
|
-
|
|
1644
|
-
resources = {}
|
|
1645
|
-
resclass = ''
|
|
1646
|
-
propname = ''
|
|
1647
|
-
link_otypes = []
|
|
1648
|
-
propcnt = 0
|
|
1649
|
-
propindex = {} # we have to keep the order of the properties as given in the ontology....
|
|
1650
|
-
for row in qres:
|
|
1651
|
-
|
|
1652
|
-
nresclass = row.res.toPython()
|
|
1653
|
-
nresclass = nresclass[nresclass.find('#') + 1:]
|
|
1654
|
-
if resclass != nresclass:
|
|
1655
|
-
resclass = nresclass
|
|
1656
|
-
resources[resclass] = []
|
|
1657
|
-
propcnt = 0
|
|
1658
|
-
superprop = row.superprop.toPython()
|
|
1659
|
-
superprop = superprop[superprop.find('#') + 1:]
|
|
1660
|
-
if superprop == 'hasLinkToValue': # we ignore this one....
|
|
1661
|
-
continue
|
|
1662
|
-
npropname = row.prop.toPython()
|
|
1663
|
-
npropname = npropname[npropname.find('#') + 1:]
|
|
1664
|
-
attr = row.attr.toPython() if row.attr is not None else None
|
|
1665
|
-
if attr is not None:
|
|
1666
|
-
attr = attr.split('=')
|
|
1667
|
-
if propname == npropname:
|
|
1668
|
-
propcnt -= 1
|
|
1669
|
-
|
|
1670
|
-
# process attribute (there might be multiple attributes)
|
|
1671
|
-
if attr is not None:
|
|
1672
|
-
if resources[resclass][propcnt]["attr"] is not None: # TODO: why is this necessary???
|
|
1673
|
-
resources[resclass][propcnt]["attr"][attr[0]] = attr[1].strip('<>')
|
|
1674
|
-
|
|
1675
|
-
# process superprop (there might be multiple superprops)
|
|
1676
|
-
if superprop not in resources[resclass][propcnt]["superprop"]:
|
|
1677
|
-
resources[resclass][propcnt]["superprop"].append(superprop)
|
|
1678
|
-
# pprint.pprint(resources[resclass])
|
|
1679
|
-
propcnt += 1
|
|
1680
|
-
continue
|
|
1681
|
-
else:
|
|
1682
|
-
propname = npropname
|
|
1683
|
-
objtype = row.otype.toPython()
|
|
1684
|
-
objtype = objtype[objtype.find('#') + 1:]
|
|
1685
|
-
card = row.card.toPython()
|
|
1686
|
-
card = card[card.find('#') + 1:]
|
|
1687
|
-
guiele = row.guiele.toPython()
|
|
1688
|
-
guiele = guiele[guiele.find('#') + 1:]
|
|
1689
|
-
resources[resclass].append({
|
|
1690
|
-
"propname": propname,
|
|
1691
|
-
"otype": objtype,
|
|
1692
|
-
"superprop": [superprop],
|
|
1693
|
-
"guiele": guiele,
|
|
1694
|
-
"attr": {attr[0]: attr[1].strip('<>')} if attr is not None else None,
|
|
1695
|
-
"card": card,
|
|
1696
|
-
"cardval": row.cardval.toPython()
|
|
1697
|
-
})
|
|
1698
|
-
# pprint.pprint(resources[resclass])
|
|
1699
|
-
if superprop == "hasLinkTo":
|
|
1700
|
-
link_otypes.append(objtype)
|
|
1701
|
-
propindex[propname] = propcnt
|
|
1702
|
-
propcnt += 1
|
|
1703
|
-
|
|
1704
|
-
# Get info about lists attached to the project
|
|
1705
|
-
listdata = {}
|
|
1706
|
-
lists = self.get_lists(shortcode)
|
|
1707
|
-
lists = lists["lists"]
|
|
1708
|
-
for list in lists:
|
|
1709
|
-
tmp = self.get_complete_list(list["id"])
|
|
1710
|
-
clist = tmp["list"]
|
|
1711
|
-
listdata[clist["listinfo"]["name"]] = {
|
|
1712
|
-
"id": clist["listinfo"]["id"],
|
|
1713
|
-
"nodes": self.list_creator(clist["children"])
|
|
1714
|
-
}
|
|
1715
|
-
schema = {
|
|
1716
|
-
"proj_iri": proj_iri,
|
|
1717
|
-
"shortcode": shortcode,
|
|
1718
|
-
"ontoname": shortname,
|
|
1719
|
-
"onto_iri": onto_iri,
|
|
1720
|
-
"lists": listdata,
|
|
1721
|
-
"resources": resources,
|
|
1722
|
-
"link_otypes": link_otypes
|
|
1723
|
-
}
|
|
1724
|
-
return schema
|
|
1725
|
-
|
|
1726
|
-
def reset_triplestore_content(self):
|
|
1727
|
-
rdfdata = [
|
|
1728
|
-
{
|
|
1729
|
-
"path": "./knora-ontologies/knora-admin.ttl",
|
|
1730
|
-
"name": "http://www.knora.org/ontology/knora-admin"
|
|
1731
|
-
},
|
|
1732
|
-
{
|
|
1733
|
-
"path": "./knora-ontologies/knora-base.ttl",
|
|
1734
|
-
"name": "http://www.knora.org/ontology/knora-base"
|
|
1735
|
-
},
|
|
1736
|
-
{
|
|
1737
|
-
"path": "./knora-ontologies/standoff-onto.ttl",
|
|
1738
|
-
"name": "http://www.knora.org/ontology/standoff"
|
|
1739
|
-
},
|
|
1740
|
-
{
|
|
1741
|
-
"path": "./knora-ontologies/standoff-data.ttl",
|
|
1742
|
-
"name": "http://www.knora.org/data/standoff"
|
|
1743
|
-
},
|
|
1744
|
-
{
|
|
1745
|
-
"path": "./knora-ontologies/salsah-gui.ttl",
|
|
1746
|
-
"name": "http://www.knora.org/ontology/salsah-gui"
|
|
1747
|
-
},
|
|
1748
|
-
{
|
|
1749
|
-
"path": "./_test_data/all_data/admin-data.ttl",
|
|
1750
|
-
"name": "http://www.knora.org/data/admin"
|
|
1751
|
-
},
|
|
1752
|
-
{
|
|
1753
|
-
"path": "./_test_data/all_data/permissions-data.ttl",
|
|
1754
|
-
"name": "http://www.knora.org/data/permissions"
|
|
1755
|
-
},
|
|
1756
|
-
{
|
|
1757
|
-
"path": "./_test_data/all_data/system-data.ttl",
|
|
1758
|
-
"name": "http://www.knora.org/data/0000/SystemProject"
|
|
1759
|
-
}
|
|
1760
|
-
]
|
|
1761
|
-
jsondata = json.dumps(rdfdata)
|
|
1762
|
-
url = self.server + '/admin/store/ResetTriplestoreContent?prependdefaults=false'
|
|
1763
|
-
|
|
1764
|
-
req = requests.post(url,
|
|
1765
|
-
headers={'Content-Type': 'application/json; charset=UTF-8'},
|
|
1766
|
-
data=jsondata)
|
|
1767
|
-
self.on_api_error(req)
|
|
1768
|
-
res = req.json()
|
|
1769
|
-
# pprint(res)
|
|
1770
|
-
return res
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
class Sipi:
|
|
1774
|
-
def __init__(self, sipiserver: str, token: str):
|
|
1775
|
-
self.sipiserver = sipiserver
|
|
1776
|
-
self.token = token
|
|
1777
|
-
|
|
1778
|
-
def on_api_error(self, res):
|
|
1779
|
-
"""
|
|
1780
|
-
Method to check for any API errors
|
|
1781
|
-
:param res: The input to check, usually JSON format
|
|
1782
|
-
:return: Possible KnoraError that is being raised
|
|
1783
|
-
"""
|
|
1784
|
-
|
|
1785
|
-
if (res.status_code != 200):
|
|
1786
|
-
raise KnoraError("SIPI-ERROR: status code=" + str(res.status_code) + "\nMessage:" + res.text)
|
|
1787
|
-
|
|
1788
|
-
if 'error' in res:
|
|
1789
|
-
raise KnoraError("SIPI-ERROR: API error: " + res.error)
|
|
1790
|
-
|
|
1791
|
-
def upload_image(self, filepath):
|
|
1792
|
-
files = {
|
|
1793
|
-
'file': (filepath, open(filepath, 'rb')),
|
|
1794
|
-
}
|
|
1795
|
-
req = requests.post(self.sipiserver + "/upload?token=" + self.token,
|
|
1796
|
-
files=files)
|
|
1797
|
-
self.on_api_error(req)
|
|
1798
|
-
res = req.json()
|
|
1799
|
-
return res
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
class BulkImport:
|
|
1803
|
-
def __init__(self, schema: Dict):
|
|
1804
|
-
self.schema = schema
|
|
1805
|
-
self.proj_prefix = 'p' + schema['shortcode'] + '-' + schema["ontoname"]
|
|
1806
|
-
self.proj_iri = "http://api.knora.org/ontology/" + schema['shortcode'] + "/" + schema[
|
|
1807
|
-
"ontoname"] + "/xml-import/v1#"
|
|
1808
|
-
self.xml_prefixes = {
|
|
1809
|
-
None: self.proj_iri,
|
|
1810
|
-
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
|
1811
|
-
self.proj_prefix: self.proj_iri,
|
|
1812
|
-
"knoraXmlImport": "http://api.knora.org/ontology/knoraXmlImport/v1#"
|
|
1813
|
-
}
|
|
1814
|
-
self.root = etree.Element('{http://api.knora.org/ontology/knoraXmlImport/v1#}resources',
|
|
1815
|
-
nsmap=self.xml_prefixes)
|
|
1816
|
-
self.project_shortcode = schema["shortcode"]
|
|
1817
|
-
|
|
1818
|
-
def new_xml_element(self, tag: str, options: Dict = None, value: str = None):
|
|
1819
|
-
tagp = tag.split(':')
|
|
1820
|
-
if len(tagp) > 1:
|
|
1821
|
-
fulltag = '{' + self.xml_prefixes.get(tagp[0]) + '}' + tagp[1]
|
|
1822
|
-
else:
|
|
1823
|
-
fulltag = tagp[0]
|
|
1824
|
-
if options is None:
|
|
1825
|
-
ele = etree.Element(fulltag)
|
|
1826
|
-
else:
|
|
1827
|
-
ele = etree.Element(fulltag, options)
|
|
1828
|
-
if value is not None:
|
|
1829
|
-
ele.text = value
|
|
1830
|
-
return ele
|
|
1831
|
-
|
|
1832
|
-
def write_xml(self, filename: str):
|
|
1833
|
-
# print(etree.tostring(self.root, pretty_print=True, xml_declaration=True, encoding='utf-8'))
|
|
1834
|
-
f = open(filename, "wb")
|
|
1835
|
-
f.write(etree.tostring(self.root, pretty_print=True, xml_declaration=True, encoding='utf-8'))
|
|
1836
|
-
f.close()
|
|
1837
|
-
|
|
1838
|
-
def get_xml_string(self):
|
|
1839
|
-
"""
|
|
1840
|
-
This method returns the Bulk-Import XML as an UTF-8 encoded string.
|
|
1841
|
-
:return: UTF-8 encoded string.
|
|
1842
|
-
"""
|
|
1843
|
-
string = etree.tostring(self.root, pretty_print=True, xml_declaration=True, encoding='utf-8')
|
|
1844
|
-
return string
|
|
1845
|
-
|
|
1846
|
-
def upload(self, user, password, hostname, port):
|
|
1847
|
-
"""
|
|
1848
|
-
Upload the Bulk-Import XML to the server.
|
|
1849
|
-
:param user: the email of the user
|
|
1850
|
-
:param password: the password of the user
|
|
1851
|
-
:param hostname: the hostname (e.g., localhost, api.example.org, etc.)
|
|
1852
|
-
:param port: the port where the API is running (e.g., 3333)
|
|
1853
|
-
:return: the JSON response
|
|
1854
|
-
"""
|
|
1855
|
-
project_iri = "http://rdfh.ch/projects/" + self.project_shortcode
|
|
1856
|
-
url_encoded_project_iri = urllib.parse.quote_plus(project_iri)
|
|
1857
|
-
bulkimport_api_url = "http://" + hostname + ":" + port + "/v1/resources/xmlimport/" + url_encoded_project_iri
|
|
1858
|
-
headers = {"Content-Type": "application/xml"}
|
|
1859
|
-
r = requests.post(bulkimport_api_url, data=self.get_xml_string(), headers=headers, auth=(user, password))
|
|
1860
|
-
return r.json()
|
|
1861
|
-
|
|
1862
|
-
def add_resource(self, resclass: str, id: str, label: str, properties: Dict):
|
|
1863
|
-
"""
|
|
1864
|
-
|
|
1865
|
-
:param resclass:
|
|
1866
|
-
:param id:
|
|
1867
|
-
:param label:
|
|
1868
|
-
:param properties:
|
|
1869
|
-
:return:
|
|
1870
|
-
"""
|
|
1871
|
-
|
|
1872
|
-
def find_list_node_id(nodename: str, nodes: List):
|
|
1873
|
-
"""
|
|
1874
|
-
Finds a list node ID from the nodename in a (eventually hierarchical) list of nodes
|
|
1875
|
-
|
|
1876
|
-
:param nodename: Name of the node
|
|
1877
|
-
:param nodes: List of nodes
|
|
1878
|
-
:return: the id of the list node (an IRI)
|
|
1879
|
-
"""
|
|
1880
|
-
for node in nodes:
|
|
1881
|
-
if node["name"] == nodename:
|
|
1882
|
-
return node["id"]
|
|
1883
|
-
if node.get("nodes") is not None and len(node.get("nodes")) > 0:
|
|
1884
|
-
node_id = find_list_node_id(nodename, node["nodes"])
|
|
1885
|
-
if node_id is not None:
|
|
1886
|
-
return node_id
|
|
1887
|
-
return None
|
|
1888
|
-
|
|
1889
|
-
def process_properties(propinfo: Dict, valuestr: any):
|
|
1890
|
-
"""
|
|
1891
|
-
Processes a property in order to generate the approptiate XML for V1 bulk import.
|
|
1892
|
-
|
|
1893
|
-
:param pname: property name
|
|
1894
|
-
:param valuestr: value of the property
|
|
1895
|
-
:return: Tuple with xml options and processed value: (xmlopt, val)
|
|
1896
|
-
"""
|
|
1897
|
-
switcher = {
|
|
1898
|
-
'TextValue': {'knoraType': 'richtext_value'},
|
|
1899
|
-
'ColorValue': {'knoraType': 'color_value'},
|
|
1900
|
-
'DateValue': {'knoraType': 'date_value'},
|
|
1901
|
-
'DecimalValue': {'knoraType': 'decimal_value'},
|
|
1902
|
-
'GeomValue': {'knoraType': 'geom_value'},
|
|
1903
|
-
'GeonameValue': {'knoraType': 'geoname_value'},
|
|
1904
|
-
'IntValue': {'knoraType': 'int_value'},
|
|
1905
|
-
'BooleanValue': {'knoraType': 'boolean_value'},
|
|
1906
|
-
'UriValue': {'knoraType': 'uri_value'},
|
|
1907
|
-
'IntervalValue': {'knoraType': 'interval_value'},
|
|
1908
|
-
'ListValue': {'knoraType': 'hlist_value'},
|
|
1909
|
-
'LinkValue': {'knoraType': 'link_value'}
|
|
1910
|
-
}
|
|
1911
|
-
for link_otype in self.schema["link_otypes"]:
|
|
1912
|
-
switcher[link_otype] = {'knoraType': 'link_value'}
|
|
1913
|
-
xmlopt = switcher.get(propinfo["otype"])
|
|
1914
|
-
if xmlopt is None:
|
|
1915
|
-
raise KnoraError("Did not find " + propinfo["otype"] + " in switcher!")
|
|
1916
|
-
if xmlopt['knoraType'] == 'link_value':
|
|
1917
|
-
xmlopt['target'] = str(valuestr)
|
|
1918
|
-
if validators.url(str(valuestr)):
|
|
1919
|
-
xmlopt['linkType'] = 'iri'
|
|
1920
|
-
else:
|
|
1921
|
-
xmlopt['linkType'] = 'ref'
|
|
1922
|
-
value = None
|
|
1923
|
-
elif propinfo["otype"] == 'ListValue':
|
|
1924
|
-
if validators.url(str(valuestr)):
|
|
1925
|
-
# it's a full IRI identifying the node
|
|
1926
|
-
value = valuestr
|
|
1927
|
-
else:
|
|
1928
|
-
# it's only a node name. First let's get the node list from the ontology schema
|
|
1929
|
-
list_id = propinfo["attr"]["hlist"]
|
|
1930
|
-
for listname in self.schema["lists"]:
|
|
1931
|
-
if self.schema["lists"][listname]["id"] == list_id:
|
|
1932
|
-
nodes = self.schema["lists"][listname]["nodes"]
|
|
1933
|
-
value = find_list_node_id(str(valuestr), nodes)
|
|
1934
|
-
# if value == 'http://rdfh.ch/lists/0808/X6bb-JerQyu5ULruCGEO0w':
|
|
1935
|
-
# print("BANG!")
|
|
1936
|
-
# exit(0)
|
|
1937
|
-
elif propinfo["otype"] == 'DateValue':
|
|
1938
|
-
# processing and validating date format
|
|
1939
|
-
res = re.match('(GREGORIAN:|JULIAN:)?(\d{4})?(-\d{1,2})?(-\d{1,2})?(:\d{4})?(-\d{1,2})?(-\d{1,2})?',
|
|
1940
|
-
str(valuestr))
|
|
1941
|
-
if res is None:
|
|
1942
|
-
raise KnoraError("Invalid date format! " + str(valuestr))
|
|
1943
|
-
dp = res.groups()
|
|
1944
|
-
calendar = 'GREGORIAN:' if dp[0] is None else dp[0]
|
|
1945
|
-
y1 = None if dp[1] is None else int(dp[1].strip('-: '))
|
|
1946
|
-
m1 = None if dp[2] is None else int(dp[2].strip('-: '))
|
|
1947
|
-
d1 = None if dp[3] is None else int(dp[3].strip('-: '))
|
|
1948
|
-
y2 = None if dp[4] is None else int(dp[4].strip('-: '))
|
|
1949
|
-
m2 = None if dp[5] is None else int(dp[5].strip('-: '))
|
|
1950
|
-
d2 = None if dp[6] is None else int(dp[6].strip('-: '))
|
|
1951
|
-
if y1 is None:
|
|
1952
|
-
raise KnoraError("Invalid date format! " + str(valuestr))
|
|
1953
|
-
if y2 is not None:
|
|
1954
|
-
date1 = y1 * 10000;
|
|
1955
|
-
if m1 is not None:
|
|
1956
|
-
date1 += m1 * 100
|
|
1957
|
-
if d1 is not None:
|
|
1958
|
-
date1 += d1
|
|
1959
|
-
date2 = y2 * 10000;
|
|
1960
|
-
if m2 is not None:
|
|
1961
|
-
date2 += m2 * 100
|
|
1962
|
-
if d1 is not None:
|
|
1963
|
-
date2 += d2
|
|
1964
|
-
if date1 > date2:
|
|
1965
|
-
y1, y2 = y2, y1
|
|
1966
|
-
m1, m2 = m2, m1
|
|
1967
|
-
d1, d2 = d2, d1
|
|
1968
|
-
value = calendar + str(y1)
|
|
1969
|
-
if m1 is not None:
|
|
1970
|
-
value += f'-{m1:02d}'
|
|
1971
|
-
if d1 is not None:
|
|
1972
|
-
value += f'-{d1:02d}'
|
|
1973
|
-
if y2 is not None:
|
|
1974
|
-
value += f':{y2:04d}'
|
|
1975
|
-
if m2 is not None:
|
|
1976
|
-
value += f'-{m2:02d}'
|
|
1977
|
-
if d2 is not None:
|
|
1978
|
-
value += f'-{d2:02d}'
|
|
1979
|
-
else:
|
|
1980
|
-
value = str(valuestr)
|
|
1981
|
-
return xmlopt, value
|
|
1982
|
-
|
|
1983
|
-
if self.schema["resources"].get(resclass) is None:
|
|
1984
|
-
raise KnoraError('Resource class is not defined in ontology!')
|
|
1985
|
-
resnode = self.new_xml_element(self.proj_prefix + ':' + resclass, {'id': str(id)})
|
|
1986
|
-
|
|
1987
|
-
labelnode = self.new_xml_element('knoraXmlImport:label')
|
|
1988
|
-
labelnode.text = str(label)
|
|
1989
|
-
resnode.append(labelnode)
|
|
1990
|
-
|
|
1991
|
-
for prop_info in self.schema["resources"][resclass]:
|
|
1992
|
-
# first we check if the cardinality allows to add this property
|
|
1993
|
-
if properties.get(prop_info["propname"]) is None: # this property-value is missing
|
|
1994
|
-
if prop_info["card"] == 'cardinality' \
|
|
1995
|
-
and prop_info["cardval"] == 1:
|
|
1996
|
-
raise KnoraError(
|
|
1997
|
-
resclass + " requires exactly one " + prop_info["propname"] + "-value: none supplied!")
|
|
1998
|
-
if prop_info["card"] == 'minCardinality' \
|
|
1999
|
-
and prop_info["cardval"] == 1:
|
|
2000
|
-
raise KnoraError(
|
|
2001
|
-
resclass + " requires at least one " + prop_info["propname"] + "-value: none supplied!")
|
|
2002
|
-
continue
|
|
2003
|
-
if type(properties[prop_info["propname"]]) is list:
|
|
2004
|
-
if len(properties[prop_info["propname"]]) > 1:
|
|
2005
|
-
if prop_info["card"] == 'maxCardinality' \
|
|
2006
|
-
and prop_info["cardval"] == 1:
|
|
2007
|
-
raise KnoraError(
|
|
2008
|
-
resclass + " allows maximal one " + prop_info["propname"] + "-value: several supplied!")
|
|
2009
|
-
if prop_info["card"] == 'cardinality' \
|
|
2010
|
-
and prop_info["cardval"] == 1:
|
|
2011
|
-
raise KnoraError(
|
|
2012
|
-
resclass + " requires exactly one " + prop_info["propname"] + "-value: several supplied!")
|
|
2013
|
-
for p in properties[prop_info["propname"]]:
|
|
2014
|
-
xmlopt, value = process_properties(prop_info, p)
|
|
2015
|
-
if xmlopt['knoraType'] == 'link_value':
|
|
2016
|
-
pnode = self.new_xml_element(self.proj_prefix + ':' + prop_info["propname"])
|
|
2017
|
-
pnode.append(self.new_xml_element(self.proj_prefix + ':' + prop_info["otype"], xmlopt, value))
|
|
2018
|
-
else:
|
|
2019
|
-
pnode = self.new_xml_element(self.proj_prefix + ':' + prop_info["propname"], xmlopt, value)
|
|
2020
|
-
resnode.append(pnode)
|
|
2021
|
-
else:
|
|
2022
|
-
xmlopt, value = process_properties(prop_info, properties[prop_info["propname"]])
|
|
2023
|
-
if xmlopt['knoraType'] == 'link_value':
|
|
2024
|
-
pnode = self.new_xml_element(self.proj_prefix + ':' + prop_info["propname"])
|
|
2025
|
-
pnode.append(self.new_xml_element(self.proj_prefix + ':' + prop_info["otype"], xmlopt, value))
|
|
2026
|
-
else:
|
|
2027
|
-
pnode = self.new_xml_element(self.proj_prefix + ':' + prop_info["propname"], xmlopt, value)
|
|
2028
|
-
resnode.append(pnode)
|
|
2029
|
-
self.root.append(resnode)
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
class IrisLookup:
|
|
2033
|
-
def __init__(self, local_id_to_iri_json):
|
|
2034
|
-
self.iris = local_id_to_iri_json
|
|
2035
|
-
|
|
2036
|
-
def get_resource_iri(self, local_id):
|
|
2037
|
-
"""
|
|
2038
|
-
Given the result of the bulk-import as json, allow retrieving the resource
|
|
2039
|
-
IRI based on the local ID.
|
|
2040
|
-
{'createdResources': [{'clientResourceID': 'LM_1',
|
|
2041
|
-
'label': '1',
|
|
2042
|
-
'resourceIri': 'http://rdfh.ch/0807/rNxoIK-oR_i0-lO21Y9-CQ'},
|
|
2043
|
-
{'clientResourceID': 'LM_2']}
|
|
2044
|
-
|
|
2045
|
-
:param local_id: the local id. resulting JSON from a bulk import upload.
|
|
2046
|
-
:return:
|
|
2047
|
-
"""
|
|
2048
|
-
|
|
2049
|
-
try:
|
|
2050
|
-
resources = self.iris["createdResources"]
|
|
2051
|
-
iri = ""
|
|
2052
|
-
for resource in resources:
|
|
2053
|
-
try:
|
|
2054
|
-
res_id = resource["clientResourceID"]
|
|
2055
|
-
if res_id == local_id:
|
|
2056
|
-
iri = resource["resourceIri"]
|
|
2057
|
-
else:
|
|
2058
|
-
pass
|
|
2059
|
-
except KeyError:
|
|
2060
|
-
pass
|
|
2061
|
-
|
|
2062
|
-
if iri == "":
|
|
2063
|
-
return None
|
|
2064
|
-
else:
|
|
2065
|
-
return iri
|
|
2066
|
-
except KeyError:
|
|
2067
|
-
print("IrisLookup.get_resource_iri - 'createdResources' not found")
|
|
2068
|
-
|
|
2069
|
-
def get_iris_json(self):
|
|
2070
|
-
return self.iris
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
class ListsLookup:
|
|
2074
|
-
def __init__(self, lists_json):
|
|
2075
|
-
self.lists = lists_json
|
|
2076
|
-
|
|
2077
|
-
def get_list_iri(self, listname):
|
|
2078
|
-
return self.lists[listname]["id"]
|
|
2079
|
-
|
|
2080
|
-
def get_list_node_iri(self, listname, nodename):
|
|
2081
|
-
if nodename is not None:
|
|
2082
|
-
nodes = self.lists[listname]["nodes"]
|
|
2083
|
-
res = ""
|
|
2084
|
-
for node in nodes:
|
|
2085
|
-
try:
|
|
2086
|
-
res = node[nodename]["id"]
|
|
2087
|
-
except KeyError:
|
|
2088
|
-
pass
|
|
2089
|
-
|
|
2090
|
-
if res == "":
|
|
2091
|
-
return None
|
|
2092
|
-
else:
|
|
2093
|
-
return res
|
|
2094
|
-
else:
|
|
2095
|
-
return None
|
|
2096
|
-
|
|
2097
|
-
def get_lists_json(self):
|
|
2098
|
-
return self.lists
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
if __name__ == '__main__':
|
|
2102
|
-
con = Knora('http://localhost:3333')
|
|
2103
|
-
con.login('root@example.com', 'test')
|
|
2104
|
-
res = con.get_resource_by_label('Bertschy, Leon',
|
|
2105
|
-
res_class="http://0.0.0.0:3333/ontology/0807/mls/v2#Lemma")
|
|
2106
|
-
print('RES-IRI: ', res['@id'])
|
|
2107
|
-
con.logout()
|
|
2108
|
-
|