cognite-neat 0.123.2__py3-none-any.whl → 0.127.30__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 (333) hide show
  1. cognite/neat/__init__.py +2 -2
  2. cognite/neat/_client/__init__.py +4 -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 +125 -0
  7. cognite/neat/_client/data_classes.py +44 -0
  8. cognite/neat/_client/data_model_api.py +115 -0
  9. cognite/neat/_client/spaces_api.py +115 -0
  10. cognite/neat/_client/statistics_api.py +24 -0
  11. cognite/neat/_client/views_api.py +129 -0
  12. cognite/neat/_config.py +185 -0
  13. cognite/neat/_data_model/_analysis.py +196 -0
  14. cognite/neat/_data_model/_constants.py +67 -0
  15. cognite/neat/_data_model/_identifiers.py +61 -0
  16. cognite/neat/_data_model/_shared.py +41 -0
  17. cognite/neat/_data_model/deployer/_differ.py +140 -0
  18. cognite/neat/_data_model/deployer/_differ_container.py +360 -0
  19. cognite/neat/_data_model/deployer/_differ_data_model.py +54 -0
  20. cognite/neat/_data_model/deployer/_differ_space.py +9 -0
  21. cognite/neat/_data_model/deployer/_differ_view.py +299 -0
  22. cognite/neat/_data_model/deployer/data_classes.py +529 -0
  23. cognite/neat/_data_model/deployer/deployer.py +401 -0
  24. cognite/neat/_data_model/exporters/__init__.py +15 -0
  25. cognite/neat/_data_model/exporters/_api_exporter.py +37 -0
  26. cognite/neat/_data_model/exporters/_base.py +24 -0
  27. cognite/neat/_data_model/exporters/_table_exporter/exporter.py +128 -0
  28. cognite/neat/_data_model/exporters/_table_exporter/workbook.py +409 -0
  29. cognite/neat/_data_model/exporters/_table_exporter/writer.py +421 -0
  30. cognite/neat/_data_model/importers/__init__.py +5 -0
  31. cognite/neat/_data_model/importers/_api_importer.py +166 -0
  32. cognite/neat/_data_model/importers/_base.py +16 -0
  33. cognite/neat/_data_model/importers/_table_importer/data_classes.py +295 -0
  34. cognite/neat/_data_model/importers/_table_importer/importer.py +192 -0
  35. cognite/neat/_data_model/importers/_table_importer/reader.py +1063 -0
  36. cognite/neat/_data_model/importers/_table_importer/source.py +94 -0
  37. cognite/neat/_data_model/models/conceptual/_base.py +18 -0
  38. cognite/neat/_data_model/models/conceptual/_concept.py +67 -0
  39. cognite/neat/_data_model/models/conceptual/_data_model.py +51 -0
  40. cognite/neat/_data_model/models/conceptual/_properties.py +104 -0
  41. cognite/neat/_data_model/models/conceptual/_property.py +105 -0
  42. cognite/neat/_data_model/models/dms/__init__.py +206 -0
  43. cognite/neat/_data_model/models/dms/_base.py +31 -0
  44. cognite/neat/_data_model/models/dms/_constants.py +48 -0
  45. cognite/neat/_data_model/models/dms/_constraints.py +42 -0
  46. cognite/neat/_data_model/models/dms/_container.py +159 -0
  47. cognite/neat/_data_model/models/dms/_data_model.py +95 -0
  48. cognite/neat/_data_model/models/dms/_data_types.py +195 -0
  49. cognite/neat/_data_model/models/dms/_http.py +28 -0
  50. cognite/neat/_data_model/models/dms/_indexes.py +30 -0
  51. cognite/neat/_data_model/models/dms/_limits.py +96 -0
  52. cognite/neat/_data_model/models/dms/_references.py +135 -0
  53. cognite/neat/_data_model/models/dms/_schema.py +18 -0
  54. cognite/neat/_data_model/models/dms/_space.py +48 -0
  55. cognite/neat/_data_model/models/dms/_types.py +17 -0
  56. cognite/neat/_data_model/models/dms/_view_filter.py +282 -0
  57. cognite/neat/_data_model/models/dms/_view_property.py +235 -0
  58. cognite/neat/_data_model/models/dms/_views.py +210 -0
  59. cognite/neat/_data_model/models/entities/__init__.py +50 -0
  60. cognite/neat/_data_model/models/entities/_base.py +101 -0
  61. cognite/neat/_data_model/models/entities/_constants.py +22 -0
  62. cognite/neat/_data_model/models/entities/_data_types.py +144 -0
  63. cognite/neat/_data_model/models/entities/_identifiers.py +61 -0
  64. cognite/neat/_data_model/models/entities/_parser.py +226 -0
  65. cognite/neat/_data_model/validation/dms/__init__.py +75 -0
  66. cognite/neat/_data_model/validation/dms/_ai_readiness.py +364 -0
  67. cognite/neat/_data_model/validation/dms/_base.py +307 -0
  68. cognite/neat/_data_model/validation/dms/_connections.py +638 -0
  69. cognite/neat/_data_model/validation/dms/_consistency.py +57 -0
  70. cognite/neat/_data_model/validation/dms/_containers.py +174 -0
  71. cognite/neat/_data_model/validation/dms/_limits.py +420 -0
  72. cognite/neat/_data_model/validation/dms/_orchestrator.py +222 -0
  73. cognite/neat/_data_model/validation/dms/_views.py +103 -0
  74. cognite/neat/_exceptions.py +56 -0
  75. cognite/neat/_issues.py +68 -0
  76. cognite/neat/_session/__init__.py +3 -0
  77. cognite/neat/_session/_html/_render.py +30 -0
  78. cognite/neat/_session/_html/static/__init__.py +8 -0
  79. cognite/neat/_session/_html/static/deployment.css +303 -0
  80. cognite/neat/_session/_html/static/deployment.js +150 -0
  81. cognite/neat/_session/_html/static/issues.css +211 -0
  82. cognite/neat/_session/_html/static/issues.js +168 -0
  83. cognite/neat/_session/_html/static/shared.css +186 -0
  84. cognite/neat/_session/_html/templates/__init__.py +4 -0
  85. cognite/neat/_session/_html/templates/deployment.html +75 -0
  86. cognite/neat/_session/_html/templates/issues.html +45 -0
  87. cognite/neat/_session/_issues.py +81 -0
  88. cognite/neat/_session/_opt.py +35 -0
  89. cognite/neat/_session/_physical.py +261 -0
  90. cognite/neat/_session/_result.py +236 -0
  91. cognite/neat/_session/_session.py +88 -0
  92. cognite/neat/_session/_usage_analytics/__init__.py +0 -0
  93. cognite/neat/_session/_usage_analytics/_collector.py +131 -0
  94. cognite/neat/_session/_usage_analytics/_constants.py +23 -0
  95. cognite/neat/_session/_usage_analytics/_storage.py +240 -0
  96. cognite/neat/_session/_wrappers.py +82 -0
  97. cognite/neat/_state_machine/__init__.py +10 -0
  98. cognite/neat/_state_machine/_base.py +37 -0
  99. cognite/neat/_state_machine/_states.py +52 -0
  100. cognite/neat/_store/__init__.py +3 -0
  101. cognite/neat/_store/_provenance.py +81 -0
  102. cognite/neat/_store/_store.py +156 -0
  103. cognite/neat/_utils/__init__.py +0 -0
  104. cognite/neat/_utils/_reader.py +194 -0
  105. cognite/neat/_utils/auxiliary.py +39 -0
  106. cognite/neat/_utils/collection.py +11 -0
  107. cognite/neat/_utils/http_client/__init__.py +39 -0
  108. cognite/neat/_utils/http_client/_client.py +245 -0
  109. cognite/neat/_utils/http_client/_config.py +19 -0
  110. cognite/neat/_utils/http_client/_data_classes.py +294 -0
  111. cognite/neat/_utils/http_client/_tracker.py +31 -0
  112. cognite/neat/_utils/text.py +71 -0
  113. cognite/neat/_utils/useful_types.py +37 -0
  114. cognite/neat/_utils/validation.py +154 -0
  115. cognite/neat/_version.py +1 -1
  116. cognite/neat/v0/__init__.py +0 -0
  117. cognite/neat/v0/core/__init__.py +0 -0
  118. cognite/neat/v0/core/_client/_api/__init__.py +0 -0
  119. cognite/neat/{core → v0/core}/_client/_api/data_modeling_loaders.py +86 -7
  120. cognite/neat/{core → v0/core}/_client/_api/neat_instances.py +5 -5
  121. cognite/neat/{core → v0/core}/_client/_api/schema.py +5 -5
  122. cognite/neat/{core → v0/core}/_client/_api/statistics.py +3 -3
  123. cognite/neat/{core → v0/core}/_client/_api_client.py +1 -1
  124. cognite/neat/v0/core/_client/data_classes/__init__.py +0 -0
  125. cognite/neat/{core → v0/core}/_client/data_classes/schema.py +4 -4
  126. cognite/neat/{core → v0/core}/_client/testing.py +1 -1
  127. cognite/neat/{core → v0/core}/_constants.py +10 -3
  128. cognite/neat/v0/core/_data_model/__init__.py +0 -0
  129. cognite/neat/{core → v0/core}/_data_model/_constants.py +9 -6
  130. cognite/neat/{core → v0/core}/_data_model/_shared.py +5 -5
  131. cognite/neat/{core → v0/core}/_data_model/analysis/_base.py +12 -8
  132. cognite/neat/{core → v0/core}/_data_model/exporters/__init__.py +1 -2
  133. cognite/neat/{core → v0/core}/_data_model/exporters/_base.py +7 -7
  134. cognite/neat/{core → v0/core}/_data_model/exporters/_data_model2dms.py +9 -9
  135. cognite/neat/{core → v0/core}/_data_model/exporters/_data_model2excel.py +13 -13
  136. cognite/neat/{core → v0/core}/_data_model/exporters/_data_model2instance_template.py +4 -4
  137. cognite/neat/{core/_data_model/exporters/_data_model2ontology.py → v0/core/_data_model/exporters/_data_model2semantic_model.py} +126 -133
  138. cognite/neat/{core → v0/core}/_data_model/exporters/_data_model2yaml.py +1 -1
  139. cognite/neat/{core → v0/core}/_data_model/importers/__init__.py +4 -6
  140. cognite/neat/{core → v0/core}/_data_model/importers/_base.py +5 -5
  141. cognite/neat/{core → v0/core}/_data_model/importers/_base_file_reader.py +2 -2
  142. cognite/neat/{core → v0/core}/_data_model/importers/_dict2data_model.py +6 -6
  143. cognite/neat/{core → v0/core}/_data_model/importers/_dms2data_model.py +19 -16
  144. cognite/neat/v0/core/_data_model/importers/_graph2data_model.py +299 -0
  145. cognite/neat/v0/core/_data_model/importers/_rdf/__init__.py +4 -0
  146. cognite/neat/{core → v0/core}/_data_model/importers/_rdf/_base.py +13 -13
  147. cognite/neat/{core → v0/core}/_data_model/importers/_rdf/_inference2rdata_model.py +14 -14
  148. cognite/neat/v0/core/_data_model/importers/_rdf/_owl2data_model.py +144 -0
  149. cognite/neat/v0/core/_data_model/importers/_rdf/_shared.py +255 -0
  150. cognite/neat/{core → v0/core}/_data_model/importers/_spreadsheet2data_model.py +94 -13
  151. cognite/neat/{core → v0/core}/_data_model/models/__init__.py +3 -3
  152. cognite/neat/{core → v0/core}/_data_model/models/_base_verified.py +5 -5
  153. cognite/neat/v0/core/_data_model/models/_import_contexts.py +82 -0
  154. cognite/neat/{core → v0/core}/_data_model/models/_types.py +5 -5
  155. cognite/neat/{core → v0/core}/_data_model/models/conceptual/_unverified.py +18 -12
  156. cognite/neat/v0/core/_data_model/models/conceptual/_validation.py +308 -0
  157. cognite/neat/{core → v0/core}/_data_model/models/conceptual/_verified.py +13 -11
  158. cognite/neat/{core → v0/core}/_data_model/models/data_types.py +14 -4
  159. cognite/neat/{core → v0/core}/_data_model/models/entities/__init__.py +6 -0
  160. cognite/neat/v0/core/_data_model/models/entities/_loaders.py +155 -0
  161. cognite/neat/{core → v0/core}/_data_model/models/entities/_multi_value.py +2 -2
  162. cognite/neat/v0/core/_data_model/models/entities/_restrictions.py +230 -0
  163. cognite/neat/{core → v0/core}/_data_model/models/entities/_single_value.py +121 -16
  164. cognite/neat/{core → v0/core}/_data_model/models/entities/_types.py +10 -0
  165. cognite/neat/{core → v0/core}/_data_model/models/mapping/_classic2core.py +5 -5
  166. cognite/neat/{core → v0/core}/_data_model/models/physical/__init__.py +1 -1
  167. cognite/neat/{core → v0/core}/_data_model/models/physical/_exporter.py +28 -21
  168. cognite/neat/{core → v0/core}/_data_model/models/physical/_unverified.py +141 -38
  169. cognite/neat/{core → v0/core}/_data_model/models/physical/_validation.py +190 -24
  170. cognite/neat/{core → v0/core}/_data_model/models/physical/_verified.py +135 -15
  171. cognite/neat/{core → v0/core}/_data_model/transformers/__init__.py +2 -0
  172. cognite/neat/{core → v0/core}/_data_model/transformers/_base.py +4 -4
  173. cognite/neat/{core → v0/core}/_data_model/transformers/_converters.py +39 -32
  174. cognite/neat/{core → v0/core}/_data_model/transformers/_mapping.py +7 -7
  175. cognite/neat/v0/core/_data_model/transformers/_union_conceptual.py +208 -0
  176. cognite/neat/{core → v0/core}/_data_model/transformers/_verification.py +7 -7
  177. cognite/neat/v0/core/_instances/__init__.py +0 -0
  178. cognite/neat/{core → v0/core}/_instances/_tracking/base.py +1 -1
  179. cognite/neat/{core → v0/core}/_instances/_tracking/log.py +1 -1
  180. cognite/neat/{core → v0/core}/_instances/extractors/__init__.py +1 -1
  181. cognite/neat/{core → v0/core}/_instances/extractors/_base.py +6 -6
  182. cognite/neat/v0/core/_instances/extractors/_classic_cdf/__init__.py +0 -0
  183. cognite/neat/{core → v0/core}/_instances/extractors/_classic_cdf/_base.py +7 -7
  184. cognite/neat/{core → v0/core}/_instances/extractors/_classic_cdf/_classic.py +12 -12
  185. cognite/neat/{core → v0/core}/_instances/extractors/_classic_cdf/_relationships.py +3 -3
  186. cognite/neat/{core → v0/core}/_instances/extractors/_classic_cdf/_sequences.py +2 -2
  187. cognite/neat/{core → v0/core}/_instances/extractors/_dict.py +6 -3
  188. cognite/neat/{core → v0/core}/_instances/extractors/_dms.py +6 -6
  189. cognite/neat/{core → v0/core}/_instances/extractors/_dms_graph.py +11 -11
  190. cognite/neat/{core → v0/core}/_instances/extractors/_mock_graph_generator.py +10 -10
  191. cognite/neat/{core → v0/core}/_instances/extractors/_raw.py +3 -3
  192. cognite/neat/{core → v0/core}/_instances/extractors/_rdf_file.py +7 -7
  193. cognite/neat/{core → v0/core}/_instances/loaders/_base.py +5 -5
  194. cognite/neat/{core → v0/core}/_instances/loaders/_rdf2dms.py +17 -17
  195. cognite/neat/{core → v0/core}/_instances/loaders/_rdf_to_instance_space.py +11 -11
  196. cognite/neat/{core → v0/core}/_instances/queries/_select.py +29 -3
  197. cognite/neat/{core → v0/core}/_instances/queries/_update.py +1 -1
  198. cognite/neat/{core → v0/core}/_instances/transformers/_base.py +4 -4
  199. cognite/neat/{core → v0/core}/_instances/transformers/_classic_cdf.py +6 -6
  200. cognite/neat/{core → v0/core}/_instances/transformers/_prune_graph.py +4 -4
  201. cognite/neat/{core → v0/core}/_instances/transformers/_rdfpath.py +1 -1
  202. cognite/neat/{core → v0/core}/_instances/transformers/_value_type.py +4 -4
  203. cognite/neat/{core → v0/core}/_issues/_base.py +11 -6
  204. cognite/neat/{core → v0/core}/_issues/_contextmanagers.py +8 -6
  205. cognite/neat/{core → v0/core}/_issues/_factory.py +11 -8
  206. cognite/neat/{core → v0/core}/_issues/errors/__init__.py +3 -1
  207. cognite/neat/{core → v0/core}/_issues/errors/_external.py +1 -1
  208. cognite/neat/{core → v0/core}/_issues/errors/_general.py +1 -1
  209. cognite/neat/{core → v0/core}/_issues/errors/_properties.py +12 -1
  210. cognite/neat/{core → v0/core}/_issues/errors/_resources.py +2 -2
  211. cognite/neat/{core → v0/core}/_issues/errors/_wrapper.py +7 -3
  212. cognite/neat/{core → v0/core}/_issues/warnings/__init__.py +5 -1
  213. cognite/neat/{core → v0/core}/_issues/warnings/_external.py +1 -1
  214. cognite/neat/{core → v0/core}/_issues/warnings/_general.py +1 -1
  215. cognite/neat/{core → v0/core}/_issues/warnings/_models.py +39 -4
  216. cognite/neat/{core → v0/core}/_issues/warnings/_properties.py +13 -2
  217. cognite/neat/{core → v0/core}/_issues/warnings/_resources.py +1 -1
  218. cognite/neat/{core → v0/core}/_issues/warnings/user_modeling.py +1 -1
  219. cognite/neat/{core → v0/core}/_store/_data_model.py +13 -12
  220. cognite/neat/{core → v0/core}/_store/_instance.py +45 -12
  221. cognite/neat/{core → v0/core}/_store/_provenance.py +3 -3
  222. cognite/neat/{core → v0/core}/_store/exceptions.py +4 -4
  223. cognite/neat/v0/core/_utils/__init__.py +0 -0
  224. cognite/neat/{core → v0/core}/_utils/auth.py +1 -1
  225. cognite/neat/{core → v0/core}/_utils/auxiliary.py +7 -1
  226. cognite/neat/{core → v0/core}/_utils/collection_.py +2 -2
  227. cognite/neat/{core → v0/core}/_utils/graph_transformations_report.py +1 -1
  228. cognite/neat/{core → v0/core}/_utils/rdf_.py +38 -14
  229. cognite/neat/{core → v0/core}/_utils/reader/_base.py +1 -1
  230. cognite/neat/{core → v0/core}/_utils/spreadsheet.py +22 -4
  231. cognite/neat/v0/core/_utils/tarjan.py +44 -0
  232. cognite/neat/{core → v0/core}/_utils/text.py +1 -1
  233. cognite/neat/{core → v0/core}/_utils/upload.py +3 -3
  234. cognite/neat/v0/plugins/__init__.py +4 -0
  235. cognite/neat/v0/plugins/_base.py +9 -0
  236. cognite/neat/v0/plugins/_data_model.py +48 -0
  237. cognite/neat/{plugins → v0/plugins}/_issues.py +1 -1
  238. cognite/neat/{plugins → v0/plugins}/_manager.py +7 -16
  239. cognite/neat/{session → v0/session}/_base.py +13 -10
  240. cognite/neat/{session → v0/session}/_collector.py +1 -1
  241. cognite/neat/v0/session/_diff.py +51 -0
  242. cognite/neat/{session → v0/session}/_drop.py +3 -3
  243. cognite/neat/{session → v0/session}/_explore.py +2 -2
  244. cognite/neat/{session → v0/session}/_fix.py +2 -2
  245. cognite/neat/{session → v0/session}/_inspect.py +3 -3
  246. cognite/neat/{session → v0/session}/_mapping.py +3 -3
  247. cognite/neat/{session → v0/session}/_plugin.py +4 -5
  248. cognite/neat/{session → v0/session}/_prepare.py +8 -8
  249. cognite/neat/{session → v0/session}/_read.py +33 -43
  250. cognite/neat/{session → v0/session}/_set.py +8 -8
  251. cognite/neat/{session → v0/session}/_show.py +5 -5
  252. cognite/neat/{session → v0/session}/_state.py +22 -8
  253. cognite/neat/{session → v0/session}/_subset.py +4 -4
  254. cognite/neat/{session → v0/session}/_template.py +11 -11
  255. cognite/neat/{session → v0/session}/_to.py +12 -12
  256. cognite/neat/{session → v0/session}/_wizard.py +1 -1
  257. cognite/neat/{session → v0/session}/engine/_load.py +1 -1
  258. cognite/neat/{session → v0/session}/exceptions.py +5 -5
  259. cognite/neat/v1.py +3 -0
  260. {cognite_neat-0.123.2.dist-info → cognite_neat-0.127.30.dist-info}/METADATA +9 -8
  261. cognite_neat-0.127.30.dist-info/RECORD +319 -0
  262. {cognite_neat-0.123.2.dist-info → cognite_neat-0.127.30.dist-info}/WHEEL +1 -1
  263. cognite/neat/core/_data_model/importers/_dtdl2data_model/__init__.py +0 -3
  264. cognite/neat/core/_data_model/importers/_dtdl2data_model/_unit_lookup.py +0 -224
  265. cognite/neat/core/_data_model/importers/_dtdl2data_model/dtdl_converter.py +0 -320
  266. cognite/neat/core/_data_model/importers/_dtdl2data_model/dtdl_importer.py +0 -155
  267. cognite/neat/core/_data_model/importers/_dtdl2data_model/spec.py +0 -363
  268. cognite/neat/core/_data_model/importers/_rdf/__init__.py +0 -5
  269. cognite/neat/core/_data_model/importers/_rdf/_imf2data_model.py +0 -98
  270. cognite/neat/core/_data_model/importers/_rdf/_owl2data_model.py +0 -87
  271. cognite/neat/core/_data_model/importers/_rdf/_shared.py +0 -168
  272. cognite/neat/core/_data_model/models/conceptual/_validation.py +0 -294
  273. cognite/neat/core/_data_model/models/entities/_loaders.py +0 -75
  274. cognite/neat/plugins/__init__.py +0 -3
  275. cognite/neat/plugins/data_model/importers/__init__.py +0 -5
  276. cognite/neat/plugins/data_model/importers/_base.py +0 -28
  277. cognite_neat-0.123.2.dist-info/RECORD +0 -197
  278. /cognite/neat/{core → _data_model}/__init__.py +0 -0
  279. /cognite/neat/{core/_client/_api → _data_model/deployer}/__init__.py +0 -0
  280. /cognite/neat/{core/_client/data_classes → _data_model/exporters/_table_exporter}/__init__.py +0 -0
  281. /cognite/neat/{core/_data_model → _data_model/importers/_table_importer}/__init__.py +0 -0
  282. /cognite/neat/{core/_instances → _data_model/models}/__init__.py +0 -0
  283. /cognite/neat/{core/_instances/extractors/_classic_cdf → _data_model/models/conceptual}/__init__.py +0 -0
  284. /cognite/neat/{core/_utils → _data_model/validation}/__init__.py +0 -0
  285. /cognite/neat/{plugins/data_model → _session/_html}/__init__.py +0 -0
  286. /cognite/neat/{core → v0/core}/_client/__init__.py +0 -0
  287. /cognite/neat/{core → v0/core}/_client/data_classes/data_modeling.py +0 -0
  288. /cognite/neat/{core → v0/core}/_client/data_classes/neat_sequence.py +0 -0
  289. /cognite/neat/{core → v0/core}/_client/data_classes/statistics.py +0 -0
  290. /cognite/neat/{core → v0/core}/_config.py +0 -0
  291. /cognite/neat/{core → v0/core}/_data_model/analysis/__init__.py +0 -0
  292. /cognite/neat/{core → v0/core}/_data_model/catalog/__init__.py +0 -0
  293. /cognite/neat/{core → v0/core}/_data_model/catalog/classic_model.xlsx +0 -0
  294. /cognite/neat/{core → v0/core}/_data_model/catalog/conceptual-imf-data-model.xlsx +0 -0
  295. /cognite/neat/{core → v0/core}/_data_model/catalog/hello_world_pump.xlsx +0 -0
  296. /cognite/neat/{core → v0/core}/_data_model/models/_base_unverified.py +0 -0
  297. /cognite/neat/{core → v0/core}/_data_model/models/conceptual/__init__.py +0 -0
  298. /cognite/neat/{core → v0/core}/_data_model/models/entities/_constants.py +0 -0
  299. /cognite/neat/{core → v0/core}/_data_model/models/entities/_wrapped.py +0 -0
  300. /cognite/neat/{core → v0/core}/_data_model/models/mapping/__init__.py +0 -0
  301. /cognite/neat/{core → v0/core}/_data_model/models/mapping/_classic2core.yaml +0 -0
  302. /cognite/neat/{core → v0/core}/_instances/_shared.py +0 -0
  303. /cognite/neat/{core → v0/core}/_instances/_tracking/__init__.py +0 -0
  304. /cognite/neat/{core → v0/core}/_instances/examples/Knowledge-Graph-Nordic44-dirty.xml +0 -0
  305. /cognite/neat/{core → v0/core}/_instances/examples/Knowledge-Graph-Nordic44.xml +0 -0
  306. /cognite/neat/{core → v0/core}/_instances/examples/__init__.py +0 -0
  307. /cognite/neat/{core → v0/core}/_instances/examples/skos-capturing-sheet-wind-topics.xlsx +0 -0
  308. /cognite/neat/{core → v0/core}/_instances/extractors/_classic_cdf/_assets.py +0 -0
  309. /cognite/neat/{core → v0/core}/_instances/extractors/_classic_cdf/_data_sets.py +0 -0
  310. /cognite/neat/{core → v0/core}/_instances/extractors/_classic_cdf/_events.py +0 -0
  311. /cognite/neat/{core → v0/core}/_instances/extractors/_classic_cdf/_files.py +0 -0
  312. /cognite/neat/{core → v0/core}/_instances/extractors/_classic_cdf/_labels.py +0 -0
  313. /cognite/neat/{core → v0/core}/_instances/extractors/_classic_cdf/_timeseries.py +0 -0
  314. /cognite/neat/{core → v0/core}/_instances/loaders/__init__.py +0 -0
  315. /cognite/neat/{core → v0/core}/_instances/queries/__init__.py +0 -0
  316. /cognite/neat/{core → v0/core}/_instances/queries/_base.py +0 -0
  317. /cognite/neat/{core → v0/core}/_instances/queries/_queries.py +0 -0
  318. /cognite/neat/{core → v0/core}/_instances/transformers/__init__.py +0 -0
  319. /cognite/neat/{core → v0/core}/_issues/__init__.py +0 -0
  320. /cognite/neat/{core → v0/core}/_issues/formatters.py +0 -0
  321. /cognite/neat/{core → v0/core}/_shared.py +0 -0
  322. /cognite/neat/{core → v0/core}/_store/__init__.py +0 -0
  323. /cognite/neat/{core → v0/core}/_utils/io_.py +0 -0
  324. /cognite/neat/{core → v0/core}/_utils/reader/__init__.py +0 -0
  325. /cognite/neat/{core → v0/core}/_utils/time_.py +0 -0
  326. /cognite/neat/{core → v0/core}/_utils/xml_.py +0 -0
  327. /cognite/neat/{session → v0/session}/__init__.py +0 -0
  328. /cognite/neat/{session → v0/session}/_experimental.py +0 -0
  329. /cognite/neat/{session → v0/session}/_state/README.md +0 -0
  330. /cognite/neat/{session → v0/session}/engine/__init__.py +0 -0
  331. /cognite/neat/{session → v0/session}/engine/_import.py +0 -0
  332. /cognite/neat/{session → v0/session}/engine/_interface.py +0 -0
  333. {cognite_neat-0.123.2.dist-info → cognite_neat-0.127.30.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,529 @@
1
+ import itertools
2
+ import sys
3
+ from abc import ABC, abstractmethod
4
+ from collections import UserList, defaultdict
5
+ from datetime import datetime
6
+ from enum import Enum
7
+ from typing import Any, Generic, Literal, TypeAlias, cast
8
+
9
+ from pydantic import BaseModel, Field
10
+ from pydantic.alias_generators import to_camel
11
+
12
+ from cognite.neat._data_model.models.dms import (
13
+ BaseModelObject,
14
+ Constraint,
15
+ ContainerConstraintReference,
16
+ ContainerIndexReference,
17
+ ContainerPropertyDefinition,
18
+ ContainerReference,
19
+ ContainerRequest,
20
+ DataModelReference,
21
+ DataModelRequest,
22
+ DataModelResource,
23
+ Index,
24
+ NodeReference,
25
+ SpaceReference,
26
+ SpaceRequest,
27
+ T_DataModelResource,
28
+ T_ResourceId,
29
+ ViewReference,
30
+ ViewRequest,
31
+ ViewRequestProperty,
32
+ )
33
+ from cognite.neat._utils.http_client import (
34
+ FailedRequestItems,
35
+ FailedResponseItems,
36
+ SuccessResponse,
37
+ SuccessResponseItems,
38
+ )
39
+ from cognite.neat._utils.useful_types import T_Reference
40
+
41
+ if sys.version_info >= (3, 11):
42
+ from typing import Self
43
+ else:
44
+ from typing_extensions import Self
45
+
46
+ JsonPath: TypeAlias = str # e.g., 'properties.temperature', 'constraints.uniqueKey'
47
+ DataModelEndpoint: TypeAlias = Literal["spaces", "containers", "views", "datamodels", "instances"]
48
+
49
+
50
+ class SeverityType(Enum):
51
+ SAFE = 1
52
+ WARNING = 2
53
+ BREAKING = 3
54
+
55
+ @classmethod
56
+ def max_severity(cls, severities: list["SeverityType"], default: "SeverityType") -> "SeverityType":
57
+ value = max([severity.value for severity in severities], default=default.value)
58
+ return cls(value)
59
+
60
+
61
+ class BaseDeployObject(BaseModel, alias_generator=to_camel, extra="ignore", populate_by_name=True):
62
+ """Base class for all deployer data model objects."""
63
+
64
+ ...
65
+
66
+
67
+ class FieldChange(BaseDeployObject, ABC):
68
+ """Represents a change to a specific property or field."""
69
+
70
+ field_path: JsonPath
71
+
72
+ @property
73
+ @abstractmethod
74
+ def severity(self) -> SeverityType:
75
+ """The severity of the change."""
76
+ raise NotImplementedError()
77
+
78
+
79
+ class PrimitiveField(FieldChange, ABC):
80
+ """Base class for changes to primitive properties."""
81
+
82
+ item_severity: SeverityType
83
+
84
+ @property
85
+ def severity(self) -> SeverityType:
86
+ return self.item_severity
87
+
88
+
89
+ class AddedField(PrimitiveField):
90
+ new_value: BaseModelObject | str | int | float | bool | None
91
+
92
+ @property
93
+ def description(self) -> str:
94
+ return f"added with value {self.new_value!r}"
95
+
96
+
97
+ class RemovedField(PrimitiveField):
98
+ current_value: BaseModelObject | str | int | float | bool | None
99
+
100
+ @property
101
+ def description(self) -> str:
102
+ return f"removed (was {self.current_value!r})"
103
+
104
+
105
+ class ChangedField(PrimitiveField):
106
+ new_value: BaseModelObject | str | int | float | bool | None
107
+ current_value: BaseModelObject | str | int | float | bool | None
108
+
109
+ @property
110
+ def description(self) -> str:
111
+ if self.new_value is None:
112
+ return f"removed (was {self.current_value!r})"
113
+ elif self.current_value is None:
114
+ return f"added with value {self.new_value!r}"
115
+ return f"changed from {self.current_value!r} to {self.new_value!r}"
116
+
117
+
118
+ class FieldChanges(FieldChange):
119
+ """Represents a nested property, i.e., a property that contains other properties."""
120
+
121
+ changes: list[FieldChange]
122
+
123
+ @property
124
+ def severity(self) -> SeverityType:
125
+ return SeverityType.max_severity([item.severity for item in self.changes], default=SeverityType.SAFE)
126
+
127
+
128
+ class ResourceChange(BaseDeployObject, Generic[T_ResourceId, T_DataModelResource]):
129
+ resource_id: T_ResourceId
130
+ new_value: T_DataModelResource | None
131
+ current_value: T_DataModelResource | None = None
132
+ changes: list[FieldChange] = Field(default_factory=list)
133
+ message: str | None = None
134
+
135
+ @property
136
+ def change_type(self) -> Literal["create", "update", "delete", "unchanged", "skip"]:
137
+ if self.current_value is None and self.new_value is not None:
138
+ return "create"
139
+ elif self.new_value is None and self.current_value is not None:
140
+ return "delete"
141
+ elif self.changes:
142
+ return "update"
143
+ elif self.new_value is None and self.current_value is None:
144
+ return "skip"
145
+ else:
146
+ return "unchanged"
147
+
148
+ @property
149
+ def severity(self) -> SeverityType:
150
+ return SeverityType.max_severity([change.severity for change in self.changes], default=SeverityType.SAFE)
151
+
152
+
153
+ class ResourceDeploymentPlan(BaseDeployObject, Generic[T_ResourceId, T_DataModelResource]):
154
+ endpoint: DataModelEndpoint
155
+ resources: list[ResourceChange[T_ResourceId, T_DataModelResource]]
156
+
157
+ @property
158
+ def to_upsert(self) -> list[ResourceChange[T_ResourceId, T_DataModelResource]]:
159
+ return [change for change in self.resources if change.change_type in ("create", "update")]
160
+
161
+ @property
162
+ def to_create(self) -> list[ResourceChange[T_ResourceId, T_DataModelResource]]:
163
+ return [change for change in self.resources if change.change_type == "create"]
164
+
165
+ @property
166
+ def to_update(self) -> list[ResourceChange[T_ResourceId, T_DataModelResource]]:
167
+ return [change for change in self.resources if change.change_type == "update"]
168
+
169
+ @property
170
+ def to_delete(self) -> list[ResourceChange[T_ResourceId, T_DataModelResource]]:
171
+ return [change for change in self.resources if change.change_type == "delete"]
172
+
173
+ @property
174
+ def unchanged(self) -> list[ResourceChange[T_ResourceId, T_DataModelResource]]:
175
+ return [change for change in self.resources if change.change_type == "unchanged"]
176
+
177
+ @property
178
+ def skip(self) -> list[ResourceChange[T_ResourceId, T_DataModelResource]]:
179
+ return [change for change in self.resources if change.change_type == "skip"]
180
+
181
+
182
+ class ContainerDeploymentPlan(ResourceDeploymentPlan[ContainerReference, ContainerRequest]):
183
+ endpoint: Literal["containers"] = "containers"
184
+ resources: list[ResourceChange[ContainerReference, ContainerRequest]]
185
+
186
+ @property
187
+ def indexes_to_remove(self) -> dict[ContainerIndexReference, RemovedField]:
188
+ return self._get_fields_to_remove("indexes.", ContainerIndexReference)
189
+
190
+ @property
191
+ def constraints_to_remove(self) -> dict[ContainerConstraintReference, RemovedField]:
192
+ return self._get_fields_to_remove("constraints.", ContainerConstraintReference)
193
+
194
+ def _get_fields_to_remove(self, field_prefix: str, ref_cls: type) -> dict:
195
+ items: dict = {}
196
+ for resource_change in self.resources:
197
+ for change in resource_change.changes:
198
+ if isinstance(change, RemovedField) and change.field_path.startswith(field_prefix):
199
+ identifier = change.field_path.removeprefix(field_prefix)
200
+ items[
201
+ ref_cls(
202
+ space=resource_change.resource_id.space,
203
+ external_id=resource_change.resource_id.external_id,
204
+ identifier=identifier,
205
+ )
206
+ ] = change
207
+ return items
208
+
209
+ @property
210
+ def to_upsert(self) -> list[ResourceChange[ContainerReference, ContainerRequest]]:
211
+ return [change for change in self.resources if change.change_type == "create" or self._is_update(change)]
212
+
213
+ @property
214
+ def to_update(self) -> list[ResourceChange[ContainerReference, ContainerRequest]]:
215
+ return [change for change in self.resources if self._is_update(change)]
216
+
217
+ @classmethod
218
+ def _is_update(cls, change: ResourceChange[ContainerReference, ContainerRequest]) -> bool:
219
+ """Whether the container change is an update.
220
+
221
+ Containers with only index or constraint removals are not considered updates, as these are handled by a
222
+ separate API call.
223
+ """
224
+ if change.change_type != "update":
225
+ return False
226
+ for c in change.changes:
227
+ if not (
228
+ isinstance(c, RemovedField)
229
+ and (c.field_path.startswith("indexes.") or c.field_path.startswith("constraints."))
230
+ ):
231
+ return True
232
+ return False
233
+
234
+
235
+ class SchemaSnapshot(BaseDeployObject):
236
+ timestamp: datetime
237
+ data_model: dict[DataModelReference, DataModelRequest]
238
+ views: dict[ViewReference, ViewRequest]
239
+ containers: dict[ContainerReference, ContainerRequest]
240
+ spaces: dict[SpaceReference, SpaceRequest]
241
+ node_types: dict[NodeReference, NodeReference]
242
+
243
+
244
+ class ResourceDeploymentPlanList(UserList[ResourceDeploymentPlan]):
245
+ def consolidate_changes(self) -> Self:
246
+ """Consolidate the deployment plans by applying field removals to the new_value of resources."""
247
+ return type(self)([self._consolidate_resource_plan(plan) for plan in self.data])
248
+
249
+ def _consolidate_resource_plan(self, plan: ResourceDeploymentPlan) -> ResourceDeploymentPlan:
250
+ consolidated_resources = [
251
+ self._consolidate_resource_change(resource_change) for resource_change in plan.resources
252
+ ]
253
+ return plan.model_copy(update={"resources": consolidated_resources})
254
+
255
+ def _consolidate_resource_change(
256
+ self, resource: ResourceChange[T_ResourceId, T_DataModelResource]
257
+ ) -> ResourceChange[T_ResourceId, T_DataModelResource]:
258
+ if resource.new_value is None and resource.current_value is not None:
259
+ # Changed deletion (new_value is None and curren_value is not None) to unchanged by copying
260
+ # current_value to new_value.
261
+ updated_resource = resource.model_copy(update={"new_value": resource.current_value})
262
+ elif resource.changes and resource.new_value is not None:
263
+ # Find all field removals and update new_value accordingly.
264
+ removals = [change for change in resource.changes if isinstance(change, RemovedField)]
265
+ addition_paths = {change.field_path for change in resource.changes if isinstance(change, AddedField)}
266
+ if removals:
267
+ if resource.current_value is None:
268
+ raise RuntimeError("Bug in Neat: current_value is None for a resource with removals.")
269
+ new_value = self._consolidate_resource(
270
+ resource.current_value, resource.new_value, removals, addition_paths
271
+ )
272
+
273
+ updated_resource = resource.model_copy(
274
+ update={
275
+ "new_value": new_value,
276
+ "changes": [
277
+ change
278
+ for change in resource.changes
279
+ if not isinstance(change, RemovedField)
280
+ or (isinstance(change, RemovedField) and change.field_path in addition_paths)
281
+ ],
282
+ }
283
+ )
284
+ else:
285
+ # No removals, keep as is.
286
+ updated_resource = resource
287
+ else:
288
+ # Creation or unchanged, keep as is.
289
+ updated_resource = resource
290
+ return updated_resource
291
+
292
+ def _consolidate_resource(
293
+ self, current: DataModelResource, new: DataModelResource, removals: list[RemovedField], addition_paths: set[str]
294
+ ) -> DataModelResource:
295
+ if isinstance(new, DataModelRequest):
296
+ if not isinstance(current, DataModelRequest):
297
+ # This should not happen, as only containers, views, and data models have removable fields.
298
+ raise RuntimeError("Bug in Neat: current value is not a DataModelRequest during consolidation.")
299
+ return self._consolidate_data_model(current, new)
300
+ elif isinstance(new, ViewRequest):
301
+ return self._consolidate_view(new, removals)
302
+ elif isinstance(new, ContainerRequest):
303
+ return self._consolidate_container(new, removals, addition_paths)
304
+ elif removals:
305
+ # This should not happen, as only containers, views, and data models have removable fields.
306
+ raise RuntimeError("Bug in Neat: attempted to consolidate removals for unsupported resource type.")
307
+ return new
308
+
309
+ @staticmethod
310
+ def _consolidate_data_model(current: DataModelRequest, new: DataModelRequest) -> DataModelResource:
311
+ current_views = set(v for v in (current.views or []))
312
+ new_only_views = [v for v in (new.views or []) if v not in current_views]
313
+ final_views = (current.views or []) + new_only_views
314
+ return new.model_copy(update={"views": final_views}, deep=True)
315
+
316
+ @staticmethod
317
+ def _consolidate_view(resource: ViewRequest, removals: list[RemovedField]) -> DataModelResource:
318
+ view_properties = resource.properties.copy()
319
+ for removal in removals:
320
+ if removal.field_path.startswith("properties."):
321
+ prop_key = removal.field_path.removeprefix("properties.")
322
+ view_properties[prop_key] = cast(ViewRequestProperty, removal.current_value)
323
+ return resource.model_copy(update={"properties": view_properties}, deep=True)
324
+
325
+ @staticmethod
326
+ def _consolidate_container(
327
+ resource: ContainerRequest, removals: list[RemovedField], addition_paths: set[str]
328
+ ) -> DataModelResource:
329
+ container_properties = resource.properties.copy()
330
+ indexes = (resource.indexes or {}).copy()
331
+ constraints = (resource.constraints or {}).copy()
332
+ for removal in removals:
333
+ if removal.field_path.startswith("properties."):
334
+ prop_key = removal.field_path.removeprefix("properties.")
335
+ container_properties[prop_key] = cast(ContainerPropertyDefinition, removal.current_value)
336
+ elif removal.field_path.startswith("indexes.") and removal.field_path not in addition_paths:
337
+ # Index was removed and not re-added, so we need to restore it.
338
+ index_key = removal.field_path.removeprefix("indexes.")
339
+ indexes[index_key] = cast(Index, removal.current_value)
340
+ elif removal.field_path.startswith("constraints.") and removal.field_path not in addition_paths:
341
+ # Constraint was removed and not re-added, so we need to restore it.
342
+ constraint_key = removal.field_path.removeprefix("constraints.")
343
+ constraints[constraint_key] = cast(Constraint, removal.current_value)
344
+ return resource.model_copy(
345
+ update={"properties": container_properties, "indexes": indexes or None, "constraints": constraints or None},
346
+ deep=True,
347
+ )
348
+
349
+ def force_changes(self, drop_data: bool) -> Self:
350
+ """Force all resources by deleting and recreating them.
351
+
352
+ Args:
353
+ drop_data: If True, containers will be deleted and recreated. If False, containers
354
+ will be consolidated instead.
355
+ Returns:
356
+ A new ResourceDeploymentPlanList with forced changes.
357
+ """
358
+ forced_plans: list[ResourceDeploymentPlan] = []
359
+ for plan in self.data:
360
+ forced_resources: list[ResourceChange] = []
361
+ for resource in plan.resources:
362
+ if resource.change_type == "update" and resource.severity == SeverityType.BREAKING:
363
+ if drop_data or plan.endpoint != "containers":
364
+ deletion = resource.model_copy(deep=True, update={"new_value": None, "changes": []})
365
+ recreation = resource.model_copy(deep=True, update={"current_value": None, "changes": []})
366
+ forced_resources.append(deletion)
367
+ forced_resources.append(recreation)
368
+ else:
369
+ # For containers, we try to consolidate instead of deleting and recreating.
370
+ # Note that there might still be breaking changes left which will cause the deployment to fail.
371
+ # For example, if the usedFor field has changed from node->edge, then this cannot be
372
+ # consolidated.
373
+ consolidated_resource = self._consolidate_resource_change(resource)
374
+ forced_resources.append(consolidated_resource)
375
+ else:
376
+ # No need to force, keep as is.
377
+ forced_resources.append(resource)
378
+ forced_plans.append(plan.model_copy(update={"resources": forced_resources}))
379
+ return type(self)(forced_plans)
380
+
381
+
382
+ class ChangeResult(BaseDeployObject, Generic[T_ResourceId, T_DataModelResource]):
383
+ endpoint: DataModelEndpoint
384
+ change: ResourceChange[T_ResourceId, T_DataModelResource]
385
+ message: SuccessResponseItems[T_ResourceId] | FailedResponseItems[T_ResourceId] | FailedRequestItems[T_ResourceId]
386
+
387
+
388
+ class ChangedFieldResult(BaseDeployObject, Generic[T_Reference]):
389
+ field_change: FieldChange
390
+ message: SuccessResponseItems[T_Reference] | FailedResponseItems[T_Reference] | FailedRequestItems[T_Reference]
391
+
392
+
393
+ class AppliedChanges(BaseDeployObject):
394
+ """The result of applying changes to the data model.
395
+
396
+ Contains lists of created, updated, deleted, and unchanged resources.
397
+
398
+ In addition, it has changed fields which tracks the removal of indexes and constraints from containers.
399
+ This is needed as these changes are done with a separate API call per change.
400
+ """
401
+
402
+ created: list[ChangeResult] = Field(default_factory=list)
403
+ updated: list[ChangeResult] = Field(default_factory=list)
404
+ deletions: list[ChangeResult] = Field(default_factory=list)
405
+ unchanged: list[ResourceChange] = Field(default_factory=list)
406
+ skipped: list[ResourceChange] = Field(default_factory=list)
407
+ changed_fields: list[ChangedFieldResult] = Field(default_factory=list)
408
+
409
+ @property
410
+ def is_success(self) -> bool:
411
+ return all(
412
+ # MyPy fails to understand that ChangeFieldResult.message has the same structure as ChangeResult.message
413
+ isinstance(change.message, SuccessResponse) # type: ignore[attr-defined]
414
+ for change in itertools.chain(self.created, self.updated, self.deletions, self.changed_fields)
415
+ )
416
+
417
+ def as_recovery_plan(self) -> list[ResourceDeploymentPlan]:
418
+ """Generate a recovery plan based on the applied changes."""
419
+ recovery_plan: dict[DataModelEndpoint, ResourceDeploymentPlan] = {}
420
+ for change_result in itertools.chain(self.created, self.updated, self.deletions):
421
+ if not isinstance(change_result.message, SuccessResponse):
422
+ continue # Skip failed changes.
423
+ change = change_result.change
424
+ if change.change_type == "create":
425
+ # To recover a created resource, we need to delete it.
426
+ # MyPy wants an annotation were we want this to be generic.
427
+ recovery_change = ResourceChange( # type: ignore[var-annotated]
428
+ resource_id=change.resource_id,
429
+ current_value=change.new_value,
430
+ new_value=None,
431
+ changes=[],
432
+ )
433
+ elif change.change_type == "delete":
434
+ # To recover a deleted resource, we need to create it.
435
+ recovery_change = ResourceChange(
436
+ resource_id=change.resource_id,
437
+ current_value=None,
438
+ new_value=change.current_value,
439
+ changes=[],
440
+ )
441
+ elif change.change_type == "update":
442
+ # To recover an updated resource, we need to revert to the previous state.
443
+ recovery_change = ResourceChange(
444
+ resource_id=change.resource_id,
445
+ current_value=change.new_value,
446
+ new_value=change.current_value,
447
+ changes=self._reverse_changes(change.changes),
448
+ )
449
+ else:
450
+ continue # Unchanged resources do not need recovery.
451
+
452
+ if change_result.endpoint not in recovery_plan:
453
+ recovery_plan[change_result.endpoint] = ResourceDeploymentPlan(
454
+ endpoint=change_result.endpoint, resources=[]
455
+ )
456
+ recovery_plan[change_result.endpoint].resources.append(recovery_change)
457
+
458
+ return list(recovery_plan.values())
459
+
460
+ def _reverse_changes(self, changes: list[FieldChange]) -> list[FieldChange]:
461
+ reversed_changes: list[FieldChange] = []
462
+ for change in changes:
463
+ if isinstance(change, AddedField):
464
+ reversed_changes.append(
465
+ RemovedField(
466
+ field_path=change.field_path,
467
+ current_value=change.new_value,
468
+ item_severity=change.item_severity,
469
+ )
470
+ )
471
+ elif isinstance(change, RemovedField):
472
+ reversed_changes.append(
473
+ AddedField(
474
+ field_path=change.field_path,
475
+ new_value=change.current_value,
476
+ item_severity=change.item_severity,
477
+ )
478
+ )
479
+ elif isinstance(change, ChangedField):
480
+ reversed_changes.append(
481
+ ChangedField(
482
+ field_path=change.field_path,
483
+ current_value=change.new_value,
484
+ new_value=change.current_value,
485
+ item_severity=change.item_severity,
486
+ )
487
+ )
488
+ elif isinstance(change, FieldChanges):
489
+ reversed_changes.append(
490
+ FieldChanges(
491
+ field_path=change.field_path,
492
+ changes=self._reverse_changes(change.changes),
493
+ )
494
+ )
495
+ return reversed_changes
496
+
497
+
498
+ class DeploymentResult(BaseDeployObject):
499
+ status: Literal["success", "failure", "partial", "pending"]
500
+ plan: list[ResourceDeploymentPlan]
501
+ snapshot: SchemaSnapshot
502
+ responses: AppliedChanges | None = None
503
+ recovery: AppliedChanges | None = None
504
+
505
+ @property
506
+ def is_dry_run(self) -> bool:
507
+ return self.status == "pending"
508
+
509
+ @property
510
+ def is_success(self) -> bool:
511
+ return self.status in ("success", "pending")
512
+
513
+ def as_mixpanel_event(self) -> dict[str, Any]:
514
+ """Convert deployment result to mixpanel event format"""
515
+ output: dict[str, Any] = {
516
+ "status": self.status,
517
+ "isDryRun": self.is_dry_run,
518
+ "isSuccess": self.is_success,
519
+ }
520
+ if self.responses:
521
+ counts: dict[str, int] = defaultdict(int)
522
+ for change in itertools.chain(self.responses.created, self.responses.updated, self.responses.deletions):
523
+ suffix = type(change.message).__name__.removesuffix("[TypeVar]").removesuffix("[~T_ResourceId]")
524
+ # For example: containers.created.successResponseItems
525
+ prefix = f"{change.endpoint}.{change.change.change_type}.{suffix}"
526
+ counts[prefix] += len(change.message.ids)
527
+
528
+ output.update(counts)
529
+ return output