infrahub-server 1.2.11__py3-none-any.whl → 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infrahub/actions/constants.py +130 -0
- infrahub/actions/gather.py +114 -0
- infrahub/actions/models.py +243 -0
- infrahub/actions/parsers.py +104 -0
- infrahub/actions/schema.py +393 -0
- infrahub/actions/tasks.py +119 -0
- infrahub/actions/triggers.py +21 -0
- infrahub/branch/__init__.py +0 -0
- infrahub/branch/tasks.py +29 -0
- infrahub/branch/triggers.py +22 -0
- infrahub/cli/db.py +3 -4
- infrahub/computed_attribute/gather.py +3 -1
- infrahub/computed_attribute/tasks.py +23 -29
- infrahub/core/account.py +24 -47
- infrahub/core/attribute.py +13 -15
- infrahub/core/constants/__init__.py +10 -0
- infrahub/core/constants/database.py +1 -0
- infrahub/core/constants/infrahubkind.py +9 -0
- infrahub/core/constraint/node/runner.py +3 -1
- infrahub/core/convert_object_type/__init__.py +0 -0
- infrahub/core/convert_object_type/conversion.py +124 -0
- infrahub/core/convert_object_type/schema_mapping.py +56 -0
- infrahub/core/diff/coordinator.py +8 -1
- infrahub/core/diff/query/all_conflicts.py +1 -5
- infrahub/core/diff/query/artifact.py +10 -20
- infrahub/core/diff/query/delete_query.py +8 -4
- infrahub/core/diff/query/diff_get.py +3 -6
- infrahub/core/diff/query/field_specifiers.py +1 -1
- infrahub/core/diff/query/field_summary.py +2 -4
- infrahub/core/diff/query/merge.py +72 -125
- infrahub/core/diff/query/save.py +83 -68
- infrahub/core/diff/query/summary_counts_enricher.py +34 -54
- infrahub/core/diff/query/time_range_query.py +0 -1
- infrahub/core/diff/repository/repository.py +4 -0
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/manager.py +14 -11
- infrahub/core/migrations/graph/__init__.py +6 -0
- infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
- infrahub/core/migrations/graph/m012_convert_account_generic.py +1 -1
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -6
- infrahub/core/migrations/graph/m015_diff_format_update.py +1 -2
- infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -2
- infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
- infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
- infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
- infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +2 -2
- infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
- infrahub/core/migrations/graph/m028_delete_diffs.py +1 -2
- infrahub/core/migrations/graph/m029_duplicates_cleanup.py +662 -0
- infrahub/core/migrations/graph/m030_illegal_edges.py +82 -0
- infrahub/core/migrations/query/attribute_add.py +14 -11
- infrahub/core/migrations/query/attribute_rename.py +6 -11
- infrahub/core/migrations/query/delete_element_in_schema.py +19 -17
- infrahub/core/migrations/query/node_duplicate.py +19 -21
- infrahub/core/migrations/query/relationship_duplicate.py +19 -18
- infrahub/core/migrations/schema/node_attribute_remove.py +4 -8
- infrahub/core/migrations/schema/node_remove.py +19 -20
- infrahub/core/models.py +29 -2
- infrahub/core/node/__init__.py +131 -28
- infrahub/core/node/base.py +1 -1
- infrahub/core/node/create.py +211 -0
- infrahub/core/node/resource_manager/number_pool.py +31 -5
- infrahub/core/node/standard.py +6 -1
- infrahub/core/path.py +15 -1
- infrahub/core/protocols.py +57 -0
- infrahub/core/protocols_base.py +3 -0
- infrahub/core/query/__init__.py +2 -2
- infrahub/core/query/delete.py +3 -3
- infrahub/core/query/diff.py +19 -32
- infrahub/core/query/ipam.py +10 -20
- infrahub/core/query/node.py +29 -47
- infrahub/core/query/relationship.py +55 -34
- infrahub/core/query/resource_manager.py +1 -2
- infrahub/core/query/standard_node.py +19 -5
- infrahub/core/query/subquery.py +2 -4
- infrahub/core/relationship/constraints/count.py +10 -9
- infrahub/core/relationship/constraints/interface.py +2 -1
- infrahub/core/relationship/constraints/peer_kind.py +2 -1
- infrahub/core/relationship/constraints/peer_parent.py +56 -0
- infrahub/core/relationship/constraints/peer_relatives.py +72 -0
- infrahub/core/relationship/constraints/profiles_kind.py +1 -1
- infrahub/core/relationship/model.py +4 -1
- infrahub/core/schema/__init__.py +2 -1
- infrahub/core/schema/attribute_parameters.py +160 -0
- infrahub/core/schema/attribute_schema.py +130 -7
- infrahub/core/schema/basenode_schema.py +27 -3
- infrahub/core/schema/definitions/core/__init__.py +29 -1
- infrahub/core/schema/definitions/core/group.py +45 -0
- infrahub/core/schema/definitions/core/resource_pool.py +9 -0
- infrahub/core/schema/definitions/internal.py +43 -5
- infrahub/core/schema/generated/attribute_schema.py +16 -3
- infrahub/core/schema/generated/relationship_schema.py +11 -1
- infrahub/core/schema/manager.py +7 -2
- infrahub/core/schema/schema_branch.py +109 -12
- infrahub/core/validators/__init__.py +15 -2
- infrahub/core/validators/attribute/choices.py +1 -3
- infrahub/core/validators/attribute/enum.py +1 -3
- infrahub/core/validators/attribute/kind.py +1 -3
- infrahub/core/validators/attribute/length.py +13 -7
- infrahub/core/validators/attribute/min_max.py +118 -0
- infrahub/core/validators/attribute/number_pool.py +106 -0
- infrahub/core/validators/attribute/optional.py +1 -4
- infrahub/core/validators/attribute/regex.py +5 -6
- infrahub/core/validators/attribute/unique.py +1 -3
- infrahub/core/validators/determiner.py +18 -2
- infrahub/core/validators/enum.py +12 -0
- infrahub/core/validators/node/hierarchy.py +3 -6
- infrahub/core/validators/query.py +1 -3
- infrahub/core/validators/relationship/count.py +6 -12
- infrahub/core/validators/relationship/optional.py +2 -4
- infrahub/core/validators/relationship/peer.py +177 -12
- infrahub/core/validators/tasks.py +1 -1
- infrahub/core/validators/uniqueness/query.py +5 -9
- infrahub/database/__init__.py +12 -4
- infrahub/database/validation.py +100 -0
- infrahub/dependencies/builder/constraint/grouped/node_runner.py +4 -0
- infrahub/dependencies/builder/constraint/relationship_manager/peer_parent.py +8 -0
- infrahub/dependencies/builder/constraint/relationship_manager/peer_relatives.py +8 -0
- infrahub/dependencies/builder/constraint/schema/aggregated.py +2 -0
- infrahub/dependencies/builder/constraint/schema/relationship_peer.py +8 -0
- infrahub/dependencies/builder/diff/deserializer.py +1 -1
- infrahub/dependencies/registry.py +4 -0
- infrahub/events/group_action.py +1 -0
- infrahub/events/models.py +1 -1
- infrahub/git/base.py +5 -3
- infrahub/git/integrator.py +96 -5
- infrahub/git/tasks.py +1 -0
- infrahub/graphql/analyzer.py +139 -18
- infrahub/graphql/manager.py +4 -0
- infrahub/graphql/mutations/action.py +164 -0
- infrahub/graphql/mutations/convert_object_type.py +71 -0
- infrahub/graphql/mutations/main.py +25 -176
- infrahub/graphql/mutations/proposed_change.py +20 -17
- infrahub/graphql/mutations/relationship.py +32 -0
- infrahub/graphql/mutations/resource_manager.py +63 -7
- infrahub/graphql/queries/convert_object_type_mapping.py +34 -0
- infrahub/graphql/queries/resource_manager.py +7 -1
- infrahub/graphql/resolvers/many_relationship.py +1 -1
- infrahub/graphql/resolvers/resolver.py +2 -2
- infrahub/graphql/resolvers/single_relationship.py +1 -1
- infrahub/graphql/schema.py +6 -0
- infrahub/menu/menu.py +34 -2
- infrahub/message_bus/messages/__init__.py +0 -10
- infrahub/message_bus/operations/__init__.py +0 -8
- infrahub/message_bus/operations/refresh/registry.py +4 -7
- infrahub/patch/queries/delete_duplicated_edges.py +45 -39
- infrahub/pools/models.py +14 -0
- infrahub/pools/number.py +5 -3
- infrahub/pools/registration.py +22 -0
- infrahub/pools/tasks.py +126 -0
- infrahub/prefect_server/models.py +1 -19
- infrahub/proposed_change/models.py +68 -3
- infrahub/proposed_change/tasks.py +911 -34
- infrahub/schema/__init__.py +0 -0
- infrahub/schema/tasks.py +27 -0
- infrahub/schema/triggers.py +23 -0
- infrahub/task_manager/models.py +10 -6
- infrahub/trigger/catalogue.py +6 -0
- infrahub/trigger/models.py +23 -6
- infrahub/trigger/setup.py +26 -2
- infrahub/trigger/tasks.py +4 -2
- infrahub/types.py +6 -0
- infrahub/webhook/tasks.py +6 -9
- infrahub/workflows/catalogue.py +103 -1
- infrahub_sdk/client.py +43 -10
- infrahub_sdk/ctl/generator.py +4 -4
- infrahub_sdk/ctl/repository.py +1 -1
- infrahub_sdk/node/__init__.py +39 -0
- infrahub_sdk/node/attribute.py +122 -0
- infrahub_sdk/node/constants.py +21 -0
- infrahub_sdk/{node.py → node/node.py} +158 -803
- infrahub_sdk/node/parsers.py +15 -0
- infrahub_sdk/node/property.py +24 -0
- infrahub_sdk/node/related_node.py +266 -0
- infrahub_sdk/node/relationship.py +302 -0
- infrahub_sdk/protocols.py +112 -0
- infrahub_sdk/protocols_base.py +34 -2
- infrahub_sdk/pytest_plugin/items/python_transform.py +2 -1
- infrahub_sdk/query_groups.py +17 -5
- infrahub_sdk/schema/main.py +1 -0
- infrahub_sdk/schema/repository.py +16 -0
- infrahub_sdk/spec/object.py +1 -1
- infrahub_sdk/store.py +1 -1
- infrahub_sdk/testing/schemas/car_person.py +1 -0
- infrahub_sdk/utils.py +7 -20
- infrahub_sdk/yaml.py +6 -5
- {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/METADATA +5 -5
- {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/RECORD +197 -168
- {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/WHEEL +1 -1
- infrahub_testcontainers/container.py +239 -65
- infrahub_testcontainers/docker-compose-cluster.test.yml +321 -0
- infrahub_testcontainers/docker-compose.test.yml +2 -1
- infrahub_testcontainers/helpers.py +23 -3
- infrahub_testcontainers/plugin.py +9 -0
- infrahub/message_bus/messages/check_generator_run.py +0 -26
- infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
- infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
- infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
- infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
- infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
- infrahub/message_bus/operations/check/__init__.py +0 -3
- infrahub/message_bus/operations/check/generator.py +0 -156
- infrahub/message_bus/operations/finalize/__init__.py +0 -3
- infrahub/message_bus/operations/finalize/validator.py +0 -133
- infrahub/message_bus/operations/requests/__init__.py +0 -9
- infrahub/message_bus/operations/requests/generator_definition.py +0 -140
- infrahub/message_bus/operations/requests/proposed_change.py +0 -629
- infrahub/patch/queries/consolidate_duplicated_nodes.py +0 -109
- /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
- {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/entry_points.txt +0 -0
infrahub_sdk/ctl/repository.py
CHANGED
|
@@ -45,7 +45,7 @@ def get_repository_config(repo_config_file: Path) -> InfrahubRepositoryConfig:
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def load_repository_config_file(repo_config_file: Path) -> dict:
|
|
48
|
-
yaml_data = read_file(
|
|
48
|
+
yaml_data = read_file(file_path=repo_config_file)
|
|
49
49
|
|
|
50
50
|
try:
|
|
51
51
|
data = yaml.safe_load(yaml_data)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .constants import (
|
|
4
|
+
ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE,
|
|
5
|
+
ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE,
|
|
6
|
+
ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE,
|
|
7
|
+
HFID_STR_SEPARATOR,
|
|
8
|
+
IP_TYPES,
|
|
9
|
+
PROPERTIES_FLAG,
|
|
10
|
+
PROPERTIES_OBJECT,
|
|
11
|
+
SAFE_VALUE,
|
|
12
|
+
)
|
|
13
|
+
from .node import InfrahubNode, InfrahubNodeBase, InfrahubNodeSync
|
|
14
|
+
from .parsers import parse_human_friendly_id
|
|
15
|
+
from .property import NodeProperty
|
|
16
|
+
from .related_node import RelatedNode, RelatedNodeBase, RelatedNodeSync
|
|
17
|
+
from .relationship import RelationshipManager, RelationshipManagerBase, RelationshipManagerSync
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE",
|
|
21
|
+
"ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE",
|
|
22
|
+
"ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE",
|
|
23
|
+
"HFID_STR_SEPARATOR",
|
|
24
|
+
"IP_TYPES",
|
|
25
|
+
"PROPERTIES_FLAG",
|
|
26
|
+
"PROPERTIES_OBJECT",
|
|
27
|
+
"SAFE_VALUE",
|
|
28
|
+
"InfrahubNode",
|
|
29
|
+
"InfrahubNodeBase",
|
|
30
|
+
"InfrahubNodeSync",
|
|
31
|
+
"NodeProperty",
|
|
32
|
+
"RelatedNode",
|
|
33
|
+
"RelatedNodeBase",
|
|
34
|
+
"RelatedNodeSync",
|
|
35
|
+
"RelationshipManager",
|
|
36
|
+
"RelationshipManagerBase",
|
|
37
|
+
"RelationshipManagerSync",
|
|
38
|
+
"parse_human_friendly_id",
|
|
39
|
+
]
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ipaddress
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Callable, get_args
|
|
5
|
+
|
|
6
|
+
from ..protocols_base import CoreNodeBase
|
|
7
|
+
from ..uuidt import UUIDT
|
|
8
|
+
from .constants import IP_TYPES, PROPERTIES_FLAG, PROPERTIES_OBJECT, SAFE_VALUE
|
|
9
|
+
from .property import NodeProperty
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ..schema import AttributeSchemaAPI
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Attribute:
|
|
16
|
+
"""Represents an attribute of a Node, including its schema, value, and properties."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, name: str, schema: AttributeSchemaAPI, data: Any | dict):
|
|
19
|
+
"""
|
|
20
|
+
Args:
|
|
21
|
+
name (str): The name of the attribute.
|
|
22
|
+
schema (AttributeSchema): The schema defining the attribute.
|
|
23
|
+
data (Union[Any, dict]): The data for the attribute, either in raw form or as a dictionary.
|
|
24
|
+
"""
|
|
25
|
+
self.name = name
|
|
26
|
+
self._schema = schema
|
|
27
|
+
|
|
28
|
+
if not isinstance(data, dict) or "value" not in data.keys():
|
|
29
|
+
data = {"value": data}
|
|
30
|
+
|
|
31
|
+
self._properties_flag = PROPERTIES_FLAG
|
|
32
|
+
self._properties_object = PROPERTIES_OBJECT
|
|
33
|
+
self._properties = self._properties_flag + self._properties_object
|
|
34
|
+
|
|
35
|
+
self._read_only = ["updated_at", "is_inherited"]
|
|
36
|
+
|
|
37
|
+
self.id: str | None = data.get("id", None)
|
|
38
|
+
|
|
39
|
+
self._value: Any | None = data.get("value", None)
|
|
40
|
+
self.value_has_been_mutated = False
|
|
41
|
+
self.is_default: bool | None = data.get("is_default", None)
|
|
42
|
+
self.is_from_profile: bool | None = data.get("is_from_profile", None)
|
|
43
|
+
|
|
44
|
+
if self._value:
|
|
45
|
+
value_mapper: dict[str, Callable] = {
|
|
46
|
+
"IPHost": ipaddress.ip_interface,
|
|
47
|
+
"IPNetwork": ipaddress.ip_network,
|
|
48
|
+
}
|
|
49
|
+
mapper = value_mapper.get(schema.kind, lambda value: value)
|
|
50
|
+
self._value = mapper(data.get("value"))
|
|
51
|
+
|
|
52
|
+
self.is_inherited: bool | None = data.get("is_inherited", None)
|
|
53
|
+
self.updated_at: str | None = data.get("updated_at", None)
|
|
54
|
+
|
|
55
|
+
self.is_visible: bool | None = data.get("is_visible", None)
|
|
56
|
+
self.is_protected: bool | None = data.get("is_protected", None)
|
|
57
|
+
|
|
58
|
+
self.source: NodeProperty | None = None
|
|
59
|
+
self.owner: NodeProperty | None = None
|
|
60
|
+
|
|
61
|
+
for prop_name in self._properties_object:
|
|
62
|
+
if data.get(prop_name):
|
|
63
|
+
setattr(self, prop_name, NodeProperty(data=data.get(prop_name))) # type: ignore[arg-type]
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def value(self) -> Any:
|
|
67
|
+
return self._value
|
|
68
|
+
|
|
69
|
+
@value.setter
|
|
70
|
+
def value(self, value: Any) -> None:
|
|
71
|
+
self._value = value
|
|
72
|
+
self.value_has_been_mutated = True
|
|
73
|
+
|
|
74
|
+
def _generate_input_data(self) -> dict | None:
|
|
75
|
+
data: dict[str, Any] = {}
|
|
76
|
+
variables: dict[str, Any] = {}
|
|
77
|
+
|
|
78
|
+
if self.value is None:
|
|
79
|
+
return data
|
|
80
|
+
|
|
81
|
+
if isinstance(self.value, str):
|
|
82
|
+
if SAFE_VALUE.match(self.value):
|
|
83
|
+
data["value"] = self.value
|
|
84
|
+
else:
|
|
85
|
+
var_name = f"value_{UUIDT.new().hex}"
|
|
86
|
+
variables[var_name] = self.value
|
|
87
|
+
data["value"] = f"${var_name}"
|
|
88
|
+
elif isinstance(self.value, get_args(IP_TYPES)):
|
|
89
|
+
data["value"] = self.value.with_prefixlen
|
|
90
|
+
elif isinstance(self.value, CoreNodeBase) and self.value.is_resource_pool():
|
|
91
|
+
data["from_pool"] = {"id": self.value.id}
|
|
92
|
+
else:
|
|
93
|
+
data["value"] = self.value
|
|
94
|
+
|
|
95
|
+
for prop_name in self._properties_flag:
|
|
96
|
+
if getattr(self, prop_name) is not None:
|
|
97
|
+
data[prop_name] = getattr(self, prop_name)
|
|
98
|
+
|
|
99
|
+
for prop_name in self._properties_object:
|
|
100
|
+
if getattr(self, prop_name) is not None:
|
|
101
|
+
data[prop_name] = getattr(self, prop_name)._generate_input_data()
|
|
102
|
+
|
|
103
|
+
return {"data": data, "variables": variables}
|
|
104
|
+
|
|
105
|
+
def _generate_query_data(self, property: bool = False) -> dict | None:
|
|
106
|
+
data: dict[str, Any] = {"value": None}
|
|
107
|
+
|
|
108
|
+
if property:
|
|
109
|
+
data.update({"is_default": None, "is_from_profile": None})
|
|
110
|
+
|
|
111
|
+
for prop_name in self._properties_flag:
|
|
112
|
+
data[prop_name] = None
|
|
113
|
+
for prop_name in self._properties_object:
|
|
114
|
+
data[prop_name] = {"id": None, "display_label": None, "__typename": None}
|
|
115
|
+
|
|
116
|
+
return data
|
|
117
|
+
|
|
118
|
+
def _generate_mutation_query(self) -> dict[str, Any]:
|
|
119
|
+
if isinstance(self.value, CoreNodeBase) and self.value.is_resource_pool():
|
|
120
|
+
# If it points to a pool, ask for the value of the pool allocated resource
|
|
121
|
+
return {self.name: {"value": None}}
|
|
122
|
+
return {}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import ipaddress
|
|
2
|
+
import re
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
PROPERTIES_FLAG = ["is_visible", "is_protected"]
|
|
6
|
+
PROPERTIES_OBJECT = ["source", "owner"]
|
|
7
|
+
SAFE_VALUE = re.compile(r"(^[\. /:a-zA-Z0-9_-]+$)|(^$)")
|
|
8
|
+
|
|
9
|
+
IP_TYPES = Union[ipaddress.IPv4Interface, ipaddress.IPv6Interface, ipaddress.IPv4Network, ipaddress.IPv6Network]
|
|
10
|
+
|
|
11
|
+
ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE = (
|
|
12
|
+
"calling artifact_fetch is only supported for nodes that are Artifact Definition target"
|
|
13
|
+
)
|
|
14
|
+
ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE = (
|
|
15
|
+
"calling artifact_generate is only supported for nodes that are Artifact Definition targets"
|
|
16
|
+
)
|
|
17
|
+
ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE = (
|
|
18
|
+
"calling generate is only supported for CoreArtifactDefinition nodes"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
HFID_STR_SEPARATOR = "__"
|