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.
Files changed (119) hide show
  1. iop/__init__.py +276 -18
  2. iop/__main__.py +3 -3
  3. iop/cli/__init__.py +1 -0
  4. iop/cli/formatting.py +45 -0
  5. iop/{_cli.py → cli/main.py} +238 -216
  6. iop/cli/parser.py +165 -0
  7. iop/cli/types.py +73 -0
  8. iop/cls/IOP/Common.cls +11 -2
  9. iop/cls/IOP/PrivateSession/Duplex.cls +1 -1
  10. iop/cls/IOP/Service/Remote/Rest/v1.cls +152 -5
  11. iop/cls/IOP/Utils.cls +814 -7
  12. iop/cls/IOP/Wrapper.cls +1 -1
  13. iop/components/__init__.py +16 -0
  14. iop/{_async_request.py → components/async_request.py} +7 -7
  15. iop/components/business_host.py +363 -0
  16. iop/components/business_operation.py +84 -0
  17. iop/{_business_process.py → components/business_process.py} +145 -125
  18. iop/components/business_service.py +106 -0
  19. iop/{_common.py → components/common.py} +160 -164
  20. iop/{_debugpy.py → components/debugpy.py} +11 -11
  21. iop/{_generator_request.py → components/generator_request.py} +4 -4
  22. iop/{_inbound_adapter.py → components/inbound_adapter.py} +10 -18
  23. iop/{_log_manager.py → components/log_manager.py} +1 -1
  24. iop/{_outbound_adapter.py → components/outbound_adapter.py} +1 -1
  25. iop/components/polling_business_service.py +48 -0
  26. iop/{_private_session_duplex.py → components/private_session_duplex.py} +7 -9
  27. iop/{_private_session_process.py → components/private_session_process.py} +2 -2
  28. iop/{_settings.py → components/settings.py} +23 -3
  29. iop/messages/__init__.py +7 -0
  30. iop/messages/base.py +61 -0
  31. iop/{_decorators.py → messages/decorators.py} +8 -2
  32. iop/messages/dispatch.py +351 -0
  33. iop/{_persistent_message.py → messages/persistent.py} +24 -19
  34. iop/{_serialization.py → messages/serialization.py} +34 -21
  35. iop/{_message_validator.py → messages/validation.py} +15 -13
  36. iop/migration/__init__.py +1 -0
  37. iop/migration/io.py +61 -0
  38. iop/migration/manifest.py +534 -0
  39. iop/migration/plans.py +80 -0
  40. iop/migration/utils.py +1126 -0
  41. iop/production/__init__.py +55 -0
  42. iop/production/actions.py +525 -0
  43. iop/production/common.py +103 -0
  44. iop/production/component.py +234 -0
  45. iop/production/declarations.py +190 -0
  46. iop/production/declarative.py +214 -0
  47. iop/production/diff.py +327 -0
  48. iop/production/import_.py +296 -0
  49. iop/production/inspection.py +92 -0
  50. iop/production/model.py +1143 -0
  51. iop/production/planning.py +678 -0
  52. iop/production/reconstruction.py +341 -0
  53. iop/production/rendering.py +610 -0
  54. iop/production/runtime.py +91 -0
  55. iop/production/source_inference.py +729 -0
  56. iop/production/types.py +546 -0
  57. iop/production/validation.py +452 -0
  58. iop/runtime/__init__.py +1 -0
  59. iop/runtime/director.py +415 -0
  60. iop/runtime/environment.py +59 -0
  61. iop/{_iris.py → runtime/iris.py} +2 -2
  62. iop/runtime/local.py +145 -0
  63. iop/{_director_protocol.py → runtime/protocol.py} +25 -8
  64. iop/runtime/remote/__init__.py +12 -0
  65. iop/runtime/remote/client.py +90 -0
  66. iop/runtime/remote/director.py +283 -0
  67. iop/runtime/remote/migration.py +57 -0
  68. iop/runtime/remote/settings.py +69 -0
  69. iop/runtime/remote/setup.py +72 -0
  70. {iris_pex_embedded_python-3.7.2b2.dist-info → iris_pex_embedded_python-4.0.0.dist-info}/METADATA +11 -2
  71. iris_pex_embedded_python-4.0.0.dist-info/RECORD +99 -0
  72. iris_pex_embedded_python-4.0.0.dist-info/entry_points.txt +2 -0
  73. iris_pex_embedded_python-4.0.0.dist-info/top_level.txt +1 -0
  74. grongier/__init__.py +0 -0
  75. grongier/cls/Grongier/PEX/BusinessOperation.cls +0 -8
  76. grongier/cls/Grongier/PEX/BusinessProcess.cls +0 -13
  77. grongier/cls/Grongier/PEX/BusinessService.cls +0 -8
  78. grongier/cls/Grongier/PEX/Common.cls +0 -10
  79. grongier/cls/Grongier/PEX/Director.cls +0 -10
  80. grongier/cls/Grongier/PEX/Duplex/Operation.cls +0 -4
  81. grongier/cls/Grongier/PEX/Duplex/Process.cls +0 -13
  82. grongier/cls/Grongier/PEX/Duplex/Service.cls +0 -4
  83. grongier/cls/Grongier/PEX/InboundAdapter.cls +0 -8
  84. grongier/cls/Grongier/PEX/Message.cls +0 -13
  85. grongier/cls/Grongier/PEX/OutboundAdapter.cls +0 -8
  86. grongier/cls/Grongier/PEX/PickleMessage.cls +0 -13
  87. grongier/cls/Grongier/PEX/PrivateSession/Duplex.cls +0 -8
  88. grongier/cls/Grongier/PEX/PrivateSession/Message/Ack.cls +0 -14
  89. grongier/cls/Grongier/PEX/PrivateSession/Message/Poll.cls +0 -14
  90. grongier/cls/Grongier/PEX/PrivateSession/Message/Start.cls +0 -14
  91. grongier/cls/Grongier/PEX/PrivateSession/Message/Stop.cls +0 -14
  92. grongier/cls/Grongier/PEX/Test.cls +0 -10
  93. grongier/cls/Grongier/PEX/Utils.cls +0 -10
  94. grongier/cls/Grongier/Service/WSGI.cls +0 -4
  95. grongier/pex/__init__.py +0 -75
  96. grongier/pex/__main__.py +0 -4
  97. grongier/pex/_business_host.py +0 -1
  98. grongier/pex/_cli.py +0 -4
  99. grongier/pex/_common.py +0 -1
  100. grongier/pex/_director.py +0 -1
  101. grongier/pex/_utils.py +0 -1
  102. grongier/pex/wsgi/handlers.py +0 -104
  103. iop/_business_host.py +0 -322
  104. iop/_business_operation.py +0 -77
  105. iop/_business_service.py +0 -67
  106. iop/_director.py +0 -333
  107. iop/_dispatch.py +0 -194
  108. iop/_local.py +0 -106
  109. iop/_message.py +0 -45
  110. iop/_polling_business_service.py +0 -6
  111. iop/_remote.py +0 -473
  112. iop/_utils.py +0 -912
  113. iop/cls/IOP/Service/WSGI.cls +0 -310
  114. iop/wsgi/handlers.py +0 -112
  115. iris_pex_embedded_python-3.7.2b2.dist-info/RECORD +0 -96
  116. iris_pex_embedded_python-3.7.2b2.dist-info/entry_points.txt +0 -2
  117. iris_pex_embedded_python-3.7.2b2.dist-info/top_level.txt +0 -2
  118. {iris_pex_embedded_python-3.7.2b2.dist-info → iris_pex_embedded_python-4.0.0.dist-info}/WHEEL +0 -0
  119. {iris_pex_embedded_python-3.7.2b2.dist-info → iris_pex_embedded_python-4.0.0.dist-info}/licenses/LICENSE +0 -0
iop/production/diff.py ADDED
@@ -0,0 +1,327 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from .common import PRODUCTION_SETTING_FIELDS, _bool_text, _text_value
7
+ from .types import (
8
+ GraphEdge,
9
+ ProductionDiff,
10
+ ProductionDiffEntry,
11
+ _canonical_value,
12
+ )
13
+
14
+ if TYPE_CHECKING:
15
+ from .component import ComponentRef
16
+ from .model import Production
17
+
18
+
19
+ def _diff_productions(
20
+ desired: Production,
21
+ current: Production,
22
+ *,
23
+ include_graph_metadata: bool = False,
24
+ ) -> ProductionDiff:
25
+ changes: list[ProductionDiffEntry] = []
26
+ warnings = _diff_warnings(desired, current)
27
+
28
+ if current.name != desired.name:
29
+ changes.append(
30
+ ProductionDiffEntry(
31
+ action="change",
32
+ kind="production",
33
+ path="production.name",
34
+ before=current.name,
35
+ after=desired.name,
36
+ )
37
+ )
38
+
39
+ _diff_mapping_values(
40
+ changes,
41
+ kind="production",
42
+ base_path="production",
43
+ before=_production_signature(current),
44
+ after=_production_signature(desired),
45
+ )
46
+ _diff_items(changes, desired=desired, current=current)
47
+ _diff_connections(
48
+ changes,
49
+ desired=desired,
50
+ current=current,
51
+ include_graph_metadata=include_graph_metadata,
52
+ )
53
+
54
+ return ProductionDiff(
55
+ production_name=desired.name,
56
+ changes=tuple(changes),
57
+ warnings=tuple(warnings),
58
+ )
59
+
60
+
61
+ def _diff_warnings(desired: Production, current: Production) -> list[str]:
62
+ warnings = []
63
+ warnings.extend(f"desired: {warning}" for warning in desired.graph().warnings)
64
+ warnings.extend(f"current: {warning}" for warning in current.graph().warnings)
65
+ return warnings
66
+
67
+
68
+ def _production_signature(production: Production) -> dict[str, Any]:
69
+ signature = {
70
+ "testing_enabled": _bool_text(production.testing_enabled),
71
+ "log_general_trace_events": _bool_text(production.log_general_trace_events),
72
+ "actor_pool_size": _text_value(production.actor_pool_size),
73
+ "description": production.description,
74
+ }
75
+ signature.update(
76
+ {
77
+ field_name: _text_value(getattr(production, field_name))
78
+ for field_name in PRODUCTION_SETTING_FIELDS
79
+ }
80
+ )
81
+ return signature
82
+
83
+
84
+ def _item_signatures(production: Production) -> dict[str, dict[str, Any]]:
85
+ return {item.name: _item_signature(item) for item in production.items}
86
+
87
+
88
+ def _item_signature(item: ComponentRef) -> dict[str, Any]:
89
+ return {
90
+ "class_name": item.class_name or "",
91
+ "category": item.category,
92
+ "pool_size": _text_value(item.pool_size),
93
+ "enabled": _bool_text(item.enabled),
94
+ "foreground": _bool_text(item.foreground),
95
+ "comment": item.comment,
96
+ "log_trace_events": _bool_text(item.log_trace_events),
97
+ "schedule": item.schedule,
98
+ "host_settings": _settings_signature(item.host_settings),
99
+ "adapter_settings": _settings_signature(item.adapter_settings),
100
+ "other_settings": _canonical_value(item.other_settings),
101
+ }
102
+
103
+
104
+ def _settings_signature(settings: dict[str, Any]) -> dict[str, str]:
105
+ return {key: _text_value(settings[key]) for key in sorted(settings)}
106
+
107
+
108
+ def _diff_items(
109
+ changes: list[ProductionDiffEntry],
110
+ *,
111
+ desired: Production,
112
+ current: Production,
113
+ ) -> None:
114
+ desired_items = _item_signatures(desired)
115
+ current_items = _item_signatures(current)
116
+
117
+ for item_name in sorted(current_items.keys() - desired_items.keys()):
118
+ changes.append(
119
+ ProductionDiffEntry(
120
+ action="remove",
121
+ kind="item",
122
+ path=f"items.{item_name}",
123
+ before=current_items[item_name],
124
+ )
125
+ )
126
+
127
+ for item_name in sorted(desired_items.keys() - current_items.keys()):
128
+ changes.append(
129
+ ProductionDiffEntry(
130
+ action="add",
131
+ kind="item",
132
+ path=f"items.{item_name}",
133
+ after=desired_items[item_name],
134
+ )
135
+ )
136
+
137
+ for item_name in sorted(desired_items.keys() & current_items.keys()):
138
+ _diff_item_fields(
139
+ changes,
140
+ item_name=item_name,
141
+ before=current_items[item_name],
142
+ after=desired_items[item_name],
143
+ )
144
+
145
+
146
+ def _diff_item_fields(
147
+ changes: list[ProductionDiffEntry],
148
+ *,
149
+ item_name: str,
150
+ before: dict[str, Any],
151
+ after: dict[str, Any],
152
+ ) -> None:
153
+ scalar_fields = [
154
+ "class_name",
155
+ "category",
156
+ "pool_size",
157
+ "enabled",
158
+ "foreground",
159
+ "comment",
160
+ "log_trace_events",
161
+ "schedule",
162
+ ]
163
+ _diff_mapping_values(
164
+ changes,
165
+ kind="item",
166
+ base_path=f"items.{item_name}",
167
+ before={field: before[field] for field in scalar_fields},
168
+ after={field: after[field] for field in scalar_fields},
169
+ )
170
+ _diff_mapping_values(
171
+ changes,
172
+ kind="setting",
173
+ base_path=f"items.{item_name}.settings.Host",
174
+ before=before["host_settings"],
175
+ after=after["host_settings"],
176
+ )
177
+ _diff_mapping_values(
178
+ changes,
179
+ kind="setting",
180
+ base_path=f"items.{item_name}.settings.Adapter",
181
+ before=before["adapter_settings"],
182
+ after=after["adapter_settings"],
183
+ )
184
+ if before["other_settings"] != after["other_settings"]:
185
+ changes.append(
186
+ ProductionDiffEntry(
187
+ action="change",
188
+ kind="setting",
189
+ path=f"items.{item_name}.settings.Other",
190
+ before=before["other_settings"],
191
+ after=after["other_settings"],
192
+ )
193
+ )
194
+
195
+
196
+ def _diff_mapping_values(
197
+ changes: list[ProductionDiffEntry],
198
+ *,
199
+ kind: str,
200
+ base_path: str,
201
+ before: dict[str, Any],
202
+ after: dict[str, Any],
203
+ ) -> None:
204
+ for key in sorted(before.keys() - after.keys()):
205
+ changes.append(
206
+ ProductionDiffEntry(
207
+ action="remove",
208
+ kind=kind,
209
+ path=f"{base_path}.{key}",
210
+ before=before[key],
211
+ )
212
+ )
213
+ for key in sorted(after.keys() - before.keys()):
214
+ changes.append(
215
+ ProductionDiffEntry(
216
+ action="add",
217
+ kind=kind,
218
+ path=f"{base_path}.{key}",
219
+ after=after[key],
220
+ )
221
+ )
222
+ for key in sorted(before.keys() & after.keys()):
223
+ if before[key] != after[key]:
224
+ changes.append(
225
+ ProductionDiffEntry(
226
+ action="change",
227
+ kind=kind,
228
+ path=f"{base_path}.{key}",
229
+ before=before[key],
230
+ after=after[key],
231
+ )
232
+ )
233
+
234
+
235
+ def _diff_connections(
236
+ changes: list[ProductionDiffEntry],
237
+ *,
238
+ desired: Production,
239
+ current: Production,
240
+ include_graph_metadata: bool = False,
241
+ ) -> None:
242
+ desired_connections = _connection_signature(
243
+ desired,
244
+ include_graph_metadata=include_graph_metadata,
245
+ )
246
+ current_connections = _connection_signature(
247
+ current,
248
+ include_graph_metadata=include_graph_metadata,
249
+ )
250
+
251
+ for source in sorted(current_connections.keys() - desired_connections.keys()):
252
+ changes.append(
253
+ ProductionDiffEntry(
254
+ action="remove",
255
+ kind="connection",
256
+ path=f"connections.{_connection_source_path(source)}",
257
+ before=current_connections[source],
258
+ )
259
+ )
260
+ for source in sorted(desired_connections.keys() - current_connections.keys()):
261
+ changes.append(
262
+ ProductionDiffEntry(
263
+ action="add",
264
+ kind="connection",
265
+ path=f"connections.{_connection_source_path(source)}",
266
+ after=desired_connections[source],
267
+ )
268
+ )
269
+ for source in sorted(desired_connections.keys() & current_connections.keys()):
270
+ before = current_connections[source]
271
+ after = desired_connections[source]
272
+ if before != after:
273
+ changes.append(
274
+ ProductionDiffEntry(
275
+ action="change",
276
+ kind="connection",
277
+ path=f"connections.{_connection_source_path(source)}",
278
+ before=before,
279
+ after=after,
280
+ )
281
+ )
282
+
283
+
284
+ def _connection_signature(
285
+ production: Production,
286
+ *,
287
+ include_graph_metadata: bool = False,
288
+ ) -> dict[tuple[str, str], list[Any]]:
289
+ if include_graph_metadata:
290
+ detailed_connections: dict[tuple[str, str], list[dict[str, Any]]] = {}
291
+ for edge in production.graph().edges:
292
+ source = (edge.source_item, edge.source_target_setting)
293
+ detailed_connections.setdefault(source, []).append(_edge_diff_value(edge))
294
+ return {
295
+ source: sorted(
296
+ values,
297
+ key=lambda value: json.dumps(value, sort_keys=True),
298
+ )
299
+ for source, values in sorted(detailed_connections.items())
300
+ }
301
+
302
+ connections: dict[tuple[str, str], set[str]] = {}
303
+ for edge in production.graph().edges:
304
+ source = (edge.source_item, edge.source_target_setting)
305
+ connections.setdefault(source, set()).add(edge.target)
306
+ return {
307
+ source: list(sorted(targets))
308
+ for source, targets in sorted(connections.items())
309
+ }
310
+
311
+
312
+ def _edge_diff_value(edge: GraphEdge) -> dict[str, Any]:
313
+ data: dict[str, Any] = {
314
+ "target": edge.target,
315
+ "origin": edge.origin,
316
+ "interaction": edge.interaction,
317
+ }
318
+ if edge.metadata:
319
+ data["metadata"] = _canonical_value(edge.metadata)
320
+ return data
321
+
322
+
323
+ def _connection_source_path(source: tuple[str, str]) -> str:
324
+ source_item, source_target_setting = source
325
+ if source_target_setting:
326
+ return f"{source_item}.{source_target_setting}"
327
+ return source_item
@@ -0,0 +1,296 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from .common import PRODUCTION_SETTING_FIELDS_BY_IRIS, _text_value
6
+
7
+
8
+ def _as_list(value: Any) -> list[Any]:
9
+ if value is None:
10
+ return []
11
+ if isinstance(value, list):
12
+ return value
13
+ return [value]
14
+
15
+
16
+ def _production_payload(data: dict[str, Any]) -> tuple[str, dict[str, Any]]:
17
+ if not isinstance(data, dict) or not data:
18
+ raise ValueError("Production data must be a non-empty dictionary.")
19
+
20
+ if "Production" in data:
21
+ production_data = data["Production"]
22
+ if not isinstance(production_data, dict):
23
+ raise ValueError("Production data must be a dictionary.")
24
+ production_name = production_data.get("@Name") or "Production"
25
+ return production_name, production_data
26
+
27
+ production_name = next(iter(data.keys()))
28
+ production_data = data[production_name]
29
+ if not isinstance(production_data, dict):
30
+ raise ValueError("Production data must be a dictionary.")
31
+ production_name = production_data.get("@Name") or production_name
32
+ return production_name, production_data
33
+
34
+
35
+ def _split_settings(
36
+ settings: Any,
37
+ ) -> tuple[dict[str, Any], dict[str, Any], list[dict[str, Any]]]:
38
+ host_settings: dict[str, Any] = {}
39
+ adapter_settings: dict[str, Any] = {}
40
+ other_settings: list[dict[str, Any]] = []
41
+
42
+ for setting in _as_list(settings):
43
+ if not isinstance(setting, dict):
44
+ continue
45
+ target = setting.get("@Target", "Host")
46
+ name = setting.get("@Name", "")
47
+ value = setting.get("#text", "")
48
+ if target in ("", "Host"):
49
+ host_settings[name] = value
50
+ elif target == "Adapter":
51
+ adapter_settings[name] = value
52
+ else:
53
+ other_settings.append(dict(setting))
54
+ return host_settings, adapter_settings, other_settings
55
+
56
+
57
+ def _split_production_settings(settings: Any) -> dict[str, Any]:
58
+ values: dict[str, Any] = {}
59
+ for setting in _as_list(settings):
60
+ if not isinstance(setting, dict):
61
+ continue
62
+ target = setting.get("@Target", "")
63
+ if target not in ("", "Production"):
64
+ continue
65
+ iris_name = str(setting.get("@Name", ""))
66
+ field_name = PRODUCTION_SETTING_FIELDS_BY_IRIS.get(iris_name)
67
+ if field_name:
68
+ values[field_name] = setting.get("#text", "")
69
+ return values
70
+
71
+
72
+ def _normalize_connections(
73
+ connections: Any,
74
+ ) -> tuple[dict[str, list[dict[str, Any]]], set[str], list[str]]:
75
+ connection_map: dict[str, list[dict[str, Any]]] = {}
76
+ runtime_sources: set[str] = set()
77
+ warnings: list[str] = []
78
+
79
+ if connections is None:
80
+ return connection_map, runtime_sources, warnings
81
+
82
+ if isinstance(connections, dict):
83
+ warnings.extend(str(value) for value in _as_list(connections.get("warnings")))
84
+ if "items" in connections:
85
+ for item in _as_list(connections.get("items")):
86
+ if not isinstance(item, dict):
87
+ continue
88
+ source = (
89
+ item.get("item")
90
+ or item.get("name")
91
+ or item.get("source_item")
92
+ or ""
93
+ )
94
+ if not source:
95
+ continue
96
+ item_warnings = [value for value in _as_list(item.get("warnings")) if value]
97
+ warnings.extend(
98
+ f"{source}: {value}"
99
+ for value in item_warnings
100
+ )
101
+ discovered = False
102
+ for target in _as_list(item.get("connections")):
103
+ normalized = _normalize_connection_target(target)
104
+ if normalized is not None:
105
+ discovered = True
106
+ connection_map.setdefault(source, []).append(normalized)
107
+ for edge in _as_list(item.get("edges")):
108
+ normalized = _normalize_connection_target(edge)
109
+ if normalized is not None:
110
+ discovered = True
111
+ connection_map.setdefault(source, []).append(normalized)
112
+ if not item_warnings or discovered:
113
+ runtime_sources.add(source)
114
+ return connection_map, runtime_sources, warnings
115
+
116
+ for source, targets in connections.items():
117
+ if source in ("production", "warnings"):
118
+ continue
119
+ runtime_sources.add(str(source))
120
+ for target in _as_list(targets):
121
+ normalized = _normalize_connection_target(target)
122
+ if normalized is not None:
123
+ connection_map.setdefault(str(source), []).append(normalized)
124
+ return connection_map, runtime_sources, warnings
125
+
126
+ if isinstance(connections, list):
127
+ for edge in connections:
128
+ if not isinstance(edge, dict):
129
+ continue
130
+ source = edge.get("source_item") or edge.get("item") or ""
131
+ if not source and "." in str(edge.get("source", "")):
132
+ source, _, source_target_setting = str(edge["source"]).rpartition(".")
133
+ edge = {**edge, "source_target_setting": source_target_setting}
134
+ if not source:
135
+ continue
136
+ runtime_sources.add(str(source))
137
+ normalized = _normalize_connection_target(edge)
138
+ if normalized is not None:
139
+ connection_map.setdefault(str(source), []).append(normalized)
140
+ return connection_map, runtime_sources, warnings
141
+
142
+ warnings.append(f"Ignoring invalid runtime connections: {connections!r}")
143
+ return connection_map, runtime_sources, warnings
144
+
145
+
146
+ def _normalize_runtime_item_metadata(connections: Any) -> dict[str, dict[str, str]]:
147
+ metadata: dict[str, dict[str, str]] = {}
148
+ if not isinstance(connections, dict):
149
+ return metadata
150
+ for item in _as_list(connections.get("items")):
151
+ if not isinstance(item, dict):
152
+ continue
153
+ item_name = (
154
+ item.get("item")
155
+ or item.get("name")
156
+ or item.get("source_item")
157
+ or ""
158
+ )
159
+ if not item_name:
160
+ continue
161
+ adapter_class_name = (
162
+ item.get("adapter_class_name")
163
+ or item.get("adapter")
164
+ or item.get("AdapterClassName")
165
+ or item.get("Adapter")
166
+ or ""
167
+ )
168
+ item_metadata: dict[str, str] = {}
169
+ if adapter_class_name:
170
+ item_metadata["adapter_class_name"] = str(adapter_class_name)
171
+ kind = item.get("kind") or item.get("type") or item.get("role") or ""
172
+ if kind:
173
+ item_metadata["kind"] = str(kind)
174
+ if item.get("iop") is not None:
175
+ item_metadata["iop"] = str(item.get("iop"))
176
+ module = item.get("module") or item.get("%module") or ""
177
+ if module:
178
+ item_metadata["%module"] = str(module)
179
+ classname = item.get("classname") or item.get("%classname") or ""
180
+ if classname:
181
+ item_metadata["%classname"] = str(classname)
182
+ classpaths = item.get("classpaths") or item.get("%classpaths") or ""
183
+ if classpaths:
184
+ item_metadata["%classpaths"] = str(classpaths)
185
+ if item_metadata:
186
+ metadata[str(item_name)] = item_metadata
187
+ return metadata
188
+
189
+
190
+ def _normalize_connection_target(value: Any) -> dict[str, Any] | None:
191
+ if isinstance(value, str):
192
+ if not value:
193
+ return None
194
+ return {"target": value, "source_target_setting": ""}
195
+ if not isinstance(value, dict):
196
+ return None
197
+ target = value.get("target") or value.get("name") or value.get("to") or ""
198
+ if not target:
199
+ return None
200
+ source_target_setting = (
201
+ value.get("source_target_setting")
202
+ or value.get("source_port")
203
+ or value.get("port")
204
+ or ""
205
+ )
206
+ metadata = dict(value.get("metadata") or {})
207
+ known_keys = {
208
+ "target",
209
+ "name",
210
+ "to",
211
+ "source_target_setting",
212
+ "source_port",
213
+ "port",
214
+ "interaction",
215
+ "metadata",
216
+ "logical_name",
217
+ }
218
+ for key, item in value.items():
219
+ if key not in known_keys and item is not None:
220
+ metadata.setdefault(str(key), item)
221
+ normalized: dict[str, Any] = {
222
+ "target": str(target),
223
+ "source_target_setting": str(source_target_setting),
224
+ }
225
+ if value.get("interaction"):
226
+ normalized["interaction"] = str(value["interaction"])
227
+ if metadata:
228
+ normalized["metadata"] = metadata
229
+ return normalized
230
+
231
+
232
+ def _normalize_queue_info(value: Any) -> tuple[dict[str, dict[str, Any]], list[str]]:
233
+ queue_map: dict[str, dict[str, Any]] = {}
234
+ warnings: list[str] = []
235
+ if value is None:
236
+ return queue_map, warnings
237
+
238
+ if isinstance(value, dict):
239
+ warnings.extend(str(item) for item in _as_list(value.get("warnings")) if item)
240
+ if "items" in value:
241
+ for item in _as_list(value.get("items")):
242
+ normalized = _normalize_queue_item(item)
243
+ if normalized is None:
244
+ continue
245
+ item_name, info = normalized
246
+ queue_map[item_name] = info
247
+ return queue_map, warnings
248
+
249
+ for item_name, info in value.items():
250
+ if item_name in ("production", "warnings"):
251
+ continue
252
+ if isinstance(info, dict):
253
+ queue_map[str(item_name)] = dict(info)
254
+ return queue_map, warnings
255
+
256
+ if isinstance(value, list):
257
+ for item in value:
258
+ normalized = _normalize_queue_item(item)
259
+ if normalized is None:
260
+ continue
261
+ item_name, info = normalized
262
+ queue_map[item_name] = info
263
+ return queue_map, warnings
264
+
265
+ warnings.append(f"Ignoring invalid queue info: {value!r}")
266
+ return queue_map, warnings
267
+
268
+
269
+ def _normalize_queue_item(value: Any) -> tuple[str, dict[str, Any]] | None:
270
+ if not isinstance(value, dict):
271
+ return None
272
+ item_name = value.get("item") or value.get("name") or value.get("queue_name") or ""
273
+ if not item_name:
274
+ return None
275
+ info = dict(value)
276
+ info.pop("item", None)
277
+ info.setdefault("queue_name", info.pop("name", item_name))
278
+ return str(item_name), info
279
+
280
+
281
+ def _setting_targets(value: Any) -> list[str]:
282
+ text = _text_value(value).strip()
283
+ if not text:
284
+ return []
285
+ return [part.strip() for part in text.split(",") if part.strip()]
286
+
287
+
288
+ def _matching_host_setting(component: Any, target_name: str) -> str:
289
+ for name, value in component.host_settings.items():
290
+ if target_name in _setting_targets(value):
291
+ return name
292
+ return ""
293
+
294
+
295
+ def _is_internal_runtime_target(target_name: str) -> bool:
296
+ return target_name in {"Ens.Alert", "Ens.ScheduleHandler"}