iris-pex-embedded-python 3.7.2b2__py3-none-any.whl → 4.0.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.
- iop/__init__.py +276 -18
- iop/__main__.py +3 -3
- iop/cli/__init__.py +1 -0
- iop/cli/formatting.py +45 -0
- iop/{_cli.py → cli/main.py} +238 -216
- iop/cli/parser.py +165 -0
- iop/cli/types.py +73 -0
- iop/cls/IOP/Common.cls +11 -2
- iop/cls/IOP/PrivateSession/Duplex.cls +1 -1
- iop/cls/IOP/Service/Remote/Rest/v1.cls +152 -5
- iop/cls/IOP/Utils.cls +814 -7
- iop/cls/IOP/Wrapper.cls +1 -1
- iop/components/__init__.py +16 -0
- iop/{_async_request.py → components/async_request.py} +7 -7
- iop/components/business_host.py +363 -0
- iop/components/business_operation.py +84 -0
- iop/{_business_process.py → components/business_process.py} +145 -125
- iop/components/business_service.py +106 -0
- iop/{_common.py → components/common.py} +160 -164
- iop/{_debugpy.py → components/debugpy.py} +11 -11
- iop/{_generator_request.py → components/generator_request.py} +4 -4
- iop/{_inbound_adapter.py → components/inbound_adapter.py} +10 -18
- iop/{_log_manager.py → components/log_manager.py} +1 -1
- iop/{_outbound_adapter.py → components/outbound_adapter.py} +1 -1
- iop/components/polling_business_service.py +48 -0
- iop/{_private_session_duplex.py → components/private_session_duplex.py} +7 -9
- iop/{_private_session_process.py → components/private_session_process.py} +2 -2
- iop/{_settings.py → components/settings.py} +23 -3
- iop/messages/__init__.py +7 -0
- iop/messages/base.py +61 -0
- iop/{_decorators.py → messages/decorators.py} +8 -2
- iop/messages/dispatch.py +351 -0
- iop/{_persistent_message.py → messages/persistent.py} +24 -19
- iop/{_serialization.py → messages/serialization.py} +34 -21
- iop/{_message_validator.py → messages/validation.py} +15 -13
- iop/migration/__init__.py +1 -0
- iop/migration/io.py +61 -0
- iop/migration/manifest.py +534 -0
- iop/migration/plans.py +80 -0
- iop/migration/utils.py +1126 -0
- iop/production/__init__.py +55 -0
- iop/production/actions.py +525 -0
- iop/production/common.py +103 -0
- iop/production/component.py +234 -0
- iop/production/declarations.py +190 -0
- iop/production/declarative.py +214 -0
- iop/production/diff.py +327 -0
- iop/production/import_.py +296 -0
- iop/production/inspection.py +92 -0
- iop/production/model.py +1143 -0
- iop/production/planning.py +678 -0
- iop/production/reconstruction.py +341 -0
- iop/production/rendering.py +610 -0
- iop/production/runtime.py +91 -0
- iop/production/source_inference.py +729 -0
- iop/production/types.py +546 -0
- iop/production/validation.py +452 -0
- iop/runtime/__init__.py +1 -0
- iop/runtime/director.py +415 -0
- iop/runtime/environment.py +59 -0
- iop/{_iris.py → runtime/iris.py} +2 -2
- iop/runtime/local.py +145 -0
- iop/{_director_protocol.py → runtime/protocol.py} +25 -8
- iop/runtime/remote/__init__.py +12 -0
- iop/runtime/remote/client.py +90 -0
- iop/runtime/remote/director.py +283 -0
- iop/runtime/remote/migration.py +57 -0
- iop/runtime/remote/settings.py +69 -0
- iop/runtime/remote/setup.py +72 -0
- {iris_pex_embedded_python-3.7.2b2.dist-info → iris_pex_embedded_python-4.0.0.dist-info}/METADATA +11 -2
- iris_pex_embedded_python-4.0.0.dist-info/RECORD +99 -0
- iris_pex_embedded_python-4.0.0.dist-info/entry_points.txt +2 -0
- iris_pex_embedded_python-4.0.0.dist-info/top_level.txt +1 -0
- grongier/__init__.py +0 -0
- grongier/cls/Grongier/PEX/BusinessOperation.cls +0 -8
- grongier/cls/Grongier/PEX/BusinessProcess.cls +0 -13
- grongier/cls/Grongier/PEX/BusinessService.cls +0 -8
- grongier/cls/Grongier/PEX/Common.cls +0 -10
- grongier/cls/Grongier/PEX/Director.cls +0 -10
- grongier/cls/Grongier/PEX/Duplex/Operation.cls +0 -4
- grongier/cls/Grongier/PEX/Duplex/Process.cls +0 -13
- grongier/cls/Grongier/PEX/Duplex/Service.cls +0 -4
- grongier/cls/Grongier/PEX/InboundAdapter.cls +0 -8
- grongier/cls/Grongier/PEX/Message.cls +0 -13
- grongier/cls/Grongier/PEX/OutboundAdapter.cls +0 -8
- grongier/cls/Grongier/PEX/PickleMessage.cls +0 -13
- grongier/cls/Grongier/PEX/PrivateSession/Duplex.cls +0 -8
- grongier/cls/Grongier/PEX/PrivateSession/Message/Ack.cls +0 -14
- grongier/cls/Grongier/PEX/PrivateSession/Message/Poll.cls +0 -14
- grongier/cls/Grongier/PEX/PrivateSession/Message/Start.cls +0 -14
- grongier/cls/Grongier/PEX/PrivateSession/Message/Stop.cls +0 -14
- grongier/cls/Grongier/PEX/Test.cls +0 -10
- grongier/cls/Grongier/PEX/Utils.cls +0 -10
- grongier/cls/Grongier/Service/WSGI.cls +0 -4
- grongier/pex/__init__.py +0 -75
- grongier/pex/__main__.py +0 -4
- grongier/pex/_business_host.py +0 -1
- grongier/pex/_cli.py +0 -4
- grongier/pex/_common.py +0 -1
- grongier/pex/_director.py +0 -1
- grongier/pex/_utils.py +0 -1
- grongier/pex/wsgi/handlers.py +0 -104
- iop/_business_host.py +0 -322
- iop/_business_operation.py +0 -77
- iop/_business_service.py +0 -67
- iop/_director.py +0 -333
- iop/_dispatch.py +0 -194
- iop/_local.py +0 -106
- iop/_message.py +0 -45
- iop/_polling_business_service.py +0 -6
- iop/_remote.py +0 -473
- iop/_utils.py +0 -912
- iop/cls/IOP/Service/WSGI.cls +0 -310
- iop/wsgi/handlers.py +0 -112
- iris_pex_embedded_python-3.7.2b2.dist-info/RECORD +0 -96
- iris_pex_embedded_python-3.7.2b2.dist-info/entry_points.txt +0 -2
- iris_pex_embedded_python-3.7.2b2.dist-info/top_level.txt +0 -2
- {iris_pex_embedded_python-3.7.2b2.dist-info → iris_pex_embedded_python-4.0.0.dist-info}/WHEEL +0 -0
- {iris_pex_embedded_python-3.7.2b2.dist-info → iris_pex_embedded_python-4.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .component import ComponentRef
|
|
6
|
+
from .import_ import (
|
|
7
|
+
_as_list,
|
|
8
|
+
_is_internal_runtime_target,
|
|
9
|
+
_matching_host_setting,
|
|
10
|
+
_normalize_connections,
|
|
11
|
+
_normalize_queue_info,
|
|
12
|
+
_normalize_runtime_item_metadata,
|
|
13
|
+
_production_payload,
|
|
14
|
+
_setting_targets,
|
|
15
|
+
_split_production_settings,
|
|
16
|
+
_split_settings,
|
|
17
|
+
)
|
|
18
|
+
from .source_inference import infer_source_connections
|
|
19
|
+
from .types import GraphEdge
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def production_from_dict(
|
|
23
|
+
production_cls,
|
|
24
|
+
data: dict[str, Any],
|
|
25
|
+
*,
|
|
26
|
+
connections: Any = None,
|
|
27
|
+
queue_info: Any = None,
|
|
28
|
+
namespace: str | None = None,
|
|
29
|
+
director: Any = None,
|
|
30
|
+
):
|
|
31
|
+
production_name, production_data = _production_payload(data)
|
|
32
|
+
production_settings = _split_production_settings(production_data.get("Setting"))
|
|
33
|
+
production = production_cls._new_unhydrated(
|
|
34
|
+
production_name,
|
|
35
|
+
testing_enabled=production_data.get("@TestingEnabled", False),
|
|
36
|
+
log_general_trace_events=production_data.get("@LogGeneralTraceEvents", False),
|
|
37
|
+
actor_pool_size=production_data.get("ActorPoolSize", 2),
|
|
38
|
+
description=production_data.get("Description", ""),
|
|
39
|
+
shutdown_timeout=production_settings.get("shutdown_timeout", 120),
|
|
40
|
+
update_timeout=production_settings.get("update_timeout", 10),
|
|
41
|
+
alert_notification_manager=production_settings.get(
|
|
42
|
+
"alert_notification_manager", ""
|
|
43
|
+
),
|
|
44
|
+
alert_notification_operation=production_settings.get(
|
|
45
|
+
"alert_notification_operation", ""
|
|
46
|
+
),
|
|
47
|
+
alert_notification_recipients=production_settings.get(
|
|
48
|
+
"alert_notification_recipients", ""
|
|
49
|
+
),
|
|
50
|
+
alert_action_window=production_settings.get("alert_action_window", 60),
|
|
51
|
+
namespace=namespace,
|
|
52
|
+
director=director,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
_add_imported_items(production, production_data)
|
|
56
|
+
_apply_queue_info(production, queue_info)
|
|
57
|
+
_apply_runtime_item_metadata(production, connections)
|
|
58
|
+
runtime_sources, runtime_sources_with_targets = _apply_runtime_connections(
|
|
59
|
+
production, connections
|
|
60
|
+
)
|
|
61
|
+
_infer_connections_from_host_settings(
|
|
62
|
+
production,
|
|
63
|
+
runtime_sources=runtime_sources,
|
|
64
|
+
runtime_sources_with_targets=runtime_sources_with_targets,
|
|
65
|
+
)
|
|
66
|
+
_infer_connections_from_source(production)
|
|
67
|
+
return production
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _add_imported_items(production, production_data: dict[str, Any]) -> None:
|
|
71
|
+
for item_data in _as_list(production_data.get("Item", [])):
|
|
72
|
+
if not isinstance(item_data, dict):
|
|
73
|
+
continue
|
|
74
|
+
host_settings, adapter_settings, other_settings = _split_settings(
|
|
75
|
+
item_data.get("Setting", [])
|
|
76
|
+
)
|
|
77
|
+
ref = ComponentRef(
|
|
78
|
+
production=production,
|
|
79
|
+
name=item_data.get("@Name", ""),
|
|
80
|
+
class_name=item_data.get("@ClassName", ""),
|
|
81
|
+
adapter_class_name=(
|
|
82
|
+
item_data.get("@AdapterClassName") or item_data.get("@Adapter") or ""
|
|
83
|
+
),
|
|
84
|
+
kind=_component_kind(item_data),
|
|
85
|
+
category=item_data.get("@Category", ""),
|
|
86
|
+
pool_size=item_data.get("@PoolSize", 1),
|
|
87
|
+
enabled=item_data.get("@Enabled", True),
|
|
88
|
+
foreground=item_data.get("@Foreground", False),
|
|
89
|
+
comment=item_data.get("@Comment", ""),
|
|
90
|
+
log_trace_events=item_data.get("@LogTraceEvents", False),
|
|
91
|
+
schedule=item_data.get("@Schedule", ""),
|
|
92
|
+
host_settings=host_settings,
|
|
93
|
+
adapter_settings=adapter_settings,
|
|
94
|
+
other_settings=other_settings,
|
|
95
|
+
)
|
|
96
|
+
production._add_item(ref)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _apply_queue_info(production, queue_info: Any) -> None:
|
|
100
|
+
queue_map, queue_warnings = _normalize_queue_info(queue_info)
|
|
101
|
+
production._graph_warnings.extend(queue_warnings)
|
|
102
|
+
for item_name, info in queue_map.items():
|
|
103
|
+
if item_name not in production._items_by_name:
|
|
104
|
+
production._graph_warnings.append(f"Queue info item does not exist: {item_name}")
|
|
105
|
+
continue
|
|
106
|
+
production._queue_info[item_name] = dict(info)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _apply_runtime_item_metadata(production, connections: Any) -> None:
|
|
110
|
+
runtime_item_metadata = _normalize_runtime_item_metadata(connections)
|
|
111
|
+
for item_name, metadata in runtime_item_metadata.items():
|
|
112
|
+
ref = production._items_by_name.get(item_name)
|
|
113
|
+
if ref is None:
|
|
114
|
+
continue
|
|
115
|
+
adapter_class_name = metadata.get("adapter_class_name", "")
|
|
116
|
+
if adapter_class_name and not ref.adapter_class_name:
|
|
117
|
+
ref.adapter_class_name = adapter_class_name
|
|
118
|
+
kind = _normalize_component_kind(metadata.get("kind", ""))
|
|
119
|
+
if kind:
|
|
120
|
+
ref.kind = kind
|
|
121
|
+
if "iop" in metadata:
|
|
122
|
+
ref.runtime_metadata["iop"] = metadata["iop"]
|
|
123
|
+
for setting_name in ("%module", "%classname", "%classpaths"):
|
|
124
|
+
setting_value = metadata.get(setting_name, "")
|
|
125
|
+
if setting_value and setting_name not in ref.host_settings:
|
|
126
|
+
ref.host_settings[setting_name] = setting_value
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _apply_runtime_connections(production, connections: Any) -> tuple[set[str], set[str]]:
|
|
130
|
+
connection_map, runtime_sources, warnings = _normalize_connections(connections)
|
|
131
|
+
production._graph_warnings.extend(warnings)
|
|
132
|
+
runtime_sources_with_targets: set[str] = set()
|
|
133
|
+
|
|
134
|
+
for source_item, targets in connection_map.items():
|
|
135
|
+
if source_item not in production._items_by_name:
|
|
136
|
+
production._graph_warnings.append(
|
|
137
|
+
f"Runtime connection source does not exist: {source_item}"
|
|
138
|
+
)
|
|
139
|
+
continue
|
|
140
|
+
ref = production._items_by_name[source_item]
|
|
141
|
+
for target in targets:
|
|
142
|
+
target_name = target.get("target", "")
|
|
143
|
+
if not target_name:
|
|
144
|
+
continue
|
|
145
|
+
source_target_setting = target.get(
|
|
146
|
+
"source_target_setting",
|
|
147
|
+
"",
|
|
148
|
+
) or _matching_host_setting(ref, target_name)
|
|
149
|
+
if source_target_setting:
|
|
150
|
+
ref.target_setting_names.add(source_target_setting)
|
|
151
|
+
if target_name not in production._items_by_name:
|
|
152
|
+
if _is_internal_runtime_target(target_name):
|
|
153
|
+
continue
|
|
154
|
+
production._graph_warnings.append(
|
|
155
|
+
f"Runtime connection target does not exist: "
|
|
156
|
+
f"{source_item} -> {target_name}"
|
|
157
|
+
)
|
|
158
|
+
runtime_sources_with_targets.add(source_item)
|
|
159
|
+
production._register_connection(
|
|
160
|
+
source_item,
|
|
161
|
+
source_target_setting,
|
|
162
|
+
target_name,
|
|
163
|
+
origin="runtime",
|
|
164
|
+
interaction=target.get("interaction", "request"),
|
|
165
|
+
metadata=target.get("metadata", {}),
|
|
166
|
+
validate_target=False,
|
|
167
|
+
)
|
|
168
|
+
return runtime_sources, runtime_sources_with_targets
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _component_kind(item_data: dict[str, Any]) -> str:
|
|
172
|
+
explicit_kind = _normalize_component_kind(
|
|
173
|
+
item_data.get("@Kind")
|
|
174
|
+
or item_data.get("@kind")
|
|
175
|
+
or item_data.get("kind")
|
|
176
|
+
or item_data.get("type")
|
|
177
|
+
or item_data.get("role")
|
|
178
|
+
or ""
|
|
179
|
+
)
|
|
180
|
+
if explicit_kind:
|
|
181
|
+
return explicit_kind
|
|
182
|
+
return _component_kind_from_class_name(str(item_data.get("@ClassName", "")))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _component_kind_from_class_name(class_name: str) -> str:
|
|
186
|
+
class_name = str(class_name or "")
|
|
187
|
+
if not class_name.startswith(("Python.", "Ens.", "EnsLib.", "IOP.")):
|
|
188
|
+
return "component"
|
|
189
|
+
last_part = class_name.rpartition(".")[2].lower()
|
|
190
|
+
if not last_part:
|
|
191
|
+
return "component"
|
|
192
|
+
if last_part.endswith("service"):
|
|
193
|
+
return "service"
|
|
194
|
+
if last_part.endswith("process"):
|
|
195
|
+
return "process"
|
|
196
|
+
if last_part.endswith("operation"):
|
|
197
|
+
return "operation"
|
|
198
|
+
return "component"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _normalize_component_kind(kind: Any) -> str:
|
|
202
|
+
normalized = str(kind or "").strip().lower()
|
|
203
|
+
if normalized in {"service", "businessservice"}:
|
|
204
|
+
return "service"
|
|
205
|
+
if normalized in {"process", "businessprocess"}:
|
|
206
|
+
return "process"
|
|
207
|
+
if normalized in {"operation", "businessoperation"}:
|
|
208
|
+
return "operation"
|
|
209
|
+
if normalized in {"component", "host", "businesshost"}:
|
|
210
|
+
return "component"
|
|
211
|
+
return ""
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _infer_connections_from_host_settings(
|
|
215
|
+
production,
|
|
216
|
+
*,
|
|
217
|
+
runtime_sources: set[str],
|
|
218
|
+
runtime_sources_with_targets: set[str],
|
|
219
|
+
) -> None:
|
|
220
|
+
for ref in production._items:
|
|
221
|
+
for setting_name, value in ref.host_settings.items():
|
|
222
|
+
if str(setting_name).startswith("%"):
|
|
223
|
+
continue
|
|
224
|
+
has_runtime_targets = ref.name in runtime_sources_with_targets
|
|
225
|
+
if has_runtime_targets and not _is_target_config_setting(setting_name):
|
|
226
|
+
continue
|
|
227
|
+
for target_name in _setting_targets(value):
|
|
228
|
+
if target_name not in production._items_by_name:
|
|
229
|
+
continue
|
|
230
|
+
if _has_connection(production, ref.name, setting_name, target_name):
|
|
231
|
+
continue
|
|
232
|
+
ref.target_setting_names.add(setting_name)
|
|
233
|
+
metadata = {"source": "Host setting fallback"}
|
|
234
|
+
if has_runtime_targets:
|
|
235
|
+
metadata["reason"] = (
|
|
236
|
+
"runtime discovery did not report this target setting"
|
|
237
|
+
)
|
|
238
|
+
elif ref.name in runtime_sources:
|
|
239
|
+
metadata["reason"] = "runtime discovery returned no targets"
|
|
240
|
+
production._register_connection(
|
|
241
|
+
ref.name,
|
|
242
|
+
setting_name,
|
|
243
|
+
target_name,
|
|
244
|
+
origin="inferred",
|
|
245
|
+
metadata=metadata,
|
|
246
|
+
validate_target=False,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _is_target_config_setting(setting_name: Any) -> bool:
|
|
251
|
+
normalized = str(setting_name or "").strip().lower()
|
|
252
|
+
return normalized.endswith("targetconfigname") or normalized.endswith(
|
|
253
|
+
"targetconfignames"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _has_connection(
|
|
258
|
+
production,
|
|
259
|
+
source_item: str,
|
|
260
|
+
source_target_setting: str,
|
|
261
|
+
target_name: str,
|
|
262
|
+
) -> bool:
|
|
263
|
+
return any(
|
|
264
|
+
edge.source_item == source_item
|
|
265
|
+
and edge.source_target_setting == source_target_setting
|
|
266
|
+
and edge.target == target_name
|
|
267
|
+
for edge in production._edges
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _infer_connections_from_source(production) -> None:
|
|
272
|
+
existing = {
|
|
273
|
+
(edge.source_item, edge.target): edge
|
|
274
|
+
for edge in production._edges
|
|
275
|
+
}
|
|
276
|
+
for ref in production._items:
|
|
277
|
+
for connection in infer_source_connections(
|
|
278
|
+
ref.class_name,
|
|
279
|
+
ref.host_settings,
|
|
280
|
+
iop=_truthy(ref.runtime_metadata.get("iop")),
|
|
281
|
+
):
|
|
282
|
+
target_name = connection.target
|
|
283
|
+
if target_name not in production._items_by_name:
|
|
284
|
+
continue
|
|
285
|
+
key = (ref.name, target_name)
|
|
286
|
+
if key in existing:
|
|
287
|
+
existing[key] = _apply_source_connection_interaction(
|
|
288
|
+
production,
|
|
289
|
+
existing[key],
|
|
290
|
+
connection,
|
|
291
|
+
)
|
|
292
|
+
continue
|
|
293
|
+
metadata = {"source": connection.source}
|
|
294
|
+
if connection.detail:
|
|
295
|
+
metadata["detail"] = connection.detail
|
|
296
|
+
production._register_connection(
|
|
297
|
+
ref.name,
|
|
298
|
+
"",
|
|
299
|
+
target_name,
|
|
300
|
+
origin="inferred",
|
|
301
|
+
interaction=connection.interaction,
|
|
302
|
+
metadata=metadata,
|
|
303
|
+
validate_target=False,
|
|
304
|
+
)
|
|
305
|
+
existing[key] = production._edges[-1]
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _apply_source_connection_interaction(
|
|
309
|
+
production,
|
|
310
|
+
edge: GraphEdge,
|
|
311
|
+
connection,
|
|
312
|
+
) -> GraphEdge:
|
|
313
|
+
if connection.interaction in ("", "request"):
|
|
314
|
+
return edge
|
|
315
|
+
if edge.interaction not in ("", "request", "unknown"):
|
|
316
|
+
return edge
|
|
317
|
+
|
|
318
|
+
metadata = dict(edge.metadata)
|
|
319
|
+
metadata.setdefault("source", connection.source)
|
|
320
|
+
if connection.detail:
|
|
321
|
+
metadata.setdefault("detail", connection.detail)
|
|
322
|
+
|
|
323
|
+
updated = GraphEdge(
|
|
324
|
+
source_item=edge.source_item,
|
|
325
|
+
source_target_setting=edge.source_target_setting,
|
|
326
|
+
target=edge.target,
|
|
327
|
+
origin=edge.origin,
|
|
328
|
+
interaction=connection.interaction,
|
|
329
|
+
metadata=metadata,
|
|
330
|
+
)
|
|
331
|
+
production._edges = [
|
|
332
|
+
updated if existing is edge else existing
|
|
333
|
+
for existing in production._edges
|
|
334
|
+
]
|
|
335
|
+
return updated
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _truthy(value: Any) -> bool:
|
|
339
|
+
if isinstance(value, bool):
|
|
340
|
+
return value
|
|
341
|
+
return str(value or "").strip().lower() in {"1", "true", "yes", "on"}
|