infrahub-server 1.3.0b6__py3-none-any.whl → 1.3.1__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 (29) hide show
  1. infrahub/cli/db.py +7 -5
  2. infrahub/cli/upgrade.py +6 -1
  3. infrahub/core/attribute.py +5 -0
  4. infrahub/core/diff/coordinator.py +8 -1
  5. infrahub/core/diff/query/field_specifiers.py +1 -1
  6. infrahub/core/diff/query/merge.py +2 -2
  7. infrahub/core/graph/__init__.py +1 -1
  8. infrahub/core/migrations/graph/__init__.py +2 -0
  9. infrahub/core/migrations/graph/m012_convert_account_generic.py +1 -1
  10. infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +2 -2
  11. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +2 -2
  12. infrahub/core/migrations/graph/m031_check_number_attributes.py +102 -0
  13. infrahub/core/migrations/query/attribute_rename.py +1 -1
  14. infrahub/core/node/__init__.py +5 -1
  15. infrahub/core/query/delete.py +3 -3
  16. infrahub/core/schema/attribute_parameters.py +12 -5
  17. infrahub/core/schema/basenode_schema.py +107 -1
  18. infrahub/core/schema/schema_branch.py +17 -5
  19. infrahub/core/validators/attribute/min_max.py +7 -2
  20. infrahub/graphql/app.py +5 -1
  21. infrahub/services/adapters/message_bus/nats.py +5 -1
  22. infrahub/services/scheduler.py +5 -1
  23. infrahub_sdk/node/__init__.py +2 -0
  24. infrahub_sdk/node/node.py +21 -2
  25. {infrahub_server-1.3.0b6.dist-info → infrahub_server-1.3.1.dist-info}/METADATA +1 -1
  26. {infrahub_server-1.3.0b6.dist-info → infrahub_server-1.3.1.dist-info}/RECORD +29 -28
  27. {infrahub_server-1.3.0b6.dist-info → infrahub_server-1.3.1.dist-info}/LICENSE.txt +0 -0
  28. {infrahub_server-1.3.0b6.dist-info → infrahub_server-1.3.1.dist-info}/WHEEL +0 -0
  29. {infrahub_server-1.3.0b6.dist-info → infrahub_server-1.3.1.dist-info}/entry_points.txt +0 -0
infrahub/cli/db.py CHANGED
@@ -287,10 +287,10 @@ async def index(
287
287
  await dbdriver.close()
288
288
 
289
289
 
290
- async def migrate_database(db: InfrahubDatabase, initialize: bool = False, check: bool = False) -> None:
290
+ async def migrate_database(db: InfrahubDatabase, initialize: bool = False, check: bool = False) -> bool:
291
291
  """Apply the latest migrations to the database, this function will print the status directly in the console.
292
292
 
293
- This function is expected to run on an empty
293
+ Returns a boolean indicating whether a migration failed or if all migrations succeeded.
294
294
 
295
295
  Args:
296
296
  db: The database object.
@@ -306,14 +306,14 @@ async def migrate_database(db: InfrahubDatabase, initialize: bool = False, check
306
306
 
307
307
  if not migrations:
308
308
  rprint(f"Database up-to-date (v{root_node.graph_version}), no migration to execute.")
309
- return
309
+ return True
310
310
 
311
311
  rprint(
312
312
  f"Database needs to be updated (v{root_node.graph_version} -> v{GRAPH_VERSION}), {len(migrations)} migrations pending"
313
313
  )
314
314
 
315
315
  if check:
316
- return
316
+ return True
317
317
 
318
318
  for migration in migrations:
319
319
  execution_result = await migration.execute(db=db)
@@ -333,7 +333,9 @@ async def migrate_database(db: InfrahubDatabase, initialize: bool = False, check
333
333
  if validation_result and not validation_result.success:
334
334
  for error in validation_result.errors:
335
335
  rprint(f" {error}")
336
- break
336
+ return False
337
+
338
+ return True
337
339
 
338
340
 
339
341
  async def initialize_internal_schema() -> None:
infrahub/cli/upgrade.py CHANGED
@@ -75,7 +75,12 @@ async def upgrade_cmd(
75
75
  # -------------------------------------------
76
76
  # Upgrade Infrahub Database and Schema
77
77
  # -------------------------------------------
78
- await migrate_database(db=dbdriver, initialize=False, check=check)
78
+
79
+ if not await migrate_database(db=dbdriver, initialize=False, check=check):
80
+ # A migration failed, stop the upgrade process
81
+ rprint("Upgrade cancelled due to migration failure.")
82
+ await dbdriver.close()
83
+ return
79
84
 
80
85
  await initialize_internal_schema()
81
86
  await update_core_schema(db=dbdriver, service=service, initialize=False)
@@ -31,6 +31,7 @@ from infrahub.helpers import hash_password
31
31
 
32
32
  from ..types import ATTRIBUTE_TYPES, LARGE_ATTRIBUTE_TYPES
33
33
  from .constants.relationship_label import RELATIONSHIP_TO_NODE_LABEL, RELATIONSHIP_TO_VALUE_LABEL
34
+ from .schema.attribute_parameters import NumberAttributeParameters
34
35
 
35
36
  if TYPE_CHECKING:
36
37
  from infrahub.core.branch import Branch
@@ -255,6 +256,10 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
255
256
  if len(value) > max_length:
256
257
  raise ValidationError({name: f"{value} must have a maximum length of {max_length!r}"})
257
258
 
259
+ # Some invalid values may exist due to https://github.com/opsmill/infrahub/issues/6714.
260
+ if config.SETTINGS.main.schema_strict_mode and isinstance(schema.parameters, NumberAttributeParameters):
261
+ schema.parameters.check_valid_value(value=value, name=name)
262
+
258
263
  if schema.enum:
259
264
  try:
260
265
  schema.convert_value_to_enum(value)
@@ -4,7 +4,10 @@ from dataclasses import dataclass, field
4
4
  from typing import TYPE_CHECKING, Iterable, Literal, Sequence, overload
5
5
  from uuid import uuid4
6
6
 
7
+ from prefect import flow
8
+
7
9
  from infrahub import lock
10
+ from infrahub.core.branch import Branch # noqa: TC001
8
11
  from infrahub.core.timestamp import Timestamp
9
12
  from infrahub.exceptions import ValidationError
10
13
  from infrahub.log import get_logger
@@ -22,7 +25,6 @@ from .model.path import (
22
25
  )
23
26
 
24
27
  if TYPE_CHECKING:
25
- from infrahub.core.branch import Branch
26
28
  from infrahub.core.node import Node
27
29
 
28
30
  from .calculator import DiffCalculator
@@ -301,6 +303,11 @@ class DiffCoordinator:
301
303
  force_branch_refresh: Literal[False] = ...,
302
304
  ) -> tuple[EnrichedDiffs | EnrichedDiffsMetadata, set[NodeIdentifier]]: ...
303
305
 
306
+ @flow( # type: ignore[misc]
307
+ name="update-diff",
308
+ flow_run_name="Update diff for {base_branch.name} - {diff_branch.name}: ({from_time}-{to_time}),tracking_id={tracking_id}",
309
+ validate_parameters=False,
310
+ )
304
311
  async def _update_diffs(
305
312
  self,
306
313
  base_branch: Branch,
@@ -15,7 +15,7 @@ class EnrichedDiffFieldSpecifiersQuery(Query):
15
15
  async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
16
16
  self.params["diff_id"] = self.diff_id
17
17
  query = """
18
- CALL {
18
+ CALL () {
19
19
  MATCH (root:DiffRoot {uuid: $diff_id})-[:DIFF_HAS_NODE]->(node:DiffNode)-[:DIFF_HAS_ATTRIBUTE]->(attr:DiffAttribute)
20
20
  WHERE (root.is_merged IS NULL OR root.is_merged <> TRUE)
21
21
  RETURN node.uuid AS node_uuid, node.kind AS node_kind, attr.name AS field_name
@@ -710,14 +710,14 @@ class DiffMergeRollbackQuery(Query):
710
710
  // ---------------------------
711
711
  // reset to times on target branch
712
712
  // ---------------------------
713
- CALL {
713
+ CALL () {
714
714
  OPTIONAL MATCH ()-[r_to {to: $at, branch: $target_branch}]-()
715
715
  SET r_to.to = NULL
716
716
  }
717
717
  // ---------------------------
718
718
  // reset from times on target branch
719
719
  // ---------------------------
720
- CALL {
720
+ CALL () {
721
721
  OPTIONAL MATCH ()-[r_from {from: $at, branch: $target_branch}]-()
722
722
  DELETE r_from
723
723
  }
@@ -1 +1 @@
1
- GRAPH_VERSION = 30
1
+ GRAPH_VERSION = 31
@@ -32,6 +32,7 @@ from .m027_delete_isolated_nodes import Migration027
32
32
  from .m028_delete_diffs import Migration028
33
33
  from .m029_duplicates_cleanup import Migration029
34
34
  from .m030_illegal_edges import Migration030
35
+ from .m031_check_number_attributes import Migration031
35
36
 
36
37
  if TYPE_CHECKING:
37
38
  from infrahub.core.root import Root
@@ -69,6 +70,7 @@ MIGRATIONS: list[type[GraphMigration | InternalSchemaMigration | ArbitraryMigrat
69
70
  Migration028,
70
71
  Migration029,
71
72
  Migration030,
73
+ Migration031,
72
74
  ]
73
75
 
74
76
 
@@ -61,7 +61,7 @@ class Migration012RenameTypeAttributeData(AttributeRenameQuery):
61
61
  def render_match(self) -> str:
62
62
  query = """
63
63
  // Find all the active nodes
64
- CALL {
64
+ CALL () {
65
65
  MATCH (node:%(node_kind)s)
66
66
  WHERE exists((node)-[:HAS_ATTRIBUTE]-(:Attribute { name: $prev_attr.name }))
67
67
  AND NOT exists((node)-[:HAS_ATTRIBUTE]-(:Attribute { name: $new_attr.name }))
@@ -52,7 +52,7 @@ class DedupCardinalityOneRelsQuery(Query):
52
52
  # of a one-to-many BIDIR relationship.
53
53
  query = """
54
54
 
55
- CALL {
55
+ CALL () {
56
56
  MATCH (rel_node: Relationship)-[edge:IS_RELATED]->(n: Node)<-[edge_2:IS_RELATED]-(rel_node_2: Relationship {name: rel_node.name})
57
57
  WHERE rel_node.name in $rel_one_identifiers_inbound[n.kind]
58
58
  AND edge.branch = edge_2.branch
@@ -64,7 +64,7 @@ class DedupCardinalityOneRelsQuery(Query):
64
64
  DETACH DELETE rel_node
65
65
  }
66
66
 
67
- CALL {
67
+ CALL () {
68
68
  MATCH (rel_node_3: Relationship)<-[edge_3:IS_RELATED]-(n: Node)-[edge_4:IS_RELATED]->(rel_node_4: Relationship {name: rel_node_3.name})
69
69
  WHERE rel_node_3.name in $rel_one_identifiers_outbound[n.kind]
70
70
  AND edge_3.branch = edge_4.branch
@@ -535,12 +535,12 @@ class PerformHardDeletes(Query):
535
535
 
536
536
  async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
537
537
  query = """
538
- CALL {
538
+ CALL () {
539
539
  MATCH (n)
540
540
  WHERE n.to_delete = TRUE
541
541
  DETACH DELETE n
542
542
  }
543
- CALL {
543
+ CALL () {
544
544
  MATCH ()-[e]-()
545
545
  WHERE e.to_delete = TRUE
546
546
  DELETE e
@@ -0,0 +1,102 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Sequence
4
+
5
+ from infrahub import config
6
+ from infrahub.core import registry
7
+ from infrahub.core.branch import Branch
8
+ from infrahub.core.constants import SchemaPathType
9
+ from infrahub.core.initialization import initialization
10
+ from infrahub.core.migrations.shared import InternalSchemaMigration, MigrationResult, SchemaMigration
11
+ from infrahub.core.path import SchemaPath
12
+ from infrahub.core.schema import GenericSchema, NodeSchema
13
+ from infrahub.core.schema.attribute_parameters import NumberAttributeParameters
14
+ from infrahub.core.validators.attribute.min_max import AttributeNumberChecker
15
+ from infrahub.core.validators.enum import ConstraintIdentifier
16
+ from infrahub.core.validators.model import SchemaConstraintValidatorRequest
17
+ from infrahub.lock import initialize_lock
18
+ from infrahub.log import get_logger
19
+ from infrahub.types import Number
20
+
21
+ if TYPE_CHECKING:
22
+ from infrahub.database import InfrahubDatabase
23
+
24
+ log = get_logger()
25
+
26
+
27
+ class Migration031(InternalSchemaMigration):
28
+ """
29
+ Some nodes with invalid number attributes may have been created as min/max/excluded_values were not working properly.
30
+ This migration indicates corrupted nodes. If strict mode is disabled, both this migration and min/max/excludes_values constraints are disabled,
31
+ so that users can carry one with their corrupted data without any failure.
32
+ """
33
+
34
+ name: str = "031_check_number_attributes"
35
+ minimum_version: int = 30
36
+ migrations: Sequence[SchemaMigration] = []
37
+
38
+ async def execute(self, db: InfrahubDatabase) -> MigrationResult:
39
+ """Retrieve all number attributes that have a min/max/excluded_values
40
+ For any of these attributes, check if corresponding existing nodes are valid."""
41
+
42
+ if not config.SETTINGS.main.schema_strict_mode:
43
+ return MigrationResult()
44
+
45
+ # load schemas from database into registry
46
+ initialize_lock()
47
+ await initialization(db=db)
48
+
49
+ node_id_to_error_message = {}
50
+
51
+ branches = await Branch.get_list(db=db)
52
+ for branch in branches: # noqa
53
+ schema_branch = await registry.schema.load_schema_from_db(db=db, branch=branch)
54
+ for node_schema_kind in schema_branch.node_names:
55
+ schema = schema_branch.get_node(name=node_schema_kind, duplicate=False)
56
+ if not isinstance(schema, (NodeSchema, GenericSchema)):
57
+ continue
58
+
59
+ for attr in schema.attributes:
60
+ if attr.kind != Number.label:
61
+ continue
62
+
63
+ # Check if the attribute has a min/max/excluded_values being violated
64
+ if isinstance(attr.parameters, NumberAttributeParameters) and (
65
+ attr.parameters.min_value is not None
66
+ or attr.parameters.max_value is not None
67
+ or attr.parameters.excluded_values
68
+ ):
69
+ request = SchemaConstraintValidatorRequest(
70
+ branch=branch,
71
+ constraint_name=ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_VALUE_UPDATE.value,
72
+ node_schema=schema,
73
+ schema_path=SchemaPath(
74
+ path_type=SchemaPathType.ATTRIBUTE, schema_kind=schema.kind, field_name=attr.name
75
+ ),
76
+ schema_branch=db.schema.get_schema_branch(name=registry.default_branch),
77
+ )
78
+
79
+ constraint_checker = AttributeNumberChecker(db=db, branch=branch)
80
+ grouped_data_paths = await constraint_checker.check(request)
81
+ data_paths = grouped_data_paths[0].get_all_data_paths()
82
+ for data_path in data_paths:
83
+ # Avoid having duplicated error messages for nodes present on multiple branches.
84
+ if data_path.node_id not in node_id_to_error_message:
85
+ node_id_to_error_message[data_path.node_id] = (
86
+ f"Node {data_path.node_id} on branch {branch.name} "
87
+ f"has an invalid Number attribute {data_path.field_name}: {data_path.value}"
88
+ )
89
+
90
+ if len(node_id_to_error_message) == 0:
91
+ return MigrationResult()
92
+
93
+ error_str = (
94
+ "Following nodes attributes values must be updated to not violate corresponding min_value, "
95
+ "max_value or excluded_values schema constraints"
96
+ )
97
+ errors_messages = list(node_id_to_error_message.values())
98
+ return MigrationResult(errors=[error_str] + errors_messages)
99
+
100
+ async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
101
+ result = MigrationResult()
102
+ return result
@@ -38,7 +38,7 @@ class AttributeRenameQuery(Query):
38
38
  def render_match(self) -> str:
39
39
  query = """
40
40
  // Find all the active nodes
41
- CALL {
41
+ CALL () {
42
42
  MATCH (node:%(node_kind)s)
43
43
  WHERE exists((node)-[:HAS_ATTRIBUTE]-(:Attribute { name: $prev_attr.name }))
44
44
  RETURN node
@@ -70,7 +70,9 @@ log = get_logger()
70
70
 
71
71
  class Node(BaseNode, metaclass=BaseNodeMeta):
72
72
  @classmethod
73
- def __init_subclass_with_meta__(cls, _meta=None, default_filter=None, **options) -> None:
73
+ def __init_subclass_with_meta__(
74
+ cls, _meta: BaseNodeOptions | None = None, default_filter: None = None, **options: dict[str, Any]
75
+ ) -> None:
74
76
  if not _meta:
75
77
  _meta = BaseNodeOptions(cls)
76
78
 
@@ -959,6 +961,8 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
959
961
  if relationship.kind == RelationshipKind.PARENT:
960
962
  return relationship.name
961
963
 
964
+ return None
965
+
962
966
  async def get_object_template(self, db: InfrahubDatabase) -> CoreObjectTemplate | None:
963
967
  object_template: RelationshipManager = getattr(self, OBJECT_TEMPLATE_RELATIONSHIP_NAME, None)
964
968
  return (
@@ -21,7 +21,7 @@ class DeleteAfterTimeQuery(Query):
21
21
  // ---------------------
22
22
  // Reset edges with to time after timestamp
23
23
  // ---------------------
24
- CALL {
24
+ CALL () {
25
25
  OPTIONAL MATCH (p)-[r]-(q)
26
26
  WHERE r.to > $timestamp
27
27
  SET r.to = NULL
@@ -33,7 +33,7 @@ class DeleteAfterTimeQuery(Query):
33
33
  // ---------------------
34
34
  // Delete edges with from time after timestamp timestamp
35
35
  // ---------------------
36
- CALL {
36
+ CALL () {
37
37
  OPTIONAL MATCH (p)-[r]->(q)
38
38
  WHERE r.from > $timestamp
39
39
  DELETE r
@@ -49,7 +49,7 @@ class DeleteAfterTimeQuery(Query):
49
49
  // ---------------------
50
50
  // Delete edges with from time after timestamp timestamp
51
51
  // ---------------------
52
- CALL {
52
+ CALL () {
53
53
  OPTIONAL MATCH (p)-[r]->(q)
54
54
  WHERE r.from > $timestamp
55
55
  DELETE r
@@ -8,6 +8,7 @@ from pydantic import ConfigDict, Field, model_validator
8
8
  from infrahub import config
9
9
  from infrahub.core.constants.schema import UpdateSupport
10
10
  from infrahub.core.models import HashableModel
11
+ from infrahub.exceptions import ValidationError
11
12
 
12
13
 
13
14
  def get_attribute_parameters_class_for_kind(kind: str) -> type[AttributeParameters]:
@@ -124,16 +125,22 @@ class NumberAttributeParameters(AttributeParameters):
124
125
  return ranges
125
126
 
126
127
  def is_valid_value(self, value: int) -> bool:
127
- if self.min_value is not None and value < self.min_value:
128
+ try:
129
+ self.check_valid_value(value=value, name="UNUSED")
130
+ except ValidationError:
128
131
  return False
132
+ return True
133
+
134
+ def check_valid_value(self, value: int, name: str) -> None:
135
+ if self.min_value is not None and value < self.min_value:
136
+ raise ValidationError({name: f"{value} is lower than the minimum allowed value {self.min_value!r}"})
129
137
  if self.max_value is not None and value > self.max_value:
130
- return False
138
+ raise ValidationError({name: f"{value} is higher than the maximum allowed value {self.max_value!r}"})
131
139
  if value in self.get_excluded_single_values():
132
- return False
140
+ raise ValidationError({name: f"{value} is in the excluded values"})
133
141
  for start, end in self.get_excluded_ranges():
134
142
  if start <= value <= end:
135
- return False
136
- return True
143
+ raise ValidationError({name: f"{value} is in an the excluded range {start}-{end}"})
137
144
 
138
145
 
139
146
  class NumberPoolParameters(AttributeParameters):
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import hashlib
4
4
  import keyword
5
5
  import os
6
+ from collections import defaultdict
6
7
  from dataclasses import asdict, dataclass
7
8
  from enum import Enum
8
9
  from typing import TYPE_CHECKING, Any, Callable, Iterable, Literal, overload
@@ -10,7 +11,7 @@ from typing import TYPE_CHECKING, Any, Callable, Iterable, Literal, overload
10
11
  from infrahub_sdk.utils import compare_lists, intersection
11
12
  from pydantic import field_validator
12
13
 
13
- from infrahub.core.constants import RelationshipCardinality, RelationshipKind
14
+ from infrahub.core.constants import HashableModelState, RelationshipCardinality, RelationshipKind
14
15
  from infrahub.core.models import HashableModel, HashableModelDiff
15
16
 
16
17
  from .attribute_schema import AttributeSchema, get_attribute_schema_class_for_kind
@@ -514,7 +515,86 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
514
515
  return UniquenessConstraintType.SUBSET_OF_HFID
515
516
  return UniquenessConstraintType.STANDARD
516
517
 
518
+ def _update_schema_paths(
519
+ self, schema_paths_list: list[str], field_name_update_map: dict[str, str], deleted_field_names: set[str]
520
+ ) -> list[str]:
521
+ """
522
+ For each schema_path (eg name__value, device__name_value), update the field name if the current name is
523
+ in field_name_update_map, remove the path if the field name is in deleted_field_names
524
+ """
525
+ updated_element_list = []
526
+ for schema_path in schema_paths_list:
527
+ split_path = schema_path.split("__", maxsplit=1)
528
+ current_field_name = split_path[0]
529
+ if current_field_name in deleted_field_names:
530
+ continue
531
+ new_field_name = field_name_update_map.get(current_field_name)
532
+ if not new_field_name:
533
+ updated_element_list.append(schema_path)
534
+ continue
535
+ rest_of_path = f"__{split_path[1]}" if len(split_path) > 1 else ""
536
+ new_element_str = f"{new_field_name}{rest_of_path}"
537
+ updated_element_list.append(new_element_str)
538
+ return updated_element_list
539
+
540
+ def handle_field_renames_and_deletes(self, other: BaseNodeSchema) -> None:
541
+ properties_to_update = [self.uniqueness_constraints, self.human_friendly_id, self.display_labels, self.order_by]
542
+ if not any(p for p in properties_to_update):
543
+ return
544
+
545
+ deleted_names: set[str] = set()
546
+ field_names_by_id = defaultdict(list)
547
+ for field in self.attributes + self.relationships:
548
+ if not field.id:
549
+ continue
550
+ field_names_by_id[field.id].append(field.name)
551
+ for field in other.attributes + other.relationships:
552
+ # identify fields deleted in the other schema
553
+ if field.state is HashableModelState.ABSENT:
554
+ deleted_names.add(field.name)
555
+ if not field.id:
556
+ continue
557
+ if field.name not in field_names_by_id[field.id]:
558
+ field_names_by_id[field.id].append(field.name)
559
+ # identify fields renamed from this schema to the other schema
560
+ renamed_field_name_map = {v[0]: v[-1] for v in field_names_by_id.values() if len(v) > 1}
561
+
562
+ if self.uniqueness_constraints:
563
+ updated_constraints = []
564
+ for constraint in self.uniqueness_constraints:
565
+ updated_constraint = self._update_schema_paths(
566
+ schema_paths_list=constraint,
567
+ field_name_update_map=renamed_field_name_map,
568
+ deleted_field_names=deleted_names,
569
+ )
570
+ if updated_constraint:
571
+ updated_constraints.append(updated_constraint)
572
+ self.uniqueness_constraints = updated_constraints
573
+ if self.human_friendly_id:
574
+ self.human_friendly_id = self._update_schema_paths(
575
+ schema_paths_list=self.human_friendly_id,
576
+ field_name_update_map=renamed_field_name_map,
577
+ deleted_field_names=deleted_names,
578
+ )
579
+ if self.display_labels:
580
+ self.display_labels = self._update_schema_paths(
581
+ schema_paths_list=self.display_labels,
582
+ field_name_update_map=renamed_field_name_map,
583
+ deleted_field_names=deleted_names,
584
+ )
585
+ if self.order_by:
586
+ self.order_by = self._update_schema_paths(
587
+ schema_paths_list=self.order_by,
588
+ field_name_update_map=renamed_field_name_map,
589
+ deleted_field_names=deleted_names,
590
+ )
591
+
517
592
  def update(self, other: HashableModel) -> Self:
593
+ # handle renamed/deleted field updates for schema properties here
594
+ # so that they can still be overridden during the call to `update()` below
595
+ if isinstance(other, BaseNodeSchema):
596
+ self.handle_field_renames_and_deletes(other=other)
597
+
518
598
  super().update(other=other)
519
599
 
520
600
  # Allow to specify empty string to remove existing fields values
@@ -551,6 +631,24 @@ class SchemaAttributePath:
551
631
  attribute_schema: AttributeSchema | None = None
552
632
  attribute_property_name: str | None = None
553
633
 
634
+ def __str__(self) -> str:
635
+ return self.to_string()
636
+
637
+ def to_string(self, field_name_override: str | None = None) -> str:
638
+ str_path = ""
639
+ if self.relationship_schema:
640
+ str_path += field_name_override or self.relationship_schema.name
641
+ if self.attribute_schema:
642
+ if str_path:
643
+ str_path += "__"
644
+ attr_name = self.attribute_schema.name
645
+ else:
646
+ attr_name = field_name_override or self.attribute_schema.name
647
+ str_path += attr_name
648
+ if self.attribute_property_name:
649
+ str_path += f"__{self.attribute_property_name}"
650
+ return str_path
651
+
554
652
  @property
555
653
  def is_type_attribute(self) -> bool:
556
654
  return bool(self.attribute_schema and not self.related_schema and not self.relationship_schema)
@@ -563,6 +661,14 @@ class SchemaAttributePath:
563
661
  def has_property(self) -> bool:
564
662
  return bool(self.attribute_property_name)
565
663
 
664
+ @property
665
+ def field_name(self) -> str | None:
666
+ if self.relationship_schema:
667
+ return self.relationship_schema.name
668
+ if self.attribute_schema:
669
+ return self.attribute_schema.name
670
+ return None
671
+
566
672
  @property
567
673
  def active_relationship_schema(self) -> RelationshipSchema:
568
674
  if self.relationship_schema:
@@ -710,7 +710,9 @@ class SchemaBranch:
710
710
  ):
711
711
  unique_attrs_in_constraints.add(schema_attribute_path.attribute_schema.name)
712
712
 
713
- unique_attrs_in_attrs = {attr_schema.name for attr_schema in node_schema.unique_attributes}
713
+ unique_attrs_in_attrs = {
714
+ attr_schema.name for attr_schema in node_schema.unique_attributes if not attr_schema.inherited
715
+ }
714
716
  if unique_attrs_in_attrs == unique_attrs_in_constraints:
715
717
  continue
716
718
 
@@ -822,11 +824,16 @@ class SchemaBranch:
822
824
  ) from exc
823
825
 
824
826
  def _is_attr_combination_unique(
825
- self, attrs_paths: list[str], uniqueness_constraints: list[list[str]] | None
827
+ self, attrs_paths: list[str], uniqueness_constraints: list[list[str]] | None, unique_attribute_names: list[str]
826
828
  ) -> bool:
827
829
  """
828
- Return whether at least one combination of any length of `attrs_paths` is equal to a uniqueness constraint.
830
+ Return whether at least one combination of any length of `attrs_paths` is unique
829
831
  """
832
+ if unique_attribute_names:
833
+ for attr_path in attrs_paths:
834
+ for unique_attr_name in unique_attribute_names:
835
+ if attr_path.startswith(unique_attr_name):
836
+ return True
830
837
 
831
838
  if not uniqueness_constraints:
832
839
  return False
@@ -868,9 +875,14 @@ class SchemaBranch:
868
875
  if config.SETTINGS.main.schema_strict_mode:
869
876
  # For every relationship referred within hfid, check whether the combination of attributes is unique is the peer schema node
870
877
  for related_schema, attrs_paths in rel_schemas_to_paths.values():
871
- if not self._is_attr_combination_unique(attrs_paths, related_schema.uniqueness_constraints):
878
+ if not self._is_attr_combination_unique(
879
+ attrs_paths=attrs_paths,
880
+ uniqueness_constraints=related_schema.uniqueness_constraints,
881
+ unique_attribute_names=[a.name for a in related_schema.unique_attributes],
882
+ ):
872
883
  raise ValidationError(
873
- f"HFID of {node_schema.kind} refers peer {related_schema.kind} with a non-unique combination of attributes {attrs_paths}"
884
+ f"HFID of {node_schema.kind} refers to peer {related_schema.kind}"
885
+ f" with a non-unique combination of attributes {attrs_paths}"
874
886
  )
875
887
 
876
888
  def validate_required_relationships(self) -> None:
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
+ from infrahub import config
5
6
  from infrahub.core.constants import PathType
6
7
  from infrahub.core.path import DataPath, GroupedDataPaths
7
8
  from infrahub.core.schema.attribute_parameters import NumberAttributeParameters
@@ -87,6 +88,10 @@ class AttributeNumberChecker(ConstraintCheckerInterface):
87
88
  return "attribute.number.update"
88
89
 
89
90
  def supports(self, request: SchemaConstraintValidatorRequest) -> bool:
91
+ # Some invalid values may exist due to https://github.com/opsmill/infrahub/issues/6714.
92
+ if not config.SETTINGS.main.schema_strict_mode:
93
+ return False
94
+
90
95
  return request.constraint_name in (
91
96
  ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_VALUE_UPDATE.value,
92
97
  ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_VALUE_UPDATE.value,
@@ -94,7 +99,6 @@ class AttributeNumberChecker(ConstraintCheckerInterface):
94
99
  )
95
100
 
96
101
  async def check(self, request: SchemaConstraintValidatorRequest) -> list[GroupedDataPaths]:
97
- grouped_data_paths_list: list[GroupedDataPaths] = []
98
102
  if not request.schema_path.field_name:
99
103
  raise ValueError("field_name is not defined")
100
104
  attribute_schema = request.node_schema.get_attribute(name=request.schema_path.field_name)
@@ -106,8 +110,9 @@ class AttributeNumberChecker(ConstraintCheckerInterface):
106
110
  and attribute_schema.parameters.max_value is None
107
111
  and attribute_schema.parameters.excluded_values is None
108
112
  ):
109
- return grouped_data_paths_list
113
+ return []
110
114
 
115
+ grouped_data_paths_list: list[GroupedDataPaths] = []
111
116
  for query_class in self.query_classes:
112
117
  # TODO add exception handling
113
118
  query = await query_class.init(
infrahub/graphql/app.py CHANGED
@@ -88,6 +88,8 @@ GQL_STOP = "stop"
88
88
  ContextValue = Any | Callable[[HTTPConnection], Any]
89
89
  RootValue = Any
90
90
 
91
+ subscription_tasks = set()
92
+
91
93
 
92
94
  class InfrahubGraphQLApp:
93
95
  def __init__(
@@ -446,7 +448,9 @@ class InfrahubGraphQLApp:
446
448
 
447
449
  asyncgen = cast(AsyncGenerator[Any, None], result)
448
450
  subscriptions[operation_id] = asyncgen
449
- asyncio.create_task(self._observe_subscription(asyncgen, operation_id, websocket))
451
+ task = asyncio.create_task(self._observe_subscription(asyncgen, operation_id, websocket))
452
+ subscription_tasks.add(task)
453
+ task.add_done_callback(subscription_tasks.discard)
450
454
  return []
451
455
 
452
456
  async def _observe_subscription(
@@ -26,6 +26,8 @@ if TYPE_CHECKING:
26
26
  MessageFunction = Callable[[InfrahubMessage], Awaitable[None]]
27
27
  ResponseClass = TypeVar("ResponseClass")
28
28
 
29
+ publish_tasks = set()
30
+
29
31
 
30
32
  async def _add_request_id(message: InfrahubMessage) -> None:
31
33
  log_data = get_log_data()
@@ -223,7 +225,9 @@ class NATSMessageBus(InfrahubMessageBus):
223
225
  # Delayed retries are directly handled in the callback using Nack
224
226
  return
225
227
  # Use asyncio task for delayed publish since NATS does not support that out of the box
226
- asyncio.create_task(self._publish_with_delay(message, routing_key, delay))
228
+ task = asyncio.create_task(self._publish_with_delay(message, routing_key, delay))
229
+ publish_tasks.add(task)
230
+ task.add_done_callback(publish_tasks.discard)
227
231
  return
228
232
 
229
233
  for enricher in self.message_enrichers:
@@ -16,6 +16,8 @@ if TYPE_CHECKING:
16
16
 
17
17
  log = get_logger()
18
18
 
19
+ background_tasks = set()
20
+
19
21
 
20
22
  @dataclass
21
23
  class Schedule:
@@ -56,7 +58,9 @@ class InfrahubScheduler:
56
58
 
57
59
  async def start_schedule(self) -> None:
58
60
  for schedule in self.schedules:
59
- asyncio.create_task(self.run_schedule(schedule=schedule), name=f"scheduled_task_{schedule.name}")
61
+ task = asyncio.create_task(self.run_schedule(schedule=schedule), name=f"scheduled_task_{schedule.name}")
62
+ background_tasks.add(task)
63
+ task.add_done_callback(background_tasks.discard)
60
64
 
61
65
  async def shutdown(self) -> None:
62
66
  self.running = False
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from .attribute import Attribute
3
4
  from .constants import (
4
5
  ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE,
5
6
  ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE,
@@ -25,6 +26,7 @@ __all__ = [
25
26
  "PROPERTIES_FLAG",
26
27
  "PROPERTIES_OBJECT",
27
28
  "SAFE_VALUE",
29
+ "Attribute",
28
30
  "InfrahubNode",
29
31
  "InfrahubNodeBase",
30
32
  "InfrahubNodeSync",
infrahub_sdk/node/node.py CHANGED
@@ -501,11 +501,21 @@ class InfrahubNode(InfrahubNodeBase):
501
501
 
502
502
  return cls(client=client, schema=schema, branch=branch, data=cls._strip_alias(data))
503
503
 
504
- def _init_relationships(self, data: dict | None = None) -> None:
504
+ def _init_relationships(self, data: dict | RelatedNode | None = None) -> None:
505
505
  for rel_schema in self._schema.relationships:
506
506
  rel_data = data.get(rel_schema.name, None) if isinstance(data, dict) else None
507
507
 
508
508
  if rel_schema.cardinality == "one":
509
+ if isinstance(rel_data, RelatedNode):
510
+ peer_id_data: dict[str, Any] = {}
511
+ if rel_data.id:
512
+ peer_id_data["id"] = rel_data.id
513
+ if rel_data.hfid:
514
+ peer_id_data["hfid"] = rel_data.hfid
515
+ if peer_id_data:
516
+ rel_data = peer_id_data
517
+ else:
518
+ rel_data = None
509
519
  self._relationship_cardinality_one_data[rel_schema.name] = RelatedNode(
510
520
  name=rel_schema.name, branch=self._branch, client=self._client, schema=rel_schema, data=rel_data
511
521
  )
@@ -1079,10 +1089,19 @@ class InfrahubNodeSync(InfrahubNodeBase):
1079
1089
  rel_data = data.get(rel_schema.name, None) if isinstance(data, dict) else None
1080
1090
 
1081
1091
  if rel_schema.cardinality == "one":
1092
+ if isinstance(rel_data, RelatedNodeSync):
1093
+ peer_id_data: dict[str, Any] = {}
1094
+ if rel_data.id:
1095
+ peer_id_data["id"] = rel_data.id
1096
+ if rel_data.hfid:
1097
+ peer_id_data["hfid"] = rel_data.hfid
1098
+ if peer_id_data:
1099
+ rel_data = peer_id_data
1100
+ else:
1101
+ rel_data = None
1082
1102
  self._relationship_cardinality_one_data[rel_schema.name] = RelatedNodeSync(
1083
1103
  name=rel_schema.name, branch=self._branch, client=self._client, schema=rel_schema, data=rel_data
1084
1104
  )
1085
-
1086
1105
  else:
1087
1106
  self._relationship_cardinality_many_data[rel_schema.name] = RelationshipManagerSync(
1088
1107
  name=rel_schema.name,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: infrahub-server
3
- Version: 1.3.0b6
3
+ Version: 1.3.1
4
4
  Summary: Infrahub is taking a new approach to Infrastructure Management by providing a new generation of datastore to organize and control all the data that defines how an infrastructure should run.
5
5
  License: AGPL-3.0-only
6
6
  Author: OpsMill
@@ -38,13 +38,13 @@ infrahub/branch/triggers.py,sha256=4sywoEX79fY2NkaGe6tTHnmytf4k6gXDm2FJHkkRJOw,7
38
38
  infrahub/cli/__init__.py,sha256=zQjE9zMrwAmk_4qb5mbUgNi06g3HKvrPwQvJLQmv9JY,1814
39
39
  infrahub/cli/constants.py,sha256=CoCeTMnfsA3j7ArdLKLZK4VPxOM7ls17qpxGJmND0m8,129
40
40
  infrahub/cli/context.py,sha256=20CJj_D1VhigR9uhTDPHiVHnV7vzsgK8v-uLKs06kzA,398
41
- infrahub/cli/db.py,sha256=mQ0BYcYwhzzp2YLC0CiBLOSVGz_egLgZTi1BuoTiurE,28395
41
+ infrahub/cli/db.py,sha256=0kBWyzHo6EFnINivClMj0jTBPC_9-RDVeuxrQrRYRws,28472
42
42
  infrahub/cli/events.py,sha256=nJmowQgTxRs6qaT41A71Ei9jm6qtYaL2amAT5TA1H_k,1726
43
43
  infrahub/cli/git_agent.py,sha256=ajT9-kdd3xLIysOPe8GqZyCDMkpNyhqfWjBg9HPWVcg,5240
44
44
  infrahub/cli/patch.py,sha256=ztOkWyo0l_Wo0WX10bvSqGZibKzowrwx82oi69cjwkY,6018
45
45
  infrahub/cli/server.py,sha256=zeKgJE9V0usSMVBwye0sRNNh6Ctj-nSZHqHbNskqyz4,2248
46
46
  infrahub/cli/tasks.py,sha256=uVtMuUbcXwb6H3hnunUl9JJh99XShpWn2pwryVrR7hg,1952
47
- infrahub/cli/upgrade.py,sha256=GQWzga8AFTvfS_VY6s1Nmf_J1UJb533IUVQiF_FC9r0,5031
47
+ infrahub/cli/upgrade.py,sha256=NMYIXv31ZsdBQvq7bpQEYYtXbjGCI4Ak3_92GceEIrM,5203
48
48
  infrahub/components.py,sha256=lSLDCDwIZoakZ2iBrfHi9c3BxzugMiuiZO6V7Egt6tk,107
49
49
  infrahub/computed_attribute/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
50
  infrahub/computed_attribute/constants.py,sha256=oTMPEfRuf2mcfCkBpRLWRALO6nsLHpFm9jJGu0lowS4,446
@@ -58,7 +58,7 @@ infrahub/constants/database.py,sha256=WmV1iuOk4xulxZHOVvO3sS_VF1eTf7fKh0TPe_RnfV
58
58
  infrahub/context.py,sha256=8SZRKSECkkcsNNzDaKEUJ7Nyr0EzUfToAy969LXjQVk,1554
59
59
  infrahub/core/__init__.py,sha256=z6EJBZyCYCBqinoBtX9li6BTBbbGV8WCkE_4CrEsmDA,104
60
60
  infrahub/core/account.py,sha256=s8ZC7J8rtEvQZQjbVuiKMlPhl6aQjtAjbZhBejLC0X8,26182
61
- infrahub/core/attribute.py,sha256=6JSnPb0eC6sHiBzoXy9xcrnWRzOR0cLm_t-jf5ksQLM,43908
61
+ infrahub/core/attribute.py,sha256=stmJ_dOr7rFTXzH80keuE64f6y3K3393GiSYeOaay3s,44257
62
62
  infrahub/core/branch/__init__.py,sha256=h0oIj0gHp1xI-N1cYW8_N6VZ81CBOmLuiUt5cS5nKuk,49
63
63
  infrahub/core/branch/models.py,sha256=8e0BXwbFV4zHMSpqAp1Zp4L8YlxBs0Mxpl9gMoFGeJ4,19539
64
64
  infrahub/core/branch/tasks.py,sha256=_5tuv068xczwGYB2MZffPIdchhhgm1ciqOawdIO2pAo,20904
@@ -85,7 +85,7 @@ infrahub/core/diff/combiner.py,sha256=qL4WQsphB2sVnncgskSG_QcJBqBHjaK0vWU_apeTn-
85
85
  infrahub/core/diff/conflict_transferer.py,sha256=LZCuS9Dbr4yBf-bd3RF-9cPnaOvVWiU3KBmmwxbRZl0,3968
86
86
  infrahub/core/diff/conflicts_enricher.py,sha256=x6qiZOXO2A3BQ2Fm78apJ4WA7HLzPO84JomJfcyuyDg,12552
87
87
  infrahub/core/diff/conflicts_extractor.py,sha256=HysGoyNy9qMxfQ0Lh4AVZsRpHUBpezQNUa8cteVLb2k,9715
88
- infrahub/core/diff/coordinator.py,sha256=HDbz6dTIALTR6NzmGw1DnEb6o_VYDYbpZzP8TUqN7l4,27134
88
+ infrahub/core/diff/coordinator.py,sha256=wlQSnjuBQ-N6b3wH7t2rBsVnFJb0ACgvKCApBCpIlvw,27405
89
89
  infrahub/core/diff/data_check_synchronizer.py,sha256=HcbYEIe-70MBiSR6P0AmAwgY9aFxYCJktxOiRcJaxj8,9241
90
90
  infrahub/core/diff/enricher/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
91
  infrahub/core/diff/enricher/aggregated.py,sha256=-LnAeNKDo6mifjL3d3ylCg1A9dTZJBySngWPeF7DfrY,793
@@ -115,12 +115,12 @@ infrahub/core/diff/query/delete_query.py,sha256=KMZ-HbSz2RKYqqMZ1Lj0mZBlWvkBYOyO
115
115
  infrahub/core/diff/query/diff_get.py,sha256=SzlJAF5DNKcbavgVOxLKJ-o8EsuImTGE2uNPg9hcMq0,7438
116
116
  infrahub/core/diff/query/diff_summary.py,sha256=sypXfK4EO_BZBuohlv419AjgL5ZeRwMiwnI7IIlh0KE,3841
117
117
  infrahub/core/diff/query/drop_nodes.py,sha256=NP29dbW-z4s_rp_MtIwTl3FXElfCP6eqEpF_9r3Z3VA,1674
118
- infrahub/core/diff/query/field_specifiers.py,sha256=XXtP2kwBxKnN7ALJN5DYuMyUl17ARnz4NJgeDzldm8w,1699
118
+ infrahub/core/diff/query/field_specifiers.py,sha256=y_kPpPnOYQvUvdl6xc2sqQzVXODP4NQWIC55ya0x3ps,1702
119
119
  infrahub/core/diff/query/field_summary.py,sha256=-1p6xeyn0w6fQPrpOI6a9Id95wqtdxKUREulTH_yIec,3150
120
120
  infrahub/core/diff/query/filters.py,sha256=McTtRNGg8fmnqTtNH-msfzH-8eKCBsM6-fitxTp5T8w,4324
121
121
  infrahub/core/diff/query/get_conflict_query.py,sha256=kpGZA4QZrXxv_vnoAP5oa9-347VzsNWUIBWcg7rg03U,892
122
122
  infrahub/core/diff/query/has_conflicts_query.py,sha256=kt0Z606vP2r1g7OqW2RrYj9LbiVkrzGfQ0AKCHx21XI,2547
123
- infrahub/core/diff/query/merge.py,sha256=duFTAd81BcoztSEIhK4KP6vpbWvrefSV_Xb97vrQlaM,32149
123
+ infrahub/core/diff/query/merge.py,sha256=UJ816ALrxsg0mjc1RWUygzL5Wy-ObKar0rOpIljlmTM,32155
124
124
  infrahub/core/diff/query/merge_tracking_id.py,sha256=VLGsKuOCIMYe0I-0r01YHF5iaLYIkfSCVQatHM-ybFA,833
125
125
  infrahub/core/diff/query/roots_metadata.py,sha256=FT-48amqoR2RS4CkfnnXGI7Z5uOL4hm7IdZiz3SFHRo,2182
126
126
  infrahub/core/diff/query/save.py,sha256=xBKWpWfRWfaP7g523xKMK82ogg0AfVQTTMeyz8oe-o0,22956
@@ -133,7 +133,7 @@ infrahub/core/diff/repository/deserializer.py,sha256=bhN9ao8HxqKyRz273QGLNV9z9_S
133
133
  infrahub/core/diff/repository/repository.py,sha256=x3QP9VmBVYBOVtf3IZUyzXqCd8sSfmHTqVoYlAOdGao,26006
134
134
  infrahub/core/diff/tasks.py,sha256=7_k-ZNcJZsiDp-xCZvCQfPJjg0xRxpaGTiVVNuRPfBI,3322
135
135
  infrahub/core/enums.py,sha256=qGbhRVoH43Xi0iDkUfWdQiKapJbLT9UKsCobFk_paIk,491
136
- infrahub/core/graph/__init__.py,sha256=2EUPjmgYV1tf8ln4lfXK57uUCRFRuanN9SNHZmXdy24,19
136
+ infrahub/core/graph/__init__.py,sha256=BGr-0-sGLkh6KZu0KVKYgP5KJXbAfOfNoaKpB2168Cg,19
137
137
  infrahub/core/graph/constraints.py,sha256=lmuzrKDFoeSKRiLtycB9PXi6zhMYghczKrPYvfWyy90,10396
138
138
  infrahub/core/graph/index.py,sha256=IHLP-zPRp7HJYLGHMRDRXQp8RC69ztP10Tr5NcL2j4Y,1736
139
139
  infrahub/core/graph/schema.py,sha256=FmEPPb1XOFv3nnS_XJCuUqlp8HsStX5A2frHjlhoqvE,10105
@@ -152,7 +152,7 @@ infrahub/core/ipam/utilization.py,sha256=d-zpXCaWsHgJxBLopCDd7y4sJYvHcIzzpYhbTMI
152
152
  infrahub/core/manager.py,sha256=NaUuSY7Veesa67epQRuQ2TJD0-ooUSnvNRIUZCntV3g,47576
153
153
  infrahub/core/merge.py,sha256=bZvToLKyphJlWMbQAzGuSHcrG2DfeqL69KSfqb1wWdc,10430
154
154
  infrahub/core/migrations/__init__.py,sha256=syPb3-Irf11dXCHgbT0UdmTnEBbpf4wXJ3m8ADYXDpk,1175
155
- infrahub/core/migrations/graph/__init__.py,sha256=e0OzJEAn1qvtPghPwan8kWrLzDXX9eaw7YgbFlAZevA,2872
155
+ infrahub/core/migrations/graph/__init__.py,sha256=jf-xSA-9fSQAQYESBhnPIXCM60gCALgLrzxWPyAo2_o,2945
156
156
  infrahub/core/migrations/graph/m001_add_version_to_graph.py,sha256=YcLN6cFjE6IGheXR4Ujb6CcyY8bJ7WE289hcKJaENOc,1515
157
157
  infrahub/core/migrations/graph/m002_attribute_is_default.py,sha256=wB6f2N_ChTvGajqHD-OWCG5ahRMDhhXZuwo79ieq_II,1036
158
158
  infrahub/core/migrations/graph/m003_relationship_parent_optional.py,sha256=Aya-s98XfE9C7YluOwEjilwgnjaBnZxp27w_Xdv_NmU,2330
@@ -164,7 +164,7 @@ infrahub/core/migrations/graph/m008_add_human_friendly_id.py,sha256=7zswLvod5iTp
164
164
  infrahub/core/migrations/graph/m009_add_generate_profile_attr.py,sha256=7FfjKyVYOebU51SeRtRYkTWKX26SBQx2dfofi7TiQQ8,1346
165
165
  infrahub/core/migrations/graph/m010_add_generate_profile_attr_generic.py,sha256=M4Orq480PzwBEz85QZqdBh-1arJdIwXNwnPA6cWy5Yg,1366
166
166
  infrahub/core/migrations/graph/m011_remove_profile_relationship_schema.py,sha256=TYQ1jXNucLIBbqLS35nUb_72OmMspXexSSW83Ax0oEw,1980
167
- infrahub/core/migrations/graph/m012_convert_account_generic.py,sha256=XvOKS0CSJSOdXQfan7N_Nrak6CB75r9xyT5rErUb61w,10998
167
+ infrahub/core/migrations/graph/m012_convert_account_generic.py,sha256=FZWv-axVbhq86-3l-T8TLnzW7IW2rHLbkgAEb5ol-6A,11001
168
168
  infrahub/core/migrations/graph/m013_convert_git_password_credential.py,sha256=Vq9jH4NK4LNH__2c_2wCRIHZg17-nxhfLB0CiMSdmNk,12734
169
169
  infrahub/core/migrations/graph/m014_remove_index_attr_value.py,sha256=Amds1gl8YtNIekU0tSXpHzdfk8UFqChC2LOLfnQ1YTM,1441
170
170
  infrahub/core/migrations/graph/m015_diff_format_update.py,sha256=fMnUja-VgKbCxtx4Rh3PnA_s3iidaYunJWEkw0AD51w,1169
@@ -175,17 +175,18 @@ infrahub/core/migrations/graph/m019_restore_rels_to_time.py,sha256=jsSrUWHxvvfJf
175
175
  infrahub/core/migrations/graph/m020_duplicate_edges.py,sha256=zzz7CLPynCUvfyXhUPD1RzzXUSOJhga_7vsHSoRDdPE,6749
176
176
  infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py,sha256=sd3its0zG2c8b36wIyhmcFUlwrIX34SyTegJsRQ-Wk4,1623
177
177
  infrahub/core/migrations/graph/m022_add_generate_template_attr.py,sha256=CmSxcXoWdjNXk4UxbUUeR7X49qiyNIIJuvigV9pOqNA,1748
178
- infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py,sha256=tW-su33h0K1zZk6GsOxqZcqpAsTNCmKo7kN88Te7jAA,3930
178
+ infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py,sha256=NwGnJdbjOWCm7bG-E9E5Uw2fRw4KzHZwaJLKBIVhTcw,3936
179
179
  infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py,sha256=_Ex13D1JYCSLcX_eXNgx_etcQXdqbxdgQ-6z7CpJKns,2487
180
180
  infrahub/core/migrations/graph/m025_uniqueness_nulls.py,sha256=n_g09PDLs1yo3dMYL00HH2VtmYkjV1sVnxFL0KL4hOg,863
181
181
  infrahub/core/migrations/graph/m026_0000_prefix_fix.py,sha256=7sP6nQZrqgzFyRUHKf5fKSX2LrzKEAAsiDsRSu9noJM,1944
182
182
  infrahub/core/migrations/graph/m027_delete_isolated_nodes.py,sha256=aAfDUdhsR05CpehVeyLWQ1tRstgrF0HY2V5V6X5ALxM,1589
183
183
  infrahub/core/migrations/graph/m028_delete_diffs.py,sha256=c2FyUkbeuXfmsNxzcG11b0mD7KqCipyGq6SiFsFWaoM,1299
184
- infrahub/core/migrations/graph/m029_duplicates_cleanup.py,sha256=pXG1cmuEkBjDnPuuyugCJ-OsweOcm3GgorKcklwaAyU,28272
184
+ infrahub/core/migrations/graph/m029_duplicates_cleanup.py,sha256=DpOwTMzkdi9-kha-UI6DzzJ_6qWen9kdCl_6j2IimV4,28278
185
185
  infrahub/core/migrations/graph/m030_illegal_edges.py,sha256=Saz7QmUqwuLiBtSBdQf54E1Bj3hz0k9KAOQ-pwPBH4g,2797
186
+ infrahub/core/migrations/graph/m031_check_number_attributes.py,sha256=s3sVoKIkrZAMVZtWWH8baJW42UCAePp5nMUKy5FDSiM,4944
186
187
  infrahub/core/migrations/query/__init__.py,sha256=JoWOUWlV6IzwxWxObsfCnAAKUOHJkE7dZlOsfB64ZEo,876
187
188
  infrahub/core/migrations/query/attribute_add.py,sha256=LlhkIfVOR3TFSUJEV_4kU5JBKXsWwTsRiX1ySUPe4TU,3655
188
- infrahub/core/migrations/query/attribute_rename.py,sha256=crY7divyLxUAnPsPK7xy_09eEPzr2Bc4FxyEB43Mnrc,6889
189
+ infrahub/core/migrations/query/attribute_rename.py,sha256=onb9Nanht1Tz47JgneAcFsuhqqvPS6dvI2nNjRupLLo,6892
189
190
  infrahub/core/migrations/query/delete_element_in_schema.py,sha256=QYw2LIpJGQXBPOTm6w9gFdCltZRd-V_YUh5l9HmGthg,7402
190
191
  infrahub/core/migrations/query/node_duplicate.py,sha256=CXkGoa6Dqg4njdg8GSNQUwoDDnHgiOn2O45zU1vN9lE,8527
191
192
  infrahub/core/migrations/query/relationship_duplicate.py,sha256=hjUFjNqhaFc2tEg79BXR2xDr_4Wdmu9fVF02cTEICTk,7319
@@ -201,7 +202,7 @@ infrahub/core/migrations/schema/placeholder_dummy.py,sha256=3T3dBwC_ZyehOJr2KRKF
201
202
  infrahub/core/migrations/schema/tasks.py,sha256=x6c_5N0pcQ_lTH5Vaqg2_MwlQ08I35BdX-8NhRDozBE,4165
202
203
  infrahub/core/migrations/shared.py,sha256=e7HEBijWhG46UN8ODjSmxvGeK8KAQ3Twnj2q1dvb2m0,6983
203
204
  infrahub/core/models.py,sha256=aqsqO2cP0MndeX6KZk4NEBmeIy6dE7Ob9UqsmjTIAtA,26149
204
- infrahub/core/node/__init__.py,sha256=2HJXakQ4FVwLmgpm6GBN_3rKzFrD7aGl2qQNydbGa9Q,41653
205
+ infrahub/core/node/__init__.py,sha256=4of1tA26-EV8FOubg-rhBFU9wkJ0BBipePnnvx1vi_8,41738
205
206
  infrahub/core/node/base.py,sha256=BAowVRCK_WC50yXym1kCyUppJDJnrODGU5uoj1s0Yd4,2564
206
207
  infrahub/core/node/constraints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
207
208
  infrahub/core/node/constraints/attribute_uniqueness.py,sha256=9MThTmuqZ7RgK71ZZARlw1k1x3ARn1U67g2_Gatd6rE,2099
@@ -223,7 +224,7 @@ infrahub/core/protocols_base.py,sha256=cEi6giHtEUmaD0JWfDfWHJhEv_6wjaBA3oJRJCbvc
223
224
  infrahub/core/query/__init__.py,sha256=2qIMaODLwJ6pK6BUd5vODTlA15Aecf5I8_-J44UlCso,23089
224
225
  infrahub/core/query/attribute.py,sha256=DzwbElgTaZs6-nBYGmnDpBr9n0lmUPK3p7eyI30Snh8,11783
225
226
  infrahub/core/query/branch.py,sha256=Fqycgk8kzhmc1H_-gfiw3c-ZjNjAHw64XU7OQUkhDi0,4976
226
- infrahub/core/query/delete.py,sha256=_PL97nz-ybF0JqDSYlTPhIa4oCxwPiFerwd8Wjw-x-8,1918
227
+ infrahub/core/query/delete.py,sha256=7tPP1qtNV6QGYtmgE1RKsuQ9oxENnMTVkttLvJ2PiKg,1927
227
228
  infrahub/core/query/diff.py,sha256=DOtPHIu45Yp8wvj8wp16me9E3AK7wVcVfzS2_LIZn2k,35952
228
229
  infrahub/core/query/ipam.py,sha256=0glfVQmcKqMvNyK4GU_zRl2O9pjl7JBeavyE8VC-De4,28234
229
230
  infrahub/core/query/node.py,sha256=yEDRmEsVUttO7CXNCXnRK0p98wmUHqwybavJS4gfOAo,65140
@@ -246,9 +247,9 @@ infrahub/core/relationship/constraints/profiles_kind.py,sha256=nEZPGtGcmelZ1Nb8E
246
247
  infrahub/core/relationship/model.py,sha256=CiSJn6uGBuDrdP4K1Kl0w_A6Lq_FLoNZH4ksssZnXMM,47004
247
248
  infrahub/core/root.py,sha256=8ZLSOtnmjQcrjqX2vxNO-AGopEUArmBPo_X5NeZBdP0,416
248
249
  infrahub/core/schema/__init__.py,sha256=Q8kzfcX7zhpHThTBoQMMjcXG95DdHcfOWT4poS0QJEY,4035
249
- infrahub/core/schema/attribute_parameters.py,sha256=S0vOpkq2rd6W2py8LIZ53MusUZDLELeBBRmCRU9ZmMw,5787
250
+ infrahub/core/schema/attribute_parameters.py,sha256=-c8Gh8mHaQk6rZXvBz7VcicRQ8l1Ijmk3BxoxPFAQrc,6336
250
251
  infrahub/core/schema/attribute_schema.py,sha256=Da7h3254LHqW4MuIEY6a_kbzhzhyVlCXEKU7f7DtN4Q,10228
251
- infrahub/core/schema/basenode_schema.py,sha256=q-jytPJUaaOq9l6mGXOnAiE3HPIRQe7hSJijH_6mkNk,23376
252
+ infrahub/core/schema/basenode_schema.py,sha256=9iCPN6IsDnNQ3qO5aQu_w8lgbZX-XgmQgaMMZcU0uIk,28182
252
253
  infrahub/core/schema/computed_attribute.py,sha256=9rznZJpGqX8fxLx0EguPmww8LoHsadMtQQUKaMoJPcI,1809
253
254
  infrahub/core/schema/constants.py,sha256=KtFrvwNckyKZSGIMD4XfxI5eFTZqBRiw54R7BE5h39Q,374
254
255
  infrahub/core/schema/definitions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -288,7 +289,7 @@ infrahub/core/schema/manager.py,sha256=UEOPCq6iz4H1xcz_Copztk_03PamggL-pjtANruY3
288
289
  infrahub/core/schema/node_schema.py,sha256=ld_Wrqf-RsoEUVz_lKE0tcSf5n_oYZYtRI0lTqtd63o,6150
289
290
  infrahub/core/schema/profile_schema.py,sha256=cOPSOt5KLgQ0nbqrAN_o33hY_pUtrKmiwSbY_YpVolI,1092
290
291
  infrahub/core/schema/relationship_schema.py,sha256=lVbyQKMP2jPZZwZGK6DBvXdXfEQEsQGMbZ2WYxOZKTw,8261
291
- infrahub/core/schema/schema_branch.py,sha256=TnEP58sNC451_Wh6yob6y0RQP6pjT00t_XVfA1mW6Nw,103141
292
+ infrahub/core/schema/schema_branch.py,sha256=1NS6YuGXcxedUn8O8gnASo7P5bPs1Njs8FKIbbn3Qn4,103688
292
293
  infrahub/core/schema/schema_branch_computed.py,sha256=14UUsQJDLMHkYhg7QMqeLiTF3PO8c8rGa90ul3F2ZZo,10629
293
294
  infrahub/core/schema/template_schema.py,sha256=O-PBS9IRM4JX6PxeoyZKwqZ0u0SdQ2zxWMc01PJ2_EA,1084
294
295
  infrahub/core/task/__init__.py,sha256=Ied1NvKGJUDmff27z_-yWW8ArenHxGvSvQTaQyx1iHs,128
@@ -304,7 +305,7 @@ infrahub/core/validators/attribute/choices.py,sha256=a5rMF80FARUvOHjyL9VfpKoQ5oN
304
305
  infrahub/core/validators/attribute/enum.py,sha256=3PzkYUuzbt8NqRH4IP4cMjoDxzUvJzbNYC5ZpW5zKZQ,4161
305
306
  infrahub/core/validators/attribute/kind.py,sha256=knOToYe5NrAsEifnUC-ci05CMkQC3GB3Yeqf2Vi_ss4,4394
306
307
  infrahub/core/validators/attribute/length.py,sha256=H0nP2x2ynzcbMnc6neIje01wXipbt8Wr49iNTqIvWxI,4526
307
- infrahub/core/validators/attribute/min_max.py,sha256=dMBKcS8m-9o0tI2HoDI7TNTppOHlRNO_VKtuRTT_0C8,5476
308
+ infrahub/core/validators/attribute/min_max.py,sha256=3x6iCJuVdt3vim6wPaF4Bar8RlR3FhJu3DYQiR2GZRI,5661
308
309
  infrahub/core/validators/attribute/number_pool.py,sha256=edWmpHbme9YqWxeZJ5V0dvTCyIqLFyej2YNTyM-Emq4,4709
309
310
  infrahub/core/validators/attribute/optional.py,sha256=qczSkKll4eKsutLgiVi_lCHgqa8ASmQbWOa00dXyxwg,3801
310
311
  infrahub/core/validators/attribute/regex.py,sha256=DENGbf3H5aS4dZZTeBQc39bJL8Ih70yITqXfpAR6etU,4201
@@ -435,7 +436,7 @@ infrahub/graphql/analyzer.py,sha256=TAWo4AWMr33MFjK3YcYBxXSjdwRHxU2HzpIuY9tTHqU,
435
436
  infrahub/graphql/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
436
437
  infrahub/graphql/api/dependencies.py,sha256=-NMUA_N4tWcVpS6ksCebAyza-JTmHqyYY_QZizgBR1c,1690
437
438
  infrahub/graphql/api/endpoints.py,sha256=wH9eO3CFT-eoSe1Y32BhU9mIf6smEnPeP3tAxZkdt4g,1510
438
- infrahub/graphql/app.py,sha256=4xKptosI6SXlBfQSNJHHGNrdCRAZQ2uMTpPPKY3_mzE,21004
439
+ infrahub/graphql/app.py,sha256=zjlsZxkYRqye9yL0c1Y69QcBMr4mwgTu_PdVxyEUlG8,21135
439
440
  infrahub/graphql/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
440
441
  infrahub/graphql/auth/query_permission_checker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
441
442
  infrahub/graphql/auth/query_permission_checker/anonymous_checker.py,sha256=ibsmGyOelLJbN2Kfkmffv-5D79h7tRc1Fez5tauFY8w,1377
@@ -617,14 +618,14 @@ infrahub/services/adapters/http/__init__.py,sha256=SyMHID_aqH6bGLVZgDxQFqhJB7v_5
617
618
  infrahub/services/adapters/http/httpx.py,sha256=jUPbxnjYZzWxk7fnFt2D4eSbd4JmiAGZFPk0Tz-Eyo0,3652
618
619
  infrahub/services/adapters/message_bus/__init__.py,sha256=259um9F05MXukfbDvEyxY_iDDbPUmpkcYR8LWP_W_vY,3261
619
620
  infrahub/services/adapters/message_bus/local.py,sha256=wG4i-Bp6gW2bDaP0x6eMPtmmJYGbM5pavuaYrPNCBvI,2403
620
- infrahub/services/adapters/message_bus/nats.py,sha256=SPjwPZQHSdUbMoap0H38Ulbe1V58wP0W1TcHM7Ud9hI,12009
621
+ infrahub/services/adapters/message_bus/nats.py,sha256=yHqSeXJL9BiHAP3JQ0S86fITSQUbe7TSJnQrq4r7Cu4,12141
621
622
  infrahub/services/adapters/message_bus/rabbitmq.py,sha256=OHEnQlZiY58S-5OksaxxvpltOZ-VJ-HXmY7fzdCDWKM,10975
622
623
  infrahub/services/adapters/workflow/__init__.py,sha256=NvZnbwK2Gp5CYaMEiiQVClNa5u_4QWVN4G2KDtfNZBI,1642
623
624
  infrahub/services/adapters/workflow/local.py,sha256=4W-WIrq2LSsn35K0ITmaJXCi_RAI4qsMp0iuQBBR26I,1850
624
625
  infrahub/services/adapters/workflow/worker.py,sha256=T3TaqvdG8dZtf1oQOgrTgRCaC6ycKAMeHieu0WQalp0,3097
625
626
  infrahub/services/component.py,sha256=uQvmhDtvPTfxYAdDkaQulbdIWh_kxrFAuhJooKufaac,5634
626
627
  infrahub/services/protocols.py,sha256=Ci4cnWK6L_R_5V2qAPnQpHtKXYS0hktp7CoJWIbcbc0,754
627
- infrahub/services/scheduler.py,sha256=LbuIyLsyYa5E8eWA6aXidGyhIIninGJ8ue5tO5qmiCA,3113
628
+ infrahub/services/scheduler.py,sha256=TbKg74oBINScHJYtV8_lOuQR2RjxqS6IfU_slyjpNYw,3246
628
629
  infrahub/storage.py,sha256=bpK8m7GNlp5LHI0yXuFNZhhBVQpU7RZr7MeWCaAAPLk,1812
629
630
  infrahub/task_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
630
631
  infrahub/task_manager/constants.py,sha256=1t1BZRa8_y89gIDPNHzIbRKo63nHOP37-r5OvtHa56c,559
@@ -715,10 +716,10 @@ infrahub_sdk/generator.py,sha256=I00G7BdQohJFZ7wQru1SWcwO41gPbuQ3ZGEDVkLIn60,340
715
716
  infrahub_sdk/graphql.py,sha256=zrxRveg8-t0FbLtOEMDiiW0vqtBHc2qaFRkiHF9Bp6g,7019
716
717
  infrahub_sdk/groups.py,sha256=GL14ByW4GHrkqOLJ-_vGhu6bkYDxljqPtkErcQVehv0,711
717
718
  infrahub_sdk/jinja2.py,sha256=lTfV9E_P5gApaX6RW9M8U8oixQi-0H3U8wcs8fdGVaU,1150
718
- infrahub_sdk/node/__init__.py,sha256=sUTxgpA6gnnotu-_83Va_W8VNkOtEqijel_0PPiLqSQ,1212
719
+ infrahub_sdk/node/__init__.py,sha256=clAUZ9lNVPFguelR5Sg9PzklAZruTKEm2xk-BaO68l8,1262
719
720
  infrahub_sdk/node/attribute.py,sha256=oEY1qxip8ETEx9Q33NhSQo013zmzrmpVIFzSkEMUY8M,4547
720
721
  infrahub_sdk/node/constants.py,sha256=TJO4uxvv7sc3FjoLdQdV7Ccymqz8AqxDenARst8awb4,775
721
- infrahub_sdk/node/node.py,sha256=cmLp-Xdv5xjgDDyJZwimLY3taDEJNvsSXGv_j3VlN1w,69219
722
+ infrahub_sdk/node/node.py,sha256=ht6T2Td9CSRrdgCTIemVJmTqEuL4a9YC77zhElN8S0U,70138
722
723
  infrahub_sdk/node/parsers.py,sha256=sLDdT6neoYSZIjOCmq8Bgd0LK8FFoasjvJLuSz0whSU,543
723
724
  infrahub_sdk/node/property.py,sha256=8Mjkc8bp3kLlHyllwxDJlpJTuOA1ciMgY8mtH3dFVLM,728
724
725
  infrahub_sdk/node/related_node.py,sha256=41VTj4k1qojuyBZr0XiD7e2NESl8YwsU3fCmaarlrD0,9916
@@ -800,8 +801,8 @@ infrahub_testcontainers/models.py,sha256=ASYyvl7d_WQz_i7y8-3iab9hwwmCl3OCJavqVbe
800
801
  infrahub_testcontainers/performance_test.py,sha256=hvwiy6tc_lWniYqGkqfOXVGAmA_IV15VOZqbiD9ezno,6149
801
802
  infrahub_testcontainers/plugin.py,sha256=I3RuZQ0dARyKHuqCf0y1Yj731P2Mwf3BJUehRJKeWrs,5645
802
803
  infrahub_testcontainers/prometheus.yml,sha256=610xQEyj3xuVJMzPkC4m1fRnCrjGpiRBrXA2ytCLa54,599
803
- infrahub_server-1.3.0b6.dist-info/LICENSE.txt,sha256=TfPDBt3ar0uv_f9cqCDMZ5rIzW3CY8anRRd4PkL6ejs,34522
804
- infrahub_server-1.3.0b6.dist-info/METADATA,sha256=9CZIMdsxhxRo1eavv6Jl1CuKHoqXPXkbNcNE4vG3aBE,8207
805
- infrahub_server-1.3.0b6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
806
- infrahub_server-1.3.0b6.dist-info/entry_points.txt,sha256=UXIeFWDsrV-4IllNvUEd6KieYGzQfn9paga2YyABOQI,393
807
- infrahub_server-1.3.0b6.dist-info/RECORD,,
804
+ infrahub_server-1.3.1.dist-info/LICENSE.txt,sha256=TfPDBt3ar0uv_f9cqCDMZ5rIzW3CY8anRRd4PkL6ejs,34522
805
+ infrahub_server-1.3.1.dist-info/METADATA,sha256=7WxSk_ctoonbM_snztUovCRsd9Lq-grShqcalwFQuEo,8205
806
+ infrahub_server-1.3.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
807
+ infrahub_server-1.3.1.dist-info/entry_points.txt,sha256=UXIeFWDsrV-4IllNvUEd6KieYGzQfn9paga2YyABOQI,393
808
+ infrahub_server-1.3.1.dist-info/RECORD,,