infrahub-server 1.6.3__py3-none-any.whl → 1.7.0__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 (250) hide show
  1. infrahub/actions/tasks.py +4 -2
  2. infrahub/api/exceptions.py +2 -2
  3. infrahub/api/schema.py +3 -1
  4. infrahub/artifacts/tasks.py +1 -0
  5. infrahub/auth.py +2 -2
  6. infrahub/cli/db.py +54 -28
  7. infrahub/computed_attribute/gather.py +3 -4
  8. infrahub/computed_attribute/tasks.py +23 -6
  9. infrahub/config.py +8 -0
  10. infrahub/constants/enums.py +12 -0
  11. infrahub/core/account.py +4 -4
  12. infrahub/core/attribute.py +106 -108
  13. infrahub/core/branch/models.py +44 -71
  14. infrahub/core/branch/tasks.py +5 -3
  15. infrahub/core/changelog/diff.py +1 -20
  16. infrahub/core/changelog/models.py +0 -7
  17. infrahub/core/constants/__init__.py +17 -0
  18. infrahub/core/constants/database.py +0 -1
  19. infrahub/core/constants/schema.py +0 -1
  20. infrahub/core/convert_object_type/repository_conversion.py +3 -4
  21. infrahub/core/diff/branch_differ.py +1 -1
  22. infrahub/core/diff/conflict_transferer.py +1 -1
  23. infrahub/core/diff/data_check_synchronizer.py +4 -3
  24. infrahub/core/diff/enricher/cardinality_one.py +2 -2
  25. infrahub/core/diff/enricher/hierarchy.py +1 -1
  26. infrahub/core/diff/enricher/labels.py +1 -1
  27. infrahub/core/diff/merger/merger.py +28 -2
  28. infrahub/core/diff/merger/serializer.py +3 -10
  29. infrahub/core/diff/model/diff.py +1 -1
  30. infrahub/core/diff/query/merge.py +376 -135
  31. infrahub/core/diff/repository/repository.py +3 -1
  32. infrahub/core/graph/__init__.py +1 -1
  33. infrahub/core/graph/constraints.py +3 -3
  34. infrahub/core/graph/schema.py +2 -12
  35. infrahub/core/ipam/reconciler.py +8 -6
  36. infrahub/core/ipam/utilization.py +8 -15
  37. infrahub/core/manager.py +133 -152
  38. infrahub/core/merge.py +1 -1
  39. infrahub/core/metadata/__init__.py +0 -0
  40. infrahub/core/metadata/interface.py +37 -0
  41. infrahub/core/metadata/model.py +31 -0
  42. infrahub/core/metadata/query/__init__.py +0 -0
  43. infrahub/core/metadata/query/node_metadata.py +301 -0
  44. infrahub/core/migrations/graph/__init__.py +4 -0
  45. infrahub/core/migrations/graph/m012_convert_account_generic.py +12 -12
  46. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +7 -12
  47. infrahub/core/migrations/graph/m017_add_core_profile.py +5 -2
  48. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +2 -1
  49. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +0 -10
  50. infrahub/core/migrations/graph/m020_duplicate_edges.py +0 -8
  51. infrahub/core/migrations/graph/m025_uniqueness_nulls.py +2 -1
  52. infrahub/core/migrations/graph/m026_0000_prefix_fix.py +2 -1
  53. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +0 -1
  54. infrahub/core/migrations/graph/m031_check_number_attributes.py +2 -2
  55. infrahub/core/migrations/graph/m038_redo_0000_prefix_fix.py +2 -1
  56. infrahub/core/migrations/graph/m041_deleted_dup_edges.py +1 -1
  57. infrahub/core/migrations/graph/m049_remove_is_visible_relationship.py +53 -0
  58. infrahub/core/migrations/graph/m050_backfill_vertex_metadata.py +168 -0
  59. infrahub/core/migrations/query/__init__.py +2 -2
  60. infrahub/core/migrations/query/attribute_add.py +17 -6
  61. infrahub/core/migrations/query/attribute_remove.py +19 -5
  62. infrahub/core/migrations/query/attribute_rename.py +21 -5
  63. infrahub/core/migrations/query/node_duplicate.py +19 -4
  64. infrahub/core/migrations/query/schema_attribute_update.py +1 -1
  65. infrahub/core/migrations/schema/attribute_kind_update.py +21 -2
  66. infrahub/core/migrations/schema/attribute_name_update.py +1 -1
  67. infrahub/core/migrations/schema/attribute_supports_profile.py +5 -3
  68. infrahub/core/migrations/schema/models.py +3 -0
  69. infrahub/core/migrations/schema/node_attribute_add.py +5 -2
  70. infrahub/core/migrations/schema/node_attribute_remove.py +1 -1
  71. infrahub/core/migrations/schema/node_kind_update.py +1 -1
  72. infrahub/core/migrations/schema/node_remove.py +24 -2
  73. infrahub/core/migrations/schema/tasks.py +4 -1
  74. infrahub/core/migrations/shared.py +13 -6
  75. infrahub/core/models.py +6 -6
  76. infrahub/core/node/__init__.py +157 -58
  77. infrahub/core/node/base.py +9 -5
  78. infrahub/core/node/create.py +7 -3
  79. infrahub/core/node/delete_validator.py +1 -1
  80. infrahub/core/node/standard.py +100 -14
  81. infrahub/core/order.py +30 -0
  82. infrahub/core/property.py +0 -1
  83. infrahub/core/protocols.py +1 -0
  84. infrahub/core/protocols_base.py +10 -2
  85. infrahub/core/query/__init__.py +5 -3
  86. infrahub/core/query/attribute.py +164 -49
  87. infrahub/core/query/branch.py +58 -70
  88. infrahub/core/query/delete.py +1 -1
  89. infrahub/core/query/diff.py +7 -7
  90. infrahub/core/query/ipam.py +104 -43
  91. infrahub/core/query/node.py +1072 -281
  92. infrahub/core/query/relationship.py +531 -325
  93. infrahub/core/query/resource_manager.py +107 -18
  94. infrahub/core/query/standard_node.py +25 -5
  95. infrahub/core/query/utils.py +2 -4
  96. infrahub/core/relationship/constraints/count.py +1 -1
  97. infrahub/core/relationship/constraints/peer_kind.py +1 -1
  98. infrahub/core/relationship/constraints/peer_parent.py +1 -1
  99. infrahub/core/relationship/constraints/peer_relatives.py +1 -1
  100. infrahub/core/relationship/constraints/profiles_kind.py +1 -1
  101. infrahub/core/relationship/constraints/profiles_removal.py +168 -0
  102. infrahub/core/relationship/model.py +293 -139
  103. infrahub/core/schema/attribute_schema.py +2 -2
  104. infrahub/core/schema/basenode_schema.py +3 -0
  105. infrahub/core/schema/definitions/core/__init__.py +8 -2
  106. infrahub/core/schema/definitions/core/account.py +10 -10
  107. infrahub/core/schema/definitions/core/artifact.py +14 -8
  108. infrahub/core/schema/definitions/core/check.py +10 -4
  109. infrahub/core/schema/definitions/core/generator.py +26 -6
  110. infrahub/core/schema/definitions/core/graphql_query.py +1 -1
  111. infrahub/core/schema/definitions/core/group.py +9 -2
  112. infrahub/core/schema/definitions/core/ipam.py +80 -10
  113. infrahub/core/schema/definitions/core/menu.py +41 -7
  114. infrahub/core/schema/definitions/core/permission.py +16 -2
  115. infrahub/core/schema/definitions/core/profile.py +16 -2
  116. infrahub/core/schema/definitions/core/propose_change.py +24 -4
  117. infrahub/core/schema/definitions/core/propose_change_comment.py +23 -11
  118. infrahub/core/schema/definitions/core/propose_change_validator.py +50 -21
  119. infrahub/core/schema/definitions/core/repository.py +10 -0
  120. infrahub/core/schema/definitions/core/resource_pool.py +8 -1
  121. infrahub/core/schema/definitions/core/template.py +19 -2
  122. infrahub/core/schema/definitions/core/transform.py +11 -5
  123. infrahub/core/schema/definitions/core/webhook.py +27 -9
  124. infrahub/core/schema/manager.py +63 -43
  125. infrahub/core/schema/relationship_schema.py +6 -2
  126. infrahub/core/schema/schema_branch.py +48 -10
  127. infrahub/core/task/task.py +4 -2
  128. infrahub/core/utils.py +3 -25
  129. infrahub/core/validators/aggregated_checker.py +1 -1
  130. infrahub/core/validators/attribute/choices.py +1 -1
  131. infrahub/core/validators/attribute/enum.py +1 -1
  132. infrahub/core/validators/attribute/kind.py +1 -1
  133. infrahub/core/validators/attribute/length.py +1 -1
  134. infrahub/core/validators/attribute/min_max.py +1 -1
  135. infrahub/core/validators/attribute/number_pool.py +1 -1
  136. infrahub/core/validators/attribute/optional.py +1 -1
  137. infrahub/core/validators/attribute/regex.py +1 -1
  138. infrahub/core/validators/determiner.py +3 -3
  139. infrahub/core/validators/node/attribute.py +1 -1
  140. infrahub/core/validators/node/relationship.py +1 -1
  141. infrahub/core/validators/relationship/peer.py +1 -1
  142. infrahub/database/__init__.py +4 -4
  143. infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
  144. infrahub/dependencies/builder/constraint/relationship_manager/profiles_removal.py +8 -0
  145. infrahub/dependencies/registry.py +2 -0
  146. infrahub/display_labels/tasks.py +12 -3
  147. infrahub/git/integrator.py +18 -18
  148. infrahub/git/tasks.py +1 -1
  149. infrahub/git/utils.py +1 -1
  150. infrahub/graphql/constants.py +3 -0
  151. infrahub/graphql/context.py +1 -1
  152. infrahub/graphql/field_extractor.py +1 -1
  153. infrahub/graphql/initialization.py +11 -0
  154. infrahub/graphql/loaders/account.py +134 -0
  155. infrahub/graphql/loaders/node.py +5 -12
  156. infrahub/graphql/loaders/peers.py +5 -7
  157. infrahub/graphql/manager.py +175 -21
  158. infrahub/graphql/metadata.py +91 -0
  159. infrahub/graphql/mutations/account.py +6 -6
  160. infrahub/graphql/mutations/attribute.py +0 -2
  161. infrahub/graphql/mutations/branch.py +9 -5
  162. infrahub/graphql/mutations/computed_attribute.py +1 -1
  163. infrahub/graphql/mutations/display_label.py +1 -1
  164. infrahub/graphql/mutations/hfid.py +1 -1
  165. infrahub/graphql/mutations/ipam.py +4 -6
  166. infrahub/graphql/mutations/main.py +9 -4
  167. infrahub/graphql/mutations/profile.py +16 -22
  168. infrahub/graphql/mutations/proposed_change.py +4 -4
  169. infrahub/graphql/mutations/relationship.py +40 -10
  170. infrahub/graphql/mutations/repository.py +14 -12
  171. infrahub/graphql/mutations/schema.py +2 -2
  172. infrahub/graphql/order.py +14 -0
  173. infrahub/graphql/queries/branch.py +62 -6
  174. infrahub/graphql/queries/resource_manager.py +25 -24
  175. infrahub/graphql/resolvers/account_metadata.py +84 -0
  176. infrahub/graphql/resolvers/ipam.py +6 -8
  177. infrahub/graphql/resolvers/many_relationship.py +77 -35
  178. infrahub/graphql/resolvers/resolver.py +59 -14
  179. infrahub/graphql/resolvers/single_relationship.py +87 -23
  180. infrahub/graphql/subscription/graphql_query.py +2 -0
  181. infrahub/graphql/types/__init__.py +0 -1
  182. infrahub/graphql/types/attribute.py +10 -5
  183. infrahub/graphql/types/branch.py +40 -53
  184. infrahub/graphql/types/enums.py +3 -0
  185. infrahub/graphql/types/metadata.py +28 -0
  186. infrahub/graphql/types/node.py +22 -2
  187. infrahub/graphql/types/relationship.py +10 -2
  188. infrahub/graphql/types/standard_node.py +12 -7
  189. infrahub/hfid/tasks.py +12 -3
  190. infrahub/lock.py +7 -0
  191. infrahub/menu/repository.py +1 -1
  192. infrahub/patch/queries/base.py +1 -1
  193. infrahub/pools/number.py +1 -8
  194. infrahub/profiles/gather.py +56 -0
  195. infrahub/profiles/mandatory_fields_checker.py +116 -0
  196. infrahub/profiles/models.py +66 -0
  197. infrahub/profiles/node_applier.py +154 -13
  198. infrahub/profiles/queries/get_profile_data.py +143 -31
  199. infrahub/profiles/tasks.py +79 -27
  200. infrahub/profiles/triggers.py +22 -0
  201. infrahub/proposed_change/action_checker.py +1 -1
  202. infrahub/proposed_change/tasks.py +4 -1
  203. infrahub/services/__init__.py +1 -1
  204. infrahub/services/adapters/cache/nats.py +1 -1
  205. infrahub/services/adapters/cache/redis.py +7 -0
  206. infrahub/tasks/artifact.py +1 -0
  207. infrahub/transformations/tasks.py +2 -2
  208. infrahub/trigger/catalogue.py +2 -0
  209. infrahub/trigger/models.py +1 -0
  210. infrahub/trigger/setup.py +3 -3
  211. infrahub/trigger/tasks.py +3 -0
  212. infrahub/validators/tasks.py +1 -0
  213. infrahub/webhook/gather.py +1 -1
  214. infrahub/webhook/models.py +1 -1
  215. infrahub/webhook/tasks.py +23 -7
  216. infrahub/workers/dependencies.py +9 -3
  217. infrahub/workers/infrahub_async.py +13 -4
  218. infrahub/workflows/catalogue.py +19 -0
  219. infrahub_sdk/analyzer.py +2 -2
  220. infrahub_sdk/branch.py +12 -39
  221. infrahub_sdk/checks.py +4 -4
  222. infrahub_sdk/client.py +36 -0
  223. infrahub_sdk/ctl/cli_commands.py +2 -1
  224. infrahub_sdk/ctl/graphql.py +15 -4
  225. infrahub_sdk/ctl/utils.py +2 -2
  226. infrahub_sdk/enums.py +6 -0
  227. infrahub_sdk/graphql/renderers.py +21 -0
  228. infrahub_sdk/graphql/utils.py +85 -0
  229. infrahub_sdk/node/attribute.py +12 -2
  230. infrahub_sdk/node/constants.py +12 -0
  231. infrahub_sdk/node/metadata.py +69 -0
  232. infrahub_sdk/node/node.py +65 -14
  233. infrahub_sdk/node/property.py +3 -0
  234. infrahub_sdk/node/related_node.py +37 -5
  235. infrahub_sdk/node/relationship.py +18 -1
  236. infrahub_sdk/operation.py +2 -2
  237. infrahub_sdk/schema/repository.py +1 -2
  238. infrahub_sdk/transforms.py +2 -2
  239. infrahub_sdk/types.py +18 -2
  240. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/METADATA +17 -16
  241. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/RECORD +249 -228
  242. infrahub_testcontainers/container.py +3 -3
  243. infrahub_testcontainers/docker-compose-cluster.test.yml +7 -7
  244. infrahub_testcontainers/docker-compose.test.yml +13 -5
  245. infrahub_testcontainers/models.py +3 -3
  246. infrahub_testcontainers/performance_test.py +1 -1
  247. infrahub/graphql/models.py +0 -6
  248. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/WHEEL +0 -0
  249. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/entry_points.txt +0 -0
  250. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/licenses/LICENSE.txt +0 -0
infrahub/core/merge.py CHANGED
@@ -43,7 +43,7 @@ class BranchMerger:
43
43
  diff_locker: DiffLocker,
44
44
  destination_branch: Branch | None = None,
45
45
  workflow: InfrahubWorkflow | None = None,
46
- ):
46
+ ) -> None:
47
47
  self.source_branch = source_branch
48
48
  self.destination_branch: Branch = destination_branch or registry.get_branch_from_registry()
49
49
  self.db = db
File without changes
@@ -0,0 +1,37 @@
1
+ from abc import abstractmethod
2
+
3
+ from infrahub.core.timestamp import Timestamp
4
+
5
+
6
+ class MetadataInterface:
7
+ @abstractmethod
8
+ def _set_created_at(self, value: Timestamp | None) -> None:
9
+ raise NotImplementedError()
10
+
11
+ @abstractmethod
12
+ def _set_created_by(self, value: str | None) -> None:
13
+ raise NotImplementedError()
14
+
15
+ @abstractmethod
16
+ def _set_updated_at(self, value: Timestamp | None) -> None:
17
+ raise NotImplementedError()
18
+
19
+ @abstractmethod
20
+ def _set_updated_by(self, value: str | None) -> None:
21
+ raise NotImplementedError()
22
+
23
+ @abstractmethod
24
+ def _get_created_at(self) -> Timestamp | None:
25
+ raise NotImplementedError()
26
+
27
+ @abstractmethod
28
+ def _get_created_by(self) -> str | None:
29
+ raise NotImplementedError()
30
+
31
+ @abstractmethod
32
+ def _get_updated_at(self) -> Timestamp | None:
33
+ raise NotImplementedError()
34
+
35
+ @abstractmethod
36
+ def _get_updated_by(self) -> str | None:
37
+ raise NotImplementedError()
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING
5
+
6
+ from infrahub.core.constants import MetadataOptions
7
+
8
+ if TYPE_CHECKING:
9
+ from infrahub.core.timestamp import Timestamp
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class MetadataQueryOptions:
14
+ node_level: MetadataOptions = MetadataOptions.NONE
15
+ attribute_level: MetadataOptions = MetadataOptions.NONE
16
+ relationship_level: MetadataOptions = MetadataOptions.NONE
17
+
18
+ def __or__(self, other: MetadataQueryOptions) -> MetadataQueryOptions:
19
+ return MetadataQueryOptions(
20
+ node_level=self.node_level | other.node_level,
21
+ attribute_level=self.attribute_level | other.attribute_level,
22
+ relationship_level=self.relationship_level | other.relationship_level,
23
+ )
24
+
25
+
26
+ @dataclass
27
+ class MetadataInfo:
28
+ created_at: Timestamp | None = None
29
+ created_by: str | None = None
30
+ updated_at: Timestamp | None = None
31
+ updated_by: str | None = None
File without changes
@@ -0,0 +1,301 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from enum import Enum
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from infrahub.core.query import Query, QueryType
8
+
9
+ if TYPE_CHECKING:
10
+ from infrahub.core.timestamp import Timestamp
11
+ from infrahub.database import InfrahubDatabase
12
+
13
+
14
+ class RelationshipDirection(Enum):
15
+ OUTBOUND = "outbound"
16
+ INBOUND = "inbound"
17
+ BIDIRECTIONAL = "bidirectional"
18
+
19
+
20
+ @dataclass
21
+ class AttributeMetadata:
22
+ uuid: str
23
+ name: str
24
+ is_deleted: bool
25
+ created_at: Timestamp | None = None
26
+ created_by: str | None = None
27
+ updated_at: Timestamp | None = None
28
+ updated_by: str | None = None
29
+
30
+ def __hash__(self) -> int:
31
+ return hash(
32
+ (
33
+ self.uuid,
34
+ self.name,
35
+ self.is_deleted,
36
+ self.created_at,
37
+ self.created_by,
38
+ self.updated_at,
39
+ self.updated_by,
40
+ )
41
+ )
42
+
43
+
44
+ @dataclass
45
+ class RelationshipMetadata:
46
+ uuid: str
47
+ identifier: str # Relationship.name in DB (e.g., "testcar__testperson")
48
+ peer_uuid: str
49
+ direction: RelationshipDirection
50
+ is_deleted: bool
51
+ created_at: Timestamp | None = None
52
+ created_by: str | None = None
53
+ updated_at: Timestamp | None = None
54
+ updated_by: str | None = None
55
+
56
+ def __hash__(self) -> int:
57
+ return hash(
58
+ (
59
+ self.uuid,
60
+ self.identifier,
61
+ self.peer_uuid,
62
+ self.direction,
63
+ self.is_deleted,
64
+ self.created_at,
65
+ self.created_by,
66
+ self.updated_at,
67
+ self.updated_by,
68
+ )
69
+ )
70
+
71
+
72
+ @dataclass
73
+ class NodeMetadata:
74
+ uuid: str
75
+ kind: str
76
+ is_deleted: bool
77
+ created_at: Timestamp | None = None
78
+ created_by: str | None = None
79
+ updated_at: Timestamp | None = None
80
+ updated_by: str | None = None
81
+ attributes: list[AttributeMetadata] = field(default_factory=list)
82
+ relationships: list[RelationshipMetadata] = field(default_factory=list)
83
+
84
+ def __hash__(self) -> int:
85
+ return hash(
86
+ (
87
+ self.uuid,
88
+ self.kind,
89
+ self.is_deleted,
90
+ self.created_at,
91
+ self.created_by,
92
+ self.updated_at,
93
+ self.updated_by,
94
+ tuple(self.attributes),
95
+ tuple(self.relationships),
96
+ )
97
+ )
98
+
99
+
100
+ class NodeMetadataDefaultBranchQuery(Query):
101
+ """Query to retrieve metadata for nodes and their attributes/relationships.
102
+
103
+ This query only works on the default branch and reads metadata directly from
104
+ vertex properties. It supports retrieving deleted nodes, attributes, and
105
+ relationships.
106
+ """
107
+
108
+ name = "node_metadata"
109
+ type = QueryType.READ
110
+ insert_return = False
111
+
112
+ def __init__(self, node_uuids: list[str], **kwargs: Any) -> None:
113
+ self.node_uuids = node_uuids
114
+ super().__init__(**kwargs)
115
+
116
+ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
117
+ if not self.branch.is_default:
118
+ raise ValueError("NodeMetadataQuery only runs on the default branch")
119
+
120
+ self.params["node_uuids"] = self.node_uuids
121
+ self.params["branch"] = self.branch.name
122
+
123
+ # Query nodes with their metadata and deletion status
124
+ # Then query attributes and relationships
125
+ query = """
126
+ // ------------------
127
+ // Part 1: Get node metadata with deletion status
128
+ // ------------------
129
+ UNWIND $node_uuids AS node_uuid
130
+ CALL (node_uuid) {
131
+ MATCH (n:Node {uuid: node_uuid})-[r_ipo:IS_PART_OF]->(root:Root)
132
+ WHERE r_ipo.branch = $branch
133
+ RETURN n, r_ipo
134
+ ORDER BY r_ipo.from DESC
135
+ LIMIT 1
136
+ }
137
+ WITH n,
138
+ CASE
139
+ WHEN r_ipo.status = "deleted" THEN true
140
+ WHEN r_ipo.to IS NOT NULL THEN true
141
+ ELSE false
142
+ END AS node_is_deleted
143
+
144
+ // ------------------
145
+ // Part 2: Get attribute details
146
+ // ------------------
147
+ OPTIONAL MATCH (n)-[:HAS_ATTRIBUTE {branch: $branch}]->(attr:Attribute)
148
+ WITH DISTINCT n, node_is_deleted, attr
149
+ CALL (n, attr) {
150
+ OPTIONAL MATCH (n)-[r_attr:HAS_ATTRIBUTE]->(attr)
151
+ WHERE r_attr.branch = $branch
152
+ WITH r_attr, attr
153
+ ORDER BY r_attr.from DESC
154
+ LIMIT 1
155
+ RETURN {
156
+ uuid: attr.uuid,
157
+ name: attr.name,
158
+ is_deleted: CASE
159
+ WHEN r_attr.status = "deleted" THEN true
160
+ WHEN r_attr.to IS NOT NULL THEN true
161
+ ELSE false
162
+ END,
163
+ created_at: attr.created_at,
164
+ created_by: attr.created_by,
165
+ updated_at: attr.updated_at,
166
+ updated_by: attr.updated_by
167
+ } AS attribute_details
168
+ }
169
+ WITH n, node_is_deleted, COALESCE(collect(attribute_details), []) AS attributes
170
+
171
+ // ------------------
172
+ // Part 3: Get relationship details
173
+ // ------------------
174
+ OPTIONAL MATCH (n)-[:IS_RELATED {branch: $branch}]-(rel:Relationship)-[:IS_RELATED {branch: $branch}]-(peer:Node)
175
+ WHERE n <> peer
176
+ WITH DISTINCT n, node_is_deleted, attributes, rel, peer
177
+ CALL (n, rel, peer) {
178
+ OPTIONAL MATCH (n)-[r1:IS_RELATED]-(rel:Relationship)-[r2:IS_RELATED]-(peer:Node)
179
+ WHERE r1.branch = $branch AND r2.branch = $branch
180
+ WITH r1, r2
181
+ ORDER BY r1.from DESC, r2.from DESC
182
+ LIMIT 1
183
+ RETURN {
184
+ uuid: rel.uuid,
185
+ identifier: rel.name,
186
+ peer_uuid: peer.uuid,
187
+ direction: CASE
188
+ WHEN startNode(r1) = n AND startNode(r2) = rel THEN "outbound"
189
+ WHEN startNode(r1) = rel AND startNode(r2) = peer THEN "inbound"
190
+ ELSE "bidirectional"
191
+ END,
192
+ is_deleted: CASE
193
+ WHEN (r1.status = "deleted" OR r1.to IS NOT NULL) AND (r2.status = "deleted" OR r2.to IS NOT NULL) THEN true
194
+ ELSE false
195
+ END,
196
+ created_at: rel.created_at,
197
+ created_by: rel.created_by,
198
+ updated_at: rel.updated_at,
199
+ updated_by: rel.updated_by
200
+ } AS relationship_details
201
+ }
202
+ WITH n, node_is_deleted, attributes, COALESCE(collect(relationship_details), []) AS relationships
203
+
204
+ RETURN n.uuid AS node_uuid, n.kind AS node_kind, node_is_deleted,
205
+ n.created_at AS node_created_at, n.created_by AS node_created_by,
206
+ n.updated_at AS node_updated_at, n.updated_by AS node_updated_by,
207
+ attributes, relationships
208
+ """
209
+ self.return_labels = [
210
+ "node_uuid",
211
+ "node_kind",
212
+ "node_is_deleted",
213
+ "node_created_at",
214
+ "node_created_by",
215
+ "node_updated_at",
216
+ "node_updated_by",
217
+ "attributes",
218
+ "relationships",
219
+ ]
220
+ self.add_to_query(query)
221
+
222
+ def get_metadatas(self) -> list[NodeMetadata]:
223
+ """Process query results into NodeMetadata dataclasses."""
224
+ from infrahub.core.timestamp import Timestamp
225
+
226
+ nodes: list[NodeMetadata] = []
227
+
228
+ for result in self.get_results():
229
+ node_uuid = result.get_as_type("node_uuid", return_type=str)
230
+ node_kind = result.get_as_type("node_kind", return_type=str)
231
+ node_is_deleted = result.get_as_type("node_is_deleted", bool)
232
+
233
+ node_created_at_str = result.get_as_str("node_created_at")
234
+ node_created_at = Timestamp(node_created_at_str) if node_created_at_str else None
235
+ node_created_by = result.get_as_str("node_created_by")
236
+ node_updated_at_str = result.get_as_str("node_updated_at")
237
+ node_updated_at = Timestamp(node_updated_at_str) if node_updated_at_str else None
238
+ node_updated_by = result.get_as_str("node_updated_by")
239
+
240
+ # Parse attributes
241
+ attributes_data: list[dict[str, Any]] = result.get_as_type("attributes", list)
242
+ attributes: list[AttributeMetadata] = []
243
+ for attr_data in attributes_data:
244
+ attr_created_at_str = attr_data.get("created_at")
245
+ attr_created_at = Timestamp(attr_created_at_str) if attr_created_at_str else None
246
+ attr_updated_at_str = attr_data.get("updated_at")
247
+ attr_updated_at = Timestamp(attr_updated_at_str) if attr_updated_at_str else None
248
+
249
+ attributes.append(
250
+ AttributeMetadata(
251
+ uuid=attr_data["uuid"],
252
+ name=attr_data["name"],
253
+ is_deleted=attr_data["is_deleted"],
254
+ created_at=attr_created_at,
255
+ created_by=attr_data.get("created_by"),
256
+ updated_at=attr_updated_at,
257
+ updated_by=attr_data.get("updated_by"),
258
+ )
259
+ )
260
+
261
+ # Parse relationships
262
+ relationships_data: list[dict[str, Any]] = result.get_as_type("relationships", list)
263
+ relationships: list[RelationshipMetadata] = []
264
+ for rel_data in relationships_data:
265
+ rel_created_at_str = rel_data.get("created_at")
266
+ rel_created_at = Timestamp(rel_created_at_str) if rel_created_at_str else None
267
+ rel_updated_at_str = rel_data.get("updated_at")
268
+ rel_updated_at = Timestamp(rel_updated_at_str) if rel_updated_at_str else None
269
+
270
+ direction_str = rel_data["direction"]
271
+ direction = RelationshipDirection(direction_str)
272
+
273
+ relationships.append(
274
+ RelationshipMetadata(
275
+ uuid=rel_data["uuid"],
276
+ identifier=rel_data["identifier"],
277
+ peer_uuid=rel_data["peer_uuid"],
278
+ direction=direction,
279
+ is_deleted=rel_data["is_deleted"],
280
+ created_at=rel_created_at,
281
+ created_by=rel_data.get("created_by"),
282
+ updated_at=rel_updated_at,
283
+ updated_by=rel_data.get("updated_by"),
284
+ )
285
+ )
286
+
287
+ nodes.append(
288
+ NodeMetadata(
289
+ uuid=node_uuid,
290
+ kind=node_kind,
291
+ is_deleted=node_is_deleted,
292
+ created_at=node_created_at,
293
+ created_by=node_created_by,
294
+ updated_at=node_updated_at,
295
+ updated_by=node_updated_by,
296
+ attributes=attributes,
297
+ relationships=relationships,
298
+ )
299
+ )
300
+
301
+ return nodes
@@ -50,6 +50,8 @@ from .m045_backfill_hfid_display_label_in_db_profile_template import Migration04
50
50
  from .m046_fill_agnostic_hfid_display_labels import Migration046
51
51
  from .m047_backfill_or_null_display_label import Migration047
52
52
  from .m048_undelete_rel_props import Migration048
53
+ from .m049_remove_is_visible_relationship import Migration049
54
+ from .m050_backfill_vertex_metadata import Migration050
53
55
 
54
56
  if TYPE_CHECKING:
55
57
  from ..shared import MigrationTypes
@@ -104,6 +106,8 @@ MIGRATIONS: list[type[MigrationTypes]] = [
104
106
  Migration046,
105
107
  Migration047,
106
108
  Migration048,
109
+ Migration049,
110
+ Migration050,
107
111
  ]
108
112
 
109
113
 
@@ -43,7 +43,7 @@ class Migration012RenameTypeAttributeData(AttributeRenameQuery):
43
43
  name = "migration_012_rename_attr_type"
44
44
  type = QueryType.WRITE
45
45
 
46
- def __init__(self, **kwargs: Any):
46
+ def __init__(self, **kwargs: Any) -> None:
47
47
  new_attr = AttributeInfo(
48
48
  name="account_type",
49
49
  node_kind=InfrahubKind.ACCOUNT,
@@ -83,7 +83,7 @@ class Migration012AddLabelData(NodeDuplicateQuery):
83
83
  name = "migration_012_add_labels"
84
84
  type = QueryType.WRITE
85
85
 
86
- def __init__(self, **kwargs: Any):
86
+ def __init__(self, **kwargs: Any) -> None:
87
87
  new_node = SchemaNodeInfo(
88
88
  name="Account",
89
89
  namespace="Core",
@@ -132,7 +132,7 @@ class Migration012RenameTypeAttributeSchema(SchemaAttributeUpdateQuery):
132
132
  type = QueryType.WRITE
133
133
  insert_return = False
134
134
 
135
- def __init__(self, **kwargs: Any):
135
+ def __init__(self, **kwargs: Any) -> None:
136
136
  super().__init__(
137
137
  attribute_name="name",
138
138
  node_name="Account",
@@ -150,7 +150,7 @@ class Migration012RenameRelationshipAccountTokenData(RelationshipDuplicateQuery)
150
150
  name = "migration_012_rename_rel_account_token_data"
151
151
  type = QueryType.WRITE
152
152
 
153
- def __init__(self, **kwargs: Any):
153
+ def __init__(self, **kwargs: Any) -> None:
154
154
  new_rel = SchemaRelationshipInfo(
155
155
  name="account__token",
156
156
  branch_support=BranchSupportType.AGNOSTIC.value,
@@ -173,7 +173,7 @@ class Migration012RenameRelationshipRefreshTokenData(RelationshipDuplicateQuery)
173
173
  name = "migration_012_rename_rel_refresh_token_data"
174
174
  type = QueryType.WRITE
175
175
 
176
- def __init__(self, **kwargs: Any):
176
+ def __init__(self, **kwargs: Any) -> None:
177
177
  new_rel = SchemaRelationshipInfo(
178
178
  name="account__refreshtoken",
179
179
  branch_support=BranchSupportType.AGNOSTIC.value,
@@ -196,7 +196,7 @@ class Migration012RenameRelationshipThreadData(RelationshipDuplicateQuery):
196
196
  name = "migration_012_rename_rel_thread_data"
197
197
  type = QueryType.WRITE
198
198
 
199
- def __init__(self, **kwargs: Any):
199
+ def __init__(self, **kwargs: Any) -> None:
200
200
  new_rel = SchemaRelationshipInfo(
201
201
  name="thread__account",
202
202
  branch_support=BranchSupportType.AGNOSTIC.value,
@@ -219,7 +219,7 @@ class Migration012RenameRelationshipCommentData(RelationshipDuplicateQuery):
219
219
  name = "migration_012_rename_rel_comment_data"
220
220
  type = QueryType.WRITE
221
221
 
222
- def __init__(self, **kwargs: Any):
222
+ def __init__(self, **kwargs: Any) -> None:
223
223
  new_rel = SchemaRelationshipInfo(
224
224
  name="comment__account",
225
225
  branch_support=BranchSupportType.AGNOSTIC.value,
@@ -243,7 +243,7 @@ class Migration012DeleteOldElementsSchema(DeleteElementInSchemaQuery):
243
243
  type = QueryType.WRITE
244
244
  insert_return = False
245
245
 
246
- def __init__(self, **kwargs: Any):
246
+ def __init__(self, **kwargs: Any) -> None:
247
247
  kwargs.pop("branch", None)
248
248
 
249
249
  super().__init__(
@@ -260,7 +260,7 @@ class Migration012UpdateDisplayLabels(SchemaAttributeUpdateQuery):
260
260
  type = QueryType.WRITE
261
261
  insert_return = False
262
262
 
263
- def __init__(self, **kwargs: Any):
263
+ def __init__(self, **kwargs: Any) -> None:
264
264
  kwargs.pop("branch", None)
265
265
 
266
266
  super().__init__(
@@ -277,7 +277,7 @@ class Migration012UpdateOrderBy(SchemaAttributeUpdateQuery):
277
277
  type = QueryType.WRITE
278
278
  insert_return = False
279
279
 
280
- def __init__(self, **kwargs: Any):
280
+ def __init__(self, **kwargs: Any) -> None:
281
281
  kwargs.pop("branch", None)
282
282
 
283
283
  super().__init__(
@@ -294,7 +294,7 @@ class Migration012UpdateDefaultFilter(SchemaAttributeUpdateQuery):
294
294
  type = QueryType.WRITE
295
295
  insert_return = False
296
296
 
297
- def __init__(self, **kwargs: Any):
297
+ def __init__(self, **kwargs: Any) -> None:
298
298
  kwargs.pop("branch", None)
299
299
 
300
300
  super().__init__(
@@ -311,7 +311,7 @@ class Migration012UpdateHFID(SchemaAttributeUpdateQuery):
311
311
  type = QueryType.WRITE
312
312
  insert_return = False
313
313
 
314
- def __init__(self, **kwargs: Any):
314
+ def __init__(self, **kwargs: Any) -> None:
315
315
  kwargs.pop("branch", None)
316
316
 
317
317
  super().__init__(
@@ -73,7 +73,6 @@ class Migration013ConvertCoreRepositoryWithCred(Query):
73
73
 
74
74
  self.params["current_time"] = self.at.to_string()
75
75
  self.params["is_protected_default"] = False
76
- self.params["is_visible_default"] = True
77
76
  self.params["branch_support"] = BranchSupportType.AGNOSTIC.value
78
77
 
79
78
  self.params["rel_identifier"] = "gitrepository__credential"
@@ -91,8 +90,7 @@ class Migration013ConvertCoreRepositoryWithCred(Query):
91
90
  // Prepare some nodes we'll need later
92
91
  // --------------------------------
93
92
  MERGE (is_protected_value:Boolean { value: $is_protected_default })
94
- MERGE (is_visible_value:Boolean { value: $is_visible_default })
95
- WITH git_repo, root, is_protected_value, is_visible_value
93
+ WITH git_repo, root, is_protected_value
96
94
  // --------------------------------
97
95
  // Retrieve the name of the current repository
98
96
  // --------------------------------
@@ -106,9 +104,9 @@ class Migration013ConvertCoreRepositoryWithCred(Query):
106
104
  ORDER BY r1.branch_level DESC, r1.from DESC
107
105
  LIMIT 1
108
106
  }
109
- WITH n1 as git_repo, r11 as r1, r22 as r2, av1 as git_name_value, root, is_protected_value, is_visible_value
107
+ WITH n1 as git_repo, r11 as r1, r22 as r2, av1 as git_name_value, root, is_protected_value
110
108
  WHERE r1.status = "active" AND r2.status = "active"
111
- WITH DISTINCT(git_repo) as git_repo, root, is_protected_value, is_visible_value, git_name_value
109
+ WITH DISTINCT(git_repo) as git_repo, root, is_protected_value, git_name_value
112
110
  // --------------------------------
113
111
  // Create new CorePasswordCredential node
114
112
  // --------------------------------
@@ -119,20 +117,17 @@ class Migration013ConvertCoreRepositoryWithCred(Query):
119
117
  CREATE (attr_name)<-[:HAS_ATTRIBUTE $rel_props_new ]-(cred)
120
118
  CREATE (attr_name)-[:HAS_VALUE $rel_props_new ]->(git_name_value)
121
119
  CREATE (attr_name)-[:IS_PROTECTED $rel_props_new]->(is_protected_value)
122
- CREATE (attr_name)-[:IS_VISIBLE $rel_props_new]->(is_visible_value)
123
120
  // attribute: label
124
121
  CREATE (attr_lbl:Attribute { name: "label", branch_support: $branch_support })
125
122
  CREATE (attr_lbl)<-[:HAS_ATTRIBUTE $rel_props_new ]-(cred)
126
123
  CREATE (attr_lbl)-[:HAS_VALUE $rel_props_new ]->(git_name_value)
127
124
  CREATE (attr_lbl)-[:IS_PROTECTED $rel_props_new]->(is_protected_value)
128
- CREATE (attr_lbl)-[:IS_VISIBLE $rel_props_new]->(is_visible_value)
129
125
  // attribute: description
130
126
  CREATE (attr_desc:Attribute { name: "description", branch_support: $branch_support })
131
127
  MERGE (av_desc:AttributeValue { value: "Credential for " + git_name_value.value, is_default: true })
132
128
  CREATE (attr_desc)<-[:HAS_ATTRIBUTE $rel_props_new ]-(cred)
133
129
  CREATE (attr_desc)-[:HAS_VALUE $rel_props_new ]->(av_desc)
134
130
  CREATE (attr_desc)-[:IS_PROTECTED $rel_props_new]->(is_protected_value)
135
- CREATE (attr_desc)-[:IS_VISIBLE $rel_props_new]->(is_visible_value)
136
131
  %(attr_name_guid)s
137
132
  %(attr_label_guid)s
138
133
  %(attr_desc_guid)s
@@ -233,7 +228,7 @@ class Migration013DeleteUsernamePasswordGenericSchema(DeleteElementInSchemaQuery
233
228
  type = QueryType.WRITE
234
229
  insert_return = False
235
230
 
236
- def __init__(self, **kwargs: Any):
231
+ def __init__(self, **kwargs: Any) -> None:
237
232
  kwargs.pop("branch", None)
238
233
 
239
234
  super().__init__(
@@ -250,7 +245,7 @@ class Migration013DeleteUsernamePasswordReadWriteSchema(DeleteElementInSchemaQue
250
245
  type = QueryType.WRITE
251
246
  insert_return = False
252
247
 
253
- def __init__(self, **kwargs: Any):
248
+ def __init__(self, **kwargs: Any) -> None:
254
249
  kwargs.pop("branch", None)
255
250
 
256
251
  super().__init__(
@@ -267,7 +262,7 @@ class Migration013DeleteUsernamePasswordReadOnlySchema(DeleteElementInSchemaQuer
267
262
  type = QueryType.WRITE
268
263
  insert_return = False
269
264
 
270
- def __init__(self, **kwargs: Any):
265
+ def __init__(self, **kwargs: Any) -> None:
271
266
  kwargs.pop("branch", None)
272
267
 
273
268
  super().__init__(
@@ -282,7 +277,7 @@ class Migration013DeleteUsernamePasswordReadOnlySchema(DeleteElementInSchemaQuer
282
277
  class Migration013AddInternalStatusData(AttributeAddQuery):
283
278
  type = QueryType.WRITE
284
279
 
285
- def __init__(self, **kwargs: Any):
280
+ def __init__(self, **kwargs: Any) -> None:
286
281
  kwargs.pop("branch", None)
287
282
 
288
283
  super().__init__(
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING, Sequence
4
4
 
5
5
  from infrahub.core import registry
6
+ from infrahub.core.constants import SYSTEM_USER_ID
6
7
  from infrahub.core.migrations.shared import MigrationResult
7
8
  from infrahub.core.schema.definitions.core import core_profile_schema_definition
8
9
  from infrahub.core.schema.manager import SchemaManager
@@ -26,7 +27,7 @@ class Migration017(InternalSchemaMigration):
26
27
 
27
28
  return result
28
29
 
29
- async def execute(self, db: InfrahubDatabase) -> MigrationResult:
30
+ async def execute(self, db: InfrahubDatabase, user_id: str = SYSTEM_USER_ID) -> MigrationResult:
30
31
  """
31
32
  Load CoreProfile schema node in db.
32
33
  """
@@ -35,6 +36,8 @@ class Migration017(InternalSchemaMigration):
35
36
  manager.set_schema_branch(name=default_branch.name, schema=self.get_internal_schema())
36
37
 
37
38
  db.add_schema(manager.get_schema_branch(default_branch.name))
38
- await manager.load_node_to_db(node=core_profile_schema_definition, db=db, branch=default_branch)
39
+ await manager.load_node_to_db(
40
+ node=core_profile_schema_definition, db=db, branch=default_branch, user_id=user_id
41
+ )
39
42
 
40
43
  return MigrationResult()
@@ -4,6 +4,7 @@ from collections import defaultdict
4
4
  from typing import TYPE_CHECKING, Sequence
5
5
 
6
6
  from infrahub.core import registry
7
+ from infrahub.core.constants import SYSTEM_USER_ID
7
8
  from infrahub.core.diff.payload_builder import get_display_labels_per_kind
8
9
  from infrahub.core.migrations.shared import MigrationResult
9
10
  from infrahub.core.schema import GenericSchema, NodeSchema, SchemaRoot, internal_schema
@@ -95,5 +96,5 @@ class Migration018(InternalSchemaMigration):
95
96
  async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
96
97
  return MigrationResult()
97
98
 
98
- async def execute(self, db: InfrahubDatabase) -> MigrationResult:
99
+ async def execute(self, db: InfrahubDatabase, user_id: str = SYSTEM_USER_ID) -> MigrationResult: # noqa: ARG002
99
100
  return await validate_nulls_in_uniqueness_constraints(db=db)
@@ -169,11 +169,6 @@ class DeleteNodesRelsQuery(Query):
169
169
  MERGE (rel)-[:IS_PROTECTED {status: "deleted", branch: branch, branch_level: branch_level, from: deleted_time}]->(peer_2)
170
170
  }
171
171
 
172
- CALL (rel, peer_2, branch, branch_level, deleted_time) {
173
- MATCH (rel)-[:IS_VISIBLE]->(peer_2)
174
- MERGE (rel)-[:IS_VISIBLE {status: "deleted", branch: branch, branch_level: branch_level, from: deleted_time}]->(peer_2)
175
- }
176
-
177
172
  CALL (rel, peer_2, branch, branch_level, deleted_time) {
178
173
  MATCH (rel)-[:HAS_OWNER]->(peer_2)
179
174
  MERGE (rel)-[:HAS_OWNER {status: "deleted", branch: branch, branch_level: branch_level, from: deleted_time}]->(peer_2)
@@ -194,11 +189,6 @@ class DeleteNodesRelsQuery(Query):
194
189
  MERGE (rel)<-[:IS_PROTECTED {status: "deleted", branch: branch, branch_level: branch_level, from: deleted_time}]-(peer_2)
195
190
  }
196
191
 
197
- CALL (rel, peer_2, branch, branch_level, deleted_time) {
198
- MATCH (rel)<-[:IS_VISIBLE]-(peer_2)
199
- MERGE (rel)<-[:IS_VISIBLE {status: "deleted", branch: branch, branch_level: branch_level, from: deleted_time}]-(peer_2)
200
- }
201
-
202
192
  CALL (rel, peer_2, branch, branch_level, deleted_time) {
203
193
  MATCH (rel)<-[:HAS_OWNER]-(peer_2)
204
194
  MERGE (rel)<-[:HAS_OWNER {status: "deleted", branch: branch, branch_level: branch_level, from: deleted_time}]-(peer_2)
@@ -108,13 +108,6 @@ CALL (a, branch, status, from, to, b, e_id_to_keep) {
108
108
  self.add_to_query(query)
109
109
 
110
110
 
111
- class DeleteDuplicateIsVisibleEdgesQuery(DeleteDuplicateBooleanEdgesQuery):
112
- name = "delete_duplicate_is_visible_edges"
113
- type = QueryType.WRITE
114
- insert_return = False
115
- edge_type = DatabaseEdgeType.IS_VISIBLE
116
-
117
-
118
111
  class DeleteDuplicateIsProtectedEdgesQuery(DeleteDuplicateBooleanEdgesQuery):
119
112
  name = "delete_duplicate_is_protected_edges"
120
113
  type = QueryType.WRITE
@@ -144,7 +137,6 @@ class Migration020(GraphMigration):
144
137
  minimum_version: int = 19
145
138
  queries: Sequence[type[Query]] = [
146
139
  DeleteDuplicateHasValueEdgesQuery,
147
- DeleteDuplicateIsVisibleEdgesQuery,
148
140
  DeleteDuplicateIsProtectedEdgesQuery,
149
141
  ]
150
142