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,638 @@
1
+ """Validators for connections in data model specifications."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from cognite.neat._data_model.models.dms._data_types import DirectNodeRelation
6
+ from cognite.neat._data_model.models.dms._references import (
7
+ ContainerDirectReference,
8
+ ViewDirectReference,
9
+ ViewReference,
10
+ )
11
+ from cognite.neat._data_model.models.dms._view_property import ViewCorePropertyRequest
12
+ from cognite.neat._data_model.validation.dms._base import DataModelValidator
13
+ from cognite.neat._issues import ConsistencyError, Recommendation
14
+
15
+ BASE_CODE = "NEAT-DMS-CONNECTIONS"
16
+
17
+
18
+ class ConnectionValueTypeUnexisting(DataModelValidator):
19
+ """Validates that connection value types exist.
20
+
21
+ ## What it does
22
+ Validates that all connection value types defined in the data model exist.
23
+
24
+ ## Why is this bad?
25
+ If a connection value type does not exist, the data model cannot be deployed to CDF.
26
+ This means that the connection will not be able to function.
27
+
28
+ ## Example
29
+ If view WindTurbine has a connection property windFarm with value type WindFarm, but WindFarm view is not defined,
30
+ the data model cannot be deployed to CDF.
31
+ """
32
+
33
+ code = f"{BASE_CODE}-001"
34
+ issue_type = ConsistencyError
35
+
36
+ def run(self) -> list[ConsistencyError]:
37
+ undefined_value_types = []
38
+
39
+ for (view, property_), value_type in self.local_resources.connection_end_node_types.items():
40
+ if value_type is None:
41
+ continue
42
+
43
+ if value_type in self.local_resources.views_by_reference:
44
+ continue
45
+
46
+ if (
47
+ self.modus_operandi == "additive" or value_type.space != self.local_resources.data_model_reference.space
48
+ ) and value_type in self.cdf_resources.views_by_reference:
49
+ continue
50
+
51
+ undefined_value_types.append((view, property_, value_type))
52
+
53
+ return [
54
+ ConsistencyError(
55
+ message=(
56
+ f"View {view!s} connection {property_!s} has value type {value_type!s} "
57
+ "which is not defined as a view in the data model neither exists in CDF."
58
+ ),
59
+ fix="Define necessary view",
60
+ code=self.code,
61
+ )
62
+ for (view, property_, value_type) in undefined_value_types
63
+ ]
64
+
65
+
66
+ class ConnectionValueTypeUndefined(DataModelValidator):
67
+ """Validates that connection value types are not None, i.e. undefined.
68
+
69
+ ## What it does
70
+ Validates that connections have explicitly defined value types (i.e., end connection node type).
71
+
72
+ ## Why is this bad?
73
+ If a connection value type is None (undefined), there is no type information about the end node of the connection.
74
+ This yields an ambiguous data model definition, which may lead to issues during consumption of data from CDF.
75
+
76
+ ## Example
77
+ Consider a scenario where we have views WindTurbine,ArrayCable and Substation. Lets say WindTurbine has a connection
78
+ `connectsTo` with value type None (undefined), then it is unclear what type of view the connection points to as
79
+ both ArrayCable and Substation are valid targets for the connection.
80
+
81
+ """
82
+
83
+ code = f"{BASE_CODE}-002"
84
+ issue_type = Recommendation
85
+
86
+ def run(self) -> list[Recommendation]:
87
+ missing_value_types = []
88
+
89
+ for (view, property_), value_type in self.local_resources.connection_end_node_types.items():
90
+ if not value_type:
91
+ missing_value_types.append((view, property_))
92
+
93
+ return [
94
+ Recommendation(
95
+ message=(
96
+ f"View {view!s} connection {property_!s} is missing value type (end node type)."
97
+ " This yields ambiguous data model definition."
98
+ ),
99
+ fix="Define necessary value type",
100
+ code=self.code,
101
+ )
102
+ for (view, property_) in missing_value_types
103
+ ]
104
+
105
+
106
+ @dataclass
107
+ class ReverseConnectionContext:
108
+ """Context for validating a bidirectional connection.
109
+ This context holds all necessary references to validate the connection.
110
+
111
+ Attributes:
112
+ target_view_ref: Reference to the target view containing the reverse property.
113
+ reverse_property: Identifier of the reverse property in the target view.
114
+ through: Direct reference defining the direct property used in the reverse connection.
115
+ source_view_ref: Reference to the source view containing the direct property, to which the reverse points.
116
+
117
+
118
+ Example:
119
+ View `WindTurbine` has property `windFarm` (direct) → points to View `WindFarm`
120
+ View `WindFarm` has property `turbines` (reverse) → points to View `WindTurbine` through `WindTurbine.windFarm`
121
+ """
122
+
123
+ target_view_ref: ViewReference
124
+ reverse_property: str
125
+ through: ViewDirectReference
126
+ source_view_ref: ViewReference
127
+
128
+
129
+ def _normalize_through_reference(
130
+ source_view_ref: ViewReference, through: ContainerDirectReference | ViewDirectReference
131
+ ) -> ViewDirectReference:
132
+ """Normalize through reference to ViewDirectReference for consistent processing."""
133
+ if isinstance(through, ContainerDirectReference):
134
+ return ViewDirectReference(source=source_view_ref, identifier=through.identifier)
135
+ return through
136
+
137
+
138
+ class ReverseConnectionSourceViewMissing(DataModelValidator):
139
+ """Validates that source view referenced in reverse connection exist.
140
+
141
+ ## What it does
142
+ Checks that the source view used to configure a reverse connection exists.
143
+
144
+ ## Why is this bad?
145
+ A reverse connection requires a corresponding direct connection in the source view.
146
+ If the source view doesn't exist, the reverse connection is invalid.
147
+
148
+ ## Example
149
+ If WindFarm has a reverse property `turbines` through `WindTurbine.windFarm`,
150
+ but WindTurbine view doesn't exist, the reverse connection cannot function.
151
+ """
152
+
153
+ code = f"{BASE_CODE}-REVERSE-001"
154
+ issue_type = ConsistencyError
155
+
156
+ def run(self) -> list[ConsistencyError]:
157
+ errors: list[ConsistencyError] = []
158
+
159
+ for (target_view_ref, reverse_prop_name), (
160
+ source_view_ref,
161
+ through,
162
+ ) in self.local_resources.reverse_to_direct_mapping.items():
163
+ through = _normalize_through_reference(source_view_ref, through)
164
+ source_view = self._select_view_with_property(source_view_ref, through.identifier)
165
+
166
+ if not source_view:
167
+ errors.append(
168
+ ConsistencyError(
169
+ message=(
170
+ f"Source view {source_view_ref!s} used to configure reverse connection "
171
+ f"'{reverse_prop_name}' in target view {target_view_ref!s} "
172
+ "does not exist in the data model or CDF."
173
+ ),
174
+ fix="Define the missing source view",
175
+ code=self.code,
176
+ )
177
+ )
178
+
179
+ return errors
180
+
181
+
182
+ class ReverseConnectionSourcePropertyMissing(DataModelValidator):
183
+ """Validates that source property referenced in reverse connections exist.
184
+
185
+ ## What it does
186
+ Checks that the direct connection property in the source view (used in the reverse connection's 'through')
187
+ actually exists in the source view.
188
+
189
+ ## Why is this bad?
190
+ A reverse connection requires a corresponding direct connection property in the source view.
191
+ If this property doesn't exist, the bidirectional connection is incomplete.
192
+
193
+ ## Example
194
+ If WindFarm has a reverse property `turbines` through `WindTurbine.windFarm`,
195
+ but WindTurbine view doesn't have a `windFarm` property, the reverse connection is invalid.
196
+ """
197
+
198
+ code = f"{BASE_CODE}-REVERSE-002"
199
+ issue_type = ConsistencyError
200
+
201
+ def run(self) -> list[ConsistencyError]:
202
+ errors: list[ConsistencyError] = []
203
+
204
+ for (target_view_ref, reverse_prop_name), (
205
+ source_view_ref,
206
+ through,
207
+ ) in self.local_resources.reverse_to_direct_mapping.items():
208
+ through = _normalize_through_reference(source_view_ref, through)
209
+ source_view = self._select_view_with_property(source_view_ref, through.identifier)
210
+
211
+ if not source_view:
212
+ continue # Handled by ReverseConnectionSourceViewMissing
213
+
214
+ if not source_view.properties or through.identifier not in source_view.properties:
215
+ errors.append(
216
+ ConsistencyError(
217
+ message=(
218
+ f"Source view {source_view_ref!s} is missing property '{through.identifier}' "
219
+ f"which is required to configure the reverse connection "
220
+ f"'{reverse_prop_name}' in target view {target_view_ref!s}."
221
+ ),
222
+ fix="Add the missing property to the source view",
223
+ code=self.code,
224
+ )
225
+ )
226
+
227
+ return errors
228
+
229
+
230
+ class ReverseConnectionSourcePropertyWrongType(DataModelValidator):
231
+ """Validates that source property for the reverse connections is a direct relation.
232
+
233
+ ## What it does
234
+ Checks that the property referenced in a reverse connection's 'through' clause
235
+ is actually a direct connection property (not a primitive or other type).
236
+
237
+ ## Why is this bad?
238
+ Reverse connections can only work with direct connection properties.
239
+ Using other property types breaks the bidirectional relationship.
240
+
241
+ ## Example
242
+ If WindFarm has a reverse property `turbines` through `WindTurbine.name`,
243
+ but `name` is a Text property (not a direct connection), the reverse connection is invalid.
244
+ """
245
+
246
+ code = f"{BASE_CODE}-REVERSE-003"
247
+ issue_type = ConsistencyError
248
+
249
+ def run(self) -> list[ConsistencyError]:
250
+ errors: list[ConsistencyError] = []
251
+
252
+ for (target_view_ref, reverse_prop_name), (
253
+ source_view_ref,
254
+ through,
255
+ ) in self.local_resources.reverse_to_direct_mapping.items():
256
+ through = _normalize_through_reference(source_view_ref, through)
257
+ source_view = self._select_view_with_property(source_view_ref, through.identifier)
258
+
259
+ if not source_view or not source_view.properties:
260
+ continue # Handled by other validators
261
+
262
+ source_property = source_view.properties.get(through.identifier)
263
+ if not source_property:
264
+ continue # Handled by ReverseConnectionSourcePropertyMissing
265
+
266
+ if not isinstance(source_property, ViewCorePropertyRequest):
267
+ errors.append(
268
+ ConsistencyError(
269
+ message=(
270
+ f"Source view {source_view_ref!s} property '{through.identifier}' "
271
+ f"used for configuring the reverse connection '{reverse_prop_name}' "
272
+ f"in target view {target_view_ref!s} is not a direct connection property."
273
+ ),
274
+ fix="Update view property to be a direct connection property",
275
+ code=self.code,
276
+ )
277
+ )
278
+
279
+ return errors
280
+
281
+
282
+ class ReverseConnectionContainerMissing(DataModelValidator):
283
+ """Validates that the container referenced by the reverse connection source properties exist.
284
+
285
+ ## What it does
286
+ Checks that the container holding the direct connection property (used in reverse connection) exists.
287
+
288
+ ## Why is this bad?
289
+ The direct connection property must be stored in a container.
290
+ If the container doesn't exist, the connection cannot be persisted.
291
+
292
+ ## Example
293
+ If WindTurbine.windFarm maps to container `WindTurbine`, but this container doesn't exist,
294
+ the reverse connection from WindFarm cannot function.
295
+ """
296
+
297
+ code = f"{BASE_CODE}-REVERSE-004"
298
+ issue_type = ConsistencyError
299
+
300
+ def run(self) -> list[ConsistencyError]:
301
+ errors: list[ConsistencyError] = []
302
+
303
+ for (target_view_ref, reverse_prop_name), (
304
+ source_view_ref,
305
+ through,
306
+ ) in self.local_resources.reverse_to_direct_mapping.items():
307
+ through = _normalize_through_reference(source_view_ref, through)
308
+ source_view = self._select_view_with_property(source_view_ref, through.identifier)
309
+
310
+ if not source_view or not source_view.properties:
311
+ continue # Handled by other validators
312
+
313
+ source_property = source_view.properties.get(through.identifier)
314
+ if not source_property or not isinstance(source_property, ViewCorePropertyRequest):
315
+ continue # Handled by other validators
316
+
317
+ container_ref = source_property.container
318
+ container_property_id = source_property.container_property_identifier
319
+
320
+ source_container = self._select_container_with_property(container_ref, container_property_id)
321
+ if not source_container:
322
+ errors.append(
323
+ ConsistencyError(
324
+ message=(
325
+ f"Container {container_ref!s} is missing in both the data model and CDF. "
326
+ f"This container is required by view {source_view_ref!s}"
327
+ f" property '{through.identifier}', "
328
+ f"which configures the reverse connection '{reverse_prop_name}'"
329
+ f" in target view {target_view_ref!s}."
330
+ ),
331
+ fix="Define the missing container",
332
+ code=self.code,
333
+ )
334
+ )
335
+
336
+ return errors
337
+
338
+
339
+ class ReverseConnectionContainerPropertyMissing(DataModelValidator):
340
+ """Validates that container property referenced by the reverse connections exists.
341
+
342
+ ## What it does
343
+ Checks that the property in the container (mapped from the view's direct connection property)
344
+ actually exists in the container.
345
+
346
+ ## Why is this bad?
347
+ The view property must map to an actual container property for data persistence.
348
+ If the container property doesn't exist, data cannot be stored.
349
+
350
+ ## Example
351
+ If WindTurbine.windFarm maps to container property `WindTurbine.windFarm`,
352
+ but this container property doesn't exist, the connection cannot be stored.
353
+ """
354
+
355
+ code = f"{BASE_CODE}-REVERSE-005"
356
+ issue_type = ConsistencyError
357
+
358
+ def run(self) -> list[ConsistencyError]:
359
+ errors: list[ConsistencyError] = []
360
+
361
+ for (target_view_ref, reverse_prop_name), (
362
+ source_view_ref,
363
+ through,
364
+ ) in self.local_resources.reverse_to_direct_mapping.items():
365
+ through = _normalize_through_reference(source_view_ref, through)
366
+ source_view = self._select_view_with_property(source_view_ref, through.identifier)
367
+
368
+ if not source_view or not source_view.properties:
369
+ continue # Handled by other validators
370
+
371
+ source_property = source_view.properties.get(through.identifier)
372
+ if not source_property or not isinstance(source_property, ViewCorePropertyRequest):
373
+ continue # Handled by other validators
374
+
375
+ container_ref = source_property.container
376
+ container_property_id = source_property.container_property_identifier
377
+
378
+ source_container = self._select_container_with_property(container_ref, container_property_id)
379
+ if not source_container:
380
+ continue # Handled by ReverseConnectionContainerMissing
381
+
382
+ if not source_container.properties or container_property_id not in source_container.properties:
383
+ errors.append(
384
+ ConsistencyError(
385
+ message=(
386
+ f"Container {container_ref!s} is missing property '{container_property_id}'. "
387
+ f"This property is required by the source view {source_view_ref!s}"
388
+ f" property '{through.identifier}', "
389
+ f"which configures the reverse connection '{reverse_prop_name}' "
390
+ f"in target view {target_view_ref!s}."
391
+ ),
392
+ fix="Add the missing property to the container",
393
+ code=self.code,
394
+ )
395
+ )
396
+
397
+ return errors
398
+
399
+
400
+ class ReverseConnectionContainerPropertyWrongType(DataModelValidator):
401
+ """Validates that the container property used in reverse connection is the direct relations.
402
+
403
+ ## What it does
404
+ Checks that the container property (mapped from view's direct connection property)
405
+ has type DirectNodeRelation.
406
+
407
+ ## Why is this bad?
408
+ Container properties backing connection view properties must be DirectNodeRelation type.
409
+ Other types cannot represent connections in the underlying storage.
410
+
411
+ ## Example
412
+ If WindTurbine.windFarm maps to container property with type Text instead of DirectNodeRelation,
413
+ the connection cannot be stored correctly.
414
+ """
415
+
416
+ code = f"{BASE_CODE}-REVERSE-006"
417
+ issue_type = ConsistencyError
418
+
419
+ def run(self) -> list[ConsistencyError]:
420
+ errors: list[ConsistencyError] = []
421
+
422
+ for (target_view_ref, reverse_prop_name), (
423
+ source_view_ref,
424
+ through,
425
+ ) in self.local_resources.reverse_to_direct_mapping.items():
426
+ through = _normalize_through_reference(source_view_ref, through)
427
+ source_view = self._select_view_with_property(source_view_ref, through.identifier)
428
+
429
+ if not source_view or not source_view.properties:
430
+ continue # Handled by other validators
431
+
432
+ source_property = source_view.properties.get(through.identifier)
433
+ if not source_property or not isinstance(source_property, ViewCorePropertyRequest):
434
+ continue # Handled by other validators
435
+
436
+ container_ref = source_property.container
437
+ container_property_id = source_property.container_property_identifier
438
+
439
+ source_container = self._select_container_with_property(container_ref, container_property_id)
440
+ if not source_container or not source_container.properties:
441
+ continue # Handled by other validators
442
+
443
+ container_property = source_container.properties.get(container_property_id)
444
+ if not container_property:
445
+ continue # Handled by ReverseConnectionContainerPropertyMissing
446
+
447
+ if not isinstance(container_property.type, DirectNodeRelation):
448
+ errors.append(
449
+ ConsistencyError(
450
+ message=(
451
+ f"Container property '{container_property_id}' in container {container_ref!s} "
452
+ f"must be a direct connection, but found type '{container_property.type!s}'. "
453
+ f"This property is used by source view {source_view_ref!s} property '{through.identifier}' "
454
+ f"to configure reverse connection '{reverse_prop_name}' in target view {target_view_ref!s}."
455
+ ),
456
+ fix="Change container property type to be a direct connection",
457
+ code=self.code,
458
+ )
459
+ )
460
+
461
+ return errors
462
+
463
+
464
+ class ReverseConnectionTargetMissing(DataModelValidator):
465
+ """Validates that the direct connection in reverse connection pair have target views specified.
466
+
467
+ ## What it does
468
+ Checks whether the direct connection property (referenced by reverse connection) has a value type.
469
+
470
+ ## Why is this bad?
471
+ While CDF allows value type None as a SEARCH hack for multi-value relations,
472
+ it's better to explicitly specify the target view for clarity and maintainability.
473
+
474
+ ## Example
475
+ If WindTurbine.windFarm has value type None instead of WindFarm,
476
+ this validator recommends specifying WindFarm explicitly.
477
+ """
478
+
479
+ code = f"{BASE_CODE}-REVERSE-007"
480
+ issue_type = Recommendation
481
+
482
+ def run(self) -> list[Recommendation]:
483
+ recommendations: list[Recommendation] = []
484
+
485
+ for (target_view_ref, reverse_prop_name), (
486
+ source_view_ref,
487
+ through,
488
+ ) in self.local_resources.reverse_to_direct_mapping.items():
489
+ through = _normalize_through_reference(source_view_ref, through)
490
+ source_view = self._select_view_with_property(source_view_ref, through.identifier)
491
+
492
+ if not source_view or not source_view.properties:
493
+ continue # Handled by other validators
494
+
495
+ source_property = source_view.properties.get(through.identifier)
496
+ if not source_property or not isinstance(source_property, ViewCorePropertyRequest):
497
+ continue # Handled by other validators
498
+
499
+ actual_target_view = source_property.source
500
+
501
+ if not actual_target_view:
502
+ recommendations.append(
503
+ Recommendation(
504
+ message=(
505
+ f"Source view {source_view_ref!s} property '{through.identifier}' "
506
+ f"has no target view specified (value type is None). "
507
+ f"This property is used for reverse connection '{reverse_prop_name}' "
508
+ f"in target view {target_view_ref!s}. "
509
+ f"While this works as a hack for multi-value relations in CDF Search, "
510
+ f"it's recommended to explicitly define the target view as {target_view_ref!s}."
511
+ ),
512
+ fix="Set the property's value type to the target view for better clarity",
513
+ code=self.code,
514
+ )
515
+ )
516
+
517
+ return recommendations
518
+
519
+
520
+ class ReverseConnectionPointsToAncestor(DataModelValidator):
521
+ """Validates that direct connections point to specific views rather than ancestors.
522
+
523
+ ## What it does
524
+ Checks whether the direct connection property points to an ancestor of the expected target view
525
+ and recommends pointing to the specific target instead.
526
+
527
+ ## Why is this bad?
528
+ While technically valid, pointing to ancestors can be confusing and may lead to mistakes.
529
+ It's clearer to point to the specific target view.
530
+
531
+ ## Example
532
+ If WindFarm.turbines expects WindTurbine.windFarm to point to WindFarm,
533
+ but it points to Asset (ancestor of WindFarm), this validator recommends the change.
534
+ """
535
+
536
+ code = f"{BASE_CODE}-REVERSE-008"
537
+ issue_type = Recommendation
538
+
539
+ def run(self) -> list[Recommendation]:
540
+ recommendations: list[Recommendation] = []
541
+
542
+ for (target_view_ref, reverse_prop_name), (
543
+ source_view_ref,
544
+ through,
545
+ ) in self.local_resources.reverse_to_direct_mapping.items():
546
+ through = _normalize_through_reference(source_view_ref, through)
547
+ source_view = self._select_view_with_property(source_view_ref, through.identifier)
548
+
549
+ if not source_view or not source_view.properties:
550
+ continue # Handled by other validators
551
+
552
+ source_property = source_view.properties.get(through.identifier)
553
+ if not source_property or not isinstance(source_property, ViewCorePropertyRequest):
554
+ continue # Handled by other validators
555
+
556
+ actual_target_view = source_property.source
557
+
558
+ if not actual_target_view:
559
+ continue # Handled by ReverseConnectionTargetMissing
560
+
561
+ if self._is_ancestor_of_target(actual_target_view, target_view_ref):
562
+ recommendations.append(
563
+ Recommendation(
564
+ message=(
565
+ f"The direct connection property '{through.identifier}' in view {source_view_ref!s} "
566
+ f"configures the reverse connection '{reverse_prop_name}' in {target_view_ref!s}. "
567
+ f"Therefore, it is expected that '{through.identifier}' points to {target_view_ref!s}. "
568
+ f"However, it currently points to {actual_target_view!s}, which is an ancestor of "
569
+ f"{target_view_ref!s}. "
570
+ "While this will allow for model to be valid, it can be a source of confusion and mistakes."
571
+ ),
572
+ fix="Update the direct connection property to point to the target view instead of its ancestor",
573
+ code=self.code,
574
+ )
575
+ )
576
+
577
+ return recommendations
578
+
579
+
580
+ class ReverseConnectionTargetMismatch(DataModelValidator):
581
+ """Validates that direct connections point to the correct target views.
582
+
583
+ ## What it does
584
+ Checks that the direct connection property points to the expected target view
585
+ (the view containing the reverse connection).
586
+
587
+ ## Why is this bad?
588
+ The reverse connection expects a bidirectional relationship.
589
+ If the direct connection points to a different view, the relationship is broken.
590
+
591
+ ## Example
592
+ If WindFarm.turbines is a reverse through WindTurbine.windFarm,
593
+ but WindTurbine.windFarm points to SolarFarm instead of WindFarm, the connection is invalid.
594
+ """
595
+
596
+ code = f"{BASE_CODE}-REVERSE-009"
597
+ issue_type = ConsistencyError
598
+
599
+ def run(self) -> list[ConsistencyError]:
600
+ errors: list[ConsistencyError] = []
601
+
602
+ for (target_view_ref, reverse_prop_name), (
603
+ source_view_ref,
604
+ through,
605
+ ) in self.local_resources.reverse_to_direct_mapping.items():
606
+ through = _normalize_through_reference(source_view_ref, through)
607
+ source_view = self._select_view_with_property(source_view_ref, through.identifier)
608
+
609
+ if not source_view or not source_view.properties:
610
+ continue # Handled by other validators
611
+
612
+ source_property = source_view.properties.get(through.identifier)
613
+ if not source_property or not isinstance(source_property, ViewCorePropertyRequest):
614
+ continue # Handled by other validators
615
+
616
+ actual_target_view = source_property.source
617
+
618
+ if not actual_target_view:
619
+ continue # Handled by ReverseConnectionTargetMissing
620
+
621
+ if self._is_ancestor_of_target(actual_target_view, target_view_ref):
622
+ continue # Handled by ReverseConnectionTargetAncestor
623
+
624
+ if actual_target_view != target_view_ref:
625
+ errors.append(
626
+ ConsistencyError(
627
+ message=(
628
+ f"The reverse connection '{reverse_prop_name}' in view {target_view_ref!s} "
629
+ f"expects its corresponding direct connection in view {source_view_ref!s} "
630
+ f"(property '{through.identifier}') to point back to {target_view_ref!s}, "
631
+ f"but it actually points to {actual_target_view!s}."
632
+ ),
633
+ fix="Update the direct connection property to point back to the correct target view",
634
+ code=self.code,
635
+ )
636
+ )
637
+
638
+ return errors
@@ -0,0 +1,57 @@
1
+ """Validators checking for consistency issues in data model."""
2
+
3
+ from cognite.neat._data_model._constants import COGNITE_SPACES
4
+ from cognite.neat._data_model.validation.dms._base import DataModelValidator
5
+ from cognite.neat._issues import Recommendation
6
+
7
+ BASE_CODE = "NEAT-DMS-CONSISTENCY"
8
+
9
+
10
+ class ViewSpaceVersionInconsistentWithDataModel(DataModelValidator):
11
+ """Validates that views have consistent space and version with the data model.
12
+
13
+ ## What it does
14
+ Validates that all views in the data model have the same space and version as the data model.
15
+
16
+ ## Why is this bad?
17
+ If views have different space or version than the data model, it may lead to more demanding development and
18
+ maintenance efforts. The industry best practice is to keep views in the same space and version as the data model.
19
+
20
+ ## Example
21
+ If the data model is defined in space "my_space" version "v1", but a view is defined in the same spave but with
22
+ version "v2", this requires additional attention during deployment and maintenance of the data model.
23
+ """
24
+
25
+ code = f"{BASE_CODE}-001"
26
+ issue_type = Recommendation
27
+
28
+ def run(self) -> list[Recommendation]:
29
+ recommendations: list[Recommendation] = []
30
+
31
+ for view_ref in self.local_resources.views_by_reference:
32
+ issue_description = ""
33
+
34
+ if view_ref.space not in COGNITE_SPACES:
35
+ # notify about inconsisten space
36
+ if view_ref.space != self.local_resources.data_model_reference.space:
37
+ issue_description = (
38
+ f"space (view: {view_ref.space}, data model: {self.local_resources.data_model_reference.space})"
39
+ )
40
+
41
+ # or version if spaces are same
42
+ elif view_ref.version != self.local_resources.data_model_reference.version:
43
+ issue_description = (
44
+ f"version (view: {view_ref.version}, "
45
+ f"data model: {self.local_resources.data_model_reference.version})"
46
+ )
47
+
48
+ if issue_description:
49
+ recommendations.append(
50
+ Recommendation(
51
+ message=(f"View {view_ref!s} has inconsistent {issue_description} with the data model."),
52
+ fix="Update view version and/or space to match data model",
53
+ code=self.code,
54
+ )
55
+ )
56
+
57
+ return recommendations