infrahub-server 1.4.10__py3-none-any.whl → 1.5.0b1__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 (178) hide show
  1. infrahub/actions/tasks.py +208 -16
  2. infrahub/api/artifact.py +3 -0
  3. infrahub/api/diff/diff.py +1 -1
  4. infrahub/api/query.py +2 -0
  5. infrahub/api/schema.py +3 -0
  6. infrahub/auth.py +5 -5
  7. infrahub/cli/db.py +26 -2
  8. infrahub/cli/db_commands/clean_duplicate_schema_fields.py +212 -0
  9. infrahub/config.py +7 -2
  10. infrahub/core/attribute.py +25 -22
  11. infrahub/core/branch/models.py +2 -2
  12. infrahub/core/branch/needs_rebase_status.py +11 -0
  13. infrahub/core/branch/tasks.py +4 -3
  14. infrahub/core/changelog/models.py +4 -12
  15. infrahub/core/constants/__init__.py +1 -0
  16. infrahub/core/constants/infrahubkind.py +1 -0
  17. infrahub/core/convert_object_type/object_conversion.py +201 -0
  18. infrahub/core/convert_object_type/repository_conversion.py +89 -0
  19. infrahub/core/convert_object_type/schema_mapping.py +27 -3
  20. infrahub/core/diff/model/path.py +4 -0
  21. infrahub/core/diff/payload_builder.py +1 -1
  22. infrahub/core/diff/query/artifact.py +1 -1
  23. infrahub/core/graph/__init__.py +1 -1
  24. infrahub/core/initialization.py +2 -2
  25. infrahub/core/ipam/utilization.py +1 -1
  26. infrahub/core/manager.py +9 -84
  27. infrahub/core/migrations/graph/__init__.py +6 -0
  28. infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +166 -0
  29. infrahub/core/migrations/graph/m041_create_hfid_display_label_in_db.py +97 -0
  30. infrahub/core/migrations/graph/m042_backfill_hfid_display_label_in_db.py +86 -0
  31. infrahub/core/migrations/schema/node_attribute_add.py +5 -2
  32. infrahub/core/migrations/shared.py +5 -6
  33. infrahub/core/node/__init__.py +165 -42
  34. infrahub/core/node/constraints/attribute_uniqueness.py +3 -1
  35. infrahub/core/node/create.py +67 -35
  36. infrahub/core/node/lock_utils.py +98 -0
  37. infrahub/core/node/node_property_attribute.py +230 -0
  38. infrahub/core/node/standard.py +1 -1
  39. infrahub/core/property.py +11 -0
  40. infrahub/core/protocols.py +8 -1
  41. infrahub/core/query/attribute.py +27 -15
  42. infrahub/core/query/node.py +61 -185
  43. infrahub/core/query/relationship.py +43 -26
  44. infrahub/core/query/subquery.py +0 -8
  45. infrahub/core/registry.py +2 -2
  46. infrahub/core/relationship/constraints/count.py +1 -1
  47. infrahub/core/relationship/model.py +60 -20
  48. infrahub/core/schema/attribute_schema.py +0 -2
  49. infrahub/core/schema/basenode_schema.py +42 -2
  50. infrahub/core/schema/definitions/core/__init__.py +2 -0
  51. infrahub/core/schema/definitions/core/generator.py +2 -0
  52. infrahub/core/schema/definitions/core/group.py +16 -2
  53. infrahub/core/schema/definitions/core/repository.py +7 -0
  54. infrahub/core/schema/definitions/internal.py +14 -1
  55. infrahub/core/schema/generated/base_node_schema.py +6 -1
  56. infrahub/core/schema/node_schema.py +5 -2
  57. infrahub/core/schema/relationship_schema.py +0 -1
  58. infrahub/core/schema/schema_branch.py +137 -2
  59. infrahub/core/schema/schema_branch_display.py +123 -0
  60. infrahub/core/schema/schema_branch_hfid.py +114 -0
  61. infrahub/core/validators/aggregated_checker.py +1 -1
  62. infrahub/core/validators/determiner.py +12 -1
  63. infrahub/core/validators/relationship/peer.py +1 -1
  64. infrahub/core/validators/tasks.py +1 -1
  65. infrahub/display_labels/__init__.py +0 -0
  66. infrahub/display_labels/gather.py +48 -0
  67. infrahub/display_labels/models.py +240 -0
  68. infrahub/display_labels/tasks.py +186 -0
  69. infrahub/display_labels/triggers.py +22 -0
  70. infrahub/events/group_action.py +1 -1
  71. infrahub/events/node_action.py +1 -1
  72. infrahub/generators/constants.py +7 -0
  73. infrahub/generators/models.py +38 -12
  74. infrahub/generators/tasks.py +34 -16
  75. infrahub/git/base.py +38 -1
  76. infrahub/git/integrator.py +22 -14
  77. infrahub/graphql/analyzer.py +1 -1
  78. infrahub/graphql/api/dependencies.py +2 -4
  79. infrahub/graphql/api/endpoints.py +2 -2
  80. infrahub/graphql/app.py +2 -4
  81. infrahub/graphql/initialization.py +2 -3
  82. infrahub/graphql/manager.py +212 -137
  83. infrahub/graphql/middleware.py +12 -0
  84. infrahub/graphql/mutations/branch.py +11 -0
  85. infrahub/graphql/mutations/computed_attribute.py +110 -3
  86. infrahub/graphql/mutations/convert_object_type.py +34 -13
  87. infrahub/graphql/mutations/display_label.py +111 -0
  88. infrahub/graphql/mutations/generator.py +25 -7
  89. infrahub/graphql/mutations/hfid.py +118 -0
  90. infrahub/graphql/mutations/ipam.py +21 -8
  91. infrahub/graphql/mutations/main.py +37 -153
  92. infrahub/graphql/mutations/profile.py +195 -0
  93. infrahub/graphql/mutations/proposed_change.py +2 -1
  94. infrahub/graphql/mutations/relationship.py +2 -2
  95. infrahub/graphql/mutations/repository.py +22 -83
  96. infrahub/graphql/mutations/resource_manager.py +2 -2
  97. infrahub/graphql/mutations/schema.py +5 -5
  98. infrahub/graphql/mutations/webhook.py +1 -1
  99. infrahub/graphql/queries/resource_manager.py +1 -1
  100. infrahub/graphql/registry.py +173 -0
  101. infrahub/graphql/resolvers/resolver.py +2 -0
  102. infrahub/graphql/schema.py +8 -1
  103. infrahub/groups/tasks.py +1 -1
  104. infrahub/hfid/__init__.py +0 -0
  105. infrahub/hfid/gather.py +48 -0
  106. infrahub/hfid/models.py +240 -0
  107. infrahub/hfid/tasks.py +185 -0
  108. infrahub/hfid/triggers.py +22 -0
  109. infrahub/lock.py +67 -30
  110. infrahub/locks/__init__.py +0 -0
  111. infrahub/locks/tasks.py +37 -0
  112. infrahub/middleware.py +26 -1
  113. infrahub/patch/plan_writer.py +2 -2
  114. infrahub/profiles/__init__.py +0 -0
  115. infrahub/profiles/node_applier.py +101 -0
  116. infrahub/profiles/queries/__init__.py +0 -0
  117. infrahub/profiles/queries/get_profile_data.py +99 -0
  118. infrahub/profiles/tasks.py +63 -0
  119. infrahub/proposed_change/tasks.py +10 -1
  120. infrahub/repositories/__init__.py +0 -0
  121. infrahub/repositories/create_repository.py +113 -0
  122. infrahub/server.py +16 -3
  123. infrahub/services/__init__.py +8 -5
  124. infrahub/tasks/registry.py +6 -4
  125. infrahub/trigger/catalogue.py +4 -0
  126. infrahub/trigger/models.py +2 -0
  127. infrahub/trigger/tasks.py +3 -0
  128. infrahub/webhook/models.py +1 -1
  129. infrahub/workflows/catalogue.py +110 -3
  130. infrahub/workflows/initialization.py +16 -0
  131. infrahub/workflows/models.py +17 -2
  132. infrahub_sdk/branch.py +5 -8
  133. infrahub_sdk/checks.py +1 -1
  134. infrahub_sdk/client.py +364 -84
  135. infrahub_sdk/convert_object_type.py +61 -0
  136. infrahub_sdk/ctl/check.py +2 -3
  137. infrahub_sdk/ctl/cli_commands.py +18 -12
  138. infrahub_sdk/ctl/config.py +8 -2
  139. infrahub_sdk/ctl/generator.py +6 -3
  140. infrahub_sdk/ctl/graphql.py +184 -0
  141. infrahub_sdk/ctl/repository.py +39 -1
  142. infrahub_sdk/ctl/schema.py +18 -3
  143. infrahub_sdk/ctl/utils.py +4 -0
  144. infrahub_sdk/ctl/validate.py +5 -3
  145. infrahub_sdk/diff.py +4 -5
  146. infrahub_sdk/exceptions.py +2 -0
  147. infrahub_sdk/generator.py +7 -1
  148. infrahub_sdk/graphql/__init__.py +12 -0
  149. infrahub_sdk/graphql/constants.py +1 -0
  150. infrahub_sdk/graphql/plugin.py +85 -0
  151. infrahub_sdk/graphql/query.py +77 -0
  152. infrahub_sdk/{graphql.py → graphql/renderers.py} +88 -75
  153. infrahub_sdk/graphql/utils.py +40 -0
  154. infrahub_sdk/node/attribute.py +2 -0
  155. infrahub_sdk/node/node.py +28 -20
  156. infrahub_sdk/playback.py +1 -2
  157. infrahub_sdk/protocols.py +54 -6
  158. infrahub_sdk/pytest_plugin/plugin.py +7 -4
  159. infrahub_sdk/pytest_plugin/utils.py +40 -0
  160. infrahub_sdk/repository.py +1 -2
  161. infrahub_sdk/schema/__init__.py +38 -0
  162. infrahub_sdk/schema/main.py +1 -0
  163. infrahub_sdk/schema/repository.py +8 -0
  164. infrahub_sdk/spec/object.py +120 -7
  165. infrahub_sdk/spec/range_expansion.py +118 -0
  166. infrahub_sdk/timestamp.py +18 -6
  167. infrahub_sdk/transforms.py +1 -1
  168. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/METADATA +9 -11
  169. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/RECORD +177 -134
  170. infrahub_testcontainers/container.py +1 -1
  171. infrahub_testcontainers/docker-compose-cluster.test.yml +1 -1
  172. infrahub_testcontainers/docker-compose.test.yml +1 -1
  173. infrahub_testcontainers/models.py +2 -2
  174. infrahub_testcontainers/performance_test.py +4 -4
  175. infrahub/core/convert_object_type/conversion.py +0 -134
  176. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/LICENSE.txt +0 -0
  177. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/WHEEL +0 -0
  178. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/entry_points.txt +0 -0
@@ -11,6 +11,7 @@ from infrahub import config
11
11
  from infrahub.core import registry
12
12
  from infrahub.core.constants import (
13
13
  GLOBAL_BRANCH_NAME,
14
+ PROFILE_NODE_RELATIONSHIP_IDENTIFIER,
14
15
  AttributeDBNodeType,
15
16
  RelationshipDirection,
16
17
  RelationshipHierarchyDirection,
@@ -43,7 +44,6 @@ class NodeToProcess:
43
44
 
44
45
  node_id: str
45
46
  node_uuid: str
46
- profile_uuids: list[str]
47
47
 
48
48
  updated_at: str
49
49
 
@@ -142,9 +142,22 @@ class NodeCreateAllQuery(NodeQuery):
142
142
  attributes_ipnetwork: list[AttributeCreateData] = []
143
143
  attributes_indexed: list[AttributeCreateData] = []
144
144
 
145
+ if self.node.has_display_label():
146
+ attributes_indexed.append(
147
+ self.node._display_label.get_node_attribute(node=self.node, at=at).get_create_data(
148
+ node_schema=self.node.get_schema()
149
+ )
150
+ )
151
+ if self.node.has_human_friendly_id():
152
+ attributes_indexed.append(
153
+ self.node._human_friendly_id.get_node_attribute(node=self.node, at=at).get_create_data(
154
+ node_schema=self.node.get_schema()
155
+ )
156
+ )
157
+
145
158
  for attr_name in self.node._attributes:
146
159
  attr: BaseAttribute = getattr(self.node, attr_name)
147
- attr_data = attr.get_create_data()
160
+ attr_data = attr.get_create_data(node_schema=self.node.get_schema())
148
161
  node_type = attr.get_db_node_type()
149
162
 
150
163
  if AttributeDBNodeType.IPHOST in node_type:
@@ -606,6 +619,7 @@ class NodeListGetAttributeQuery(Query):
606
619
 
607
620
  async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG002
608
621
  self.params["ids"] = self.ids
622
+ self.params["profile_relationship_name"] = PROFILE_NODE_RELATIONSHIP_IDENTIFIER
609
623
 
610
624
  branch_filter, branch_params = self.branch.get_query_filter_path(
611
625
  at=self.at, branch_agnostic=self.branch_agnostic
@@ -614,6 +628,7 @@ class NodeListGetAttributeQuery(Query):
614
628
 
615
629
  query = """
616
630
  MATCH (n:Node) WHERE n.uuid IN $ids
631
+ WITH n, exists((n)-[:IS_RELATED]-(:Relationship {name: $profile_relationship_name})) AS might_use_profile
617
632
  MATCH (n)-[:HAS_ATTRIBUTE]-(a:Attribute)
618
633
  """
619
634
  if self.fields:
@@ -626,15 +641,23 @@ class NodeListGetAttributeQuery(Query):
626
641
  CALL (n, a) {
627
642
  MATCH (n)-[r:HAS_ATTRIBUTE]-(a:Attribute)
628
643
  WHERE %(branch_filter)s
629
- RETURN n as n1, r as r1, a as a1
644
+ RETURN r AS r1
630
645
  ORDER BY r.branch_level DESC, r.from DESC
631
646
  LIMIT 1
632
647
  }
633
- WITH n1 as n, r1, a1 as a
648
+ WITH n, r1, a, might_use_profile
634
649
  WHERE r1.status = "active"
635
- WITH n, r1, a
650
+ WITH n, r1, a, might_use_profile
636
651
  MATCH (a)-[r:HAS_VALUE]-(av:AttributeValue)
637
652
  WHERE %(branch_filter)s
653
+ CALL (a, might_use_profile) {
654
+ OPTIONAL MATCH (a)-[r:HAS_SOURCE]->(:CoreProfile)
655
+ WHERE might_use_profile = TRUE AND %(branch_filter)s
656
+ RETURN r.status = "active" AS has_active_profile
657
+ ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
658
+ LIMIT 1
659
+ }
660
+ WITH *, has_active_profile = TRUE AS is_from_profile
638
661
  CALL (a, av) {
639
662
  MATCH (a)-[r:HAS_VALUE]-(av:AttributeValue)
640
663
  WHERE %(branch_filter)s
@@ -642,13 +665,13 @@ class NodeListGetAttributeQuery(Query):
642
665
  ORDER BY r.branch_level DESC, r.from DESC
643
666
  LIMIT 1
644
667
  }
645
- WITH n, r1, a1 as a, r2, av1 as av
668
+ WITH n, r1, a1 as a, r2, av1 as av, is_from_profile
646
669
  WHERE r2.status = "active"
647
- WITH n, a, av, r1, r2
670
+ WITH n, a, av, r1, r2, is_from_profile
648
671
  """ % {"branch_filter": branch_filter}
649
672
  self.add_to_query(query)
650
673
 
651
- self.return_labels = ["n", "a", "av", "r1", "r2"]
674
+ self.return_labels = ["n", "a", "av", "r1", "r2", "is_from_profile"]
652
675
 
653
676
  # Add Is_Protected and Is_visible
654
677
  rel_isv_branch_filter, _ = self.branch.get_query_filter_path(
@@ -668,16 +691,32 @@ class NodeListGetAttributeQuery(Query):
668
691
 
669
692
  if self.include_source:
670
693
  query = """
671
- OPTIONAL MATCH (a)-[rel_source:HAS_SOURCE]-(source)
672
- WHERE all(r IN [rel_source] WHERE ( %(branch_filter)s ))
694
+ CALL (a) {
695
+ OPTIONAL MATCH (a)-[rel_source:HAS_SOURCE]-(source)
696
+ WHERE all(r IN [rel_source] WHERE ( %(branch_filter)s ))
697
+ RETURN source, rel_source
698
+ ORDER BY rel_source.branch_level DESC, rel_source.from DESC, rel_source.status ASC
699
+ LIMIT 1
700
+ }
701
+ WITH *,
702
+ CASE WHEN rel_source.status = "active" THEN source ELSE NULL END AS source,
703
+ CASE WHEN rel_source.status = "active" THEN rel_source ELSE NULL END AS rel_source
673
704
  """ % {"branch_filter": branch_filter}
674
705
  self.add_to_query(query)
675
706
  self.return_labels.extend(["source", "rel_source"])
676
707
 
677
708
  if self.include_owner:
678
709
  query = """
679
- OPTIONAL MATCH (a)-[rel_owner:HAS_OWNER]-(owner)
680
- WHERE all(r IN [rel_owner] WHERE ( %(branch_filter)s ))
710
+ CALL (a) {
711
+ OPTIONAL MATCH (a)-[rel_owner:HAS_OWNER]-(owner)
712
+ WHERE all(r IN [rel_owner] WHERE ( %(branch_filter)s ))
713
+ RETURN owner, rel_owner
714
+ ORDER BY rel_owner.branch_level DESC, rel_owner.from DESC, rel_owner.status ASC
715
+ LIMIT 1
716
+ }
717
+ WITH *,
718
+ CASE WHEN rel_owner.status = "active" THEN owner ELSE NULL END AS owner,
719
+ CASE WHEN rel_owner.status = "active" THEN rel_owner ELSE NULL END AS rel_owner
681
720
  """ % {"branch_filter": branch_filter}
682
721
  self.add_to_query(query)
683
722
  self.return_labels.extend(["owner", "rel_owner"])
@@ -708,6 +747,7 @@ class NodeListGetAttributeQuery(Query):
708
747
  def _extract_attribute_data(self, result: QueryResult) -> AttributeFromDB:
709
748
  attr = result.get_node("a")
710
749
  attr_value = result.get_node("av")
750
+ is_from_profile = result.get_as_type(label="is_from_profile", return_type=bool)
711
751
 
712
752
  data = AttributeFromDB(
713
753
  name=attr.get("name"),
@@ -719,6 +759,7 @@ class NodeListGetAttributeQuery(Query):
719
759
  updated_at=result.get_rel("r2").get("from"),
720
760
  value=attr_value.get("value"),
721
761
  is_default=attr_value.get("is_default"),
762
+ is_from_profile=is_from_profile,
722
763
  content=attr_value._properties,
723
764
  branch=self.branch.name,
724
765
  flag_properties={
@@ -942,6 +983,7 @@ class NodeListGetInfoQuery(Query):
942
983
  at=self.at, branch_agnostic=self.branch_agnostic
943
984
  )
944
985
  self.params.update(branch_params)
986
+ self.params["ids"] = self.ids
945
987
 
946
988
  query = """
947
989
  MATCH p = (root:Root)<-[:IS_PART_OF]-(n:Node)
@@ -955,16 +997,11 @@ class NodeListGetInfoQuery(Query):
955
997
  }
956
998
  WITH n1 as n, r1 as rb
957
999
  WHERE rb.status = "active"
958
- OPTIONAL MATCH profile_path = (n)-[:IS_RELATED]->(profile_r:Relationship)<-[:IS_RELATED]-(profile:Node)-[:IS_PART_OF]->(:Root)
959
- WHERE profile_r.name = "node__profile"
960
- AND profile.namespace = "Profile"
961
- AND all(r in relationships(profile_path) WHERE %(branch_filter)s and r.status = "active")
962
1000
  """ % {"branch_filter": branch_filter}
963
1001
 
964
1002
  self.add_to_query(query)
965
- self.params["ids"] = self.ids
966
1003
 
967
- self.return_labels = ["collect(profile.uuid) as profile_uuids", "n", "rb"]
1004
+ self.return_labels = ["n", "rb"]
968
1005
 
969
1006
  async def get_nodes(self, db: InfrahubDatabase, duplicate: bool = False) -> AsyncIterator[NodeToProcess]:
970
1007
  """Return all the node objects as NodeToProcess."""
@@ -978,24 +1015,11 @@ class NodeListGetInfoQuery(Query):
978
1015
  schema=schema,
979
1016
  node_id=result.get_node("n").element_id,
980
1017
  node_uuid=result.get_node("n").get("uuid"),
981
- profile_uuids=[str(puuid) for puuid in result.get("profile_uuids")],
982
1018
  updated_at=result.get_rel("rb").get("from"),
983
1019
  branch=node_branch,
984
1020
  labels=list(result.get_node("n").labels),
985
1021
  )
986
1022
 
987
- def get_profile_ids_by_node_id(self) -> dict[str, list[str]]:
988
- profile_id_map: dict[str, list[str]] = {}
989
- for result in self.results:
990
- node_id = result.get_node("n").get("uuid")
991
- profile_ids = result.get("profile_uuids")
992
- if not node_id or not profile_ids:
993
- continue
994
- if node_id not in profile_id_map:
995
- profile_id_map[node_id] = []
996
- profile_id_map[node_id].extend(profile_ids)
997
- return profile_id_map
998
-
999
1023
 
1000
1024
  class FieldAttributeRequirementType(Enum):
1001
1025
  FILTER = "filter"
@@ -1012,7 +1036,7 @@ class FieldAttributeRequirement:
1012
1036
  types: list[FieldAttributeRequirementType] = dataclass_field(default_factory=list)
1013
1037
 
1014
1038
  @property
1015
- def supports_profile(self) -> bool:
1039
+ def is_attribute_value(self) -> bool:
1016
1040
  return bool(self.field and self.field.is_attribute and self.field_attr_name in ("value", "values", "isnull"))
1017
1041
 
1018
1042
  @property
@@ -1023,26 +1047,10 @@ class FieldAttributeRequirement:
1023
1047
  def is_order(self) -> bool:
1024
1048
  return FieldAttributeRequirementType.ORDER in self.types
1025
1049
 
1026
- @property
1027
- def is_default_query_variable(self) -> str:
1028
- return f"attr{self.index}_is_default"
1029
-
1030
1050
  @property
1031
1051
  def node_value_query_variable(self) -> str:
1032
1052
  return f"attr{self.index}_node_value"
1033
1053
 
1034
- @property
1035
- def profile_value_query_variable(self) -> str:
1036
- return f"attr{self.index}_profile_value"
1037
-
1038
- @property
1039
- def profile_final_value_query_variable(self) -> str:
1040
- return f"attr{self.index}_final_profile_value"
1041
-
1042
- @property
1043
- def final_value_query_variable(self) -> str:
1044
- return f"attr{self.index}_final_value"
1045
-
1046
1054
  @property
1047
1055
  def comparison_operator(self) -> str:
1048
1056
  if self.field_attr_name == "isnull":
@@ -1178,7 +1186,6 @@ class NodeGetListQuery(Query):
1178
1186
  self.params["node_ids"] = self.filters["ids"]
1179
1187
 
1180
1188
  field_attribute_requirements = self._get_field_requirements(disable_order=disable_order)
1181
- use_profiles = any(far for far in field_attribute_requirements if far.supports_profile)
1182
1189
  await self._add_node_filter_attributes(
1183
1190
  db=db, field_attribute_requirements=field_attribute_requirements, branch_filter=branch_filter
1184
1191
  )
@@ -1190,21 +1197,11 @@ class NodeGetListQuery(Query):
1190
1197
  for far in field_attribute_requirements:
1191
1198
  if not far.is_order:
1192
1199
  continue
1193
- if far.supports_profile:
1194
- self.order_by.append(far.final_value_query_variable)
1195
- continue
1196
1200
  self.order_by.append(far.node_value_query_variable)
1197
1201
 
1198
1202
  # Always order by uuid to guarantee pagination, see https://github.com/opsmill/infrahub/pull/4704.
1199
1203
  self.order_by.append("n.uuid")
1200
1204
 
1201
- if use_profiles:
1202
- await self._add_profiles_per_node_query(db=db, branch_filter=branch_filter)
1203
- await self._add_profile_attributes(
1204
- db=db, field_attribute_requirements=field_attribute_requirements, branch_filter=branch_filter
1205
- )
1206
- await self._add_profile_rollups(field_attribute_requirements=field_attribute_requirements)
1207
-
1208
1205
  self._add_final_filter(field_attribute_requirements=field_attribute_requirements)
1209
1206
 
1210
1207
  async def _add_node_filter_attributes(
@@ -1222,8 +1219,6 @@ class NodeGetListQuery(Query):
1222
1219
 
1223
1220
  for far in field_attribute_requirements:
1224
1221
  extra_tail_properties = {far.node_value_query_variable: "value"}
1225
- if far.supports_profile:
1226
- extra_tail_properties[far.is_default_query_variable] = "is_default"
1227
1222
  subquery, subquery_params, subquery_result_name = await build_subquery_filter(
1228
1223
  db=db,
1229
1224
  field=far.field,
@@ -1234,7 +1229,6 @@ class NodeGetListQuery(Query):
1234
1229
  branch=self.branch,
1235
1230
  subquery_idx=far.index,
1236
1231
  partial_match=self.partial_match,
1237
- support_profiles=far.supports_profile,
1238
1232
  extra_tail_properties=extra_tail_properties,
1239
1233
  )
1240
1234
  for query_var in extra_tail_properties:
@@ -1274,9 +1268,6 @@ class NodeGetListQuery(Query):
1274
1268
  for far in field_attribute_requirements:
1275
1269
  if far.field is None:
1276
1270
  continue
1277
- extra_tail_properties = {}
1278
- if far.supports_profile:
1279
- extra_tail_properties[far.is_default_query_variable] = "is_default"
1280
1271
 
1281
1272
  subquery, subquery_params, _ = await build_subquery_order(
1282
1273
  db=db,
@@ -1287,11 +1278,7 @@ class NodeGetListQuery(Query):
1287
1278
  branch=self.branch,
1288
1279
  subquery_idx=far.index,
1289
1280
  result_prefix=far.node_value_query_variable,
1290
- support_profiles=far.supports_profile,
1291
- extra_tail_properties=extra_tail_properties,
1292
1281
  )
1293
- for query_var in extra_tail_properties:
1294
- self._track_variable(query_var)
1295
1282
  self._track_variable(far.node_value_query_variable)
1296
1283
  with_str = ", ".join(self._get_tracked_variables())
1297
1284
 
@@ -1305,122 +1292,11 @@ class NodeGetListQuery(Query):
1305
1292
  self.add_to_query(sort_query)
1306
1293
  self.params.update(sort_params)
1307
1294
 
1308
- async def _add_profiles_per_node_query(self, db: InfrahubDatabase, branch_filter: str) -> None:
1309
- with_str = ", ".join(self._get_tracked_variables())
1310
- froms_str = db.render_list_comprehension(items="relationships(profile_path)", item_name="from")
1311
- profiles_per_node_query = (
1312
- """
1313
- CALL (n) {
1314
- OPTIONAL MATCH profile_path = (n)-[:IS_RELATED]->(profile_r:Relationship)<-[:IS_RELATED]-(maybe_profile_n:Node)-[:IS_PART_OF]->(:Root)
1315
- WHERE profile_r.name = "node__profile"
1316
- AND all(r in relationships(profile_path) WHERE %(branch_filter)s)
1317
- WITH
1318
- maybe_profile_n,
1319
- profile_path,
1320
- reduce(br_lvl = 0, r in relationships(profile_path) | br_lvl + r.branch_level) AS branch_level,
1321
- %(froms_str)s AS froms,
1322
- all(r in relationships(profile_path) WHERE r.status = "active") AS is_active
1323
- RETURN maybe_profile_n, is_active, branch_level, froms
1324
- }
1325
- WITH %(with_str)s, maybe_profile_n, branch_level, froms, is_active
1326
- ORDER BY n.uuid, maybe_profile_n.uuid, branch_level DESC, froms[-1] DESC, froms[-2] DESC, froms[-3] DESC
1327
- WITH %(with_str)s, maybe_profile_n, collect(is_active) as ordered_is_actives
1328
- WITH %(with_str)s, CASE
1329
- WHEN ordered_is_actives[0] = True THEN maybe_profile_n ELSE NULL
1330
- END AS profile_n
1331
- CALL (profile_n) {
1332
- OPTIONAL MATCH profile_priority_path = (profile_n)-[pr1:HAS_ATTRIBUTE]->(a:Attribute)-[pr2:HAS_VALUE]->(av:AttributeValue)
1333
- WHERE a.name = "profile_priority"
1334
- AND all(r in relationships(profile_priority_path) WHERE %(branch_filter)s and r.status = "active")
1335
- RETURN av.value as profile_priority
1336
- ORDER BY pr1.branch_level + pr2.branch_level DESC, pr2.from DESC, pr1.from DESC
1337
- LIMIT 1
1338
- }
1339
- WITH %(with_str)s, profile_n, profile_priority
1340
- """
1341
- ) % {"branch_filter": branch_filter, "with_str": with_str, "froms_str": froms_str}
1342
- self.add_to_query(profiles_per_node_query)
1343
- self._track_variable("profile_n")
1344
- self._track_variable("profile_priority")
1345
-
1346
- async def _add_profile_attributes(
1347
- self, db: InfrahubDatabase, field_attribute_requirements: list[FieldAttributeRequirement], branch_filter: str
1348
- ) -> None:
1349
- attributes_queries: list[str] = []
1350
- attributes_params: dict[str, Any] = {}
1351
- profile_attributes = [far for far in field_attribute_requirements if far.supports_profile]
1352
-
1353
- for profile_attr in profile_attributes:
1354
- if not profile_attr.field:
1355
- continue
1356
- subquery, subquery_params, _ = await build_subquery_order(
1357
- db=db,
1358
- field=profile_attr.field,
1359
- node_alias="profile_n",
1360
- name=profile_attr.field_name,
1361
- order_by=profile_attr.field_attr_name,
1362
- branch_filter=branch_filter,
1363
- branch=self.branch,
1364
- subquery_idx=profile_attr.index,
1365
- result_prefix=profile_attr.profile_value_query_variable,
1366
- support_profiles=False,
1367
- )
1368
- attributes_params.update(subquery_params)
1369
- self._track_variable(profile_attr.profile_value_query_variable)
1370
- with_str = ", ".join(self._get_tracked_variables())
1371
-
1372
- attributes_queries.append("CALL (profile_n) {")
1373
- attributes_queries.append(subquery)
1374
- attributes_queries.append("}")
1375
- attributes_queries.append(f"WITH {with_str}")
1376
-
1377
- self.add_to_query(attributes_queries)
1378
- self.params.update(attributes_params)
1379
-
1380
- async def _add_profile_rollups(self, field_attribute_requirements: list[FieldAttributeRequirement]) -> None:
1381
- profile_attributes = [far for far in field_attribute_requirements if far.supports_profile]
1382
- profile_value_collects = []
1383
- for profile_attr in profile_attributes:
1384
- self._untrack_variable(profile_attr.profile_value_query_variable)
1385
- profile_value_collects.append(
1386
- f"""head(
1387
- reduce(
1388
- non_null_values = [], v in collect({profile_attr.profile_value_query_variable}) |
1389
- CASE WHEN v IS NOT NULL AND v <> "NULL" THEN non_null_values + [v] ELSE non_null_values END
1390
- )
1391
- ) as {profile_attr.profile_final_value_query_variable}"""
1392
- )
1393
- self._untrack_variable("profile_n")
1394
- self._untrack_variable("profile_priority")
1395
- profile_rollup_with_str = ", ".join(self._get_tracked_variables() + profile_value_collects)
1396
- profile_rollup_query = f"""
1397
- ORDER BY n.uuid, profile_priority ASC, profile_n.uuid ASC
1398
- WITH {profile_rollup_with_str}
1399
- """
1400
- self.add_to_query(profile_rollup_query)
1401
- for profile_attr in profile_attributes:
1402
- self._track_variable(profile_attr.profile_final_value_query_variable)
1403
-
1404
- final_value_with = []
1405
- for profile_attr in profile_attributes:
1406
- final_value_with.append(f"""
1407
- CASE
1408
- WHEN {profile_attr.is_default_query_variable} AND {profile_attr.profile_final_value_query_variable} IS NOT NULL
1409
- THEN {profile_attr.profile_final_value_query_variable}
1410
- ELSE {profile_attr.node_value_query_variable}
1411
- END AS {profile_attr.final_value_query_variable}
1412
- """)
1413
- self._untrack_variable(profile_attr.is_default_query_variable)
1414
- self._untrack_variable(profile_attr.profile_final_value_query_variable)
1415
- self._untrack_variable(profile_attr.node_value_query_variable)
1416
- final_value_with_str = ", ".join(self._get_tracked_variables() + final_value_with)
1417
- self.add_to_query(f"WITH {final_value_with_str}")
1418
-
1419
1295
  def _add_final_filter(self, field_attribute_requirements: list[FieldAttributeRequirement]) -> None:
1420
1296
  where_parts = []
1421
1297
  where_str = ""
1422
1298
  for far in field_attribute_requirements:
1423
- if not far.is_filter or not far.supports_profile:
1299
+ if not far.is_filter or not far.is_attribute_value:
1424
1300
  continue
1425
1301
  var_name = f"final_attr_value{far.index}"
1426
1302
  self.params[var_name] = far.field_attr_comparison_value
@@ -1429,11 +1305,11 @@ class NodeGetListQuery(Query):
1429
1305
  # If the any filter is an array/list
1430
1306
  var_array = f"{var_name}_array"
1431
1307
  where_parts.append(
1432
- f"any({var_array} IN ${var_name} WHERE toLower(toString({far.final_value_query_variable})) CONTAINS toLower({var_array}))"
1308
+ f"any({var_array} IN ${var_name} WHERE toLower(toString({far.node_value_query_variable})) CONTAINS toLower({var_array}))"
1433
1309
  )
1434
1310
  else:
1435
1311
  where_parts.append(
1436
- f"toLower(toString({far.final_value_query_variable})) CONTAINS toLower(toString(${var_name}))"
1312
+ f"toLower(toString({far.node_value_query_variable})) CONTAINS toLower(toString(${var_name}))"
1437
1313
  )
1438
1314
  continue
1439
1315
  if far.field and isinstance(far.field, AttributeSchema) and far.field.kind == "List":
@@ -1442,10 +1318,10 @@ class NodeGetListQuery(Query):
1442
1318
  else:
1443
1319
  self.params[var_name] = build_regex_attrs(values=[far.field_attr_comparison_value])
1444
1320
 
1445
- where_parts.append(f"toString({far.final_value_query_variable}) =~ ${var_name}")
1321
+ where_parts.append(f"toString({far.node_value_query_variable}) =~ ${var_name}")
1446
1322
  continue
1447
1323
 
1448
- where_parts.append(f"{far.final_value_query_variable} {far.comparison_operator} ${var_name}")
1324
+ where_parts.append(f"{far.node_value_query_variable} {far.comparison_operator} ${var_name}")
1449
1325
  if where_parts:
1450
1326
  where_str = "WHERE " + " AND ".join(where_parts)
1451
1327
  self.add_to_query(where_str)
@@ -350,17 +350,20 @@ class RelationshipUpdatePropertyQuery(RelationshipQuery):
350
350
 
351
351
  def __init__(
352
352
  self,
353
- properties_to_update: list[str],
354
- data: RelationshipPeerData,
353
+ rel_node_id: str,
354
+ flag_properties_to_update: dict[str, bool],
355
+ node_properties_to_update: dict[str, str],
355
356
  **kwargs,
356
357
  ):
357
- self.properties_to_update = properties_to_update
358
- self.data = data
359
-
358
+ self.rel_node_id = rel_node_id
359
+ if not flag_properties_to_update and not node_properties_to_update:
360
+ raise ValueError("Either flag_properties_to_update or node_properties_to_update must be set")
361
+ self.flag_properties_to_update = flag_properties_to_update
362
+ self.node_properties_to_update = node_properties_to_update
360
363
  super().__init__(**kwargs)
361
364
 
362
365
  async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG002
363
- self.params["rel_node_id"] = self.data.rel_node_id
366
+ self.params["rel_node_id"] = self.rel_node_id
364
367
  self.params["branch"] = self.branch.name
365
368
  self.params["branch_level"] = self.branch.hierarchy_level
366
369
  self.params["at"] = self.at.to_string()
@@ -370,36 +373,51 @@ class RelationshipUpdatePropertyQuery(RelationshipQuery):
370
373
  """
371
374
  self.add_to_query(query)
372
375
 
373
- self.query_add_all_flag_property_merge()
374
376
  self.query_add_all_node_property_merge()
377
+ self.query_add_all_flag_property_merge()
375
378
 
376
- self.query_add_all_flag_property_create()
377
379
  self.query_add_all_node_property_create()
380
+ self.query_add_all_flag_property_create()
378
381
 
379
382
  def query_add_all_flag_property_merge(self) -> None:
380
- for prop_name in self.rel._flag_properties:
381
- if prop_name in self.properties_to_update:
382
- self.query_add_flag_property_merge(name=prop_name)
383
+ for prop_name, prop_value in self.flag_properties_to_update.items():
384
+ self.query_add_flag_property_merge(name=prop_name, value=prop_value)
383
385
 
384
- def query_add_flag_property_merge(self, name: str) -> None:
386
+ def query_add_flag_property_merge(self, name: str, value: bool) -> None:
385
387
  self.add_to_query("MERGE (prop_%s:Boolean { value: $prop_%s })" % (name, name))
386
- self.params[f"prop_{name}"] = getattr(self.rel, name)
388
+ self.params[f"prop_{name}"] = value
387
389
  self.return_labels.append(f"prop_{name}")
388
390
 
389
391
  def query_add_all_node_property_merge(self) -> None:
390
- for prop_name in self.rel._node_properties:
391
- if prop_name in self.properties_to_update:
392
- self.query_add_node_property_merge(name=prop_name)
392
+ branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at)
393
+ self.params.update(branch_params)
393
394
 
394
- def query_add_node_property_merge(self, name: str) -> None:
395
- self.add_to_query("MERGE (prop_%s:Node { uuid: $prop_%s })" % (name, name))
396
- self.params[f"prop_{name}"] = getattr(self.rel, f"{name}_id")
397
- self.return_labels.append(f"prop_{name}")
395
+ for prop_name, prop_value in self.node_properties_to_update.items():
396
+ self.params[f"prop_{prop_name}"] = prop_value
397
+ if self.branch.is_default or self.branch.is_global:
398
+ node_query = """
399
+ MATCH (prop_%(prop_name)s:Node {uuid: $prop_%(prop_name)s })-[r_%(prop_name)s:IS_PART_OF]->(:Root)
400
+ WHERE r_%(prop_name)s.branch IN $branch0
401
+ AND r_%(prop_name)s.status = "active"
402
+ AND r_%(prop_name)s.from <= $at AND (r_%(prop_name)s.to IS NULL OR r_%(prop_name)s.to > $at)
403
+ WITH *
404
+ LIMIT 1
405
+ """ % {"prop_name": prop_name}
406
+ else:
407
+ node_query = """
408
+ MATCH (prop_%(prop_name)s:Node {uuid: $prop_%(prop_name)s })-[r_%(prop_name)s:IS_PART_OF]->(:Root)
409
+ WHERE all(r in [r_%(prop_name)s] WHERE %(branch_filter)s)
410
+ ORDER BY r_%(prop_name)s.branch_level DESC, r_%(prop_name)s.from DESC, r_%(prop_name)s.status ASC
411
+ LIMIT 1
412
+ WITH *
413
+ WHERE r_%(prop_name)s.status = "active"
414
+ """ % {"branch_filter": branch_filter, "prop_name": prop_name}
415
+ self.add_to_query(node_query)
416
+ self.return_labels.append(f"prop_{prop_name}")
398
417
 
399
418
  def query_add_all_flag_property_create(self) -> None:
400
- for prop_name in self.rel._flag_properties:
401
- if prop_name in self.properties_to_update:
402
- self.query_add_flag_property_create(name=prop_name)
419
+ for prop_name in self.flag_properties_to_update:
420
+ self.query_add_flag_property_create(name=prop_name)
403
421
 
404
422
  def query_add_flag_property_create(self, name: str) -> None:
405
423
  query = """
@@ -411,9 +429,8 @@ class RelationshipUpdatePropertyQuery(RelationshipQuery):
411
429
  self.add_to_query(query)
412
430
 
413
431
  def query_add_all_node_property_create(self) -> None:
414
- for prop_name in self.rel._node_properties:
415
- if prop_name in self.properties_to_update:
416
- self.query_add_node_property_create(name=prop_name)
432
+ for prop_name in self.node_properties_to_update:
433
+ self.query_add_node_property_create(name=prop_name)
417
434
 
418
435
  def query_add_node_property_create(self, name: str) -> None:
419
436
  query = """
@@ -25,12 +25,8 @@ async def build_subquery_filter(
25
25
  partial_match: bool = False,
26
26
  optional_match: bool = False,
27
27
  result_prefix: str = "filter",
28
- support_profiles: bool = False,
29
28
  extra_tail_properties: dict[str, str] | None = None,
30
29
  ) -> tuple[str, dict[str, Any], str]:
31
- support_profiles = (
32
- support_profiles and field and field.is_attribute and filter_name in ("value", "values", "isnull")
33
- )
34
30
  params = {}
35
31
  prefix = f"{result_prefix}{subquery_idx}"
36
32
 
@@ -52,7 +48,6 @@ async def build_subquery_filter(
52
48
  param_prefix=prefix,
53
49
  db=db,
54
50
  partial_match=partial_match,
55
- support_profiles=support_profiles,
56
51
  )
57
52
  params.update(field_params)
58
53
 
@@ -109,10 +104,8 @@ async def build_subquery_order(
109
104
  branch: Branch = None,
110
105
  subquery_idx: int = 1,
111
106
  result_prefix: str | None = None,
112
- support_profiles: bool = False,
113
107
  extra_tail_properties: dict[str, str] | None = None,
114
108
  ) -> tuple[str, dict[str, Any], str]:
115
- support_profiles = support_profiles and field and field.is_attribute and order_by in ("value", "values")
116
109
  params = {}
117
110
  prefix = result_prefix or f"order{subquery_idx}"
118
111
 
@@ -124,7 +117,6 @@ async def build_subquery_order(
124
117
  filter_value=None,
125
118
  branch=branch,
126
119
  param_prefix=prefix,
127
- support_profiles=support_profiles,
128
120
  )
129
121
  params.update(field_params)
130
122
 
infrahub/core/registry.py CHANGED
@@ -113,8 +113,8 @@ class Registry:
113
113
  return True
114
114
  return False
115
115
 
116
- def get_node_schema(self, name: str, branch: Branch | str | None = None) -> NodeSchema:
117
- return self.schema.get_node_schema(name=name, branch=branch)
116
+ def get_node_schema(self, name: str, branch: Branch | str | None = None, duplicate: bool = False) -> NodeSchema:
117
+ return self.schema.get_node_schema(name=name, branch=branch, duplicate=duplicate)
118
118
 
119
119
  def get_data_type(self, name: str) -> type[InfrahubDataType]:
120
120
  if name not in self.data_type:
@@ -40,7 +40,7 @@ class RelationshipCountConstraint(RelationshipManagerConstraintInterface):
40
40
  # peer_ids_present_database_only:
41
41
  # relationship to be deleted, need to check if the schema on the other side has a min_count defined
42
42
  # TODO see how to manage Generic node
43
- peer_schema = registry.schema.get(name=relm.schema.peer, branch=branch)
43
+ peer_schema = registry.schema.get(name=relm.schema.peer, branch=branch, duplicate=False)
44
44
  peer_rels = peer_schema.get_relationships_by_identifier(id=relm.schema.get_identifier())
45
45
  if not peer_rels:
46
46
  return