Skip to content

Commit c43add3

Browse files
authored
Merge pull request #6530 from opsmill/pog-attribute-numberpool-migration
Attribute NumberPool migration and tests
2 parents 204dc9e + 3d9ce0f commit c43add3

File tree

4 files changed

+159
-1
lines changed

4 files changed

+159
-1
lines changed

backend/infrahub/core/validators/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .attribute.enum import AttributeEnumChecker
55
from .attribute.kind import AttributeKindChecker
66
from .attribute.length import AttributeLengthChecker
7+
from .attribute.number_pool import AttributeNumberPoolChecker
78
from .attribute.optional import AttributeOptionalChecker
89
from .attribute.regex import AttributeRegexChecker
910
from .attribute.unique import AttributeUniquenessChecker
@@ -31,6 +32,8 @@
3132
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_VALUE_UPDATE.value: AttributeNumberChecker,
3233
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_VALUE_UPDATE.value: AttributeNumberChecker,
3334
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_EXCLUDED_VALUES_UPDATE.value: AttributeNumberChecker,
35+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_START_RANGE_UPDATE: AttributeNumberPoolChecker,
36+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_END_RANGE_UPDATE: AttributeNumberPoolChecker,
3437
"attribute.unique.update": AttributeUniquenessChecker,
3538
"attribute.optional.update": AttributeOptionalChecker,
3639
"attribute.choices.update": AttributeChoicesChecker,
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

backend/infrahub/core/validators/enum.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ class ConstraintIdentifier(str, Enum):
88
ATTRIBUTE_PARAMETERS_MIN_VALUE_UPDATE = "attribute.parameters.min_value.update"
99
ATTRIBUTE_PARAMETERS_MAX_VALUE_UPDATE = "attribute.parameters.max_value.update"
1010
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"

backend/tests/integration/schema_lifecycle/test_attribute_parameters_update.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
from infrahub.core import registry
88
from infrahub.core.schema import SchemaRoot
9-
from infrahub.core.schema.attribute_parameters import NumberAttributeParameters, TextAttributeParameters
9+
from infrahub.core.schema.attribute_parameters import (
10+
NumberAttributeParameters,
11+
NumberPoolParameters,
12+
TextAttributeParameters,
13+
)
1014
from tests.helpers.schema import load_schema as load_schema_root
1115
from tests.helpers.test_app import TestInfrahubApp
1216

@@ -32,6 +36,13 @@ def schema_thing_legacy(self) -> dict[str, Any]:
3236
"label": "Thing",
3337
"attributes": [
3438
{"name": "value", "kind": "Text", "regex": "old", "min_length": 0, "max_length": 4},
39+
{
40+
"name": "assigned_number",
41+
"kind": "NumberPool",
42+
"optional": False,
43+
"read_only": True,
44+
"parameters": {"start_range": 10, "end_range": 200},
45+
},
3546
],
3647
}
3748

@@ -45,6 +56,13 @@ def schema_thing(self) -> dict[str, Any]:
4556
"attributes": [
4657
{"name": "value", "kind": "Text", "parameters": {"regex": "newnew", "min_length": 5, "max_length": 6}},
4758
{"name": "number", "kind": "Number", "parameters": {"min_value": 0, "max_value": 10}},
59+
{
60+
"name": "assigned_number",
61+
"kind": "NumberPool",
62+
"optional": False,
63+
"read_only": True,
64+
"parameters": {"start_range": 5, "end_range": 10000},
65+
},
4866
],
4967
}
5068

@@ -107,6 +125,13 @@ def schema_thing_02(self, regex_02, min_length_02, max_length_02) -> dict[str, A
107125
"kind": "Number",
108126
"parameters": {"min_value": 20, "max_value": 30},
109127
},
128+
{
129+
"name": "assigned_number",
130+
"kind": "NumberPool",
131+
"optional": False,
132+
"read_only": True,
133+
"parameters": {"start_range": 50, "end_range": 1200},
134+
},
110135
],
111136
}
112137

@@ -153,6 +178,12 @@ def _validate_schema_number_parameters(self, schema: NodeSchema, min_value: int
153178
assert number_attr.parameters.min_value == min_value
154179
assert number_attr.parameters.max_value == max_value
155180

181+
def _validate_schema_numberpool_parameters(self, schema: NodeSchema, start_range: int, end_range: int) -> None:
182+
number_attr = schema.get_attribute("assigned_number")
183+
assert isinstance(number_attr.parameters, NumberPoolParameters)
184+
assert number_attr.parameters.start_range == start_range
185+
assert number_attr.parameters.end_range == end_range
186+
156187
async def test_schema_01_is_correct(self, db: InfrahubDatabase, default_branch: Branch, load_schema_01) -> None:
157188
schema_branch = await registry.schema.load_schema_from_db(db=db, branch=default_branch.name)
158189

@@ -162,6 +193,7 @@ async def test_schema_01_is_correct(self, db: InfrahubDatabase, default_branch:
162193
new_schema = schema_branch.get_node(name=NEW_KIND, duplicate=False)
163194
self._validate_schema_value_parameters(schema=new_schema, regex="newnew", min_length=5, max_length=6)
164195
self._validate_schema_number_parameters(schema=new_schema, min_value=0, max_value=10)
196+
self._validate_schema_numberpool_parameters(schema=new_schema, start_range=5, end_range=10000)
165197

166198
async def test_schema02_load_update(
167199
self,
@@ -243,6 +275,20 @@ async def test_schema02_load_update(
243275
},
244276
"removed": {},
245277
},
278+
"assigned_number": {
279+
"added": {},
280+
"changed": {
281+
"parameters": {
282+
"added": {},
283+
"changed": {
284+
"start_range": None,
285+
"end_range": None,
286+
},
287+
"removed": {},
288+
},
289+
},
290+
"removed": {},
291+
},
246292
},
247293
"removed": {},
248294
},
@@ -279,3 +325,4 @@ async def test_step02_load_schema_with_overrides(
279325
schema=new_schema, regex=regex_02, min_length=min_length_02, max_length=max_length_02
280326
)
281327
self._validate_schema_number_parameters(schema=new_schema, min_value=20, max_value=30)
328+
self._validate_schema_numberpool_parameters(schema=new_schema, start_range=50, end_range=1200)

0 commit comments

Comments
 (0)