infrahub-server 1.2.9rc0__py3-none-any.whl → 1.3.0a0__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 (166) hide show
  1. infrahub/actions/constants.py +86 -0
  2. infrahub/actions/gather.py +114 -0
  3. infrahub/actions/models.py +241 -0
  4. infrahub/actions/parsers.py +104 -0
  5. infrahub/actions/schema.py +382 -0
  6. infrahub/actions/tasks.py +126 -0
  7. infrahub/actions/triggers.py +21 -0
  8. infrahub/cli/db.py +1 -2
  9. infrahub/computed_attribute/models.py +13 -0
  10. infrahub/computed_attribute/tasks.py +48 -26
  11. infrahub/config.py +9 -0
  12. infrahub/core/account.py +24 -47
  13. infrahub/core/attribute.py +53 -14
  14. infrahub/core/branch/models.py +8 -9
  15. infrahub/core/branch/tasks.py +0 -2
  16. infrahub/core/constants/infrahubkind.py +8 -0
  17. infrahub/core/constraint/node/runner.py +1 -1
  18. infrahub/core/convert_object_type/__init__.py +0 -0
  19. infrahub/core/convert_object_type/conversion.py +122 -0
  20. infrahub/core/convert_object_type/schema_mapping.py +56 -0
  21. infrahub/core/diff/calculator.py +65 -11
  22. infrahub/core/diff/combiner.py +38 -31
  23. infrahub/core/diff/coordinator.py +44 -28
  24. infrahub/core/diff/data_check_synchronizer.py +3 -2
  25. infrahub/core/diff/enricher/hierarchy.py +36 -27
  26. infrahub/core/diff/ipam_diff_parser.py +5 -4
  27. infrahub/core/diff/merger/merger.py +46 -16
  28. infrahub/core/diff/merger/serializer.py +1 -0
  29. infrahub/core/diff/model/field_specifiers_map.py +64 -0
  30. infrahub/core/diff/model/path.py +58 -58
  31. infrahub/core/diff/parent_node_adder.py +14 -16
  32. infrahub/core/diff/query/all_conflicts.py +1 -5
  33. infrahub/core/diff/query/artifact.py +10 -20
  34. infrahub/core/diff/query/diff_get.py +3 -6
  35. infrahub/core/diff/query/drop_nodes.py +42 -0
  36. infrahub/core/diff/query/field_specifiers.py +8 -7
  37. infrahub/core/diff/query/field_summary.py +2 -4
  38. infrahub/core/diff/query/filters.py +15 -1
  39. infrahub/core/diff/query/merge.py +284 -101
  40. infrahub/core/diff/query/save.py +26 -34
  41. infrahub/core/diff/query/summary_counts_enricher.py +34 -54
  42. infrahub/core/diff/query_parser.py +55 -65
  43. infrahub/core/diff/repository/deserializer.py +38 -24
  44. infrahub/core/diff/repository/repository.py +31 -12
  45. infrahub/core/diff/tasks.py +3 -3
  46. infrahub/core/graph/__init__.py +1 -1
  47. infrahub/core/manager.py +14 -11
  48. infrahub/core/migrations/graph/__init__.py +2 -0
  49. infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
  50. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -4
  51. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
  52. infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
  53. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
  54. infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
  55. infrahub/core/migrations/graph/m027_delete_isolated_nodes.py +50 -0
  56. infrahub/core/migrations/graph/m028_delete_diffs.py +38 -0
  57. infrahub/core/migrations/query/attribute_add.py +1 -2
  58. infrahub/core/migrations/query/attribute_rename.py +3 -6
  59. infrahub/core/migrations/query/delete_element_in_schema.py +3 -6
  60. infrahub/core/migrations/query/node_duplicate.py +3 -6
  61. infrahub/core/migrations/query/relationship_duplicate.py +3 -6
  62. infrahub/core/migrations/schema/node_attribute_remove.py +3 -6
  63. infrahub/core/migrations/schema/node_remove.py +3 -6
  64. infrahub/core/models.py +29 -2
  65. infrahub/core/node/__init__.py +18 -4
  66. infrahub/core/node/create.py +211 -0
  67. infrahub/core/protocols.py +51 -0
  68. infrahub/core/protocols_base.py +3 -0
  69. infrahub/core/query/__init__.py +2 -2
  70. infrahub/core/query/branch.py +27 -17
  71. infrahub/core/query/diff.py +186 -81
  72. infrahub/core/query/ipam.py +10 -20
  73. infrahub/core/query/node.py +65 -49
  74. infrahub/core/query/relationship.py +156 -58
  75. infrahub/core/query/resource_manager.py +1 -2
  76. infrahub/core/query/subquery.py +4 -6
  77. infrahub/core/relationship/model.py +4 -1
  78. infrahub/core/schema/__init__.py +2 -1
  79. infrahub/core/schema/attribute_parameters.py +36 -0
  80. infrahub/core/schema/attribute_schema.py +83 -8
  81. infrahub/core/schema/basenode_schema.py +25 -1
  82. infrahub/core/schema/definitions/core/__init__.py +21 -0
  83. infrahub/core/schema/definitions/internal.py +13 -3
  84. infrahub/core/schema/generated/attribute_schema.py +9 -3
  85. infrahub/core/schema/schema_branch.py +15 -7
  86. infrahub/core/validators/__init__.py +5 -1
  87. infrahub/core/validators/attribute/choices.py +1 -2
  88. infrahub/core/validators/attribute/enum.py +1 -2
  89. infrahub/core/validators/attribute/kind.py +1 -2
  90. infrahub/core/validators/attribute/length.py +13 -6
  91. infrahub/core/validators/attribute/optional.py +1 -2
  92. infrahub/core/validators/attribute/regex.py +5 -5
  93. infrahub/core/validators/attribute/unique.py +1 -3
  94. infrahub/core/validators/determiner.py +18 -2
  95. infrahub/core/validators/enum.py +7 -0
  96. infrahub/core/validators/node/hierarchy.py +3 -6
  97. infrahub/core/validators/query.py +1 -3
  98. infrahub/core/validators/relationship/count.py +6 -12
  99. infrahub/core/validators/relationship/optional.py +2 -4
  100. infrahub/core/validators/relationship/peer.py +3 -8
  101. infrahub/core/validators/tasks.py +1 -1
  102. infrahub/core/validators/uniqueness/query.py +12 -9
  103. infrahub/database/__init__.py +1 -3
  104. infrahub/events/group_action.py +1 -0
  105. infrahub/graphql/analyzer.py +139 -18
  106. infrahub/graphql/app.py +1 -1
  107. infrahub/graphql/loaders/node.py +1 -1
  108. infrahub/graphql/loaders/peers.py +1 -1
  109. infrahub/graphql/manager.py +4 -0
  110. infrahub/graphql/mutations/action.py +164 -0
  111. infrahub/graphql/mutations/convert_object_type.py +62 -0
  112. infrahub/graphql/mutations/main.py +24 -175
  113. infrahub/graphql/mutations/proposed_change.py +21 -18
  114. infrahub/graphql/queries/convert_object_type_mapping.py +36 -0
  115. infrahub/graphql/queries/diff/tree.py +2 -1
  116. infrahub/graphql/queries/relationship.py +1 -1
  117. infrahub/graphql/resolvers/many_relationship.py +4 -4
  118. infrahub/graphql/resolvers/resolver.py +4 -4
  119. infrahub/graphql/resolvers/single_relationship.py +2 -2
  120. infrahub/graphql/schema.py +6 -0
  121. infrahub/graphql/subscription/graphql_query.py +2 -2
  122. infrahub/graphql/types/branch.py +1 -1
  123. infrahub/menu/menu.py +31 -0
  124. infrahub/message_bus/messages/__init__.py +0 -10
  125. infrahub/message_bus/operations/__init__.py +0 -8
  126. infrahub/message_bus/operations/refresh/registry.py +1 -1
  127. infrahub/patch/queries/consolidate_duplicated_nodes.py +3 -6
  128. infrahub/patch/queries/delete_duplicated_edges.py +5 -10
  129. infrahub/prefect_server/models.py +1 -19
  130. infrahub/proposed_change/models.py +68 -3
  131. infrahub/proposed_change/tasks.py +907 -30
  132. infrahub/task_manager/models.py +10 -6
  133. infrahub/telemetry/database.py +1 -1
  134. infrahub/telemetry/tasks.py +1 -1
  135. infrahub/trigger/catalogue.py +2 -0
  136. infrahub/trigger/models.py +29 -3
  137. infrahub/trigger/setup.py +51 -15
  138. infrahub/trigger/tasks.py +4 -5
  139. infrahub/types.py +1 -1
  140. infrahub/webhook/models.py +2 -1
  141. infrahub/workflows/catalogue.py +85 -0
  142. infrahub/workflows/initialization.py +1 -3
  143. infrahub_sdk/timestamp.py +2 -2
  144. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/METADATA +4 -4
  145. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/RECORD +153 -146
  146. infrahub_testcontainers/container.py +0 -1
  147. infrahub_testcontainers/docker-compose.test.yml +4 -4
  148. infrahub_testcontainers/helpers.py +8 -2
  149. infrahub_testcontainers/performance_test.py +6 -3
  150. infrahub/message_bus/messages/check_generator_run.py +0 -26
  151. infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
  152. infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
  153. infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
  154. infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
  155. infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
  156. infrahub/message_bus/operations/check/__init__.py +0 -3
  157. infrahub/message_bus/operations/check/generator.py +0 -156
  158. infrahub/message_bus/operations/finalize/__init__.py +0 -3
  159. infrahub/message_bus/operations/finalize/validator.py +0 -133
  160. infrahub/message_bus/operations/requests/__init__.py +0 -9
  161. infrahub/message_bus/operations/requests/generator_definition.py +0 -140
  162. infrahub/message_bus/operations/requests/proposed_change.py +0 -629
  163. /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
  164. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/LICENSE.txt +0 -0
  165. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/WHEEL +0 -0
  166. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/entry_points.txt +0 -0
@@ -13,7 +13,7 @@ from pydantic import field_validator
13
13
  from infrahub.core.constants import RelationshipCardinality, RelationshipKind
14
14
  from infrahub.core.models import HashableModel, HashableModelDiff
15
15
 
16
- from .attribute_schema import AttributeSchema
16
+ from .attribute_schema import AttributeSchema, get_attribute_schema_class_for_kind
17
17
  from .generated.base_node_schema import GeneratedBaseNodeSchema
18
18
  from .relationship_schema import RelationshipSchema
19
19
 
@@ -74,6 +74,30 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
74
74
  Be careful hash generated from hash() have a salt by default and they will not be the same across run"""
75
75
  return hash(self.get_hash())
76
76
 
77
+ @field_validator("attributes", mode="before")
78
+ @classmethod
79
+ def set_attribute_type(cls, raw_attributes: Any) -> Any:
80
+ if not isinstance(raw_attributes, list):
81
+ return raw_attributes
82
+ attribute_schemas_with_types: list[Any] = []
83
+ for raw_attr in raw_attributes:
84
+ if not isinstance(raw_attr, (dict, AttributeSchema)):
85
+ attribute_schemas_with_types.append(raw_attr)
86
+ continue
87
+ if isinstance(raw_attr, dict):
88
+ kind = raw_attr.get("kind")
89
+ attribute_type_class = get_attribute_schema_class_for_kind(kind=kind)
90
+ attribute_schemas_with_types.append(attribute_type_class(**raw_attr))
91
+ continue
92
+
93
+ expected_attr_schema_class = get_attribute_schema_class_for_kind(kind=raw_attr.kind)
94
+ if not isinstance(raw_attr, expected_attr_schema_class):
95
+ final_attr = expected_attr_schema_class(**raw_attr.model_dump())
96
+ else:
97
+ final_attr = raw_attr
98
+ attribute_schemas_with_types.append(final_attr)
99
+ return attribute_schemas_with_types
100
+
77
101
  def to_dict(self) -> dict:
78
102
  data = self.model_dump(
79
103
  exclude_unset=True, exclude_none=True, exclude_defaults=True, exclude={"attributes", "relationships"}
@@ -1,5 +1,17 @@
1
1
  from typing import Any
2
2
 
3
+ from infrahub.actions.schema import (
4
+ core_action,
5
+ core_generator_action,
6
+ core_group_action,
7
+ core_group_trigger_rule,
8
+ core_node_trigger_attribute_match,
9
+ core_node_trigger_match,
10
+ core_node_trigger_relationship_match,
11
+ core_node_trigger_rule,
12
+ core_trigger_rule,
13
+ )
14
+
3
15
  from ...generic_schema import GenericSchema
4
16
  from ...node_schema import NodeSchema
5
17
  from .account import (
@@ -63,6 +75,9 @@ from .webhook import core_custom_webhook, core_standard_webhook, core_webhook
63
75
 
64
76
  core_models_mixed: dict[str, list] = {
65
77
  "generics": [
78
+ core_action,
79
+ core_trigger_rule,
80
+ core_node_trigger_match,
66
81
  core_node,
67
82
  lineage_owner,
68
83
  core_profile_schema_definition,
@@ -90,12 +105,18 @@ core_models_mixed: dict[str, list] = {
90
105
  ],
91
106
  "nodes": [
92
107
  menu_item,
108
+ core_group_action,
93
109
  core_standard_group,
94
110
  core_generator_group,
95
111
  core_graphql_query_group,
96
112
  builtin_tag,
97
113
  core_account,
98
114
  core_account_token,
115
+ core_generator_action,
116
+ core_group_trigger_rule,
117
+ core_node_trigger_rule,
118
+ core_node_trigger_attribute_match,
119
+ core_node_trigger_relationship_match,
99
120
  core_password_credential,
100
121
  core_refresh_token,
101
122
  core_proposed_change,
@@ -31,6 +31,7 @@ from infrahub.core.constants import (
31
31
  RelationshipKind,
32
32
  UpdateSupport,
33
33
  )
34
+ from infrahub.core.schema.attribute_parameters import AttributeParameters
34
35
  from infrahub.core.schema.attribute_schema import AttributeSchema
35
36
  from infrahub.core.schema.computed_attribute import ComputedAttribute
36
37
  from infrahub.core.schema.dropdown import DropdownChoice
@@ -506,21 +507,21 @@ attribute_schema = SchemaNode(
506
507
  SchemaAttribute(
507
508
  name="regex",
508
509
  kind="Text",
509
- description="Regex uses to limit the characters allowed in for the attributes.",
510
+ description="Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)",
510
511
  optional=True,
511
512
  extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
512
513
  ),
513
514
  SchemaAttribute(
514
515
  name="max_length",
515
516
  kind="Number",
516
- description="Set a maximum number of characters allowed for a given attribute.",
517
+ description="Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)",
517
518
  optional=True,
518
519
  extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
519
520
  ),
520
521
  SchemaAttribute(
521
522
  name="min_length",
522
523
  kind="Number",
523
- description="Set a minimum number of characters allowed for a given attribute.",
524
+ description="Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)",
524
525
  optional=True,
525
526
  extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
526
527
  ),
@@ -617,6 +618,15 @@ attribute_schema = SchemaNode(
617
618
  optional=True,
618
619
  extra={"update": UpdateSupport.ALLOWED},
619
620
  ),
621
+ SchemaAttribute(
622
+ name="parameters",
623
+ kind="JSON",
624
+ internal_kind=AttributeParameters,
625
+ optional=True,
626
+ description="Extra parameters specific to this kind of attribute",
627
+ extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
628
+ default_factory="AttributeParameters",
629
+ ),
620
630
  SchemaAttribute(
621
631
  name="deprecation",
622
632
  kind="Text",
@@ -8,6 +8,7 @@ from pydantic import Field
8
8
 
9
9
  from infrahub.core.constants import AllowOverrideType, BranchSupportType, HashableModelState
10
10
  from infrahub.core.models import HashableModel
11
+ from infrahub.core.schema.attribute_parameters import AttributeParameters # noqa: TC001
11
12
  from infrahub.core.schema.computed_attribute import ComputedAttribute # noqa: TC001
12
13
  from infrahub.core.schema.dropdown import DropdownChoice # noqa: TC001
13
14
 
@@ -44,17 +45,17 @@ class GeneratedAttributeSchema(HashableModel):
44
45
  )
45
46
  regex: str | None = Field(
46
47
  default=None,
47
- description="Regex uses to limit the characters allowed in for the attributes.",
48
+ description="Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)",
48
49
  json_schema_extra={"update": "validate_constraint"},
49
50
  )
50
51
  max_length: int | None = Field(
51
52
  default=None,
52
- description="Set a maximum number of characters allowed for a given attribute.",
53
+ description="Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)",
53
54
  json_schema_extra={"update": "validate_constraint"},
54
55
  )
55
56
  min_length: int | None = Field(
56
57
  default=None,
57
- description="Set a minimum number of characters allowed for a given attribute.",
58
+ description="Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)",
58
59
  json_schema_extra={"update": "validate_constraint"},
59
60
  )
60
61
  label: str | None = Field(
@@ -112,6 +113,11 @@ class GeneratedAttributeSchema(HashableModel):
112
113
  description="Type of allowed override for the attribute.",
113
114
  json_schema_extra={"update": "allowed"},
114
115
  )
116
+ parameters: AttributeParameters = Field(
117
+ default_factory=AttributeParameters,
118
+ description="Extra parameters specific to this kind of attribute",
119
+ json_schema_extra={"update": "validate_constraint"},
120
+ )
115
121
  deprecation: str | None = Field(
116
122
  default=None,
117
123
  description="Mark attribute as deprecated and provide a user-friendly message to display",
@@ -49,6 +49,7 @@ from infrahub.core.schema import (
49
49
  SchemaRoot,
50
50
  TemplateSchema,
51
51
  )
52
+ from infrahub.core.schema.attribute_schema import get_attribute_schema_class_for_kind
52
53
  from infrahub.core.schema.definitions.core import core_profile_schema_definition
53
54
  from infrahub.core.validators import CONSTRAINT_VALIDATOR_MAP
54
55
  from infrahub.exceptions import SchemaNotFoundError, ValidationError
@@ -1136,7 +1137,7 @@ class SchemaBranch:
1136
1137
  self.set(name=name, schema=node)
1137
1138
 
1138
1139
  def process_labels(self) -> None:
1139
- def check_if_need_to_update_label(node) -> bool:
1140
+ def check_if_need_to_update_label(node: MainSchemaTypes) -> bool:
1140
1141
  if not node.label:
1141
1142
  return True
1142
1143
  for item in node.relationships + node.attributes:
@@ -1812,12 +1813,14 @@ class SchemaBranch:
1812
1813
  def generate_profile_from_node(self, node: NodeSchema) -> ProfileSchema:
1813
1814
  core_profile_schema = self.get(name=InfrahubKind.PROFILE, duplicate=False)
1814
1815
  core_name_attr = core_profile_schema.get_attribute(name="profile_name")
1815
- profile_name_attr = AttributeSchema(
1816
+ name_attr_schema_class = get_attribute_schema_class_for_kind(kind=core_name_attr.kind)
1817
+ profile_name_attr = name_attr_schema_class(
1816
1818
  **core_name_attr.model_dump(exclude=["id", "inherited"]),
1817
1819
  )
1818
1820
  profile_name_attr.branch = node.branch
1819
1821
  core_priority_attr = core_profile_schema.get_attribute(name="profile_priority")
1820
- profile_priority_attr = AttributeSchema(
1822
+ priority_attr_schema_class = get_attribute_schema_class_for_kind(kind=core_priority_attr.kind)
1823
+ profile_priority_attr = priority_attr_schema_class(
1821
1824
  **core_priority_attr.model_dump(exclude=["id", "inherited"]),
1822
1825
  )
1823
1826
  profile_priority_attr.branch = node.branch
@@ -1848,8 +1851,8 @@ class SchemaBranch:
1848
1851
  for node_attr in node.attributes:
1849
1852
  if node_attr.read_only or node_attr.optional is False:
1850
1853
  continue
1851
-
1852
- attr = AttributeSchema(
1854
+ attr_schema_class = get_attribute_schema_class_for_kind(kind=node_attr.kind)
1855
+ attr = attr_schema_class(
1853
1856
  optional=True,
1854
1857
  **node_attr.model_dump(exclude=["id", "unique", "optional", "read_only", "default_value", "inherited"]),
1855
1858
  )
@@ -1973,7 +1976,8 @@ class SchemaBranch:
1973
1976
  else self.get(name=InfrahubKind.OBJECTTEMPLATE, duplicate=False)
1974
1977
  )
1975
1978
  core_name_attr = core_template_schema.get_attribute(name=OBJECT_TEMPLATE_NAME_ATTR)
1976
- template_name_attr = AttributeSchema(
1979
+ name_attr_schema_class = get_attribute_schema_class_for_kind(kind=core_name_attr.kind)
1980
+ template_name_attr = name_attr_schema_class(
1977
1981
  **core_name_attr.model_dump(exclude=["id", "inherited"]),
1978
1982
  )
1979
1983
  template_name_attr.branch = node.branch
@@ -2033,7 +2037,8 @@ class SchemaBranch:
2033
2037
  if node_attr.unique or node_attr.read_only:
2034
2038
  continue
2035
2039
 
2036
- attr = AttributeSchema(
2040
+ attr_schema_class = get_attribute_schema_class_for_kind(kind=node_attr.kind)
2041
+ attr = attr_schema_class(
2037
2042
  optional=node_attr.optional if is_autogenerated_subtemplate else True,
2038
2043
  **node_attr.model_dump(exclude=["id", "unique", "optional", "read_only"]),
2039
2044
  )
@@ -2050,6 +2055,9 @@ class SchemaBranch:
2050
2055
 
2051
2056
  identified.add(node_schema)
2052
2057
 
2058
+ if node_schema.is_node_schema:
2059
+ identified.update([self.get(name=kind, duplicate=False) for kind in node_schema.inherit_from])
2060
+
2053
2061
  for relationship in node_schema.relationships:
2054
2062
  if (
2055
2063
  relationship.peer in [InfrahubKind.GENERICGROUP, InfrahubKind.PROFILE]
@@ -5,6 +5,7 @@ from .attribute.length import AttributeLengthChecker
5
5
  from .attribute.optional import AttributeOptionalChecker
6
6
  from .attribute.regex import AttributeRegexChecker
7
7
  from .attribute.unique import AttributeUniquenessChecker
8
+ from .enum import ConstraintIdentifier
8
9
  from .interface import ConstraintCheckerInterface
9
10
  from .node.attribute import NodeAttributeAddChecker
10
11
  from .node.generate_profile import NodeGenerateProfileChecker
@@ -17,11 +18,14 @@ from .relationship.peer import RelationshipPeerChecker
17
18
  from .uniqueness.checker import UniquenessChecker
18
19
 
19
20
  CONSTRAINT_VALIDATOR_MAP: dict[str, type[ConstraintCheckerInterface] | None] = {
21
+ "attribute.kind.update": AttributeKindChecker,
20
22
  "attribute.regex.update": AttributeRegexChecker,
23
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_REGEX_UPDATE.value: AttributeRegexChecker,
21
24
  "attribute.enum.update": AttributeEnumChecker,
22
- "attribute.kind.update": AttributeKindChecker,
23
25
  "attribute.min_length.update": AttributeLengthChecker,
24
26
  "attribute.max_length.update": AttributeLengthChecker,
27
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_LENGTH_UPDATE.value: AttributeLengthChecker,
28
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_LENGTH_UPDATE.value: AttributeLengthChecker,
25
29
  "attribute.unique.update": AttributeUniquenessChecker,
26
30
  "attribute.optional.update": AttributeOptionalChecker,
27
31
  "attribute.choices.update": AttributeChoicesChecker,
@@ -31,8 +31,7 @@ class AttributeChoicesUpdateValidatorQuery(AttributeSchemaValidatorQuery):
31
31
 
32
32
  query = """
33
33
  MATCH p = (n:%(node_kind)s)
34
- CALL {
35
- WITH n
34
+ CALL (n) {
36
35
  MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
37
36
  WHERE all(
38
37
  r in relationships(path)
@@ -30,8 +30,7 @@ class AttributeEnumUpdateValidatorQuery(AttributeSchemaValidatorQuery):
30
30
  self.params["null_value"] = NULL_VALUE
31
31
  query = """
32
32
  MATCH (n:%(node_kind)s)
33
- CALL {
34
- WITH n
33
+ CALL (n) {
35
34
  MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
36
35
  WHERE all(
37
36
  r in relationships(path)
@@ -37,8 +37,7 @@ class AttributeKindUpdateValidatorQuery(AttributeSchemaValidatorQuery):
37
37
 
38
38
  query = """
39
39
  MATCH p = (n:%(node_kind)s)
40
- CALL {
41
- WITH n
40
+ CALL (n) {
42
41
  MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
43
42
  WHERE all(
44
43
  r in relationships(path)
@@ -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)
@@ -79,14 +79,21 @@ class AttributeLengthChecker(ConstraintCheckerInterface):
79
79
  return "attribute.length.update"
80
80
 
81
81
  def supports(self, request: SchemaConstraintValidatorRequest) -> bool:
82
- return request.constraint_name in ("attribute.min_length.update", "attribute.max_length.update")
82
+ return request.constraint_name in (
83
+ "attribute.min_length.update",
84
+ "attribute.max_length.update",
85
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_LENGTH_UPDATE.value,
86
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_LENGTH_UPDATE.value,
87
+ )
83
88
 
84
89
  async def check(self, request: SchemaConstraintValidatorRequest) -> list[GroupedDataPaths]:
85
90
  grouped_data_paths_list: list[GroupedDataPaths] = []
86
91
  if not request.schema_path.field_name:
87
92
  raise ValueError("field_name is not defined")
88
93
  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:
94
+ min_length = attribute_schema.get_min_length()
95
+ max_length = attribute_schema.get_max_length()
96
+ if min_length is None and max_length is None:
90
97
  return grouped_data_paths_list
91
98
 
92
99
  for query_class in self.query_classes:
@@ -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)
@@ -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)
@@ -79,14 +79,14 @@ class AttributeRegexChecker(ConstraintCheckerInterface):
79
79
  return "attribute.regex.update"
80
80
 
81
81
  def supports(self, request: SchemaConstraintValidatorRequest) -> bool:
82
- return request.constraint_name == self.name
82
+ return request.constraint_name in (self.name, ConstraintIdentifier.ATTRIBUTE_PARAMETERS_REGEX_UPDATE.value)
83
83
 
84
84
  async def check(self, request: SchemaConstraintValidatorRequest) -> list[GroupedDataPaths]:
85
85
  grouped_data_paths_list: list[GroupedDataPaths] = []
86
86
  if not request.schema_path.field_name:
87
87
  raise ValueError("field_name is not defined")
88
88
  attribute_schema = request.node_schema.get_attribute(name=request.schema_path.field_name)
89
- if not attribute_schema.regex:
89
+ if not attribute_schema.get_regex():
90
90
  return grouped_data_paths_list
91
91
 
92
92
  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,7 @@
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"
@@ -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
@@ -36,8 +36,7 @@ class RelationshipPeerUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
36
36
  # ruff: noqa: E501
37
37
  query = """
38
38
  MATCH (n:%(node_kind)s)
39
- CALL {
40
- WITH n
39
+ CALL (n) {
41
40
  MATCH path = (root:Root)<-[rroot:IS_PART_OF]-(n)
42
41
  WHERE all(r in relationships(path) WHERE %(branch_filter)s)
43
42
  RETURN path as full_path, n as active_node
@@ -45,10 +44,8 @@ class RelationshipPeerUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
45
44
  LIMIT 1
46
45
  }
47
46
  WITH full_path, active_node
48
- WITH full_path, active_node
49
47
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
50
- CALL {
51
- WITH active_node
48
+ CALL (active_node) {
52
49
  MATCH path = (active_node)-[rrel1:IS_RELATED]-(rel:Relationship { name: $relationship_id })-[rrel2:IS_RELATED]-(peer:Node)
53
50
  WHERE all(
54
51
  r in relationships(path)
@@ -68,8 +65,7 @@ class RelationshipPeerUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
68
65
  collect([branch_level_sum, from_times, active_relationship_count, relationship_path, deepest_branch_name]) as enriched_paths,
69
66
  start_node,
70
67
  peer_node
71
- CALL {
72
- WITH enriched_paths, peer_node
68
+ CALL (enriched_paths, peer_node) {
73
69
  UNWIND enriched_paths as path_to_check
74
70
  RETURN path_to_check[3] as current_path, path_to_check[4] as branch_name, peer_node as current_peer
75
71
  ORDER BY
@@ -80,7 +76,6 @@ class RelationshipPeerUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
80
76
  LIMIT 1
81
77
  }
82
78
  WITH start_node, current_peer, branch_name, current_path
83
- WITH start_node, current_peer, branch_name, current_path
84
79
  WHERE all(r in relationships(current_path) WHERE r.status = "active")
85
80
  AND NOT any(label IN LABELS(current_peer) WHERE label IN $allowed_peer_kinds)
86
81
  """ % {"branch_filter": branch_filter, "node_kind": self.node_schema.kind}
@@ -71,7 +71,7 @@ async def schema_path_validate(
71
71
  schema_branch: SchemaBranch,
72
72
  service: InfrahubServices,
73
73
  ) -> SchemaValidatorPathResponseData:
74
- async with service.database.start_session() as db:
74
+ async with service.database.start_session(read_only=True) as db:
75
75
  constraint_request = SchemaConstraintValidatorRequest(
76
76
  branch=branch,
77
77
  constraint_name=constraint_name,