infrahub-server 1.2.11__py3-none-any.whl → 1.3.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 (147) 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/core/account.py +24 -47
  10. infrahub/core/attribute.py +13 -15
  11. infrahub/core/constants/__init__.py +5 -0
  12. infrahub/core/constants/infrahubkind.py +9 -0
  13. infrahub/core/convert_object_type/__init__.py +0 -0
  14. infrahub/core/convert_object_type/conversion.py +122 -0
  15. infrahub/core/convert_object_type/schema_mapping.py +56 -0
  16. infrahub/core/diff/query/all_conflicts.py +1 -5
  17. infrahub/core/diff/query/artifact.py +10 -20
  18. infrahub/core/diff/query/diff_get.py +3 -6
  19. infrahub/core/diff/query/field_summary.py +2 -4
  20. infrahub/core/diff/query/merge.py +70 -123
  21. infrahub/core/diff/query/save.py +20 -32
  22. infrahub/core/diff/query/summary_counts_enricher.py +34 -54
  23. infrahub/core/manager.py +14 -11
  24. infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
  25. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -4
  26. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
  27. infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
  28. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
  29. infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
  30. infrahub/core/migrations/query/attribute_add.py +1 -2
  31. infrahub/core/migrations/query/attribute_rename.py +5 -10
  32. infrahub/core/migrations/query/delete_element_in_schema.py +19 -17
  33. infrahub/core/migrations/query/node_duplicate.py +19 -21
  34. infrahub/core/migrations/query/relationship_duplicate.py +19 -17
  35. infrahub/core/migrations/schema/node_attribute_remove.py +4 -8
  36. infrahub/core/migrations/schema/node_remove.py +19 -19
  37. infrahub/core/models.py +29 -2
  38. infrahub/core/node/__init__.py +90 -18
  39. infrahub/core/node/create.py +211 -0
  40. infrahub/core/node/resource_manager/number_pool.py +31 -5
  41. infrahub/core/node/standard.py +6 -1
  42. infrahub/core/protocols.py +56 -0
  43. infrahub/core/protocols_base.py +3 -0
  44. infrahub/core/query/__init__.py +2 -2
  45. infrahub/core/query/diff.py +19 -32
  46. infrahub/core/query/ipam.py +10 -20
  47. infrahub/core/query/node.py +28 -46
  48. infrahub/core/query/relationship.py +53 -32
  49. infrahub/core/query/resource_manager.py +1 -2
  50. infrahub/core/query/subquery.py +2 -4
  51. infrahub/core/relationship/model.py +3 -0
  52. infrahub/core/schema/__init__.py +2 -1
  53. infrahub/core/schema/attribute_parameters.py +160 -0
  54. infrahub/core/schema/attribute_schema.py +111 -8
  55. infrahub/core/schema/basenode_schema.py +25 -1
  56. infrahub/core/schema/definitions/core/__init__.py +29 -1
  57. infrahub/core/schema/definitions/core/group.py +45 -0
  58. infrahub/core/schema/definitions/internal.py +27 -4
  59. infrahub/core/schema/generated/attribute_schema.py +16 -3
  60. infrahub/core/schema/manager.py +3 -0
  61. infrahub/core/schema/schema_branch.py +67 -7
  62. infrahub/core/validators/__init__.py +13 -1
  63. infrahub/core/validators/attribute/choices.py +1 -3
  64. infrahub/core/validators/attribute/enum.py +1 -3
  65. infrahub/core/validators/attribute/kind.py +1 -3
  66. infrahub/core/validators/attribute/length.py +13 -7
  67. infrahub/core/validators/attribute/min_max.py +118 -0
  68. infrahub/core/validators/attribute/number_pool.py +106 -0
  69. infrahub/core/validators/attribute/optional.py +1 -4
  70. infrahub/core/validators/attribute/regex.py +5 -6
  71. infrahub/core/validators/attribute/unique.py +1 -3
  72. infrahub/core/validators/determiner.py +18 -2
  73. infrahub/core/validators/enum.py +12 -0
  74. infrahub/core/validators/node/hierarchy.py +3 -6
  75. infrahub/core/validators/query.py +1 -3
  76. infrahub/core/validators/relationship/count.py +6 -12
  77. infrahub/core/validators/relationship/optional.py +2 -4
  78. infrahub/core/validators/relationship/peer.py +3 -8
  79. infrahub/core/validators/uniqueness/query.py +5 -9
  80. infrahub/database/__init__.py +11 -2
  81. infrahub/events/group_action.py +1 -0
  82. infrahub/git/base.py +5 -3
  83. infrahub/git/integrator.py +102 -3
  84. infrahub/graphql/analyzer.py +139 -18
  85. infrahub/graphql/manager.py +4 -0
  86. infrahub/graphql/mutations/action.py +164 -0
  87. infrahub/graphql/mutations/convert_object_type.py +62 -0
  88. infrahub/graphql/mutations/main.py +24 -175
  89. infrahub/graphql/mutations/proposed_change.py +20 -17
  90. infrahub/graphql/mutations/resource_manager.py +62 -6
  91. infrahub/graphql/queries/convert_object_type_mapping.py +36 -0
  92. infrahub/graphql/queries/resource_manager.py +7 -1
  93. infrahub/graphql/schema.py +6 -0
  94. infrahub/menu/menu.py +31 -0
  95. infrahub/message_bus/messages/__init__.py +0 -10
  96. infrahub/message_bus/operations/__init__.py +0 -8
  97. infrahub/patch/queries/consolidate_duplicated_nodes.py +3 -6
  98. infrahub/patch/queries/delete_duplicated_edges.py +5 -10
  99. infrahub/pools/number.py +5 -3
  100. infrahub/prefect_server/models.py +1 -19
  101. infrahub/proposed_change/models.py +68 -3
  102. infrahub/proposed_change/tasks.py +907 -30
  103. infrahub/task_manager/models.py +10 -6
  104. infrahub/trigger/catalogue.py +2 -0
  105. infrahub/trigger/models.py +18 -2
  106. infrahub/trigger/tasks.py +3 -1
  107. infrahub/types.py +6 -0
  108. infrahub/workflows/catalogue.py +76 -0
  109. infrahub_sdk/client.py +43 -10
  110. infrahub_sdk/node/__init__.py +39 -0
  111. infrahub_sdk/node/attribute.py +122 -0
  112. infrahub_sdk/node/constants.py +21 -0
  113. infrahub_sdk/{node.py → node/node.py} +50 -749
  114. infrahub_sdk/node/parsers.py +15 -0
  115. infrahub_sdk/node/property.py +24 -0
  116. infrahub_sdk/node/related_node.py +266 -0
  117. infrahub_sdk/node/relationship.py +302 -0
  118. infrahub_sdk/protocols.py +112 -0
  119. infrahub_sdk/protocols_base.py +34 -2
  120. infrahub_sdk/query_groups.py +13 -2
  121. infrahub_sdk/schema/main.py +1 -0
  122. infrahub_sdk/schema/repository.py +16 -0
  123. infrahub_sdk/spec/object.py +1 -1
  124. infrahub_sdk/store.py +1 -1
  125. infrahub_sdk/testing/schemas/car_person.py +1 -0
  126. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0b1.dist-info}/METADATA +4 -4
  127. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0b1.dist-info}/RECORD +134 -122
  128. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0b1.dist-info}/WHEEL +1 -1
  129. infrahub_testcontainers/container.py +0 -1
  130. infrahub_testcontainers/docker-compose.test.yml +1 -1
  131. infrahub_testcontainers/helpers.py +8 -2
  132. infrahub/message_bus/messages/check_generator_run.py +0 -26
  133. infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
  134. infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
  135. infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
  136. infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
  137. infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
  138. infrahub/message_bus/operations/check/__init__.py +0 -3
  139. infrahub/message_bus/operations/check/generator.py +0 -156
  140. infrahub/message_bus/operations/finalize/__init__.py +0 -3
  141. infrahub/message_bus/operations/finalize/validator.py +0 -133
  142. infrahub/message_bus/operations/requests/__init__.py +0 -9
  143. infrahub/message_bus/operations/requests/generator_definition.py +0 -140
  144. infrahub/message_bus/operations/requests/proposed_change.py +0 -629
  145. /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
  146. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0b1.dist-info}/LICENSE.txt +0 -0
  147. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0b1.dist-info}/entry_points.txt +0 -0
@@ -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 (
@@ -16,7 +28,13 @@ from .check import core_check_definition
16
28
  from .core import core_node, core_task_target
17
29
  from .generator import core_generator_definition, core_generator_instance
18
30
  from .graphql_query import core_graphql_query
19
- from .group import core_generator_group, core_graphql_query_group, core_group, core_standard_group
31
+ from .group import (
32
+ core_generator_group,
33
+ core_graphql_query_group,
34
+ core_group,
35
+ core_repository_group,
36
+ core_standard_group,
37
+ )
20
38
  from .ipam import builtin_ip_address, builtin_ip_prefix, builtin_ipam, core_ipam_namespace
21
39
  from .lineage import lineage_owner, lineage_source
22
40
  from .menu import generic_menu_item, menu_item
@@ -69,6 +87,9 @@ from .webhook import core_custom_webhook, core_standard_webhook, core_webhook
69
87
 
70
88
  core_models_mixed: dict[str, list] = {
71
89
  "generics": [
90
+ core_action,
91
+ core_trigger_rule,
92
+ core_node_trigger_match,
72
93
  core_node,
73
94
  lineage_owner,
74
95
  core_profile_schema_definition,
@@ -97,12 +118,19 @@ core_models_mixed: dict[str, list] = {
97
118
  ],
98
119
  "nodes": [
99
120
  menu_item,
121
+ core_group_action,
100
122
  core_standard_group,
101
123
  core_generator_group,
102
124
  core_graphql_query_group,
125
+ core_repository_group,
103
126
  builtin_tag,
104
127
  core_account,
105
128
  core_account_token,
129
+ core_generator_action,
130
+ core_group_trigger_rule,
131
+ core_node_trigger_rule,
132
+ core_node_trigger_attribute_match,
133
+ core_node_trigger_relationship_match,
106
134
  core_password_credential,
107
135
  core_refresh_token,
108
136
  core_proposed_change,
@@ -1,9 +1,11 @@
1
1
  from infrahub.core.constants import (
2
2
  BranchSupportType,
3
3
  InfrahubKind,
4
+ RepositoryObjects,
4
5
  )
5
6
  from infrahub.core.constants import RelationshipCardinality as Cardinality
6
7
  from infrahub.core.constants import RelationshipKind as RelKind
8
+ from infrahub.core.schema.dropdown import DropdownChoice
7
9
 
8
10
  from ...attribute_schema import AttributeSchema as Attr
9
11
  from ...generic_schema import GenericSchema
@@ -80,6 +82,7 @@ core_generator_group = NodeSchema(
80
82
  generate_profile=False,
81
83
  )
82
84
 
85
+
83
86
  core_graphql_query_group = NodeSchema(
84
87
  name="GraphQLQueryGroup",
85
88
  namespace="Core",
@@ -106,3 +109,45 @@ core_graphql_query_group = NodeSchema(
106
109
  ),
107
110
  ],
108
111
  )
112
+
113
+
114
+ core_repository_group = NodeSchema(
115
+ name="RepositoryGroup",
116
+ namespace="Core",
117
+ description="Group of nodes associated with a given repository.",
118
+ include_in_menu=False,
119
+ icon="mdi:account-group",
120
+ label="Repository Group",
121
+ default_filter="name__value",
122
+ order_by=["name__value"],
123
+ display_labels=["name__value"],
124
+ branch=BranchSupportType.LOCAL,
125
+ inherit_from=[InfrahubKind.GENERICGROUP],
126
+ generate_profile=False,
127
+ attributes=[
128
+ Attr(
129
+ name="content",
130
+ kind="Dropdown",
131
+ description="Type of data to load, can be either `object` or `menu`",
132
+ choices=[
133
+ DropdownChoice(
134
+ name=RepositoryObjects.OBJECT.value,
135
+ label="Objects",
136
+ ),
137
+ DropdownChoice(
138
+ name=RepositoryObjects.MENU.value,
139
+ label="Menus",
140
+ ),
141
+ ],
142
+ optional=False,
143
+ ),
144
+ ],
145
+ relationships=[
146
+ Rel(
147
+ name="repository",
148
+ peer=InfrahubKind.GENERICREPOSITORY,
149
+ optional=False,
150
+ cardinality=Cardinality.ONE,
151
+ ),
152
+ ],
153
+ )
@@ -31,6 +31,12 @@ from infrahub.core.constants import (
31
31
  RelationshipKind,
32
32
  UpdateSupport,
33
33
  )
34
+ from infrahub.core.schema.attribute_parameters import (
35
+ AttributeParameters,
36
+ NumberAttributeParameters,
37
+ NumberPoolParameters,
38
+ TextAttributeParameters,
39
+ )
34
40
  from infrahub.core.schema.attribute_schema import AttributeSchema
35
41
  from infrahub.core.schema.computed_attribute import ComputedAttribute
36
42
  from infrahub.core.schema.dropdown import DropdownChoice
@@ -47,7 +53,7 @@ class SchemaAttribute(BaseModel):
47
53
  kind: str
48
54
  description: str
49
55
  extra: ExtraField
50
- internal_kind: type[Any] | GenericAlias | None = None
56
+ internal_kind: type[Any] | GenericAlias | list[type[Any]] | None = None
51
57
  regex: str | None = None
52
58
  unique: bool | None = None
53
59
  optional: bool | None = None
@@ -93,6 +99,9 @@ class SchemaAttribute(BaseModel):
93
99
  if isinstance(self.internal_kind, GenericAlias):
94
100
  return str(self.internal_kind)
95
101
 
102
+ if isinstance(self.internal_kind, list):
103
+ return " | ".join([internal_kind.__name__ for internal_kind in self.internal_kind])
104
+
96
105
  if self.internal_kind and self.kind == "List":
97
106
  return f"list[{self.internal_kind.__name__}]"
98
107
 
@@ -506,21 +515,21 @@ attribute_schema = SchemaNode(
506
515
  SchemaAttribute(
507
516
  name="regex",
508
517
  kind="Text",
509
- description="Regex uses to limit the characters allowed in for the attributes.",
518
+ description="Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)",
510
519
  optional=True,
511
520
  extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
512
521
  ),
513
522
  SchemaAttribute(
514
523
  name="max_length",
515
524
  kind="Number",
516
- description="Set a maximum number of characters allowed for a given attribute.",
525
+ description="Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)",
517
526
  optional=True,
518
527
  extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
519
528
  ),
520
529
  SchemaAttribute(
521
530
  name="min_length",
522
531
  kind="Number",
523
- description="Set a minimum number of characters allowed for a given attribute.",
532
+ description="Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)",
524
533
  optional=True,
525
534
  extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
526
535
  ),
@@ -617,6 +626,20 @@ attribute_schema = SchemaNode(
617
626
  optional=True,
618
627
  extra={"update": UpdateSupport.ALLOWED},
619
628
  ),
629
+ SchemaAttribute(
630
+ name="parameters",
631
+ kind="JSON",
632
+ internal_kind=[
633
+ AttributeParameters,
634
+ TextAttributeParameters,
635
+ NumberAttributeParameters,
636
+ NumberPoolParameters,
637
+ ],
638
+ optional=True,
639
+ description="Extra parameters specific to this kind of attribute",
640
+ extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
641
+ default_factory="AttributeParameters",
642
+ ),
620
643
  SchemaAttribute(
621
644
  name="deprecation",
622
645
  kind="Text",
@@ -8,6 +8,12 @@ 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 (
12
+ AttributeParameters, # noqa: TC001
13
+ NumberAttributeParameters, # noqa: TC001
14
+ NumberPoolParameters, # noqa: TC001
15
+ TextAttributeParameters, # noqa: TC001
16
+ )
11
17
  from infrahub.core.schema.computed_attribute import ComputedAttribute # noqa: TC001
12
18
  from infrahub.core.schema.dropdown import DropdownChoice # noqa: TC001
13
19
 
@@ -44,17 +50,17 @@ class GeneratedAttributeSchema(HashableModel):
44
50
  )
45
51
  regex: str | None = Field(
46
52
  default=None,
47
- description="Regex uses to limit the characters allowed in for the attributes.",
53
+ description="Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)",
48
54
  json_schema_extra={"update": "validate_constraint"},
49
55
  )
50
56
  max_length: int | None = Field(
51
57
  default=None,
52
- description="Set a maximum number of characters allowed for a given attribute.",
58
+ description="Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)",
53
59
  json_schema_extra={"update": "validate_constraint"},
54
60
  )
55
61
  min_length: int | None = Field(
56
62
  default=None,
57
- description="Set a minimum number of characters allowed for a given attribute.",
63
+ description="Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)",
58
64
  json_schema_extra={"update": "validate_constraint"},
59
65
  )
60
66
  label: str | None = Field(
@@ -112,6 +118,13 @@ class GeneratedAttributeSchema(HashableModel):
112
118
  description="Type of allowed override for the attribute.",
113
119
  json_schema_extra={"update": "allowed"},
114
120
  )
121
+ parameters: AttributeParameters | TextAttributeParameters | NumberAttributeParameters | NumberPoolParameters = (
122
+ Field(
123
+ default_factory=AttributeParameters,
124
+ description="Extra parameters specific to this kind of attribute",
125
+ json_schema_extra={"update": "validate_constraint"},
126
+ )
127
+ )
115
128
  deprecation: str | None = Field(
116
129
  default=None,
117
130
  description="Mark attribute as deprecated and provide a user-friendly message to display",
@@ -764,3 +764,6 @@ class SchemaManager(NodeManager):
764
764
  del self._cache[hash_key]
765
765
 
766
766
  return removed_branches
767
+
768
+ def get_branches(self) -> list[str]:
769
+ return list(self._branches.keys())
@@ -5,6 +5,7 @@ import hashlib
5
5
  from collections import defaultdict
6
6
  from itertools import chain, combinations
7
7
  from typing import Any
8
+ from uuid import uuid4
8
9
 
9
10
  from infrahub_sdk.template import Jinja2Template
10
11
  from infrahub_sdk.template.exceptions import JinjaTemplateError, JinjaTemplateOperationViolationError
@@ -49,6 +50,8 @@ from infrahub.core.schema import (
49
50
  SchemaRoot,
50
51
  TemplateSchema,
51
52
  )
53
+ from infrahub.core.schema.attribute_parameters import NumberPoolParameters
54
+ from infrahub.core.schema.attribute_schema import get_attribute_schema_class_for_kind
52
55
  from infrahub.core.schema.definitions.core import core_profile_schema_definition
53
56
  from infrahub.core.validators import CONSTRAINT_VALIDATOR_MAP
54
57
  from infrahub.exceptions import SchemaNotFoundError, ValidationError
@@ -517,6 +520,7 @@ class SchemaBranch:
517
520
  self.validate_names()
518
521
  self.validate_kinds()
519
522
  self.validate_computed_attributes()
523
+ self.validate_attribute_parameters()
520
524
  self.validate_default_values()
521
525
  self.validate_count_against_cardinality()
522
526
  self.validate_identifiers()
@@ -994,6 +998,58 @@ class SchemaBranch:
994
998
  f"{node.kind}: Relationship {rel.name!r} is referring an invalid peer {rel.peer!r}"
995
999
  ) from None
996
1000
 
1001
+ def validate_attribute_parameters(self) -> None:
1002
+ for name in self.generics.keys():
1003
+ generic_schema = self.get_generic(name=name, duplicate=False)
1004
+ for attribute in generic_schema.attributes:
1005
+ if (
1006
+ attribute.kind == "NumberPool"
1007
+ and isinstance(attribute.parameters, NumberPoolParameters)
1008
+ and not attribute.parameters.number_pool_id
1009
+ ):
1010
+ attribute.parameters.number_pool_id = str(uuid4())
1011
+
1012
+ for name in self.nodes.keys():
1013
+ node_schema = self.get_node(name=name, duplicate=False)
1014
+ for attribute in node_schema.attributes:
1015
+ if (
1016
+ attribute.kind == "NumberPool"
1017
+ and isinstance(attribute.parameters, NumberPoolParameters)
1018
+ and not attribute.parameters.number_pool_id
1019
+ ):
1020
+ self._validate_number_pool_parameters(
1021
+ node_schema=node_schema, attribute=attribute, number_pool_parameters=attribute.parameters
1022
+ )
1023
+
1024
+ def _validate_number_pool_parameters(
1025
+ self, node_schema: NodeSchema, attribute: AttributeSchema, number_pool_parameters: NumberPoolParameters
1026
+ ) -> None:
1027
+ if attribute.optional:
1028
+ raise ValidationError(f"{node_schema.kind}.{attribute.name} is a NumberPool it can't be optional")
1029
+
1030
+ if not attribute.read_only:
1031
+ raise ValidationError(
1032
+ f"{node_schema.kind}.{attribute.name} is a NumberPool it has to be a read_only attribute"
1033
+ )
1034
+
1035
+ if attribute.inherited:
1036
+ generics_with_attribute = []
1037
+ for generic_name in node_schema.inherit_from:
1038
+ generic_schema = self.get_generic(name=generic_name, duplicate=False)
1039
+ if attribute.name in generic_schema.attribute_names:
1040
+ generic_attribute = generic_schema.get_attribute(name=attribute.name)
1041
+ generics_with_attribute.append(generic_schema)
1042
+ if isinstance(generic_attribute.parameters, NumberPoolParameters):
1043
+ number_pool_parameters.number_pool_id = generic_attribute.parameters.number_pool_id
1044
+
1045
+ if len(generics_with_attribute) > 1:
1046
+ raise ValidationError(
1047
+ f"{node_schema.kind}.{attribute.name} is a NumberPool inherited from more than one generic"
1048
+ )
1049
+
1050
+ else:
1051
+ number_pool_parameters.number_pool_id = str(uuid4())
1052
+
997
1053
  def validate_computed_attributes(self) -> None:
998
1054
  self.computed_attributes = ComputedAttributes()
999
1055
  for name in self.nodes.keys():
@@ -1136,7 +1192,7 @@ class SchemaBranch:
1136
1192
  self.set(name=name, schema=node)
1137
1193
 
1138
1194
  def process_labels(self) -> None:
1139
- def check_if_need_to_update_label(node) -> bool:
1195
+ def check_if_need_to_update_label(node: MainSchemaTypes) -> bool:
1140
1196
  if not node.label:
1141
1197
  return True
1142
1198
  for item in node.relationships + node.attributes:
@@ -1812,12 +1868,14 @@ class SchemaBranch:
1812
1868
  def generate_profile_from_node(self, node: NodeSchema) -> ProfileSchema:
1813
1869
  core_profile_schema = self.get(name=InfrahubKind.PROFILE, duplicate=False)
1814
1870
  core_name_attr = core_profile_schema.get_attribute(name="profile_name")
1815
- profile_name_attr = AttributeSchema(
1871
+ name_attr_schema_class = get_attribute_schema_class_for_kind(kind=core_name_attr.kind)
1872
+ profile_name_attr = name_attr_schema_class(
1816
1873
  **core_name_attr.model_dump(exclude=["id", "inherited"]),
1817
1874
  )
1818
1875
  profile_name_attr.branch = node.branch
1819
1876
  core_priority_attr = core_profile_schema.get_attribute(name="profile_priority")
1820
- profile_priority_attr = AttributeSchema(
1877
+ priority_attr_schema_class = get_attribute_schema_class_for_kind(kind=core_priority_attr.kind)
1878
+ profile_priority_attr = priority_attr_schema_class(
1821
1879
  **core_priority_attr.model_dump(exclude=["id", "inherited"]),
1822
1880
  )
1823
1881
  profile_priority_attr.branch = node.branch
@@ -1848,8 +1906,8 @@ class SchemaBranch:
1848
1906
  for node_attr in node.attributes:
1849
1907
  if node_attr.read_only or node_attr.optional is False:
1850
1908
  continue
1851
-
1852
- attr = AttributeSchema(
1909
+ attr_schema_class = get_attribute_schema_class_for_kind(kind=node_attr.kind)
1910
+ attr = attr_schema_class(
1853
1911
  optional=True,
1854
1912
  **node_attr.model_dump(exclude=["id", "unique", "optional", "read_only", "default_value", "inherited"]),
1855
1913
  )
@@ -1973,7 +2031,8 @@ class SchemaBranch:
1973
2031
  else self.get(name=InfrahubKind.OBJECTTEMPLATE, duplicate=False)
1974
2032
  )
1975
2033
  core_name_attr = core_template_schema.get_attribute(name=OBJECT_TEMPLATE_NAME_ATTR)
1976
- template_name_attr = AttributeSchema(
2034
+ name_attr_schema_class = get_attribute_schema_class_for_kind(kind=core_name_attr.kind)
2035
+ template_name_attr = name_attr_schema_class(
1977
2036
  **core_name_attr.model_dump(exclude=["id", "inherited"]),
1978
2037
  )
1979
2038
  template_name_attr.branch = node.branch
@@ -2033,7 +2092,8 @@ class SchemaBranch:
2033
2092
  if node_attr.unique or node_attr.read_only:
2034
2093
  continue
2035
2094
 
2036
- attr = AttributeSchema(
2095
+ attr_schema_class = get_attribute_schema_class_for_kind(kind=node_attr.kind)
2096
+ attr = attr_schema_class(
2037
2097
  optional=node_attr.optional if is_autogenerated_subtemplate else True,
2038
2098
  **node_attr.model_dump(exclude=["id", "unique", "optional", "read_only"]),
2039
2099
  )
@@ -1,10 +1,14 @@
1
+ from infrahub.core.validators.attribute.min_max import AttributeNumberChecker
2
+
1
3
  from .attribute.choices import AttributeChoicesChecker
2
4
  from .attribute.enum import AttributeEnumChecker
3
5
  from .attribute.kind import AttributeKindChecker
4
6
  from .attribute.length import AttributeLengthChecker
7
+ from .attribute.number_pool import AttributeNumberPoolChecker
5
8
  from .attribute.optional import AttributeOptionalChecker
6
9
  from .attribute.regex import AttributeRegexChecker
7
10
  from .attribute.unique import AttributeUniquenessChecker
11
+ from .enum import ConstraintIdentifier
8
12
  from .interface import ConstraintCheckerInterface
9
13
  from .node.attribute import NodeAttributeAddChecker
10
14
  from .node.generate_profile import NodeGenerateProfileChecker
@@ -17,11 +21,19 @@ from .relationship.peer import RelationshipPeerChecker
17
21
  from .uniqueness.checker import UniquenessChecker
18
22
 
19
23
  CONSTRAINT_VALIDATOR_MAP: dict[str, type[ConstraintCheckerInterface] | None] = {
24
+ "attribute.kind.update": AttributeKindChecker,
20
25
  "attribute.regex.update": AttributeRegexChecker,
26
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_REGEX_UPDATE.value: AttributeRegexChecker,
21
27
  "attribute.enum.update": AttributeEnumChecker,
22
- "attribute.kind.update": AttributeKindChecker,
23
28
  "attribute.min_length.update": AttributeLengthChecker,
24
29
  "attribute.max_length.update": AttributeLengthChecker,
30
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_LENGTH_UPDATE.value: AttributeLengthChecker,
31
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_LENGTH_UPDATE.value: AttributeLengthChecker,
32
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_VALUE_UPDATE.value: AttributeNumberChecker,
33
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_VALUE_UPDATE.value: AttributeNumberChecker,
34
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_EXCLUDED_VALUES_UPDATE.value: AttributeNumberChecker,
35
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_START_RANGE_UPDATE: AttributeNumberPoolChecker,
36
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_END_RANGE_UPDATE: AttributeNumberPoolChecker,
25
37
  "attribute.unique.update": AttributeUniquenessChecker,
26
38
  "attribute.optional.update": AttributeOptionalChecker,
27
39
  "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)
@@ -43,7 +42,6 @@ class AttributeChoicesUpdateValidatorQuery(AttributeSchemaValidatorQuery):
43
42
  LIMIT 1
44
43
  }
45
44
  WITH full_path, node, attribute_value, value_relationship
46
- WITH full_path, node, attribute_value, value_relationship
47
45
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
48
46
  AND attribute_value IS NOT NULL
49
47
  AND attribute_value <> $null_value
@@ -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)
@@ -42,7 +41,6 @@ class AttributeEnumUpdateValidatorQuery(AttributeSchemaValidatorQuery):
42
41
  LIMIT 1
43
42
  }
44
43
  WITH full_path, node, attribute_value, value_relationship
45
- WITH full_path, node, attribute_value, value_relationship
46
44
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
47
45
  AND attribute_value IS NOT NULL
48
46
  AND attribute_value <> $null_value
@@ -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)
@@ -49,7 +48,6 @@ class AttributeKindUpdateValidatorQuery(AttributeSchemaValidatorQuery):
49
48
  LIMIT 1
50
49
  }
51
50
  WITH full_path, node, attribute_value, value_relationship
52
- WITH full_path, node, attribute_value, value_relationship
53
51
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
54
52
  AND attribute_value IS NOT NULL
55
53
  AND attribute_value <> $null_value
@@ -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