dsp-tools 9.1.0.post11__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.
Files changed (316) hide show
  1. dsp_tools/__init__.py +4 -0
  2. dsp_tools/cli/args.py +36 -0
  3. dsp_tools/cli/call_action.py +51 -231
  4. dsp_tools/cli/call_action_files_only.py +101 -0
  5. dsp_tools/cli/call_action_with_network.py +207 -0
  6. dsp_tools/cli/create_parsers.py +156 -58
  7. dsp_tools/cli/entry_point.py +56 -26
  8. dsp_tools/cli/utils.py +87 -0
  9. dsp_tools/clients/CLAUDE.md +420 -0
  10. dsp_tools/clients/authentication_client.py +14 -0
  11. dsp_tools/clients/authentication_client_live.py +66 -0
  12. dsp_tools/{utils → clients}/connection.py +2 -18
  13. dsp_tools/clients/connection_live.py +233 -0
  14. dsp_tools/clients/fuseki_metrics.py +60 -0
  15. dsp_tools/clients/group_user_clients.py +35 -0
  16. dsp_tools/clients/group_user_clients_live.py +181 -0
  17. dsp_tools/clients/legal_info_client.py +23 -0
  18. dsp_tools/clients/legal_info_client_live.py +132 -0
  19. dsp_tools/clients/list_client.py +49 -0
  20. dsp_tools/clients/list_client_live.py +166 -0
  21. dsp_tools/clients/metadata_client.py +24 -0
  22. dsp_tools/clients/metadata_client_live.py +47 -0
  23. dsp_tools/clients/ontology_clients.py +49 -0
  24. dsp_tools/clients/ontology_create_client_live.py +166 -0
  25. dsp_tools/clients/ontology_get_client_live.py +80 -0
  26. dsp_tools/clients/permissions_client.py +68 -0
  27. dsp_tools/clients/project_client.py +16 -0
  28. dsp_tools/clients/project_client_live.py +66 -0
  29. dsp_tools/commands/create/communicate_problems.py +24 -0
  30. dsp_tools/commands/create/create.py +134 -0
  31. dsp_tools/commands/create/create_on_server/cardinalities.py +111 -0
  32. dsp_tools/commands/create/create_on_server/classes.py +99 -0
  33. dsp_tools/commands/create/create_on_server/complete_ontologies.py +116 -0
  34. dsp_tools/commands/create/create_on_server/default_permissions.py +134 -0
  35. dsp_tools/commands/create/create_on_server/group_users.py +165 -0
  36. dsp_tools/commands/create/create_on_server/lists.py +163 -0
  37. dsp_tools/commands/create/create_on_server/mappers.py +12 -0
  38. dsp_tools/commands/create/create_on_server/onto_utils.py +74 -0
  39. dsp_tools/commands/create/create_on_server/ontology.py +52 -0
  40. dsp_tools/commands/create/create_on_server/project.py +68 -0
  41. dsp_tools/commands/create/create_on_server/properties.py +119 -0
  42. dsp_tools/commands/create/exceptions.py +29 -0
  43. dsp_tools/commands/create/lists_only.py +66 -0
  44. dsp_tools/commands/create/models/create_problems.py +87 -0
  45. dsp_tools/commands/create/models/parsed_ontology.py +88 -0
  46. dsp_tools/commands/create/models/parsed_project.py +81 -0
  47. dsp_tools/commands/create/models/rdf_ontology.py +12 -0
  48. dsp_tools/commands/create/models/server_project_info.py +100 -0
  49. dsp_tools/commands/create/parsing/parse_lists.py +45 -0
  50. dsp_tools/commands/create/parsing/parse_ontology.py +243 -0
  51. dsp_tools/commands/create/parsing/parse_project.py +149 -0
  52. dsp_tools/commands/create/parsing/parsing_utils.py +40 -0
  53. dsp_tools/commands/create/project_validate.py +595 -0
  54. dsp_tools/commands/create/serialisation/ontology.py +119 -0
  55. dsp_tools/commands/create/serialisation/project.py +44 -0
  56. dsp_tools/commands/excel2json/CLAUDE.md +101 -0
  57. dsp_tools/commands/excel2json/json_header.py +57 -23
  58. dsp_tools/commands/excel2json/{new_lists → lists}/compliance_checks.py +26 -26
  59. dsp_tools/commands/excel2json/{new_lists/make_new_lists.py → lists/make_lists.py} +19 -18
  60. dsp_tools/commands/excel2json/{new_lists → lists}/models/input_error.py +1 -12
  61. dsp_tools/commands/excel2json/{new_lists → lists}/models/serialise.py +9 -5
  62. dsp_tools/commands/excel2json/{new_lists → lists}/utils.py +4 -4
  63. dsp_tools/commands/excel2json/models/input_error.py +31 -11
  64. dsp_tools/commands/excel2json/models/json_header.py +53 -15
  65. dsp_tools/commands/excel2json/models/ontology.py +4 -3
  66. dsp_tools/commands/excel2json/{lists.py → old_lists.py} +26 -112
  67. dsp_tools/commands/excel2json/project.py +78 -34
  68. dsp_tools/commands/excel2json/properties.py +57 -36
  69. dsp_tools/commands/excel2json/resources.py +32 -12
  70. dsp_tools/commands/excel2json/utils.py +20 -1
  71. dsp_tools/commands/excel2xml/__init__.py +2 -2
  72. dsp_tools/commands/excel2xml/excel2xml_cli.py +7 -15
  73. dsp_tools/commands/excel2xml/excel2xml_lib.py +138 -493
  74. dsp_tools/commands/excel2xml/propertyelement.py +5 -5
  75. dsp_tools/commands/{project → get}/get.py +29 -13
  76. dsp_tools/commands/get/get_permissions.py +257 -0
  77. dsp_tools/commands/get/get_permissions_legacy.py +89 -0
  78. dsp_tools/commands/{project/models → get/legacy_models}/context.py +6 -6
  79. dsp_tools/commands/{project/models → get/legacy_models}/group.py +5 -10
  80. dsp_tools/commands/{project/models → get/legacy_models}/listnode.py +5 -35
  81. dsp_tools/commands/{project/models → get/legacy_models}/model.py +1 -1
  82. dsp_tools/commands/{project/models → get/legacy_models}/ontology.py +9 -14
  83. dsp_tools/commands/{project/models → get/legacy_models}/project.py +13 -6
  84. dsp_tools/commands/{project/models → get/legacy_models}/propertyclass.py +9 -16
  85. dsp_tools/commands/{project/models → get/legacy_models}/resourceclass.py +8 -46
  86. dsp_tools/commands/{project/models → get/legacy_models}/user.py +19 -60
  87. dsp_tools/commands/get/models/permissions_models.py +10 -0
  88. dsp_tools/commands/id2iri.py +20 -10
  89. dsp_tools/commands/ingest_xmlupload/bulk_ingest_client.py +81 -56
  90. dsp_tools/commands/ingest_xmlupload/create_resources/apply_ingest_id.py +4 -10
  91. dsp_tools/commands/ingest_xmlupload/create_resources/upload_xml.py +97 -37
  92. dsp_tools/commands/ingest_xmlupload/create_resources/user_information.py +2 -2
  93. dsp_tools/commands/ingest_xmlupload/ingest_files/ingest_files.py +9 -10
  94. dsp_tools/commands/ingest_xmlupload/upload_files/filechecker.py +3 -3
  95. dsp_tools/commands/ingest_xmlupload/upload_files/input_error.py +2 -10
  96. dsp_tools/commands/ingest_xmlupload/upload_files/upload_failures.py +12 -2
  97. dsp_tools/commands/ingest_xmlupload/upload_files/upload_files.py +8 -9
  98. dsp_tools/commands/resume_xmlupload/resume_xmlupload.py +18 -18
  99. dsp_tools/commands/start_stack.py +126 -77
  100. dsp_tools/commands/update_legal/CLAUDE.md +344 -0
  101. dsp_tools/commands/update_legal/__init__.py +0 -0
  102. dsp_tools/commands/update_legal/core.py +182 -0
  103. dsp_tools/commands/update_legal/csv_operations.py +135 -0
  104. dsp_tools/commands/update_legal/models.py +87 -0
  105. dsp_tools/commands/update_legal/xml_operations.py +247 -0
  106. dsp_tools/commands/validate_data/CLAUDE.md +159 -0
  107. dsp_tools/commands/validate_data/__init__.py +0 -0
  108. dsp_tools/commands/validate_data/constants.py +59 -0
  109. dsp_tools/commands/validate_data/mappers.py +143 -0
  110. dsp_tools/commands/validate_data/models/__init__.py +0 -0
  111. dsp_tools/commands/validate_data/models/api_responses.py +45 -0
  112. dsp_tools/commands/validate_data/models/input_problems.py +119 -0
  113. dsp_tools/commands/validate_data/models/rdf_like_data.py +117 -0
  114. dsp_tools/commands/validate_data/models/validation.py +106 -0
  115. dsp_tools/commands/validate_data/prepare_data/__init__.py +0 -0
  116. dsp_tools/commands/validate_data/prepare_data/get_rdf_like_data.py +296 -0
  117. dsp_tools/commands/validate_data/prepare_data/make_data_graph.py +91 -0
  118. dsp_tools/commands/validate_data/prepare_data/prepare_data.py +184 -0
  119. dsp_tools/commands/validate_data/process_validation_report/__init__.py +0 -0
  120. dsp_tools/commands/validate_data/process_validation_report/get_user_validation_message.py +358 -0
  121. dsp_tools/commands/validate_data/process_validation_report/query_validation_result.py +507 -0
  122. dsp_tools/commands/validate_data/process_validation_report/reformat_validation_results.py +150 -0
  123. dsp_tools/commands/validate_data/shacl_cli_validator.py +70 -0
  124. dsp_tools/commands/validate_data/sparql/__init__.py +0 -0
  125. dsp_tools/commands/{xml_validate/sparql/resource_shacl.py → validate_data/sparql/cardinality_shacl.py} +45 -47
  126. dsp_tools/commands/validate_data/sparql/construct_shacl.py +92 -0
  127. dsp_tools/commands/validate_data/sparql/legal_info_shacl.py +36 -0
  128. dsp_tools/commands/validate_data/sparql/value_shacl.py +357 -0
  129. dsp_tools/commands/validate_data/utils.py +59 -0
  130. dsp_tools/commands/validate_data/validate_data.py +283 -0
  131. dsp_tools/commands/validate_data/validation/__init__.py +0 -0
  132. dsp_tools/commands/validate_data/validation/check_duplicate_files.py +55 -0
  133. dsp_tools/commands/validate_data/validation/check_for_unknown_classes.py +67 -0
  134. dsp_tools/commands/validate_data/validation/get_validation_report.py +94 -0
  135. dsp_tools/commands/validate_data/validation/validate_ontology.py +107 -0
  136. dsp_tools/commands/xmlupload/CLAUDE.md +292 -0
  137. dsp_tools/commands/xmlupload/make_rdf_graph/__init__.py +0 -0
  138. dsp_tools/commands/xmlupload/make_rdf_graph/constants.py +63 -0
  139. dsp_tools/commands/xmlupload/make_rdf_graph/jsonld_utils.py +44 -0
  140. dsp_tools/commands/xmlupload/make_rdf_graph/make_file_value.py +77 -0
  141. dsp_tools/commands/xmlupload/make_rdf_graph/make_resource_and_values.py +114 -0
  142. dsp_tools/commands/xmlupload/make_rdf_graph/make_values.py +262 -0
  143. dsp_tools/commands/xmlupload/models/bitstream_info.py +18 -0
  144. dsp_tools/commands/xmlupload/models/formatted_text_value.py +0 -25
  145. dsp_tools/commands/xmlupload/models/ingest.py +56 -70
  146. dsp_tools/commands/xmlupload/models/input_problems.py +6 -14
  147. dsp_tools/commands/xmlupload/models/lookup_models.py +21 -0
  148. dsp_tools/commands/xmlupload/models/permission.py +0 -39
  149. dsp_tools/commands/xmlupload/models/{deserialise/xmlpermission.py → permissions_parsed.py} +2 -2
  150. dsp_tools/commands/xmlupload/models/processed/__init__.py +0 -0
  151. dsp_tools/commands/xmlupload/models/processed/file_values.py +29 -0
  152. dsp_tools/commands/xmlupload/models/processed/res.py +27 -0
  153. dsp_tools/commands/xmlupload/models/processed/values.py +101 -0
  154. dsp_tools/commands/xmlupload/models/rdf_models.py +26 -0
  155. dsp_tools/commands/xmlupload/models/upload_clients.py +3 -3
  156. dsp_tools/commands/xmlupload/models/upload_state.py +2 -4
  157. dsp_tools/commands/xmlupload/prepare_xml_input/__init__.py +0 -0
  158. dsp_tools/commands/xmlupload/{ark2iri.py → prepare_xml_input/ark2iri.py} +1 -1
  159. dsp_tools/commands/xmlupload/prepare_xml_input/get_processed_resources.py +252 -0
  160. dsp_tools/commands/xmlupload/{iiif_uri_validator.py → prepare_xml_input/iiif_uri_validator.py} +2 -14
  161. dsp_tools/commands/xmlupload/{list_client.py → prepare_xml_input/list_client.py} +15 -10
  162. dsp_tools/commands/xmlupload/prepare_xml_input/prepare_xml_input.py +67 -0
  163. dsp_tools/commands/xmlupload/prepare_xml_input/read_validate_xml_file.py +58 -0
  164. dsp_tools/commands/xmlupload/prepare_xml_input/transform_input_values.py +118 -0
  165. dsp_tools/commands/xmlupload/resource_create_client.py +7 -468
  166. dsp_tools/commands/xmlupload/richtext_id2iri.py +37 -0
  167. dsp_tools/commands/xmlupload/stash/{construct_and_analyze_graph.py → analyse_circular_reference_graph.py} +64 -157
  168. dsp_tools/commands/xmlupload/stash/create_info_for_graph.py +53 -0
  169. dsp_tools/commands/xmlupload/stash/graph_models.py +13 -8
  170. dsp_tools/commands/xmlupload/stash/stash_circular_references.py +48 -115
  171. dsp_tools/commands/xmlupload/stash/stash_models.py +4 -9
  172. dsp_tools/commands/xmlupload/stash/upload_stashed_resptr_props.py +34 -40
  173. dsp_tools/commands/xmlupload/stash/upload_stashed_xml_texts.py +98 -108
  174. dsp_tools/commands/xmlupload/upload_config.py +8 -0
  175. dsp_tools/commands/xmlupload/write_diagnostic_info.py +14 -9
  176. dsp_tools/commands/xmlupload/xmlupload.py +214 -192
  177. dsp_tools/config/__init__.py +0 -0
  178. dsp_tools/config/logger_config.py +69 -0
  179. dsp_tools/{utils → config}/warnings_config.py +4 -1
  180. dsp_tools/error/__init__.py +0 -0
  181. dsp_tools/error/custom_warnings.py +39 -0
  182. dsp_tools/error/exceptions.py +204 -0
  183. dsp_tools/error/problems.py +10 -0
  184. dsp_tools/error/xmllib_errors.py +20 -0
  185. dsp_tools/error/xmllib_warnings.py +54 -0
  186. dsp_tools/error/xmllib_warnings_util.py +159 -0
  187. dsp_tools/error/xsd_validation_error_msg.py +19 -0
  188. dsp_tools/legacy_models/__init__.py +0 -0
  189. dsp_tools/{models → legacy_models}/datetimestamp.py +7 -7
  190. dsp_tools/{models → legacy_models}/langstring.py +1 -1
  191. dsp_tools/{models → legacy_models}/projectContext.py +4 -4
  192. dsp_tools/resources/schema/data.xsd +108 -83
  193. dsp_tools/resources/schema/lists-only.json +4 -23
  194. dsp_tools/resources/schema/project.json +80 -35
  195. dsp_tools/resources/schema/properties-only.json +1 -4
  196. dsp_tools/resources/start-stack/docker-compose.override-host.j2 +11 -0
  197. dsp_tools/resources/start-stack/docker-compose.yml +34 -30
  198. dsp_tools/resources/start-stack/dsp-app-config.json +45 -0
  199. dsp_tools/resources/start-stack/dsp-app-config.override-host.j2 +26 -0
  200. dsp_tools/resources/validate_data/api-shapes-resource-cardinalities.ttl +191 -0
  201. dsp_tools/resources/validate_data/api-shapes.ttl +804 -0
  202. dsp_tools/resources/validate_data/shacl-cli-image.yml +4 -0
  203. dsp_tools/resources/validate_data/validate-ontology.ttl +99 -0
  204. dsp_tools/utils/ansi_colors.py +32 -0
  205. dsp_tools/utils/data_formats/__init__.py +0 -0
  206. dsp_tools/utils/{date_util.py → data_formats/date_util.py} +13 -1
  207. dsp_tools/utils/data_formats/iri_util.py +30 -0
  208. dsp_tools/utils/{shared.py → data_formats/shared.py} +1 -35
  209. dsp_tools/utils/{uri_util.py → data_formats/uri_util.py} +12 -2
  210. dsp_tools/utils/fuseki_bloating.py +63 -0
  211. dsp_tools/utils/json_parsing.py +22 -0
  212. dsp_tools/utils/rdf_constants.py +42 -0
  213. dsp_tools/utils/rdflib_utils.py +10 -0
  214. dsp_tools/utils/replace_id_with_iri.py +66 -0
  215. dsp_tools/utils/request_utils.py +238 -0
  216. dsp_tools/utils/xml_parsing/__init__.py +0 -0
  217. dsp_tools/utils/xml_parsing/get_lookups.py +32 -0
  218. dsp_tools/utils/xml_parsing/get_parsed_resources.py +325 -0
  219. dsp_tools/utils/xml_parsing/models/__init__.py +0 -0
  220. dsp_tools/utils/xml_parsing/models/parsed_resource.py +76 -0
  221. dsp_tools/utils/xml_parsing/parse_clean_validate_xml.py +137 -0
  222. dsp_tools/xmllib/CLAUDE.md +302 -0
  223. dsp_tools/xmllib/__init__.py +49 -0
  224. dsp_tools/xmllib/general_functions.py +877 -0
  225. dsp_tools/xmllib/internal/__init__.py +0 -0
  226. dsp_tools/xmllib/internal/checkers.py +162 -0
  227. dsp_tools/xmllib/internal/circumvent_circular_imports.py +36 -0
  228. dsp_tools/xmllib/internal/constants.py +46 -0
  229. dsp_tools/xmllib/internal/input_converters.py +155 -0
  230. dsp_tools/xmllib/internal/serialise_file_value.py +57 -0
  231. dsp_tools/xmllib/internal/serialise_resource.py +177 -0
  232. dsp_tools/xmllib/internal/serialise_values.py +152 -0
  233. dsp_tools/xmllib/internal/type_aliases.py +11 -0
  234. dsp_tools/xmllib/models/config_options.py +28 -0
  235. dsp_tools/xmllib/models/date_formats.py +48 -0
  236. dsp_tools/xmllib/models/dsp_base_resources.py +1380 -400
  237. dsp_tools/xmllib/models/internal/__init__.py +0 -0
  238. dsp_tools/xmllib/models/internal/file_values.py +172 -0
  239. dsp_tools/xmllib/models/internal/geometry.py +162 -0
  240. dsp_tools/xmllib/models/{migration_metadata.py → internal/migration_metadata.py} +14 -10
  241. dsp_tools/xmllib/models/internal/serialise_permissions.py +66 -0
  242. dsp_tools/xmllib/models/internal/values.py +342 -0
  243. dsp_tools/xmllib/models/licenses/__init__.py +0 -0
  244. dsp_tools/xmllib/models/licenses/other.py +59 -0
  245. dsp_tools/xmllib/models/licenses/recommended.py +107 -0
  246. dsp_tools/xmllib/models/permissions.py +41 -0
  247. dsp_tools/xmllib/models/res.py +1782 -0
  248. dsp_tools/xmllib/models/root.py +313 -26
  249. dsp_tools/xmllib/value_checkers.py +310 -47
  250. dsp_tools/xmllib/value_converters.py +765 -8
  251. dsp_tools-18.3.0.post13.dist-info/METADATA +90 -0
  252. dsp_tools-18.3.0.post13.dist-info/RECORD +286 -0
  253. dsp_tools-18.3.0.post13.dist-info/WHEEL +4 -0
  254. {dsp_tools-9.1.0.post11.dist-info → dsp_tools-18.3.0.post13.dist-info}/entry_points.txt +1 -0
  255. dsp_tools/commands/project/create/project_create.py +0 -1107
  256. dsp_tools/commands/project/create/project_create_lists.py +0 -204
  257. dsp_tools/commands/project/create/project_validate.py +0 -453
  258. dsp_tools/commands/project/models/project_definition.py +0 -12
  259. dsp_tools/commands/rosetta.py +0 -124
  260. dsp_tools/commands/template.py +0 -30
  261. dsp_tools/commands/xml_validate/api_connection.py +0 -122
  262. dsp_tools/commands/xml_validate/deserialise_input.py +0 -135
  263. dsp_tools/commands/xml_validate/make_data_rdf.py +0 -193
  264. dsp_tools/commands/xml_validate/models/data_deserialised.py +0 -108
  265. dsp_tools/commands/xml_validate/models/data_rdf.py +0 -214
  266. dsp_tools/commands/xml_validate/models/input_problems.py +0 -191
  267. dsp_tools/commands/xml_validate/models/validation.py +0 -29
  268. dsp_tools/commands/xml_validate/reformat_validaton_result.py +0 -89
  269. dsp_tools/commands/xml_validate/sparql/construct_shapes.py +0 -16
  270. dsp_tools/commands/xml_validate/xml_validate.py +0 -151
  271. dsp_tools/commands/xmlupload/check_consistency_with_ontology.py +0 -253
  272. dsp_tools/commands/xmlupload/models/deserialise/deserialise_value.py +0 -236
  273. dsp_tools/commands/xmlupload/models/deserialise/xmlresource.py +0 -171
  274. dsp_tools/commands/xmlupload/models/namespace_context.py +0 -39
  275. dsp_tools/commands/xmlupload/models/ontology_lookup_models.py +0 -161
  276. dsp_tools/commands/xmlupload/models/ontology_problem_models.py +0 -178
  277. dsp_tools/commands/xmlupload/models/serialise/jsonld_serialiser.py +0 -40
  278. dsp_tools/commands/xmlupload/models/serialise/serialise_value.py +0 -51
  279. dsp_tools/commands/xmlupload/ontology_client.py +0 -92
  280. dsp_tools/commands/xmlupload/project_client.py +0 -91
  281. dsp_tools/commands/xmlupload/read_validate_xml_file.py +0 -99
  282. dsp_tools/models/custom_warnings.py +0 -31
  283. dsp_tools/models/exceptions.py +0 -90
  284. dsp_tools/resources/0100-template-repo/template.json +0 -45
  285. dsp_tools/resources/0100-template-repo/template.xml +0 -27
  286. dsp_tools/resources/start-stack/docker-compose-validation.yml +0 -5
  287. dsp_tools/resources/start-stack/start-stack-config.yml +0 -4
  288. dsp_tools/resources/xml_validate/api-shapes.ttl +0 -411
  289. dsp_tools/resources/xml_validate/replace_namespace.xslt +0 -61
  290. dsp_tools/utils/connection_live.py +0 -383
  291. dsp_tools/utils/iri_util.py +0 -14
  292. dsp_tools/utils/logger_config.py +0 -41
  293. dsp_tools/utils/set_encoder.py +0 -20
  294. dsp_tools/utils/xml_utils.py +0 -145
  295. dsp_tools/utils/xml_validation.py +0 -197
  296. dsp_tools/utils/xml_validation_models.py +0 -68
  297. dsp_tools/xmllib/models/file_values.py +0 -78
  298. dsp_tools/xmllib/models/resource.py +0 -415
  299. dsp_tools/xmllib/models/values.py +0 -428
  300. dsp_tools-9.1.0.post11.dist-info/METADATA +0 -130
  301. dsp_tools-9.1.0.post11.dist-info/RECORD +0 -167
  302. dsp_tools-9.1.0.post11.dist-info/WHEEL +0 -4
  303. dsp_tools-9.1.0.post11.dist-info/licenses/LICENSE +0 -674
  304. /dsp_tools/{commands/excel2json/new_lists → clients}/__init__.py +0 -0
  305. /dsp_tools/commands/{excel2json/new_lists/models → create}/__init__.py +0 -0
  306. /dsp_tools/commands/{project → create/create_on_server}/__init__.py +0 -0
  307. /dsp_tools/commands/{project/create → create/models}/__init__.py +0 -0
  308. /dsp_tools/commands/{project/models → create/parsing}/__init__.py +0 -0
  309. /dsp_tools/commands/{xml_validate → create/serialisation}/__init__.py +0 -0
  310. /dsp_tools/commands/{xml_validate/models → excel2json/lists}/__init__.py +0 -0
  311. /dsp_tools/commands/{xml_validate/sparql → excel2json/lists/models}/__init__.py +0 -0
  312. /dsp_tools/commands/excel2json/{new_lists → lists}/models/deserialise.py +0 -0
  313. /dsp_tools/commands/{xmlupload/models/deserialise → get}/__init__.py +0 -0
  314. /dsp_tools/commands/{xmlupload/models/serialise → get/legacy_models}/__init__.py +0 -0
  315. /dsp_tools/commands/{project/models → get/legacy_models}/helpers.py +0 -0
  316. /dsp_tools/{models → commands/get/models}/__init__.py +0 -0
@@ -1,204 +0,0 @@
1
- from typing import Any
2
- from typing import Optional
3
- from typing import Union
4
-
5
- from loguru import logger
6
-
7
- from dsp_tools.cli.args import ServerCredentials
8
- from dsp_tools.commands.excel2json.lists import expand_lists_from_excel
9
- from dsp_tools.commands.project.create.project_validate import validate_project
10
- from dsp_tools.commands.project.models.listnode import ListNode
11
- from dsp_tools.commands.project.models.project import Project
12
- from dsp_tools.models.exceptions import BaseError
13
- from dsp_tools.models.exceptions import UserError
14
- from dsp_tools.utils.connection import Connection
15
- from dsp_tools.utils.connection_live import ConnectionLive
16
- from dsp_tools.utils.shared import parse_json_input
17
-
18
-
19
- def _create_list_node(
20
- con: Connection,
21
- project: Project,
22
- node: dict[str, Any],
23
- parent_node: Optional[ListNode] = None,
24
- ) -> tuple[dict[str, Any], bool]:
25
- """
26
- Creates a list node on the DSP server, recursively scanning through all its subnodes, creating them as well.
27
- If a node cannot be created, an error message is printed, but the process continues.
28
-
29
- Args:
30
- con: connection to the DSP server
31
- project: project that holds the list where this node should be added to
32
- node: the node to be created
33
- parent_node: parent node of the node to be created (optional)
34
-
35
- Returns:
36
- Returns a tuple consisting of a dict and a bool.
37
- The dict contains the IRIs of the created list nodes,
38
- nested according to their hierarchy structure,
39
- i.e. ``{nodename: {"id": IRI, "nodes": {...}}}``.
40
- The bool is True if all nodes could be created,
41
- False if any node could not be created.
42
-
43
- Raises:
44
- BaseError: if the created node has no name
45
- """
46
- new_node: ListNode = ListNode(
47
- con=con,
48
- project=project,
49
- label=node["labels"],
50
- comments=node.get("comments"),
51
- name=node["name"],
52
- parent=parent_node,
53
- )
54
- try:
55
- new_node = new_node.create()
56
- except BaseError:
57
- print(f"WARNING: Cannot create list node '{node['name']}'.")
58
- logger.opt(exception=True).warning("Cannot create list node '{node['name']}'.")
59
- return {}, False
60
-
61
- # if node has child nodes, call the method recursively
62
- if node.get("nodes"):
63
- overall_success = True
64
- subnode_list = []
65
- for subnode in node["nodes"]:
66
- created_subnode, success = _create_list_node(con=con, project=project, node=subnode, parent_node=new_node)
67
- subnode_list.append(created_subnode)
68
- if not success:
69
- overall_success = False
70
- if not new_node.name:
71
- raise BaseError(f"Node {new_node} has no name.")
72
- return {new_node.name: {"id": new_node.iri, "nodes": subnode_list}}, overall_success
73
- else:
74
- if not new_node.name:
75
- raise BaseError(f"Node {new_node} has no name.")
76
- return {new_node.name: {"id": new_node.iri}}, True
77
-
78
-
79
- def create_lists_on_server(
80
- lists_to_create: list[dict[str, Any]],
81
- con: Connection,
82
- project_remote: Project,
83
- ) -> tuple[dict[str, Any], bool]:
84
- """
85
- Creates the "lists" section of a JSON project definition on a DSP server.
86
- If a list with the same name is already existing in this project on the DSP server, this list is skipped.
87
- If a node or an entire list cannot be created, an error message is printed, but the process continues.
88
-
89
- Args:
90
- lists_to_create: "lists" section of a JSON project definition
91
- con: connection to the DSP server
92
- project_remote: representation of the project on the DSP server
93
-
94
- Raises:
95
- BaseError: if one of the lists to be created already exists on the DSP server, but it has no name
96
-
97
- Returns:
98
- tuple consisting of the IRIs of the list nodes and the success status (True if everything went well)
99
- """
100
-
101
- overall_success = True
102
-
103
- # retrieve existing lists
104
- try:
105
- existing_lists = ListNode.getAllLists(con=con, project_iri=project_remote.iri)
106
- except BaseError:
107
- err_msg = "Unable to retrieve existing lists on DSP server. Cannot check if your lists are already existing."
108
- print(f"WARNING: {err_msg}")
109
- logger.opt(exception=True).warning(err_msg)
110
- existing_lists = []
111
- overall_success = False
112
-
113
- current_project_lists: dict[str, Any] = {}
114
- for new_lst in lists_to_create:
115
- if existing_lst := [x for x in existing_lists if x.project == project_remote.iri and x.name == new_lst["name"]]:
116
- existing_list_name = existing_lst[0].name
117
- if not existing_list_name:
118
- raise BaseError(f"Node {existing_lst[0]} has no name.")
119
- current_project_lists[existing_list_name] = {
120
- "id": existing_lst[0].iri,
121
- "nodes": new_lst["nodes"],
122
- }
123
- print(f" WARNING: List '{new_lst['name']}' already exists on the DSP server. Skipping...")
124
- overall_success = False
125
- continue
126
-
127
- created_list, success = _create_list_node(con=con, project=project_remote, node=new_lst)
128
- current_project_lists.update(created_list)
129
- if not success:
130
- overall_success = False
131
- print(f" Created list '{new_lst['name']}'.")
132
-
133
- return current_project_lists, overall_success
134
-
135
-
136
- def create_lists(
137
- project_file_as_path_or_parsed: Union[str, dict[str, Any]],
138
- creds: ServerCredentials,
139
- ) -> tuple[dict[str, Any], bool]:
140
- """
141
- This method accepts a JSON project definition,
142
- expands the Excel sheets referenced in its "lists" section,
143
- connects to a DSP server,
144
- and uploads the "lists" section to the server.
145
-
146
- The project must already exist on the DSP server.
147
- If a list with the same name is already existing in this project on the DSP server, this list is skipped.
148
-
149
- Args:
150
- project_file_as_path_or_parsed: path to the JSON project definition, or parsed JSON object
151
- creds: credentials to connect to the DSP server
152
-
153
- Raises:
154
- UserError:
155
- - if the project cannot be read from the server
156
- - if the connection to the DSP server cannot be established
157
-
158
- BaseError:
159
- - if the input is invalid
160
- - if a problem occurred while trying to expand the Excel files
161
- - if the JSON file is invalid according to the schema
162
-
163
- Returns:
164
- Returns a tuple consisting of a dict and a bool.
165
- The dict contains the IRIs of the created list nodes,
166
- nested according to their hierarchy structure,
167
- i.e. ``{nodename: {"id": IRI, "nodes": {...}}}``.
168
- If there are no lists in the project definition,
169
- an empty dictionary is returned.
170
- The bool indicates if everything went smoothly during the process.
171
- If a warning or error occurred (e.g. one of the lists already exists,
172
- or one of the nodes could not be created),
173
- it is False.
174
- """
175
- project_definition = parse_json_input(project_file_as_path_or_parsed=project_file_as_path_or_parsed)
176
- if not project_definition.get("project", {}).get("lists"):
177
- return {}, True
178
- lists_to_create = expand_lists_from_excel(project_definition["project"]["lists"])
179
- project_definition["project"]["lists"] = lists_to_create
180
- validate_project(project_definition, expand_lists=False)
181
- print("JSON project file is syntactically correct and passed validation.")
182
-
183
- # connect to the DSP server
184
- con = ConnectionLive(creds.server)
185
- con.login(creds.user, creds.password)
186
-
187
- # retrieve the project
188
- shortcode = project_definition["project"]["shortcode"]
189
- project_local = Project(con=con, shortcode=shortcode)
190
- try:
191
- project_remote = project_local.read()
192
- except BaseError:
193
- err_msg = f"Unable to create the lists: The project {shortcode} cannot be found on the DSP server."
194
- logger.opt(exception=True).error(err_msg)
195
- raise UserError(err_msg) from None
196
-
197
- # create new lists
198
- current_project_lists, success = create_lists_on_server(
199
- lists_to_create=lists_to_create,
200
- con=con,
201
- project_remote=project_remote,
202
- )
203
-
204
- return current_project_lists, success
@@ -1,453 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import importlib.resources
4
- import json
5
- from pathlib import Path
6
- from typing import Any
7
- from typing import Union
8
-
9
- import jsonpath_ng
10
- import jsonpath_ng.ext
11
- import jsonschema
12
- import networkx as nx
13
- import regex
14
-
15
- from dsp_tools.commands.excel2json.lists import expand_lists_from_excel
16
- from dsp_tools.models.exceptions import BaseError
17
-
18
-
19
- def _check_for_duplicate_names(project_definition: dict[str, Any]) -> bool:
20
- """
21
- Check that the resource names and property names are unique.
22
-
23
- Args:
24
- project_definition: parsed JSON project definition
25
-
26
- Raises:
27
- BaseError: detailed error message if there is a duplicate resource name / property name
28
-
29
- Returns:
30
- True if the resource/property names are unique
31
- """
32
- propnames_duplicates, resnames_duplicates = _find_duplicates(project_definition)
33
-
34
- if not resnames_duplicates and not propnames_duplicates:
35
- return True
36
-
37
- err_msg = "Resource names and property names must be unique inside every ontology.\n"
38
- for ontoname, res_duplicates in resnames_duplicates.items():
39
- for res_duplicate in sorted(res_duplicates):
40
- err_msg += f"Resource '{res_duplicate}' appears multiple times in the ontology '{ontoname}'.\n"
41
- for ontoname, prop_duplicates in propnames_duplicates.items():
42
- for prop_duplicate in sorted(prop_duplicates):
43
- err_msg += f"Property '{prop_duplicate}' appears multiple times in the ontology '{ontoname}'.\n"
44
-
45
- raise BaseError(err_msg)
46
-
47
-
48
- def _find_duplicates(project_definition: dict[str, Any]) -> tuple[dict[str, set[str]], dict[str, set[str]]]:
49
- resnames_duplicates: dict[str, set[str]] = {}
50
- propnames_duplicates: dict[str, set[str]] = {}
51
- for onto in project_definition["project"]["ontologies"]:
52
- resnames = [r["name"] for r in onto["resources"]]
53
- if len(set(resnames)) != len(resnames):
54
- for elem in resnames:
55
- if resnames.count(elem) > 1:
56
- if resnames_duplicates.get(onto["name"]):
57
- resnames_duplicates[onto["name"]].add(elem)
58
- else:
59
- resnames_duplicates[onto["name"]] = {elem}
60
-
61
- propnames = [p["name"] for p in onto["properties"]]
62
- if len(set(propnames)) != len(propnames):
63
- for elem in propnames:
64
- if propnames.count(elem) > 1:
65
- if propnames_duplicates.get(onto["name"]):
66
- propnames_duplicates[onto["name"]].add(elem)
67
- else:
68
- propnames_duplicates[onto["name"]] = {elem}
69
- return propnames_duplicates, resnames_duplicates
70
-
71
-
72
- def _check_for_undefined_super_resource(project_definition: dict[str, Any]) -> bool:
73
- """
74
- Check the superresources that claim to point to a resource defined in the same JSON project.
75
- Check if the resource they point to actually exists.
76
- (DSP base resources and resources from other ontologies are not considered.)
77
-
78
- Args:
79
- project_definition: parsed JSON project definition
80
-
81
- Raises:
82
- BaseError: detailed error message if a superresource is not existent
83
-
84
- Returns:
85
- True if the superresource are valid
86
- """
87
- errors: dict[str, list[str]] = {}
88
- for onto in project_definition["project"]["ontologies"]:
89
- ontoname = onto["name"]
90
- resnames = [r["name"] for r in onto["resources"]]
91
- for res in onto["resources"]:
92
- supers = res["super"] if isinstance(res["super"], list) else [res["super"]]
93
- # form of supers:
94
- # - Resource # DSP base resource
95
- # - other:res # other onto
96
- # - same:res # same onto
97
- # - :res # same onto (short form)
98
-
99
- # filter out DSP base resources
100
- supers = [s for s in supers if ":" in s]
101
- # extend short form
102
- supers = [regex.sub(r"^:", f"{ontoname}:", s) for s in supers]
103
- # filter out other ontos
104
- supers = [s for s in supers if regex.search(f"^{ontoname}:", s)]
105
- # convert to short form
106
- supers = [regex.sub(f"^{ontoname}", "", s) for s in supers]
107
-
108
- if invalid_references := [s for s in supers if regex.sub(":", "", s) not in resnames]:
109
- errors[f"Ontology '{ontoname}', resource '{res['name']}'"] = invalid_references
110
-
111
- if errors:
112
- err_msg = "Your data model contains resources that are derived from an invalid super-resource:\n" + "\n".join(
113
- f" - {loc}: {invalids}" for loc, invalids in errors.items()
114
- )
115
- raise BaseError(err_msg)
116
- return True
117
-
118
-
119
- def _check_for_undefined_super_property(project_definition: dict[str, Any]) -> bool:
120
- """
121
- Check the superproperties that claim to point to a property defined in the same JSON project.
122
- Check if the property they point to actually exists.
123
- (DSP base properties and properties from other ontologies are not considered.)
124
-
125
- Args:
126
- project_definition: parsed JSON project definition
127
-
128
- Raises:
129
- BaseError: detailed error message if a superproperty is not existent
130
-
131
- Returns:
132
- True if the superproperties are valid
133
- """
134
- errors: dict[str, list[str]] = {}
135
- for onto in project_definition["project"]["ontologies"]:
136
- ontoname = onto["name"]
137
- propnames = [p["name"] for p in onto["properties"]]
138
- for prop in onto["properties"]:
139
- supers = prop["super"]
140
- # form of supers:
141
- # - isSegmentOf # DSP base property
142
- # - other:prop # other onto
143
- # - same:prop # same onto
144
- # - :prop # same onto (short form)
145
-
146
- # filter out DSP base properties
147
- supers = [s for s in supers if ":" in s]
148
- # extend short form
149
- supers = [regex.sub(r"^:", f"{ontoname}:", s) for s in supers]
150
- # filter out other ontos
151
- supers = [s for s in supers if regex.search(f"^{ontoname}:", s)]
152
- # convert to short form
153
- supers = [regex.sub(f"^{ontoname}", "", s) for s in supers]
154
-
155
- if invalid_references := [s for s in supers if regex.sub(":", "", s) not in propnames]:
156
- errors[f"Ontology '{ontoname}', property '{prop['name']}'"] = invalid_references
157
-
158
- if errors:
159
- err_msg = "Your data model contains properties that are derived from an invalid super-property:\n" + "\n".join(
160
- f" - {loc}: {invalids}" for loc, invalids in errors.items()
161
- )
162
- raise BaseError(err_msg)
163
- return True
164
-
165
-
166
- def _check_for_undefined_cardinalities(project_definition: dict[str, Any]) -> bool:
167
- """
168
- Check if the propnames that are used in the cardinalities of each resource are defined in the "properties"
169
- section. (DSP base properties and properties from other ontologies are not considered.)
170
-
171
- Args:
172
- project_definition: parsed JSON project definition
173
-
174
- Raises:
175
- BaseError: detailed error message if a cardinality is used that is not defined
176
-
177
- Returns:
178
- True if all cardinalities are defined in the "properties" section
179
- """
180
- errors: dict[str, list[str]] = {}
181
- for onto in project_definition["project"]["ontologies"]:
182
- ontoname = onto["name"]
183
- propnames = [prop["name"] for prop in onto["properties"]]
184
- for res in onto["resources"]:
185
- cardnames = [card["propname"] for card in res.get("cardinalities", [])]
186
- # form of the cardnames:
187
- # - isSegmentOf # DSP base property
188
- # - other:prop # other onto
189
- # - same:prop # same onto
190
- # - :prop # same onto (short form)
191
-
192
- # filter out DSP base properties
193
- cardnames = [card for card in cardnames if ":" in card]
194
- # extend short form
195
- cardnames = [regex.sub(r"^:", f"{ontoname}:", card) for card in cardnames]
196
- # filter out other ontos
197
- cardnames = [card for card in cardnames if regex.search(f"^{ontoname}:", card)]
198
- # convert to short form
199
- cardnames = [regex.sub(f"^{ontoname}:", ":", card) for card in cardnames]
200
-
201
- if invalid_cardnames := [card for card in cardnames if regex.sub(":", "", card) not in propnames]:
202
- errors[f"Ontology '{ontoname}', resource '{res['name']}'"] = invalid_cardnames
203
-
204
- if errors:
205
- err_msg = "Your data model contains cardinalities with invalid propnames:\n" + "\n".join(
206
- f" - {loc}: {invalids}" for loc, invalids in errors.items()
207
- )
208
- raise BaseError(err_msg)
209
- return True
210
-
211
-
212
- def _check_for_deprecated_syntax(project_definition: dict[str, Any]) -> bool: # noqa: ARG001 (unused argument)
213
- return True
214
-
215
-
216
- def validate_project(
217
- input_file_or_json: Union[dict[str, Any], str],
218
- expand_lists: bool = True,
219
- ) -> bool:
220
- """
221
- Validates a JSON project definition file.
222
-
223
- First, the Excel file references in the "lists" section are expanded
224
- (unless this behaviour is disabled).
225
-
226
- Then, the project is validated against the JSON schema.
227
-
228
- Next, some checks are performed that are too complex for JSON schema.
229
-
230
- At last, a check is performed
231
- if this project's ontologies contain properties derived from hasLinkTo
232
- that form a circular reference.
233
- If so, these properties must have the cardinality 0-1 or 0-n,
234
- because during the xmlupload process,
235
- these values are temporarily removed.
236
-
237
- Args:
238
- input_file_or_json: the project to be validated, can either be a file path or a parsed JSON file
239
- expand_lists: if True, the Excel file references in the "lists" section will be expanded
240
-
241
- Raises:
242
- BaseError: detailed error report if the validation doesn't pass
243
-
244
- Returns:
245
- True if the project passed validation.
246
- """
247
-
248
- # parse input
249
- if isinstance(input_file_or_json, dict) and "project" in input_file_or_json:
250
- project_definition = input_file_or_json
251
- elif (
252
- isinstance(input_file_or_json, str)
253
- and Path(input_file_or_json).is_file()
254
- and regex.search(r"\.json$", input_file_or_json)
255
- ):
256
- with open(input_file_or_json, encoding="utf-8") as f:
257
- project_definition = json.load(f)
258
- else:
259
- raise BaseError(f"Input '{input_file_or_json}' is neither a file path nor a JSON object.")
260
-
261
- # expand all lists referenced in the "lists" section of the project definition,
262
- # and add them to the project definition
263
- if expand_lists:
264
- if new_lists := expand_lists_from_excel(project_definition["project"].get("lists", [])):
265
- project_definition["project"]["lists"] = new_lists
266
-
267
- # validate the project definition against the schema
268
- with (
269
- importlib.resources.files("dsp_tools")
270
- .joinpath("resources/schema/project.json")
271
- .open(encoding="utf-8") as schema_file
272
- ):
273
- project_schema = json.load(schema_file)
274
- try:
275
- jsonschema.validate(instance=project_definition, schema=project_schema)
276
- except jsonschema.ValidationError as err:
277
- raise BaseError(
278
- f"The JSON project file cannot be created due to the following validation error: {err.message}.\n"
279
- f"The error occurred at {err.json_path}:\n{err.instance}"
280
- ) from None
281
-
282
- # make some checks that are too complex for JSON schema
283
- _check_for_undefined_super_property(project_definition)
284
- _check_for_undefined_super_resource(project_definition)
285
- _check_for_undefined_cardinalities(project_definition)
286
- _check_for_duplicate_names(project_definition)
287
- _check_for_deprecated_syntax(project_definition)
288
-
289
- # cardinalities check for circular references
290
- return _check_cardinalities_of_circular_references(project_definition)
291
-
292
-
293
- def _check_cardinalities_of_circular_references(project_definition: dict[Any, Any]) -> bool:
294
- """
295
- Check a JSON project file if it contains properties derived from hasLinkTo that form a circular reference. If so,
296
- these properties must have the cardinality 0-1 or 0-n, because during the xmlupload process, these values
297
- are temporarily removed.
298
-
299
- Args:
300
- project_definition: dictionary with a DSP project (as defined in a JSON project file)
301
-
302
- Raises:
303
- BaseError: if there is a circle with at least one element that has a cardinality of "1" or "1-n"
304
-
305
- Returns:
306
- True if no circle was detected, or if all elements of all circles are of cardinality "0-1" or "0-n".
307
- """
308
-
309
- link_properties = _collect_link_properties(project_definition)
310
- errors = _identify_problematic_cardinalities(project_definition, link_properties)
311
-
312
- if len(errors) == 0:
313
- return True
314
-
315
- error_message = (
316
- "ERROR: Your ontology contains properties derived from 'hasLinkTo' that allow circular references "
317
- "between resources. This is not a problem in itself, but if you try to upload data that actually "
318
- "contains circular references, these 'hasLinkTo' properties will be temporarily removed from the "
319
- "affected resources. Therefore, it is necessary that all involved 'hasLinkTo' properties have a "
320
- "cardinality of 0-1 or 0-n. \n"
321
- "Please make sure that the following properties have a cardinality of 0-1 or 0-n:"
322
- )
323
- for error in errors:
324
- error_message = f"{error_message}\n - Resource {error[0]}, property {error[1]}"
325
- raise BaseError(error_message)
326
-
327
-
328
- def _collect_link_properties(project_definition: dict[Any, Any]) -> dict[str, list[str]]:
329
- """
330
- Maps the properties derived from hasLinkTo to the resource classes they point to.
331
-
332
- Args:
333
- project_definition: parsed JSON file
334
-
335
- Returns:
336
- A (possibly empty) dictionary in the form {"rosetta:hasImage2D": ["rosetta:Image2D"], ...}
337
- """
338
- ontos = project_definition["project"]["ontologies"]
339
- hasLinkTo_props = {"hasLinkTo", "isPartOf", "isRegionOf", "isAnnotationOf"}
340
- link_properties: dict[str, list[str]] = {}
341
- for index, onto in enumerate(ontos):
342
- hasLinkTo_matches = []
343
- # look for child-properties down to 5 inheritance levels that are derived from hasLinkTo-properties
344
- for _ in range(5):
345
- for hasLinkTo_prop in hasLinkTo_props:
346
- hasLinkTo_matches.extend(
347
- jsonpath_ng.ext.parse(
348
- f"$.project.ontologies[{index}].properties[?super[*] == {hasLinkTo_prop}]"
349
- ).find(project_definition)
350
- )
351
- # make the children from this iteration to the parents of the next iteration
352
- hasLinkTo_props = {x.value["name"] for x in hasLinkTo_matches}
353
- prop_obj_pair: dict[str, list[str]] = {}
354
- for match in hasLinkTo_matches:
355
- prop = onto["name"] + ":" + match.value["name"]
356
- target = match.value["object"]
357
- if target != "Resource":
358
- # make the target a fully qualified name (with the ontology's name prefixed)
359
- target = regex.sub(r"^:([^:]+)$", f"{onto['name']}:\\1", target)
360
- prop_obj_pair[prop] = [target]
361
- link_properties.update(prop_obj_pair)
362
-
363
- # in case the object of a property is "Resource", the link can point to any resource class
364
- all_res_names: list[str] = []
365
- for onto in ontos:
366
- matches = jsonpath_ng.ext.parse("$.resources[*].name").find(onto)
367
- tmp = [f"{onto['name']}:{match.value}" for match in matches]
368
- all_res_names.extend(tmp)
369
- for prop, targ in link_properties.items():
370
- if "Resource" in targ:
371
- link_properties[prop] = all_res_names
372
-
373
- return link_properties
374
-
375
-
376
- def _identify_problematic_cardinalities(
377
- project_definition: dict[Any, Any],
378
- link_properties: dict[str, list[str]],
379
- ) -> list[tuple[str, str]]:
380
- """
381
- Make an error list with all cardinalities that are part of a circle but have a cardinality of "1" or "1-n".
382
-
383
- Args:
384
- project_definition: parsed JSON file
385
- link_properties: mapping of hasLinkTo-properties to classes they point to,
386
- e.g. {"rosetta:hasImage2D": ["rosetta:Image2D"], ...}
387
-
388
- Returns:
389
- a (possibly empty) list of (resource, problematic_cardinality) tuples
390
- """
391
- cardinalities, dependencies = _extract_cardinalities_from_project(project_definition, link_properties)
392
- graph = _make_cardinality_dependency_graph(dependencies)
393
- errors = _find_circles_with_min_one_cardinality(graph, cardinalities, dependencies)
394
- return sorted(errors, key=lambda x: x[0])
395
-
396
-
397
- def _extract_cardinalities_from_project(
398
- project_definition: dict[Any, Any],
399
- link_properties: dict[str, list[str]],
400
- ) -> tuple[dict[str, dict[str, str]], dict[str, dict[str, list[str]]]]:
401
- # dependencies = {"rosetta:Text": {"rosetta:hasImage2D": ["rosetta:Image2D"], ...}}
402
- dependencies: dict[str, dict[str, list[str]]] = {}
403
- # cardinalities = {"rosetta:Text": {"rosetta:hasImage2D": "0-1", ...}}
404
- cardinalities: dict[str, dict[str, str]] = {}
405
-
406
- for onto in project_definition["project"]["ontologies"]:
407
- for resource in onto["resources"]:
408
- resname: str = onto["name"] + ":" + resource["name"]
409
- for card in resource.get("cardinalities", []):
410
- # make the cardinality a fully qualified name (with the ontology's name prefixed)
411
- cardname = regex.sub(r"^(:?)([^:]+)$", f"{onto['name']}:\\2", card["propname"])
412
- if cardname in link_properties:
413
- # Look out: if `targets` is created with `targets = link_properties[cardname]`, the ex-
414
- # pression `dependencies[resname][cardname] = targets` causes `dependencies[resname][cardname]`
415
- # to point to `link_properties[cardname]`. Due to that, the expression
416
- # `dependencies[resname][cardname].extend(targets)` will modify "link_properties"!
417
- # For this reason, `targets` must be created with `targets = list(link_properties[cardname])`
418
- targets = list(link_properties[cardname])
419
- if resname not in dependencies:
420
- dependencies[resname] = {cardname: targets}
421
- cardinalities[resname] = {cardname: card["cardinality"]}
422
- elif cardname not in dependencies[resname]:
423
- dependencies[resname][cardname] = targets
424
- cardinalities[resname][cardname] = card["cardinality"]
425
- else:
426
- dependencies[resname][cardname].extend(targets)
427
- return cardinalities, dependencies
428
-
429
-
430
- def _make_cardinality_dependency_graph(dependencies: dict[str, dict[str, list[str]]]) -> nx.MultiDiGraph[Any]:
431
- graph: nx.MultiDiGraph[Any] = nx.MultiDiGraph()
432
- for start, cards in dependencies.items():
433
- for edge, targets in cards.items():
434
- for target in targets:
435
- graph.add_edge(start, target, edge)
436
- return graph
437
-
438
-
439
- def _find_circles_with_min_one_cardinality(
440
- graph: nx.MultiDiGraph[Any], cardinalities: dict[str, dict[str, str]], dependencies: dict[str, dict[str, list[str]]]
441
- ) -> set[tuple[str, str]]:
442
- errors: set[tuple[str, str]] = set()
443
- circles = list(nx.algorithms.cycles.simple_cycles(graph))
444
- for circle in circles:
445
- for index, resource in enumerate(circle):
446
- target = circle[(index + 1) % len(circle)]
447
- prop = ""
448
- for _property, targets in dependencies[resource].items():
449
- if target in targets:
450
- prop = _property
451
- if cardinalities[resource].get(prop) not in ["0-1", "0-n"]:
452
- errors.add((resource, prop))
453
- return errors
@@ -1,12 +0,0 @@
1
- from dataclasses import dataclass
2
-
3
-
4
- @dataclass
5
- class ProjectDefinition:
6
- shortcode: str
7
- shortname: str
8
- longname: str
9
- keywords: list[str] | None = None
10
- descriptions: dict[str, str] | None = None
11
- groups: list[dict[str, str]] | None = None
12
- users: list[dict[str, str]] | None = None