infrahub-server 1.3.0a0__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 (68) hide show
  1. infrahub/core/attribute.py +3 -3
  2. infrahub/core/constants/__init__.py +5 -0
  3. infrahub/core/constants/infrahubkind.py +2 -0
  4. infrahub/core/migrations/query/attribute_rename.py +2 -4
  5. infrahub/core/migrations/query/delete_element_in_schema.py +16 -11
  6. infrahub/core/migrations/query/node_duplicate.py +16 -15
  7. infrahub/core/migrations/query/relationship_duplicate.py +16 -11
  8. infrahub/core/migrations/schema/node_attribute_remove.py +1 -2
  9. infrahub/core/migrations/schema/node_remove.py +16 -13
  10. infrahub/core/node/__init__.py +72 -14
  11. infrahub/core/node/resource_manager/ip_address_pool.py +6 -2
  12. infrahub/core/node/resource_manager/ip_prefix_pool.py +6 -2
  13. infrahub/core/node/resource_manager/number_pool.py +31 -5
  14. infrahub/core/node/standard.py +6 -1
  15. infrahub/core/protocols.py +9 -0
  16. infrahub/core/query/relationship.py +2 -4
  17. infrahub/core/schema/attribute_parameters.py +129 -5
  18. infrahub/core/schema/attribute_schema.py +38 -10
  19. infrahub/core/schema/definitions/core/__init__.py +16 -2
  20. infrahub/core/schema/definitions/core/group.py +45 -0
  21. infrahub/core/schema/definitions/core/resource_pool.py +20 -0
  22. infrahub/core/schema/definitions/internal.py +16 -3
  23. infrahub/core/schema/generated/attribute_schema.py +12 -5
  24. infrahub/core/schema/manager.py +3 -0
  25. infrahub/core/schema/schema_branch.py +55 -0
  26. infrahub/core/validators/__init__.py +8 -0
  27. infrahub/core/validators/attribute/choices.py +0 -1
  28. infrahub/core/validators/attribute/enum.py +0 -1
  29. infrahub/core/validators/attribute/kind.py +0 -1
  30. infrahub/core/validators/attribute/length.py +0 -1
  31. infrahub/core/validators/attribute/min_max.py +118 -0
  32. infrahub/core/validators/attribute/number_pool.py +106 -0
  33. infrahub/core/validators/attribute/optional.py +0 -2
  34. infrahub/core/validators/attribute/regex.py +0 -1
  35. infrahub/core/validators/enum.py +5 -0
  36. infrahub/database/__init__.py +15 -3
  37. infrahub/git/base.py +5 -3
  38. infrahub/git/integrator.py +102 -3
  39. infrahub/graphql/mutations/resource_manager.py +62 -6
  40. infrahub/graphql/queries/resource_manager.py +7 -1
  41. infrahub/graphql/queries/task.py +10 -0
  42. infrahub/graphql/types/task_log.py +3 -2
  43. infrahub/menu/menu.py +3 -3
  44. infrahub/pools/number.py +5 -3
  45. infrahub/task_manager/task.py +44 -4
  46. infrahub/types.py +6 -0
  47. infrahub_sdk/client.py +43 -10
  48. infrahub_sdk/node/__init__.py +39 -0
  49. infrahub_sdk/node/attribute.py +122 -0
  50. infrahub_sdk/node/constants.py +21 -0
  51. infrahub_sdk/{node.py → node/node.py} +50 -749
  52. infrahub_sdk/node/parsers.py +15 -0
  53. infrahub_sdk/node/property.py +24 -0
  54. infrahub_sdk/node/related_node.py +266 -0
  55. infrahub_sdk/node/relationship.py +302 -0
  56. infrahub_sdk/protocols.py +112 -0
  57. infrahub_sdk/protocols_base.py +34 -2
  58. infrahub_sdk/query_groups.py +13 -2
  59. infrahub_sdk/schema/main.py +1 -0
  60. infrahub_sdk/schema/repository.py +16 -0
  61. infrahub_sdk/spec/object.py +1 -1
  62. infrahub_sdk/store.py +1 -1
  63. infrahub_sdk/testing/schemas/car_person.py +1 -0
  64. {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b1.dist-info}/METADATA +3 -3
  65. {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b1.dist-info}/RECORD +68 -59
  66. {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b1.dist-info}/WHEEL +1 -1
  67. {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b1.dist-info}/LICENSE.txt +0 -0
  68. {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b1.dist-info}/entry_points.txt +0 -0
@@ -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,7 @@ from infrahub.core.schema import (
49
50
  SchemaRoot,
50
51
  TemplateSchema,
51
52
  )
53
+ from infrahub.core.schema.attribute_parameters import NumberPoolParameters
52
54
  from infrahub.core.schema.attribute_schema import get_attribute_schema_class_for_kind
53
55
  from infrahub.core.schema.definitions.core import core_profile_schema_definition
54
56
  from infrahub.core.validators import CONSTRAINT_VALIDATOR_MAP
@@ -518,6 +520,7 @@ class SchemaBranch:
518
520
  self.validate_names()
519
521
  self.validate_kinds()
520
522
  self.validate_computed_attributes()
523
+ self.validate_attribute_parameters()
521
524
  self.validate_default_values()
522
525
  self.validate_count_against_cardinality()
523
526
  self.validate_identifiers()
@@ -995,6 +998,58 @@ class SchemaBranch:
995
998
  f"{node.kind}: Relationship {rel.name!r} is referring an invalid peer {rel.peer!r}"
996
999
  ) from None
997
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
+
998
1053
  def validate_computed_attributes(self) -> None:
999
1054
  self.computed_attributes = ComputedAttributes()
1000
1055
  for name in self.nodes.keys():
@@ -1,7 +1,10 @@
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
@@ -26,6 +29,11 @@ CONSTRAINT_VALIDATOR_MAP: dict[str, type[ConstraintCheckerInterface] | None] = {
26
29
  "attribute.max_length.update": AttributeLengthChecker,
27
30
  ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_LENGTH_UPDATE.value: AttributeLengthChecker,
28
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,
29
37
  "attribute.unique.update": AttributeUniquenessChecker,
30
38
  "attribute.optional.update": AttributeOptionalChecker,
31
39
  "attribute.choices.update": AttributeChoicesChecker,
@@ -42,7 +42,6 @@ class AttributeChoicesUpdateValidatorQuery(AttributeSchemaValidatorQuery):
42
42
  LIMIT 1
43
43
  }
44
44
  WITH full_path, node, attribute_value, value_relationship
45
- WITH full_path, node, attribute_value, value_relationship
46
45
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
47
46
  AND attribute_value IS NOT NULL
48
47
  AND attribute_value <> $null_value
@@ -41,7 +41,6 @@ class AttributeEnumUpdateValidatorQuery(AttributeSchemaValidatorQuery):
41
41
  LIMIT 1
42
42
  }
43
43
  WITH full_path, node, attribute_value, value_relationship
44
- WITH full_path, node, attribute_value, value_relationship
45
44
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
46
45
  AND attribute_value IS NOT NULL
47
46
  AND attribute_value <> $null_value
@@ -48,7 +48,6 @@ class AttributeKindUpdateValidatorQuery(AttributeSchemaValidatorQuery):
48
48
  LIMIT 1
49
49
  }
50
50
  WITH full_path, node, attribute_value, value_relationship
51
- WITH full_path, node, attribute_value, value_relationship
52
51
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
53
52
  AND attribute_value IS NOT NULL
54
53
  AND attribute_value <> $null_value
@@ -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))
@@ -0,0 +1,118 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from infrahub.core.constants import PathType
6
+ from infrahub.core.path import DataPath, GroupedDataPaths
7
+ from infrahub.core.schema.attribute_parameters import NumberAttributeParameters
8
+ from infrahub.core.validators.enum import ConstraintIdentifier
9
+
10
+ from ..interface import ConstraintCheckerInterface
11
+ from ..shared import AttributeSchemaValidatorQuery
12
+
13
+ if TYPE_CHECKING:
14
+ from infrahub.core.branch import Branch
15
+ from infrahub.database import InfrahubDatabase
16
+
17
+ from ..model import SchemaConstraintValidatorRequest
18
+
19
+
20
+ class AttributeNumberUpdateValidatorQuery(AttributeSchemaValidatorQuery):
21
+ name: str = "attribute_constraints_number_validator"
22
+
23
+ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
24
+ branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string())
25
+ self.params.update(branch_params)
26
+
27
+ if not isinstance(self.attribute_schema.parameters, NumberAttributeParameters):
28
+ raise ValueError("attribute parameters are not a NumberAttributeParameters")
29
+
30
+ self.params["attr_name"] = self.attribute_schema.name
31
+ self.params["min_value"] = self.attribute_schema.parameters.min_value
32
+ self.params["max_value"] = self.attribute_schema.parameters.max_value
33
+ self.params["excluded_values"] = self.attribute_schema.parameters.get_excluded_single_values()
34
+ self.params["excluded_ranges"] = self.attribute_schema.parameters.get_excluded_ranges()
35
+
36
+ query = """
37
+ MATCH (n:%(node_kind)s)
38
+ CALL (n) {
39
+ MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
40
+ WHERE all(
41
+ r in relationships(path)
42
+ WHERE %(branch_filter)s
43
+ )
44
+ RETURN path as full_path, n as node, rv as value_relationship, av.value as attribute_value
45
+ ORDER BY rv.branch_level DESC, ra.branch_level DESC, rr.branch_level DESC, rv.from DESC, ra.from DESC, rr.from DESC
46
+ LIMIT 1
47
+ }
48
+ WITH full_path, node, attribute_value, value_relationship
49
+ WHERE all(r in relationships(full_path) WHERE r.status = "active")
50
+ AND (
51
+ (toInteger($min_value) IS NOT NULL AND attribute_value < toInteger($min_value))
52
+ OR (toInteger($max_value) IS NOT NULL AND attribute_value > toInteger($max_value))
53
+ OR (size($excluded_values) > 0 AND attribute_value IN $excluded_values)
54
+ OR (size($excluded_ranges) > 0 AND any(range in $excluded_ranges WHERE attribute_value >= range[0] AND attribute_value <= range[1]))
55
+ )
56
+ """ % {"branch_filter": branch_filter, "node_kind": self.node_schema.kind}
57
+
58
+ self.add_to_query(query)
59
+ self.return_labels = ["node.uuid", "value_relationship", "attribute_value"]
60
+
61
+ async def get_paths(self) -> GroupedDataPaths:
62
+ grouped_data_paths = GroupedDataPaths()
63
+ for result in self.results:
64
+ grouped_data_paths.add_data_path(
65
+ DataPath(
66
+ branch=str(result.get("value_relationship").get("branch")),
67
+ path_type=PathType.ATTRIBUTE,
68
+ node_id=str(result.get("node.uuid")),
69
+ field_name=self.attribute_schema.name,
70
+ kind=self.node_schema.kind,
71
+ value=result.get("attribute_value"),
72
+ ),
73
+ )
74
+
75
+ return grouped_data_paths
76
+
77
+
78
+ class AttributeNumberChecker(ConstraintCheckerInterface):
79
+ query_classes = [AttributeNumberUpdateValidatorQuery]
80
+
81
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
82
+ self.db = db
83
+ self.branch = branch
84
+
85
+ @property
86
+ def name(self) -> str:
87
+ return "attribute.number.update"
88
+
89
+ def supports(self, request: SchemaConstraintValidatorRequest) -> bool:
90
+ return request.constraint_name in (
91
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_VALUE_UPDATE.value,
92
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_VALUE_UPDATE.value,
93
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_EXCLUDED_VALUES_UPDATE.value,
94
+ )
95
+
96
+ async def check(self, request: SchemaConstraintValidatorRequest) -> list[GroupedDataPaths]:
97
+ grouped_data_paths_list: list[GroupedDataPaths] = []
98
+ if not request.schema_path.field_name:
99
+ raise ValueError("field_name is not defined")
100
+ attribute_schema = request.node_schema.get_attribute(name=request.schema_path.field_name)
101
+ if not isinstance(attribute_schema.parameters, NumberAttributeParameters):
102
+ raise ValueError("attribute parameters are not a NumberAttributeParameters")
103
+
104
+ if (
105
+ attribute_schema.parameters.min_value is None
106
+ and attribute_schema.parameters.max_value is None
107
+ and attribute_schema.parameters.excluded_values is None
108
+ ):
109
+ return grouped_data_paths_list
110
+
111
+ for query_class in self.query_classes:
112
+ # TODO add exception handling
113
+ query = await query_class.init(
114
+ db=self.db, branch=self.branch, node_schema=request.node_schema, schema_path=request.schema_path
115
+ )
116
+ await query.execute(db=self.db)
117
+ grouped_data_paths_list.append(await query.get_paths())
118
+ return grouped_data_paths_list
@@ -0,0 +1,106 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from infrahub.core.constants import PathType
6
+ from infrahub.core.path import DataPath, GroupedDataPaths
7
+ from infrahub.core.schema.attribute_parameters import NumberPoolParameters
8
+ from infrahub.core.validators.enum import ConstraintIdentifier
9
+
10
+ from ..interface import ConstraintCheckerInterface
11
+ from ..shared import AttributeSchemaValidatorQuery
12
+
13
+ if TYPE_CHECKING:
14
+ from infrahub.core.branch import Branch
15
+ from infrahub.database import InfrahubDatabase
16
+
17
+ from ..model import SchemaConstraintValidatorRequest
18
+
19
+
20
+ class AttributeNumberPoolUpdateValidatorQuery(AttributeSchemaValidatorQuery):
21
+ name: str = "attribute_constraints_numberpool_validator"
22
+
23
+ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
24
+ branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string())
25
+ self.params.update(branch_params)
26
+
27
+ if not isinstance(self.attribute_schema.parameters, NumberPoolParameters):
28
+ raise ValueError("attribute parameters are not a NumberPoolParameters")
29
+
30
+ self.params["attr_name"] = self.attribute_schema.name
31
+ self.params["start_range"] = self.attribute_schema.parameters.start_range
32
+ self.params["end_range"] = self.attribute_schema.parameters.end_range
33
+
34
+ query = """
35
+ MATCH (n:%(node_kind)s)
36
+ CALL (n) {
37
+ MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
38
+ WHERE all(
39
+ r in relationships(path)
40
+ WHERE %(branch_filter)s
41
+ )
42
+ RETURN path as full_path, n as node, rv as value_relationship, av.value as attribute_value
43
+ ORDER BY rv.branch_level DESC, ra.branch_level DESC, rr.branch_level DESC, rv.from DESC, ra.from DESC, rr.from DESC
44
+ LIMIT 1
45
+ }
46
+ WITH full_path, node, attribute_value, value_relationship
47
+ WHERE all(r in relationships(full_path) WHERE r.status = "active")
48
+ AND (
49
+ (toInteger($start_range) IS NOT NULL AND attribute_value < toInteger($start_range))
50
+ OR (toInteger($end_range) IS NOT NULL AND attribute_value > toInteger($end_range))
51
+ )
52
+ """ % {"branch_filter": branch_filter, "node_kind": self.node_schema.kind}
53
+
54
+ self.add_to_query(query)
55
+ self.return_labels = ["node.uuid", "value_relationship", "attribute_value"]
56
+
57
+ async def get_paths(self) -> GroupedDataPaths:
58
+ grouped_data_paths = GroupedDataPaths()
59
+ for result in self.results:
60
+ grouped_data_paths.add_data_path(
61
+ DataPath(
62
+ branch=str(result.get("value_relationship").get("branch")),
63
+ path_type=PathType.ATTRIBUTE,
64
+ node_id=str(result.get("node.uuid")),
65
+ field_name=self.attribute_schema.name,
66
+ kind=self.node_schema.kind,
67
+ value=result.get("attribute_value"),
68
+ ),
69
+ )
70
+
71
+ return grouped_data_paths
72
+
73
+
74
+ class AttributeNumberPoolChecker(ConstraintCheckerInterface):
75
+ query_classes = [AttributeNumberPoolUpdateValidatorQuery]
76
+
77
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
78
+ self.db = db
79
+ self.branch = branch
80
+
81
+ @property
82
+ def name(self) -> str:
83
+ return "attribute.number.update"
84
+
85
+ def supports(self, request: SchemaConstraintValidatorRequest) -> bool:
86
+ return request.constraint_name in (
87
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_START_RANGE_UPDATE.value,
88
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_END_RANGE_UPDATE.value,
89
+ )
90
+
91
+ async def check(self, request: SchemaConstraintValidatorRequest) -> list[GroupedDataPaths]:
92
+ grouped_data_paths_list: list[GroupedDataPaths] = []
93
+ if not request.schema_path.field_name:
94
+ raise ValueError("field_name is not defined")
95
+ attribute_schema = request.node_schema.get_attribute(name=request.schema_path.field_name)
96
+ if not isinstance(attribute_schema.parameters, NumberPoolParameters):
97
+ raise ValueError("attribute parameters are not a NumberPoolParameters")
98
+
99
+ for query_class in self.query_classes:
100
+ # TODO add exception handling
101
+ query = await query_class.init(
102
+ db=self.db, branch=self.branch, node_schema=request.node_schema, schema_path=request.schema_path
103
+ )
104
+ await query.execute(db=self.db)
105
+ grouped_data_paths_list.append(await query.get_paths())
106
+ return grouped_data_paths_list
@@ -38,7 +38,6 @@ class AttributeOptionalUpdateValidatorQuery(AttributeSchemaValidatorQuery):
38
38
  LIMIT 1
39
39
  }
40
40
  WITH full_path, node, attribute_value, value_relationship
41
- WITH full_path, node, attribute_value, value_relationship
42
41
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
43
42
  AND (attribute_value IS NULL OR attribute_value = $null_value)
44
43
  """ % {"branch_filter": branch_filter, "node_kind": self.node_schema.kind}
@@ -85,7 +84,6 @@ class AttributeOptionalChecker(ConstraintCheckerInterface):
85
84
  return grouped_data_paths_list
86
85
 
87
86
  for query_class in self.query_classes:
88
- # TODO add exception handling
89
87
  query = await query_class.init(
90
88
  db=self.db, branch=self.branch, node_schema=request.node_schema, schema_path=request.schema_path
91
89
  )
@@ -39,7 +39,6 @@ class AttributeRegexUpdateValidatorQuery(AttributeSchemaValidatorQuery):
39
39
  LIMIT 1
40
40
  }
41
41
  WITH full_path, node, attribute_value, value_relationship
42
- WITH full_path, node, attribute_value, value_relationship
43
42
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
44
43
  AND attribute_value <> $null_value
45
44
  AND NOT attribute_value =~ $attr_value_regex
@@ -5,3 +5,8 @@ class ConstraintIdentifier(str, Enum):
5
5
  ATTRIBUTE_PARAMETERS_REGEX_UPDATE = "attribute.parameters.regex.update"
6
6
  ATTRIBUTE_PARAMETERS_MIN_LENGTH_UPDATE = "attribute.parameters.min_length.update"
7
7
  ATTRIBUTE_PARAMETERS_MAX_LENGTH_UPDATE = "attribute.parameters.max_length.update"
8
+ ATTRIBUTE_PARAMETERS_MIN_VALUE_UPDATE = "attribute.parameters.min_value.update"
9
+ ATTRIBUTE_PARAMETERS_MAX_VALUE_UPDATE = "attribute.parameters.max_value.update"
10
+ ATTRIBUTE_PARAMETERS_EXCLUDED_VALUES_UPDATE = "attribute.parameters.excluded_values.update"
11
+ ATTRIBUTE_PARAMETERS_END_RANGE_UPDATE = "attribute.parameters.end_range.update"
12
+ ATTRIBUTE_PARAMETERS_START_RANGE_UPDATE = "attribute.parameters.start_range.update"
@@ -39,7 +39,7 @@ if TYPE_CHECKING:
39
39
  from types import TracebackType
40
40
 
41
41
  from infrahub.core.branch import Branch
42
- from infrahub.core.schema import MainSchemaTypes, NodeSchema
42
+ from infrahub.core.schema import GenericSchema, MainSchemaTypes, NodeSchema
43
43
  from infrahub.core.schema.schema_branch import SchemaBranch
44
44
 
45
45
  validated_database = {}
@@ -91,6 +91,15 @@ class DatabaseSchemaManager:
91
91
 
92
92
  raise ValueError("The selected node is not of type NodeSchema")
93
93
 
94
+ def get_generic_schema(
95
+ self, name: str, branch: Branch | str | None = None, duplicate: bool = True
96
+ ) -> GenericSchema:
97
+ schema = self.get(name=name, branch=branch, duplicate=duplicate)
98
+ if schema.is_generic_schema:
99
+ return schema
100
+
101
+ raise ValueError("The selected node is not of type GenericSchema")
102
+
94
103
  def set(self, name: str, schema: MainSchemaTypes, branch: str | None = None) -> int:
95
104
  branch_name = get_branch_name(branch=branch)
96
105
  if branch_name not in self._db._schemas:
@@ -284,7 +293,7 @@ class InfrahubDatabase:
284
293
  exc_type: type[BaseException] | None,
285
294
  exc_value: BaseException | None,
286
295
  traceback: TracebackType | None,
287
- ):
296
+ ) -> None:
288
297
  if self._mode == InfrahubDatabaseMode.SESSION:
289
298
  return await self._session.close()
290
299
 
@@ -487,7 +496,10 @@ async def get_db(retry: int = 0) -> AsyncDriver:
487
496
  auth=(config.SETTINGS.database.username, config.SETTINGS.database.password),
488
497
  encrypted=config.SETTINGS.database.tls_enabled,
489
498
  trusted_certificates=trusted_certificates,
490
- notifications_disabled_categories=[NotificationDisabledCategory.UNRECOGNIZED],
499
+ notifications_disabled_categories=[
500
+ NotificationDisabledCategory.UNRECOGNIZED,
501
+ NotificationDisabledCategory.DEPRECATION, # TODO: Remove me with 1.3
502
+ ],
491
503
  notifications_min_severity=NotificationMinimumSeverity.WARNING,
492
504
  )
493
505
 
infrahub/git/base.py CHANGED
@@ -162,6 +162,11 @@ class InfrahubRepositoryBase(BaseModel, ABC):
162
162
  infrahub_branch_name: str | None = Field(None, description="Infrahub branch on which to sync the remote repository")
163
163
  model_config = ConfigDict(arbitrary_types_allowed=True, ignored_types=(Flow, Task))
164
164
 
165
+ def get_client(self) -> InfrahubClient:
166
+ if self.client is None:
167
+ raise ValueError("Client is not set")
168
+ return self.client
169
+
165
170
  @property
166
171
  def sdk(self) -> InfrahubClient:
167
172
  if self.client:
@@ -445,9 +450,6 @@ class InfrahubRepositoryBase(BaseModel, ABC):
445
450
 
446
451
  return [Worktree.init(response) for response in responses]
447
452
 
448
- def get_client(self) -> InfrahubClient:
449
- return self.sdk
450
-
451
453
  def get_location(self) -> str:
452
454
  if self.location:
453
455
  return self.location
@@ -29,10 +29,12 @@ from infrahub_sdk.schema.repository import (
29
29
  InfrahubPythonTransformConfig,
30
30
  InfrahubRepositoryConfig,
31
31
  )
32
+ from infrahub_sdk.spec.menu import MenuFile
33
+ from infrahub_sdk.spec.object import ObjectFile
32
34
  from infrahub_sdk.template import Jinja2Template
33
35
  from infrahub_sdk.template.exceptions import JinjaTemplateError
34
36
  from infrahub_sdk.utils import compare_lists
35
- from infrahub_sdk.yaml import SchemaFile
37
+ from infrahub_sdk.yaml import InfrahubFile, SchemaFile
36
38
  from prefect import flow, task
37
39
  from prefect.cache_policies import NONE
38
40
  from prefect.logging import get_run_logger
@@ -40,7 +42,7 @@ from pydantic import BaseModel, Field
40
42
  from pydantic import ValidationError as PydanticValidationError
41
43
  from typing_extensions import Self
42
44
 
43
- from infrahub.core.constants import ArtifactStatus, ContentType, InfrahubKind, RepositorySyncStatus
45
+ from infrahub.core.constants import ArtifactStatus, ContentType, InfrahubKind, RepositoryObjects, RepositorySyncStatus
44
46
  from infrahub.core.registry import registry
45
47
  from infrahub.events.artifact_action import ArtifactCreatedEvent, ArtifactUpdatedEvent
46
48
  from infrahub.events.models import EventMeta
@@ -54,6 +56,7 @@ if TYPE_CHECKING:
54
56
  import types
55
57
 
56
58
  from infrahub_sdk.checks import InfrahubCheck
59
+ from infrahub_sdk.ctl.utils import YamlFileVar
57
60
  from infrahub_sdk.schema.repository import InfrahubRepositoryArtifactDefinitionConfig
58
61
  from infrahub_sdk.transforms import InfrahubTransform
59
62
 
@@ -159,7 +162,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
159
162
  async def ensure_location_is_defined(self) -> None:
160
163
  if self.location:
161
164
  return
162
- client = self.get_client()
165
+ client = self.sdk
163
166
  repo = await client.get(
164
167
  kind=CoreGenericRepository, name__value=self.name, exclude=["tags", "credential"], raise_when_missing=True
165
168
  )
@@ -179,6 +182,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
179
182
 
180
183
  config_file = await self.get_repository_config(branch_name=infrahub_branch_name, commit=commit) # type: ignore[misc]
181
184
  sync_status = RepositorySyncStatus.IN_SYNC if config_file else RepositorySyncStatus.ERROR_IMPORT
185
+
182
186
  error: Exception | None = None
183
187
 
184
188
  try:
@@ -189,6 +193,17 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
189
193
  branch_name=infrahub_branch_name, commit=commit, config_file=config_file
190
194
  ) # type: ignore[misc]
191
195
 
196
+ await self.import_objects(
197
+ branch_name=infrahub_branch_name,
198
+ commit=commit,
199
+ config_file=config_file,
200
+ ) # type: ignore[misc]
201
+ await self.import_objects(
202
+ branch_name=infrahub_branch_name,
203
+ commit=commit,
204
+ config_file=config_file,
205
+ ) # type: ignore[misc]
206
+
192
207
  await self.import_all_python_files( # type: ignore[call-overload]
193
208
  branch_name=infrahub_branch_name, commit=commit, config_file=config_file
194
209
  ) # type: ignore[misc]
@@ -815,6 +830,80 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
815
830
  log.info(f"TransformPython {transform_name!r} not found locally, deleting")
816
831
  await transform_definition_in_graph[transform_name].delete()
817
832
 
833
+ async def _load_yamlfile_from_disk(self, paths: list[Path], file_type: type[YamlFileVar]) -> list[YamlFileVar]:
834
+ data_files = file_type.load_from_disk(paths=paths)
835
+
836
+ for data_file in data_files:
837
+ if not data_file.valid or not data_file.content:
838
+ raise ValueError(f"{data_file.error_message} ({data_file.location})")
839
+
840
+ return data_files
841
+
842
+ async def _load_objects(
843
+ self,
844
+ paths: list[Path],
845
+ branch: str,
846
+ file_type: type[InfrahubFile],
847
+ ) -> None:
848
+ """Load one or multiple objects files into Infrahub."""
849
+
850
+ log = get_run_logger()
851
+ files = await self._load_yamlfile_from_disk(paths=paths, file_type=file_type)
852
+
853
+ for file in files:
854
+ await file.validate_format(client=self.sdk, branch=branch)
855
+ schema = await self.sdk.schema.get(kind=file.spec.kind, branch=branch)
856
+ if not schema.human_friendly_id and not schema.default_filter:
857
+ raise ValueError(
858
+ f"Schemas of objects or menus defined within {file.location} "
859
+ "should have a `human_friendly_id` defined to avoid creating duplicated objects."
860
+ )
861
+
862
+ for file in files:
863
+ log.info(f"Loading objects defined in {file.location}")
864
+ await file.process(client=self.sdk, branch=branch)
865
+
866
+ async def _import_file_paths(
867
+ self, branch_name: str, commit: str, files_pathes: list[Path], object_type: RepositoryObjects
868
+ ) -> None:
869
+ branch_wt = self.get_worktree(identifier=commit or branch_name)
870
+ file_pathes = [branch_wt.directory / file_path for file_path in files_pathes]
871
+
872
+ # We currently assume there can't be concurrent imports, but if so, we might need to clone the client before tracking here.
873
+ async with self.sdk.start_tracking(
874
+ identifier=f"group-repo-{object_type.value}-{self.id}",
875
+ delete_unused_nodes=True,
876
+ branch=branch_name,
877
+ group_type="CoreRepositoryGroup",
878
+ group_params={"content": object_type.value, "repository": str(self.id)},
879
+ ):
880
+ file_type = repo_object_type_to_file_type(object_type)
881
+ await self._load_objects(
882
+ paths=file_pathes,
883
+ branch=branch_name,
884
+ file_type=file_type,
885
+ )
886
+
887
+ @task(name="import-objects", task_run_name="Import Objects", cache_policy=NONE) # type: ignore[arg-type]
888
+ async def import_objects(
889
+ self,
890
+ branch_name: str,
891
+ commit: str,
892
+ config_file: InfrahubRepositoryConfig,
893
+ ) -> None:
894
+ await self._import_file_paths(
895
+ branch_name=branch_name,
896
+ commit=commit,
897
+ files_pathes=config_file.objects,
898
+ object_type=RepositoryObjects.OBJECT,
899
+ )
900
+ await self._import_file_paths(
901
+ branch_name=branch_name,
902
+ commit=commit,
903
+ files_pathes=config_file.menus,
904
+ object_type=RepositoryObjects.MENU,
905
+ )
906
+
818
907
  @task(name="check-definition-get", task_run_name="Get Check Definition", cache_policy=NONE) # type: ignore[arg-type]
819
908
  async def get_check_definition(
820
909
  self,
@@ -1342,3 +1431,13 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
1342
1431
 
1343
1432
  await self.service.event.send(event=event)
1344
1433
  return ArtifactGenerateResult(changed=True, checksum=checksum, storage_id=storage_id, artifact_id=artifact.id)
1434
+
1435
+
1436
+ def repo_object_type_to_file_type(repo_object: RepositoryObjects) -> type[InfrahubFile]:
1437
+ match repo_object:
1438
+ case RepositoryObjects.OBJECT:
1439
+ return ObjectFile
1440
+ case RepositoryObjects.MENU:
1441
+ return MenuFile
1442
+ case _:
1443
+ raise ValueError(f"Unknown repository object type: {repo_object}")