infrahub-server 1.2.12__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 (205) 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/infrahubkind.py +9 -0
  18. infrahub/core/constraint/node/runner.py +3 -1
  19. infrahub/core/convert_object_type/__init__.py +0 -0
  20. infrahub/core/convert_object_type/conversion.py +124 -0
  21. infrahub/core/convert_object_type/schema_mapping.py +56 -0
  22. infrahub/core/diff/coordinator.py +8 -1
  23. infrahub/core/diff/query/all_conflicts.py +1 -5
  24. infrahub/core/diff/query/artifact.py +10 -20
  25. infrahub/core/diff/query/delete_query.py +8 -4
  26. infrahub/core/diff/query/diff_get.py +3 -6
  27. infrahub/core/diff/query/field_specifiers.py +1 -1
  28. infrahub/core/diff/query/field_summary.py +2 -4
  29. infrahub/core/diff/query/merge.py +72 -125
  30. infrahub/core/diff/query/save.py +28 -43
  31. infrahub/core/diff/query/summary_counts_enricher.py +34 -54
  32. infrahub/core/diff/query/time_range_query.py +0 -1
  33. infrahub/core/diff/repository/repository.py +4 -0
  34. infrahub/core/manager.py +14 -11
  35. infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
  36. infrahub/core/migrations/graph/m012_convert_account_generic.py +1 -1
  37. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -6
  38. infrahub/core/migrations/graph/m015_diff_format_update.py +1 -2
  39. infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -2
  40. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
  41. infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
  42. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
  43. infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +2 -2
  44. infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
  45. infrahub/core/migrations/graph/m028_delete_diffs.py +1 -2
  46. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +30 -48
  47. infrahub/core/migrations/graph/m030_illegal_edges.py +1 -2
  48. infrahub/core/migrations/query/attribute_add.py +1 -2
  49. infrahub/core/migrations/query/attribute_rename.py +6 -11
  50. infrahub/core/migrations/query/delete_element_in_schema.py +19 -17
  51. infrahub/core/migrations/query/node_duplicate.py +19 -21
  52. infrahub/core/migrations/query/relationship_duplicate.py +19 -18
  53. infrahub/core/migrations/schema/node_attribute_remove.py +4 -8
  54. infrahub/core/migrations/schema/node_remove.py +19 -20
  55. infrahub/core/models.py +29 -2
  56. infrahub/core/node/__init__.py +131 -28
  57. infrahub/core/node/base.py +1 -1
  58. infrahub/core/node/create.py +211 -0
  59. infrahub/core/node/resource_manager/number_pool.py +31 -5
  60. infrahub/core/node/standard.py +6 -1
  61. infrahub/core/path.py +15 -1
  62. infrahub/core/protocols.py +57 -0
  63. infrahub/core/protocols_base.py +3 -0
  64. infrahub/core/query/__init__.py +2 -2
  65. infrahub/core/query/delete.py +3 -3
  66. infrahub/core/query/diff.py +19 -32
  67. infrahub/core/query/ipam.py +10 -20
  68. infrahub/core/query/node.py +29 -47
  69. infrahub/core/query/relationship.py +55 -34
  70. infrahub/core/query/resource_manager.py +1 -2
  71. infrahub/core/query/standard_node.py +19 -5
  72. infrahub/core/query/subquery.py +2 -4
  73. infrahub/core/relationship/constraints/count.py +10 -9
  74. infrahub/core/relationship/constraints/interface.py +2 -1
  75. infrahub/core/relationship/constraints/peer_kind.py +2 -1
  76. infrahub/core/relationship/constraints/peer_parent.py +56 -0
  77. infrahub/core/relationship/constraints/peer_relatives.py +72 -0
  78. infrahub/core/relationship/constraints/profiles_kind.py +1 -1
  79. infrahub/core/relationship/model.py +4 -1
  80. infrahub/core/schema/__init__.py +2 -1
  81. infrahub/core/schema/attribute_parameters.py +160 -0
  82. infrahub/core/schema/attribute_schema.py +130 -7
  83. infrahub/core/schema/basenode_schema.py +27 -3
  84. infrahub/core/schema/definitions/core/__init__.py +29 -1
  85. infrahub/core/schema/definitions/core/group.py +45 -0
  86. infrahub/core/schema/definitions/core/resource_pool.py +9 -0
  87. infrahub/core/schema/definitions/internal.py +43 -5
  88. infrahub/core/schema/generated/attribute_schema.py +16 -3
  89. infrahub/core/schema/generated/relationship_schema.py +11 -1
  90. infrahub/core/schema/manager.py +7 -2
  91. infrahub/core/schema/schema_branch.py +104 -9
  92. infrahub/core/validators/__init__.py +15 -2
  93. infrahub/core/validators/attribute/choices.py +1 -3
  94. infrahub/core/validators/attribute/enum.py +1 -3
  95. infrahub/core/validators/attribute/kind.py +1 -3
  96. infrahub/core/validators/attribute/length.py +13 -7
  97. infrahub/core/validators/attribute/min_max.py +118 -0
  98. infrahub/core/validators/attribute/number_pool.py +106 -0
  99. infrahub/core/validators/attribute/optional.py +1 -4
  100. infrahub/core/validators/attribute/regex.py +5 -6
  101. infrahub/core/validators/attribute/unique.py +1 -3
  102. infrahub/core/validators/determiner.py +18 -2
  103. infrahub/core/validators/enum.py +12 -0
  104. infrahub/core/validators/node/hierarchy.py +3 -6
  105. infrahub/core/validators/query.py +1 -3
  106. infrahub/core/validators/relationship/count.py +6 -12
  107. infrahub/core/validators/relationship/optional.py +2 -4
  108. infrahub/core/validators/relationship/peer.py +177 -12
  109. infrahub/core/validators/tasks.py +1 -1
  110. infrahub/core/validators/uniqueness/query.py +5 -9
  111. infrahub/database/__init__.py +12 -4
  112. infrahub/database/validation.py +1 -2
  113. infrahub/dependencies/builder/constraint/grouped/node_runner.py +4 -0
  114. infrahub/dependencies/builder/constraint/relationship_manager/peer_parent.py +8 -0
  115. infrahub/dependencies/builder/constraint/relationship_manager/peer_relatives.py +8 -0
  116. infrahub/dependencies/builder/constraint/schema/aggregated.py +2 -0
  117. infrahub/dependencies/builder/constraint/schema/relationship_peer.py +8 -0
  118. infrahub/dependencies/builder/diff/deserializer.py +1 -1
  119. infrahub/dependencies/registry.py +4 -0
  120. infrahub/events/group_action.py +1 -0
  121. infrahub/events/models.py +1 -1
  122. infrahub/git/base.py +5 -3
  123. infrahub/git/integrator.py +96 -5
  124. infrahub/git/tasks.py +1 -0
  125. infrahub/graphql/analyzer.py +139 -18
  126. infrahub/graphql/manager.py +4 -0
  127. infrahub/graphql/mutations/action.py +164 -0
  128. infrahub/graphql/mutations/convert_object_type.py +71 -0
  129. infrahub/graphql/mutations/main.py +24 -175
  130. infrahub/graphql/mutations/proposed_change.py +20 -17
  131. infrahub/graphql/mutations/relationship.py +32 -0
  132. infrahub/graphql/mutations/resource_manager.py +63 -7
  133. infrahub/graphql/queries/convert_object_type_mapping.py +34 -0
  134. infrahub/graphql/queries/resource_manager.py +7 -1
  135. infrahub/graphql/resolvers/many_relationship.py +1 -1
  136. infrahub/graphql/resolvers/resolver.py +2 -2
  137. infrahub/graphql/resolvers/single_relationship.py +1 -1
  138. infrahub/graphql/schema.py +6 -0
  139. infrahub/menu/menu.py +34 -2
  140. infrahub/message_bus/messages/__init__.py +0 -10
  141. infrahub/message_bus/operations/__init__.py +0 -8
  142. infrahub/message_bus/operations/refresh/registry.py +3 -6
  143. infrahub/patch/queries/delete_duplicated_edges.py +10 -15
  144. infrahub/pools/models.py +14 -0
  145. infrahub/pools/number.py +5 -3
  146. infrahub/pools/registration.py +22 -0
  147. infrahub/pools/tasks.py +126 -0
  148. infrahub/prefect_server/models.py +1 -19
  149. infrahub/proposed_change/models.py +68 -3
  150. infrahub/proposed_change/tasks.py +911 -34
  151. infrahub/schema/__init__.py +0 -0
  152. infrahub/schema/tasks.py +27 -0
  153. infrahub/schema/triggers.py +23 -0
  154. infrahub/task_manager/models.py +10 -6
  155. infrahub/trigger/catalogue.py +6 -0
  156. infrahub/trigger/models.py +23 -6
  157. infrahub/trigger/setup.py +26 -2
  158. infrahub/trigger/tasks.py +4 -2
  159. infrahub/types.py +6 -0
  160. infrahub/webhook/tasks.py +4 -8
  161. infrahub/workflows/catalogue.py +103 -1
  162. infrahub_sdk/client.py +43 -10
  163. infrahub_sdk/ctl/generator.py +4 -4
  164. infrahub_sdk/ctl/repository.py +1 -1
  165. infrahub_sdk/node/__init__.py +39 -0
  166. infrahub_sdk/node/attribute.py +122 -0
  167. infrahub_sdk/node/constants.py +21 -0
  168. infrahub_sdk/{node.py → node/node.py} +158 -803
  169. infrahub_sdk/node/parsers.py +15 -0
  170. infrahub_sdk/node/property.py +24 -0
  171. infrahub_sdk/node/related_node.py +266 -0
  172. infrahub_sdk/node/relationship.py +302 -0
  173. infrahub_sdk/protocols.py +112 -0
  174. infrahub_sdk/protocols_base.py +34 -2
  175. infrahub_sdk/pytest_plugin/items/python_transform.py +2 -1
  176. infrahub_sdk/query_groups.py +17 -5
  177. infrahub_sdk/schema/main.py +1 -0
  178. infrahub_sdk/schema/repository.py +16 -0
  179. infrahub_sdk/spec/object.py +1 -1
  180. infrahub_sdk/store.py +1 -1
  181. infrahub_sdk/testing/schemas/car_person.py +1 -0
  182. infrahub_sdk/utils.py +7 -20
  183. infrahub_sdk/yaml.py +6 -5
  184. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/METADATA +3 -3
  185. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/RECORD +192 -166
  186. infrahub_testcontainers/container.py +0 -1
  187. infrahub_testcontainers/docker-compose.test.yml +1 -1
  188. infrahub_testcontainers/helpers.py +8 -2
  189. infrahub/message_bus/messages/check_generator_run.py +0 -26
  190. infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
  191. infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
  192. infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
  193. infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
  194. infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
  195. infrahub/message_bus/operations/check/__init__.py +0 -3
  196. infrahub/message_bus/operations/check/generator.py +0 -156
  197. infrahub/message_bus/operations/finalize/__init__.py +0 -3
  198. infrahub/message_bus/operations/finalize/validator.py +0 -133
  199. infrahub/message_bus/operations/requests/__init__.py +0 -9
  200. infrahub/message_bus/operations/requests/generator_definition.py +0 -140
  201. infrahub/message_bus/operations/requests/proposed_change.py +0 -629
  202. /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
  203. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/LICENSE.txt +0 -0
  204. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/WHEEL +0 -0
  205. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.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
+ )
@@ -1,6 +1,7 @@
1
1
  from infrahub.core.constants import (
2
2
  BranchSupportType,
3
3
  InfrahubKind,
4
+ NumberPoolType,
4
5
  )
5
6
  from infrahub.core.constants import RelationshipCardinality as Cardinality
6
7
  from infrahub.core.constants import RelationshipKind as RelKind
@@ -186,5 +187,13 @@ core_number_pool = NodeSchema(
186
187
  Attr(
187
188
  name="end_range", kind="Number", optional=False, description="The end range for the pool", order_weight=6000
188
189
  ),
190
+ Attr(
191
+ name="pool_type",
192
+ kind="Text",
193
+ description="Defines how this number pool was created",
194
+ default_value=NumberPoolType.USER.value,
195
+ enum=NumberPoolType.available_types(),
196
+ read_only=True,
197
+ ),
189
198
  ],
190
199
  )
@@ -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
@@ -76,7 +82,7 @@ class SchemaAttribute(BaseModel):
76
82
 
77
83
  @property
78
84
  def optional_in_model(self) -> bool:
79
- if self.optional and self.default_value is None and self.default_factory is None or self.default_to_none:
85
+ if (self.optional and self.default_value is None and self.default_factory is None) or self.default_to_none:
80
86
  return True
81
87
 
82
88
  return False
@@ -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",
@@ -731,6 +754,21 @@ relationship_schema = SchemaNode(
731
754
  optional=True,
732
755
  extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
733
756
  ),
757
+ SchemaAttribute(
758
+ name="common_parent",
759
+ kind="Text",
760
+ optional=True,
761
+ description="Name of a parent relationship on the peer schema that must share the same related object with the object's parent.",
762
+ extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
763
+ ),
764
+ SchemaAttribute(
765
+ name="common_relatives",
766
+ kind="List",
767
+ internal_kind=str,
768
+ optional=True,
769
+ description="List of relationship names on the peer schema for which all objects must share the same set of peers.",
770
+ extra={"update": UpdateSupport.ALLOWED},
771
+ ),
734
772
  SchemaAttribute(
735
773
  name="order_weight",
736
774
  kind="Number",
@@ -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,
13
+ NumberAttributeParameters,
14
+ NumberPoolParameters,
15
+ TextAttributeParameters,
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",
@@ -12,7 +12,7 @@ from infrahub.core.constants import (
12
12
  RelationshipDeleteBehavior,
13
13
  RelationshipDirection,
14
14
  RelationshipKind,
15
- ) # noqa: TC001
15
+ )
16
16
  from infrahub.core.models import HashableModel
17
17
 
18
18
 
@@ -73,6 +73,16 @@ class GeneratedRelationshipSchema(HashableModel):
73
73
  description="Defines the maximum objects allowed on the other side of the relationship.",
74
74
  json_schema_extra={"update": "validate_constraint"},
75
75
  )
76
+ common_parent: str | None = Field(
77
+ default=None,
78
+ description="Name of a parent relationship on the peer schema that must share the same related object with the object's parent.",
79
+ json_schema_extra={"update": "validate_constraint"},
80
+ )
81
+ common_relatives: list[str] | None = Field(
82
+ default=None,
83
+ description="List of relationship names on the peer schema for which all objects must share the same set of peers.",
84
+ json_schema_extra={"update": "allowed"},
85
+ )
76
86
  order_weight: int | None = Field(
77
87
  default=None,
78
88
  description="Number used to order the relationship in the frontend (table and view). Lowest value will be ordered first.",
@@ -471,7 +471,7 @@ class SchemaManager(NodeManager):
471
471
  if diff_attributes:
472
472
  for item in node.local_attributes:
473
473
  # if item is in changed and has no ID, then it is being overridden from a generic and must be added
474
- if item.name in diff_attributes.added or item.name in diff_attributes.changed and item.id is None:
474
+ if item.name in diff_attributes.added or (item.name in diff_attributes.changed and item.id is None):
475
475
  created_item = await self.create_attribute_in_db(
476
476
  schema=attribute_schema, item=item, branch=branch, db=db, parent=obj
477
477
  )
@@ -491,7 +491,9 @@ class SchemaManager(NodeManager):
491
491
  if diff_relationships:
492
492
  for item in node.local_relationships:
493
493
  # if item is in changed and has no ID, then it is being overridden from a generic and must be added
494
- if item.name in diff_relationships.added or item.name in diff_relationships.changed and item.id is None:
494
+ if item.name in diff_relationships.added or (
495
+ item.name in diff_relationships.changed and item.id is None
496
+ ):
495
497
  created_rel = await self.create_relationship_in_db(
496
498
  schema=relationship_schema, item=item, branch=branch, db=db, parent=obj
497
499
  )
@@ -764,3 +766,6 @@ class SchemaManager(NodeManager):
764
766
  del self._cache[hash_key]
765
767
 
766
768
  return removed_branches
769
+
770
+ def get_branches(self) -> list[str]:
771
+ 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
@@ -88,7 +91,7 @@ class SchemaBranch:
88
91
  self.templates = data.get("templates", {})
89
92
 
90
93
  @classmethod
91
- def validate(cls, data: Any) -> Self: # noqa: ARG003
94
+ def validate(cls, data: Any) -> Self:
92
95
  if isinstance(data, cls):
93
96
  return data
94
97
  if isinstance(data, dict):
@@ -450,7 +453,7 @@ class SchemaBranch:
450
453
  if isinstance(node, NodeSchema | ProfileSchema | TemplateSchema):
451
454
  return node.generate_fields_for_display_label()
452
455
 
453
- fields: dict[str, str | None | dict[str, None]] = {}
456
+ fields: dict[str, str | dict[str, None] | None] = {}
454
457
  if isinstance(node, GenericSchema):
455
458
  for child_node_name in node.used_by:
456
459
  child_node = self.get(name=child_node_name, duplicate=False)
@@ -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()
@@ -971,6 +975,28 @@ class SchemaBranch:
971
975
  ):
972
976
  raise ValueError(f"{node.kind}: {rel.name} isn't allowed as a relationship name.")
973
977
 
978
+ def _validate_common_parent(self, node: NodeSchema, rel: RelationshipSchema) -> None:
979
+ if not rel.common_parent:
980
+ return
981
+
982
+ peer_schema = self.get(name=rel.peer, duplicate=False)
983
+ if not node.has_parent_relationship:
984
+ raise ValueError(
985
+ f"{node.kind}: Relationship {rel.name!r} defines 'common_parent' but node does not have a parent relationship"
986
+ )
987
+
988
+ try:
989
+ parent_rel = peer_schema.get_relationship(name=rel.common_parent)
990
+ except ValueError as exc:
991
+ raise ValueError(
992
+ f"{node.kind}: Relationship {rel.name!r} defines 'common_parent' but '{rel.peer}.{rel.common_parent}' does not exist"
993
+ ) from exc
994
+
995
+ if parent_rel.kind != RelationshipKind.PARENT:
996
+ raise ValueError(
997
+ f"{node.kind}: Relationship {rel.name!r} defines 'common_parent' but '{rel.peer}.{rel.common_parent} is not of kind 'parent'"
998
+ )
999
+
974
1000
  def validate_kinds(self) -> None:
975
1001
  for name in list(self.nodes.keys()):
976
1002
  node = self.get_node(name=name, duplicate=False)
@@ -994,6 +1020,71 @@ class SchemaBranch:
994
1020
  f"{node.kind}: Relationship {rel.name!r} is referring an invalid peer {rel.peer!r}"
995
1021
  ) from None
996
1022
 
1023
+ self._validate_common_parent(node=node, rel=rel)
1024
+
1025
+ if rel.common_relatives:
1026
+ peer_schema = self.get(name=rel.peer, duplicate=False)
1027
+ for common_relatives_rel_name in rel.common_relatives:
1028
+ if common_relatives_rel_name not in peer_schema.relationship_names:
1029
+ raise ValueError(
1030
+ f"{node.kind}: Relationship {rel.name!r} set 'common_relatives' with invalid relationship from '{rel.peer}'"
1031
+ ) from None
1032
+
1033
+ def validate_attribute_parameters(self) -> None:
1034
+ for name in self.generics.keys():
1035
+ generic_schema = self.get_generic(name=name, duplicate=False)
1036
+ for attribute in generic_schema.attributes:
1037
+ if (
1038
+ attribute.kind == "NumberPool"
1039
+ and isinstance(attribute.parameters, NumberPoolParameters)
1040
+ and not attribute.parameters.number_pool_id
1041
+ ):
1042
+ attribute.parameters.number_pool_id = str(uuid4())
1043
+
1044
+ for name in self.nodes.keys():
1045
+ node_schema = self.get_node(name=name, duplicate=False)
1046
+ for attribute in node_schema.attributes:
1047
+ if attribute.kind == "NumberPool" and isinstance(attribute.parameters, NumberPoolParameters):
1048
+ self._validate_number_pool_parameters(
1049
+ node_schema=node_schema, attribute=attribute, number_pool_parameters=attribute.parameters
1050
+ )
1051
+
1052
+ def _validate_number_pool_parameters(
1053
+ self, node_schema: NodeSchema, attribute: AttributeSchema, number_pool_parameters: NumberPoolParameters
1054
+ ) -> None:
1055
+ if attribute.optional:
1056
+ raise ValidationError(f"{node_schema.kind}.{attribute.name} is a NumberPool it can't be optional")
1057
+
1058
+ if not attribute.read_only:
1059
+ raise ValidationError(
1060
+ f"{node_schema.kind}.{attribute.name} is a NumberPool it has to be a read_only attribute"
1061
+ )
1062
+
1063
+ if attribute.inherited and not number_pool_parameters.number_pool_id:
1064
+ generics_with_attribute = []
1065
+ for generic_name in node_schema.inherit_from:
1066
+ generic_schema = self.get_generic(name=generic_name, duplicate=False)
1067
+ if attribute.name in generic_schema.attribute_names:
1068
+ generic_attribute = generic_schema.get_attribute(name=attribute.name)
1069
+ generics_with_attribute.append(generic_schema)
1070
+ if isinstance(generic_attribute.parameters, NumberPoolParameters):
1071
+ number_pool_parameters.number_pool_id = generic_attribute.parameters.number_pool_id
1072
+
1073
+ if len(generics_with_attribute) > 1:
1074
+ raise ValidationError(
1075
+ f"{node_schema.kind}.{attribute.name} is a NumberPool inherited from more than one generic"
1076
+ )
1077
+ elif not attribute.inherited:
1078
+ for generic_name in node_schema.inherit_from:
1079
+ generic_schema = self.get_generic(name=generic_name, duplicate=False)
1080
+ if attribute.name in generic_schema.attribute_names:
1081
+ raise ValidationError(
1082
+ f"Overriding '{node_schema.kind}.{attribute.name}' NumberPool attribute from generic '{generic_name}' is not supported"
1083
+ )
1084
+
1085
+ if not number_pool_parameters.number_pool_id:
1086
+ number_pool_parameters.number_pool_id = str(uuid4())
1087
+
997
1088
  def validate_computed_attributes(self) -> None:
998
1089
  self.computed_attributes = ComputedAttributes()
999
1090
  for name in self.nodes.keys():
@@ -1136,7 +1227,7 @@ class SchemaBranch:
1136
1227
  self.set(name=name, schema=node)
1137
1228
 
1138
1229
  def process_labels(self) -> None:
1139
- def check_if_need_to_update_label(node) -> bool:
1230
+ def check_if_need_to_update_label(node: MainSchemaTypes) -> bool:
1140
1231
  if not node.label:
1141
1232
  return True
1142
1233
  for item in node.relationships + node.attributes:
@@ -1812,12 +1903,14 @@ class SchemaBranch:
1812
1903
  def generate_profile_from_node(self, node: NodeSchema) -> ProfileSchema:
1813
1904
  core_profile_schema = self.get(name=InfrahubKind.PROFILE, duplicate=False)
1814
1905
  core_name_attr = core_profile_schema.get_attribute(name="profile_name")
1815
- profile_name_attr = AttributeSchema(
1906
+ name_attr_schema_class = get_attribute_schema_class_for_kind(kind=core_name_attr.kind)
1907
+ profile_name_attr = name_attr_schema_class(
1816
1908
  **core_name_attr.model_dump(exclude=["id", "inherited"]),
1817
1909
  )
1818
1910
  profile_name_attr.branch = node.branch
1819
1911
  core_priority_attr = core_profile_schema.get_attribute(name="profile_priority")
1820
- profile_priority_attr = AttributeSchema(
1912
+ priority_attr_schema_class = get_attribute_schema_class_for_kind(kind=core_priority_attr.kind)
1913
+ profile_priority_attr = priority_attr_schema_class(
1821
1914
  **core_priority_attr.model_dump(exclude=["id", "inherited"]),
1822
1915
  )
1823
1916
  profile_priority_attr.branch = node.branch
@@ -1848,8 +1941,8 @@ class SchemaBranch:
1848
1941
  for node_attr in node.attributes:
1849
1942
  if node_attr.read_only or node_attr.optional is False:
1850
1943
  continue
1851
-
1852
- attr = AttributeSchema(
1944
+ attr_schema_class = get_attribute_schema_class_for_kind(kind=node_attr.kind)
1945
+ attr = attr_schema_class(
1853
1946
  optional=True,
1854
1947
  **node_attr.model_dump(exclude=["id", "unique", "optional", "read_only", "default_value", "inherited"]),
1855
1948
  )
@@ -1977,7 +2070,8 @@ class SchemaBranch:
1977
2070
  else self.get(name=InfrahubKind.OBJECTTEMPLATE, duplicate=False)
1978
2071
  )
1979
2072
  core_name_attr = core_template_schema.get_attribute(name=OBJECT_TEMPLATE_NAME_ATTR)
1980
- template_name_attr = AttributeSchema(
2073
+ name_attr_schema_class = get_attribute_schema_class_for_kind(kind=core_name_attr.kind)
2074
+ template_name_attr = name_attr_schema_class(
1981
2075
  **core_name_attr.model_dump(exclude=["id", "inherited"]),
1982
2076
  )
1983
2077
  template_name_attr.branch = node.branch
@@ -2035,7 +2129,8 @@ class SchemaBranch:
2035
2129
  if node_attr.unique or node_attr.read_only:
2036
2130
  continue
2037
2131
 
2038
- attr = AttributeSchema(
2132
+ attr_schema_class = get_attribute_schema_class_for_kind(kind=node_attr.kind)
2133
+ attr = attr_schema_class(
2039
2134
  optional=node_attr.optional if is_autogenerated_subtemplate else True,
2040
2135
  **node_attr.model_dump(exclude=["id", "unique", "optional", "read_only"]),
2041
2136
  )
@@ -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
@@ -13,15 +17,23 @@ from .node.inherit_from import NodeInheritFromChecker
13
17
  from .node.relationship import NodeRelationshipAddChecker
14
18
  from .relationship.count import RelationshipCountChecker
15
19
  from .relationship.optional import RelationshipOptionalChecker
16
- from .relationship.peer import RelationshipPeerChecker
20
+ from .relationship.peer import RelationshipPeerChecker, RelationshipPeerParentChecker
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,
@@ -30,6 +42,7 @@ CONSTRAINT_VALIDATOR_MAP: dict[str, type[ConstraintCheckerInterface] | None] = {
30
42
  "relationship.optional.update": RelationshipOptionalChecker,
31
43
  "relationship.min_count.update": RelationshipCountChecker,
32
44
  "relationship.max_count.update": RelationshipCountChecker,
45
+ "relationship.common_parent.update": RelationshipPeerParentChecker,
33
46
  "node.inherit_from.update": NodeInheritFromChecker,
34
47
  "node.uniqueness_constraints.update": UniquenessChecker,
35
48
  "node.parent.update": NodeHierarchyChecker,
@@ -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