cognite-neat 0.123.24__py3-none-any.whl → 1.0.22__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 (342) hide show
  1. cognite/neat/__init__.py +4 -3
  2. cognite/neat/_client/__init__.py +5 -0
  3. cognite/neat/_client/api.py +8 -0
  4. cognite/neat/_client/client.py +21 -0
  5. cognite/neat/_client/config.py +40 -0
  6. cognite/neat/_client/containers_api.py +138 -0
  7. cognite/neat/_client/data_classes.py +44 -0
  8. cognite/neat/_client/data_model_api.py +115 -0
  9. cognite/neat/_client/init/credentials.py +70 -0
  10. cognite/neat/_client/init/env_vars.py +131 -0
  11. cognite/neat/_client/init/main.py +51 -0
  12. cognite/neat/_client/spaces_api.py +115 -0
  13. cognite/neat/_client/statistics_api.py +24 -0
  14. cognite/neat/_client/views_api.py +144 -0
  15. cognite/neat/_config.py +266 -0
  16. cognite/neat/_data_model/_analysis.py +571 -0
  17. cognite/neat/_data_model/_constants.py +74 -0
  18. cognite/neat/_data_model/_identifiers.py +61 -0
  19. cognite/neat/_data_model/_shared.py +41 -0
  20. cognite/neat/_data_model/_snapshot.py +134 -0
  21. cognite/neat/_data_model/deployer/_differ.py +140 -0
  22. cognite/neat/_data_model/deployer/_differ_container.py +360 -0
  23. cognite/neat/_data_model/deployer/_differ_data_model.py +54 -0
  24. cognite/neat/_data_model/deployer/_differ_space.py +9 -0
  25. cognite/neat/_data_model/deployer/_differ_view.py +299 -0
  26. cognite/neat/_data_model/deployer/data_classes.py +644 -0
  27. cognite/neat/_data_model/deployer/deployer.py +431 -0
  28. cognite/neat/_data_model/exporters/__init__.py +15 -0
  29. cognite/neat/_data_model/exporters/_api_exporter.py +37 -0
  30. cognite/neat/_data_model/exporters/_base.py +24 -0
  31. cognite/neat/_data_model/exporters/_table_exporter/exporter.py +128 -0
  32. cognite/neat/_data_model/exporters/_table_exporter/workbook.py +409 -0
  33. cognite/neat/_data_model/exporters/_table_exporter/writer.py +480 -0
  34. cognite/neat/_data_model/importers/__init__.py +5 -0
  35. cognite/neat/_data_model/importers/_api_importer.py +166 -0
  36. cognite/neat/_data_model/importers/_base.py +16 -0
  37. cognite/neat/_data_model/importers/_table_importer/data_classes.py +344 -0
  38. cognite/neat/_data_model/importers/_table_importer/importer.py +192 -0
  39. cognite/neat/_data_model/importers/_table_importer/reader.py +1102 -0
  40. cognite/neat/_data_model/importers/_table_importer/source.py +94 -0
  41. cognite/neat/_data_model/models/conceptual/_base.py +18 -0
  42. cognite/neat/_data_model/models/conceptual/_concept.py +67 -0
  43. cognite/neat/_data_model/models/conceptual/_data_model.py +51 -0
  44. cognite/neat/_data_model/models/conceptual/_properties.py +104 -0
  45. cognite/neat/_data_model/models/conceptual/_property.py +105 -0
  46. cognite/neat/_data_model/models/dms/__init__.py +206 -0
  47. cognite/neat/_data_model/models/dms/_base.py +31 -0
  48. cognite/neat/_data_model/models/dms/_constants.py +48 -0
  49. cognite/neat/_data_model/models/dms/_constraints.py +42 -0
  50. cognite/neat/_data_model/models/dms/_container.py +159 -0
  51. cognite/neat/_data_model/models/dms/_data_model.py +95 -0
  52. cognite/neat/_data_model/models/dms/_data_types.py +195 -0
  53. cognite/neat/_data_model/models/dms/_http.py +28 -0
  54. cognite/neat/_data_model/models/dms/_indexes.py +30 -0
  55. cognite/neat/_data_model/models/dms/_limits.py +96 -0
  56. cognite/neat/_data_model/models/dms/_references.py +141 -0
  57. cognite/neat/_data_model/models/dms/_schema.py +18 -0
  58. cognite/neat/_data_model/models/dms/_space.py +48 -0
  59. cognite/neat/_data_model/models/dms/_types.py +17 -0
  60. cognite/neat/_data_model/models/dms/_view_filter.py +310 -0
  61. cognite/neat/_data_model/models/dms/_view_property.py +235 -0
  62. cognite/neat/_data_model/models/dms/_views.py +216 -0
  63. cognite/neat/_data_model/models/entities/__init__.py +50 -0
  64. cognite/neat/_data_model/models/entities/_base.py +101 -0
  65. cognite/neat/_data_model/models/entities/_constants.py +22 -0
  66. cognite/neat/_data_model/models/entities/_data_types.py +144 -0
  67. cognite/neat/_data_model/models/entities/_identifiers.py +61 -0
  68. cognite/neat/_data_model/models/entities/_parser.py +226 -0
  69. cognite/neat/_data_model/validation/dms/__init__.py +75 -0
  70. cognite/neat/_data_model/validation/dms/_ai_readiness.py +381 -0
  71. cognite/neat/_data_model/validation/dms/_base.py +25 -0
  72. cognite/neat/_data_model/validation/dms/_connections.py +681 -0
  73. cognite/neat/_data_model/validation/dms/_consistency.py +58 -0
  74. cognite/neat/_data_model/validation/dms/_containers.py +199 -0
  75. cognite/neat/_data_model/validation/dms/_limits.py +368 -0
  76. cognite/neat/_data_model/validation/dms/_orchestrator.py +70 -0
  77. cognite/neat/_data_model/validation/dms/_views.py +164 -0
  78. cognite/neat/_exceptions.py +68 -0
  79. cognite/neat/_issues.py +68 -0
  80. cognite/neat/_session/__init__.py +3 -0
  81. cognite/neat/_session/_html/_render.py +30 -0
  82. cognite/neat/_session/_html/static/__init__.py +8 -0
  83. cognite/neat/_session/_html/static/deployment.css +476 -0
  84. cognite/neat/_session/_html/static/deployment.js +181 -0
  85. cognite/neat/_session/_html/static/issues.css +211 -0
  86. cognite/neat/_session/_html/static/issues.js +168 -0
  87. cognite/neat/_session/_html/static/shared.css +186 -0
  88. cognite/neat/_session/_html/templates/__init__.py +4 -0
  89. cognite/neat/_session/_html/templates/deployment.html +80 -0
  90. cognite/neat/_session/_html/templates/issues.html +45 -0
  91. cognite/neat/_session/_issues.py +81 -0
  92. cognite/neat/_session/_physical.py +294 -0
  93. cognite/neat/_session/_result/__init__.py +3 -0
  94. cognite/neat/_session/_result/_deployment/__init__.py +0 -0
  95. cognite/neat/_session/_result/_deployment/_physical/__init__.py +0 -0
  96. cognite/neat/_session/_result/_deployment/_physical/_changes.py +196 -0
  97. cognite/neat/_session/_result/_deployment/_physical/_statistics.py +180 -0
  98. cognite/neat/_session/_result/_deployment/_physical/serializer.py +35 -0
  99. cognite/neat/_session/_result/_result.py +31 -0
  100. cognite/neat/_session/_session.py +81 -0
  101. cognite/neat/_session/_usage_analytics/__init__.py +0 -0
  102. cognite/neat/_session/_usage_analytics/_collector.py +131 -0
  103. cognite/neat/_session/_usage_analytics/_constants.py +23 -0
  104. cognite/neat/_session/_usage_analytics/_storage.py +240 -0
  105. cognite/neat/_session/_wrappers.py +101 -0
  106. cognite/neat/_state_machine/__init__.py +10 -0
  107. cognite/neat/_state_machine/_base.py +37 -0
  108. cognite/neat/_state_machine/_states.py +52 -0
  109. cognite/neat/_store/__init__.py +3 -0
  110. cognite/neat/_store/_provenance.py +88 -0
  111. cognite/neat/_store/_store.py +220 -0
  112. cognite/neat/_utils/__init__.py +0 -0
  113. cognite/neat/_utils/_reader.py +194 -0
  114. cognite/neat/_utils/auxiliary.py +49 -0
  115. cognite/neat/_utils/collection.py +11 -0
  116. cognite/neat/_utils/http_client/__init__.py +39 -0
  117. cognite/neat/_utils/http_client/_client.py +245 -0
  118. cognite/neat/_utils/http_client/_config.py +19 -0
  119. cognite/neat/_utils/http_client/_data_classes.py +294 -0
  120. cognite/neat/_utils/http_client/_tracker.py +31 -0
  121. cognite/neat/_utils/repo.py +19 -0
  122. cognite/neat/_utils/text.py +71 -0
  123. cognite/neat/_utils/useful_types.py +37 -0
  124. cognite/neat/_utils/validation.py +154 -0
  125. cognite/neat/_v0/__init__.py +0 -0
  126. cognite/neat/_v0/core/__init__.py +0 -0
  127. cognite/neat/_v0/core/_client/_api/__init__.py +0 -0
  128. cognite/neat/{core → _v0/core}/_client/_api/data_modeling_loaders.py +8 -7
  129. cognite/neat/{core → _v0/core}/_client/_api/neat_instances.py +5 -5
  130. cognite/neat/{core → _v0/core}/_client/_api/schema.py +5 -5
  131. cognite/neat/{core → _v0/core}/_client/_api/statistics.py +3 -3
  132. cognite/neat/{core → _v0/core}/_client/_api_client.py +1 -1
  133. cognite/neat/_v0/core/_client/data_classes/__init__.py +0 -0
  134. cognite/neat/{core → _v0/core}/_client/data_classes/schema.py +4 -4
  135. cognite/neat/{core → _v0/core}/_client/testing.py +1 -1
  136. cognite/neat/{core → _v0/core}/_constants.py +5 -3
  137. cognite/neat/_v0/core/_data_model/__init__.py +0 -0
  138. cognite/neat/{core → _v0/core}/_data_model/_constants.py +7 -0
  139. cognite/neat/{core → _v0/core}/_data_model/_shared.py +4 -4
  140. cognite/neat/{core → _v0/core}/_data_model/analysis/_base.py +8 -8
  141. cognite/neat/{core → _v0/core}/_data_model/exporters/__init__.py +1 -2
  142. cognite/neat/{core → _v0/core}/_data_model/exporters/_base.py +7 -7
  143. cognite/neat/{core → _v0/core}/_data_model/exporters/_data_model2dms.py +9 -9
  144. cognite/neat/{core → _v0/core}/_data_model/exporters/_data_model2excel.py +12 -12
  145. cognite/neat/{core → _v0/core}/_data_model/exporters/_data_model2instance_template.py +4 -4
  146. cognite/neat/{core/_data_model/exporters/_data_model2ontology.py → _v0/core/_data_model/exporters/_data_model2semantic_model.py} +126 -116
  147. cognite/neat/{core → _v0/core}/_data_model/exporters/_data_model2yaml.py +1 -1
  148. cognite/neat/{core → _v0/core}/_data_model/importers/_base.py +5 -5
  149. cognite/neat/{core → _v0/core}/_data_model/importers/_base_file_reader.py +2 -2
  150. cognite/neat/{core → _v0/core}/_data_model/importers/_dict2data_model.py +5 -5
  151. cognite/neat/{core → _v0/core}/_data_model/importers/_dms2data_model.py +18 -15
  152. cognite/neat/{core → _v0/core}/_data_model/importers/_graph2data_model.py +12 -12
  153. cognite/neat/{core → _v0/core}/_data_model/importers/_rdf/_base.py +12 -12
  154. cognite/neat/{core → _v0/core}/_data_model/importers/_rdf/_inference2rdata_model.py +14 -14
  155. cognite/neat/_v0/core/_data_model/importers/_rdf/_owl2data_model.py +144 -0
  156. cognite/neat/{core → _v0/core}/_data_model/importers/_rdf/_shared.py +17 -13
  157. cognite/neat/{core → _v0/core}/_data_model/importers/_spreadsheet2data_model.py +92 -12
  158. cognite/neat/{core → _v0/core}/_data_model/models/__init__.py +3 -3
  159. cognite/neat/{core → _v0/core}/_data_model/models/_base_verified.py +5 -5
  160. cognite/neat/{core → _v0/core}/_data_model/models/_import_contexts.py +1 -1
  161. cognite/neat/{core → _v0/core}/_data_model/models/_types.py +5 -5
  162. cognite/neat/{core → _v0/core}/_data_model/models/conceptual/_unverified.py +16 -10
  163. cognite/neat/{core → _v0/core}/_data_model/models/conceptual/_validation.py +12 -12
  164. cognite/neat/{core → _v0/core}/_data_model/models/conceptual/_verified.py +9 -9
  165. cognite/neat/{core → _v0/core}/_data_model/models/data_types.py +14 -4
  166. cognite/neat/{core → _v0/core}/_data_model/models/entities/__init__.py +6 -0
  167. cognite/neat/_v0/core/_data_model/models/entities/_loaders.py +155 -0
  168. cognite/neat/{core → _v0/core}/_data_model/models/entities/_multi_value.py +2 -2
  169. cognite/neat/_v0/core/_data_model/models/entities/_restrictions.py +230 -0
  170. cognite/neat/{core → _v0/core}/_data_model/models/entities/_single_value.py +121 -16
  171. cognite/neat/{core → _v0/core}/_data_model/models/entities/_types.py +10 -0
  172. cognite/neat/{core → _v0/core}/_data_model/models/mapping/_classic2core.py +5 -5
  173. cognite/neat/{core → _v0/core}/_data_model/models/physical/__init__.py +1 -1
  174. cognite/neat/{core → _v0/core}/_data_model/models/physical/_exporter.py +26 -19
  175. cognite/neat/{core → _v0/core}/_data_model/models/physical/_unverified.py +133 -37
  176. cognite/neat/{core → _v0/core}/_data_model/models/physical/_validation.py +24 -20
  177. cognite/neat/{core → _v0/core}/_data_model/models/physical/_verified.py +95 -24
  178. cognite/neat/{core → _v0/core}/_data_model/transformers/_base.py +4 -4
  179. cognite/neat/{core → _v0/core}/_data_model/transformers/_converters.py +35 -28
  180. cognite/neat/{core → _v0/core}/_data_model/transformers/_mapping.py +7 -7
  181. cognite/neat/{core → _v0/core}/_data_model/transformers/_union_conceptual.py +5 -5
  182. cognite/neat/{core → _v0/core}/_data_model/transformers/_verification.py +7 -7
  183. cognite/neat/_v0/core/_instances/__init__.py +0 -0
  184. cognite/neat/{core → _v0/core}/_instances/_tracking/base.py +1 -1
  185. cognite/neat/{core → _v0/core}/_instances/_tracking/log.py +1 -1
  186. cognite/neat/{core → _v0/core}/_instances/extractors/__init__.py +3 -2
  187. cognite/neat/{core → _v0/core}/_instances/extractors/_base.py +6 -6
  188. cognite/neat/_v0/core/_instances/extractors/_classic_cdf/__init__.py +0 -0
  189. cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_base.py +7 -7
  190. cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_classic.py +12 -12
  191. cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_relationships.py +3 -3
  192. cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_sequences.py +2 -2
  193. cognite/neat/{core → _v0/core}/_instances/extractors/_dict.py +6 -3
  194. cognite/neat/{core → _v0/core}/_instances/extractors/_dms.py +6 -6
  195. cognite/neat/{core → _v0/core}/_instances/extractors/_dms_graph.py +11 -11
  196. cognite/neat/{core → _v0/core}/_instances/extractors/_mock_graph_generator.py +10 -10
  197. cognite/neat/{core → _v0/core}/_instances/extractors/_raw.py +3 -3
  198. cognite/neat/{core → _v0/core}/_instances/extractors/_rdf_file.py +7 -7
  199. cognite/neat/{core → _v0/core}/_instances/loaders/_base.py +5 -5
  200. cognite/neat/{core → _v0/core}/_instances/loaders/_rdf2dms.py +17 -17
  201. cognite/neat/{core → _v0/core}/_instances/loaders/_rdf_to_instance_space.py +11 -11
  202. cognite/neat/{core → _v0/core}/_instances/queries/_select.py +29 -3
  203. cognite/neat/{core → _v0/core}/_instances/queries/_update.py +1 -1
  204. cognite/neat/{core → _v0/core}/_instances/transformers/_base.py +4 -4
  205. cognite/neat/{core → _v0/core}/_instances/transformers/_classic_cdf.py +6 -6
  206. cognite/neat/{core → _v0/core}/_instances/transformers/_prune_graph.py +4 -4
  207. cognite/neat/{core → _v0/core}/_instances/transformers/_rdfpath.py +1 -1
  208. cognite/neat/{core → _v0/core}/_instances/transformers/_value_type.py +4 -4
  209. cognite/neat/{core → _v0/core}/_issues/_base.py +5 -5
  210. cognite/neat/{core → _v0/core}/_issues/_contextmanagers.py +1 -1
  211. cognite/neat/{core → _v0/core}/_issues/_factory.py +3 -3
  212. cognite/neat/{core → _v0/core}/_issues/errors/__init__.py +1 -1
  213. cognite/neat/{core → _v0/core}/_issues/errors/_external.py +1 -1
  214. cognite/neat/{core → _v0/core}/_issues/errors/_general.py +1 -1
  215. cognite/neat/{core → _v0/core}/_issues/errors/_properties.py +1 -1
  216. cognite/neat/{core → _v0/core}/_issues/errors/_resources.py +2 -2
  217. cognite/neat/{core → _v0/core}/_issues/errors/_wrapper.py +7 -3
  218. cognite/neat/{core → _v0/core}/_issues/warnings/__init__.py +1 -1
  219. cognite/neat/{core → _v0/core}/_issues/warnings/_external.py +1 -1
  220. cognite/neat/{core → _v0/core}/_issues/warnings/_general.py +1 -1
  221. cognite/neat/{core → _v0/core}/_issues/warnings/_models.py +2 -2
  222. cognite/neat/{core → _v0/core}/_issues/warnings/_properties.py +2 -2
  223. cognite/neat/{core → _v0/core}/_issues/warnings/_resources.py +1 -1
  224. cognite/neat/{core → _v0/core}/_issues/warnings/user_modeling.py +1 -1
  225. cognite/neat/{core → _v0/core}/_store/_data_model.py +12 -12
  226. cognite/neat/{core → _v0/core}/_store/_instance.py +43 -10
  227. cognite/neat/{core → _v0/core}/_store/_provenance.py +3 -3
  228. cognite/neat/{core → _v0/core}/_store/exceptions.py +4 -4
  229. cognite/neat/_v0/core/_utils/__init__.py +0 -0
  230. cognite/neat/{core → _v0/core}/_utils/auth.py +22 -12
  231. cognite/neat/{core → _v0/core}/_utils/auxiliary.py +1 -1
  232. cognite/neat/{core → _v0/core}/_utils/collection_.py +2 -2
  233. cognite/neat/{core → _v0/core}/_utils/graph_transformations_report.py +1 -1
  234. cognite/neat/{core → _v0/core}/_utils/rdf_.py +1 -1
  235. cognite/neat/{core → _v0/core}/_utils/reader/_base.py +1 -1
  236. cognite/neat/{core → _v0/core}/_utils/spreadsheet.py +18 -4
  237. cognite/neat/{core → _v0/core}/_utils/text.py +1 -1
  238. cognite/neat/{core → _v0/core}/_utils/upload.py +3 -3
  239. cognite/neat/{session → _v0}/engine/_load.py +1 -1
  240. cognite/neat/_v0/plugins/__init__.py +4 -0
  241. cognite/neat/_v0/plugins/_base.py +9 -0
  242. cognite/neat/_v0/plugins/_data_model.py +48 -0
  243. cognite/neat/{plugins → _v0/plugins}/_issues.py +1 -1
  244. cognite/neat/{plugins → _v0/plugins}/_manager.py +7 -16
  245. cognite/neat/{session → _v0/session}/_base.py +13 -15
  246. cognite/neat/{session → _v0/session}/_collector.py +1 -1
  247. cognite/neat/_v0/session/_diff.py +51 -0
  248. cognite/neat/{session → _v0/session}/_drop.py +3 -3
  249. cognite/neat/{session → _v0/session}/_explore.py +2 -2
  250. cognite/neat/{session → _v0/session}/_fix.py +2 -2
  251. cognite/neat/{session → _v0/session}/_inspect.py +3 -3
  252. cognite/neat/{session → _v0/session}/_mapping.py +3 -3
  253. cognite/neat/{session → _v0/session}/_plugin.py +4 -5
  254. cognite/neat/{session → _v0/session}/_prepare.py +8 -8
  255. cognite/neat/{session → _v0/session}/_read.py +34 -21
  256. cognite/neat/{session → _v0/session}/_set.py +8 -8
  257. cognite/neat/{session → _v0/session}/_show.py +5 -5
  258. cognite/neat/{session → _v0/session}/_state.py +10 -10
  259. cognite/neat/{session → _v0/session}/_subset.py +4 -4
  260. cognite/neat/{session → _v0/session}/_template.py +11 -11
  261. cognite/neat/{session → _v0/session}/_to.py +12 -12
  262. cognite/neat/{session → _v0/session}/_wizard.py +1 -1
  263. cognite/neat/{session → _v0/session}/exceptions.py +5 -5
  264. cognite/neat/_version.py +1 -1
  265. cognite/neat/legacy.py +6 -0
  266. cognite_neat-1.0.22.dist-info/METADATA +123 -0
  267. cognite_neat-1.0.22.dist-info/RECORD +329 -0
  268. cognite_neat-1.0.22.dist-info/WHEEL +4 -0
  269. cognite/neat/core/_data_model/importers/_rdf/_owl2data_model.py +0 -91
  270. cognite/neat/core/_data_model/models/entities/_loaders.py +0 -75
  271. cognite/neat/plugins/__init__.py +0 -3
  272. cognite/neat/plugins/data_model/importers/__init__.py +0 -5
  273. cognite/neat/plugins/data_model/importers/_base.py +0 -28
  274. cognite/neat/session/_session/_data_model/__init__.py +0 -3
  275. cognite/neat/session/_session/_data_model/_read.py +0 -193
  276. cognite/neat/session/_session/_data_model/_routes.py +0 -45
  277. cognite/neat/session/_session/_data_model/_show.py +0 -147
  278. cognite/neat/session/_session/_data_model/_write.py +0 -335
  279. cognite_neat-0.123.24.dist-info/METADATA +0 -144
  280. cognite_neat-0.123.24.dist-info/RECORD +0 -201
  281. cognite_neat-0.123.24.dist-info/WHEEL +0 -4
  282. cognite_neat-0.123.24.dist-info/licenses/LICENSE +0 -201
  283. /cognite/neat/{core → _client/init}/__init__.py +0 -0
  284. /cognite/neat/{core/_client/_api → _data_model}/__init__.py +0 -0
  285. /cognite/neat/{core/_client/data_classes → _data_model/deployer}/__init__.py +0 -0
  286. /cognite/neat/{core/_data_model → _data_model/exporters/_table_exporter}/__init__.py +0 -0
  287. /cognite/neat/{core/_instances → _data_model/importers/_table_importer}/__init__.py +0 -0
  288. /cognite/neat/{core/_instances/extractors/_classic_cdf → _data_model/models}/__init__.py +0 -0
  289. /cognite/neat/{core/_utils → _data_model/models/conceptual}/__init__.py +0 -0
  290. /cognite/neat/{plugins/data_model → _data_model/validation}/__init__.py +0 -0
  291. /cognite/neat/{session/_session → _session/_html}/__init__.py +0 -0
  292. /cognite/neat/{core → _v0/core}/_client/__init__.py +0 -0
  293. /cognite/neat/{core → _v0/core}/_client/data_classes/data_modeling.py +0 -0
  294. /cognite/neat/{core → _v0/core}/_client/data_classes/neat_sequence.py +0 -0
  295. /cognite/neat/{core → _v0/core}/_client/data_classes/statistics.py +0 -0
  296. /cognite/neat/{core → _v0/core}/_config.py +0 -0
  297. /cognite/neat/{core → _v0/core}/_data_model/analysis/__init__.py +0 -0
  298. /cognite/neat/{core → _v0/core}/_data_model/catalog/__init__.py +0 -0
  299. /cognite/neat/{core → _v0/core}/_data_model/catalog/classic_model.xlsx +0 -0
  300. /cognite/neat/{core → _v0/core}/_data_model/catalog/conceptual-imf-data-model.xlsx +0 -0
  301. /cognite/neat/{core → _v0/core}/_data_model/catalog/hello_world_pump.xlsx +0 -0
  302. /cognite/neat/{core → _v0/core}/_data_model/importers/__init__.py +0 -0
  303. /cognite/neat/{core → _v0/core}/_data_model/importers/_rdf/__init__.py +0 -0
  304. /cognite/neat/{core → _v0/core}/_data_model/models/_base_unverified.py +0 -0
  305. /cognite/neat/{core → _v0/core}/_data_model/models/conceptual/__init__.py +0 -0
  306. /cognite/neat/{core → _v0/core}/_data_model/models/entities/_constants.py +0 -0
  307. /cognite/neat/{core → _v0/core}/_data_model/models/entities/_wrapped.py +0 -0
  308. /cognite/neat/{core → _v0/core}/_data_model/models/mapping/__init__.py +0 -0
  309. /cognite/neat/{core → _v0/core}/_data_model/models/mapping/_classic2core.yaml +0 -0
  310. /cognite/neat/{core → _v0/core}/_data_model/transformers/__init__.py +0 -0
  311. /cognite/neat/{core → _v0/core}/_instances/_shared.py +0 -0
  312. /cognite/neat/{core → _v0/core}/_instances/_tracking/__init__.py +0 -0
  313. /cognite/neat/{core → _v0/core}/_instances/examples/Knowledge-Graph-Nordic44-dirty.xml +0 -0
  314. /cognite/neat/{core → _v0/core}/_instances/examples/Knowledge-Graph-Nordic44.xml +0 -0
  315. /cognite/neat/{core → _v0/core}/_instances/examples/__init__.py +0 -0
  316. /cognite/neat/{core → _v0/core}/_instances/examples/skos-capturing-sheet-wind-topics.xlsx +0 -0
  317. /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_assets.py +0 -0
  318. /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_data_sets.py +0 -0
  319. /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_events.py +0 -0
  320. /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_files.py +0 -0
  321. /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_labels.py +0 -0
  322. /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_timeseries.py +0 -0
  323. /cognite/neat/{core → _v0/core}/_instances/loaders/__init__.py +0 -0
  324. /cognite/neat/{core → _v0/core}/_instances/queries/__init__.py +0 -0
  325. /cognite/neat/{core → _v0/core}/_instances/queries/_base.py +0 -0
  326. /cognite/neat/{core → _v0/core}/_instances/queries/_queries.py +0 -0
  327. /cognite/neat/{core → _v0/core}/_instances/transformers/__init__.py +0 -0
  328. /cognite/neat/{core → _v0/core}/_issues/__init__.py +0 -0
  329. /cognite/neat/{core → _v0/core}/_issues/formatters.py +0 -0
  330. /cognite/neat/{core → _v0/core}/_shared.py +0 -0
  331. /cognite/neat/{core → _v0/core}/_store/__init__.py +0 -0
  332. /cognite/neat/{core → _v0/core}/_utils/io_.py +0 -0
  333. /cognite/neat/{core → _v0/core}/_utils/reader/__init__.py +0 -0
  334. /cognite/neat/{core → _v0/core}/_utils/tarjan.py +0 -0
  335. /cognite/neat/{core → _v0/core}/_utils/time_.py +0 -0
  336. /cognite/neat/{core → _v0/core}/_utils/xml_.py +0 -0
  337. /cognite/neat/{session → _v0}/engine/__init__.py +0 -0
  338. /cognite/neat/{session → _v0}/engine/_import.py +0 -0
  339. /cognite/neat/{session → _v0}/engine/_interface.py +0 -0
  340. /cognite/neat/{session → _v0/session}/__init__.py +0 -0
  341. /cognite/neat/{session → _v0/session}/_experimental.py +0 -0
  342. /cognite/neat/{session → _v0/session}/_state/README.md +0 -0
@@ -0,0 +1,245 @@
1
+ import gzip
2
+ import random
3
+ import sys
4
+ import time
5
+ from collections import deque
6
+ from collections.abc import MutableMapping, Sequence, Set
7
+ from typing import Literal
8
+
9
+ import httpx
10
+ from cognite.client import ClientConfig, global_config
11
+
12
+ from cognite.neat._utils.auxiliary import get_current_neat_version
13
+ from cognite.neat._utils.http_client._config import get_user_agent
14
+ from cognite.neat._utils.http_client._data_classes import (
15
+ APIResponse,
16
+ BodyRequest,
17
+ ErrorDetails,
18
+ FailedRequestMessage,
19
+ HTTPMessage,
20
+ ItemsRequest,
21
+ ParametersRequest,
22
+ RequestMessage,
23
+ ResponseMessage,
24
+ )
25
+ from cognite.neat._utils.useful_types import PrimaryTypes
26
+
27
+ if sys.version_info >= (3, 11):
28
+ from typing import Self
29
+ else:
30
+ from typing_extensions import Self
31
+
32
+
33
+ class HTTPClient:
34
+ """An HTTP client.
35
+
36
+ This class handles rate limiting, retries, and error handling for HTTP requests.
37
+
38
+ Args:
39
+ config (ClientConfig): Configuration for the client.
40
+ pool_connections (int): The number of connection pools to cache. Default is 10.
41
+ pool_maxsize (int): The maximum number of connections to save in the pool. Default
42
+ is 20.
43
+ max_retries (int): The maximum number of retries for a request. Default is 10.
44
+ retry_status_codes (frozenset[int]): HTTP status codes that should trigger a retry.
45
+ Default is {408, 429, 502, 503, 504}.
46
+ split_items_status_codes (frozenset[int]): In the case of ItemRequest with multiple
47
+ items, these status codes will trigger splitting the request into smaller batches.
48
+
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ config: ClientConfig,
54
+ max_retries: int = 10,
55
+ pool_connections: int = 10,
56
+ pool_maxsize: int = 20,
57
+ retry_status_codes: Set[int] = frozenset({429, 502, 503, 504}),
58
+ split_items_status_codes: Set[int] = frozenset({400, 408, 409, 422, 502, 503, 504}),
59
+ ):
60
+ self.config = config
61
+ self._max_retries = max_retries
62
+ self._pool_connections = pool_connections
63
+ self._pool_maxsize = pool_maxsize
64
+ self._retry_status_codes = retry_status_codes
65
+ self._split_items_status_codes = split_items_status_codes
66
+
67
+ # Thread-safe session for connection pooling
68
+ self.session = self._create_thread_safe_session()
69
+
70
+ def __enter__(self) -> Self:
71
+ return self
72
+
73
+ def __exit__(
74
+ self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: object | None
75
+ ) -> Literal[False]:
76
+ """Close the session when exiting the context."""
77
+ self.session.close()
78
+ return False # Do not suppress exceptions
79
+
80
+ def request(self, message: RequestMessage) -> Sequence[HTTPMessage]:
81
+ """Send an HTTP request and return the response.
82
+
83
+ Args:
84
+ message (RequestMessage): The request message to send.
85
+
86
+ Returns:
87
+ Sequence[HTTPMessage]: The response message(s). This can also
88
+ include RequestMessage(s) to be retried.
89
+ """
90
+ if isinstance(message, ItemsRequest) and message.tracker and message.tracker.limit_reached():
91
+ error_msg = (
92
+ f"Aborting further splitting of requests after {message.tracker.failed_split_count} failed attempts."
93
+ )
94
+ return message.create_failed_request(error_msg)
95
+ try:
96
+ response = self._make_request(message)
97
+ results = self._handle_response(response, message)
98
+ except Exception as e:
99
+ results = self._handle_error(e, message)
100
+ return results
101
+
102
+ def request_with_retries(self, message: RequestMessage) -> APIResponse:
103
+ """Send an HTTP request and handle retries.
104
+
105
+ This method will keep retrying the request until it either succeeds or
106
+ exhausts the maximum number of retries.
107
+
108
+ Note this method will use the current thread to process all request, thus
109
+ it is blocking.
110
+
111
+ Args:
112
+ message (RequestMessage): The request message to send.
113
+
114
+ Returns:
115
+ Sequence[ResponseMessage | FailedRequestMessage]: The final response
116
+ messages, which can be either successful responses or failed requests.
117
+ """
118
+ if message.total_attempts > 0:
119
+ raise RuntimeError(f"RequestMessage has already been attempted {message.total_attempts} times.")
120
+ pending_requests: deque[RequestMessage] = deque()
121
+ pending_requests.append(message)
122
+ final_responses = APIResponse()
123
+
124
+ while pending_requests:
125
+ current_request = pending_requests.popleft()
126
+ results = self.request(current_request)
127
+
128
+ for result in results:
129
+ if isinstance(result, RequestMessage):
130
+ pending_requests.append(result)
131
+ elif isinstance(result, ResponseMessage | FailedRequestMessage):
132
+ final_responses.append(result)
133
+ else:
134
+ raise TypeError(f"Unexpected result type: {type(result)}")
135
+
136
+ return final_responses
137
+
138
+ def _create_thread_safe_session(self) -> httpx.Client:
139
+ return httpx.Client(
140
+ limits=httpx.Limits(
141
+ max_connections=self._pool_maxsize,
142
+ max_keepalive_connections=self._pool_connections,
143
+ ),
144
+ timeout=self.config.timeout,
145
+ )
146
+
147
+ def _create_headers(self, api_version: str | None = None) -> MutableMapping[str, str]:
148
+ headers: MutableMapping[str, str] = {}
149
+ headers["User-Agent"] = f"httpx/{httpx.__version__} {get_user_agent()}"
150
+ auth_name, auth_value = self.config.credentials.authorization_header()
151
+ headers[auth_name] = auth_value
152
+ headers["content-type"] = "application/json"
153
+ headers["accept"] = "application/json"
154
+ headers["x-cdp-sdk"] = f"CogniteNeat:{get_current_neat_version()}"
155
+ headers["x-cdp-app"] = self.config.client_name
156
+ headers["cdf-version"] = api_version or self.config.api_subversion
157
+ if not global_config.disable_gzip:
158
+ headers["Content-Encoding"] = "gzip"
159
+ return headers
160
+
161
+ def _make_request(self, item: RequestMessage) -> httpx.Response:
162
+ headers = self._create_headers(item.api_version)
163
+ params: dict[str, PrimaryTypes] | None = None
164
+ if isinstance(item, ParametersRequest):
165
+ params = item.parameters
166
+ data: str | bytes | None = None
167
+ if isinstance(item, BodyRequest):
168
+ data = item.data()
169
+ if not global_config.disable_gzip:
170
+ data = gzip.compress(data.encode("utf-8"))
171
+ return self.session.request(
172
+ method=item.method,
173
+ url=item.endpoint_url,
174
+ content=data,
175
+ headers=headers,
176
+ params=params,
177
+ timeout=self.config.timeout,
178
+ follow_redirects=False,
179
+ )
180
+
181
+ def _handle_response(
182
+ self,
183
+ response: httpx.Response,
184
+ request: RequestMessage,
185
+ ) -> Sequence[HTTPMessage]:
186
+ if 200 <= response.status_code < 300:
187
+ return request.create_success_response(response)
188
+
189
+ if (
190
+ isinstance(request, ItemsRequest)
191
+ and len(request.body.items) > 1
192
+ and response.status_code in self._split_items_status_codes
193
+ ):
194
+ # 4XX: Status there is at least one item that is invalid, split the batch to get all valid items processed
195
+ # 5xx: Server error, split to reduce the number of items in each request, and count as a status attempt
196
+ status_attempts = request.status_attempt
197
+ if 500 <= response.status_code < 600:
198
+ status_attempts += 1
199
+ splits = request.split(status_attempts=status_attempts)
200
+ if splits[0].tracker and splits[0].tracker.limit_reached():
201
+ return request.create_failure_response(response)
202
+ return splits
203
+
204
+ error = ErrorDetails.from_response(response)
205
+
206
+ if request.status_attempt < self._max_retries and (
207
+ response.status_code in self._retry_status_codes or error.is_auto_retryable
208
+ ):
209
+ request.status_attempt += 1
210
+ time.sleep(self._backoff_time(request.total_attempts))
211
+ return [request]
212
+ else:
213
+ # Permanent failure
214
+ return request.create_failure_response(response)
215
+
216
+ @staticmethod
217
+ def _backoff_time(attempts: int) -> float:
218
+ backoff_time = 0.5 * (2**attempts)
219
+ return min(backoff_time, global_config.max_retry_backoff) * random.uniform(0, 1.0)
220
+
221
+ def _handle_error(
222
+ self,
223
+ e: Exception,
224
+ request: RequestMessage,
225
+ ) -> Sequence[HTTPMessage]:
226
+ if isinstance(e, httpx.ReadTimeout | httpx.TimeoutException):
227
+ error_type = "read"
228
+ request.read_attempt += 1
229
+ attempts = request.read_attempt
230
+ elif isinstance(e, ConnectionError | httpx.ConnectError | httpx.ConnectTimeout):
231
+ error_type = "connect"
232
+ request.connect_attempt += 1
233
+ attempts = request.connect_attempt
234
+ else:
235
+ error_msg = f"Unexpected exception: {e!s}"
236
+ return request.create_failed_request(error_msg)
237
+
238
+ if attempts <= self._max_retries:
239
+ time.sleep(self._backoff_time(request.total_attempts))
240
+ return [request]
241
+ else:
242
+ # We have already incremented the attempt count, so we subtract 1 here
243
+ error_msg = f"RequestException after {request.total_attempts - 1} attempts ({error_type} error): {e!s}"
244
+
245
+ return request.create_failed_request(error_msg)
@@ -0,0 +1,19 @@
1
+ import functools
2
+ import platform
3
+
4
+ from cognite.neat._utils.auxiliary import get_current_neat_version
5
+
6
+
7
+ @functools.lru_cache(maxsize=1)
8
+ def get_user_agent() -> str:
9
+ neat_version = f"CogniteNeat/{get_current_neat_version()}"
10
+ python_version = (
11
+ f"{platform.python_implementation()}/{platform.python_version()} "
12
+ f"({platform.python_build()};{platform.python_compiler()})"
13
+ )
14
+ os_version_info = [platform.release(), platform.machine(), platform.architecture()[0]]
15
+ os_version_info = [s for s in os_version_info if s] # Ignore empty strings
16
+ os_version_info_str = "-".join(os_version_info)
17
+ operating_system = f"{platform.system()}/{os_version_info_str}"
18
+
19
+ return f"{neat_version} {python_version} {operating_system}"
@@ -0,0 +1,294 @@
1
+ import sys
2
+ from abc import ABC, abstractmethod
3
+ from collections import UserList
4
+ from collections.abc import MutableSequence, Sequence
5
+ from typing import Generic, Literal, TypeAlias, TypeVar
6
+
7
+ import httpx
8
+ from pydantic import BaseModel, ConfigDict, Field, JsonValue, ValidationError, model_serializer
9
+
10
+ from cognite.neat._exceptions import CDFAPIException
11
+ from cognite.neat._utils.http_client._tracker import ItemsRequestTracker
12
+ from cognite.neat._utils.useful_types import PrimaryTypes, ReferenceObject, T_Reference
13
+
14
+ if sys.version_info >= (3, 11):
15
+ from typing import Self
16
+ else:
17
+ from typing_extensions import Self
18
+
19
+ StatusCode: TypeAlias = int
20
+
21
+
22
+ class HTTPMessage(BaseModel):
23
+ """Base class for HTTP messages (requests and responses)"""
24
+
25
+
26
+ class FailedRequestMessage(HTTPMessage):
27
+ message: str
28
+
29
+ def __str__(self) -> str:
30
+ return self.message
31
+
32
+
33
+ class ResponseMessage(HTTPMessage):
34
+ code: StatusCode
35
+ body: str
36
+
37
+
38
+ class SuccessResponse(ResponseMessage): ...
39
+
40
+
41
+ class ErrorDetails(BaseModel):
42
+ """This is the structure of failure responses from CDF APIs"""
43
+
44
+ code: StatusCode
45
+ message: str
46
+ missing: list[JsonValue] | None = None
47
+ duplicated: list[JsonValue] | None = None
48
+ is_auto_retryable: bool | None = Field(None, alias="isAutoRetryable")
49
+
50
+ @classmethod
51
+ def from_response(cls, response: httpx.Response) -> "ErrorDetails":
52
+ try:
53
+ return _ErrorResponse.model_validate_json(response.text).error
54
+ except ValidationError:
55
+ return cls(code=response.status_code, message=response.text)
56
+
57
+
58
+ class _ErrorResponse(BaseModel):
59
+ error: ErrorDetails
60
+
61
+
62
+ class FailedResponse(ResponseMessage):
63
+ error: ErrorDetails
64
+
65
+ def __str__(self) -> str:
66
+ return f"HTTP {self.code} | {self.error.message}"
67
+
68
+
69
+ class RequestMessage(HTTPMessage, ABC):
70
+ """Base class for HTTP request messages"""
71
+
72
+ endpoint_url: str
73
+ method: Literal["GET", "POST", "PATCH", "DELETE"]
74
+ connect_attempt: int = 0
75
+ read_attempt: int = 0
76
+ status_attempt: int = 0
77
+ api_version: str | None = None
78
+
79
+ @property
80
+ def total_attempts(self) -> int:
81
+ return self.connect_attempt + self.read_attempt + self.status_attempt
82
+
83
+ @abstractmethod
84
+ def create_success_response(self, response: httpx.Response) -> Sequence[HTTPMessage]:
85
+ raise NotImplementedError()
86
+
87
+ @abstractmethod
88
+ def create_failure_response(self, response: httpx.Response) -> Sequence[HTTPMessage]:
89
+ raise NotImplementedError()
90
+
91
+ @abstractmethod
92
+ def create_failed_request(self, error_message: str) -> Sequence[HTTPMessage]:
93
+ raise NotImplementedError()
94
+
95
+
96
+ class SimpleRequest(RequestMessage):
97
+ """Base class for requests with a simple success/fail response structure"""
98
+
99
+ def create_success_response(self, response: httpx.Response) -> Sequence[HTTPMessage]:
100
+ return [SuccessResponse(code=response.status_code, body=response.text)]
101
+
102
+ def create_failure_response(self, response: httpx.Response) -> Sequence[HTTPMessage]:
103
+ return [
104
+ FailedResponse(code=response.status_code, body=response.text, error=ErrorDetails.from_response(response))
105
+ ]
106
+
107
+ def create_failed_request(self, error_message: str) -> Sequence[HTTPMessage]:
108
+ return [FailedRequestMessage(message=error_message)]
109
+
110
+
111
+ class ParametersRequest(SimpleRequest):
112
+ """Base class for HTTP request messages with query parameters"""
113
+
114
+ parameters: dict[str, PrimaryTypes] | None = None
115
+
116
+
117
+ class BodyRequest(ParametersRequest, ABC):
118
+ """Base class for HTTP request messages with a body"""
119
+
120
+ @abstractmethod
121
+ def data(self) -> str:
122
+ raise NotImplementedError()
123
+
124
+
125
+ class SimpleBodyRequest(BodyRequest):
126
+ body: str
127
+
128
+ def data(self) -> str:
129
+ return self.body
130
+
131
+
132
+ class ItemMessage(BaseModel, Generic[T_Reference], ABC):
133
+ """Base class for message related to a specific item"""
134
+
135
+ ids: Sequence[T_Reference]
136
+
137
+
138
+ class SuccessResponseItems(ItemMessage[T_Reference], SuccessResponse): ...
139
+
140
+
141
+ class FailedResponseItems(ItemMessage[T_Reference], FailedResponse): ...
142
+
143
+
144
+ class FailedRequestItems(ItemMessage[T_Reference], FailedRequestMessage): ...
145
+
146
+
147
+ T_BaseModel = TypeVar("T_BaseModel", bound=BaseModel)
148
+
149
+
150
+ class ItemBody(BaseModel, Generic[T_Reference, T_BaseModel], ABC):
151
+ items: Sequence[T_BaseModel]
152
+ extra_args: dict[str, JsonValue] | None = None
153
+
154
+ @model_serializer(mode="plain", return_type=dict)
155
+ def serialize(self) -> dict[str, JsonValue]:
156
+ data: dict[str, JsonValue] = {
157
+ "items": [item.model_dump(exclude_unset=False, by_alias=True, exclude_none=False) for item in self.items]
158
+ }
159
+ if isinstance(self.extra_args, dict):
160
+ data.update(self.extra_args)
161
+ return data
162
+
163
+ @abstractmethod
164
+ def as_ids(self) -> list[T_Reference]:
165
+ """Returns the list of item identifiers for the items in the body."""
166
+ raise NotImplementedError()
167
+
168
+ def split(self, mid: int) -> tuple[Self, Self]:
169
+ """Splits the body into two smaller bodies.
170
+
171
+ This is useful for retrying requests that fail due to size limits or timeouts.
172
+
173
+ Args:
174
+ mid: The index at which to split the items.
175
+ Returns:
176
+ A tuple containing two new ItemBody instances, each with half of the original items.
177
+
178
+ """
179
+
180
+ type_ = type(self)
181
+ return type_(items=self.items[:mid], extra_args=self.extra_args), type_(
182
+ items=self.items[mid:], extra_args=self.extra_args
183
+ )
184
+
185
+
186
+ class ItemIDBody(ItemBody[ReferenceObject, ReferenceObject]):
187
+ def as_ids(self) -> list[ReferenceObject]:
188
+ return list(self.items)
189
+
190
+
191
+ class ItemsRequest(BodyRequest, Generic[T_Reference, T_BaseModel]):
192
+ """Requests message for endpoints that accept multiple items in a single request.
193
+
194
+ This class provides functionality to split large requests into smaller ones, handle responses for each item,
195
+ and manage errors effectively.
196
+
197
+ Attributes:
198
+ body (ItemBody): The body of the request containing the items to be processed.
199
+ max_failures_before_abort (int): The maximum number of failed split requests before aborting further splits.
200
+
201
+ """
202
+
203
+ model_config = ConfigDict(arbitrary_types_allowed=True)
204
+ body: ItemBody[T_Reference, T_BaseModel]
205
+ max_failures_before_abort: int = 50
206
+ tracker: ItemsRequestTracker | None = None
207
+
208
+ def data(self) -> str:
209
+ return self.body.model_dump_json(exclude_unset=True, by_alias=True)
210
+
211
+ def split(self, status_attempts: int) -> "list[ItemsRequest]":
212
+ """Splits the request into two smaller requests.
213
+
214
+ This is useful for retrying requests that fail due to size limits or timeouts.
215
+
216
+ Args:
217
+ status_attempts: The number of status attempts to set for the new requests. This is used when the
218
+ request failed with a 5xx status code and we want to track the number of attempts. For 4xx errors,
219
+ there is at least one item causing the error, so we do not increment the status attempts, but
220
+ instead essentially do a binary search to find the problematic item(s).
221
+
222
+ Returns:
223
+ A list containing two new ItemsRequest instances, each with half of the original items.
224
+
225
+ """
226
+ mid = len(self.body.items) // 2
227
+ if mid == 0:
228
+ return [self]
229
+ tracker = self.tracker or ItemsRequestTracker(self.max_failures_before_abort)
230
+ tracker.register_failure()
231
+ messages: list[ItemsRequest] = []
232
+ for body in self.body.split(mid):
233
+ item_request = ItemsRequest(
234
+ endpoint_url=self.endpoint_url,
235
+ method=self.method,
236
+ body=body,
237
+ connect_attempt=self.connect_attempt,
238
+ read_attempt=self.read_attempt,
239
+ status_attempt=status_attempts,
240
+ )
241
+ item_request.tracker = tracker
242
+ messages.append(item_request)
243
+ return messages
244
+
245
+ def create_success_response(self, response: httpx.Response) -> Sequence[HTTPMessage]:
246
+ return [SuccessResponseItems(code=response.status_code, body=response.text, ids=self.body.as_ids())]
247
+
248
+ def create_failure_response(self, response: httpx.Response) -> Sequence[HTTPMessage]:
249
+ """Creates response messages based on the HTTP response and the original request.
250
+
251
+ Args:
252
+ response: The HTTP response received from the server.
253
+ Returns:
254
+ A sequence of HTTPMessage instances representing the outcome for each item in the request.
255
+ """
256
+ return [
257
+ FailedResponseItems(
258
+ code=response.status_code,
259
+ body=response.text,
260
+ error=ErrorDetails.from_response(response),
261
+ ids=self.body.as_ids(),
262
+ )
263
+ ]
264
+
265
+ def create_failed_request(self, error_message: str) -> Sequence[HTTPMessage]:
266
+ """Creates failed request messages for each item in the request.
267
+
268
+ Args:
269
+ error_message: The error message to include in the failed request messages.
270
+
271
+ Returns:
272
+ A sequence of HTTPMessage instances representing the failed request for each item.
273
+ """
274
+ return [FailedRequestItems(message=error_message, ids=self.body.as_ids())]
275
+
276
+
277
+ class APIResponse(UserList, MutableSequence[ResponseMessage | FailedRequestMessage]):
278
+ def __init__(self, collection: Sequence[ResponseMessage | FailedRequestMessage] | None = None):
279
+ super().__init__(collection or [])
280
+
281
+ def raise_for_status(self) -> None:
282
+ error_messages = [message for message in self.data if not isinstance(message, SuccessResponse)]
283
+ if error_messages:
284
+ raise CDFAPIException(error_messages)
285
+
286
+ @property
287
+ def success_response(self) -> SuccessResponse:
288
+ success = [msg for msg in self.data if isinstance(msg, SuccessResponse)]
289
+ if len(success) == 1:
290
+ return success[0]
291
+ elif success:
292
+ raise ValueError("Multiple successful HTTP responses found in the messages.")
293
+ else:
294
+ raise ValueError("No successful HTTP response found in the messages.")
@@ -0,0 +1,31 @@
1
+ import threading
2
+ from dataclasses import dataclass, field
3
+
4
+
5
+ @dataclass
6
+ class ItemsRequestTracker:
7
+ """Tracks the state of requests split from an original request.
8
+
9
+ Attributes:
10
+ max_failures_before_abort (int): Maximum number of allowed failed split requests before aborting
11
+ the entire operation. A value of -1 indicates no early abort.
12
+ lock (threading.Lock): A lock to ensure thread-safe updates to the failure count.
13
+ failed_split_count (int): The current count of failed split requests.
14
+
15
+ """
16
+
17
+ max_failures_before_abort: int = -1 # -1 means no early abort
18
+ lock: threading.Lock = field(default_factory=threading.Lock, init=False)
19
+ failed_split_count: int = field(default=0, init=False)
20
+
21
+ def register_failure(self) -> None:
22
+ """Register a failed split request and return whether to continue splitting."""
23
+ with self.lock:
24
+ self.failed_split_count += 1
25
+
26
+ def limit_reached(self) -> bool:
27
+ """Check if the failure limit has been reached."""
28
+ with self.lock:
29
+ if self.max_failures_before_abort < 0:
30
+ return False
31
+ return self.failed_split_count >= self.max_failures_before_abort
@@ -0,0 +1,19 @@
1
+ import subprocess
2
+ from pathlib import Path
3
+
4
+
5
+ def get_repo_root() -> Path:
6
+ """Get the root path of the git repository.
7
+
8
+ Raises:
9
+ RuntimeError: If git is not installed or the current directory is not in a git repository
10
+
11
+ """
12
+ try:
13
+ result = subprocess.run("git rev-parse --show-toplevel".split(), stdout=subprocess.PIPE)
14
+ except FileNotFoundError as e:
15
+ raise RuntimeError("Git is not installed or not found in PATH") from e
16
+ output = result.stdout.decode().strip()
17
+ if not output:
18
+ raise RuntimeError("Not in a git repository")
19
+ return Path(output)
@@ -0,0 +1,71 @@
1
+ import re
2
+ from collections.abc import Collection
3
+ from typing import Any
4
+
5
+ NEWLINE = "\n"
6
+ TAB = "\t"
7
+
8
+
9
+ def humanize_collection(collection: Collection[Any], /, *, sort: bool = True, bind_word: str = "and") -> str:
10
+ """Convert a collection of items to a human-readable string.
11
+
12
+ Args:
13
+ collection: The collection of items to convert.
14
+ sort: Whether to sort the collection before converting. Default is True.
15
+ bind_word: The word to use to bind the last two items. Default is "and".
16
+
17
+ Returns:
18
+ A human-readable string of the collection.
19
+
20
+ Examples:
21
+ >>> humanize_collection(["b", "c", "a"])
22
+ 'a, b and c'
23
+ >>> humanize_collection(["b", "c", "a"], sort=False)
24
+ 'b, c and a'
25
+ >>> humanize_collection(["a", "b"])
26
+ 'a and b'
27
+ >>> humanize_collection(["a"])
28
+ 'a'
29
+ >>> humanize_collection([])
30
+ ''
31
+
32
+ """
33
+ if not collection:
34
+ return ""
35
+ elif len(collection) == 1:
36
+ return str(next(iter(collection)))
37
+
38
+ strings = (str(item) for item in collection)
39
+ if sort:
40
+ sequence = sorted(strings)
41
+ else:
42
+ sequence = list(strings)
43
+
44
+ return f"{', '.join(sequence[:-1])} {bind_word} {sequence[-1]}"
45
+
46
+
47
+ def title_case(s: str) -> str:
48
+ """Convert a string to title case, handling underscores and hyphens.
49
+
50
+ Args:
51
+ s: The string to convert.
52
+ Returns:
53
+ The title-cased string.
54
+ Examples:
55
+ >>> title_case("hello world")
56
+ 'Hello World'
57
+ >>> title_case("hello_world")
58
+ 'Hello World'
59
+ >>> title_case("hello-world")
60
+ 'Hello World'
61
+ >>> title_case("hello_world-and-universe")
62
+ 'Hello World And Universe'
63
+ >>> title_case("HELLO WORLD")
64
+ 'Hello World'
65
+ """
66
+ return " ".join(word.capitalize() for word in s.replace("_", " ").replace("-", " ").split())
67
+
68
+
69
+ def split_on_capitals(text: str) -> list[str]:
70
+ """Split a string at capital letters."""
71
+ return re.findall(r"[A-Z][a-z]*", text)