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
@@ -0,0 +1,420 @@
1
+ # Client Pattern Documentation
2
+
3
+ This document describes the architectural pattern used for client classes in DSP-TOOLS.
4
+ These clients provide interfaces to interact with the DSP-API endpoints.
5
+
6
+ ## Overview
7
+
8
+ The client architecture follows a **Protocol-based dependency injection pattern** that separates interface definitions
9
+ from concrete implementations. This design enables testability, modularity, and clear contracts between components.
10
+ The clients should be as simple to initiate as possible and be stateless, if possible.
11
+
12
+ ## Core Components
13
+
14
+ ### 1. Protocol Classes (Abstract Interfaces)
15
+
16
+ Protocol classes define the contract that implementations must follow:
17
+
18
+ - **Location**: `src/dsp_tools/clients/`
19
+ - **Naming**: `<Domain>Client` (e.g., `AuthenticationClient`, `LegalInfoClient`)
20
+ - **Implementation**: Use `typing.Protocol` for structural subtyping
21
+ - **Purpose**: Define required attributes and method signatures without implementation
22
+
23
+ Example:
24
+
25
+ ```python
26
+ from typing import Protocol
27
+
28
+
29
+ class AuthenticationClient(Protocol):
30
+ server: str
31
+ email: str
32
+ password: str
33
+
34
+ def get_token(self) -> str:
35
+ pass
36
+ ```
37
+
38
+ ### 2. Live Implementations
39
+
40
+ Live implementations are concrete classes that interact with real DSP API endpoints:
41
+
42
+ - **Location**: `src/dsp_tools/clients/`
43
+ - **Naming**: `<Domain>ClientLive` (e.g., `AuthenticationClientLive`, `LegalInfoClientLive`)
44
+ - **Decorator**: Always use `@dataclass` decorator
45
+ - **Inheritance**: Inherit from corresponding Protocol class
46
+ - **Purpose**: Implement actual HTTP communication with the DSP-API
47
+
48
+ Example:
49
+
50
+ ```python
51
+ from dataclasses import dataclass
52
+
53
+
54
+ @dataclass
55
+ class AuthenticationClientLive(AuthenticationClient):
56
+ server: str
57
+ email: str
58
+ password: str
59
+ _token: str | None = None
60
+
61
+ def get_token(self) -> str:
62
+ # Implementation here
63
+ pass
64
+ ```
65
+
66
+ ## Implementation Pattern
67
+
68
+ ### Required Attributes
69
+
70
+ Live clients typically include:
71
+
72
+ - **Server identification**: `api_url`
73
+ - **Project identification**: `shortcode`, `project_shortcode`, `project_iri`
74
+ - **Authentication**: `auth: AuthenticationClient` (for endpoints requiring auth)
75
+ - **Private state**: Optional private attributes (e.g., `_token`)
76
+
77
+ ### Request Workflow
78
+
79
+ There are two main patterns, in some cases the code cannot continue if a request is not successful, see pattern 1.
80
+ If the code may continue even if the request is not successful, then `None` will be returned, see pattern 2.
81
+
82
+ In most cases `HTTPStatus.FORBIDDEN` will mean that a `BadCredentialsError` will be raised
83
+
84
+ Pattern 1: If the request is not successfully, then an error is raised.
85
+
86
+ ```python
87
+ def _make_request(self, url: str, data: dict[str, Any] | None = None) -> Response:
88
+ # 1. Prepare request parameters
89
+ headers = {
90
+ "Content-Type": "application/json",
91
+ "Authorization": f"Bearer {self.auth.get_token()}",
92
+ }
93
+ params = RequestParameters("POST", url, TIMEOUT, data, headers)
94
+
95
+ # 2. Log the request
96
+ log_request(params)
97
+
98
+ # 3. Execute request with exception handling
99
+ try:
100
+ response = requests.post(
101
+ url=params.url,
102
+ headers=params.headers,
103
+ data=params.data_serialized,
104
+ timeout=params.timeout,
105
+ )
106
+ except RequestException as err:
107
+ log_and_raise_request_exception(err)
108
+
109
+ # 4. Log the response
110
+ log_response(response)
111
+
112
+ # 5. Handle response status
113
+ if response.ok:
114
+ return response
115
+
116
+ if response.status_code == HTTPStatus.FORBIDDEN:
117
+ raise BadCredentialsError(
118
+ "Only a SystemAdmin or ProjectAdmin can do ... "
119
+ "Your permissions are insufficient for this action.."
120
+ )
121
+
122
+ raise FatalNonOkApiResponseCode(url, response.status_code, response.text)
123
+ ```
124
+
125
+ Pattern 2: If the request is not successful, then the user is warned and `None` is returned
126
+
127
+ ```python
128
+ def create_resource(self, resource_data: dict[str, Any]) -> str | None:
129
+ # 1. Prepare request parameters
130
+ url = f"{self.api_url}/v2/resources"
131
+ headers = {
132
+ "Content-Type": "application/json",
133
+ "Authorization": f"Bearer {self.auth.get_token()}",
134
+ }
135
+ params = RequestParameters("POST", url, TIMEOUT, resource_data, headers)
136
+
137
+ # 2. Log the request
138
+ log_request(params)
139
+
140
+ # 3. Execute request with exception handling
141
+ try:
142
+ response = requests.post(
143
+ url=params.url,
144
+ headers=params.headers,
145
+ data=params.data_serialized,
146
+ timeout=params.timeout,
147
+ )
148
+ except RequestException as err:
149
+ log_and_raise_request_exception(err)
150
+
151
+ # 4. Log the response
152
+ log_response(response)
153
+
154
+ # 5. Handle response status
155
+ if response.ok:
156
+ result = response.json()
157
+ resource_iri = cast(str, result["@id"])
158
+ return resource_iri
159
+
160
+ # 6. Handle specific known error cases
161
+ if response.status_code == HTTPStatus.FORBIDDEN:
162
+ raise BadCredentialsError("You don't have permission to create resources in this project.")
163
+
164
+ # 7. Handle unexpected errors non-fatally
165
+ log_and_warn_unexpected_non_ok_response(response.status_code, response.text)
166
+ return None
167
+ ```
168
+
169
+ ### Key Steps
170
+
171
+ 1. **Prepare Request Parameters**: Create `RequestParameters` object with method, URL, timeout, data, and headers
172
+ 2. **Log Request**: Call `log_request(params)` before making the request
173
+ 3. **Execute Request**: Use `requests.<method>()` with explicit parameters wrapped in try/except
174
+ 4. **Log Response**: Call `log_response(response)` after receiving response
175
+ 5. **Handle Status**: Check response status and raise appropriate exceptions
176
+
177
+ ### Error Handling Strategy
178
+
179
+ - **Network errors**: Catch `RequestException` and call `log_and_raise_request_exception(err)`
180
+ - **Authentication/Authorization (401/403)**: Raise `BadCredentialsError` with context-specific message
181
+ - **Other API errors**: Raise `FatalNonOkApiResponseCode` with URL, status code, and response text
182
+ - **Expected failures**: Use `log_and_warn_unexpected_non_ok_response()` when failure is non-fatal
183
+
184
+ ### 401 vs 403: Authentication vs Authorization
185
+
186
+ It's crucial to distinguish between authentication failures (401) and authorization failures (403):
187
+
188
+ #### 401 Unauthorized - Authentication Failure
189
+
190
+ - **Meaning**: "Who are you?" - The request lacks credentials, or the credentials are invalid/expired
191
+ - **Example error message**: "Please ensure that an account for this email exists and that the password is correct."
192
+ - **Where to handle this**: Only in the authentication client. All other clients depend on the authentication client,
193
+ so it's pointless to handle 401 there.
194
+
195
+ #### 403 Forbidden - Authorization Failure
196
+
197
+ - **Meaning**: "I know who you are, but you can't do that" - The user is authenticated but lacks sufficient permissions
198
+ - **When to use**: Insufficient role/permissions, project membership required, admin-only actions
199
+ - **Example error message**: "Only a SystemAdmin or ProjectAdmin can create new copyright holders."
200
+ - **Where to handle this**: In all clients except the authentication client,
201
+ and except the clients that only access public endpoints.
202
+
203
+ #### Code Examples
204
+
205
+ Correct 401 handling (authentication):
206
+
207
+ ```python
208
+ if response.status_code == HTTPStatus.UNAUTHORIZED:
209
+ raise BadCredentialsError("Authentication failed. Please check your credentials.")
210
+ ```
211
+
212
+ Correct 403 handling (authorization):
213
+
214
+ ```python
215
+ if response.status_code == HTTPStatus.FORBIDDEN:
216
+ raise BadCredentialsError(
217
+ "Only a SystemAdmin or ProjectAdmin can create new copyright holders. "
218
+ "Your permissions are insufficient for this action."
219
+ )
220
+ ```
221
+
222
+ ## Common Imports
223
+
224
+ All live clients typically import:
225
+
226
+ ```python
227
+ from dataclasses import dataclass
228
+ from http import HTTPStatus
229
+ from typing import Any, cast
230
+
231
+ import requests
232
+ from loguru import logger
233
+ from requests import RequestException, Response
234
+
235
+ from dsp_tools.clients. < protocol_file >
236
+ import < ProtocolClass >
237
+ from dsp_tools.clients.authentication_client import AuthenticationClient
238
+ from dsp_tools.error.exceptions import BadCredentialsError, FatalNonOkApiResponseCode
239
+ from dsp_tools.utils.request_utils import (
240
+ RequestParameters,
241
+ log_and_raise_request_exception,
242
+ log_request,
243
+ log_response,
244
+ )
245
+ ```
246
+
247
+ ## Constants
248
+
249
+ - **Timeout values**: Define module-level constants for timeouts (e.g., `TIMEOUT = 60`)
250
+ - **Default values**: Use module-level constants for commonly used values
251
+
252
+ ```python
253
+ TIMEOUT = 60
254
+ ```
255
+
256
+ ## Type Safety
257
+
258
+ - **Type hints**: All parameters, return types, and attributes must have type hints
259
+ - **JSON responses**: Use `cast()` when extracting values from JSON responses
260
+ - **Optional returns**: Use `| None` for methods that may return None
261
+
262
+ Example:
263
+
264
+ ```python
265
+ def get_token(self) -> str:
266
+ res_json: dict[str, Any] = response.json()
267
+ tkn = cast(str, res_json["token"])
268
+ return tkn
269
+ ```
270
+
271
+ ## Testing Strategy
272
+
273
+ - **Mock implementations**: Create mock/fake clients implementing the Protocol for testing
274
+ - **Dependency injection**: Pass Protocol types to consumers, not concrete implementations
275
+ - **Unittesting**: Test live clients with mocked `requests` library
276
+ - **End-to-end testing**: Test against DSP-API in a test container
277
+
278
+ ## Client Dependencies
279
+
280
+ Clients can depend on other clients (via their Protocol):
281
+
282
+ ```python
283
+ @dataclass
284
+ class LegalInfoClientLive(LegalInfoClient):
285
+ server: str
286
+ project_shortcode: str
287
+ auth: AuthenticationClient # Protocol type, not concrete
288
+ ```
289
+
290
+ This enables:
291
+
292
+ - Testing with mock authentication clients
293
+ - Flexibility in authentication strategy
294
+ - Clear dependency contracts
295
+
296
+ ## Authentication Pattern
297
+
298
+ Clients requiring authentication:
299
+
300
+ 1. Accept `auth: AuthenticationClient` as an attribute
301
+ 2. Call `self.auth.get_token()` to get bearer token
302
+ 3. Include token in `Authorization` header: `f"Bearer {self.auth.get_token()}"`
303
+
304
+ Example:
305
+
306
+ ```python
307
+ headers = {
308
+ "Content-Type": "application/json",
309
+ "Authorization": f"Bearer {self.auth.get_token()}",
310
+ }
311
+ ```
312
+
313
+ ## Best Practices
314
+
315
+ ### DO
316
+
317
+ - Use `@dataclass` decorator for all live implementations
318
+ - Log all requests and responses using provided utilities
319
+ - Provide descriptive, actionable error messages
320
+ - Create custom Error Classes if it makes sense
321
+ - Use module-level constants for timeouts
322
+ - Handle specific HTTP status codes with appropriate exceptions
323
+ - Type hint all parameters and return values
324
+ - Use private methods (prefix with `_`) for internal helpers
325
+ - Follow the standardized request workflow
326
+
327
+ ### DON'T
328
+
329
+ - Skip request/response logging
330
+ - Use generic error messages
331
+ - Hardcode timeout values in method calls
332
+ - Catch exceptions without re-raising or logging
333
+ - Return raw response objects from public methods
334
+ - Mix business logic with HTTP communication
335
+
336
+ ## Naming Conventions
337
+
338
+ - **Protocol files**: `<domain>_client.py`
339
+ - **Protocol classes**: `<Domain>Client`
340
+ - **Implementation files**: `<domain>_client_live.py`
341
+ - **Implementation classes**: `<Domain>ClientLive`
342
+ - **Private methods**: `_<method_name>`
343
+ - **Helper methods**: `_<verb>_and_log_request` for HTTP helpers
344
+
345
+ ## Example: Creating a New Client
346
+
347
+ First: Define the Protocol in `src/dsp_tools/clients/<domain>_client.py`:
348
+
349
+ ```python
350
+ from typing import Protocol
351
+
352
+
353
+ class UserClient(Protocol):
354
+ server: str
355
+ auth: AuthenticationClient
356
+
357
+ def get_user(self, user_iri: str) -> dict[str, Any]:
358
+ """Get user information by IRI"""
359
+ ```
360
+
361
+ Second: Implement the live client in `src/dsp_tools/clients/<domain>_client_live.py`:
362
+
363
+ ```python
364
+ from dataclasses import dataclass
365
+ from http import HTTPStatus
366
+ from typing import Any
367
+
368
+ import requests
369
+ from requests import RequestException
370
+
371
+ from dsp_tools.clients.authentication_client import AuthenticationClient
372
+ from dsp_tools.clients.user_client import UserClient
373
+ from dsp_tools.error.exceptions import BadCredentialsError, FatalNonOkApiResponseCode
374
+ from dsp_tools.utils.request_utils import (
375
+ RequestParameters,
376
+ log_and_raise_request_exception,
377
+ log_request,
378
+ log_response,
379
+ )
380
+
381
+ TIMEOUT = 60
382
+
383
+
384
+ @dataclass
385
+ class UserClientLive(UserClient):
386
+ server: str
387
+ auth: AuthenticationClient
388
+
389
+ def get_user(self, user_iri: str) -> dict[str, Any]:
390
+ url = f"{self.server}/admin/users/{user_iri}"
391
+ headers = {
392
+ "Authorization": f"Bearer {self.auth.get_token()}",
393
+ }
394
+ params = RequestParameters("GET", url, TIMEOUT, headers=headers)
395
+ log_request(params)
396
+
397
+ try:
398
+ response = requests.get(
399
+ url=params.url,
400
+ headers=params.headers,
401
+ timeout=params.timeout,
402
+ )
403
+ except RequestException as err:
404
+ log_and_raise_request_exception(err)
405
+
406
+ log_response(response)
407
+
408
+ if response.ok:
409
+ return response.json()
410
+
411
+ if response.status_code == HTTPStatus.FORBIDDEN:
412
+ raise BadCredentialsError("You don't have permission to access user information.")
413
+ raise FatalNonOkApiResponseCode(url, response.status_code, response.text)
414
+ ```
415
+
416
+ ## Related Documentation
417
+
418
+ - Request utilities: `src/dsp_tools/utils/request_utils.py`
419
+ - Exception definitions: `src/dsp_tools/error/exceptions.py`
420
+ - Main project documentation: `/CLAUDE.md`
@@ -0,0 +1,14 @@
1
+ from typing import Protocol
2
+
3
+
4
+ class AuthenticationClient(Protocol):
5
+ """
6
+ Protocol for a client that can authenticate with a DSP server and returns a token.
7
+ """
8
+
9
+ server: str
10
+ email: str
11
+ password: str
12
+
13
+ def get_token(self) -> str:
14
+ pass
@@ -0,0 +1,66 @@
1
+ from dataclasses import dataclass
2
+ from http import HTTPStatus
3
+ from importlib.metadata import version
4
+ from typing import Any
5
+ from typing import cast
6
+
7
+ import requests
8
+ from requests import RequestException
9
+
10
+ from dsp_tools.clients.authentication_client import AuthenticationClient
11
+ from dsp_tools.error.exceptions import BadCredentialsError
12
+ from dsp_tools.error.exceptions import FatalNonOkApiResponseCode
13
+ from dsp_tools.utils.request_utils import RequestParameters
14
+ from dsp_tools.utils.request_utils import log_and_raise_request_exception
15
+ from dsp_tools.utils.request_utils import log_request
16
+ from dsp_tools.utils.request_utils import log_response
17
+
18
+
19
+ @dataclass
20
+ class AuthenticationClientLive(AuthenticationClient):
21
+ """
22
+ Client that can authenticate with a DSP server and return a token.
23
+ """
24
+
25
+ server: str
26
+ email: str
27
+ password: str
28
+ _token: str | None = None
29
+
30
+ def get_token(self) -> str:
31
+ """
32
+ Returns a token. If no token is available, it will get one from the server.
33
+ """
34
+ if self._token:
35
+ return self._token
36
+ return self._get_token()
37
+
38
+ def _get_token(self) -> str:
39
+ timeout = 10
40
+ url = f"{self.server}/v2/authentication"
41
+ payload = {"email": self.email, "password": self.password}
42
+ headers = {"User-Agent": f"DSP-TOOLS/{version('dsp-tools')}"}
43
+ request_params = RequestParameters("POST", url, data=payload, timeout=timeout, headers=headers)
44
+ log_request(request_params)
45
+ try:
46
+ response = requests.post(
47
+ request_params.url,
48
+ json=request_params.data,
49
+ headers=request_params.headers,
50
+ timeout=request_params.timeout,
51
+ )
52
+ log_response(response)
53
+ except RequestException as err:
54
+ log_and_raise_request_exception(err)
55
+
56
+ if response.ok:
57
+ res_json: dict[str, Any] = response.json()
58
+ tkn = cast(str, res_json["token"])
59
+ self._token = tkn
60
+ return tkn
61
+ if response.status_code == HTTPStatus.UNAUTHORIZED:
62
+ raise BadCredentialsError(
63
+ f"Login to the API with the email '{self.email}' was not successful. "
64
+ f"Please ensure that an account for this email exists and that the password is correct."
65
+ )
66
+ raise FatalNonOkApiResponseCode(url, response.status_code, response.text)
@@ -1,7 +1,7 @@
1
1
  from typing import Any
2
2
  from typing import Protocol
3
3
 
4
- # ruff: noqa: D102 (missing docstring in public method)
4
+ from dsp_tools.utils.request_utils import PostFiles
5
5
 
6
6
 
7
7
  class Connection(Protocol):
@@ -28,24 +28,8 @@ class Connection(Protocol):
28
28
  self,
29
29
  route: str,
30
30
  data: dict[str, Any] | None = None,
31
- files: dict[str, tuple[str, Any]] | None = None,
31
+ files: PostFiles | None = None,
32
32
  headers: dict[str, str] | None = None,
33
33
  timeout: int | None = None,
34
34
  ) -> dict[str, Any]:
35
35
  pass
36
-
37
- def delete(
38
- self,
39
- route: str,
40
- headers: dict[str, str] | None = None,
41
- ) -> dict[str, Any]:
42
- pass
43
-
44
- def get_token(self) -> str:
45
- pass
46
-
47
- def login(self, email: str, password: str) -> None:
48
- pass
49
-
50
- def logout(self) -> None:
51
- pass