cognite-neat 0.123.26__py3-none-any.whl → 1.0.22__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. cognite/neat/__init__.py +4 -3
  2. cognite/neat/_client/__init__.py +5 -0
  3. cognite/neat/_client/api.py +8 -0
  4. cognite/neat/_client/client.py +21 -0
  5. cognite/neat/_client/config.py +40 -0
  6. cognite/neat/_client/containers_api.py +138 -0
  7. cognite/neat/_client/data_classes.py +44 -0
  8. cognite/neat/_client/data_model_api.py +115 -0
  9. cognite/neat/_client/init/credentials.py +70 -0
  10. cognite/neat/_client/init/env_vars.py +131 -0
  11. cognite/neat/_client/init/main.py +51 -0
  12. cognite/neat/_client/spaces_api.py +115 -0
  13. cognite/neat/_client/statistics_api.py +24 -0
  14. cognite/neat/_client/views_api.py +144 -0
  15. cognite/neat/_config.py +266 -0
  16. cognite/neat/_data_model/_analysis.py +571 -0
  17. cognite/neat/_data_model/_constants.py +74 -0
  18. cognite/neat/_data_model/_identifiers.py +61 -0
  19. cognite/neat/_data_model/_shared.py +41 -0
  20. cognite/neat/_data_model/_snapshot.py +134 -0
  21. cognite/neat/_data_model/deployer/_differ.py +140 -0
  22. cognite/neat/_data_model/deployer/_differ_container.py +360 -0
  23. cognite/neat/_data_model/deployer/_differ_data_model.py +54 -0
  24. cognite/neat/_data_model/deployer/_differ_space.py +9 -0
  25. cognite/neat/_data_model/deployer/_differ_view.py +299 -0
  26. cognite/neat/_data_model/deployer/data_classes.py +644 -0
  27. cognite/neat/_data_model/deployer/deployer.py +431 -0
  28. cognite/neat/_data_model/exporters/__init__.py +15 -0
  29. cognite/neat/_data_model/exporters/_api_exporter.py +37 -0
  30. cognite/neat/_data_model/exporters/_base.py +24 -0
  31. cognite/neat/_data_model/exporters/_table_exporter/exporter.py +128 -0
  32. cognite/neat/_data_model/exporters/_table_exporter/workbook.py +409 -0
  33. cognite/neat/_data_model/exporters/_table_exporter/writer.py +480 -0
  34. cognite/neat/_data_model/importers/__init__.py +5 -0
  35. cognite/neat/_data_model/importers/_api_importer.py +166 -0
  36. cognite/neat/_data_model/importers/_base.py +16 -0
  37. cognite/neat/_data_model/importers/_table_importer/data_classes.py +344 -0
  38. cognite/neat/_data_model/importers/_table_importer/importer.py +192 -0
  39. cognite/neat/_data_model/importers/_table_importer/reader.py +1102 -0
  40. cognite/neat/_data_model/importers/_table_importer/source.py +94 -0
  41. cognite/neat/_data_model/models/conceptual/_base.py +18 -0
  42. cognite/neat/_data_model/models/conceptual/_concept.py +67 -0
  43. cognite/neat/_data_model/models/conceptual/_data_model.py +51 -0
  44. cognite/neat/_data_model/models/conceptual/_properties.py +104 -0
  45. cognite/neat/_data_model/models/conceptual/_property.py +105 -0
  46. cognite/neat/_data_model/models/dms/__init__.py +206 -0
  47. cognite/neat/_data_model/models/dms/_base.py +31 -0
  48. cognite/neat/_data_model/models/dms/_constants.py +48 -0
  49. cognite/neat/_data_model/models/dms/_constraints.py +42 -0
  50. cognite/neat/_data_model/models/dms/_container.py +159 -0
  51. cognite/neat/_data_model/models/dms/_data_model.py +95 -0
  52. cognite/neat/_data_model/models/dms/_data_types.py +195 -0
  53. cognite/neat/_data_model/models/dms/_http.py +28 -0
  54. cognite/neat/_data_model/models/dms/_indexes.py +30 -0
  55. cognite/neat/_data_model/models/dms/_limits.py +96 -0
  56. cognite/neat/_data_model/models/dms/_references.py +141 -0
  57. cognite/neat/_data_model/models/dms/_schema.py +18 -0
  58. cognite/neat/_data_model/models/dms/_space.py +48 -0
  59. cognite/neat/_data_model/models/dms/_types.py +17 -0
  60. cognite/neat/_data_model/models/dms/_view_filter.py +310 -0
  61. cognite/neat/_data_model/models/dms/_view_property.py +235 -0
  62. cognite/neat/_data_model/models/dms/_views.py +216 -0
  63. cognite/neat/_data_model/models/entities/__init__.py +50 -0
  64. cognite/neat/_data_model/models/entities/_base.py +101 -0
  65. cognite/neat/_data_model/models/entities/_constants.py +22 -0
  66. cognite/neat/_data_model/models/entities/_data_types.py +144 -0
  67. cognite/neat/_data_model/models/entities/_identifiers.py +61 -0
  68. cognite/neat/_data_model/models/entities/_parser.py +226 -0
  69. cognite/neat/_data_model/validation/dms/__init__.py +75 -0
  70. cognite/neat/_data_model/validation/dms/_ai_readiness.py +381 -0
  71. cognite/neat/_data_model/validation/dms/_base.py +25 -0
  72. cognite/neat/_data_model/validation/dms/_connections.py +681 -0
  73. cognite/neat/_data_model/validation/dms/_consistency.py +58 -0
  74. cognite/neat/_data_model/validation/dms/_containers.py +199 -0
  75. cognite/neat/_data_model/validation/dms/_limits.py +368 -0
  76. cognite/neat/_data_model/validation/dms/_orchestrator.py +70 -0
  77. cognite/neat/_data_model/validation/dms/_views.py +164 -0
  78. cognite/neat/_exceptions.py +68 -0
  79. cognite/neat/_issues.py +68 -0
  80. cognite/neat/_session/__init__.py +3 -0
  81. cognite/neat/_session/_html/_render.py +30 -0
  82. cognite/neat/_session/_html/static/__init__.py +8 -0
  83. cognite/neat/_session/_html/static/deployment.css +476 -0
  84. cognite/neat/_session/_html/static/deployment.js +181 -0
  85. cognite/neat/_session/_html/static/issues.css +211 -0
  86. cognite/neat/_session/_html/static/issues.js +168 -0
  87. cognite/neat/_session/_html/static/shared.css +186 -0
  88. cognite/neat/_session/_html/templates/__init__.py +4 -0
  89. cognite/neat/_session/_html/templates/deployment.html +80 -0
  90. cognite/neat/_session/_html/templates/issues.html +45 -0
  91. cognite/neat/_session/_issues.py +81 -0
  92. cognite/neat/_session/_physical.py +294 -0
  93. cognite/neat/_session/_result/__init__.py +3 -0
  94. cognite/neat/_session/_result/_deployment/__init__.py +0 -0
  95. cognite/neat/_session/_result/_deployment/_physical/__init__.py +0 -0
  96. cognite/neat/_session/_result/_deployment/_physical/_changes.py +196 -0
  97. cognite/neat/_session/_result/_deployment/_physical/_statistics.py +180 -0
  98. cognite/neat/_session/_result/_deployment/_physical/serializer.py +35 -0
  99. cognite/neat/_session/_result/_result.py +31 -0
  100. cognite/neat/_session/_session.py +81 -0
  101. cognite/neat/_session/_usage_analytics/__init__.py +0 -0
  102. cognite/neat/_session/_usage_analytics/_collector.py +131 -0
  103. cognite/neat/_session/_usage_analytics/_constants.py +23 -0
  104. cognite/neat/_session/_usage_analytics/_storage.py +240 -0
  105. cognite/neat/_session/_wrappers.py +101 -0
  106. cognite/neat/_state_machine/__init__.py +10 -0
  107. cognite/neat/_state_machine/_base.py +37 -0
  108. cognite/neat/_state_machine/_states.py +52 -0
  109. cognite/neat/_store/__init__.py +3 -0
  110. cognite/neat/_store/_provenance.py +88 -0
  111. cognite/neat/_store/_store.py +220 -0
  112. cognite/neat/_utils/__init__.py +0 -0
  113. cognite/neat/_utils/_reader.py +194 -0
  114. cognite/neat/_utils/auxiliary.py +49 -0
  115. cognite/neat/_utils/collection.py +11 -0
  116. cognite/neat/_utils/http_client/__init__.py +39 -0
  117. cognite/neat/_utils/http_client/_client.py +245 -0
  118. cognite/neat/_utils/http_client/_config.py +19 -0
  119. cognite/neat/_utils/http_client/_data_classes.py +294 -0
  120. cognite/neat/_utils/http_client/_tracker.py +31 -0
  121. cognite/neat/_utils/repo.py +19 -0
  122. cognite/neat/_utils/text.py +71 -0
  123. cognite/neat/_utils/useful_types.py +37 -0
  124. cognite/neat/_utils/validation.py +154 -0
  125. cognite/neat/_v0/__init__.py +0 -0
  126. cognite/neat/_v0/core/__init__.py +0 -0
  127. cognite/neat/_v0/core/_client/_api/__init__.py +0 -0
  128. cognite/neat/{core → _v0/core}/_client/_api/data_modeling_loaders.py +8 -7
  129. cognite/neat/{core → _v0/core}/_client/_api/neat_instances.py +5 -5
  130. cognite/neat/{core → _v0/core}/_client/_api/schema.py +5 -5
  131. cognite/neat/{core → _v0/core}/_client/_api/statistics.py +3 -3
  132. cognite/neat/{core → _v0/core}/_client/_api_client.py +1 -1
  133. cognite/neat/_v0/core/_client/data_classes/__init__.py +0 -0
  134. cognite/neat/{core → _v0/core}/_client/data_classes/schema.py +4 -4
  135. cognite/neat/{core → _v0/core}/_client/testing.py +1 -1
  136. cognite/neat/{core → _v0/core}/_constants.py +5 -3
  137. cognite/neat/_v0/core/_data_model/__init__.py +0 -0
  138. cognite/neat/{core → _v0/core}/_data_model/_constants.py +7 -0
  139. cognite/neat/{core → _v0/core}/_data_model/_shared.py +4 -4
  140. cognite/neat/{core → _v0/core}/_data_model/analysis/_base.py +8 -8
  141. cognite/neat/{core → _v0/core}/_data_model/exporters/__init__.py +1 -2
  142. cognite/neat/{core → _v0/core}/_data_model/exporters/_base.py +7 -7
  143. cognite/neat/{core → _v0/core}/_data_model/exporters/_data_model2dms.py +9 -9
  144. cognite/neat/{core → _v0/core}/_data_model/exporters/_data_model2excel.py +12 -12
  145. cognite/neat/{core → _v0/core}/_data_model/exporters/_data_model2instance_template.py +4 -4
  146. cognite/neat/{core/_data_model/exporters/_data_model2ontology.py → _v0/core/_data_model/exporters/_data_model2semantic_model.py} +126 -116
  147. cognite/neat/{core → _v0/core}/_data_model/exporters/_data_model2yaml.py +1 -1
  148. cognite/neat/{core → _v0/core}/_data_model/importers/_base.py +5 -5
  149. cognite/neat/{core → _v0/core}/_data_model/importers/_base_file_reader.py +2 -2
  150. cognite/neat/{core → _v0/core}/_data_model/importers/_dict2data_model.py +5 -5
  151. cognite/neat/{core → _v0/core}/_data_model/importers/_dms2data_model.py +18 -15
  152. cognite/neat/{core → _v0/core}/_data_model/importers/_graph2data_model.py +12 -12
  153. cognite/neat/{core → _v0/core}/_data_model/importers/_rdf/_base.py +12 -12
  154. cognite/neat/{core → _v0/core}/_data_model/importers/_rdf/_inference2rdata_model.py +14 -14
  155. cognite/neat/{core → _v0/core}/_data_model/importers/_rdf/_owl2data_model.py +41 -21
  156. cognite/neat/{core → _v0/core}/_data_model/importers/_rdf/_shared.py +9 -9
  157. cognite/neat/{core → _v0/core}/_data_model/importers/_spreadsheet2data_model.py +92 -12
  158. cognite/neat/{core → _v0/core}/_data_model/models/__init__.py +3 -3
  159. cognite/neat/{core → _v0/core}/_data_model/models/_base_verified.py +5 -5
  160. cognite/neat/{core → _v0/core}/_data_model/models/_import_contexts.py +1 -1
  161. cognite/neat/{core → _v0/core}/_data_model/models/_types.py +5 -5
  162. cognite/neat/{core → _v0/core}/_data_model/models/conceptual/_unverified.py +16 -10
  163. cognite/neat/{core → _v0/core}/_data_model/models/conceptual/_validation.py +12 -12
  164. cognite/neat/{core → _v0/core}/_data_model/models/conceptual/_verified.py +9 -9
  165. cognite/neat/{core → _v0/core}/_data_model/models/data_types.py +14 -4
  166. cognite/neat/{core → _v0/core}/_data_model/models/entities/__init__.py +6 -0
  167. cognite/neat/_v0/core/_data_model/models/entities/_loaders.py +155 -0
  168. cognite/neat/{core → _v0/core}/_data_model/models/entities/_multi_value.py +2 -2
  169. cognite/neat/_v0/core/_data_model/models/entities/_restrictions.py +230 -0
  170. cognite/neat/{core → _v0/core}/_data_model/models/entities/_single_value.py +121 -16
  171. cognite/neat/{core → _v0/core}/_data_model/models/entities/_types.py +10 -0
  172. cognite/neat/{core → _v0/core}/_data_model/models/mapping/_classic2core.py +5 -5
  173. cognite/neat/{core → _v0/core}/_data_model/models/physical/__init__.py +1 -1
  174. cognite/neat/{core → _v0/core}/_data_model/models/physical/_exporter.py +26 -19
  175. cognite/neat/{core → _v0/core}/_data_model/models/physical/_unverified.py +133 -37
  176. cognite/neat/{core → _v0/core}/_data_model/models/physical/_validation.py +24 -20
  177. cognite/neat/{core → _v0/core}/_data_model/models/physical/_verified.py +95 -24
  178. cognite/neat/{core → _v0/core}/_data_model/transformers/_base.py +4 -4
  179. cognite/neat/{core → _v0/core}/_data_model/transformers/_converters.py +35 -28
  180. cognite/neat/{core → _v0/core}/_data_model/transformers/_mapping.py +7 -7
  181. cognite/neat/{core → _v0/core}/_data_model/transformers/_union_conceptual.py +5 -5
  182. cognite/neat/{core → _v0/core}/_data_model/transformers/_verification.py +7 -7
  183. cognite/neat/_v0/core/_instances/__init__.py +0 -0
  184. cognite/neat/{core → _v0/core}/_instances/_tracking/base.py +1 -1
  185. cognite/neat/{core → _v0/core}/_instances/_tracking/log.py +1 -1
  186. cognite/neat/{core → _v0/core}/_instances/extractors/__init__.py +3 -2
  187. cognite/neat/{core → _v0/core}/_instances/extractors/_base.py +6 -6
  188. cognite/neat/_v0/core/_instances/extractors/_classic_cdf/__init__.py +0 -0
  189. cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_base.py +7 -7
  190. cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_classic.py +12 -12
  191. cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_relationships.py +3 -3
  192. cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_sequences.py +2 -2
  193. cognite/neat/{core → _v0/core}/_instances/extractors/_dict.py +6 -3
  194. cognite/neat/{core → _v0/core}/_instances/extractors/_dms.py +6 -6
  195. cognite/neat/{core → _v0/core}/_instances/extractors/_dms_graph.py +11 -11
  196. cognite/neat/{core → _v0/core}/_instances/extractors/_mock_graph_generator.py +10 -10
  197. cognite/neat/{core → _v0/core}/_instances/extractors/_raw.py +3 -3
  198. cognite/neat/{core → _v0/core}/_instances/extractors/_rdf_file.py +7 -7
  199. cognite/neat/{core → _v0/core}/_instances/loaders/_base.py +5 -5
  200. cognite/neat/{core → _v0/core}/_instances/loaders/_rdf2dms.py +17 -17
  201. cognite/neat/{core → _v0/core}/_instances/loaders/_rdf_to_instance_space.py +11 -11
  202. cognite/neat/{core → _v0/core}/_instances/queries/_select.py +29 -3
  203. cognite/neat/{core → _v0/core}/_instances/queries/_update.py +1 -1
  204. cognite/neat/{core → _v0/core}/_instances/transformers/_base.py +4 -4
  205. cognite/neat/{core → _v0/core}/_instances/transformers/_classic_cdf.py +6 -6
  206. cognite/neat/{core → _v0/core}/_instances/transformers/_prune_graph.py +4 -4
  207. cognite/neat/{core → _v0/core}/_instances/transformers/_rdfpath.py +1 -1
  208. cognite/neat/{core → _v0/core}/_instances/transformers/_value_type.py +4 -4
  209. cognite/neat/{core → _v0/core}/_issues/_base.py +5 -5
  210. cognite/neat/{core → _v0/core}/_issues/_contextmanagers.py +1 -1
  211. cognite/neat/{core → _v0/core}/_issues/_factory.py +3 -3
  212. cognite/neat/{core → _v0/core}/_issues/errors/__init__.py +1 -1
  213. cognite/neat/{core → _v0/core}/_issues/errors/_external.py +1 -1
  214. cognite/neat/{core → _v0/core}/_issues/errors/_general.py +1 -1
  215. cognite/neat/{core → _v0/core}/_issues/errors/_properties.py +1 -1
  216. cognite/neat/{core → _v0/core}/_issues/errors/_resources.py +2 -2
  217. cognite/neat/{core → _v0/core}/_issues/errors/_wrapper.py +7 -3
  218. cognite/neat/{core → _v0/core}/_issues/warnings/__init__.py +1 -1
  219. cognite/neat/{core → _v0/core}/_issues/warnings/_external.py +1 -1
  220. cognite/neat/{core → _v0/core}/_issues/warnings/_general.py +1 -1
  221. cognite/neat/{core → _v0/core}/_issues/warnings/_models.py +2 -2
  222. cognite/neat/{core → _v0/core}/_issues/warnings/_properties.py +2 -2
  223. cognite/neat/{core → _v0/core}/_issues/warnings/_resources.py +1 -1
  224. cognite/neat/{core → _v0/core}/_issues/warnings/user_modeling.py +1 -1
  225. cognite/neat/{core → _v0/core}/_store/_data_model.py +12 -12
  226. cognite/neat/{core → _v0/core}/_store/_instance.py +43 -10
  227. cognite/neat/{core → _v0/core}/_store/_provenance.py +3 -3
  228. cognite/neat/{core → _v0/core}/_store/exceptions.py +4 -4
  229. cognite/neat/_v0/core/_utils/__init__.py +0 -0
  230. cognite/neat/{core → _v0/core}/_utils/auth.py +22 -12
  231. cognite/neat/{core → _v0/core}/_utils/auxiliary.py +1 -1
  232. cognite/neat/{core → _v0/core}/_utils/collection_.py +2 -2
  233. cognite/neat/{core → _v0/core}/_utils/graph_transformations_report.py +1 -1
  234. cognite/neat/{core → _v0/core}/_utils/rdf_.py +1 -1
  235. cognite/neat/{core → _v0/core}/_utils/reader/_base.py +1 -1
  236. cognite/neat/{core → _v0/core}/_utils/spreadsheet.py +18 -4
  237. cognite/neat/{core → _v0/core}/_utils/text.py +1 -1
  238. cognite/neat/{core → _v0/core}/_utils/upload.py +3 -3
  239. cognite/neat/{session → _v0}/engine/_load.py +1 -1
  240. cognite/neat/_v0/plugins/__init__.py +4 -0
  241. cognite/neat/_v0/plugins/_base.py +9 -0
  242. cognite/neat/_v0/plugins/_data_model.py +48 -0
  243. cognite/neat/{plugins → _v0/plugins}/_issues.py +1 -1
  244. cognite/neat/{plugins → _v0/plugins}/_manager.py +7 -16
  245. cognite/neat/{session → _v0/session}/_base.py +13 -15
  246. cognite/neat/{session → _v0/session}/_collector.py +1 -1
  247. cognite/neat/_v0/session/_diff.py +51 -0
  248. cognite/neat/{session → _v0/session}/_drop.py +3 -3
  249. cognite/neat/{session → _v0/session}/_explore.py +2 -2
  250. cognite/neat/{session → _v0/session}/_fix.py +2 -2
  251. cognite/neat/{session → _v0/session}/_inspect.py +3 -3
  252. cognite/neat/{session → _v0/session}/_mapping.py +3 -3
  253. cognite/neat/{session → _v0/session}/_plugin.py +4 -5
  254. cognite/neat/{session → _v0/session}/_prepare.py +8 -8
  255. cognite/neat/{session → _v0/session}/_read.py +34 -21
  256. cognite/neat/{session → _v0/session}/_set.py +8 -8
  257. cognite/neat/{session → _v0/session}/_show.py +5 -5
  258. cognite/neat/{session → _v0/session}/_state.py +10 -10
  259. cognite/neat/{session → _v0/session}/_subset.py +4 -4
  260. cognite/neat/{session → _v0/session}/_template.py +11 -11
  261. cognite/neat/{session → _v0/session}/_to.py +12 -12
  262. cognite/neat/{session → _v0/session}/_wizard.py +1 -1
  263. cognite/neat/{session → _v0/session}/exceptions.py +5 -5
  264. cognite/neat/_version.py +1 -1
  265. cognite/neat/legacy.py +6 -0
  266. cognite_neat-1.0.22.dist-info/METADATA +123 -0
  267. cognite_neat-1.0.22.dist-info/RECORD +329 -0
  268. cognite_neat-1.0.22.dist-info/WHEEL +4 -0
  269. cognite/neat/core/_data_model/models/entities/_loaders.py +0 -75
  270. cognite/neat/plugins/__init__.py +0 -3
  271. cognite/neat/plugins/data_model/importers/__init__.py +0 -5
  272. cognite/neat/plugins/data_model/importers/_base.py +0 -28
  273. cognite/neat/session/_session/_data_model/__init__.py +0 -3
  274. cognite/neat/session/_session/_data_model/_read.py +0 -193
  275. cognite/neat/session/_session/_data_model/_routes.py +0 -45
  276. cognite/neat/session/_session/_data_model/_show.py +0 -147
  277. cognite/neat/session/_session/_data_model/_write.py +0 -335
  278. cognite_neat-0.123.26.dist-info/METADATA +0 -144
  279. cognite_neat-0.123.26.dist-info/RECORD +0 -201
  280. cognite_neat-0.123.26.dist-info/WHEEL +0 -4
  281. cognite_neat-0.123.26.dist-info/licenses/LICENSE +0 -201
  282. /cognite/neat/{core → _client/init}/__init__.py +0 -0
  283. /cognite/neat/{core/_client/_api → _data_model}/__init__.py +0 -0
  284. /cognite/neat/{core/_client/data_classes → _data_model/deployer}/__init__.py +0 -0
  285. /cognite/neat/{core/_data_model → _data_model/exporters/_table_exporter}/__init__.py +0 -0
  286. /cognite/neat/{core/_instances → _data_model/importers/_table_importer}/__init__.py +0 -0
  287. /cognite/neat/{core/_instances/extractors/_classic_cdf → _data_model/models}/__init__.py +0 -0
  288. /cognite/neat/{core/_utils → _data_model/models/conceptual}/__init__.py +0 -0
  289. /cognite/neat/{plugins/data_model → _data_model/validation}/__init__.py +0 -0
  290. /cognite/neat/{session/_session → _session/_html}/__init__.py +0 -0
  291. /cognite/neat/{core → _v0/core}/_client/__init__.py +0 -0
  292. /cognite/neat/{core → _v0/core}/_client/data_classes/data_modeling.py +0 -0
  293. /cognite/neat/{core → _v0/core}/_client/data_classes/neat_sequence.py +0 -0
  294. /cognite/neat/{core → _v0/core}/_client/data_classes/statistics.py +0 -0
  295. /cognite/neat/{core → _v0/core}/_config.py +0 -0
  296. /cognite/neat/{core → _v0/core}/_data_model/analysis/__init__.py +0 -0
  297. /cognite/neat/{core → _v0/core}/_data_model/catalog/__init__.py +0 -0
  298. /cognite/neat/{core → _v0/core}/_data_model/catalog/classic_model.xlsx +0 -0
  299. /cognite/neat/{core → _v0/core}/_data_model/catalog/conceptual-imf-data-model.xlsx +0 -0
  300. /cognite/neat/{core → _v0/core}/_data_model/catalog/hello_world_pump.xlsx +0 -0
  301. /cognite/neat/{core → _v0/core}/_data_model/importers/__init__.py +0 -0
  302. /cognite/neat/{core → _v0/core}/_data_model/importers/_rdf/__init__.py +0 -0
  303. /cognite/neat/{core → _v0/core}/_data_model/models/_base_unverified.py +0 -0
  304. /cognite/neat/{core → _v0/core}/_data_model/models/conceptual/__init__.py +0 -0
  305. /cognite/neat/{core → _v0/core}/_data_model/models/entities/_constants.py +0 -0
  306. /cognite/neat/{core → _v0/core}/_data_model/models/entities/_wrapped.py +0 -0
  307. /cognite/neat/{core → _v0/core}/_data_model/models/mapping/__init__.py +0 -0
  308. /cognite/neat/{core → _v0/core}/_data_model/models/mapping/_classic2core.yaml +0 -0
  309. /cognite/neat/{core → _v0/core}/_data_model/transformers/__init__.py +0 -0
  310. /cognite/neat/{core → _v0/core}/_instances/_shared.py +0 -0
  311. /cognite/neat/{core → _v0/core}/_instances/_tracking/__init__.py +0 -0
  312. /cognite/neat/{core → _v0/core}/_instances/examples/Knowledge-Graph-Nordic44-dirty.xml +0 -0
  313. /cognite/neat/{core → _v0/core}/_instances/examples/Knowledge-Graph-Nordic44.xml +0 -0
  314. /cognite/neat/{core → _v0/core}/_instances/examples/__init__.py +0 -0
  315. /cognite/neat/{core → _v0/core}/_instances/examples/skos-capturing-sheet-wind-topics.xlsx +0 -0
  316. /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_assets.py +0 -0
  317. /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_data_sets.py +0 -0
  318. /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_events.py +0 -0
  319. /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_files.py +0 -0
  320. /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_labels.py +0 -0
  321. /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_timeseries.py +0 -0
  322. /cognite/neat/{core → _v0/core}/_instances/loaders/__init__.py +0 -0
  323. /cognite/neat/{core → _v0/core}/_instances/queries/__init__.py +0 -0
  324. /cognite/neat/{core → _v0/core}/_instances/queries/_base.py +0 -0
  325. /cognite/neat/{core → _v0/core}/_instances/queries/_queries.py +0 -0
  326. /cognite/neat/{core → _v0/core}/_instances/transformers/__init__.py +0 -0
  327. /cognite/neat/{core → _v0/core}/_issues/__init__.py +0 -0
  328. /cognite/neat/{core → _v0/core}/_issues/formatters.py +0 -0
  329. /cognite/neat/{core → _v0/core}/_shared.py +0 -0
  330. /cognite/neat/{core → _v0/core}/_store/__init__.py +0 -0
  331. /cognite/neat/{core → _v0/core}/_utils/io_.py +0 -0
  332. /cognite/neat/{core → _v0/core}/_utils/reader/__init__.py +0 -0
  333. /cognite/neat/{core → _v0/core}/_utils/tarjan.py +0 -0
  334. /cognite/neat/{core → _v0/core}/_utils/time_.py +0 -0
  335. /cognite/neat/{core → _v0/core}/_utils/xml_.py +0 -0
  336. /cognite/neat/{session → _v0}/engine/__init__.py +0 -0
  337. /cognite/neat/{session → _v0}/engine/_import.py +0 -0
  338. /cognite/neat/{session → _v0}/engine/_interface.py +0 -0
  339. /cognite/neat/{session → _v0/session}/__init__.py +0 -0
  340. /cognite/neat/{session → _v0/session}/_experimental.py +0 -0
  341. /cognite/neat/{session → _v0/session}/_state/README.md +0 -0
@@ -0,0 +1,681 @@
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
+ errors: list[ConsistencyError] = []
38
+
39
+ for (view, property_), value_type in self.validation_resources.connection_end_node_types.items():
40
+ if value_type is None:
41
+ continue
42
+
43
+ if self.validation_resources.select_view(value_type) is not None:
44
+ continue
45
+
46
+ errors.append(
47
+ ConsistencyError(
48
+ message=(
49
+ f"View {view!s} connection {property_!s} has value type {value_type!s} "
50
+ "which is not defined as a view in the data model neither exists in CDF."
51
+ ),
52
+ fix="Define necessary view",
53
+ code=self.code,
54
+ )
55
+ )
56
+
57
+ return errors
58
+
59
+
60
+ class ConnectionValueTypeUndefined(DataModelValidator):
61
+ """Validates that connection value types are not None, i.e. undefined.
62
+
63
+ ## What it does
64
+ Validates that connections have explicitly defined value types (i.e., end connection node type).
65
+
66
+ ## Why is this bad?
67
+ If a connection value type is None (undefined), there is no type information about the end node of the connection.
68
+ This yields an ambiguous data model definition, which may lead to issues during consumption of data from CDF.
69
+
70
+ ## Example
71
+ Consider a scenario where we have views WindTurbine,ArrayCable and Substation. Lets say WindTurbine has a connection
72
+ `connectsTo` with value type None (undefined), then it is unclear what type of view the connection points to as
73
+ both ArrayCable and Substation are valid targets for the connection.
74
+
75
+ """
76
+
77
+ code = f"{BASE_CODE}-002"
78
+ issue_type = Recommendation
79
+
80
+ def run(self) -> list[Recommendation]:
81
+ recommendations: list[Recommendation] = []
82
+
83
+ for (view, property_), value_type in self.validation_resources.connection_end_node_types.items():
84
+ if not value_type:
85
+ recommendations.append(
86
+ Recommendation(
87
+ message=(
88
+ f"View {view!s} connection {property_!s} is missing value type (end node type)."
89
+ " This yields ambiguous data model definition."
90
+ ),
91
+ fix="Define necessary value type",
92
+ code=self.code,
93
+ )
94
+ )
95
+
96
+ return recommendations
97
+
98
+
99
+ @dataclass
100
+ class ReverseConnectionContext:
101
+ """Context for validating a bidirectional connection.
102
+ This context holds all necessary references to validate the connection.
103
+
104
+ Attributes:
105
+ target_view_ref: Reference to the target view containing the reverse property.
106
+ reverse_property: Identifier of the reverse property in the target view.
107
+ through: Direct reference defining the direct property used in the reverse connection.
108
+ source_view_ref: Reference to the source view containing the direct property, to which the reverse points.
109
+
110
+
111
+ Example:
112
+ View `WindTurbine` has property `windFarm` (direct) → points to View `WindFarm`
113
+ View `WindFarm` has property `turbines` (reverse) → points to View `WindTurbine` through `WindTurbine.windFarm`
114
+ """
115
+
116
+ target_view_ref: ViewReference
117
+ reverse_property: str
118
+ through: ViewDirectReference
119
+ source_view_ref: ViewReference
120
+
121
+
122
+ def _normalize_through_reference(
123
+ source_view_ref: ViewReference, through: ContainerDirectReference | ViewDirectReference
124
+ ) -> ViewDirectReference:
125
+ """Normalize through reference to ViewDirectReference for consistent processing."""
126
+ if isinstance(through, ContainerDirectReference):
127
+ return ViewDirectReference(source=source_view_ref, identifier=through.identifier)
128
+ return through
129
+
130
+
131
+ class ReverseConnectionSourceViewMissing(DataModelValidator):
132
+ """Validates that source view referenced in reverse connection exist.
133
+
134
+ ## What it does
135
+ Checks that the source view used to configure a reverse connection exists.
136
+
137
+ ## Why is this bad?
138
+ A reverse connection requires a corresponding direct connection in the source view.
139
+ If the source view doesn't exist, the reverse connection is invalid.
140
+
141
+ ## Example
142
+ If WindFarm has a reverse property `turbines` through `WindTurbine.windFarm`,
143
+ but WindTurbine view doesn't exist, the reverse connection cannot function.
144
+ """
145
+
146
+ code = f"{BASE_CODE}-REVERSE-001"
147
+ issue_type = ConsistencyError
148
+
149
+ def run(self) -> list[ConsistencyError]:
150
+ errors: list[ConsistencyError] = []
151
+
152
+ for (target_view_ref, reverse_prop_name), (
153
+ source_view_ref,
154
+ through,
155
+ ) in self.validation_resources.reverse_to_direct_mapping.items():
156
+ through = _normalize_through_reference(source_view_ref, through)
157
+ source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
158
+
159
+ if not source_view:
160
+ errors.append(
161
+ ConsistencyError(
162
+ message=(
163
+ f"Source view {source_view_ref!s} used to configure reverse connection "
164
+ f"'{reverse_prop_name}' in target view {target_view_ref!s} "
165
+ "does not exist in the data model or CDF."
166
+ ),
167
+ fix="Define the missing source view",
168
+ code=self.code,
169
+ )
170
+ )
171
+
172
+ return errors
173
+
174
+
175
+ class ReverseConnectionSourcePropertyMissing(DataModelValidator):
176
+ """Validates that source property referenced in reverse connections exist.
177
+
178
+ ## What it does
179
+ Checks that the direct connection property in the source view (used in the reverse connection's 'through')
180
+ actually exists in the source view.
181
+
182
+ ## Why is this bad?
183
+ A reverse connection requires a corresponding direct connection property in the source view.
184
+ If this property doesn't exist, the bidirectional connection is incomplete.
185
+
186
+ ## Example
187
+ If WindFarm has a reverse property `turbines` through `WindTurbine.windFarm`,
188
+ but WindTurbine view doesn't have a `windFarm` property, the reverse connection is invalid.
189
+ """
190
+
191
+ code = f"{BASE_CODE}-REVERSE-002"
192
+ issue_type = ConsistencyError
193
+
194
+ def run(self) -> list[ConsistencyError]:
195
+ errors: list[ConsistencyError] = []
196
+
197
+ for (target_view_ref, reverse_prop_name), (
198
+ source_view_ref,
199
+ through,
200
+ ) in self.validation_resources.reverse_to_direct_mapping.items():
201
+ through = _normalize_through_reference(source_view_ref, through)
202
+ source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
203
+
204
+ if not source_view:
205
+ continue # Handled by ReverseConnectionSourceViewMissing
206
+
207
+ # critical to expand view properties to include inherited ones as otherwise we might miss the property
208
+ if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
209
+ raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
210
+
211
+ if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
212
+ errors.append(
213
+ ConsistencyError(
214
+ message=(
215
+ f"Source view {source_view_ref!s} is missing property '{through.identifier}' "
216
+ f"which is required to configure the reverse connection "
217
+ f"'{reverse_prop_name}' in target view {target_view_ref!s}."
218
+ ),
219
+ fix="Add the missing property to the source view",
220
+ code=self.code,
221
+ )
222
+ )
223
+
224
+ return errors
225
+
226
+
227
+ class ReverseConnectionSourcePropertyWrongType(DataModelValidator):
228
+ """Validates that source property for the reverse connections is a direct relation.
229
+
230
+ ## What it does
231
+ Checks that the property referenced in a reverse connection's 'through' clause
232
+ is actually a direct connection property (not a primitive or other type).
233
+
234
+ ## Why is this bad?
235
+ Reverse connections can only work with direct connection properties.
236
+ Using other property types breaks the bidirectional relationship.
237
+
238
+ ## Example
239
+ If WindFarm has a reverse property `turbines` through `WindTurbine.name`,
240
+ but `name` is a Text property (not a direct connection), the reverse connection is invalid.
241
+ """
242
+
243
+ code = f"{BASE_CODE}-REVERSE-003"
244
+ issue_type = ConsistencyError
245
+
246
+ def run(self) -> list[ConsistencyError]:
247
+ errors: list[ConsistencyError] = []
248
+
249
+ for (target_view_ref, reverse_prop_name), (
250
+ source_view_ref,
251
+ through,
252
+ ) in self.validation_resources.reverse_to_direct_mapping.items():
253
+ through = _normalize_through_reference(source_view_ref, through)
254
+ source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
255
+
256
+ if not source_view:
257
+ continue # Handled by ReverseConnectionSourceViewMissing
258
+
259
+ if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
260
+ raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
261
+
262
+ if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
263
+ continue # Handled by ReverseConnectionSourcePropertyMissing
264
+
265
+ source_property = source_view_expanded.properties[through.identifier]
266
+
267
+ if not isinstance(source_property, ViewCorePropertyRequest):
268
+ errors.append(
269
+ ConsistencyError(
270
+ message=(
271
+ f"Source view {source_view_ref!s} property '{through.identifier}' "
272
+ f"used for configuring the reverse connection '{reverse_prop_name}' "
273
+ f"in target view {target_view_ref!s} is not a direct connection property."
274
+ ),
275
+ fix="Update view property to be a direct connection property",
276
+ code=self.code,
277
+ )
278
+ )
279
+
280
+ return errors
281
+
282
+
283
+ class ReverseConnectionContainerMissing(DataModelValidator):
284
+ """Validates that the container referenced by the reverse connection source properties exist.
285
+
286
+ ## What it does
287
+ Checks that the container holding the direct connection property (used in reverse connection) exists.
288
+
289
+ ## Why is this bad?
290
+ The direct connection property must be stored in a container.
291
+ If the container doesn't exist, the connection cannot be persisted.
292
+
293
+ ## Example
294
+ If WindTurbine.windFarm maps to container `WindTurbine`, but this container doesn't exist,
295
+ the reverse connection from WindFarm cannot function.
296
+ """
297
+
298
+ code = f"{BASE_CODE}-REVERSE-004"
299
+ issue_type = ConsistencyError
300
+
301
+ def run(self) -> list[ConsistencyError]:
302
+ errors: list[ConsistencyError] = []
303
+
304
+ for (target_view_ref, reverse_prop_name), (
305
+ source_view_ref,
306
+ through,
307
+ ) in self.validation_resources.reverse_to_direct_mapping.items():
308
+ through = _normalize_through_reference(source_view_ref, through)
309
+ source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
310
+
311
+ if not source_view:
312
+ continue # Handled by ReverseConnectionSourceViewMissing
313
+
314
+ if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
315
+ raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
316
+
317
+ if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
318
+ continue # Handled by ReverseConnectionSourcePropertyMissing
319
+
320
+ source_property = source_view_expanded.properties[through.identifier]
321
+
322
+ if not isinstance(source_property, ViewCorePropertyRequest):
323
+ continue # Handled by ReverseConnectionSourcePropertyWrongType
324
+
325
+ container_ref = source_property.container
326
+ container_property_id = source_property.container_property_identifier
327
+
328
+ source_container = self.validation_resources.select_container(container_ref, container_property_id)
329
+ if not source_container:
330
+ errors.append(
331
+ ConsistencyError(
332
+ message=(
333
+ f"Container {container_ref!s} is missing in both the data model and CDF. "
334
+ f"This container is required by view {source_view_ref!s}"
335
+ f" property '{through.identifier}', "
336
+ f"which configures the reverse connection '{reverse_prop_name}'"
337
+ f" in target view {target_view_ref!s}."
338
+ ),
339
+ fix="Define the missing container",
340
+ code=self.code,
341
+ )
342
+ )
343
+
344
+ return errors
345
+
346
+
347
+ class ReverseConnectionContainerPropertyMissing(DataModelValidator):
348
+ """Validates that container property referenced by the reverse connections exists.
349
+
350
+ ## What it does
351
+ Checks that the property in the container (mapped from the view's direct connection property)
352
+ actually exists in the container.
353
+
354
+ ## Why is this bad?
355
+ The view property must map to an actual container property for data persistence.
356
+ If the container property doesn't exist, data cannot be stored.
357
+
358
+ ## Example
359
+ If WindTurbine.windFarm maps to container property `WindTurbine.windFarm`,
360
+ but this container property doesn't exist, the connection cannot be stored.
361
+ """
362
+
363
+ code = f"{BASE_CODE}-REVERSE-005"
364
+ issue_type = ConsistencyError
365
+
366
+ def run(self) -> list[ConsistencyError]:
367
+ errors: list[ConsistencyError] = []
368
+
369
+ for (target_view_ref, reverse_prop_name), (
370
+ source_view_ref,
371
+ through,
372
+ ) in self.validation_resources.reverse_to_direct_mapping.items():
373
+ through = _normalize_through_reference(source_view_ref, through)
374
+ source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
375
+
376
+ if not source_view:
377
+ continue # Handled by ReverseConnectionSourceViewMissing
378
+
379
+ if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
380
+ raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
381
+
382
+ if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
383
+ continue # Handled by ReverseConnectionSourcePropertyMissing
384
+
385
+ source_property = source_view_expanded.properties[through.identifier]
386
+
387
+ if not isinstance(source_property, ViewCorePropertyRequest):
388
+ continue # Handled by ReverseConnectionSourcePropertyWrongType
389
+
390
+ container_ref = source_property.container
391
+ container_property_id = source_property.container_property_identifier
392
+
393
+ source_container = self.validation_resources.select_container(container_ref, container_property_id)
394
+ if not source_container:
395
+ continue # Handled by ReverseConnectionContainerMissing
396
+
397
+ if not source_container.properties or container_property_id not in source_container.properties:
398
+ errors.append(
399
+ ConsistencyError(
400
+ message=(
401
+ f"Container {container_ref!s} is missing property '{container_property_id}'. "
402
+ f"This property is required by the source view {source_view_ref!s}"
403
+ f" property '{through.identifier}', "
404
+ f"which configures the reverse connection '{reverse_prop_name}' "
405
+ f"in target view {target_view_ref!s}."
406
+ ),
407
+ fix="Add the missing property to the container",
408
+ code=self.code,
409
+ )
410
+ )
411
+
412
+ return errors
413
+
414
+
415
+ class ReverseConnectionContainerPropertyWrongType(DataModelValidator):
416
+ """Validates that the container property used in reverse connection is the direct relations.
417
+
418
+ ## What it does
419
+ Checks that the container property (mapped from view's direct connection property)
420
+ has type DirectNodeRelation.
421
+
422
+ ## Why is this bad?
423
+ Container properties backing connection view properties must be DirectNodeRelation type.
424
+ Other types cannot represent connections in the underlying storage.
425
+
426
+ ## Example
427
+ If WindTurbine.windFarm maps to container property with type Text instead of DirectNodeRelation,
428
+ the connection cannot be stored correctly.
429
+ """
430
+
431
+ code = f"{BASE_CODE}-REVERSE-006"
432
+ issue_type = ConsistencyError
433
+
434
+ def run(self) -> list[ConsistencyError]:
435
+ errors: list[ConsistencyError] = []
436
+
437
+ for (target_view_ref, reverse_prop_name), (
438
+ source_view_ref,
439
+ through,
440
+ ) in self.validation_resources.reverse_to_direct_mapping.items():
441
+ through = _normalize_through_reference(source_view_ref, through)
442
+ source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
443
+
444
+ if not source_view:
445
+ continue # Handled by ReverseConnectionSourceViewMissing
446
+
447
+ if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
448
+ raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
449
+
450
+ if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
451
+ continue # Handled by ReverseConnectionSourcePropertyMissing
452
+
453
+ source_property = source_view_expanded.properties[through.identifier]
454
+
455
+ if not isinstance(source_property, ViewCorePropertyRequest):
456
+ continue # Handled by ReverseConnectionSourcePropertyWrongType
457
+
458
+ container_ref = source_property.container
459
+ container_property_id = source_property.container_property_identifier
460
+
461
+ source_container = self.validation_resources.select_container(container_ref, container_property_id)
462
+ if not source_container or not source_container.properties:
463
+ continue # Handled by other validators
464
+
465
+ container_property = source_container.properties.get(container_property_id)
466
+ if not container_property:
467
+ continue # Handled by ReverseConnectionContainerPropertyMissing
468
+
469
+ if not isinstance(container_property.type, DirectNodeRelation):
470
+ errors.append(
471
+ ConsistencyError(
472
+ message=(
473
+ f"Container property '{container_property_id}' in container {container_ref!s} "
474
+ f"must be a direct connection, but found type '{container_property.type!s}'. "
475
+ f"This property is used by source view {source_view_ref!s} property '{through.identifier}' "
476
+ f"to configure reverse connection '{reverse_prop_name}' in target view {target_view_ref!s}."
477
+ ),
478
+ fix="Change container property type to be a direct connection",
479
+ code=self.code,
480
+ )
481
+ )
482
+
483
+ return errors
484
+
485
+
486
+ class ReverseConnectionTargetMissing(DataModelValidator):
487
+ """Validates that the direct connection in reverse connection pair have target views specified.
488
+
489
+ ## What it does
490
+ Checks whether the direct connection property (referenced by reverse connection) has a value type.
491
+
492
+ ## Why is this bad?
493
+ While CDF allows value type None as a SEARCH hack for multi-value relations,
494
+ it's better to explicitly specify the target view for clarity and maintainability.
495
+
496
+ ## Example
497
+ If WindTurbine.windFarm has value type None instead of WindFarm,
498
+ this validator recommends specifying WindFarm explicitly.
499
+ """
500
+
501
+ code = f"{BASE_CODE}-REVERSE-007"
502
+ issue_type = Recommendation
503
+
504
+ def run(self) -> list[Recommendation]:
505
+ recommendations: list[Recommendation] = []
506
+
507
+ for (target_view_ref, reverse_prop_name), (
508
+ source_view_ref,
509
+ through,
510
+ ) in self.validation_resources.reverse_to_direct_mapping.items():
511
+ through = _normalize_through_reference(source_view_ref, through)
512
+ source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
513
+
514
+ if not source_view:
515
+ continue # Handled by ReverseConnectionSourceViewMissing
516
+
517
+ if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
518
+ raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
519
+
520
+ if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
521
+ continue # Handled by ReverseConnectionSourcePropertyMissing
522
+
523
+ source_property = source_view_expanded.properties[through.identifier]
524
+
525
+ if not isinstance(source_property, ViewCorePropertyRequest):
526
+ continue # Handled by ReverseConnectionSourcePropertyWrongType
527
+
528
+ actual_target_view = source_property.source
529
+
530
+ if not actual_target_view:
531
+ recommendations.append(
532
+ Recommendation(
533
+ message=(
534
+ f"Source view {source_view_ref!s} property '{through.identifier}' "
535
+ f"has no target view specified (value type is None). "
536
+ f"This property is used for reverse connection '{reverse_prop_name}' "
537
+ f"in target view {target_view_ref!s}. "
538
+ f"While this works as a hack for multi-value relations in CDF Search, "
539
+ f"it's recommended to explicitly define the target view as {target_view_ref!s}."
540
+ ),
541
+ fix="Set the property's value type to the target view for better clarity",
542
+ code=self.code,
543
+ )
544
+ )
545
+
546
+ return recommendations
547
+
548
+
549
+ class ReverseConnectionPointsToAncestor(DataModelValidator):
550
+ """Validates that direct connections point to specific views rather than ancestors.
551
+
552
+ ## What it does
553
+ Checks whether the direct connection property points to an ancestor of the expected target view
554
+ and recommends pointing to the specific target instead.
555
+
556
+ ## Why is this bad?
557
+ While technically valid, pointing to ancestors can be confusing and may lead to mistakes.
558
+ It's clearer to point to the specific target view.
559
+
560
+ ## Example
561
+ If WindFarm.turbines expects WindTurbine.windFarm to point to WindFarm,
562
+ but it points to Asset (ancestor of WindFarm), this validator recommends the change.
563
+ """
564
+
565
+ code = f"{BASE_CODE}-REVERSE-008"
566
+ issue_type = Recommendation
567
+
568
+ def run(self) -> list[Recommendation]:
569
+ recommendations: list[Recommendation] = []
570
+
571
+ for (target_view_ref, reverse_prop_name), (
572
+ source_view_ref,
573
+ through,
574
+ ) in self.validation_resources.reverse_to_direct_mapping.items():
575
+ through = _normalize_through_reference(source_view_ref, through)
576
+ source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
577
+
578
+ if not source_view:
579
+ continue # Handled by ReverseConnectionSourceViewMissing
580
+
581
+ if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
582
+ raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
583
+
584
+ if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
585
+ continue # Handled by ReverseConnectionSourcePropertyMissing
586
+
587
+ source_property = source_view_expanded.properties[through.identifier]
588
+
589
+ if not isinstance(source_property, ViewCorePropertyRequest):
590
+ continue # Handled by other validators
591
+
592
+ actual_target_view = source_property.source
593
+
594
+ if not actual_target_view:
595
+ continue # Handled by ReverseConnectionTargetMissing
596
+
597
+ if self.validation_resources.is_ancestor(target_view_ref, actual_target_view):
598
+ recommendations.append(
599
+ Recommendation(
600
+ message=(
601
+ f"The direct connection property '{through.identifier}' in view {source_view_ref!s} "
602
+ f"configures the reverse connection '{reverse_prop_name}' in {target_view_ref!s}. "
603
+ f"Therefore, it is expected that '{through.identifier}' points to {target_view_ref!s}. "
604
+ f"However, it currently points to {actual_target_view!s}, which is an ancestor of "
605
+ f"{target_view_ref!s}. "
606
+ "While this will allow for model to be valid, it can be a source of confusion and mistakes."
607
+ ),
608
+ fix="Update the direct connection property to point to the target view instead of its ancestor",
609
+ code=self.code,
610
+ )
611
+ )
612
+
613
+ return recommendations
614
+
615
+
616
+ class ReverseConnectionTargetMismatch(DataModelValidator):
617
+ """Validates that direct connections point to the correct target views.
618
+
619
+ ## What it does
620
+ Checks that the direct connection property points to the expected target view
621
+ (the view containing the reverse connection).
622
+
623
+ ## Why is this bad?
624
+ The reverse connection expects a bidirectional relationship.
625
+ If the direct connection points to a different view, the relationship is broken.
626
+
627
+ ## Example
628
+ If WindFarm.turbines is a reverse through WindTurbine.windFarm,
629
+ but WindTurbine.windFarm points to SolarFarm instead of WindFarm, the connection is invalid.
630
+ """
631
+
632
+ code = f"{BASE_CODE}-REVERSE-009"
633
+ issue_type = Recommendation
634
+
635
+ def run(self) -> list[Recommendation]:
636
+ recommendations: list[Recommendation] = []
637
+
638
+ for (target_view_ref, reverse_prop_name), (
639
+ source_view_ref,
640
+ through,
641
+ ) in self.validation_resources.reverse_to_direct_mapping.items():
642
+ through = _normalize_through_reference(source_view_ref, through)
643
+ source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
644
+
645
+ if not source_view:
646
+ continue # Handled by ReverseConnectionSourceViewMissing
647
+
648
+ if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
649
+ raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
650
+
651
+ if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
652
+ continue # Handled by ReverseConnectionSourcePropertyMissing
653
+
654
+ source_property = source_view_expanded.properties[through.identifier]
655
+
656
+ if not isinstance(source_property, ViewCorePropertyRequest):
657
+ continue # Handled by other validators
658
+
659
+ actual_target_view = source_property.source
660
+
661
+ if not actual_target_view:
662
+ continue # Handled by ReverseConnectionTargetMissing
663
+
664
+ if self.validation_resources.is_ancestor(target_view_ref, actual_target_view):
665
+ continue # Handled by ReverseConnectionTargetAncestor
666
+
667
+ if actual_target_view != target_view_ref:
668
+ recommendations.append(
669
+ Recommendation(
670
+ message=(
671
+ f"The reverse connection '{reverse_prop_name}' in view {target_view_ref!s} "
672
+ f"expects its corresponding direct connection in view {source_view_ref!s} "
673
+ f"(property '{through.identifier}') to point back to {target_view_ref!s}, "
674
+ f"but it actually points to {actual_target_view!s}."
675
+ ),
676
+ fix="Update the direct connection property to point back to the correct target view",
677
+ code=self.code,
678
+ )
679
+ )
680
+
681
+ return recommendations