dsp-tools 0.9.13__py3-none-any.whl → 18.3.0.post13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dsp_tools/__init__.py +5 -0
- dsp_tools/cli/args.py +47 -0
- dsp_tools/cli/call_action.py +85 -0
- dsp_tools/cli/call_action_files_only.py +101 -0
- dsp_tools/cli/call_action_with_network.py +207 -0
- dsp_tools/cli/create_parsers.py +479 -0
- dsp_tools/cli/entry_point.py +322 -0
- dsp_tools/cli/utils.py +87 -0
- dsp_tools/clients/CLAUDE.md +420 -0
- dsp_tools/clients/authentication_client.py +14 -0
- dsp_tools/clients/authentication_client_live.py +66 -0
- dsp_tools/clients/connection.py +35 -0
- dsp_tools/clients/connection_live.py +233 -0
- dsp_tools/clients/fuseki_metrics.py +60 -0
- dsp_tools/clients/group_user_clients.py +35 -0
- dsp_tools/clients/group_user_clients_live.py +181 -0
- dsp_tools/clients/legal_info_client.py +23 -0
- dsp_tools/clients/legal_info_client_live.py +132 -0
- dsp_tools/clients/list_client.py +49 -0
- dsp_tools/clients/list_client_live.py +166 -0
- dsp_tools/clients/metadata_client.py +24 -0
- dsp_tools/clients/metadata_client_live.py +47 -0
- dsp_tools/clients/ontology_clients.py +49 -0
- dsp_tools/clients/ontology_create_client_live.py +166 -0
- dsp_tools/clients/ontology_get_client_live.py +80 -0
- dsp_tools/clients/permissions_client.py +68 -0
- dsp_tools/clients/project_client.py +16 -0
- dsp_tools/clients/project_client_live.py +66 -0
- dsp_tools/commands/create/communicate_problems.py +24 -0
- dsp_tools/commands/create/create.py +134 -0
- dsp_tools/commands/create/create_on_server/cardinalities.py +111 -0
- dsp_tools/commands/create/create_on_server/classes.py +99 -0
- dsp_tools/commands/create/create_on_server/complete_ontologies.py +116 -0
- dsp_tools/commands/create/create_on_server/default_permissions.py +134 -0
- dsp_tools/commands/create/create_on_server/group_users.py +165 -0
- dsp_tools/commands/create/create_on_server/lists.py +163 -0
- dsp_tools/commands/create/create_on_server/mappers.py +12 -0
- dsp_tools/commands/create/create_on_server/onto_utils.py +74 -0
- dsp_tools/commands/create/create_on_server/ontology.py +52 -0
- dsp_tools/commands/create/create_on_server/project.py +68 -0
- dsp_tools/commands/create/create_on_server/properties.py +119 -0
- dsp_tools/commands/create/exceptions.py +29 -0
- dsp_tools/commands/create/lists_only.py +66 -0
- dsp_tools/commands/create/models/create_problems.py +87 -0
- dsp_tools/commands/create/models/parsed_ontology.py +88 -0
- dsp_tools/commands/create/models/parsed_project.py +81 -0
- dsp_tools/commands/create/models/rdf_ontology.py +12 -0
- dsp_tools/commands/create/models/server_project_info.py +100 -0
- dsp_tools/commands/create/parsing/parse_lists.py +45 -0
- dsp_tools/commands/create/parsing/parse_ontology.py +243 -0
- dsp_tools/commands/create/parsing/parse_project.py +149 -0
- dsp_tools/commands/create/parsing/parsing_utils.py +40 -0
- dsp_tools/commands/create/project_validate.py +595 -0
- dsp_tools/commands/create/serialisation/ontology.py +119 -0
- dsp_tools/commands/create/serialisation/project.py +44 -0
- dsp_tools/commands/excel2json/CLAUDE.md +101 -0
- dsp_tools/commands/excel2json/json_header.py +321 -0
- dsp_tools/commands/excel2json/lists/__init__.py +0 -0
- dsp_tools/commands/excel2json/lists/compliance_checks.py +292 -0
- dsp_tools/commands/excel2json/lists/make_lists.py +247 -0
- dsp_tools/commands/excel2json/lists/models/__init__.py +0 -0
- dsp_tools/commands/excel2json/lists/models/deserialise.py +30 -0
- dsp_tools/commands/excel2json/lists/models/input_error.py +216 -0
- dsp_tools/commands/excel2json/lists/models/serialise.py +57 -0
- dsp_tools/commands/excel2json/lists/utils.py +81 -0
- dsp_tools/commands/excel2json/models/__init__.py +0 -0
- dsp_tools/commands/excel2json/models/input_error.py +416 -0
- dsp_tools/commands/excel2json/models/json_header.py +175 -0
- dsp_tools/commands/excel2json/models/list_node_name.py +16 -0
- dsp_tools/commands/excel2json/models/ontology.py +76 -0
- dsp_tools/commands/excel2json/old_lists.py +328 -0
- dsp_tools/commands/excel2json/project.py +280 -0
- dsp_tools/commands/excel2json/properties.py +370 -0
- dsp_tools/commands/excel2json/resources.py +336 -0
- dsp_tools/commands/excel2json/utils.py +352 -0
- dsp_tools/commands/excel2xml/__init__.py +7 -0
- dsp_tools/commands/excel2xml/excel2xml_cli.py +523 -0
- dsp_tools/commands/excel2xml/excel2xml_lib.py +1953 -0
- dsp_tools/commands/excel2xml/propertyelement.py +47 -0
- dsp_tools/commands/get/__init__.py +0 -0
- dsp_tools/commands/get/get.py +166 -0
- dsp_tools/commands/get/get_permissions.py +257 -0
- dsp_tools/commands/get/get_permissions_legacy.py +89 -0
- dsp_tools/commands/get/legacy_models/__init__.py +0 -0
- dsp_tools/commands/get/legacy_models/context.py +318 -0
- dsp_tools/commands/get/legacy_models/group.py +241 -0
- dsp_tools/commands/get/legacy_models/helpers.py +47 -0
- dsp_tools/commands/get/legacy_models/listnode.py +390 -0
- dsp_tools/commands/get/legacy_models/model.py +12 -0
- dsp_tools/commands/get/legacy_models/ontology.py +324 -0
- dsp_tools/commands/get/legacy_models/project.py +366 -0
- dsp_tools/commands/get/legacy_models/propertyclass.py +417 -0
- dsp_tools/commands/get/legacy_models/resourceclass.py +676 -0
- dsp_tools/commands/get/legacy_models/user.py +438 -0
- dsp_tools/commands/get/models/__init__.py +0 -0
- dsp_tools/commands/get/models/permissions_models.py +10 -0
- dsp_tools/commands/id2iri.py +258 -0
- dsp_tools/commands/ingest_xmlupload/__init__.py +0 -0
- dsp_tools/commands/ingest_xmlupload/bulk_ingest_client.py +178 -0
- dsp_tools/commands/ingest_xmlupload/create_resources/__init__.py +0 -0
- dsp_tools/commands/ingest_xmlupload/create_resources/apply_ingest_id.py +69 -0
- dsp_tools/commands/ingest_xmlupload/create_resources/upload_xml.py +166 -0
- dsp_tools/commands/ingest_xmlupload/create_resources/user_information.py +121 -0
- dsp_tools/commands/ingest_xmlupload/ingest_files/__init__.py +0 -0
- dsp_tools/commands/ingest_xmlupload/ingest_files/ingest_files.py +64 -0
- dsp_tools/commands/ingest_xmlupload/upload_files/__init__.py +0 -0
- dsp_tools/commands/ingest_xmlupload/upload_files/filechecker.py +20 -0
- dsp_tools/commands/ingest_xmlupload/upload_files/input_error.py +57 -0
- dsp_tools/commands/ingest_xmlupload/upload_files/upload_failures.py +66 -0
- dsp_tools/commands/ingest_xmlupload/upload_files/upload_files.py +67 -0
- dsp_tools/commands/resume_xmlupload/__init__.py +0 -0
- dsp_tools/commands/resume_xmlupload/resume_xmlupload.py +96 -0
- dsp_tools/commands/start_stack.py +428 -0
- dsp_tools/commands/update_legal/CLAUDE.md +344 -0
- dsp_tools/commands/update_legal/__init__.py +0 -0
- dsp_tools/commands/update_legal/core.py +182 -0
- dsp_tools/commands/update_legal/csv_operations.py +135 -0
- dsp_tools/commands/update_legal/models.py +87 -0
- dsp_tools/commands/update_legal/xml_operations.py +247 -0
- dsp_tools/commands/validate_data/CLAUDE.md +159 -0
- dsp_tools/commands/validate_data/__init__.py +0 -0
- dsp_tools/commands/validate_data/constants.py +59 -0
- dsp_tools/commands/validate_data/mappers.py +143 -0
- dsp_tools/commands/validate_data/models/__init__.py +0 -0
- dsp_tools/commands/validate_data/models/api_responses.py +45 -0
- dsp_tools/commands/validate_data/models/input_problems.py +119 -0
- dsp_tools/commands/validate_data/models/rdf_like_data.py +117 -0
- dsp_tools/commands/validate_data/models/validation.py +106 -0
- dsp_tools/commands/validate_data/prepare_data/__init__.py +0 -0
- dsp_tools/commands/validate_data/prepare_data/get_rdf_like_data.py +296 -0
- dsp_tools/commands/validate_data/prepare_data/make_data_graph.py +91 -0
- dsp_tools/commands/validate_data/prepare_data/prepare_data.py +184 -0
- dsp_tools/commands/validate_data/process_validation_report/__init__.py +0 -0
- dsp_tools/commands/validate_data/process_validation_report/get_user_validation_message.py +358 -0
- dsp_tools/commands/validate_data/process_validation_report/query_validation_result.py +507 -0
- dsp_tools/commands/validate_data/process_validation_report/reformat_validation_results.py +150 -0
- dsp_tools/commands/validate_data/shacl_cli_validator.py +70 -0
- dsp_tools/commands/validate_data/sparql/__init__.py +0 -0
- dsp_tools/commands/validate_data/sparql/cardinality_shacl.py +209 -0
- dsp_tools/commands/validate_data/sparql/construct_shacl.py +92 -0
- dsp_tools/commands/validate_data/sparql/legal_info_shacl.py +36 -0
- dsp_tools/commands/validate_data/sparql/value_shacl.py +357 -0
- dsp_tools/commands/validate_data/utils.py +59 -0
- dsp_tools/commands/validate_data/validate_data.py +283 -0
- dsp_tools/commands/validate_data/validation/__init__.py +0 -0
- dsp_tools/commands/validate_data/validation/check_duplicate_files.py +55 -0
- dsp_tools/commands/validate_data/validation/check_for_unknown_classes.py +67 -0
- dsp_tools/commands/validate_data/validation/get_validation_report.py +94 -0
- dsp_tools/commands/validate_data/validation/validate_ontology.py +107 -0
- dsp_tools/commands/xmlupload/CLAUDE.md +292 -0
- dsp_tools/commands/xmlupload/__init__.py +0 -0
- dsp_tools/commands/xmlupload/iri_resolver.py +21 -0
- dsp_tools/commands/xmlupload/make_rdf_graph/__init__.py +0 -0
- dsp_tools/commands/xmlupload/make_rdf_graph/constants.py +63 -0
- dsp_tools/commands/xmlupload/make_rdf_graph/jsonld_utils.py +44 -0
- dsp_tools/commands/xmlupload/make_rdf_graph/make_file_value.py +77 -0
- dsp_tools/commands/xmlupload/make_rdf_graph/make_resource_and_values.py +114 -0
- dsp_tools/commands/xmlupload/make_rdf_graph/make_values.py +262 -0
- dsp_tools/commands/xmlupload/models/__init__.py +0 -0
- dsp_tools/commands/xmlupload/models/bitstream_info.py +18 -0
- dsp_tools/commands/xmlupload/models/formatted_text_value.py +10 -0
- dsp_tools/commands/xmlupload/models/ingest.py +143 -0
- dsp_tools/commands/xmlupload/models/input_problems.py +58 -0
- dsp_tools/commands/xmlupload/models/lookup_models.py +21 -0
- dsp_tools/commands/xmlupload/models/permission.py +45 -0
- dsp_tools/commands/xmlupload/models/permissions_parsed.py +93 -0
- dsp_tools/commands/xmlupload/models/processed/__init__.py +0 -0
- dsp_tools/commands/xmlupload/models/processed/file_values.py +29 -0
- dsp_tools/commands/xmlupload/models/processed/res.py +27 -0
- dsp_tools/commands/xmlupload/models/processed/values.py +101 -0
- dsp_tools/commands/xmlupload/models/rdf_models.py +26 -0
- dsp_tools/commands/xmlupload/models/upload_clients.py +14 -0
- dsp_tools/commands/xmlupload/models/upload_state.py +20 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/__init__.py +0 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/ark2iri.py +55 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/get_processed_resources.py +252 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/iiif_uri_validator.py +50 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/list_client.py +120 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/prepare_xml_input.py +67 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/read_validate_xml_file.py +58 -0
- dsp_tools/commands/xmlupload/prepare_xml_input/transform_input_values.py +118 -0
- dsp_tools/commands/xmlupload/resource_create_client.py +25 -0
- dsp_tools/commands/xmlupload/richtext_id2iri.py +37 -0
- dsp_tools/commands/xmlupload/stash/__init__.py +0 -0
- dsp_tools/commands/xmlupload/stash/analyse_circular_reference_graph.py +236 -0
- dsp_tools/commands/xmlupload/stash/create_info_for_graph.py +53 -0
- dsp_tools/commands/xmlupload/stash/graph_models.py +87 -0
- dsp_tools/commands/xmlupload/stash/stash_circular_references.py +68 -0
- dsp_tools/commands/xmlupload/stash/stash_models.py +109 -0
- dsp_tools/commands/xmlupload/stash/upload_stashed_resptr_props.py +106 -0
- dsp_tools/commands/xmlupload/stash/upload_stashed_xml_texts.py +196 -0
- dsp_tools/commands/xmlupload/upload_config.py +76 -0
- dsp_tools/commands/xmlupload/write_diagnostic_info.py +27 -0
- dsp_tools/commands/xmlupload/xmlupload.py +516 -0
- dsp_tools/config/__init__.py +0 -0
- dsp_tools/config/logger_config.py +69 -0
- dsp_tools/config/warnings_config.py +32 -0
- dsp_tools/error/__init__.py +0 -0
- dsp_tools/error/custom_warnings.py +39 -0
- dsp_tools/error/exceptions.py +204 -0
- dsp_tools/error/problems.py +10 -0
- dsp_tools/error/xmllib_errors.py +20 -0
- dsp_tools/error/xmllib_warnings.py +54 -0
- dsp_tools/error/xmllib_warnings_util.py +159 -0
- dsp_tools/error/xsd_validation_error_msg.py +19 -0
- dsp_tools/legacy_models/__init__.py +0 -0
- dsp_tools/legacy_models/datetimestamp.py +81 -0
- dsp_tools/legacy_models/langstring.py +253 -0
- dsp_tools/legacy_models/projectContext.py +49 -0
- dsp_tools/py.typed +0 -0
- dsp_tools/resources/schema/data.xsd +648 -0
- dsp_tools/resources/schema/lists-only.json +72 -0
- dsp_tools/resources/schema/project.json +1258 -0
- dsp_tools/resources/schema/properties-only.json +874 -0
- dsp_tools/resources/schema/resources-only.json +140 -0
- dsp_tools/resources/start-stack/docker-compose.override-host.j2 +11 -0
- dsp_tools/resources/start-stack/docker-compose.override.yml +11 -0
- dsp_tools/resources/start-stack/docker-compose.yml +88 -0
- dsp_tools/resources/start-stack/dsp-app-config.json +45 -0
- dsp_tools/resources/start-stack/dsp-app-config.override-host.j2 +26 -0
- dsp_tools/resources/validate_data/api-shapes-resource-cardinalities.ttl +191 -0
- dsp_tools/resources/validate_data/api-shapes.ttl +804 -0
- dsp_tools/resources/validate_data/shacl-cli-image.yml +4 -0
- dsp_tools/resources/validate_data/validate-ontology.ttl +99 -0
- dsp_tools/utils/__init__.py +0 -0
- dsp_tools/utils/ansi_colors.py +32 -0
- dsp_tools/utils/data_formats/__init__.py +0 -0
- dsp_tools/utils/data_formats/date_util.py +166 -0
- dsp_tools/utils/data_formats/iri_util.py +30 -0
- dsp_tools/utils/data_formats/shared.py +81 -0
- dsp_tools/utils/data_formats/uri_util.py +76 -0
- dsp_tools/utils/fuseki_bloating.py +63 -0
- dsp_tools/utils/json_parsing.py +22 -0
- dsp_tools/utils/rdf_constants.py +42 -0
- dsp_tools/utils/rdflib_utils.py +10 -0
- dsp_tools/utils/replace_id_with_iri.py +66 -0
- dsp_tools/utils/request_utils.py +238 -0
- dsp_tools/utils/xml_parsing/__init__.py +0 -0
- dsp_tools/utils/xml_parsing/get_lookups.py +32 -0
- dsp_tools/utils/xml_parsing/get_parsed_resources.py +325 -0
- dsp_tools/utils/xml_parsing/models/__init__.py +0 -0
- dsp_tools/utils/xml_parsing/models/parsed_resource.py +76 -0
- dsp_tools/utils/xml_parsing/parse_clean_validate_xml.py +137 -0
- dsp_tools/xmllib/CLAUDE.md +302 -0
- dsp_tools/xmllib/__init__.py +49 -0
- dsp_tools/xmllib/general_functions.py +877 -0
- dsp_tools/xmllib/internal/__init__.py +0 -0
- dsp_tools/xmllib/internal/checkers.py +162 -0
- dsp_tools/xmllib/internal/circumvent_circular_imports.py +36 -0
- dsp_tools/xmllib/internal/constants.py +46 -0
- dsp_tools/xmllib/internal/input_converters.py +155 -0
- dsp_tools/xmllib/internal/serialise_file_value.py +57 -0
- dsp_tools/xmllib/internal/serialise_resource.py +177 -0
- dsp_tools/xmllib/internal/serialise_values.py +152 -0
- dsp_tools/xmllib/internal/type_aliases.py +11 -0
- dsp_tools/xmllib/models/__init__.py +0 -0
- dsp_tools/xmllib/models/config_options.py +28 -0
- dsp_tools/xmllib/models/date_formats.py +48 -0
- dsp_tools/xmllib/models/dsp_base_resources.py +1542 -0
- dsp_tools/xmllib/models/internal/__init__.py +0 -0
- dsp_tools/xmllib/models/internal/file_values.py +172 -0
- dsp_tools/xmllib/models/internal/geometry.py +162 -0
- dsp_tools/xmllib/models/internal/migration_metadata.py +55 -0
- dsp_tools/xmllib/models/internal/serialise_permissions.py +66 -0
- dsp_tools/xmllib/models/internal/values.py +342 -0
- dsp_tools/xmllib/models/licenses/__init__.py +0 -0
- dsp_tools/xmllib/models/licenses/other.py +59 -0
- dsp_tools/xmllib/models/licenses/recommended.py +107 -0
- dsp_tools/xmllib/models/permissions.py +41 -0
- dsp_tools/xmllib/models/res.py +1782 -0
- dsp_tools/xmllib/models/root.py +348 -0
- dsp_tools/xmllib/value_checkers.py +434 -0
- dsp_tools/xmllib/value_converters.py +777 -0
- dsp_tools-18.3.0.post13.dist-info/METADATA +90 -0
- dsp_tools-18.3.0.post13.dist-info/RECORD +286 -0
- dsp_tools-18.3.0.post13.dist-info/WHEEL +4 -0
- dsp_tools-18.3.0.post13.dist-info/entry_points.txt +3 -0
- dsp_tools-0.9.13.dist-info/LICENSE +0 -674
- dsp_tools-0.9.13.dist-info/METADATA +0 -144
- dsp_tools-0.9.13.dist-info/RECORD +0 -71
- dsp_tools-0.9.13.dist-info/WHEEL +0 -5
- dsp_tools-0.9.13.dist-info/entry_points.txt +0 -3
- dsp_tools-0.9.13.dist-info/top_level.txt +0 -1
- dsplib/models/connection.py +0 -272
- dsplib/models/group.py +0 -296
- dsplib/models/helpers.py +0 -505
- dsplib/models/langstring.py +0 -277
- dsplib/models/listnode.py +0 -578
- dsplib/models/model.py +0 -20
- dsplib/models/ontology.py +0 -448
- dsplib/models/permission.py +0 -112
- dsplib/models/project.py +0 -547
- dsplib/models/propertyclass.py +0 -505
- dsplib/models/resource.py +0 -366
- dsplib/models/resourceclass.py +0 -810
- dsplib/models/sipi.py +0 -30
- dsplib/models/user.py +0 -731
- dsplib/models/value.py +0 -1000
- dsplib/utils/knora-data-schema.xsd +0 -454
- dsplib/utils/knora-schema-lists.json +0 -83
- dsplib/utils/knora-schema.json +0 -434
- dsplib/utils/onto_commons.py +0 -24
- dsplib/utils/onto_create_lists.py +0 -73
- dsplib/utils/onto_create_ontology.py +0 -442
- dsplib/utils/onto_get.py +0 -58
- dsplib/utils/onto_validate.py +0 -33
- dsplib/utils/xml_upload.py +0 -539
- dsplib/widgets/doublepassword.py +0 -80
- knora/MLS-import-libraries.py +0 -84
- knora/dsp_tools.py +0 -96
- knora/dsplib/models/connection.py +0 -272
- knora/dsplib/models/group.py +0 -296
- knora/dsplib/models/helpers.py +0 -506
- knora/dsplib/models/langstring.py +0 -277
- knora/dsplib/models/listnode.py +0 -578
- knora/dsplib/models/model.py +0 -20
- knora/dsplib/models/ontology.py +0 -448
- knora/dsplib/models/permission.py +0 -112
- knora/dsplib/models/project.py +0 -583
- knora/dsplib/models/propertyclass.py +0 -505
- knora/dsplib/models/resource.py +0 -416
- knora/dsplib/models/resourceclass.py +0 -811
- knora/dsplib/models/sipi.py +0 -35
- knora/dsplib/models/user.py +0 -731
- knora/dsplib/models/value.py +0 -1000
- knora/dsplib/utils/knora-data-schema.xsd +0 -464
- knora/dsplib/utils/knora-schema-lists.json +0 -83
- knora/dsplib/utils/knora-schema.json +0 -444
- knora/dsplib/utils/onto_commons.py +0 -24
- knora/dsplib/utils/onto_create_lists.py +0 -73
- knora/dsplib/utils/onto_create_ontology.py +0 -451
- knora/dsplib/utils/onto_get.py +0 -58
- knora/dsplib/utils/onto_validate.py +0 -33
- knora/dsplib/utils/xml_upload.py +0 -540
- knora/dsplib/widgets/doublepassword.py +0 -80
- knora/knora.py +0 -2108
- knora/test.py +0 -99
- knora/testit.py +0 -76
- knora/xml2knora.py +0 -633
- {dsplib → dsp_tools/cli}/__init__.py +0 -0
- {dsplib/models → dsp_tools/clients}/__init__.py +0 -0
- {dsplib/utils → dsp_tools/commands}/__init__.py +0 -0
- {dsplib/widgets → dsp_tools/commands/create}/__init__.py +0 -0
- {knora → dsp_tools/commands/create/create_on_server}/__init__.py +0 -0
- {knora/dsplib → dsp_tools/commands/create/models}/__init__.py +0 -0
- {knora/dsplib/models → dsp_tools/commands/create/parsing}/__init__.py +0 -0
- {knora/dsplib/utils → dsp_tools/commands/create/serialisation}/__init__.py +0 -0
- {knora/dsplib/widgets → dsp_tools/commands/excel2json}/__init__.py +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
from dsp_tools.error.exceptions import BaseError
|
|
6
|
+
|
|
7
|
+
# ruff: noqa: E501 (line-too-long)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class PropertyElement:
|
|
12
|
+
"""
|
|
13
|
+
A PropertyElement object carries more information about a property value than the value itself.
|
|
14
|
+
The "value" is the value that could be passed to a method as plain string/int/float/bool. Use a PropertyElement
|
|
15
|
+
instead to define more precisely what attributes your value tag (e.g. `<text>`, `<uri>`, ...) will have.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
value: This is the content that will be written into the value tag (e.g. `<text>`, `<uri>`, ...)
|
|
19
|
+
permissions: This is the permissions that your `<text>` tag (for example) will have
|
|
20
|
+
comment: This is the comment that your `<text>` tag (for example) will have
|
|
21
|
+
encoding: For `<text>` tags only. If provided, it must be "xml" or "utf8".
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
See the difference between the first and the second example:
|
|
25
|
+
|
|
26
|
+
>>> excel2xml.make_text_prop(":testproperty", "first text")
|
|
27
|
+
<text-prop name=":testproperty">
|
|
28
|
+
<text encoding="utf8" permissions="public">
|
|
29
|
+
first text
|
|
30
|
+
</text>
|
|
31
|
+
</text-prop>
|
|
32
|
+
>>> excel2xml.make_text_prop(":testproperty", excel2xml.PropertyElement("first text", permissions="private", encoding="xml"))
|
|
33
|
+
<text-prop name=":testproperty">
|
|
34
|
+
<text encoding="xml" permissions="private">
|
|
35
|
+
first text
|
|
36
|
+
</text>
|
|
37
|
+
</text-prop>
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
value: Union[str, int, float, bool]
|
|
41
|
+
permissions: str = "public"
|
|
42
|
+
comment: Optional[str] = None
|
|
43
|
+
encoding: Optional[str] = None
|
|
44
|
+
|
|
45
|
+
def __post_init__(self) -> None:
|
|
46
|
+
if self.encoding not in ["utf8", "xml", None]:
|
|
47
|
+
raise BaseError(f"'{self.encoding}' is not a valid encoding for a PropertyElement")
|
|
File without changes
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import warnings
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import regex
|
|
6
|
+
|
|
7
|
+
from dsp_tools.cli.args import ServerCredentials
|
|
8
|
+
from dsp_tools.clients.authentication_client_live import AuthenticationClientLive
|
|
9
|
+
from dsp_tools.clients.connection import Connection
|
|
10
|
+
from dsp_tools.clients.connection_live import ConnectionLive
|
|
11
|
+
from dsp_tools.commands.get.get_permissions import get_default_permissions
|
|
12
|
+
from dsp_tools.commands.get.legacy_models.group import Group
|
|
13
|
+
from dsp_tools.commands.get.legacy_models.listnode import ListNode
|
|
14
|
+
from dsp_tools.commands.get.legacy_models.ontology import Ontology
|
|
15
|
+
from dsp_tools.commands.get.legacy_models.project import Project
|
|
16
|
+
from dsp_tools.commands.get.legacy_models.user import User
|
|
17
|
+
from dsp_tools.error.exceptions import BaseError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_project(
|
|
21
|
+
project_identifier: str,
|
|
22
|
+
outfile_path: str,
|
|
23
|
+
creds: ServerCredentials,
|
|
24
|
+
verbose: bool = False,
|
|
25
|
+
) -> bool:
|
|
26
|
+
"""
|
|
27
|
+
This function writes a project from a DSP server into a JSON file.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
project_identifier: the project identifier, either shortcode, shortname or IRI of the project
|
|
31
|
+
outfile_path: the output file the JSON content should be written to
|
|
32
|
+
creds: the credentials to access the DSP server
|
|
33
|
+
verbose: verbose option for the command, if used more output is given to the user
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
BaseError: if something went wrong
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
True if the process finishes without errors
|
|
40
|
+
"""
|
|
41
|
+
auth = AuthenticationClientLive(creds.server, creds.user, creds.password)
|
|
42
|
+
try:
|
|
43
|
+
auth.get_token()
|
|
44
|
+
con = ConnectionLive(creds.server, auth)
|
|
45
|
+
authenticated = True
|
|
46
|
+
except BaseError:
|
|
47
|
+
warnings.warn("WARNING: Missing or wrong credentials. You won't get sensitive data of this project.")
|
|
48
|
+
con = ConnectionLive(creds.server)
|
|
49
|
+
authenticated = False
|
|
50
|
+
|
|
51
|
+
project = _create_project(con, project_identifier)
|
|
52
|
+
|
|
53
|
+
project = project.read()
|
|
54
|
+
project_obj = project.createDefinitionFileObj()
|
|
55
|
+
|
|
56
|
+
prefixes, ontos = _get_ontologies(con, str(project.iri), verbose)
|
|
57
|
+
|
|
58
|
+
if authenticated:
|
|
59
|
+
default_permissions, default_permissions_overrule = get_default_permissions(auth, str(project.iri), prefixes)
|
|
60
|
+
project_obj["default_permissions"] = default_permissions
|
|
61
|
+
if default_permissions_overrule:
|
|
62
|
+
project_obj["default_permissions_overrule"] = default_permissions_overrule
|
|
63
|
+
else:
|
|
64
|
+
project_obj["default_permissions"] = "Please provide credentials to retrieve the permissions of this project."
|
|
65
|
+
|
|
66
|
+
project_obj["groups"] = _get_groups(con, str(project.iri), verbose)
|
|
67
|
+
|
|
68
|
+
if users := _get_users(con, project, verbose):
|
|
69
|
+
project_obj["users"] = users
|
|
70
|
+
|
|
71
|
+
project_obj["lists"] = _get_lists(con, project, verbose)
|
|
72
|
+
|
|
73
|
+
project_obj["ontologies"] = ontos
|
|
74
|
+
|
|
75
|
+
schema = "https://raw.githubusercontent.com/dasch-swiss/dsp-tools/main/src/dsp_tools/resources/schema/project.json"
|
|
76
|
+
outfile_content = {
|
|
77
|
+
"prefixes": prefixes,
|
|
78
|
+
"$schema": schema,
|
|
79
|
+
"project": project_obj,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
with open(outfile_path, "w", encoding="utf-8") as f:
|
|
83
|
+
json.dump(outfile_content, f, indent=4, ensure_ascii=False)
|
|
84
|
+
|
|
85
|
+
return True
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _create_project(con: Connection, project_identifier: str) -> Project:
|
|
89
|
+
if regex.match("[0-9A-F]{4}", project_identifier): # shortcode
|
|
90
|
+
return Project(con=con, shortcode=project_identifier)
|
|
91
|
+
elif regex.match("^[\\w-]+$", project_identifier): # shortname
|
|
92
|
+
return Project(con=con, shortname=project_identifier.lower())
|
|
93
|
+
elif regex.match("^(http)s?://([\\w\\.\\-~]+:?\\d{,4})(/[\\w\\-~]+)+$", project_identifier): # iri
|
|
94
|
+
return Project(con=con, shortname=project_identifier)
|
|
95
|
+
else:
|
|
96
|
+
raise BaseError(
|
|
97
|
+
f"ERROR Invalid project identifier '{project_identifier}'. Use the project's shortcode, shortname or IRI."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _get_groups(con: Connection, project_iri: str, verbose: bool) -> list[dict[str, Any]]:
|
|
102
|
+
if verbose:
|
|
103
|
+
print("Getting groups...")
|
|
104
|
+
groups_obj: list[dict[str, Any]] = []
|
|
105
|
+
if groups := Group.getAllGroupsForProject(con=con, proj_iri=project_iri):
|
|
106
|
+
for group in groups:
|
|
107
|
+
groups_obj.append(group.createDefinitionFileObj())
|
|
108
|
+
if verbose:
|
|
109
|
+
print(f" Got group '{group.name}'")
|
|
110
|
+
return groups_obj
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _get_users(con: Connection, project: Project, verbose: bool) -> list[dict[str, Any]] | None:
|
|
114
|
+
if verbose:
|
|
115
|
+
print("Getting users...")
|
|
116
|
+
try:
|
|
117
|
+
users = User.getAllUsersForProject(con=con, proj_shortcode=str(project.shortcode))
|
|
118
|
+
except BaseError:
|
|
119
|
+
return None
|
|
120
|
+
if users is None:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
users_obj: list[dict[str, Any]] = []
|
|
124
|
+
for usr in users:
|
|
125
|
+
users_obj.append(
|
|
126
|
+
usr.createDefinitionFileObj(
|
|
127
|
+
con=con,
|
|
128
|
+
proj_shortname=str(project.shortname),
|
|
129
|
+
proj_iri=str(project.iri),
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
if verbose:
|
|
133
|
+
print(f" Got user '{usr.username}'")
|
|
134
|
+
return users_obj
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _get_lists(con: Connection, project: Project, verbose: bool) -> list[dict[str, Any]]:
|
|
138
|
+
if verbose:
|
|
139
|
+
print("Getting lists...")
|
|
140
|
+
list_obj: list[dict[str, Any]] = []
|
|
141
|
+
if list_roots := ListNode.getAllLists(con=con, project_iri=project.iri):
|
|
142
|
+
for list_root in list_roots:
|
|
143
|
+
complete_list = list_root.getAllNodes()
|
|
144
|
+
list_obj.append(complete_list.createDefinitionFileObj())
|
|
145
|
+
if verbose:
|
|
146
|
+
print(f" Got list '{list_root.name}'")
|
|
147
|
+
return list_obj
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _get_ontologies(con: Connection, project_iri: str, verbose: bool) -> tuple[dict[str, str], list[dict[str, Any]]]:
|
|
151
|
+
if verbose:
|
|
152
|
+
print("Getting ontologies...")
|
|
153
|
+
ontos = []
|
|
154
|
+
prefixes: dict[str, str] = {}
|
|
155
|
+
ontologies = Ontology.getProjectOntologies(con, project_iri)
|
|
156
|
+
ontology_ids = [onto.iri for onto in ontologies]
|
|
157
|
+
for ontology_id in ontology_ids:
|
|
158
|
+
onto_url_parts = ontology_id.split("/") # an id has the form http://0.0.0.0:3333/ontology/4123/testonto/v2
|
|
159
|
+
name = onto_url_parts[len(onto_url_parts) - 2]
|
|
160
|
+
shortcode = onto_url_parts[len(onto_url_parts) - 3]
|
|
161
|
+
ontology = Ontology.getOntologyFromServer(con=con, shortcode=shortcode, name=name)
|
|
162
|
+
ontos.append(ontology.createDefinitionFileObj())
|
|
163
|
+
prefixes.update(ontology.context.get_externals_used())
|
|
164
|
+
if verbose:
|
|
165
|
+
print(f" Got ontology '{name}'")
|
|
166
|
+
return prefixes, ontos
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
import regex
|
|
5
|
+
|
|
6
|
+
from dsp_tools.clients.authentication_client import AuthenticationClient
|
|
7
|
+
from dsp_tools.clients.permissions_client import PermissionsClient
|
|
8
|
+
from dsp_tools.commands.get.get_permissions_legacy import parse_legacy_doaps
|
|
9
|
+
from dsp_tools.commands.get.models.permissions_models import DoapCategories
|
|
10
|
+
from dsp_tools.error.exceptions import UnknownDOAPException
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_default_permissions(
|
|
14
|
+
auth: AuthenticationClient, project_iri: str, prefixes: dict[str, str]
|
|
15
|
+
) -> tuple[str, dict[str, list[str] | Literal["all"]] | None]:
|
|
16
|
+
"""
|
|
17
|
+
Retrieve the DOAPs of a project from the server,
|
|
18
|
+
and try to fit them into our system of "default_permissions" and "default_permissions_overrule".
|
|
19
|
+
If an anomaly is found, return an error message for "default_permissions",
|
|
20
|
+
and None for "default_permissions_overrule".
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
"default_permissions": "public" or "private" or error message
|
|
24
|
+
"default_permissions_overrule": {"private": [<classes_or_props>], "limited_view": ["all" or <img_classes>]}
|
|
25
|
+
"""
|
|
26
|
+
perm_client = PermissionsClient(auth, project_iri)
|
|
27
|
+
project_doaps = perm_client.get_project_doaps()
|
|
28
|
+
fallback_text = (
|
|
29
|
+
"We cannot determine if this project is public or private. "
|
|
30
|
+
"The DSP-TOOLS devs can assist you in analysing the existing DOAPs, "
|
|
31
|
+
"and help you decide if the original intent was rather public or rather private."
|
|
32
|
+
)
|
|
33
|
+
try:
|
|
34
|
+
default_permissions = _parse_default_permissions(project_doaps)
|
|
35
|
+
default_permissions_overrule = _parse_default_permissions_overrule(project_doaps, prefixes)
|
|
36
|
+
except UnknownDOAPException:
|
|
37
|
+
default_permissions = fallback_text
|
|
38
|
+
default_permissions_overrule = None
|
|
39
|
+
return default_permissions, default_permissions_overrule
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _parse_default_permissions(project_doaps: list[dict[str, Any]]) -> str:
|
|
43
|
+
"""
|
|
44
|
+
First tries to parse legacy DOAPs with multiple groups, then falls back to new-style parsing.
|
|
45
|
+
New-style parsing: If the DOAPs exactly match our definition of public/private, return public/private.
|
|
46
|
+
Otherwise, raise an exception.
|
|
47
|
+
"""
|
|
48
|
+
# First, try to parse legacy DOAPs (multiple groups: ProjectAdmin, ProjectMember)
|
|
49
|
+
if legacy_result := parse_legacy_doaps(project_doaps):
|
|
50
|
+
return legacy_result
|
|
51
|
+
|
|
52
|
+
# Fall back to new-style parsing (single ProjectMember group)
|
|
53
|
+
unsupported_groups = ("SystemAdmin", "ProjectAdmin", "Creator", "KnownUser", "UnknownUser")
|
|
54
|
+
if [x for x in project_doaps if x.get("forGroup", "").endswith(unsupported_groups)]:
|
|
55
|
+
raise UnknownDOAPException("The only supported target group for DOAPs is ProjectMember.")
|
|
56
|
+
proj_member_doaps = [x for x in project_doaps if x.get("forGroup", "").endswith("ProjectMember")]
|
|
57
|
+
if len(proj_member_doaps) != 1:
|
|
58
|
+
raise UnknownDOAPException("There must be exactly 1 DOAP for ProjectMember.")
|
|
59
|
+
perms = proj_member_doaps[0]["hasPermissions"]
|
|
60
|
+
if len(perms) not in [2, 4]:
|
|
61
|
+
err_msg = "The only allowed permissions are 'private' (with 2 elements), and 'limited_view' (with 4 elements)"
|
|
62
|
+
raise UnknownDOAPException(err_msg)
|
|
63
|
+
proj_adm_perms = [x for x in perms if x["additionalInformation"].endswith("ProjectAdmin")]
|
|
64
|
+
proj_mem_perms = [x for x in perms if x["additionalInformation"].endswith("ProjectMember")]
|
|
65
|
+
knwn_usr_perms = [x for x in perms if x["additionalInformation"].endswith("KnownUser")]
|
|
66
|
+
unkn_usr_perms = [x for x in perms if x["additionalInformation"].endswith("UnknownUser")]
|
|
67
|
+
if not (len(proj_adm_perms) == len(proj_mem_perms) == 1):
|
|
68
|
+
raise UnknownDOAPException("There must be always 1 permission for ProjectAdmin and 1 for ProjectMember")
|
|
69
|
+
if proj_adm_perms[0]["name"] != "CR" or proj_mem_perms[0]["name"] != "D":
|
|
70
|
+
raise UnknownDOAPException("ProjectAdmin must always have CR and ProjectMember must always have D")
|
|
71
|
+
if len(knwn_usr_perms) == len(unkn_usr_perms) == 0:
|
|
72
|
+
return "private"
|
|
73
|
+
if not (len(knwn_usr_perms) == len(unkn_usr_perms) == 1):
|
|
74
|
+
raise UnknownDOAPException("In case of 'limited_view', there must be 1 for KnownUser and 1 for UnknownUser")
|
|
75
|
+
if knwn_usr_perms[0]["name"] != "V" or unkn_usr_perms[0]["name"] != "V":
|
|
76
|
+
raise UnknownDOAPException("In case of 'public', KnownUser and UnknownUser must always have V")
|
|
77
|
+
return "public"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _parse_default_permissions_overrule(
|
|
81
|
+
project_doaps: list[dict[str, Any]], prefixes: dict[str, str]
|
|
82
|
+
) -> dict[str, list[str] | Literal["all"]] | None:
|
|
83
|
+
"""
|
|
84
|
+
The DOAPs retrieved from the server are examined if they fit into our system of the overrules.
|
|
85
|
+
If yes, an overrule object is returned. Otherwise, an exception is raised.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
project_doaps: DOAPs as retrieved from the server
|
|
89
|
+
prefixes: dict in the form {"my-onto": "http://0.0.0.0:3333/ontology/1234/my-onto/v2"}
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
an overrule object that can be written into the JSON project definition file, in this form:
|
|
93
|
+
"default_permissions_overrule": {"private": [<classes_or_props>], "limited_view": ["all" or <img_classes>]}
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
UnknownDOAPException: if there are DOAPs that do not fit into our system
|
|
97
|
+
"""
|
|
98
|
+
prefixes_knora_base_inverted = _convert_prefixes(prefixes)
|
|
99
|
+
doap_categories = _categorize_doaps(project_doaps)
|
|
100
|
+
_validate_doap_categories(doap_categories)
|
|
101
|
+
return _construct_overrule_object(doap_categories, prefixes_knora_base_inverted)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _convert_prefixes(prefixes: dict[str, str]) -> dict[str, str]:
|
|
105
|
+
"""
|
|
106
|
+
Convert knora-api form of prefixes into knora-base form (used by DOAPs), and invert it.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
prefixes: dict in the form of {"my-onto": "http://0.0.0.0:3333/ontology/1234/my-onto/v2"}
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
dict in the form of {"http://www.knora.org/ontology/1234/my-onto": "my-onto"}
|
|
113
|
+
"""
|
|
114
|
+
prefixes_knora_base_inverted = {}
|
|
115
|
+
for onto_shorthand, knora_api_iri in prefixes.items():
|
|
116
|
+
if match := regex.search(r"/ontology/([0-9A-Fa-f]{4})/([^/]+)/v2", knora_api_iri):
|
|
117
|
+
shortcode, onto_name = match.groups()
|
|
118
|
+
prefixes_knora_base_inverted[f"http://www.knora.org/ontology/{shortcode}/{onto_name}"] = onto_shorthand
|
|
119
|
+
return prefixes_knora_base_inverted
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _categorize_doaps(project_doaps: list[dict[str, Any]]) -> DoapCategories:
|
|
123
|
+
"""
|
|
124
|
+
The overrule object of the JSON project definition file has 2 categories: private and limited_view.
|
|
125
|
+
- "private" is a list of classes/properties that are private.
|
|
126
|
+
The DOAPs for these correspond 1:1 to the classes/properties.
|
|
127
|
+
- "limited_view" is
|
|
128
|
+
- a list of image classes that are limited_view:
|
|
129
|
+
The DOAPs for these are for knora-api:hasStillImageFileValue and the respective class.
|
|
130
|
+
- or the string "all". The DOAPs for these are only for knora-api:hasStillImageFileValue.
|
|
131
|
+
|
|
132
|
+
This function groups the DOAPs into these categories.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
project_doaps: the DOAPs as retrieved from the server
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
UnknownDOAPException: if there are DOAPs that do not fit into our system
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
a DTO with the categories
|
|
142
|
+
"""
|
|
143
|
+
class_doaps = []
|
|
144
|
+
prop_doaps = []
|
|
145
|
+
has_img_all_classes_doaps = []
|
|
146
|
+
has_img_specific_class_doaps = []
|
|
147
|
+
other_doaps = []
|
|
148
|
+
for doap in project_doaps:
|
|
149
|
+
match (doap.get("forResourceClass"), doap.get("forProperty")):
|
|
150
|
+
case (for_class, None) if for_class:
|
|
151
|
+
class_doaps.append(doap)
|
|
152
|
+
case (None, for_prop) if for_prop and "hasStillImageFileValue" not in for_prop:
|
|
153
|
+
prop_doaps.append(doap)
|
|
154
|
+
case (None, for_prop) if for_prop and "hasStillImageFileValue" in for_prop:
|
|
155
|
+
has_img_all_classes_doaps.append(doap)
|
|
156
|
+
case (for_class, for_prop) if for_class and for_prop and "hasStillImageFileValue" in for_prop:
|
|
157
|
+
has_img_specific_class_doaps.append(doap)
|
|
158
|
+
case _:
|
|
159
|
+
other_doaps.append(doap)
|
|
160
|
+
# Only validate other_doaps if there are any
|
|
161
|
+
if other_doaps:
|
|
162
|
+
try:
|
|
163
|
+
_parse_default_permissions(other_doaps)
|
|
164
|
+
except UnknownDOAPException:
|
|
165
|
+
raise UnknownDOAPException("Found DOAPs that do not fit into our system") from None
|
|
166
|
+
return DoapCategories(
|
|
167
|
+
class_doaps=class_doaps,
|
|
168
|
+
prop_doaps=prop_doaps,
|
|
169
|
+
has_img_all_classes_doaps=has_img_all_classes_doaps,
|
|
170
|
+
has_img_specific_class_doaps=has_img_specific_class_doaps,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _validate_doap_categories(doap_categories: DoapCategories) -> None:
|
|
175
|
+
for expected_private_doap in doap_categories.class_doaps + doap_categories.prop_doaps:
|
|
176
|
+
perm = sorted(expected_private_doap["hasPermissions"], key=lambda x: x["name"])
|
|
177
|
+
if len(perm) != 2:
|
|
178
|
+
raise UnknownDOAPException("'private' is defined as CR ProjectAdmin|D ProjectMember")
|
|
179
|
+
CR, D = perm
|
|
180
|
+
if CR["name"] != "CR" or not CR["additionalInformation"].endswith("ProjectAdmin"):
|
|
181
|
+
raise UnknownDOAPException("'private' is defined as CR ProjectAdmin|D ProjectMember")
|
|
182
|
+
if D["name"] != "D" or not D["additionalInformation"].endswith("ProjectMember"):
|
|
183
|
+
raise UnknownDOAPException("'private' is defined as CR ProjectAdmin|D ProjectMember")
|
|
184
|
+
|
|
185
|
+
for expected_limited_view in (
|
|
186
|
+
doap_categories.has_img_all_classes_doaps + doap_categories.has_img_specific_class_doaps
|
|
187
|
+
):
|
|
188
|
+
err_msg = "'limited_view' is defined as CR ProjectAdmin|D ProjectMember|RV KnownUser|RV UnknownUser"
|
|
189
|
+
perm = sorted(expected_limited_view["hasPermissions"], key=lambda x: x["name"])
|
|
190
|
+
if len(perm) != 4:
|
|
191
|
+
raise UnknownDOAPException(err_msg)
|
|
192
|
+
CR, D, RV1, RV2 = perm
|
|
193
|
+
if CR["name"] != "CR" or not CR["additionalInformation"].endswith("ProjectAdmin"):
|
|
194
|
+
raise UnknownDOAPException(err_msg)
|
|
195
|
+
if D["name"] != "D" or not D["additionalInformation"].endswith("ProjectMember"):
|
|
196
|
+
raise UnknownDOAPException(err_msg)
|
|
197
|
+
if RV1["name"] != "RV" or not RV2["additionalInformation"].endswith("nownUser"):
|
|
198
|
+
raise UnknownDOAPException(err_msg)
|
|
199
|
+
if RV2["name"] != "RV" or not RV2["additionalInformation"].endswith("nownUser"):
|
|
200
|
+
raise UnknownDOAPException(err_msg)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _construct_overrule_object(
|
|
204
|
+
doap_categories: DoapCategories, prefixes_knora_base_inverted: dict[str, str]
|
|
205
|
+
) -> dict[str, list[str] | Literal["all"]]:
|
|
206
|
+
"""
|
|
207
|
+
Construct the final overrules object that can be written into the JSON project definition file.
|
|
208
|
+
To do so, the fully qualified IRIs of the classes/properties must be converted to prefixed IRIs.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
doap_categories: The categorized DOAPs from the server
|
|
212
|
+
prefixes_knora_base_inverted: lookup from fully qualified IRIs to prefixed IRIs
|
|
213
|
+
|
|
214
|
+
Raises:
|
|
215
|
+
UnknownDOAPException: if the DOAPs do not fit into our system
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
the final overrules object that can be written into the JSON project definition file
|
|
219
|
+
"""
|
|
220
|
+
privates: list[str] = []
|
|
221
|
+
for class_doap in doap_categories.class_doaps:
|
|
222
|
+
privates.append(_get_prefixed_iri(class_doap["forResourceClass"], prefixes_knora_base_inverted))
|
|
223
|
+
for prop_doap in doap_categories.prop_doaps:
|
|
224
|
+
privates.append(_get_prefixed_iri(prop_doap["forProperty"], prefixes_knora_base_inverted))
|
|
225
|
+
|
|
226
|
+
limited_views: list[str] | Literal["all"]
|
|
227
|
+
if len(doap_categories.has_img_all_classes_doaps) > 1:
|
|
228
|
+
raise UnknownDOAPException("There can only be 1 all-images DOAP for 'hasStillImageFileValue'")
|
|
229
|
+
if len(doap_categories.has_img_all_classes_doaps) == 1 and len(doap_categories.has_img_specific_class_doaps) > 0:
|
|
230
|
+
raise UnknownDOAPException("If there is a DOAP for all images, there cannot be DOAPs for specific img classes")
|
|
231
|
+
if len(doap_categories.has_img_all_classes_doaps) == 1:
|
|
232
|
+
limited_views = "all"
|
|
233
|
+
else:
|
|
234
|
+
limited_views = []
|
|
235
|
+
for img_doap in doap_categories.has_img_specific_class_doaps:
|
|
236
|
+
limited_views.append(_get_prefixed_iri(img_doap["forResourceClass"], prefixes_knora_base_inverted))
|
|
237
|
+
|
|
238
|
+
result: dict[str, list[str] | Literal["all"]] = {}
|
|
239
|
+
if privates:
|
|
240
|
+
result["private"] = privates
|
|
241
|
+
if limited_views:
|
|
242
|
+
result["limited_view"] = limited_views
|
|
243
|
+
return result
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _get_prefixed_iri(full_iri: str, prefixes_inverted: dict[str, str]) -> str:
|
|
247
|
+
# example:
|
|
248
|
+
# - full_iri = "http://www.knora.org/ontology/1234/my-onto/v2#MyClass"
|
|
249
|
+
# - prefixes_inverted = {"http://www.knora.org/ontology/1234/my-onto": "my-onto"}
|
|
250
|
+
# - output = "my-onto:MyClass"
|
|
251
|
+
if "#" not in full_iri:
|
|
252
|
+
raise ValueError(f"{full_iri} is not a valid full IRI")
|
|
253
|
+
before_hashtag, after_hashtag = full_iri.rsplit("#", maxsplit=1)
|
|
254
|
+
if before_hashtag not in prefixes_inverted:
|
|
255
|
+
raise ValueError(f"{full_iri} belongs to an unknown ontology. It cannot be found in the prefixes.")
|
|
256
|
+
prefix = prefixes_inverted[before_hashtag]
|
|
257
|
+
return f"{prefix}:{after_hashtag}"
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def parse_legacy_doaps(project_doaps: list[dict[str, Any]]) -> Literal["public", "private"] | None:
|
|
6
|
+
"""
|
|
7
|
+
Check if DOAPs match one of the legacy patterns.
|
|
8
|
+
|
|
9
|
+
Legacy private:
|
|
10
|
+
- For group ProjectAdmin: ProjectAdmin CR, ProjectMember CR/M
|
|
11
|
+
- For group ProjectMember: ProjectAdmin CR, ProjectMember CR/M
|
|
12
|
+
|
|
13
|
+
Legacy public:
|
|
14
|
+
- For group ProjectAdmin: ProjectAdmin CR, (optionally: Creator CR), ProjectMember D/M, KnownUser V, UnknownUser V
|
|
15
|
+
- For group ProjectMember: ProjectAdmin CR, (optionally: Creator CR), ProjectMember D/M, KnownUser V, UnknownUser V
|
|
16
|
+
"""
|
|
17
|
+
if len(project_doaps) != 2:
|
|
18
|
+
return None
|
|
19
|
+
admin_doaps = [x for x in project_doaps if x.get("forGroup", "").endswith("ProjectAdmin")]
|
|
20
|
+
member_doaps = [x for x in project_doaps if x.get("forGroup", "").endswith("ProjectMember")]
|
|
21
|
+
if len(admin_doaps) != 1 or len(member_doaps) != 1:
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
admin_perms = admin_doaps[0]["hasPermissions"]
|
|
25
|
+
member_perms = member_doaps[0]["hasPermissions"]
|
|
26
|
+
if all([_is_legacy_private(admin_perms), _is_legacy_private(member_perms)]):
|
|
27
|
+
return "private"
|
|
28
|
+
if all([_is_legacy_public(admin_perms), _is_legacy_public(member_perms)]):
|
|
29
|
+
return "public"
|
|
30
|
+
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _is_legacy_private(perms: list[dict[str, Any]]) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Check if permissions match the legacy private pattern: ProjectAdmin CR, ProjectMember M/D
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
perms: List of permission objects
|
|
40
|
+
"""
|
|
41
|
+
if len(perms) != 2:
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
sorted_perms = sorted(perms, key=lambda x: x.get("name", ""))
|
|
45
|
+
|
|
46
|
+
# First should be CR for ProjectAdmin
|
|
47
|
+
if sorted_perms[0]["name"] != "CR" or not sorted_perms[0]["additionalInformation"].endswith("ProjectAdmin"):
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
# Second should be D or M for ProjectMember
|
|
51
|
+
if sorted_perms[1]["name"] not in ["D", "M"] or not sorted_perms[1]["additionalInformation"].endswith(
|
|
52
|
+
"ProjectMember"
|
|
53
|
+
):
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _is_legacy_public(perms: list[dict[str, Any]]) -> bool:
|
|
60
|
+
"""
|
|
61
|
+
Check if permissions match the public pattern:
|
|
62
|
+
ProjectAdmin CR, (optionally: Creator CR), ProjectMember D/M, KnownUser V, UnknownUser V
|
|
63
|
+
"""
|
|
64
|
+
# Should have exactly 4 permissions after filtering out Creator
|
|
65
|
+
filtered_perms = [p for p in perms if not p["additionalInformation"].endswith("Creator")]
|
|
66
|
+
if len(filtered_perms) != 4:
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
sorted_perms = sorted(filtered_perms, key=lambda x: x.get("name", ""))
|
|
70
|
+
|
|
71
|
+
# First should be CR for ProjectAdmin
|
|
72
|
+
if sorted_perms[0]["name"] != "CR" or not sorted_perms[0]["additionalInformation"].endswith("ProjectAdmin"):
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
# Second should be D or M for ProjectMember
|
|
76
|
+
if sorted_perms[1]["name"] not in ["D", "M"] or not sorted_perms[1]["additionalInformation"].endswith(
|
|
77
|
+
"ProjectMember"
|
|
78
|
+
):
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
# Third/Fourth should be V for KnownUser
|
|
82
|
+
if sorted_perms[2]["name"] != "V" or not sorted_perms[2]["additionalInformation"].endswith("nownUser"):
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
# Third/Fourth should be V for UnknownUser
|
|
86
|
+
if sorted_perms[3]["name"] != "V" or not sorted_perms[3]["additionalInformation"].endswith("nownUser"):
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
return True
|
|
File without changes
|