infrahub-server 1.2.11__py3-none-any.whl → 1.3.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 (211) hide show
  1. infrahub/actions/constants.py +130 -0
  2. infrahub/actions/gather.py +114 -0
  3. infrahub/actions/models.py +243 -0
  4. infrahub/actions/parsers.py +104 -0
  5. infrahub/actions/schema.py +393 -0
  6. infrahub/actions/tasks.py +119 -0
  7. infrahub/actions/triggers.py +21 -0
  8. infrahub/branch/__init__.py +0 -0
  9. infrahub/branch/tasks.py +29 -0
  10. infrahub/branch/triggers.py +22 -0
  11. infrahub/cli/db.py +3 -4
  12. infrahub/computed_attribute/gather.py +3 -1
  13. infrahub/computed_attribute/tasks.py +23 -29
  14. infrahub/core/account.py +24 -47
  15. infrahub/core/attribute.py +13 -15
  16. infrahub/core/constants/__init__.py +10 -0
  17. infrahub/core/constants/database.py +1 -0
  18. infrahub/core/constants/infrahubkind.py +9 -0
  19. infrahub/core/constraint/node/runner.py +3 -1
  20. infrahub/core/convert_object_type/__init__.py +0 -0
  21. infrahub/core/convert_object_type/conversion.py +124 -0
  22. infrahub/core/convert_object_type/schema_mapping.py +56 -0
  23. infrahub/core/diff/coordinator.py +8 -1
  24. infrahub/core/diff/query/all_conflicts.py +1 -5
  25. infrahub/core/diff/query/artifact.py +10 -20
  26. infrahub/core/diff/query/delete_query.py +8 -4
  27. infrahub/core/diff/query/diff_get.py +3 -6
  28. infrahub/core/diff/query/field_specifiers.py +1 -1
  29. infrahub/core/diff/query/field_summary.py +2 -4
  30. infrahub/core/diff/query/merge.py +72 -125
  31. infrahub/core/diff/query/save.py +83 -68
  32. infrahub/core/diff/query/summary_counts_enricher.py +34 -54
  33. infrahub/core/diff/query/time_range_query.py +0 -1
  34. infrahub/core/diff/repository/repository.py +4 -0
  35. infrahub/core/graph/__init__.py +1 -1
  36. infrahub/core/manager.py +14 -11
  37. infrahub/core/migrations/graph/__init__.py +6 -0
  38. infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
  39. infrahub/core/migrations/graph/m012_convert_account_generic.py +1 -1
  40. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -6
  41. infrahub/core/migrations/graph/m015_diff_format_update.py +1 -2
  42. infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -2
  43. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
  44. infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
  45. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
  46. infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +2 -2
  47. infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
  48. infrahub/core/migrations/graph/m028_delete_diffs.py +1 -2
  49. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +662 -0
  50. infrahub/core/migrations/graph/m030_illegal_edges.py +82 -0
  51. infrahub/core/migrations/query/attribute_add.py +14 -11
  52. infrahub/core/migrations/query/attribute_rename.py +6 -11
  53. infrahub/core/migrations/query/delete_element_in_schema.py +19 -17
  54. infrahub/core/migrations/query/node_duplicate.py +19 -21
  55. infrahub/core/migrations/query/relationship_duplicate.py +19 -18
  56. infrahub/core/migrations/schema/node_attribute_remove.py +4 -8
  57. infrahub/core/migrations/schema/node_remove.py +19 -20
  58. infrahub/core/models.py +29 -2
  59. infrahub/core/node/__init__.py +131 -28
  60. infrahub/core/node/base.py +1 -1
  61. infrahub/core/node/create.py +211 -0
  62. infrahub/core/node/resource_manager/number_pool.py +31 -5
  63. infrahub/core/node/standard.py +6 -1
  64. infrahub/core/path.py +15 -1
  65. infrahub/core/protocols.py +57 -0
  66. infrahub/core/protocols_base.py +3 -0
  67. infrahub/core/query/__init__.py +2 -2
  68. infrahub/core/query/delete.py +3 -3
  69. infrahub/core/query/diff.py +19 -32
  70. infrahub/core/query/ipam.py +10 -20
  71. infrahub/core/query/node.py +29 -47
  72. infrahub/core/query/relationship.py +55 -34
  73. infrahub/core/query/resource_manager.py +1 -2
  74. infrahub/core/query/standard_node.py +19 -5
  75. infrahub/core/query/subquery.py +2 -4
  76. infrahub/core/relationship/constraints/count.py +10 -9
  77. infrahub/core/relationship/constraints/interface.py +2 -1
  78. infrahub/core/relationship/constraints/peer_kind.py +2 -1
  79. infrahub/core/relationship/constraints/peer_parent.py +56 -0
  80. infrahub/core/relationship/constraints/peer_relatives.py +72 -0
  81. infrahub/core/relationship/constraints/profiles_kind.py +1 -1
  82. infrahub/core/relationship/model.py +4 -1
  83. infrahub/core/schema/__init__.py +2 -1
  84. infrahub/core/schema/attribute_parameters.py +160 -0
  85. infrahub/core/schema/attribute_schema.py +130 -7
  86. infrahub/core/schema/basenode_schema.py +27 -3
  87. infrahub/core/schema/definitions/core/__init__.py +29 -1
  88. infrahub/core/schema/definitions/core/group.py +45 -0
  89. infrahub/core/schema/definitions/core/resource_pool.py +9 -0
  90. infrahub/core/schema/definitions/internal.py +43 -5
  91. infrahub/core/schema/generated/attribute_schema.py +16 -3
  92. infrahub/core/schema/generated/relationship_schema.py +11 -1
  93. infrahub/core/schema/manager.py +7 -2
  94. infrahub/core/schema/schema_branch.py +109 -12
  95. infrahub/core/validators/__init__.py +15 -2
  96. infrahub/core/validators/attribute/choices.py +1 -3
  97. infrahub/core/validators/attribute/enum.py +1 -3
  98. infrahub/core/validators/attribute/kind.py +1 -3
  99. infrahub/core/validators/attribute/length.py +13 -7
  100. infrahub/core/validators/attribute/min_max.py +118 -0
  101. infrahub/core/validators/attribute/number_pool.py +106 -0
  102. infrahub/core/validators/attribute/optional.py +1 -4
  103. infrahub/core/validators/attribute/regex.py +5 -6
  104. infrahub/core/validators/attribute/unique.py +1 -3
  105. infrahub/core/validators/determiner.py +18 -2
  106. infrahub/core/validators/enum.py +12 -0
  107. infrahub/core/validators/node/hierarchy.py +3 -6
  108. infrahub/core/validators/query.py +1 -3
  109. infrahub/core/validators/relationship/count.py +6 -12
  110. infrahub/core/validators/relationship/optional.py +2 -4
  111. infrahub/core/validators/relationship/peer.py +177 -12
  112. infrahub/core/validators/tasks.py +1 -1
  113. infrahub/core/validators/uniqueness/query.py +5 -9
  114. infrahub/database/__init__.py +12 -4
  115. infrahub/database/validation.py +100 -0
  116. infrahub/dependencies/builder/constraint/grouped/node_runner.py +4 -0
  117. infrahub/dependencies/builder/constraint/relationship_manager/peer_parent.py +8 -0
  118. infrahub/dependencies/builder/constraint/relationship_manager/peer_relatives.py +8 -0
  119. infrahub/dependencies/builder/constraint/schema/aggregated.py +2 -0
  120. infrahub/dependencies/builder/constraint/schema/relationship_peer.py +8 -0
  121. infrahub/dependencies/builder/diff/deserializer.py +1 -1
  122. infrahub/dependencies/registry.py +4 -0
  123. infrahub/events/group_action.py +1 -0
  124. infrahub/events/models.py +1 -1
  125. infrahub/git/base.py +5 -3
  126. infrahub/git/integrator.py +96 -5
  127. infrahub/git/tasks.py +1 -0
  128. infrahub/graphql/analyzer.py +139 -18
  129. infrahub/graphql/manager.py +4 -0
  130. infrahub/graphql/mutations/action.py +164 -0
  131. infrahub/graphql/mutations/convert_object_type.py +71 -0
  132. infrahub/graphql/mutations/main.py +25 -176
  133. infrahub/graphql/mutations/proposed_change.py +20 -17
  134. infrahub/graphql/mutations/relationship.py +32 -0
  135. infrahub/graphql/mutations/resource_manager.py +63 -7
  136. infrahub/graphql/queries/convert_object_type_mapping.py +34 -0
  137. infrahub/graphql/queries/resource_manager.py +7 -1
  138. infrahub/graphql/resolvers/many_relationship.py +1 -1
  139. infrahub/graphql/resolvers/resolver.py +2 -2
  140. infrahub/graphql/resolvers/single_relationship.py +1 -1
  141. infrahub/graphql/schema.py +6 -0
  142. infrahub/menu/menu.py +34 -2
  143. infrahub/message_bus/messages/__init__.py +0 -10
  144. infrahub/message_bus/operations/__init__.py +0 -8
  145. infrahub/message_bus/operations/refresh/registry.py +4 -7
  146. infrahub/patch/queries/delete_duplicated_edges.py +45 -39
  147. infrahub/pools/models.py +14 -0
  148. infrahub/pools/number.py +5 -3
  149. infrahub/pools/registration.py +22 -0
  150. infrahub/pools/tasks.py +126 -0
  151. infrahub/prefect_server/models.py +1 -19
  152. infrahub/proposed_change/models.py +68 -3
  153. infrahub/proposed_change/tasks.py +911 -34
  154. infrahub/schema/__init__.py +0 -0
  155. infrahub/schema/tasks.py +27 -0
  156. infrahub/schema/triggers.py +23 -0
  157. infrahub/task_manager/models.py +10 -6
  158. infrahub/trigger/catalogue.py +6 -0
  159. infrahub/trigger/models.py +23 -6
  160. infrahub/trigger/setup.py +26 -2
  161. infrahub/trigger/tasks.py +4 -2
  162. infrahub/types.py +6 -0
  163. infrahub/webhook/tasks.py +6 -9
  164. infrahub/workflows/catalogue.py +103 -1
  165. infrahub_sdk/client.py +43 -10
  166. infrahub_sdk/ctl/generator.py +4 -4
  167. infrahub_sdk/ctl/repository.py +1 -1
  168. infrahub_sdk/node/__init__.py +39 -0
  169. infrahub_sdk/node/attribute.py +122 -0
  170. infrahub_sdk/node/constants.py +21 -0
  171. infrahub_sdk/{node.py → node/node.py} +158 -803
  172. infrahub_sdk/node/parsers.py +15 -0
  173. infrahub_sdk/node/property.py +24 -0
  174. infrahub_sdk/node/related_node.py +266 -0
  175. infrahub_sdk/node/relationship.py +302 -0
  176. infrahub_sdk/protocols.py +112 -0
  177. infrahub_sdk/protocols_base.py +34 -2
  178. infrahub_sdk/pytest_plugin/items/python_transform.py +2 -1
  179. infrahub_sdk/query_groups.py +17 -5
  180. infrahub_sdk/schema/main.py +1 -0
  181. infrahub_sdk/schema/repository.py +16 -0
  182. infrahub_sdk/spec/object.py +1 -1
  183. infrahub_sdk/store.py +1 -1
  184. infrahub_sdk/testing/schemas/car_person.py +1 -0
  185. infrahub_sdk/utils.py +7 -20
  186. infrahub_sdk/yaml.py +6 -5
  187. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/METADATA +5 -5
  188. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/RECORD +197 -168
  189. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/WHEEL +1 -1
  190. infrahub_testcontainers/container.py +239 -65
  191. infrahub_testcontainers/docker-compose-cluster.test.yml +321 -0
  192. infrahub_testcontainers/docker-compose.test.yml +2 -1
  193. infrahub_testcontainers/helpers.py +23 -3
  194. infrahub_testcontainers/plugin.py +9 -0
  195. infrahub/message_bus/messages/check_generator_run.py +0 -26
  196. infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
  197. infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
  198. infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
  199. infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
  200. infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
  201. infrahub/message_bus/operations/check/__init__.py +0 -3
  202. infrahub/message_bus/operations/check/generator.py +0 -156
  203. infrahub/message_bus/operations/finalize/__init__.py +0 -3
  204. infrahub/message_bus/operations/finalize/validator.py +0 -133
  205. infrahub/message_bus/operations/requests/__init__.py +0 -9
  206. infrahub/message_bus/operations/requests/generator_definition.py +0 -140
  207. infrahub/message_bus/operations/requests/proposed_change.py +0 -629
  208. infrahub/patch/queries/consolidate_duplicated_nodes.py +0 -109
  209. /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
  210. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/LICENSE.txt +0 -0
  211. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/entry_points.txt +0 -0
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from infrahub.core.constants import PathType
6
6
  from infrahub.core.path import DataPath, GroupedDataPaths
7
+ from infrahub.core.validators.enum import ConstraintIdentifier
7
8
 
8
9
  from ..interface import ConstraintCheckerInterface
9
10
  from ..shared import AttributeSchemaValidatorQuery
@@ -23,13 +24,12 @@ class AttributeLengthUpdateValidatorQuery(AttributeSchemaValidatorQuery):
23
24
  self.params.update(branch_params)
24
25
 
25
26
  self.params["attr_name"] = self.attribute_schema.name
26
- self.params["min_length"] = self.attribute_schema.min_length
27
- self.params["max_length"] = self.attribute_schema.max_length
27
+ self.params["min_length"] = self.attribute_schema.get_min_length()
28
+ self.params["max_length"] = self.attribute_schema.get_max_length()
28
29
 
29
30
  query = """
30
31
  MATCH (n:%(node_kind)s)
31
- CALL {
32
- WITH n
32
+ CALL (n) {
33
33
  MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
34
34
  WHERE all(
35
35
  r in relationships(path)
@@ -40,7 +40,6 @@ class AttributeLengthUpdateValidatorQuery(AttributeSchemaValidatorQuery):
40
40
  LIMIT 1
41
41
  }
42
42
  WITH full_path, node, attribute_value, value_relationship
43
- WITH full_path, node, attribute_value, value_relationship
44
43
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
45
44
  AND (
46
45
  (toInteger($min_length) IS NOT NULL AND size(attribute_value) < toInteger($min_length))
@@ -79,14 +78,21 @@ class AttributeLengthChecker(ConstraintCheckerInterface):
79
78
  return "attribute.length.update"
80
79
 
81
80
  def supports(self, request: SchemaConstraintValidatorRequest) -> bool:
82
- return request.constraint_name in ("attribute.min_length.update", "attribute.max_length.update")
81
+ return request.constraint_name in (
82
+ "attribute.min_length.update",
83
+ "attribute.max_length.update",
84
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_LENGTH_UPDATE.value,
85
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_LENGTH_UPDATE.value,
86
+ )
83
87
 
84
88
  async def check(self, request: SchemaConstraintValidatorRequest) -> list[GroupedDataPaths]:
85
89
  grouped_data_paths_list: list[GroupedDataPaths] = []
86
90
  if not request.schema_path.field_name:
87
91
  raise ValueError("field_name is not defined")
88
92
  attribute_schema = request.node_schema.get_attribute(name=request.schema_path.field_name)
89
- if attribute_schema.min_length is None and attribute_schema.max_length is True:
93
+ min_length = attribute_schema.get_min_length()
94
+ max_length = attribute_schema.get_max_length()
95
+ if min_length is None and max_length is None:
90
96
  return grouped_data_paths_list
91
97
 
92
98
  for query_class in self.query_classes:
@@ -0,0 +1,118 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from infrahub.core.constants import PathType
6
+ from infrahub.core.path import DataPath, GroupedDataPaths
7
+ from infrahub.core.schema.attribute_parameters import NumberAttributeParameters
8
+ from infrahub.core.validators.enum import ConstraintIdentifier
9
+
10
+ from ..interface import ConstraintCheckerInterface
11
+ from ..shared import AttributeSchemaValidatorQuery
12
+
13
+ if TYPE_CHECKING:
14
+ from infrahub.core.branch import Branch
15
+ from infrahub.database import InfrahubDatabase
16
+
17
+ from ..model import SchemaConstraintValidatorRequest
18
+
19
+
20
+ class AttributeNumberUpdateValidatorQuery(AttributeSchemaValidatorQuery):
21
+ name: str = "attribute_constraints_number_validator"
22
+
23
+ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
24
+ branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string())
25
+ self.params.update(branch_params)
26
+
27
+ if not isinstance(self.attribute_schema.parameters, NumberAttributeParameters):
28
+ raise ValueError("attribute parameters are not a NumberAttributeParameters")
29
+
30
+ self.params["attr_name"] = self.attribute_schema.name
31
+ self.params["min_value"] = self.attribute_schema.parameters.min_value
32
+ self.params["max_value"] = self.attribute_schema.parameters.max_value
33
+ self.params["excluded_values"] = self.attribute_schema.parameters.get_excluded_single_values()
34
+ self.params["excluded_ranges"] = self.attribute_schema.parameters.get_excluded_ranges()
35
+
36
+ query = """
37
+ MATCH (n:%(node_kind)s)
38
+ CALL (n) {
39
+ MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
40
+ WHERE all(
41
+ r in relationships(path)
42
+ WHERE %(branch_filter)s
43
+ )
44
+ RETURN path as full_path, n as node, rv as value_relationship, av.value as attribute_value
45
+ ORDER BY rv.branch_level DESC, ra.branch_level DESC, rr.branch_level DESC, rv.from DESC, ra.from DESC, rr.from DESC
46
+ LIMIT 1
47
+ }
48
+ WITH full_path, node, attribute_value, value_relationship
49
+ WHERE all(r in relationships(full_path) WHERE r.status = "active")
50
+ AND (
51
+ (toInteger($min_value) IS NOT NULL AND attribute_value < toInteger($min_value))
52
+ OR (toInteger($max_value) IS NOT NULL AND attribute_value > toInteger($max_value))
53
+ OR (size($excluded_values) > 0 AND attribute_value IN $excluded_values)
54
+ OR (size($excluded_ranges) > 0 AND any(range in $excluded_ranges WHERE attribute_value >= range[0] AND attribute_value <= range[1]))
55
+ )
56
+ """ % {"branch_filter": branch_filter, "node_kind": self.node_schema.kind}
57
+
58
+ self.add_to_query(query)
59
+ self.return_labels = ["node.uuid", "value_relationship", "attribute_value"]
60
+
61
+ async def get_paths(self) -> GroupedDataPaths:
62
+ grouped_data_paths = GroupedDataPaths()
63
+ for result in self.results:
64
+ grouped_data_paths.add_data_path(
65
+ DataPath(
66
+ branch=str(result.get("value_relationship").get("branch")),
67
+ path_type=PathType.ATTRIBUTE,
68
+ node_id=str(result.get("node.uuid")),
69
+ field_name=self.attribute_schema.name,
70
+ kind=self.node_schema.kind,
71
+ value=result.get("attribute_value"),
72
+ ),
73
+ )
74
+
75
+ return grouped_data_paths
76
+
77
+
78
+ class AttributeNumberChecker(ConstraintCheckerInterface):
79
+ query_classes = [AttributeNumberUpdateValidatorQuery]
80
+
81
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
82
+ self.db = db
83
+ self.branch = branch
84
+
85
+ @property
86
+ def name(self) -> str:
87
+ return "attribute.number.update"
88
+
89
+ def supports(self, request: SchemaConstraintValidatorRequest) -> bool:
90
+ return request.constraint_name in (
91
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_VALUE_UPDATE.value,
92
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_VALUE_UPDATE.value,
93
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_EXCLUDED_VALUES_UPDATE.value,
94
+ )
95
+
96
+ async def check(self, request: SchemaConstraintValidatorRequest) -> list[GroupedDataPaths]:
97
+ grouped_data_paths_list: list[GroupedDataPaths] = []
98
+ if not request.schema_path.field_name:
99
+ raise ValueError("field_name is not defined")
100
+ attribute_schema = request.node_schema.get_attribute(name=request.schema_path.field_name)
101
+ if not isinstance(attribute_schema.parameters, NumberAttributeParameters):
102
+ raise ValueError("attribute parameters are not a NumberAttributeParameters")
103
+
104
+ if (
105
+ attribute_schema.parameters.min_value is None
106
+ and attribute_schema.parameters.max_value is None
107
+ and attribute_schema.parameters.excluded_values is None
108
+ ):
109
+ return grouped_data_paths_list
110
+
111
+ for query_class in self.query_classes:
112
+ # TODO add exception handling
113
+ query = await query_class.init(
114
+ db=self.db, branch=self.branch, node_schema=request.node_schema, schema_path=request.schema_path
115
+ )
116
+ await query.execute(db=self.db)
117
+ grouped_data_paths_list.append(await query.get_paths())
118
+ return grouped_data_paths_list
@@ -0,0 +1,106 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from infrahub.core.constants import PathType
6
+ from infrahub.core.path import DataPath, GroupedDataPaths
7
+ from infrahub.core.schema.attribute_parameters import NumberPoolParameters
8
+ from infrahub.core.validators.enum import ConstraintIdentifier
9
+
10
+ from ..interface import ConstraintCheckerInterface
11
+ from ..shared import AttributeSchemaValidatorQuery
12
+
13
+ if TYPE_CHECKING:
14
+ from infrahub.core.branch import Branch
15
+ from infrahub.database import InfrahubDatabase
16
+
17
+ from ..model import SchemaConstraintValidatorRequest
18
+
19
+
20
+ class AttributeNumberPoolUpdateValidatorQuery(AttributeSchemaValidatorQuery):
21
+ name: str = "attribute_constraints_numberpool_validator"
22
+
23
+ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
24
+ branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string())
25
+ self.params.update(branch_params)
26
+
27
+ if not isinstance(self.attribute_schema.parameters, NumberPoolParameters):
28
+ raise ValueError("attribute parameters are not a NumberPoolParameters")
29
+
30
+ self.params["attr_name"] = self.attribute_schema.name
31
+ self.params["start_range"] = self.attribute_schema.parameters.start_range
32
+ self.params["end_range"] = self.attribute_schema.parameters.end_range
33
+
34
+ query = """
35
+ MATCH (n:%(node_kind)s)
36
+ CALL (n) {
37
+ MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
38
+ WHERE all(
39
+ r in relationships(path)
40
+ WHERE %(branch_filter)s
41
+ )
42
+ RETURN path as full_path, n as node, rv as value_relationship, av.value as attribute_value
43
+ ORDER BY rv.branch_level DESC, ra.branch_level DESC, rr.branch_level DESC, rv.from DESC, ra.from DESC, rr.from DESC
44
+ LIMIT 1
45
+ }
46
+ WITH full_path, node, attribute_value, value_relationship
47
+ WHERE all(r in relationships(full_path) WHERE r.status = "active")
48
+ AND (
49
+ (toInteger($start_range) IS NOT NULL AND attribute_value < toInteger($start_range))
50
+ OR (toInteger($end_range) IS NOT NULL AND attribute_value > toInteger($end_range))
51
+ )
52
+ """ % {"branch_filter": branch_filter, "node_kind": self.node_schema.kind}
53
+
54
+ self.add_to_query(query)
55
+ self.return_labels = ["node.uuid", "value_relationship", "attribute_value"]
56
+
57
+ async def get_paths(self) -> GroupedDataPaths:
58
+ grouped_data_paths = GroupedDataPaths()
59
+ for result in self.results:
60
+ grouped_data_paths.add_data_path(
61
+ DataPath(
62
+ branch=str(result.get("value_relationship").get("branch")),
63
+ path_type=PathType.ATTRIBUTE,
64
+ node_id=str(result.get("node.uuid")),
65
+ field_name=self.attribute_schema.name,
66
+ kind=self.node_schema.kind,
67
+ value=result.get("attribute_value"),
68
+ ),
69
+ )
70
+
71
+ return grouped_data_paths
72
+
73
+
74
+ class AttributeNumberPoolChecker(ConstraintCheckerInterface):
75
+ query_classes = [AttributeNumberPoolUpdateValidatorQuery]
76
+
77
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
78
+ self.db = db
79
+ self.branch = branch
80
+
81
+ @property
82
+ def name(self) -> str:
83
+ return "attribute.number.update"
84
+
85
+ def supports(self, request: SchemaConstraintValidatorRequest) -> bool:
86
+ return request.constraint_name in (
87
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_START_RANGE_UPDATE.value,
88
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_END_RANGE_UPDATE.value,
89
+ )
90
+
91
+ async def check(self, request: SchemaConstraintValidatorRequest) -> list[GroupedDataPaths]:
92
+ grouped_data_paths_list: list[GroupedDataPaths] = []
93
+ if not request.schema_path.field_name:
94
+ raise ValueError("field_name is not defined")
95
+ attribute_schema = request.node_schema.get_attribute(name=request.schema_path.field_name)
96
+ if not isinstance(attribute_schema.parameters, NumberPoolParameters):
97
+ raise ValueError("attribute parameters are not a NumberPoolParameters")
98
+
99
+ for query_class in self.query_classes:
100
+ # TODO add exception handling
101
+ query = await query_class.init(
102
+ db=self.db, branch=self.branch, node_schema=request.node_schema, schema_path=request.schema_path
103
+ )
104
+ await query.execute(db=self.db)
105
+ grouped_data_paths_list.append(await query.get_paths())
106
+ return grouped_data_paths_list
@@ -27,8 +27,7 @@ class AttributeOptionalUpdateValidatorQuery(AttributeSchemaValidatorQuery):
27
27
 
28
28
  query = """
29
29
  MATCH (n:%(node_kind)s)
30
- CALL {
31
- WITH n
30
+ CALL (n) {
32
31
  MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
33
32
  WHERE all(
34
33
  r in relationships(path)
@@ -39,7 +38,6 @@ class AttributeOptionalUpdateValidatorQuery(AttributeSchemaValidatorQuery):
39
38
  LIMIT 1
40
39
  }
41
40
  WITH full_path, node, attribute_value, value_relationship
42
- WITH full_path, node, attribute_value, value_relationship
43
41
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
44
42
  AND (attribute_value IS NULL OR attribute_value = $null_value)
45
43
  """ % {"branch_filter": branch_filter, "node_kind": self.node_schema.kind}
@@ -86,7 +84,6 @@ class AttributeOptionalChecker(ConstraintCheckerInterface):
86
84
  return grouped_data_paths_list
87
85
 
88
86
  for query_class in self.query_classes:
89
- # TODO add exception handling
90
87
  query = await query_class.init(
91
88
  db=self.db, branch=self.branch, node_schema=request.node_schema, schema_path=request.schema_path
92
89
  )
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from infrahub.core.constants import NULL_VALUE, PathType
6
6
  from infrahub.core.path import DataPath, GroupedDataPaths
7
+ from infrahub.core.validators.enum import ConstraintIdentifier
7
8
 
8
9
  from ..interface import ConstraintCheckerInterface
9
10
  from ..shared import AttributeSchemaValidatorQuery
@@ -23,12 +24,11 @@ class AttributeRegexUpdateValidatorQuery(AttributeSchemaValidatorQuery):
23
24
  self.params.update(branch_params)
24
25
 
25
26
  self.params["attr_name"] = self.attribute_schema.name
26
- self.params["attr_value_regex"] = self.attribute_schema.regex
27
+ self.params["attr_value_regex"] = self.attribute_schema.get_regex()
27
28
  self.params["null_value"] = NULL_VALUE
28
29
  query = """
29
30
  MATCH p = (n:%(node_kind)s)
30
- CALL {
31
- WITH n
31
+ CALL (n) {
32
32
  MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
33
33
  WHERE all(
34
34
  r in relationships(path)
@@ -39,7 +39,6 @@ class AttributeRegexUpdateValidatorQuery(AttributeSchemaValidatorQuery):
39
39
  LIMIT 1
40
40
  }
41
41
  WITH full_path, node, attribute_value, value_relationship
42
- WITH full_path, node, attribute_value, value_relationship
43
42
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
44
43
  AND attribute_value <> $null_value
45
44
  AND NOT attribute_value =~ $attr_value_regex
@@ -79,14 +78,14 @@ class AttributeRegexChecker(ConstraintCheckerInterface):
79
78
  return "attribute.regex.update"
80
79
 
81
80
  def supports(self, request: SchemaConstraintValidatorRequest) -> bool:
82
- return request.constraint_name == self.name
81
+ return request.constraint_name in (self.name, ConstraintIdentifier.ATTRIBUTE_PARAMETERS_REGEX_UPDATE.value)
83
82
 
84
83
  async def check(self, request: SchemaConstraintValidatorRequest) -> list[GroupedDataPaths]:
85
84
  grouped_data_paths_list: list[GroupedDataPaths] = []
86
85
  if not request.schema_path.field_name:
87
86
  raise ValueError("field_name is not defined")
88
87
  attribute_schema = request.node_schema.get_attribute(name=request.schema_path.field_name)
89
- if not attribute_schema.regex:
88
+ if not attribute_schema.get_regex():
90
89
  return grouped_data_paths_list
91
90
 
92
91
  for query_class in self.query_classes:
@@ -31,12 +31,10 @@ class AttributeUniqueUpdateValidatorQuery(AttributeSchemaValidatorQuery):
31
31
  query = """
32
32
  MATCH (potential_node:Node)
33
33
  WHERE $node_kind IN LABELS(potential_node)
34
- CALL {
35
- WITH potential_node
34
+ CALL (potential_node) {
36
35
  MATCH potential_path = (potential_node)-[:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name })-[potential_value_relationship:HAS_VALUE]-(potential_value:AttributeValue)
37
36
  WHERE all(r IN relationships(potential_path) WHERE (%(branch_filter)s))
38
37
  WITH
39
- potential_node,
40
38
  potential_value,
41
39
  potential_value_relationship,
42
40
  potential_path,
@@ -1,14 +1,20 @@
1
+ from typing import TYPE_CHECKING, Any
2
+
1
3
  from infrahub.core.constants import RelationshipKind, SchemaPathType
2
4
  from infrahub.core.constants.schema import UpdateSupport
3
5
  from infrahub.core.diff.model.path import NodeDiffFieldSummary
4
6
  from infrahub.core.models import SchemaUpdateConstraintInfo
5
7
  from infrahub.core.path import SchemaPath
6
8
  from infrahub.core.schema import AttributeSchema, MainSchemaTypes
9
+ from infrahub.core.schema.attribute_parameters import AttributeParameters
7
10
  from infrahub.core.schema.relationship_schema import RelationshipSchema
8
11
  from infrahub.core.schema.schema_branch import SchemaBranch
9
12
  from infrahub.core.validators import CONSTRAINT_VALIDATOR_MAP
10
13
  from infrahub.log import get_logger
11
14
 
15
+ if TYPE_CHECKING:
16
+ from pydantic.fields import FieldInfo
17
+
12
18
  LOG = get_logger(__name__)
13
19
 
14
20
 
@@ -133,7 +139,17 @@ class ConstraintValidatorDeterminer:
133
139
  self, schema: MainSchemaTypes, field: AttributeSchema | RelationshipSchema
134
140
  ) -> list[SchemaUpdateConstraintInfo]:
135
141
  constraints: list[SchemaUpdateConstraintInfo] = []
136
- for prop_name, prop_field_info in field.model_fields.items():
142
+ prop_details_list: list[tuple[str, FieldInfo, Any]] = []
143
+ for p_name, p_info in field.model_fields.items():
144
+ p_value = getattr(field, p_name)
145
+ if isinstance(p_value, AttributeParameters):
146
+ for parameter_name, parameter_field_info in p_value.model_fields.items():
147
+ parameter_value = getattr(p_value, parameter_name)
148
+ prop_details_list.append((f"{p_name}.{parameter_name}", parameter_field_info, parameter_value))
149
+ else:
150
+ prop_details_list.append((p_name, p_info, p_value))
151
+
152
+ for prop_name, prop_field_info, prop_value in prop_details_list:
137
153
  if not prop_field_info.json_schema_extra or not isinstance(prop_field_info.json_schema_extra, dict):
138
154
  continue
139
155
 
@@ -141,7 +157,7 @@ class ConstraintValidatorDeterminer:
141
157
  if prop_field_update != UpdateSupport.VALIDATE_CONSTRAINT.value:
142
158
  continue
143
159
 
144
- if getattr(field, prop_name) is None:
160
+ if prop_value is None:
145
161
  continue
146
162
 
147
163
  path_type = SchemaPathType.ATTRIBUTE
@@ -0,0 +1,12 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ConstraintIdentifier(str, Enum):
5
+ ATTRIBUTE_PARAMETERS_REGEX_UPDATE = "attribute.parameters.regex.update"
6
+ ATTRIBUTE_PARAMETERS_MIN_LENGTH_UPDATE = "attribute.parameters.min_length.update"
7
+ ATTRIBUTE_PARAMETERS_MAX_LENGTH_UPDATE = "attribute.parameters.max_length.update"
8
+ ATTRIBUTE_PARAMETERS_MIN_VALUE_UPDATE = "attribute.parameters.min_value.update"
9
+ ATTRIBUTE_PARAMETERS_MAX_VALUE_UPDATE = "attribute.parameters.max_value.update"
10
+ ATTRIBUTE_PARAMETERS_EXCLUDED_VALUES_UPDATE = "attribute.parameters.excluded_values.update"
11
+ ATTRIBUTE_PARAMETERS_END_RANGE_UPDATE = "attribute.parameters.end_range.update"
12
+ ATTRIBUTE_PARAMETERS_START_RANGE_UPDATE = "attribute.parameters.start_range.update"
@@ -59,8 +59,7 @@ class NodeHierarchyUpdateValidatorQuery(SchemaValidatorQuery):
59
59
  # ruff: noqa: E501
60
60
  query = """
61
61
  MATCH (n:%(node_kind)s)
62
- CALL {
63
- WITH n
62
+ CALL (n) {
64
63
  MATCH path = (root:Root)<-[rroot:IS_PART_OF]-(n)
65
64
  WHERE all(r in relationships(path) WHERE %(branch_filter)s)
66
65
  RETURN path as full_path, n as active_node
@@ -70,8 +69,7 @@ class NodeHierarchyUpdateValidatorQuery(SchemaValidatorQuery):
70
69
  WITH full_path, active_node
71
70
  WITH full_path, active_node
72
71
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
73
- CALL {
74
- WITH active_node
72
+ CALL (active_node) {
75
73
  MATCH path = (active_node)%(to_children)s-[hrel1:IS_RELATED]-%(to_parent)s(:Relationship {name: "parent__child"})%(to_children)s-[hrel2:IS_RELATED]-%(to_parent)s(peer:Node)
76
74
  WHERE all(
77
75
  r in relationships(path)
@@ -91,8 +89,7 @@ class NodeHierarchyUpdateValidatorQuery(SchemaValidatorQuery):
91
89
  collect([branch_level_sum, from_times, active_relationship_count, hierarchy_path, deepest_branch_name]) as enriched_paths,
92
90
  start_node,
93
91
  peer_node
94
- CALL {
95
- WITH enriched_paths, peer_node
92
+ CALL (enriched_paths, peer_node) {
96
93
  UNWIND enriched_paths as path_to_check
97
94
  RETURN path_to_check[3] as current_path, path_to_check[4] as branch_name, peer_node as current_peer
98
95
  ORDER BY
@@ -20,8 +20,7 @@ class NodeNotPresentValidatorQuery(SchemaValidatorQuery):
20
20
 
21
21
  query = """
22
22
  MATCH (n:%(node_kind)s)
23
- CALL {
24
- WITH n
23
+ CALL (n) {
25
24
  MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)
26
25
  WHERE all(
27
26
  r in relationships(path)
@@ -32,7 +31,6 @@ class NodeNotPresentValidatorQuery(SchemaValidatorQuery):
32
31
  LIMIT 1
33
32
  }
34
33
  WITH full_path, node, root_relationship
35
- WITH full_path, node, root_relationship
36
34
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
37
35
  """ % {"branch_filter": branch_filter, "node_kind": self.node_schema.kind}
38
36
 
@@ -50,8 +50,7 @@ class RelationshipCountUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
50
50
  query = """
51
51
  // get the nodes on these branches nodes
52
52
  MATCH (n:%(node_kind)s)
53
- CALL {
54
- WITH n
53
+ CALL (n) {
55
54
  MATCH path = (root:Root)<-[rroot:IS_PART_OF]-(n)
56
55
  WHERE all(r in relationships(path) WHERE %(branch_filter)s)
57
56
  RETURN path as full_path, n as active_node
@@ -60,11 +59,9 @@ class RelationshipCountUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
60
59
  }
61
60
  // filter to only the active nodes
62
61
  WITH full_path, active_node
63
- WITH full_path, active_node
64
62
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
65
63
  // get the relationships using the given identifier for each node
66
- CALL {
67
- WITH active_node
64
+ CALL (active_node) {
68
65
  MATCH path = (active_node)-[rrel1:IS_RELATED]-(rel:Relationship { name: $relationship_id })-[rrel2:IS_RELATED]-(peer:Node)
69
66
  WHERE ($relationship_direction <> "outbound" OR (startNode(rrel1) = active_node AND startNode(rrel2) = rel))
70
67
  AND ($relationship_direction <> "inbound" OR (startNode(rrel1) = rel AND startNode(rrel2) = peer))
@@ -87,8 +84,7 @@ class RelationshipCountUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
87
84
  start_node,
88
85
  peer_node
89
86
  // make sure to only use the latest version of this particular path
90
- CALL {
91
- WITH enriched_paths, peer_node
87
+ CALL (enriched_paths, peer_node) {
92
88
  UNWIND enriched_paths as path_to_check
93
89
  RETURN path_to_check[3] as current_path, path_to_check[4] as branch_name, peer_node as current_peer
94
90
  ORDER BY
@@ -100,8 +96,7 @@ class RelationshipCountUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
100
96
  }
101
97
  // filter to only the current active paths
102
98
  WITH collect([current_peer, current_path]) as peers_and_paths, start_node, branch_name
103
- CALL {
104
- WITH peers_and_paths
99
+ CALL (peers_and_paths) {
105
100
  UNWIND peers_and_paths AS peer_and_path
106
101
  WITH peer_and_path
107
102
  WHERE all(r in relationships(peer_and_path[1]) WHERE r.status = "active")
@@ -109,9 +104,8 @@ class RelationshipCountUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
109
104
  }
110
105
  // sum all the relationships across branches and identify the violators
111
106
  WITH collect([branch_name, num_relationships_on_branch]) as branches_and_counts, start_node
112
- CALL {
113
- WITH start_node, branches_and_counts
114
- WITH start_node, branches_and_counts, reduce(rel_total = 0, bnc in branches_and_counts | rel_total + bnc[1]) AS total_relationships_count
107
+ CALL (start_node, branches_and_counts) {
108
+ WITH reduce(rel_total = 0, bnc in branches_and_counts | rel_total + bnc[1]) AS total_relationships_count
115
109
  WHERE
116
110
  (toInteger($min_count) IS NOT NULL AND total_relationships_count < toInteger($min_count))
117
111
  OR (toInteger($max_count) IS NOT NULL AND total_relationships_count > toInteger($max_count))
@@ -30,8 +30,7 @@ class RelationshipOptionalUpdateValidatorQuery(RelationshipSchemaValidatorQuery)
30
30
  // Query all Active Nodes of type
31
31
  // and store their UUID in uuids_active_node
32
32
  MATCH (n:%(node_kind)s)
33
- CALL {
34
- WITH n
33
+ CALL (n) {
35
34
  MATCH (root:Root)<-[r:IS_PART_OF]-(n)
36
35
  WHERE %(branch_filter)s
37
36
  RETURN n as n1, r as r1
@@ -44,8 +43,7 @@ class RelationshipOptionalUpdateValidatorQuery(RelationshipSchemaValidatorQuery)
44
43
  // identifier all nodes with at least one active member for this relationship
45
44
  // and store their UUID in uuids_with_rel
46
45
  MATCH (n:%(node_kind)s)
47
- CALL {
48
- WITH n, uuids_active_node
46
+ CALL (n, uuids_active_node) {
49
47
  MATCH path = (n)-[r:IS_RELATED]-(:Relationship { name: $relationship_id })
50
48
  WHERE %(branch_filter)s
51
49
  RETURN n as n1, r as r1