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