infrahub-server 1.5.0b0__py3-none-any.whl → 1.5.0b2__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.
- infrahub/actions/tasks.py +8 -0
- infrahub/api/diff/diff.py +1 -1
- infrahub/api/internal.py +2 -0
- infrahub/api/oauth2.py +13 -19
- infrahub/api/oidc.py +15 -21
- infrahub/api/schema.py +24 -3
- infrahub/artifacts/models.py +2 -1
- infrahub/auth.py +137 -3
- infrahub/cli/__init__.py +2 -0
- infrahub/cli/db.py +103 -98
- infrahub/cli/db_commands/clean_duplicate_schema_fields.py +212 -0
- infrahub/cli/dev.py +118 -0
- infrahub/cli/tasks.py +46 -0
- infrahub/cli/upgrade.py +30 -3
- infrahub/computed_attribute/tasks.py +20 -8
- infrahub/core/attribute.py +13 -5
- infrahub/core/branch/enums.py +1 -1
- infrahub/core/branch/models.py +7 -3
- infrahub/core/branch/tasks.py +70 -8
- infrahub/core/changelog/models.py +4 -12
- infrahub/core/constants/__init__.py +3 -0
- infrahub/core/constants/infrahubkind.py +1 -0
- infrahub/core/diff/model/path.py +4 -0
- infrahub/core/diff/payload_builder.py +1 -1
- infrahub/core/diff/query/artifact.py +1 -0
- infrahub/core/diff/query/field_summary.py +1 -0
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/initialization.py +5 -2
- infrahub/core/ipam/utilization.py +1 -1
- infrahub/core/manager.py +6 -3
- infrahub/core/migrations/__init__.py +3 -0
- infrahub/core/migrations/exceptions.py +4 -0
- infrahub/core/migrations/graph/__init__.py +12 -11
- infrahub/core/migrations/graph/load_schema_branch.py +21 -0
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +1 -1
- infrahub/core/migrations/graph/m040_duplicated_attributes.py +81 -0
- infrahub/core/migrations/graph/m041_profile_attrs_in_db.py +145 -0
- infrahub/core/migrations/graph/m042_create_hfid_display_label_in_db.py +164 -0
- infrahub/core/migrations/graph/m043_backfill_hfid_display_label_in_db.py +866 -0
- infrahub/core/migrations/query/__init__.py +7 -8
- infrahub/core/migrations/query/attribute_add.py +8 -6
- infrahub/core/migrations/query/attribute_remove.py +134 -0
- infrahub/core/migrations/runner.py +54 -0
- infrahub/core/migrations/schema/attribute_kind_update.py +9 -3
- infrahub/core/migrations/schema/attribute_supports_profile.py +90 -0
- infrahub/core/migrations/schema/node_attribute_add.py +35 -4
- infrahub/core/migrations/schema/node_attribute_remove.py +13 -109
- infrahub/core/migrations/schema/node_kind_update.py +2 -1
- infrahub/core/migrations/schema/node_remove.py +2 -1
- infrahub/core/migrations/schema/placeholder_dummy.py +3 -2
- infrahub/core/migrations/shared.py +52 -19
- infrahub/core/node/__init__.py +158 -51
- infrahub/core/node/constraints/attribute_uniqueness.py +3 -1
- infrahub/core/node/create.py +46 -63
- infrahub/core/node/lock_utils.py +70 -44
- infrahub/core/node/node_property_attribute.py +230 -0
- infrahub/core/node/resource_manager/ip_address_pool.py +2 -1
- infrahub/core/node/resource_manager/ip_prefix_pool.py +2 -1
- infrahub/core/node/resource_manager/number_pool.py +2 -1
- infrahub/core/node/standard.py +1 -1
- infrahub/core/protocols.py +7 -1
- infrahub/core/query/attribute.py +55 -0
- infrahub/core/query/ipam.py +1 -0
- infrahub/core/query/node.py +23 -4
- infrahub/core/query/relationship.py +1 -0
- infrahub/core/registry.py +2 -2
- infrahub/core/relationship/constraints/count.py +1 -1
- infrahub/core/relationship/model.py +1 -1
- infrahub/core/schema/__init__.py +56 -0
- infrahub/core/schema/attribute_schema.py +4 -0
- infrahub/core/schema/basenode_schema.py +42 -2
- infrahub/core/schema/definitions/core/__init__.py +2 -0
- infrahub/core/schema/definitions/core/generator.py +2 -0
- infrahub/core/schema/definitions/core/group.py +16 -2
- infrahub/core/schema/definitions/internal.py +16 -3
- infrahub/core/schema/generated/attribute_schema.py +2 -2
- infrahub/core/schema/generated/base_node_schema.py +6 -1
- infrahub/core/schema/manager.py +22 -1
- infrahub/core/schema/node_schema.py +5 -2
- infrahub/core/schema/schema_branch.py +300 -8
- infrahub/core/schema/schema_branch_display.py +123 -0
- infrahub/core/schema/schema_branch_hfid.py +114 -0
- infrahub/core/validators/aggregated_checker.py +1 -1
- infrahub/core/validators/determiner.py +12 -1
- infrahub/core/validators/relationship/peer.py +1 -1
- infrahub/core/validators/tasks.py +1 -1
- infrahub/database/graph.py +21 -0
- infrahub/display_labels/__init__.py +0 -0
- infrahub/display_labels/gather.py +48 -0
- infrahub/display_labels/models.py +240 -0
- infrahub/display_labels/tasks.py +192 -0
- infrahub/display_labels/triggers.py +22 -0
- infrahub/events/branch_action.py +27 -1
- infrahub/events/group_action.py +1 -1
- infrahub/events/node_action.py +1 -1
- infrahub/generators/constants.py +7 -0
- infrahub/generators/models.py +7 -0
- infrahub/generators/tasks.py +34 -22
- infrahub/git/base.py +4 -1
- infrahub/git/integrator.py +23 -15
- infrahub/git/models.py +2 -1
- infrahub/git/repository.py +22 -5
- infrahub/git/tasks.py +66 -10
- infrahub/git/utils.py +123 -1
- infrahub/graphql/analyzer.py +1 -1
- infrahub/graphql/api/endpoints.py +14 -4
- infrahub/graphql/manager.py +4 -9
- infrahub/graphql/mutations/convert_object_type.py +11 -1
- infrahub/graphql/mutations/display_label.py +118 -0
- infrahub/graphql/mutations/generator.py +25 -7
- infrahub/graphql/mutations/hfid.py +125 -0
- infrahub/graphql/mutations/ipam.py +54 -35
- infrahub/graphql/mutations/main.py +27 -28
- infrahub/graphql/mutations/relationship.py +2 -2
- infrahub/graphql/mutations/resource_manager.py +2 -2
- infrahub/graphql/mutations/schema.py +5 -5
- infrahub/graphql/queries/resource_manager.py +1 -1
- infrahub/graphql/resolvers/resolver.py +2 -0
- infrahub/graphql/schema.py +4 -0
- infrahub/graphql/schema_sort.py +170 -0
- infrahub/graphql/types/branch.py +4 -1
- infrahub/graphql/types/enums.py +3 -0
- infrahub/groups/tasks.py +1 -1
- infrahub/hfid/__init__.py +0 -0
- infrahub/hfid/gather.py +48 -0
- infrahub/hfid/models.py +240 -0
- infrahub/hfid/tasks.py +191 -0
- infrahub/hfid/triggers.py +22 -0
- infrahub/lock.py +67 -16
- infrahub/message_bus/types.py +2 -1
- infrahub/middleware.py +26 -1
- infrahub/permissions/constants.py +2 -0
- infrahub/proposed_change/tasks.py +35 -17
- infrahub/server.py +21 -4
- infrahub/services/__init__.py +8 -5
- infrahub/services/adapters/http/__init__.py +5 -0
- infrahub/services/adapters/workflow/worker.py +14 -3
- infrahub/task_manager/event.py +5 -0
- infrahub/task_manager/models.py +7 -0
- infrahub/task_manager/task.py +73 -0
- infrahub/trigger/catalogue.py +4 -0
- infrahub/trigger/models.py +2 -0
- infrahub/trigger/setup.py +13 -4
- infrahub/trigger/tasks.py +6 -0
- infrahub/workers/dependencies.py +10 -1
- infrahub/workers/infrahub_async.py +10 -2
- infrahub/workflows/catalogue.py +80 -0
- infrahub/workflows/initialization.py +21 -0
- infrahub/workflows/utils.py +2 -1
- infrahub_sdk/checks.py +1 -1
- infrahub_sdk/client.py +13 -10
- infrahub_sdk/config.py +29 -2
- infrahub_sdk/ctl/cli_commands.py +2 -0
- infrahub_sdk/ctl/generator.py +4 -0
- infrahub_sdk/ctl/graphql.py +184 -0
- infrahub_sdk/ctl/schema.py +28 -9
- infrahub_sdk/generator.py +7 -1
- infrahub_sdk/graphql/__init__.py +12 -0
- infrahub_sdk/graphql/constants.py +1 -0
- infrahub_sdk/graphql/plugin.py +85 -0
- infrahub_sdk/graphql/query.py +77 -0
- infrahub_sdk/{graphql.py → graphql/renderers.py} +81 -73
- infrahub_sdk/graphql/utils.py +40 -0
- infrahub_sdk/protocols.py +14 -0
- infrahub_sdk/schema/__init__.py +70 -4
- infrahub_sdk/schema/repository.py +8 -0
- infrahub_sdk/spec/models.py +7 -0
- infrahub_sdk/spec/object.py +53 -44
- infrahub_sdk/spec/processors/__init__.py +0 -0
- infrahub_sdk/spec/processors/data_processor.py +10 -0
- infrahub_sdk/spec/processors/factory.py +34 -0
- infrahub_sdk/spec/processors/range_expand_processor.py +56 -0
- infrahub_sdk/spec/range_expansion.py +1 -1
- infrahub_sdk/transforms.py +1 -1
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/METADATA +7 -4
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/RECORD +182 -143
- infrahub_testcontainers/container.py +115 -3
- infrahub_testcontainers/docker-compose-cluster.test.yml +6 -1
- infrahub_testcontainers/docker-compose.test.yml +6 -1
- infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +0 -166
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/WHEEL +0 -0
- {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/entry_points.txt +0 -0
|
@@ -66,6 +66,8 @@ from ... import config
|
|
|
66
66
|
from ..constants.schema import PARENT_CHILD_IDENTIFIER
|
|
67
67
|
from .constants import INTERNAL_SCHEMA_NODE_KINDS, SchemaNamespace
|
|
68
68
|
from .schema_branch_computed import ComputedAttributes
|
|
69
|
+
from .schema_branch_display import DisplayLabels
|
|
70
|
+
from .schema_branch_hfid import HFIDs
|
|
69
71
|
|
|
70
72
|
log = get_logger()
|
|
71
73
|
|
|
@@ -77,6 +79,8 @@ class SchemaBranch:
|
|
|
77
79
|
name: str | None = None,
|
|
78
80
|
data: dict[str, dict[str, str]] | None = None,
|
|
79
81
|
computed_attributes: ComputedAttributes | None = None,
|
|
82
|
+
display_labels: DisplayLabels | None = None,
|
|
83
|
+
hfids: HFIDs | None = None,
|
|
80
84
|
):
|
|
81
85
|
self._cache: dict[str, NodeSchema | GenericSchema] = cache
|
|
82
86
|
self.name: str | None = name
|
|
@@ -85,6 +89,8 @@ class SchemaBranch:
|
|
|
85
89
|
self.profiles: dict[str, str] = {}
|
|
86
90
|
self.templates: dict[str, str] = {}
|
|
87
91
|
self.computed_attributes = computed_attributes or ComputedAttributes()
|
|
92
|
+
self.display_labels = display_labels or DisplayLabels()
|
|
93
|
+
self.hfids = hfids or HFIDs()
|
|
88
94
|
|
|
89
95
|
if data:
|
|
90
96
|
self.nodes = data.get("nodes", {})
|
|
@@ -163,6 +169,14 @@ class SchemaBranch:
|
|
|
163
169
|
"templates": {name: self.get(name, duplicate=duplicate) for name in self.templates},
|
|
164
170
|
}
|
|
165
171
|
|
|
172
|
+
def to_dict_api_schema_object(self) -> dict[str, list[dict]]:
|
|
173
|
+
return {
|
|
174
|
+
"nodes": [self.get(name, duplicate=False).model_dump() for name in self.nodes],
|
|
175
|
+
"profiles": [self.get(name, duplicate=False).model_dump() for name in self.profiles],
|
|
176
|
+
"generics": [self.get(name, duplicate=False).model_dump() for name in self.generics],
|
|
177
|
+
"templates": [self.get(name, duplicate=False).model_dump() for name in self.templates],
|
|
178
|
+
}
|
|
179
|
+
|
|
166
180
|
@classmethod
|
|
167
181
|
def from_dict_schema_object(cls, data: dict) -> Self:
|
|
168
182
|
type_mapping = {
|
|
@@ -270,6 +284,8 @@ class SchemaBranch:
|
|
|
270
284
|
data=copy.deepcopy(self.to_dict()),
|
|
271
285
|
cache=self._cache,
|
|
272
286
|
computed_attributes=self.computed_attributes.duplicate(),
|
|
287
|
+
display_labels=self.display_labels.duplicate(),
|
|
288
|
+
hfids=self.hfids.duplicate(),
|
|
273
289
|
)
|
|
274
290
|
|
|
275
291
|
def set(self, name: str, schema: MainSchemaTypes) -> str:
|
|
@@ -312,14 +328,23 @@ class SchemaBranch:
|
|
|
312
328
|
elif name in self.templates:
|
|
313
329
|
key = self.templates[name]
|
|
314
330
|
|
|
315
|
-
if key
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
331
|
+
if not key:
|
|
332
|
+
raise SchemaNotFoundError(
|
|
333
|
+
branch_name=self.name, identifier=name, message=f"Unable to find the schema {name!r} in the registry"
|
|
334
|
+
)
|
|
319
335
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
336
|
+
schema: MainSchemaTypes | None = None
|
|
337
|
+
try:
|
|
338
|
+
schema = self._cache[key]
|
|
339
|
+
except KeyError:
|
|
340
|
+
pass
|
|
341
|
+
|
|
342
|
+
if not schema:
|
|
343
|
+
raise ValueError(f"Schema {name!r} on branch {self.name} has incorrect hash: {key!r}")
|
|
344
|
+
|
|
345
|
+
if duplicate:
|
|
346
|
+
return schema.duplicate()
|
|
347
|
+
return schema
|
|
323
348
|
|
|
324
349
|
def get_node(self, name: str, duplicate: bool = True) -> NodeSchema:
|
|
325
350
|
"""Access a specific NodeSchema, defined by its kind."""
|
|
@@ -504,6 +529,9 @@ class SchemaBranch:
|
|
|
504
529
|
self.process_post_validation()
|
|
505
530
|
|
|
506
531
|
def process_pre_validation(self) -> None:
|
|
532
|
+
self.process_nodes_state()
|
|
533
|
+
self.process_attributes_state()
|
|
534
|
+
self.process_relationships_state()
|
|
507
535
|
self.generate_identifiers()
|
|
508
536
|
self.process_default_values()
|
|
509
537
|
self.process_deprecations()
|
|
@@ -530,11 +558,13 @@ class SchemaBranch:
|
|
|
530
558
|
self.sync_uniqueness_constraints_and_unique_attributes()
|
|
531
559
|
self.validate_uniqueness_constraints()
|
|
532
560
|
self.validate_display_labels()
|
|
561
|
+
self.validate_display_label()
|
|
533
562
|
self.validate_order_by()
|
|
534
563
|
self.validate_default_filters()
|
|
535
564
|
self.validate_parent_component()
|
|
536
565
|
self.validate_human_friendly_id()
|
|
537
566
|
self.validate_required_relationships()
|
|
567
|
+
self.validate_inherited_relationships_fields()
|
|
538
568
|
|
|
539
569
|
def process_post_validation(self) -> None:
|
|
540
570
|
self.cleanup_inherited_elements()
|
|
@@ -544,6 +574,7 @@ class SchemaBranch:
|
|
|
544
574
|
self.process_dropdowns()
|
|
545
575
|
self.process_relationships()
|
|
546
576
|
self.process_human_friendly_id()
|
|
577
|
+
self.register_human_friendly_id()
|
|
547
578
|
|
|
548
579
|
def _generate_identifier_string(self, node_kind: str, peer_kind: str) -> str:
|
|
549
580
|
return "__".join(sorted([node_kind, peer_kind])).lower()
|
|
@@ -752,6 +783,36 @@ class SchemaBranch:
|
|
|
752
783
|
element_name=element_name,
|
|
753
784
|
)
|
|
754
785
|
|
|
786
|
+
def validate_display_label(self) -> None:
|
|
787
|
+
self.display_labels = DisplayLabels()
|
|
788
|
+
for name in self.all_names:
|
|
789
|
+
node_schema = self.get(name=name, duplicate=False)
|
|
790
|
+
|
|
791
|
+
if node_schema.display_label is None and node_schema.display_labels:
|
|
792
|
+
update_candidate = self.get(name=name, duplicate=True)
|
|
793
|
+
if len(node_schema.display_labels) == 1:
|
|
794
|
+
# If the previous display_labels consist of a single attribute convert
|
|
795
|
+
# it to an attribute based display label
|
|
796
|
+
update_candidate.display_label = _format_display_label_component(
|
|
797
|
+
component=node_schema.display_labels[0]
|
|
798
|
+
)
|
|
799
|
+
else:
|
|
800
|
+
# If the previous display label consists of multiple attributes
|
|
801
|
+
# convert it to a Jinja2 based display label
|
|
802
|
+
update_candidate.display_label = " ".join(
|
|
803
|
+
[
|
|
804
|
+
f"{{{{ {_format_display_label_component(component=display_label)} }}}}"
|
|
805
|
+
for display_label in node_schema.display_labels
|
|
806
|
+
]
|
|
807
|
+
)
|
|
808
|
+
self.set(name=name, schema=update_candidate)
|
|
809
|
+
|
|
810
|
+
node_schema = self.get(name=name, duplicate=False)
|
|
811
|
+
if not node_schema.display_label:
|
|
812
|
+
continue
|
|
813
|
+
|
|
814
|
+
self._validate_display_label(node=node_schema)
|
|
815
|
+
|
|
755
816
|
def validate_display_labels(self) -> None:
|
|
756
817
|
for name in self.all_names:
|
|
757
818
|
node_schema = self.get(name=name, duplicate=False)
|
|
@@ -860,7 +921,14 @@ class SchemaBranch:
|
|
|
860
921
|
# Mapping relationship identifiers -> list of attributes paths
|
|
861
922
|
rel_schemas_to_paths: dict[str, tuple[MainSchemaTypes, list[str]]] = {}
|
|
862
923
|
|
|
924
|
+
visited_paths: list[str] = []
|
|
863
925
|
for hfid_path in node_schema.human_friendly_id:
|
|
926
|
+
if config.SETTINGS.main.schema_strict_mode and hfid_path in visited_paths:
|
|
927
|
+
raise ValidationError(
|
|
928
|
+
f"HFID of {node_schema.kind} cannot use the same path more than once: {hfid_path}"
|
|
929
|
+
)
|
|
930
|
+
|
|
931
|
+
visited_paths.append(hfid_path)
|
|
864
932
|
schema_path = self.validate_schema_path(
|
|
865
933
|
node_schema=node_schema,
|
|
866
934
|
path=hfid_path,
|
|
@@ -1137,6 +1205,50 @@ class SchemaBranch:
|
|
|
1137
1205
|
node=node_schema, attribute=attribute, generic=generic_schema
|
|
1138
1206
|
)
|
|
1139
1207
|
|
|
1208
|
+
def _validate_display_label(self, node: MainSchemaTypes) -> None:
|
|
1209
|
+
if not node.display_label:
|
|
1210
|
+
return
|
|
1211
|
+
|
|
1212
|
+
if not any(c in node.display_label for c in "{}"):
|
|
1213
|
+
schema_path = self.validate_schema_path(
|
|
1214
|
+
node_schema=node,
|
|
1215
|
+
path=node.display_label,
|
|
1216
|
+
allowed_path_types=SchemaElementPathType.ATTR_WITH_PROP,
|
|
1217
|
+
element_name="display_label - non Jinja2",
|
|
1218
|
+
)
|
|
1219
|
+
if schema_path.attribute_schema and node.is_node_schema and node.namespace not in ["Internal", "Schema"]:
|
|
1220
|
+
self.display_labels.register_attribute_based_display_label(
|
|
1221
|
+
kind=node.kind, attribute_name=schema_path.attribute_schema.name
|
|
1222
|
+
)
|
|
1223
|
+
return
|
|
1224
|
+
|
|
1225
|
+
jinja_template = Jinja2Template(template=node.display_label)
|
|
1226
|
+
try:
|
|
1227
|
+
variables = jinja_template.get_variables()
|
|
1228
|
+
jinja_template.validate(restricted=config.SETTINGS.security.restrict_untrusted_jinja2_filters)
|
|
1229
|
+
except (JinjaTemplateOperationViolationError, JinjaTemplateError) as exc:
|
|
1230
|
+
raise ValueError(
|
|
1231
|
+
f"{node.kind}: display_label is set to a jinja2 template, but has an invalid template: {exc.message}"
|
|
1232
|
+
) from exc
|
|
1233
|
+
|
|
1234
|
+
allowed_path_types = (
|
|
1235
|
+
SchemaElementPathType.ATTR_WITH_PROP
|
|
1236
|
+
| SchemaElementPathType.REL_ONE_MANDATORY_ATTR_WITH_PROP
|
|
1237
|
+
| SchemaElementPathType.REL_ONE_ATTR_WITH_PROP
|
|
1238
|
+
)
|
|
1239
|
+
for variable in variables:
|
|
1240
|
+
schema_path = self.validate_schema_path(
|
|
1241
|
+
node_schema=node, path=variable, allowed_path_types=allowed_path_types, element_name="display_label"
|
|
1242
|
+
)
|
|
1243
|
+
|
|
1244
|
+
if schema_path.is_type_attribute and schema_path.active_attribute_schema.name == "display_label":
|
|
1245
|
+
raise ValueError(f"{node.kind}: display_label the '{variable}' variable is a reference to itself")
|
|
1246
|
+
|
|
1247
|
+
if node.is_node_schema and node.namespace not in ["Internal", "Schema"]:
|
|
1248
|
+
self.display_labels.register_template_schema_path(
|
|
1249
|
+
kind=node.kind, schema_path=schema_path, template=node.display_label
|
|
1250
|
+
)
|
|
1251
|
+
|
|
1140
1252
|
def _validate_computed_attribute(self, node: NodeSchema, attribute: AttributeSchema) -> None:
|
|
1141
1253
|
if not attribute.computed_attribute or attribute.computed_attribute.kind == ComputedAttributeKind.USER:
|
|
1142
1254
|
return
|
|
@@ -1229,6 +1341,81 @@ class SchemaBranch:
|
|
|
1229
1341
|
f"{node.kind}: Relationship {rel.name!r} max_count must be 0 or greater than 1 when cardinality is MANY"
|
|
1230
1342
|
)
|
|
1231
1343
|
|
|
1344
|
+
def validate_inherited_relationships_fields(self) -> None:
|
|
1345
|
+
for name in self.node_names:
|
|
1346
|
+
node_schema = self.get(name=name, duplicate=False)
|
|
1347
|
+
if not node_schema.inherit_from:
|
|
1348
|
+
continue
|
|
1349
|
+
|
|
1350
|
+
self.validate_node_inherited_relationship_fields(node_schema)
|
|
1351
|
+
|
|
1352
|
+
def validate_node_inherited_relationship_fields(self, node_schema: NodeSchema) -> None:
|
|
1353
|
+
generics = [self.get(name=node_name, duplicate=False) for node_name in node_schema.inherit_from]
|
|
1354
|
+
relationship_names = [node.relationship_names for node in generics]
|
|
1355
|
+
related_relationship_names = set().union(
|
|
1356
|
+
*[
|
|
1357
|
+
set(relationship_name_a) & set(relationship_name_b)
|
|
1358
|
+
for index, relationship_name_a in enumerate(relationship_names)
|
|
1359
|
+
for relationship_name_b in relationship_names[index + 1 :]
|
|
1360
|
+
]
|
|
1361
|
+
)
|
|
1362
|
+
# Check that the relationship properties match
|
|
1363
|
+
# for every generic node in generics list having related relationship names
|
|
1364
|
+
for index, generic_a in enumerate(generics):
|
|
1365
|
+
for generic_b in generics[index + 1 :]:
|
|
1366
|
+
for relationship_name in related_relationship_names:
|
|
1367
|
+
try:
|
|
1368
|
+
relationship_a = generic_a.get_relationship(name=relationship_name)
|
|
1369
|
+
relationship_b = generic_b.get_relationship(name=relationship_name)
|
|
1370
|
+
except ValueError:
|
|
1371
|
+
continue
|
|
1372
|
+
|
|
1373
|
+
matched, _property = self._check_relationship_properties_match(
|
|
1374
|
+
relationship_a=relationship_a, relationship_b=relationship_b
|
|
1375
|
+
)
|
|
1376
|
+
if not matched:
|
|
1377
|
+
raise ValueError(
|
|
1378
|
+
f"{node_schema.kind} inherits from '{generic_a.kind}' & '{generic_b.kind}'"
|
|
1379
|
+
f" with different '{_property}' on the '{relationship_name}' relationship"
|
|
1380
|
+
)
|
|
1381
|
+
|
|
1382
|
+
def _check_relationship_properties_match(
|
|
1383
|
+
self, relationship_a: RelationshipSchema, relationship_b: RelationshipSchema
|
|
1384
|
+
) -> tuple[bool, str | None]:
|
|
1385
|
+
compulsorily_matching_properties = (
|
|
1386
|
+
"name",
|
|
1387
|
+
"peer",
|
|
1388
|
+
"kind",
|
|
1389
|
+
"identifier",
|
|
1390
|
+
"cardinality",
|
|
1391
|
+
"min_count",
|
|
1392
|
+
"max_count",
|
|
1393
|
+
"common_parent",
|
|
1394
|
+
"common_relatives",
|
|
1395
|
+
"optional",
|
|
1396
|
+
"branch",
|
|
1397
|
+
"direction",
|
|
1398
|
+
"on_delete",
|
|
1399
|
+
"read_only",
|
|
1400
|
+
"hierarchical",
|
|
1401
|
+
"allow_override",
|
|
1402
|
+
)
|
|
1403
|
+
for _property in compulsorily_matching_properties:
|
|
1404
|
+
if not hasattr(relationship_a, _property) or not hasattr(relationship_b, _property):
|
|
1405
|
+
continue
|
|
1406
|
+
|
|
1407
|
+
equal_delete_actions = (None, RelationshipDeleteBehavior.NO_ACTION)
|
|
1408
|
+
if (
|
|
1409
|
+
_property == "on_delete"
|
|
1410
|
+
and getattr(relationship_a, _property) in equal_delete_actions
|
|
1411
|
+
and getattr(relationship_b, _property) in equal_delete_actions
|
|
1412
|
+
):
|
|
1413
|
+
continue
|
|
1414
|
+
|
|
1415
|
+
if getattr(relationship_a, _property) != getattr(relationship_b, _property):
|
|
1416
|
+
return False, _property
|
|
1417
|
+
return True, None
|
|
1418
|
+
|
|
1232
1419
|
def process_dropdowns(self) -> None:
|
|
1233
1420
|
for name in self.all_names:
|
|
1234
1421
|
node = self.get(name=name, duplicate=False)
|
|
@@ -1357,6 +1544,34 @@ class SchemaBranch:
|
|
|
1357
1544
|
node.uniqueness_constraints = [hfid_uniqueness_constraint]
|
|
1358
1545
|
self.set(name=node.kind, schema=node)
|
|
1359
1546
|
|
|
1547
|
+
def register_human_friendly_id(self) -> None:
|
|
1548
|
+
"""Register HFID automations
|
|
1549
|
+
|
|
1550
|
+
Register the HFIDs after all processing and validation has been done.
|
|
1551
|
+
"""
|
|
1552
|
+
|
|
1553
|
+
self.hfids = HFIDs()
|
|
1554
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
1555
|
+
node_schema = self.get(name=name, duplicate=False)
|
|
1556
|
+
|
|
1557
|
+
if not node_schema.human_friendly_id:
|
|
1558
|
+
continue
|
|
1559
|
+
|
|
1560
|
+
allowed_types = SchemaElementPathType.ATTR_WITH_PROP | SchemaElementPathType.REL_ONE_MANDATORY_ATTR
|
|
1561
|
+
|
|
1562
|
+
for hfid_path in node_schema.human_friendly_id:
|
|
1563
|
+
schema_path = self.validate_schema_path(
|
|
1564
|
+
node_schema=node_schema,
|
|
1565
|
+
path=hfid_path,
|
|
1566
|
+
allowed_path_types=allowed_types,
|
|
1567
|
+
element_name="human_friendly_id",
|
|
1568
|
+
)
|
|
1569
|
+
|
|
1570
|
+
if node_schema.is_node_schema and node_schema.namespace not in ["Schema", "Internal"]:
|
|
1571
|
+
self.hfids.register_hfid_schema_path(
|
|
1572
|
+
kind=node_schema.kind, schema_path=schema_path, hfid=node_schema.human_friendly_id
|
|
1573
|
+
)
|
|
1574
|
+
|
|
1360
1575
|
def process_hierarchy(self) -> None:
|
|
1361
1576
|
for name in self.nodes.keys():
|
|
1362
1577
|
node = self.get_node(name=name, duplicate=False)
|
|
@@ -1612,10 +1827,48 @@ class SchemaBranch:
|
|
|
1612
1827
|
|
|
1613
1828
|
self.set(name=name, schema=node)
|
|
1614
1829
|
|
|
1830
|
+
def process_relationships_state(self) -> None:
|
|
1831
|
+
for name in self.node_names + self.generic_names_without_templates:
|
|
1832
|
+
node = self.get(name=name, duplicate=False)
|
|
1833
|
+
if node.id or (not node.id and not node.relationships):
|
|
1834
|
+
continue
|
|
1835
|
+
|
|
1836
|
+
filtered_relationships = [
|
|
1837
|
+
relationship for relationship in node.relationships if relationship.state != HashableModelState.ABSENT
|
|
1838
|
+
]
|
|
1839
|
+
if len(filtered_relationships) == len(node.relationships):
|
|
1840
|
+
continue
|
|
1841
|
+
updated_node = node.duplicate()
|
|
1842
|
+
updated_node.relationships = filtered_relationships
|
|
1843
|
+
self.set(name=name, schema=updated_node)
|
|
1844
|
+
|
|
1845
|
+
def process_attributes_state(self) -> None:
|
|
1846
|
+
for name in self.node_names + self.generic_names_without_templates:
|
|
1847
|
+
node = self.get(name=name, duplicate=False)
|
|
1848
|
+
if not node.attributes:
|
|
1849
|
+
continue
|
|
1850
|
+
|
|
1851
|
+
filtered_attributes = [
|
|
1852
|
+
attribute for attribute in node.attributes if attribute.state != HashableModelState.ABSENT
|
|
1853
|
+
]
|
|
1854
|
+
if len(filtered_attributes) == len(node.attributes):
|
|
1855
|
+
continue
|
|
1856
|
+
updated_node = node.duplicate()
|
|
1857
|
+
updated_node.attributes = filtered_attributes
|
|
1858
|
+
self.set(name=name, schema=updated_node)
|
|
1859
|
+
|
|
1860
|
+
def process_nodes_state(self) -> None:
|
|
1861
|
+
for name in self.node_names + self.generic_names_without_templates:
|
|
1862
|
+
node = self.get(name=name, duplicate=False)
|
|
1863
|
+
if not node.id and node.state == HashableModelState.ABSENT:
|
|
1864
|
+
self.delete(name=name)
|
|
1865
|
+
|
|
1615
1866
|
def _generate_weight_generics(self) -> None:
|
|
1616
1867
|
"""Generate order_weight for all generic schemas."""
|
|
1617
1868
|
for name in self.generic_names:
|
|
1618
1869
|
node = self.get(name=name, duplicate=False)
|
|
1870
|
+
if node.namespace == "Template":
|
|
1871
|
+
continue
|
|
1619
1872
|
|
|
1620
1873
|
items_to_update = [item for item in node.attributes + node.relationships if not item.order_weight]
|
|
1621
1874
|
|
|
@@ -1679,10 +1932,36 @@ class SchemaBranch:
|
|
|
1679
1932
|
|
|
1680
1933
|
self.set(name=name, schema=template)
|
|
1681
1934
|
|
|
1935
|
+
def _generate_generics_templates_weight(self) -> None:
|
|
1936
|
+
"""Generate order_weight for generic templates.
|
|
1937
|
+
|
|
1938
|
+
The order of the fields for the template must respect the order of the node.
|
|
1939
|
+
"""
|
|
1940
|
+
for name in self.generic_names:
|
|
1941
|
+
generic_node = self.get(name=name, duplicate=False)
|
|
1942
|
+
try:
|
|
1943
|
+
template = self.get(name=self._get_object_template_kind(node_kind=generic_node.kind), duplicate=True)
|
|
1944
|
+
except SchemaNotFoundError:
|
|
1945
|
+
continue
|
|
1946
|
+
|
|
1947
|
+
generic_node_weights = {
|
|
1948
|
+
item.name: item.order_weight
|
|
1949
|
+
for item in generic_node.attributes + generic_node.relationships
|
|
1950
|
+
if item.order_weight is not None
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
for item in template.attributes + template.relationships:
|
|
1954
|
+
if item.order_weight:
|
|
1955
|
+
continue
|
|
1956
|
+
item.order_weight = generic_node_weights[item.name] if item.name in generic_node_weights else None
|
|
1957
|
+
|
|
1958
|
+
self.set(name=template.kind, schema=template)
|
|
1959
|
+
|
|
1682
1960
|
def generate_weight(self) -> None:
|
|
1683
1961
|
self._generate_weight_generics()
|
|
1684
1962
|
self._generate_weight_nodes_profiles()
|
|
1685
1963
|
self._generate_weight_templates()
|
|
1964
|
+
self._generate_generics_templates_weight()
|
|
1686
1965
|
|
|
1687
1966
|
def cleanup_inherited_elements(self) -> None:
|
|
1688
1967
|
for name in self.node_names:
|
|
@@ -2008,7 +2287,7 @@ class SchemaBranch:
|
|
|
2008
2287
|
)
|
|
2009
2288
|
|
|
2010
2289
|
for node_attr in node.attributes:
|
|
2011
|
-
if
|
|
2290
|
+
if not node_attr.support_profiles:
|
|
2012
2291
|
continue
|
|
2013
2292
|
attr_schema_class = get_attribute_schema_class_for_kind(kind=node_attr.kind)
|
|
2014
2293
|
attr = attr_schema_class(
|
|
@@ -2313,3 +2592,16 @@ class SchemaBranch:
|
|
|
2313
2592
|
updated_used_by_node = set(chain(template_schema_kinds, set(core_node_schema.used_by)))
|
|
2314
2593
|
core_node_schema.used_by = sorted(updated_used_by_node)
|
|
2315
2594
|
self.set(name=InfrahubKind.NODE, schema=core_node_schema)
|
|
2595
|
+
|
|
2596
|
+
|
|
2597
|
+
def _format_display_label_component(component: str) -> str:
|
|
2598
|
+
"""Return correct format for display_label.
|
|
2599
|
+
|
|
2600
|
+
Previously both the format of 'name' and 'name__value' was
|
|
2601
|
+
supported this function ensures that the proper 'name__value'
|
|
2602
|
+
format is used
|
|
2603
|
+
"""
|
|
2604
|
+
if "__" in component:
|
|
2605
|
+
return component
|
|
2606
|
+
|
|
2607
|
+
return f"{component}__value"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from infrahub.core.schema import SchemaAttributePath
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class TemplateLabel:
|
|
14
|
+
template: str
|
|
15
|
+
attributes: set[str] = field(default_factory=set)
|
|
16
|
+
relationships: set[str] = field(default_factory=set)
|
|
17
|
+
filter_key: str = "ids"
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def fields(self) -> list[str]:
|
|
21
|
+
return sorted(list(self.attributes) + list(self.relationships))
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def has_related_components(self) -> bool:
|
|
25
|
+
"""Indicate if the associated template use variables from relationships"""
|
|
26
|
+
return len(self.relationships) > 0
|
|
27
|
+
|
|
28
|
+
def get_hash(self) -> str:
|
|
29
|
+
return hashlib.md5(self.template.encode(), usedforsecurity=False).hexdigest()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class RelationshipIdentifier:
|
|
34
|
+
kind: str
|
|
35
|
+
filter_key: str
|
|
36
|
+
template: str
|
|
37
|
+
|
|
38
|
+
def __hash__(self) -> int:
|
|
39
|
+
return hash(f"{self.kind}::{self.filter_key}::{self.template}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class RelationshipTriggers:
|
|
44
|
+
attributes: dict[str, set[RelationshipIdentifier]] = field(default_factory=dict)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DisplayLabels:
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
template_based_display_labels: dict[str, TemplateLabel] | None = None,
|
|
51
|
+
template_relationship_triggers: dict[str, RelationshipTriggers] | None = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
self._template_based_display_labels: dict[str, TemplateLabel] = template_based_display_labels or {}
|
|
54
|
+
self._template_relationship_triggers: dict[str, RelationshipTriggers] = template_relationship_triggers or {}
|
|
55
|
+
|
|
56
|
+
def duplicate(self) -> DisplayLabels:
|
|
57
|
+
"""Clone the current object."""
|
|
58
|
+
return self.__class__(
|
|
59
|
+
template_based_display_labels=deepcopy(self._template_based_display_labels),
|
|
60
|
+
template_relationship_triggers=deepcopy(self._template_relationship_triggers),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def register_attribute_based_display_label(self, kind: str, attribute_name: str) -> None:
|
|
64
|
+
"""Register nodes where the display label consists of a single defined attribute name."""
|
|
65
|
+
self._template_based_display_labels[kind] = TemplateLabel(
|
|
66
|
+
template=f"{{{{ {attribute_name}__value }}}}", attributes={attribute_name}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def register_template_schema_path(self, kind: str, schema_path: SchemaAttributePath, template: str) -> None:
|
|
70
|
+
"""Register Jinja2 template based display labels using the schema path of each impacted variable in the node."""
|
|
71
|
+
|
|
72
|
+
if kind not in self._template_based_display_labels:
|
|
73
|
+
self._template_based_display_labels[kind] = TemplateLabel(template=template)
|
|
74
|
+
|
|
75
|
+
if schema_path.is_type_attribute:
|
|
76
|
+
self._template_based_display_labels[kind].attributes.add(schema_path.active_attribute_schema.name)
|
|
77
|
+
elif schema_path.is_type_relationship and schema_path.related_schema:
|
|
78
|
+
self._template_based_display_labels[kind].relationships.add(schema_path.active_relationship_schema.name)
|
|
79
|
+
if schema_path.related_schema.kind not in self._template_relationship_triggers:
|
|
80
|
+
self._template_relationship_triggers[schema_path.related_schema.kind] = RelationshipTriggers()
|
|
81
|
+
if (
|
|
82
|
+
schema_path.active_attribute_schema.name
|
|
83
|
+
not in self._template_relationship_triggers[schema_path.related_schema.kind].attributes
|
|
84
|
+
):
|
|
85
|
+
self._template_relationship_triggers[schema_path.related_schema.kind].attributes[
|
|
86
|
+
schema_path.active_attribute_schema.name
|
|
87
|
+
] = set()
|
|
88
|
+
self._template_relationship_triggers[schema_path.related_schema.kind].attributes[
|
|
89
|
+
schema_path.active_attribute_schema.name
|
|
90
|
+
].add(
|
|
91
|
+
RelationshipIdentifier(
|
|
92
|
+
kind=kind, filter_key=f"{schema_path.active_relationship_schema.name}__ids", template=template
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def targets_node(self, kind: str) -> bool:
|
|
97
|
+
"""Indicates if there is a display_label defined for the targeted node"""
|
|
98
|
+
return kind in self._template_based_display_labels
|
|
99
|
+
|
|
100
|
+
def get_template_node(self, kind: str) -> TemplateLabel:
|
|
101
|
+
"""Return node kinds together with their template definitions."""
|
|
102
|
+
return self._template_based_display_labels[kind]
|
|
103
|
+
|
|
104
|
+
def get_template_nodes(self) -> dict[str, TemplateLabel]:
|
|
105
|
+
"""Return node kinds together with their template definitions."""
|
|
106
|
+
return self._template_based_display_labels
|
|
107
|
+
|
|
108
|
+
def get_related_trigger_nodes(self) -> dict[str, RelationshipTriggers]:
|
|
109
|
+
"""Return node kinds that other nodes use within their templates for display_labels."""
|
|
110
|
+
return self._template_relationship_triggers
|
|
111
|
+
|
|
112
|
+
def get_related_template(self, related_kind: str, target_kind: str) -> TemplateLabel:
|
|
113
|
+
relationship_trigger = self._template_relationship_triggers[related_kind]
|
|
114
|
+
for applicable_kinds in relationship_trigger.attributes.values():
|
|
115
|
+
for relationship_identifier in applicable_kinds:
|
|
116
|
+
if target_kind == relationship_identifier.kind:
|
|
117
|
+
template_label = self.get_template_node(kind=target_kind)
|
|
118
|
+
template_label.filter_key = relationship_identifier.filter_key
|
|
119
|
+
return template_label
|
|
120
|
+
|
|
121
|
+
raise ValueError(
|
|
122
|
+
f"Unable to find registered template for {target_kind} registered on related node {related_kind}"
|
|
123
|
+
)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from infrahub.core.schema import SchemaAttributePath
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class HFIDDefinition:
|
|
14
|
+
hfid: list[str]
|
|
15
|
+
attributes: set[str] = field(default_factory=set)
|
|
16
|
+
relationships: set[str] = field(default_factory=set)
|
|
17
|
+
filter_key: str = "ids"
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def fields(self) -> list[str]:
|
|
21
|
+
return sorted(list(self.attributes) + list(self.relationships))
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def has_related_components(self) -> bool:
|
|
25
|
+
"""Indicate if the associated template use variables from relationships"""
|
|
26
|
+
return len(self.relationships) > 0
|
|
27
|
+
|
|
28
|
+
def get_hash(self) -> str:
|
|
29
|
+
return hashlib.md5("::".join(self.hfid).encode(), usedforsecurity=False).hexdigest()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class RelationshipIdentifier:
|
|
34
|
+
kind: str
|
|
35
|
+
hfid: list[str]
|
|
36
|
+
filter_key: str
|
|
37
|
+
|
|
38
|
+
def __hash__(self) -> int:
|
|
39
|
+
return hash(f"{self.kind}::{'::'.join(self.hfid)}::{self.filter_key}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class RelationshipTriggers:
|
|
44
|
+
attributes: dict[str, set[RelationshipIdentifier]] = field(default_factory=dict)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class HFIDs:
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
node_level_hfids: dict[str, HFIDDefinition] | None = None,
|
|
51
|
+
relationship_triggers: dict[str, RelationshipTriggers] | None = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
self._node_level_hfids: dict[str, HFIDDefinition] = node_level_hfids or {}
|
|
54
|
+
self._relationship_triggers: dict[str, RelationshipTriggers] = relationship_triggers or {}
|
|
55
|
+
|
|
56
|
+
def duplicate(self) -> HFIDs:
|
|
57
|
+
return self.__class__(
|
|
58
|
+
node_level_hfids=deepcopy(self._node_level_hfids),
|
|
59
|
+
relationship_triggers=deepcopy(self._relationship_triggers),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def register_hfid_schema_path(self, kind: str, schema_path: SchemaAttributePath, hfid: list[str]) -> None:
|
|
63
|
+
"""Register HFID using the schema path of each impacted schema path in use."""
|
|
64
|
+
if kind not in self._node_level_hfids:
|
|
65
|
+
self._node_level_hfids[kind] = HFIDDefinition(hfid=hfid)
|
|
66
|
+
if schema_path.is_type_attribute:
|
|
67
|
+
self._node_level_hfids[kind].attributes.add(schema_path.active_attribute_schema.name)
|
|
68
|
+
elif schema_path.is_type_relationship and schema_path.related_schema:
|
|
69
|
+
self._node_level_hfids[kind].relationships.add(schema_path.active_relationship_schema.name)
|
|
70
|
+
if schema_path.related_schema.kind not in self._relationship_triggers:
|
|
71
|
+
self._relationship_triggers[schema_path.related_schema.kind] = RelationshipTriggers()
|
|
72
|
+
if (
|
|
73
|
+
schema_path.active_attribute_schema.name
|
|
74
|
+
not in self._relationship_triggers[schema_path.related_schema.kind].attributes
|
|
75
|
+
):
|
|
76
|
+
self._relationship_triggers[schema_path.related_schema.kind].attributes[
|
|
77
|
+
schema_path.active_attribute_schema.name
|
|
78
|
+
] = set()
|
|
79
|
+
self._relationship_triggers[schema_path.related_schema.kind].attributes[
|
|
80
|
+
schema_path.active_attribute_schema.name
|
|
81
|
+
].add(
|
|
82
|
+
RelationshipIdentifier(
|
|
83
|
+
kind=kind, filter_key=f"{schema_path.active_relationship_schema.name}__ids", hfid=hfid
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def targets_node(self, kind: str) -> bool:
|
|
88
|
+
"""Indicates if there is a human_friendly_id defined for the targeted node"""
|
|
89
|
+
return kind in self._node_level_hfids
|
|
90
|
+
|
|
91
|
+
def get_node_definition(self, kind: str) -> HFIDDefinition:
|
|
92
|
+
"""Return node kinds together with their template definitions."""
|
|
93
|
+
return self._node_level_hfids[kind]
|
|
94
|
+
|
|
95
|
+
def get_template_nodes(self) -> dict[str, HFIDDefinition]:
|
|
96
|
+
"""Return node kinds together with their template definitions."""
|
|
97
|
+
return self._node_level_hfids
|
|
98
|
+
|
|
99
|
+
def get_related_trigger_nodes(self) -> dict[str, RelationshipTriggers]:
|
|
100
|
+
"""Return node kinds that other nodes use within their templates for display_labels."""
|
|
101
|
+
return self._relationship_triggers
|
|
102
|
+
|
|
103
|
+
def get_related_definition(self, related_kind: str, target_kind: str) -> HFIDDefinition:
|
|
104
|
+
relationship_trigger = self._relationship_triggers[related_kind]
|
|
105
|
+
for applicable_kinds in relationship_trigger.attributes.values():
|
|
106
|
+
for relationship_identifier in applicable_kinds:
|
|
107
|
+
if target_kind == relationship_identifier.kind:
|
|
108
|
+
template_label = self.get_node_definition(kind=target_kind)
|
|
109
|
+
template_label.filter_key = relationship_identifier.filter_key
|
|
110
|
+
return template_label
|
|
111
|
+
|
|
112
|
+
raise ValueError(
|
|
113
|
+
f"Unable to find registered template for {target_kind} registered on related node {related_kind}"
|
|
114
|
+
)
|
|
@@ -49,7 +49,7 @@ class AggregatedConstraintChecker:
|
|
|
49
49
|
node_display_label = None
|
|
50
50
|
display_label = None
|
|
51
51
|
if node:
|
|
52
|
-
node_display_label = await node.
|
|
52
|
+
node_display_label = await node.get_display_label(db=self.db)
|
|
53
53
|
if node_display_label:
|
|
54
54
|
if request.node_schema.display_labels and node:
|
|
55
55
|
display_label = f"Node {node_display_label} ({node.get_kind()}: {path.node_id})"
|